现在的位置: 首页 > 综合 > 正文

Keyboard驱动介绍

2013年07月17日 ⁄ 综合 ⁄ 共 22275字 ⁄ 字号 评论关闭
文章目录

一.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"

 

抱歉!评论已关闭.