- 使用plus控件编写键盘模拟器
一、系统级键盘监控技术
1、键盘钩子(Keyboard Hook)
key.hook 库模块
var hk = key.hook(); // 安装全局键盘钩子
hk.proc = function(msg, vkCode, scanCode, injected, flags, timeStamp, extraInfo){
// 处理键盘事件
}
使用钩子技术实现系统级按键捕获。系统全局监控,包括所有应用程序.
2、消息类型识别
var isDown = (msg == 0x100/*_WM_KEYDOWN*/ || msg == 0x104/*_WM_SYSKEYDOWN*/);
var isUp = (msg == 0x101/*_WM_KEYUP*/ || msg == 0x105/*_WM_SYSKEYUP*/);
关键消息类型:
| 消息常量 | 十六进制值 | 说明 |
|---|---|---|
| WM_KEYDOWN | 0x100 | 普通按键按下 |
| WM_KEYUP | 0x101 | 普通按键释放 |
| WM_SYSKEYDOWN | 0x104 | 系统键按下(如Alt组合键) |
| WM_SYSKEYUP | 0x105 | 系统键释放 |
3. 注入事件过滤
if(injected) return; // 忽略程序自身发送的模拟按键
标题防循环触发机制:
- 防止程序模拟的按键再次触发钩子
- 避免无限递归调用
二、键盘数据解析
1. 虚拟键码(VK Code)处理
// 标准键码映射
var vkMap = {
[65] = "A", // 字母A
[13] = "Enter", // 回车键
[32] = "Space", // 空格键
// ... 其他键码
};
重要虚拟键码:
字母数字键:48-57 (0-9), 65-90 (A-Z)
- 功能键:112-123 (F1-F12)
- 控制键:8(Backspace), 9(Tab), 13(Enter), 27(Esc)
- 修饰键:16(Shift), 17(Ctrl), 18(Alt), 91(Win)
2. 扫描码(Scan Code)
// 根据扫描码可以识别具体物理按键
// 即使虚拟键码相同,扫描码也可能不同(如数字小键盘和主键盘区)
####扫描码特点:
- 与键盘硬件相关
- 同一键在不同布局下扫描码可能不同
- 可用于区分左右修饰键
3. 修饰键状态检测
// 需要特殊处理左右修饰键
if(vkCode == 16) { // 通用Shift键
// 需要转换为具体左Shift(160)或右Shift(161)
var isLeftShift = (flags & 0x0100) != 0;
var actualVK = isLeftShift ? 160 : 161;
}
左右修饰键的VK码:
| 通用键 | 左键 | 右键 |
|---|---|---|
| Shift (16) | 160 (VK_LSHIFT) | 161 (VK_RSHIFT) |
| Ctrl (17) | 162 (VK_LCONTROL) | 163 (VK_RCONTROL) |
| Alt (18) | 164 (VK_LMENU) | 165 (VK_RMENU) |
三、键盘状态标志位解析
1. flags参数解析
// 键盘事件的附加标志位
var extendedKey = (flags & 0x0001) != 0; // 扩展键标志
var contextCode = (flags & 0x2000) != 0; // Alt键按下标志
var previousState = (flags & 0x4000) != 0; // 之前的状态
var transitionState = (flags & 0x8000) != 0; // 状态转换
2. 扩展键识别
// 扩展键包括:
// 1. 右侧Ctrl和Alt键
// 2. Insert, Delete, Home, End, PageUp, PageDown
// 3. 方向键(↑↓←→)
// 4. 数字小键盘的Enter键
// 5. 数字小键盘的/键(在VK代码上与主键盘区不同)
四、键名转换技术
1. 虚拟键码到键名转换
var name = key.getName(vkCode);
// 内部实现通常使用查表法
function getKeyName(vkCode){
switch(vkCode){
case 8: return "Backspace";
case 9: return "Tab";
case 13: return "Enter";
// ...
}
}
2. 组合键表示
// 需要跟踪修饰键状态
var modifiers = {
ctrl: false,
shift: false,
alt: false,
win: false
};
// 当收到修饰键事件时更新状态
function updateModifiers(vkCode, isDown){
switch(vkCode){
case 162: case 163: modifiers.ctrl = isDown; break;
case 160: case 161: modifiers.shift = isDown; break;
case 164: case 165: modifiers.alt = isDown; break;
case 91: case 92: modifiers.win = isDown; break;
}
}
五、完整程序
//编写 键盘模拟器
import win.ui;
import win.ui.ctrl.plus;
import key.hook;
import key;
/*DSG{{*/
var winform = win.form(text="键盘模拟练习";right=828;bottom=414;bgcolor=0xFFFFFF;border="none")
winform.add(
bk={cls="plus";left=0;top=0;right=829;bottom=35;bgcolor=0xC0C0C0;dl=1;dr=1;dt=1;z=2};
display={cls="edit";left=31;top=57;right=797;bottom=108;align="center";edge=1;font=LOGFONT(h=-27);readonly=1;z=1};
keyboardBg={cls="plus";left=18;top=125;right=811;bottom=394;bgcolor=0xF0F0F0;db=1;dl=1;dr=1;dt=1;z=3};
title={cls="plus";text="键盘模拟器 - 请在键盘上按键测试";left=17;top=5;right=300;bottom=29;align="left";color=0x008000;font=LOGFONT(h=-14);z=4}
)
/*}}*/
// 定义键盘布局数据
var keyRows = {
{ {"ESC",1,1,27}; {"F1",2,1,112}; {"F2",3,1,113}; {"F3",4,1,114}; {"F4",5,1,115}; {"F5",6.5,1,116}; {"F6",7.5,1,117}; {"F7",8.5,1,118}; {"F8",9.5,1,119}; {"F9",11,1,120}; {"F10",12,1,121}; {"F11",13,1,122}; {"F12",14,1,123} };
{ {"~",1,2.5,192}; {"1",2,2.5,49}; {"2",3,2.5,50}; {"3",4,2.5,51}; {"4",5,2.5,52}; {"5",6,2.5,53}; {"6",7,2.5,54}; {"7",8,2.5,55}; {"8",9,2.5,56}; {"9",10,2.5,57}; {"0",11,2.5,48}; {"-",12,2.5,189}; {"=",13,2.5,187}; {"BACK",14.5,2.5,8,2} };
{ {"TAB",1.25,3.5,9,1.5}; {"Q",2.5,3.5,81}; {"W",3.5,3.5,87}; {"E",4.5,3.5,69}; {"R",5.5,3.5,82}; {"T",6.5,3.5,84}; {"Y",7.5,3.5,89}; {"U",8.5,3.5,85}; {"I",9.5,3.5,73}; {"O",10.5,3.5,79}; {"P",11.5,3.5,80}; {"[",12.5,3.5,219}; {"]",13.5,3.5,221}; {"\",14.75,3.5,220,1.5} };
{ {"CAPS",1.4,4.5,20,1.8}; {"A",2.8,4.5,65}; {"S",3.8,4.5,83}; {"D",4.8,4.5,68}; {"F",5.8,4.5,70}; {"G",6.8,4.5,71}; {"H",7.8,4.5,72}; {"J",8.8,4.5,74}; {"K",9.8,4.5,75}; {"L",10.8,4.5,76}; {";",11.8,4.5,186}; {"'",12.8,4.5,222}; {"ENTER",14.5,4.5,13,2.1} };
{ {"LSHIFT",1.65,5.5,160,2.3}; {"Z",3.3,5.5,90}; {"X",4.3,5.5,88}; {"C",5.3,5.5,67}; {"V",6.3,5.5,86}; {"B",7.3,5.5,66}; {"N",8.3,5.5,78}; {"M",9.3,5.5,77}; {",",10.3,5.5,188}; {".",11.3,5.5,190}; {"/",12.3,5.5,191}; {"RSHIFT",14.4,5.5,161,2.8} };
{ {"LCTRL",1.4,6.5,162,1.8}; {"WIN",2.6,6.5,91}; {"LALT",3.6,6.5,164}; {"SPACE",8.2,6.5,32,7.4}; {"RALT",12.8,6.5,165}; {"FN",13.8,6.5,0xFF}; {"RCTRL",15,6.5,163,1.6} }
};
var keyMap = {}; // 存储按键对应的 plus 控件
var unit = 50; // 基准宽度
var margin = 5; // 间距
// 动态创建模拟键盘
for(i=1;#keyRows){
var row = keyRows[i];
for(j=1;#row){
var info = row[j];
var text, posX, posY, vkCode, weight = table.unpack(info);
if(!weight) weight = 1;
var btn = winform.keyboardBg.add(
cls="plus";
text=text;
left=(posX-1) * unit + margin;
top=(posY-1) * unit + margin;
right=(posX-1) * unit + weight * unit - margin;
bottom=(posY-1) * unit + unit - margin;
bgcolor=0xFFFFFF;
border={radius=4;width=1;color=0xFFCCCCCC};
align="center";
valign="center";
z=j;
)
if(vkCode) keyMap[vkCode] = btn;
}
}
// 模拟按键效果函数
var highlightKey = function(vkCode, isDown){
var btn = keyMap[vkCode];
if(!btn){
// 处理左右修饰键通用码
if(vkCode == 16) btn = keyMap[160] || keyMap[161];
if(vkCode == 17) btn = keyMap[162] || keyMap[163];
if(vkCode == 18) btn = keyMap[164] || keyMap[165];
}
if(btn){
btn.color = isDown? 0xFFFFFFFF:0x00000000;
}
}
// 键盘钩子监听
var hk = key.hook();
hk.proc = function(msg, vkCode, scanCode, injected, flags, timeStamp, extraInfo){
if(injected) return;
var isDown = (msg == 0x100/*_WM_KEYDOWN*/ || msg == 0x104/*_WM_SYSKEYDOWN*/);
var isUp = (msg == 0x101/*_WM_KEYUP*/ || msg == 0x105/*_WM_SYSKEYUP*/);
if(isDown){
highlightKey(vkCode, true);
// 更新上方显示框
var name = key.getName(vkCode);
if(#name > 1){
winform.display.text = "[" + name + "]";
}
else {
winform.display.text = name;
}
}
elseif(isUp){
highlightKey(vkCode, false);
}
}
// 标题栏拖动与关闭
import win.ui.simpleWindow;
win.ui.simpleWindow(winform);
winform.show();
win.loopMessage();
六、运行界面
