Keyboard驱动介绍

 

 Keyboard驱动介绍

         最近手里面没啥事,就想看看一些DriverMDD层。

         以前改过Keyboard DriverPDD层,但是对它的MDD层还真是一片空白,这两天随便看了看KeyboardMDD层,赶紧把东西记录下来,以防以过段时间忘记了。

         很多是个人理解,难免有错误的地方。

一.Keyboard Driver的加载过程

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

         加载的大概过程如下:

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

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

二.有关Keyboard的几个概念

1Device Layout

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

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

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

         具体到功能来说,Device Layout的功能就是负责Scan CodeVirtual Code的转换,以及Virtual CodeRemapping。比如,键盘上左边有一个Shift键,右边也有一个Shift键,它们的Scan CodeVirtual 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"

2Input Language

         Virtual CodeUnicode之间进行转换。

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

3Local

         引用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中相关信息的获取方式

1MDD层如何获取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层的第uiCurrPddEntry

        // 其中一个目的是获取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);

   

    // 获取KeyboardINPUT_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_LAYOUTJ9项目上调用的是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.szDlllayout 入口函数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 LayoutMask标记,这里决定Device Layout究竟从哪里获取,代码解释如下:

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

//

// SelectDeviceLayout

//

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

// pszHkl for the given PDD.

// 选取Layout方式,通过dll的导出函数,所谓的Layout就是VKScan 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/LayoutsHandle

    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/pszHklHandle 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;

 

2Keyboard获取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;

3Keyboard Driver的工作过程

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

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

         第一部分的介绍如下:

>> IST唤醒

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

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

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

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

>> ISTScan Code进行映射处理

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

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

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

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

 

         第二部分介绍如下:

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

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

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

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

 

趣味小测试:

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

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

    "Layout File"="kbdth0.dll"

    "Layout Text"="Thai Kedmanee"

    "PS2_AT"="kbdth0.dll"

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/243297.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GDI+不同的地方

研究了GDI处理图像的地方&#xff0c;发现它一些与众不同的地方&#xff0c;被它坑了一天。。。。。1、GDI的像素的原点默认你在左下角的&#xff0c;所以读取像素的顺序是从最低一行开始的(bottom-left)&#xff0c;其他一般的图像处理软件&#xff0c;像Photoshop&#xff0c…

关于结构体的内容

关于结构体的内容 结构体使用类似于C语言的语法来定义 结构体使用struct关键字声明。结构体内的成员可以是任何数据类型&#xff0c;包括用户自定义类型和其他的结构体类型。 struct{int a,b; //32位变量opcode_t opcode;//用户定义类型logic [23:0] adress;//24位变量bit er…

Pushing Policy Failed because Checkpoint Firewall “Load on module failed – no memory”

One day when pushing firewall policy from Checkpoint management server to UTM 272 cluster gateways, it failed and I got error message “Load on module failed – no memory” on one of cluster members. “Network Security Policy ‘Montreal_DMZ’ was prepared …

电池驱动介绍

电池驱动介绍 一&#xff0e;整体框架 电池驱动代码量很小&#xff0c;可是麻雀虽小&#xff0c;五脏俱全。与其他的很多Driver一样&#xff0c;分为PDDMDD层&#xff0c;双层之间通过PDD的如下导出接口相联系。 Programming element Description BatteryDrvrGetLevels…

将1bpp的bmp图像存储为1bpp或者2bpp的tiff格式

// 将1bpp的位图转换为 1bit/2bit tiff /** 参数&#xff1a;BYTE *src 二值图像的像素数据&#xff0c;不包含头部信息&#xff0c; 1bpp, int src_width 原图的宽度, in pixles,int src_height 原图的高度, in pixlesint bpp 转换tiff指定的bpp */ static BYTE *BW2Tif(…

关于联合体的内容

关于联合体的内容 联合体只储存一个值 联合体只存储一个元素&#xff0c;但这个元素可以有多种表示方法&#xff0c;每种表示可以是不同的数据类型。 联合体的声明语法类似于结构体&#xff0c;联合体的成员的引用也跟结构体一样。 union{int i;int unsigned u; }data; ... d…

Point-BERT:一种基于Transformer架构的点云深度网络

目录 1. 前言 2. Point Tokenization 3. Transformer Backbone 4. Masked Point Modeling 5. Experiments Reference 1. 前言 从PointNet [1] 开始&#xff0c;点云深度网络逐渐成为解决点云特征提取与语义分析的主要研究方向。尤其在OpenAI的GPT模型获得了突破性成果后&#…

GNS3 VoIP Lab (Cisco 3725 and CME 4.3)

