前几天用Finale打谱需要用到小键盘的数字键,然后发现Fn键加uio等字母按不出来数字,实际出来的是NumLk没有打开情况下的方向键与PgUp和PgDn等。如果打开键盘的NumLk,按字母键可以打出数字,但我需要的是Fn直接能打出小键盘数字,这样在许多地方才方便,之前用过NEC、DELL的笔记本都能做到。
很容易可以想到,之所以会打出方向键是因为Windows认为NumLk在关着。开始并没觉得这个问题很严重,就去问了下谷哥,参考到这篇帖子:
http://acer.it168.com/thread-2136449-1-1.html
该帖子提到可以通过按外接键盘上的NumLk来激活Windows的NumLk,而通常笔记本的NumLk只是用于按字母键时输入小键盘的扫描码,与操作系统的NumLk没有关系。也就是说,当电脑收到小键盘的扫描码时,再根据是否打开了NumLk来转换为虚拟键码告诉应用程序是输入数字还是输入方向。不过我手头没有外接键盘,然后帖子又说,没有外接键盘可以打开Windows的屏幕键盘也就是osk.exe然后点一下那个NumLk键,这样就能解决问题。
然而我试了之后发现按字母就直接成了数字,也就是说当我按键盘上的NumLk时,系统的NumLk也会打开,当我点击系统的NumLk时键盘的NumLk也会打开。这种情况说明我的键盘在硬件设计上就有问题,在硬件设计上混淆了笔记本键盘的NumLk功能和101键盘的NumLk的本质,多此一举地把它们绑定了。
正确的笔记本键盘按下NumLk键的作用应该是将字母与Fn+字母所发送的扫描码颠倒,而这一操作和系统无关。
而我的键盘按下NumLk会向系统发送101键盘的NumLk键扫描码,系统收到NumLk按键就设置自己的NumLk状态,然后向键盘发送设置101键盘右上角的三个指示灯的指令。然后我的键盘收到NumLk灯亮起的指令后就打开了物理上的NumLk,造成字母与Fn字母反转。
了解到这样的过程后就有解决思路了。第一,拦截按下NumLk键的消息,然后向键盘发送指示灯命令,而不把这个消息告诉操作系统。第二,拦截操作系统的指示灯命令,将其中NumLk灯的数据改为第一个方面中物理键盘应该的数。
显然这个需要写驱动。个人觉得可以考虑写个PS/2键盘驱动来搞定,然而我不大会写驱动,也不想为了个期末作业去折腾各种的蓝屏,毕竟已经转行了。鉴于PS/2可以通过操作60H、64H端口来控制,所以下面给出的实现方案为基于WinIO的端口程序,供大家参考。
#include <windows.h> #include <stdio.h> #pragma comment(lib,"user32.lib") bool bExitFlag = false; bool (__stdcall* InitializeWinIo)() = NULL; bool (__stdcall* ShutdownWinIo)() = NULL; bool (__stdcall* GetPortVal)(WORD, PDWORD, BYTE) = NULL; bool (__stdcall* SetPortVal)(WORD, DWORD, BYTE) = NULL; bool bNumLk = false; WNDPROC oldWndProc = NULL; BYTE PortIn(WORD addr) { DWORD bVal; GetPortVal(addr, &bVal, 1); return (BYTE)bVal; } void PortOut(WORD addr, BYTE data) { SetPortVal(addr, data, 1); } //BYTE WaitForKbRead() //{ // BYTE bVal = 0; // do // { // Sleep(10); // bVal = PortIn(0x64); // } // while (!(bVal & 1)); // return bVal; //} BYTE WaitForKbWrite() { BYTE bVal; do { //Sleep(10); bVal = PortIn(0x64); } while (bVal & 2); return bVal; } void CloseKbInt() { WaitForKbWrite(); PortOut(0x64, 0x60); WaitForKbWrite(); PortOut(0x60, 0x46); } void OpenKbInt() { WaitForKbWrite(); PortOut(0x64, 0x60); WaitForKbWrite(); PortOut(0x60, 0x47); } void WriteKey(DWORD keyCode) { WaitForKbWrite(); SetPortVal(0x64, 0xD2, 1); WaitForKbWrite(); SetPortVal(0x60, keyCode, 1); } //DWORD ReadKey() //{ // DWORD keyCode = 0; // while (!bExitFlag && WaitForKbRead() & 0x20) // Sleep(10); //mouse // GetPortVal(0x60, &keyCode, 1); // return keyCode; //} void SetLed(BYTE led) { WaitForKbWrite(); PortOut(0x60, 0xED); WaitForKbWrite(); PortOut(0x60, led); } void CheckLock() { static int lastLed = -1; int led = 0; if ((GetKeyState(0x14) & 0xffff) != 0) led |= 4; if ((GetKeyState(0x90) & 0xffff) != 0) led |= 0x100; if ((GetKeyState(0x91) & 0xffff) != 0) led |= 1; if (bNumLk) led |= 2; if (led != lastLed) { lastLed = led; SetLed(led & 0x00000007); } } int MainLoop() { if (!bExitFlag) { BYTE kbc = PortIn(0x64); if (kbc & 1 && !(kbc & 0x20)) { BYTE key = PortIn(0x60); OpenKbInt(); switch (key) { case 0xC5: bNumLk = !bNumLk; case 0x45: //num lock break; default: WriteKey(key); } Sleep(1); CheckLock(); CloseKbInt(); } Sleep(1); } return 0; } LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CLOSE: bExitFlag = true; //PostQuitMessage(0); break; } return CallWindowProc(oldWndProc, hWnd, message, wParam, lParam); } int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HMODULE hModule = LoadLibraryA("WinIo32.dll"); InitializeWinIo = (bool(__stdcall*)())GetProcAddress(hModule, "InitializeWinIo"); ShutdownWinIo = (bool(__stdcall*)())GetProcAddress(hModule, "ShutdownWinIo"); GetPortVal = (bool(__stdcall*)(WORD, PDWORD, BYTE))GetProcAddress(hModule, "GetPortVal"); SetPortVal = (bool(__stdcall*)(WORD, DWORD, BYTE))GetProcAddress(hModule, "SetPortVal"); HWND hWnd = CreateWindowA((LPSTR)32770, "KeyMon", WS_OVERLAPPEDWINDOW, 0, 0, 100, 100, NULL, NULL, hInstance, NULL); //ShowWindow(hWnd, nCmdShow); //UpdateWindow(hWnd); oldWndProc = (WNDPROC)SetWindowLongA(hWnd, GWL_WNDPROC, (LONG)&WinProc); bool bRet = InitializeWinIo(); CloseKbInt(); MSG msg; while (!bExitFlag) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { MainLoop(); } } OpenKbInt(); bRet = ShutdownWinIo(); return msg.wParam; }
因为有涉及关键盘中断,所以该程序一定要通过窗口消息关闭,而万万不可结束进程!否则,嘿嘿嘿!
参考文章: