Keyboard驱动介绍

USB Optical Mouse/USB Keyboard驱动问题 阅读详情

一.Keyboard Driver的加载过程

         系统启动过程中,GWES注册表HKEY_LOCAL_MACHINE/Hardware/DeviceMap/KEYBD 下的”Drivername”下去获取Keyboard Driver的名字,如果没找到,则使用默认的名字Keybddr.dll。

         加载的大概过程如下:

         首先GWES会去验证Keyboard Driver的导出接口是否存在;

         接下来去调用导出函数KeybdDriverInitializeEx(),对Keyboard Driver进行初始化。

二.有关Keyboard的几个概念

1.Device Layout

         以PC机键盘为例,有的键盘是101键,有的是108键(呵呵,记不清楚了,反正是有按键比较多的键盘),从外观上来看,它们的按键个数和键位不同,从功能上看,按键比较多的键盘支持更多的功能,其实这就是Keyboard Layout的含义。

         对于相同Layout不同厂家生产的键盘,相同按键产生的Scan Code必须是相同的,这也是PC机在不用装驱动的情况下可以支持任何市面上见到的键盘。

         对于WinCE系统而言,为了支持市面上常见的PC键盘,也需要支持这些标准的Layout,这也是WinCE中存在很多标准Layout的根本原因。

         具体到功能来说,Device Layout的功能就是负责Scan Code和Virtual Code的转换,以及Virtual Code的Remapping。比如,键盘上左边有一个Shift键,右边也有一个Shift键,它们的Scan Code和Virtual code不一样的,但是可以通过Remapping把它们的Virtual Code进行Remapping,转化为一样的Virtual Code。

         系统中的Layout都作了哪些工作,引用Help文档中的内容来进行说明:

The following list shows, in sequence, how the Layout Manager handles scan codes:

1.              PDD receives a scan code.

2.              PDD sends the scan code to the Layout Manager.

3.              Layout Manager converts the scan code to a virtual-key code based on the keyboard that sent the event and its current device layout.

4.              Layout Manager remaps the scan code based on the keyboard that sent the event and its current device layout.

5.              Layout Manager handles the auto-repeat functionality.

All keyboards share the same auto-repeat settings.

6.              Layout Manager calls keybd_event to send an event or events.

         Layout在注册表中的位置HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control /Layouts/{Layout name},观察会发现所有Layout下的DLL是同一个,这是因为默认情况下,DLL中包含了所有的Layout。

         另外,输入法也是要在HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control /Layouts/{Layout name}记录的DLL以及UI等信息,但实际上,并不是输入法真正的Layout,它实际使用的Layout是在通过注册表项"Keyboard Layout"指出来,例如双拼的注册表配置如下:

; Make your own IMEUI dll and change "UI Module" field to your dll name.

; Only need to export one API: void CALLBACK ImeGetUIClassName(LPTSTR);

; IME will call this API to copy your UI class name.

[HKEY_LOCAL_MACHINE/System/CurrentControlSet/Control/Layouts/e0010804]

; @CESYSGEN IF WCESHELLFE_MODULES_INTLL

; @CESYSGEN ENDIF

; @CESYSGEN IF WCESHELLFE_MODULES_INTLP

    "Layout Display Name"="@//Windows//intlp.cpl,-20483"

; @CESYSGEN ENDIF

    "Layout Text"="Microsoft CHS ShuangPin IME"

    "Ime File"="msimesp.dll"

    "UI Module"="msimeuic.dll"

    "Keyboard Layout"="00000409"

2.Input Language

         在Virtual Code和Unicode之间进行转换。

         Input Language的入口函数名字是:IL_{layout序号}。

3.Local

         引用Help文档来进行说明:

Pairing of an input language with an input method. The input locale identifier is a number. For example, the input locale identifier for a standard United States 101 keyboard is 00000409. The low word is the language identifier and the high word is a type identifier. The input locale identifier for a Dvorak keyboard is 00010409.

An input locale and an input locale handle differ. Once an input locale is loaded, Layout Manager generates a handle to an input locale (HKL) for the input locale that can be used with the keyboard APIs.

         注册表HKEY_CURRENT_USER/Keyboard Layout/Preload下指定了默认的Local,而HKEY_CURRENT_USER/Keyboard Layout/Preload/{number no more than 15}指出了可用的Local。

三.Keyboard中相关信息的获取方式

1.MDD层如何获取Device Layout信息

         理论上,Layout Manager可以管理多个PDD层,这些PDD层会组成一个链表,并用全局变量g_rgpfnPddEntries来表示所有PDD层的入口,在目前我们的项目上其值为:

PFN_KEYBD_PDD_ENTRY g_rgpfnPddEntries[] =

{

         PS2_NOP_Entry, Matrix_Entry, NULL

};

         GWES调用函数KeybdDriverInitializeEx()初始化Keyboard Driver的时候,会去引用PDD层的入口函数,获取到KEYBD_PDD后将其填充到MDD层的指针变量g_pPdds指向的KEYBD_PDD_INFO链表中,链表的长度取决于PDD层入口的个数。

         下面看一下KEYBD_PDD_INFO的定义:

typedef struct tagKEYBD_PDD_INFO {

    BOOL                fValid;

    PKEYBD_PDD          pKeybdPdd;

    DEVICE_LAYOUT_INFO  dli;

} KEYBD_PDD_INFO, *PKEYBD_PDD_INFO;

         其成员pKeybdPdd就指向了从PDD层获取的KEYBD_PDD,接下来看一下KEYBD_PDD的定义:

typedef struct tagKEYBD_PDD {

    WORD wPddMask; // Matches the keyboard layout with its PDD

    LPCTSTR pszName; // Used to identify PDD to user

    PFN_KEYBD_PDD_POWER_HANDLER pfnPowerHandler;

    PFN_KEYBD_PDD_TOGGLE_LIGHTS pfnToggleLights;

} KEYBD_PDD, *PKEYBD_PDD;

         有必要指出的是其第一个成员wPddMask,后面从注册表中查询Keyboard入口并获取Device Layout的时候,需要利用它判断系统中存在的所有Device Layout并找到最合适的一个。

         好了,接下来我们看一下函数KeybdDriverInitializeEx()的代码。

//----------------------------------------------------------------------------

//

// KeybdDriverInitializeEx

//

// Initializes the layout manager and any keyboard PDDs.

//

//----------------------------------------------------------------------------

extern "C"

void

KeybdDriverInitializeEx(

                        PFN_KEYBD_EVENT_CALLBACK_EX pfnKeybdEventCallbackEx // @parm The callback into the input system.

                        )

{

    SETFNAME(_T("KeybdDriverInitializeEx"));

   

    HKL hkl;

    PFN_KEYBD_PDD_ENTRY *ppfnKeybdPddEntry;

    UINT uiCurrPdd;

    TCHAR szDefaultName[KL_NAMELENGTH]= _T("");

   

    PREFAST_DEBUGCHK(pfnKeybdEventCallbackEx != NULL);

   

    InitializeCriticalSection(&g_csAccessInputLocaleInfo);

    LockConfig();

   

    // Set up critical sections, threads, events, etc.

    InitializeCriticalSection(&g_csEventCallback);

    g_hevBeginEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    g_hevEndEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    g_hevExitNotficationThread = CreateEvent(NULL, FALSE, FALSE, NULL);

    g_hEventThread = CreateThread(NULL, 0, KeybdEventThreadProc, NULL, 0, NULL);

    HANDLE hNotificationThread = CreateThread(NULL, 0, KbdNotificationThread, NULL, 0, NULL);

    CloseHandle(hNotificationThread); // We do not need this handle anymore

   

    if ((g_hevBeginEvent == NULL) ||

        (g_hevEndEvent == NULL) ||

        (g_hevExitNotficationThread == NULL) ||

        (g_hEventThread == NULL))

    {

        ERRORMSG(1, (_T("Unable to set up keyboard layout manager/r/n")));

        if (g_hevBeginEvent != NULL) CloseHandle(g_hevBeginEvent);

        if (g_hevEndEvent != NULL) CloseHandle(g_hevEndEvent);

        if (g_hevExitNotficationThread != NULL) CloseHandle(g_hevExitNotficationThread);

        if (g_hEventThread != NULL) CloseHandle(g_hEventThread);

        DeleteCriticalSection(&g_csEventCallback);

       

        goto EXIT;

    }

   

    // Initialize our PDD data structures

    // Get a count of PDDs

    PREFAST_DEBUGCHK(g_rgpfnPddEntries != NULL);

    DEBUGCHK(g_cPdds == 0);

 

    // 获取PDD层的全局变量g_rgpfnPddEntries

    ppfnKeybdPddEntry = &g_rgpfnPddEntries[0];

 

    // 计算PDD层总共提供了多少个Entry,意思应该是说可以支持多个Entry,即支持多个键盘

    // PDD层的总数是g_cPdds

    while (*ppfnKeybdPddEntry != NULL) {

        ++g_cPdds;

        ++ppfnKeybdPddEntry;

    }

   

    // Allocate our PDD list

    // 在MDD层分配空间对PDD List进行管理

    DEBUGCHK(g_pPdds == NULL);

    g_pPdds =

        (PKEYBD_PDD_INFO) LocalAlloc(LPTR, sizeof(KEYBD_PDD_INFO) * g_cPdds);

    if (g_pPdds == NULL) {

        ERRORMSG(1, (_T("Out of memory/r/n")));

        goto EXIT;

    }   

   

    // Initialize each PDD

    // 通过引用pdd层的全局变量g_rgpfnPddEntries来调用pdd层的所有初始化函数

    // 另外,通过调用pdd层的初始化函数从pdd层获取获取KEYBD_PDD变量的值,并放到g_pPdds中

    for (uiCurrPdd = 0; uiCurrPdd < g_cPdds; ++uiCurrPdd)

    {

        // 获取刚才为管理PDD List而分配空间,第uiCurrPdd部分

        PKEYBD_PDD_INFO pKeybdPddInfo = pKeybdPddInfoFromId(uiCurrPdd);

       

        // 调用PDD层的第uiCurrPdd个Entry

        // 其中一个目的是获取PDD层的PKEYBD_PDD,一个典型的PKEYBD_PDD如

        //static KEYBD_PDD PS2NOPPdd = {

        //  PS2_NOP_PDD,

        //_T("PS/2 NOP"),

        //NULL, // Don't give the layout manager any PDD function pointers

        //NULL

        //};

        BOOL fNoErr = (*g_rgpfnPddEntries[uiCurrPdd])

            (uiCurrPdd, KeybdEventCallback, &pKeybdPddInfo->pKeybdPdd);

       

        if (fNoErr == FALSE) {

            // Something bad happened during PDD initialization. Mark it

            // as invalid.

            ERRORMSG(1, (_T("Keyboard: PDD %u initialization failed/r/n"),

                uiCurrPdd));           

            pKeybdPddInfo->fValid = FALSE;

            continue;

        }

       

        DEBUGCHK(pKeybdPddInfo->pKeybdPdd != NULL);

       

        PKEYBD_PDD pKeybdPdd = pKeybdPddInfo->pKeybdPdd;

       

        if (ValidateKeybdPdd(pKeybdPdd) == FALSE) {

            ERRORMSG(1, (_T("Keyboard: Invalid keybd PDD information for PDD %u/r/n"),

                uiCurrPdd));

            pKeybdPddInfo->fValid = FALSE;

            continue;

        } 

       

        pKeybdPddInfo->fValid = TRUE;

       

        DEBUGCHK(pKeybdPdd->pszName != NULL);

        DEBUGMSG(ZONE_INIT, (_T("%s: Initialized PDD %u - %s (Mask %u)/r/n"),

            pszFname, uiCurrPdd, pKeybdPdd->pszName, pKeybdPdd->wPddMask));

    }

   

    // Get the default input method name

    // 从HKEY_CURRENT_USER/Keyboard Layout/Preload下获取默认的layout的名字,形如"00000409"

     // 并从HKEY_CURRENT_USER/Keyboard Layout/Preload/{number}下判断是否属于有效的Layout名称

    if (GetDefaultInputMethodName(szDefaultName) == FALSE)

    {

        ERRORMSG(1, (_T("Keyboard: Default input method set up improperly. ")

            _T("Keyboard will not function correctly./r/n")));

        goto EXIT;

    }

   

    DEBUGMSG(ZONE_INIT, (_T("%s: Found default input method %s/r/n"),

        pszFname, szDefaultName));

   

    // Converts an input method name to an input method handle

    hkl = SzToHkl(szDefaultName);

   

    // 获取Keyboard的INPUT_LANGUAGE,此处调用的是IL_00000409

    if (SetInputLanguage(hkl, NULL) == FALSE) {

        ERRORMSG(1,

            (_T("Keyboard: Could not activate the default input method %s. ")

            _T("Keyboard will not function correctly./r/n"),

            szDefaultName));

        goto EXIT;

    }

   

    // Initialize each PDD's Device Layout

    for (uiCurrPdd = 0; uiCurrPdd < g_cPdds; ++uiCurrPdd)

    {

        // 啥也不说了,此处返回的就是PDD层的KEYBD_PDD全局变量

        PKEYBD_PDD pKeybdPdd = pKeybdPddFromId(uiCurrPdd);

       

        // 获取DEVICE_LAYOUT,J9项目上调用的是Matrix

        if (SetDeviceLayout(hkl, uiCurrPdd) == FALSE) {

            ERRORMSG(1,

                (_T("Keyboard: Error selecting device layout for PDD %s/r/n"),

                pKeybdPdd->pszName));

        }

    }

   

    DEBUGMSG(ZONE_INIT, (_T("%s: Layout Manager successfully initialized/r/n"),

        pszFname));

       

    RETAILMSG(1, (_T("%s: Layout Manager successfully initialized to  %d/r/n"),

        pszFname, g_cPdds));

   

EXIT:

    // Even if there was a failure, we still must send a

    // KEYBD_DEVICE_CONNECT so that GWES will allocate required buffers.

    (*pfnKeybdEventCallbackEx)(KEYBD_DEVICE_CONNECT, 0, 0);

    UnlockConfig();

    return;

}

         在获取Device Layout的时候,调用了函数SetDeviceLayout(),该函数用来从OEM代码那里获取Device Layout,但是如果OEM的代码没有提供的话,会从MDD中自带的那些Device Layout中获取到(可能没有说清楚,详细可以看一下代码)。

         函数SetDeviceLayout()的说明如下:

//----------------------------------------------------------------------------

//

// SetDeviceLayout

//

// Set the device layout for the given PDD to hkl.

//

//----------------------------------------------------------------------------

static

BOOL

SetDeviceLayout(

                HKL hkl,

                UINT uiPdd

                )

{

    SETFNAME(_T("SetDeviceLayout"));

   

    DEBUGCHK(uiPdd < g_cPdds);

    LockConfig();

   

    BOOL fRet = FALSE;

    BOOL fTemp;

    PFN_DEVICE_LAYOUT_ENTRY pfnDLEntry;

    DEVICE_LAYOUT_INFO dli;

    TCHAR szHkl[KL_NAMELENGTH];

    PKEYBD_PDD_INFO pPddInfo = pKeybdPddInfoFromId(uiPdd);

   

    PREFAST_DEBUGCHK(pPddInfo != NULL);

   

    memset(&dli, 0, sizeof(dli));

    dli.dl.dwSize = sizeof(dli.dl);

   

    if (IsHklIme(hkl) == TRUE) {

        hkl = GetImeKeyboardLayout(hkl);

       

        if (hkl == 0) {

            goto leave;

        }

    }

   

    HklToSz(hkl, szHkl);

 

    // pPddInfo->pKeybdPdd非常有必要,后续会用来判断mask等

    // 函数SelectDeviceLayout()就是用来获取dli.szDll和layout 入口函数dli.szProcName

    if (SelectDeviceLayout(szHkl, pPddInfo->pKeybdPdd,

        dli.szDll, dim(dli.szDll),

        dli.szProcName, dim(dli.szProcName)) == FALSE)

    {

        DEBUGMSG(ZONE_ERROR, (_T("%s: Could not find match PDD %u device ")

            _T("layout match for 0x%s/r/n"), pszFname, uiPdd, szHkl));

        goto leave;

    }

   

    // Assign the hkl now.

    dli.hkl = SzToHkl(szHkl);

   

     // 从dli.szDll获取入口函数dli.szProcName的地址,并保存到函数指针pfnDLEntry中

    if (GetDeviceLayoutEntry(dli.szDll, dli.szProcName, &dli.hDll, &pfnDLEntry)

        == FALSE)

    {

        DEBUGMSG(ZONE_ERROR, (_T("%s: Unable to load device layout %s - %s:%s/r/n"),

            pszFname, szHkl, dli.szDll, dli.szProcName));

        goto leave;

    }

   

    DEBUGCHK(dli.hDll != NULL);

    PREFAST_DEBUGCHK(pfnDLEntry != NULL);

 

    // 我考,终于获得了PDD层的DEVICE_LAYOUT

    __try {

        fTemp = (*pfnDLEntry)(&dli.dl);

    }

    __except(EXCEPTION_EXECUTE_HANDLER) {

        fTemp = FALSE;

        DEBUGMSG(ZONE_ERROR, (_T("%s: Exception in entry function %s/r/n"),

            pszFname, dli.szProcName));       

    }

   

    if (fTemp == FALSE) {

        DEBUGMSG(ZONE_ERROR,

            (_T("%s: Device layout entry function failed/r/n"),

            pszFname));

        FreeLibrary(dli.hDll);

        goto leave;

    }

   

    if (ValidateDeviceLayout(&dli.dl) == FALSE) {

        ERRORMSG(1, (_T("Invalid device layout information for %s/r/n"),

            szHkl));

        FreeLibrary(dli.hDll);

        goto leave;

    }

   

    // Determine the necessary remapping buffer size

    DEBUGCHK(dli.pKbdEvents == 0);

    if (dli.dl.pfnRemapKey) {

        dli.cMaxRmpKbdEvents = CallDLRemapFn(dli.dl.pfnRemapKey,

            NULL, ALTGR_MAPPING, NULL, 0); // How many will our events map to?

       

        if (dli.cMaxRmpKbdEvents > dim(dli.rgKbdEvent)) {

            // Use a dynamically allocated buffer

            dli.pKbdEvents = (KEYBD_EVENT*)

                LocalAlloc(0, sizeof(*dli.pKbdEvents) * dli.cMaxRmpKbdEvents);

           

            if (dli.pKbdEvents == NULL) {

                ERRORMSG(1, (_T("Out of memory/r/n")));

                FreeLibrary(dli.hDll);

                goto leave;

            }

        }

    }

   

    // If we made it here, then all is good. Really change device layouts.

    // Free the old input language library and data.

    if (pPddInfo->dli.hDll) FreeLibrary(pPddInfo->dli.hDll);

    if (pPddInfo->dli.pKbdEvents) LocalFree(pPddInfo->dli.pKbdEvents);

   

    pPddInfo->dli = dli;

   

    fRet = TRUE;

   

leave:

    UnlockConfig();

    return fRet;

}

         函数SelectDeviceLayout()会去判断Device Layout的Mask标记,这里决定Device Layout究竟从哪里获取,代码解释如下:

//----------------------------------------------------------------------------

//

// SelectDeviceLayout

//

// Determines the proper device layout DLL and procedure name that goes with

// pszHkl for the given PDD.

// 选取Layout方式,通过dll的导出函数,所谓的Layout就是VK和Scan Key的映射表格

// pszHkl: 类似于[in]

// pKeybdPdd: 就是PDD层的全局变量[in]

// pszDll: driver的名字[out]

// pszProcName: 返回pszDll入口的名字[out] 类似于PS2_AT_00000409,就是dll的导出函数

//----------------------------------------------------------------------------

static

BOOL

SelectDeviceLayout(

                   LPTSTR pszHkl,

                   PKEYBD_PDD pKeybdPdd,

                   LPTSTR pszDll,

                   DWORD  cchDll,

                   LPTSTR pszProcName,

                   DWORD  cchProcName

                   )

{

    SETFNAME(_T("SelectDeviceLayout"));

   

    DEBUGCHK(pszHkl != NULL);

    PREFAST_DEBUGCHK(pKeybdPdd != NULL);

    DEBUGCHK(pszDll != NULL);

    DEBUGCHK(pszProcName != NULL);

    DEBUGCHK(cchDll >= MAX_PATH);

   

    BOOL fRet = FALSE;

    BOOL fMatch = FALSE;

    HKEY hkeyLayoutParent = NULL;

    HKEY hkeyDeviceLayout = NULL;

    DWORD dwIdx = 0;

    TCHAR szValueName[DL_PROC_PREFIXLENGTH];

    DWORD cchValueName = dim(szValueName);

    DWORD dwType;

    TCHAR szValueData[MAX_PATH];

    DWORD cchValueData = dim(szValueData) * sizeof(TCHAR);

   

    // 靠,必须得,获取HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Layouts的Handle

    if (OpenKeyboardLayoutsKey(&hkeyLayoutParent) == FALSE) {

        DEBUGMSG(ZONE_ERROR, (_T("%s: Could not open %s key/r/n"),

            pszFname, g_szRegLayouts));

        goto leave;

    }

   

    // 获取HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Layouts/pszHkl的Handle hkeyDeviceLayout

    if (OpenKey(hkeyLayoutParent, pszHkl, &hkeyDeviceLayout) == FALSE) {

        DEBUGMSG(ZONE_ERROR, (_T("%s: No device layout subkey found for %s/r/n"),

            pszFname, pszHkl));

        goto leave;

    }

   

    // Go through each listed device layout for this hkl and see if it

    // matches the PDD. Return the first matching PDD.

    while (RegEnumValue(hkeyDeviceLayout, dwIdx, szValueName, &cchValueName,

        NULL, &dwType, (PBYTE)szValueData, &cchValueData) == ERROR_SUCCESS)

    {   

        szValueName[dim(szValueName) - 1] = _T('/0');

        szValueData[dim(szValueData) - 1] = _T('/0');

       

        if ((dwType == REG_SZ) &&

            (_tcsncmp(szValueName, g_szLayoutTextValue, cchValueName) != 0) &&

            (_tcsncmp(szValueName, g_szLayoutFileValue, cchValueName) != 0))

        {

            // szValueName: "PS2_AT"  szValueData: "kbdmouse.dll" pszHkl:"00000409"

            // 主要作用就是获取MDD层的Layout信息,同时将入口函数名字放到pszProcName中

            // 这里szValueName的值根据注册表配置可能有多个,具体哪个值对应的入口可以获取到

            // 正确的DEVICE LAYOUT,要靠pKeybdPdd->wPddMask于调用函数接口获取的mask对比才知道

            // 一般情况下,对于在嵌入式设备上定制的很少按键的键盘,这里就是通过对比mask找到

            // oem厂商对应的device layout

            if ( DeviceLayoutMatchesPDD(pszHkl, szValueName, szValueData,

                pKeybdPdd->wPddMask, pszProcName, cchProcName) ) {

                fMatch = TRUE;

                break;

            }

        }

       

        cchValueName = dim(szValueName);

        cchValueData = dim(szValueData) * sizeof(TCHAR);

        ++dwIdx;

    }

   

    if (fMatch == FALSE) {

        DEBUGMSG(ZONE_ERROR,

            (_T("%s: Could not find a device layout match for %s and 0x%s/r/n"),

            pszFname, pKeybdPdd->pszName, pszHkl));

        goto leave;

    }

   

    DEBUGMSG(ZONE_DEVICELAYOUT,

        (_T("%s: Found device layout match for %s and 0x%s: %s - %s/r/n"),

        pszFname, pKeybdPdd->pszName, pszHkl, szValueName, szValueData));

   

    // 将dll的名字放到pszDll中返回

    _tcsncpy(pszDll, szValueData, cchDll);

    pszDll[cchDll - 1] = _T('/0');

   

    fRet = TRUE;

   

leave:

    if (hkeyLayoutParent != NULL) RegCloseKey(hkeyLayoutParent);

    if (hkeyDeviceLayout != NULL) RegCloseKey(hkeyDeviceLayout);   

   

    return fRet;

}

         上面调用了函数DeviceLayoutMatchesPDD(),其解释如下:

//----------------------------------------------------------------------------

//

// DeviceLayoutMatchesPDD

//

// Will this device layout work with a PDD that has a mask of wPddMask?

//

// Each device layout could match multiple PDD types.

//

//----------------------------------------------------------------------------

static

BOOL

DeviceLayoutMatchesPDD(

                       LPCTSTR pszHkl,

                       LPCTSTR pszProcPrefix,

                       LPCTSTR pszDll,

                       WORD wPddMask,

                       LPTSTR pszProcName,

                       DWORD  cchProcName

                       )

{

    SETFNAME(_T("DeviceLayoutMatchesPDD"));

   

    DEBUGCHK(pszHkl != NULL);

    DEBUGCHK(pszDll != NULL);

    DEBUGCHK(pszProcPrefix != NULL);

    DEBUGCHK(wPddMask != NULL);

    DEBUGCHK(cchProcName >= DL_PROC_NAMELENGTH);

   

    BOOL fMatch = FALSE;

    BOOL fTemp;

    HINSTANCE hDll = NULL;

    PFN_DEVICE_LAYOUT_ENTRY pfnDLEntry;

    DEVICE_LAYOUT dl;

    TCHAR szFullProcName[DL_PROC_NAMELENGTH];

    LPCTSTR rgpszProcNames[] = { szFullProcName, pszProcPrefix };

    DWORD dwIdx;

   

    // 将pszHkl+pszProcPrefix组合起来放到szFullProcName中

    // J9上实际就组合成了PS2_AT_00000409

    FormDeviceLayoutEntry(pszHkl, pszProcPrefix, szFullProcName, dim(szFullProcName));

    dl.dwSize = sizeof(dl);

   

    for (dwIdx = 0; dwIdx < dim(rgpszProcNames); ++dwIdx) {

        LPCTSTR pszCurrProcName = rgpszProcNames[dwIdx];

       

        // 到"kbdmouse.dll"中获取PS2_AT_00000409函数入口

        // 该函数主要可以来实现从MDD层中获取DEVICE_LAYOUT信息

        if (GetDeviceLayoutEntry(pszDll, pszCurrProcName, &hDll,

            &pfnDLEntry) == TRUE)

        {

            DEBUGCHK(hDll != NULL);

            PREFAST_DEBUGCHK(pfnDLEntry != NULL);

           

            __try {

                // 调用入口函数

                fTemp = (*pfnDLEntry)(&dl);

            }

            __except(EXCEPTION_EXECUTE_HANDLER) {

                fTemp = FALSE;

                DEBUGMSG(ZONE_ERROR, (_T("%s: Exception in entry function %s/r/n"),

                    pszFname, pszCurrProcName));       

            }

           

            if (fTemp == FALSE) {

                DEBUGMSG(ZONE_ERROR,

                    (_T("%s: Error in device layout entry function %s/r/n"),

                    pszFname, pszCurrProcName));

            }

            else

            {

                if ((wPddMask & dl.wPddMask) != 0) {

                    fMatch = TRUE;

                }

            }

           

            FreeLibrary(hDll);

        }

       

        if (fMatch == TRUE) {

            _tcsncpy(pszProcName, pszCurrProcName, cchProcName - 1);

            pszProcName[cchProcName - 1] = _T('/0');

            break;

        }

    }

   

    return fMatch;

}

         好了,至此为止,Keyboard Driver已经获取到了Device Layout,并将其放到前面提到的全局变量g_pPdds中。

 

         顺便在这里列写一下DEVICE_LAYOUT_INFO的定义:

// Device layout wrapper

typedef struct tagDEVICE_LAYOUT_INFO {

    HKL             hkl;

    WCHAR           szProcName[DL_PROC_NAMELENGTH];

    WCHAR           szDll[MAX_PATH];

    DEVICE_LAYOUT   dl;

    HINSTANCE       hDll;

    KEYBD_EVENT     rgKbdEvent[DEFAULT_REMAP_BUFFER_SIZE];

    UINT            cMaxRmpKbdEvents;

    KEYBD_EVENT    *pKbdEvents; // Buffer in case rgKbdEvent is too small

} DEVICE_LAYOUT_INFO, *PDEVICE_LAYOUT_INFO;

 

2.Keyboard获取Input Language的过程

         参照获取Device Layout信息的过程很简单,在此不作介绍。

         最终Input Language的信息存放到全局变量g_ili中。

         顺便把INPUT_LANGUAGE_INFO的定义列写出来。

// Input language wrapper

typedef struct tagINPUT_LANGUAGE_INFO {

    HKL                    hkl;

    WCHAR                  szName[KL_NAMELENGTH];

    WCHAR                  szDll[MAX_PATH];

    INPUT_LANGUAGE         il;

    BOOL                   fGenerateNumPadChars;

    WCHAR                  wchDeadChar;

    const VK_TO_SHIFT      *pVkToShiftState;

    const VK_TO_SHIFT      *pVkToToggledState;

    MODIFIER_TO_SHIFT      rgModToShift[MAX_MODIFIERS+1]; // +1 FOR {0, 0}

    HINSTANCE              hDll;

    HKL                    hklFull;

    WCHAR                  szFullName[KL_NAMELENGTH];

} INPUT_LANGUAGE_INFO, *PINPUT_LANGUAGE_INFO;

3.Keyboard Driver的工作过程

         先简单描述一下工作过程,按键中断来之后,IST先将Scan Code转换为Virtual Code,然后调用keybd_event()告知GWES有按键动作。GWES从Event队列中发现后,会去调用Keyboard Driver的导出函数KeybdDriverVKeyToUnicode(),进行Virtual Code和Unicode的转换。

         可以看到,按键的处理包括两部分,第一部分是IST Ps2KeybdIsrThread的处理,第二部分是GWES发起的KeybdDriverVKeyToUnicode()调用。

         第一部分的介绍如下:

>> IST唤醒

         HW中断来了之后,OAL将其转换为SYSINTR,Kernel将中断Event: pKeybdIst->hevInterrupt设置为有效,接下来IST被唤醒。

>> IST调用PDD层函数进行初步处理

         IST调用PDD层提供的函数KeybdPdd_GetEventEx2将Scan Code进行初步处理,比如加入Flag,或者做一些判断直接发送一些消息给GWES等等。典型的应用如,这里直接把上下左右键进行处理并调用Mouse_Event()等。

         其实这个函数是在IST初始化的时候通过指针变量传递到MDD层的。

>> IST对Scan Code进行映射处理

         PDD层进行初始化的时候,MDD层函数会传入Call Back函数KeybdEventCallback(),其主要作用是告诉MDD层的另外一个线程KeybdEventThreadProc对Scan Code进行进一步处理。

         而PDD层会在创建线程的时候将该Call Back函数回传给MDD层的IST。

         IST首先调用ScanCodeToVKey()将Scan Code转换为Virtual Code,然后调用函数SendRemappedEvent()对Virtual Code进一步的进行Remap。其实这个Remap根据你的需要,如果不需要这些Remap的话,就可以直接砍掉。

         这里的所有的转换表格都是可以由OEM进行定制的,当然也可以直接采用Microsoft的默认方法进行映射。

 

         第二部分介绍如下:

         这里主要是针对不同的语言进行Virtual Code和Unicode的转换,也是根据一些表格进行转换。

         详细的情况,我没有看懂,也不敢在这里班门弄斧了。

         举个例子,在中国或者美国,按下Shift+2输出的就是@,可是在另外一些国家有一些特殊的要求,输出的是$的话,那么就可以通过修改Virtual Code和Unicode的映射表格来实现。

         这部分代码可以参考C:/WINCE500/PUBLIC/COMMON/OAK/DRIVERS/KEYBD/INPUTLANGS。

 

趣味小测试:

在OS中添加对泰语Keyboard(SYSGEN_KBD_THAI_KEDMANEE)的支持,会发现注册表中多了下面几行:

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Layouts/0000041E]

    "Layout File"="kbdth0.dll"

    "Layout Text"="Thai Kedmanee"

    "PS2_AT"="kbdth0.dll"

 

Low Profile Keyboard键盘驱动 for winxp&vista&win7&win10 解决winxp、vista、win7、win10(亲测)系统Lenovo键盘禁用fn键还原f1-f12功能。 阅读详情

相关推荐

Windows10开机开启小键盘开启数字键盘

win10开机默认开启小键盘 保证修改之后持续有效 很多台式机开机后,数字键盘是默认关闭的。这个可以默认打开。 解压后双击运行导入即可

操作系统——实现简单的键盘驱动

首先,下面是头文件保护机制,防止同一个头文件被多次包含,避免编译错误。当然,在最后也要写上#endif。然后要引入头文件:type.h 提供了基本类型定义(如uint8_t、uint32_t等)。port.h 用于与硬件I/O端口通信。interrupt.h 提供了中断管理的基类和相关支持函数,下面出现的KeyboardDriver是interrupt.h中的InterruptHandler的子类。

weixin_72295673的博客 2303

linux keyboard驱动

这是一个linux下的网卡驱动程序,适合搞嵌入式的使用

windows系统电脑外插键盘驱动出现感叹号或者显示未知设备,键盘无法输入的解决办法

windows系统电脑外插键盘驱动出现感叹号或者显示未知设备,键盘无法输入的解决办法

小刘同学321的博客 1万+

三维重建笔记_光束平差法(Bundle Adjustment, BA)

目录 1 光束平差法(Bundle Adjustment, BA) 1.1 SFM (Structure‐from‐Motion) 定义 问题描述 1.2 重投影误差 (Reprojection Error) 目标函数 1.3 使用 Levenberg‐Marquardt Algorithm 进行优化 问题描述 简化目标函数 参考 Gauss‐Newton Method ...

惊鸿一博 2万+

HDMI端口辐射(EMI)超标解决方案

HDMI端口辐射(EMI)超标解决方案 一、HDMI EMC设计要求: 1、HDMI EMC设计原理图 ( 图 一 ) 2、HDMI元件选型及参数说明:(图一所示) a.L510、L511、L512、L513使用专业EMI共模滤波器(普通共模电感寄生电容太大,会导致TMDS差分阻抗匹配测试失败),阻抗值为90Ω~100Ω(两路共模滤波器型号:CMF1210DH900MFR;四路共模滤波器型号:CMF2010DH101MFR), b.U1、U2为排式ESD(ESD排型号:ESD0524PA),越低的结电容

ltqdxl的博客 1万+

Windows11系统农业银行企业K宝无法正常使用的解决方法

无法在此设备上加载驱动程序 驱动程序:ABC keyBoard Safe Driver ABCChina 安全设备将阻止加载此驱动程序。你需要调整设置以加载此驱动程序

qq329218300的专栏 14万+

Materials Studio多层聚合物建模全流程:从Build Layers到LAMMPS data文件导出避坑指南

本文详细解析了使用Materials Studio构建多层聚合物模型并导出为LAMMPS data文件的全流程。重点剖析了Build Layers工具的核心逻辑、真空层处理技巧,并提供了解决导出过程中Z轴坐标偏移问题的实战方案,特别是如何利用msi2lmp工具的-shift参数进行精准校正,确保模型在LAMMPS中模拟的稳定性和准确性。

hhh00的博客 912

Keyboard/Mouse驱动驱动的工作原理

不管是USB或者8042或者虚拟的Keyboard/Mouse,设备栈最上层都是Kbdclass或者Mouclass,操作系统会专门有个现成不停的向设备栈发送Read IRP。 kd> !devstack 80d8eaa0 !DevObj !DrvObj !DevExt ObjectName > 80d8eaa0 \Driver\Kbdc...

weixin_33885253的博客 2495

手把手教你使用transciver-ip核的配置

手把手教你使用transciver-ip核的配置

u012938458的博客 5410

DeepSeek 赋能招投标全流程智能化:多模态解析、知识图谱与投标文件自动生成实战指南(附完整代码与行业案例)

摘要:本文深入探讨DeepSeek技术在招投标领域的全流程智能化应用,通过整合自然语言处理(NLP)、多模态解析、知识图谱等核心技术,实现招标文件智能解析与投标文件自动生成。文中详细解析DeepSeek-R1模型的多模态处理机制、动态知识图谱构建方法及智能模板填充算法,通过完整代码实现展示技术细节。结合广某资源交易平台、山西XX"XX云"等典型案例,验证技术方案在提升招投标效率、降低人为错误方面的显著效果。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 1462

UEFI规范驱动模型(一):驱动模型包含的要素

UEFI规范中对UEFI Driver的介绍有两章分别是第2章overview(2.5节Uefi Driver Model)和第10章Uefi Driver Model详细介绍,对UEFI类型的驱动即UEFI Driver的实现方式进行了规定,下面结合代码以PS2键盘驱动的实现为例,对UEFI Driver 的实现方式进行介绍,详细如下: 1、PS2键盘驱动的组成:     1)PS2键盘驱动

JIA_GUOQIANG的博客 2991

多物理场光学仿真软件:Ansys Lumerical_(8).CHARGE解决方案详解

CHARGE 模块主要用于求解 Poisson 方程和连续性方程,以模拟半导体器件中的电荷分布和载流子输运。通过 CHARGE 模块,可以分析器件的能带结构、载流子浓度、电流密度等关键参数。这些参数对于理解器件的工作原理和优化设计至关重要。

kkchenjj的博客 1382

Python中Selenium 和 keyboard 库的使用

Selenium 需要一个浏览器驱动来控制浏览器,比如 ChromeDriver(用于控制 Google Chrome)或 GeckoDriver(用于控制 Mozilla Firefox)。在自动化测试中,有时可能需要模拟键盘事件,这时可以将 Selenium 与 keyboard 库结合使用。例如,在捕获某些异常情况时,可以使用 keyboard 库触发特定的按键事件。隐式等待是设置一个全局等待时间,在找不到元素时,Selenium 会在这个时间范围内不断地重试,直到找到元素或超时。

haojie 1414

fasterlio原理及代码框架梳理

Faster-LIO 的优势是在保持同等高精度的前提下,显著提升计算效率,以支持更极端的应用场景(如高速无人机)和更廉价的硬件平台。可以把 Faster-LIO 理解为用一把更快的“空间哈希筛子”(iVox)替换了原来精细的“动态平衡树”(ikd-Tree),从而在精度损失极小的情况下,实现了效率的显著提升。:只在实际有地图点的空间区域动态创建和保留体素单元,形成一个稀疏的体素地图。是关键:将去畸变点云中的每个点,在 iVox 维护的局部地图中搜索近邻(如5个最近点),构建点到面的距离残差。

Studying…… 246

由异常的测试眼图引发的深入思考

信号测试中出现“双眼皮”的眼图测试效果,可能的导致因素总结如下:PCB通道阻抗设计不匹配,负载端出现严重反射;发射端采用了不当的信号预处理;接收器均衡处理不当;通道串扰严重... 基于以上的假设,本文就逐一分析下这些因素到底是如何对正常的信号眼图造成影响的。

一只豌豆象的博客 3060

如何将HelloWord-Keyboard接入SignalRGB:灯光同步控制完全教程

想要让你的HelloWord-Keyboard拥有炫酷的灯光效果吗?通过SignalRGB灯光同步控制,你可以实现键盘RGB灯与其他外设的完美联动。本教程将手把手教你如何快速配置,让你的键盘成为桌面光效的一部分。😊 ## 什么是SignalRGB灯光同步控制? SignalRGB是一款强大的RGB灯光控制软件,支持多种外设的灯光同步效果。通过将HelloWord-Keyboard接入Sign

gitblog_00894的博客 1135

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

键盘驱动笔记

前段时间想写一个简单的键盘驱动,就找网上找了一些文档看看。JIURL的键盘驱动5篇文章都很精彩,让人很快就能明白键盘驱动的原理。一般情况微软建议写的键盘驱动都是属于keyboard filter driver,它位于kbdclass和i8042prt驱动之间。利用IOCTL_INTERNAL_KEYBOARD_CONNECT来挂接KeyboardClassServiceCallback,然后在I8

3854

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

HelloWord-Keyboard接入SignalRGB终极教程:灯光同步控制完整指南

HelloWord-Keyboard作为一款支持高度自定义的机械键盘,通过接入SignalRGB灯光控制系统,可实现与其他外设的灯光联动效果。本教程将从准备工作到高级配置,全面讲解如何让你的HelloWord-Keyboard实现专业级RGB灯光同步。 ## 一、准备工作:环境与固件检查 在开始配置前,请确保你的系统和设备满足以下条件: ### 1.1 硬件与系统要求 - **HelloWo

gitblog_00949的博客 1150

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

【驱动】USB驱动实例·串口驱动·键盘驱动

Preface    USB体系支持多种类型的设备。    在 Linux内核,所有的USB设备都使用 usb_driver结构描述。    对于不同类型的 USB设备,内核使用传统的设备驱动模型建立设备驱动描述,然后映射到 USB设备驱动,最终完成特定类型的 USB设备驱动    USB驱动·入门:http://infohacker.blog.51cto.com/6751239/1226257U

成鹏致远的专栏 1186

键盘事件的响应步骤

键盘事件的响应步骤是: 键盘驱动->系统键盘消息处理->当前进程消息处理->当前窗口消息处理 WH_KEYBOARD_LL插在系统键盘消息处理前而WH_KEYBOARD则插在当前进程消息处理前. 因此如果你WH_KEYBOARD hook正确而WH_KEYBOARD_LL hook得不到想要得结果,那么唯一可能就是你把一些不该过滤的键盘消息给过滤掉了。

danming60520的专栏 852

Linux GPIO按键驱动

linux, GPIO按键驱动

JiMoKuangXiangQu的专栏 791

Qt Embedded 键盘驱动分析(上)

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34221036的博客 171

ST-Link调试指南:HelloWord-Keyboard固件烧录终极教程

HelloWord-Keyboard是一款模块化智能机械键盘,支持自定义按键布局与RGB灯效控制。本文将详细介绍如何使用ST-Link工具完成固件烧录,帮助新手快速上手这款开源硬件的调试流程。 ## 准备工作:固件与工具获取 在开始烧录前,需要准备以下文件和工具: - **预编译固件**:项目提供的预编译固件位于`2.Firmware/_Release/`目录下,包含: - `Hello

gitblog_00649的博客 1185
上一篇: linux的自旋锁struct spinlock_t的使用
下一篇: Windows 7专业版安装VS2005与WinCE6.0开发环境
wang-bob
博客等级 码龄20年 356粉丝 112原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值