Here is a simple VoIP Lab in GNS3 environment. It is only used for my lab test and recorded here for future reference. 1. Topology: GNS3 Topology:Logic Topology:xp(192.168.2.60)——–C3725 Router(192.168.2.10) 2. Enviroment: ESXi 5.5 (or Vmware Workstation…

谁知道这个代码片段干嘛的

int value 0xAAAA;for (int i0; i<8; i){int tmp value & 0x3; // 取出第两个比特位置if (tmp 0x0){//}else if (tmp 0x1){//}else if (tmp 0x2){//}else if(tmp 0x3){TRACE0("???");}TRACE1("tmp0x%x\n\n", tmp);value >> 2;}

关于数组的内容

关于数组的内容 Verilog数组声明的基本语法 <data_type><vector_size><array_name><array_dimension> 例如&#xff1a; reg[15:0] RAM [0:4095];//储存器数组SystemVerilog允许任何数据类型的非压缩数组 SystemVerilog将非压缩数组的声明进行了扩展…

Touch Driver介绍

Touch Driver介绍 一&#xff0e;相关知识介绍 1&#xff0e;Touch Driver的加载过程 GWES到[HKEY_LOCAL_MACHINE/HARDWARE/DEVICEMAP/TOUCH]的“Driver name”获取Driver DLL的名字&#xff0c;如果没有找到该键值&#xff0c;则使用默认名字Touch.dll。 Touch Driver的加…

BreadCrumb控件

BreadCrumb控件&#xff0c;如上图所示&#xff0c;即面包屑导航控件&#xff0c;类似于TreeCtrl&#xff0c;但不是一次显示所有的Item&#xff0c;VC 2010可以编译通过&#xff0c;稍微修改一下其他的也可以编译&#xff0c;源代码下载&#xff1a; http://download.csdn.net…

foreach数组循环结构体

foreach数组循环结构体 foreach循环遍历任何维数的数组 Systemverilog增加了foreach循环&#xff0c;它可用来对一维或多维数组中的元素进行迭代&#xff0c;而不必指定数组每个维度的宽度。foreach循环的自变量是数组名&#xff0c;它后面是方括号内用逗号隔开的循环变量列表…

Android简介

最近Android很火&#xff0c;小弟也想了解一下它的结构。跟CE或者Mobile比起来&#xff0c;它的结构是有点凌乱&#xff0c;也难怪&#xff0c;毕竟是基于别人的内核在上层开发了一些应用 ---------------------------------------------------------------------------------…

说不尽的刘恒

认识刘恒快三十年了&#xff0c;作为曾经的同事和他小说的责任编辑&#xff0c;我只写过他一篇文章&#xff0c;还是在二十多年前。有时候特别熟悉的人反而不知道从何写起&#xff0c;因为一想起往事&#xff0c;各种记忆像开闸的水一样涌满眼前&#xff0c;让人很难落笔。1985…

印前处理的“发动机”——RIP

对于许多印刷厂来说&#xff0c;数字印前技术仍是个谜&#xff0c;尤其是光栅处理器RIP。除了RIP之处&#xff0c;我们也总听说打印机的内置控制单元。其实&#xff0c;RIP与内置控制单元在本质上是一样的&#xff0c;但也有所不同。听说起来好像有些玄乎&#xff0c;下面我们来…

组合逻辑过程块

组合逻辑过程块 always_comb代表组合逻辑 always_comb过程块表示建立组合逻辑模型 always_comb if(!mode)y a b; elsey a - b;always_comb能推断出其敏感表 与通用always过程块不同&#xff0c;always_comb块的后面不需要指明敏感表。软件工具已经知道设计的意图是建立一个…

外行看Flash的存储原理

突然在网上看到别人两年前写的一篇关于nor和nand的好文章&#xff0c;做为csdn的合法公民&#xff0c;有必要转 一、存储数据的原理 两种闪存都是用三端器件作为存储单元&#xff0c;分别为源极、漏极和栅极&#xff0c;与场效应管的工作原理相同&#xff0c;主要是利用电场的…

数码印刷

数码印刷 目前&#xff0c;RIP已经变成了印前生产的核心问题。它影响到从色彩和文件管理到印刷的整个生产过程的方方面面。而且&#xff0c;像陷印和拼大版这些以前需要单独的应用程序处理的功能&#xff0c;现在也被加到了RIP中。    新的RIP产品和销售商有很多。象Agfa,Ha…

锁存逻辑过程块

锁存逻辑过程块 always_latch描述锁存逻辑 always_latch过程块表示过程块描述的是基于锁存器的逻辑。和always_comb一样&#xff0c;always_latch的敏感表示推断出来的。 always_latchif(enable) q < d;always_latch与always_comb语义相同 always_latch过程的语义规则与al…