559 lines
15 KiB
C
559 lines
15 KiB
C
/*****************************************************************************
|
|
*
|
|
* DIEmK.c
|
|
*
|
|
* Copyright (c) 1996 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
* Abstract:
|
|
*
|
|
* Emulation module for keyboard.
|
|
*
|
|
* Contents:
|
|
*
|
|
* CEm_Kbd_CreateInstance
|
|
* CEm_Kbd_InitKeys
|
|
* CEm_LL_KbdHook
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include "dinputpr.h"
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* The sqiffle for this file.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#define sqfl sqflEm
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Keyboard emulation
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STDMETHODIMP CEm_Kbd_Acquire(PEM this, BOOL fAcquire);
|
|
|
|
static BYTE s_rgbKbd[DIKBD_CKEYS];
|
|
HHOOK g_hhkKbd;
|
|
LPBYTE g_pbKbdXlat;
|
|
|
|
ED s_edKbd = {
|
|
&s_rgbKbd,
|
|
0,
|
|
CEm_Kbd_Acquire,
|
|
-1,
|
|
cbX(s_rgbKbd),
|
|
0x0,
|
|
};
|
|
|
|
static BOOL s_fFarEastKbd;
|
|
static BOOL fKbdCaptured;
|
|
static BOOL fNoWinKey;
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func LRESULT | CEm_Kbd_KeyboardHook |
|
|
*
|
|
* Thread-specific keyboard hook filter.
|
|
*
|
|
* Note that we need only one of these, since only the foreground
|
|
* window will require a hook.
|
|
*
|
|
* @parm int | nCode |
|
|
*
|
|
* Notification code.
|
|
*
|
|
* @parm WPARAM | wp |
|
|
*
|
|
* VK_* code.
|
|
*
|
|
* @parm LPARAM | lp |
|
|
*
|
|
* Key message information.
|
|
*
|
|
* @returns
|
|
*
|
|
* Always chains to the next hook.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
LRESULT CALLBACK
|
|
CEm_Kbd_KeyboardHook(int nCode, WPARAM wp, LPARAM lp)
|
|
{
|
|
BYTE bScan = 0x0;
|
|
BYTE bAction;
|
|
LRESULT lr;
|
|
|
|
if (nCode == HC_ACTION || nCode == HC_NOREMOVE) {
|
|
bScan = LOBYTE(HIWORD(lp));
|
|
|
|
if (HIWORD(lp) & KF_EXTENDED) {
|
|
bScan |= 0x80;
|
|
}
|
|
if (HIWORD(lp) & KF_UP) {
|
|
bAction = 0;
|
|
} else {
|
|
bAction = 0x80;
|
|
}
|
|
|
|
bScan = g_pbKbdXlat[bScan];
|
|
|
|
if( s_fFarEastKbd )
|
|
{
|
|
/*
|
|
* Manually toggle these keys on make, ignore break
|
|
*/
|
|
if( ( bScan == DIK_KANA )
|
|
||( bScan == DIK_KANJI )
|
|
||( bScan == DIK_CAPITAL ) )
|
|
{
|
|
if( bAction )
|
|
{
|
|
bAction = s_rgbKbd[bScan] ^ 0x80;
|
|
}
|
|
else
|
|
{
|
|
goto KbdHook_Skip;
|
|
}
|
|
}
|
|
}
|
|
|
|
CEm_AddEvent(&s_edKbd, bAction, bScan, GetMessageTime());
|
|
KbdHook_Skip:;
|
|
}
|
|
|
|
lr = CallNextHookEx(g_hhkKbd, nCode, wp, lp);
|
|
|
|
if( fKbdCaptured ) {
|
|
// test Alt+Tab
|
|
if( ((HIWORD(lp) & KF_ALTDOWN) && (bScan == 0x0F))
|
|
|| ((bScan == 0x38 || bScan == 0xb8) && bAction == 0)
|
|
) {
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
} else if (fNoWinKey) {
|
|
//If left_Winkey or right_WinKey pressed. We really should use virtual keys
|
|
// if we could, but unfortunately no virtual key info is available.
|
|
if( bScan == 0xdb || bScan == 0xdc ) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return lr;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_Kbd_Hook_Acquire |
|
|
*
|
|
* Acquire/unacquire a keyboard via a thread hook.
|
|
*
|
|
* @parm PEM | pem |
|
|
*
|
|
* Device being acquired.
|
|
*
|
|
* @parm BOOL | fAcquire |
|
|
*
|
|
* Whether the device is being acquired or unacquired.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STDMETHODIMP
|
|
CEm_Kbd_Hook_Acquire(PEM this, BOOL fAcquire)
|
|
{
|
|
HRESULT hres;
|
|
EnterProc(CEm_Kbd_Hook_Acquire, (_ "pu", this, fAcquire));
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
|
|
DllEnterCrit();
|
|
if (fAcquire) { /* Install the hook */
|
|
if (this->vi.hwnd) {
|
|
if (!g_hhkKbd) {
|
|
g_hhkKbd = SetWindowsHookEx(WH_KEYBOARD,
|
|
CEm_Kbd_KeyboardHook, g_hinst,
|
|
GetWindowThreadProcessId(this->vi.hwnd, 0));
|
|
hres = S_OK;
|
|
}
|
|
else
|
|
hres = E_FAIL; //already hooked
|
|
} else {
|
|
RPF("Kbd::Acquire: Background mode not supported");
|
|
hres = E_FAIL;
|
|
}
|
|
} else { /* Remove the hook */
|
|
UnhookWindowsHookEx(g_hhkKbd);
|
|
g_hhkKbd = 0;
|
|
hres = S_OK;
|
|
}
|
|
|
|
DllLeaveCrit();
|
|
|
|
ExitOleProc();
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_Kbd_Acquire |
|
|
*
|
|
* Acquire/unacquire a keyboard in a manner consistent with the
|
|
* emulation level.
|
|
*
|
|
* @parm PEM | pem |
|
|
*
|
|
* Device being acquired.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STDMETHODIMP
|
|
CEm_Kbd_Acquire(PEM this, BOOL fAcquire)
|
|
{
|
|
HRESULT hres;
|
|
EnterProc(CEm_Kbd_Acquire, (_ "pu", this, fAcquire));
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
|
|
fKbdCaptured = FALSE;
|
|
fNoWinKey = FALSE;
|
|
if( fAcquire ) {
|
|
if( this->vi.fl & VIFL_CAPTURED ) {
|
|
fKbdCaptured = TRUE;
|
|
} else if( this->vi.fl & VIFL_NOWINKEY ) {
|
|
fNoWinKey = TRUE;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_SLOW_LL_HOOKS
|
|
AssertF(DIGETEMFL(this->vi.fl) == DIEMFL_KBD ||
|
|
DIGETEMFL(this->vi.fl) == DIEMFL_KBD2);
|
|
|
|
if (this->vi.fl & DIMAKEEMFL(DIEMFL_KBD)) {
|
|
AssertF(g_fUseLLHooks);
|
|
hres = CEm_LL_Acquire(this, fAcquire, this->vi.fl, LLTS_KBD);
|
|
} else {
|
|
hres = CEm_Kbd_Hook_Acquire(this, fAcquire);
|
|
}
|
|
#else
|
|
AssertF(DIGETEMFL(this->vi.fl) == DIEMFL_KBD2);
|
|
hres = CEm_Kbd_Hook_Acquire(this, fAcquire);
|
|
#endif
|
|
|
|
ExitOleProc();
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_Kbd_CreateInstance |
|
|
*
|
|
* Create a keyboard thing. Also record what emulation
|
|
* level we ended up with so the caller knows.
|
|
*
|
|
* @parm PVXDDEVICEFORMAT | pdevf |
|
|
*
|
|
* What the object should look like. The
|
|
* <e VXDDEVICEFORMAT.dwEmulation> field is updated to specify
|
|
* exactly what emulation we ended up with.
|
|
*
|
|
* @parm PVXDINSTANCE * | ppviOut |
|
|
*
|
|
* The answer goes here.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT EXTERNAL
|
|
CEm_Kbd_CreateInstance(PVXDDEVICEFORMAT pdevf, PVXDINSTANCE *ppviOut)
|
|
{
|
|
LPBYTE pbKbdXlat;
|
|
|
|
#ifdef WINNT
|
|
/*
|
|
* In Win2K/WinXP, for legacy free machine, GetKeyboardType will return
|
|
* unreliable result for non-PS2 keyboard. We will use the first time result
|
|
* from GetKeyboardType (for GUID_SysKeyboard) which is also used by Generic
|
|
* Input to do translation.
|
|
* Related Windows bug: 363700.
|
|
*/
|
|
if( !g_pbKbdXlat ) {
|
|
#endif
|
|
pbKbdXlat = (LPBYTE)pdevf->dwExtra;
|
|
if (!pbKbdXlat) {
|
|
pbKbdXlat = pvFindResource(g_hinst, IDDATA_KBD_PCENH, RT_RCDATA);
|
|
}
|
|
|
|
AssertF(pbKbdXlat);
|
|
AssertF(fLimpFF(g_pbKbdXlat, g_pbKbdXlat == pbKbdXlat));
|
|
g_pbKbdXlat = pbKbdXlat;
|
|
#ifdef WINNT
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SLOW_LL_HOOKS
|
|
/*
|
|
* Note carefully the test. It handles the cases where
|
|
*
|
|
* 0. The app did not ask for emulation, so we give it the
|
|
* best we can. (dwEmulation == 0)
|
|
* 1. The app explicitly asked for emulation 1.
|
|
* (dwEmulation == DIEMFL_KBD)
|
|
* 2. The app explicitly asked for emulation 2.
|
|
* (dwEmulation == DIEMFL_KBD2)
|
|
* 3. The registry explicitly asked for both emulation modes.
|
|
* (dwEmulation == DIEMFL_KBD | DIEMFL_KBD2)
|
|
* Give it the best we can. (I.e., same as case 0.)
|
|
*
|
|
* All platforms support emulation 2. Not all platforms support
|
|
* emulation 1. If we want emulation 1 but can't get it, then
|
|
* we fall back on emulation 2.
|
|
*/
|
|
|
|
/*
|
|
* First, if we don't have support for emulation 1, then clearly
|
|
* we have to use emulation 2.
|
|
*/
|
|
|
|
if (!g_fUseLLHooks
|
|
#ifdef DEBUG
|
|
|| (g_flEmulation & DIEMFL_KBD2)
|
|
#endif
|
|
) {
|
|
pdevf->dwEmulation = DIEMFL_KBD2;
|
|
} else
|
|
|
|
/*
|
|
* Otherwise, we have to choose between 1 and 2. The only case
|
|
* where we choose 2 is if 2 is explicitly requested.
|
|
*/
|
|
if (pdevf->dwEmulation == DIEMFL_KBD2) {
|
|
/* Do nothing */
|
|
} else
|
|
|
|
/*
|
|
* All other cases get 1.
|
|
*/
|
|
{
|
|
pdevf->dwEmulation = DIEMFL_KBD;
|
|
}
|
|
|
|
/*
|
|
* Assert that we never give emulation 1 when it doesn't exist.
|
|
*/
|
|
AssertF(fLimpFF(pdevf->dwEmulation & DIEMFL_KBD, g_fUseLLHooks));
|
|
|
|
/*
|
|
* Assert that exactly one emulation flag is selected.
|
|
*/
|
|
AssertF(pdevf->dwEmulation == DIEMFL_KBD ||
|
|
pdevf->dwEmulation == DIEMFL_KBD2);
|
|
#else
|
|
/*
|
|
* We are being compiled for "emulation 2 only", so that simplifies
|
|
* matters immensely.
|
|
*/
|
|
pdevf->dwEmulation = DIEMFL_KBD2;
|
|
#endif
|
|
|
|
return CEm_CreateInstance(pdevf, ppviOut, &s_edKbd);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_Kbd_InitKeys |
|
|
*
|
|
* Initialize pieces of the keyboard state in preparation for
|
|
* acquisition.
|
|
*
|
|
* @parm PVXDDWORDDATA | pvdd |
|
|
*
|
|
* The states of the <c VK_KANA> and <c VK_CAPITAL> keys.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT EXTERNAL
|
|
CEm_Kbd_InitKeys(PVXDDWORDDATA pvdd)
|
|
{
|
|
|
|
/* Do this only when not acquired */
|
|
if (s_edKbd.cAcquire < 0) {
|
|
ZeroX(s_rgbKbd);
|
|
if (pvdd->dw & 1) {
|
|
s_rgbKbd[DIK_KANA] = 0x80;
|
|
}
|
|
if (pvdd->dw & 2) {
|
|
s_rgbKbd[DIK_CAPITAL] = 0x80;
|
|
}
|
|
if (pvdd->dw & 8) {
|
|
s_rgbKbd[DIK_KANJI] = 0x80;
|
|
}
|
|
s_fFarEastKbd = ((pvdd->dw & 16)) != 0;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
#ifdef USE_SLOW_LL_HOOKS
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func LRESULT | CEm_LL_KbdHook |
|
|
*
|
|
* Low-level keyboard hook filter.
|
|
*
|
|
* @parm int | nCode |
|
|
*
|
|
* Notification code.
|
|
*
|
|
* @parm WPARAM | wp |
|
|
*
|
|
* WM_* keyboard message.
|
|
*
|
|
* @parm LPARAM | lp |
|
|
*
|
|
* Key message information.
|
|
*
|
|
* @returns
|
|
*
|
|
* Always chains to the next hook.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
LRESULT CALLBACK
|
|
CEm_LL_KbdHook(int nCode, WPARAM wp, LPARAM lp)
|
|
{
|
|
PLLTHREADSTATE plts;
|
|
PKBDLLHOOKSTRUCT pllhs = (PV)lp;
|
|
|
|
if (nCode == HC_ACTION) {
|
|
BYTE bScan = 0x0;
|
|
BYTE bAction;
|
|
D(DWORD tmStart = GetTickCount());
|
|
|
|
wp; /* We don't care what the msg is */
|
|
|
|
bScan = (BYTE)pllhs->scanCode;
|
|
|
|
if( !bScan )
|
|
{
|
|
/*
|
|
* ISSUE-2001/03/29-timgill Special case for non-standard VK codes
|
|
* The bonus keys on some USB keyboards have zero scan code and
|
|
* the extended key flag is clear.
|
|
* Get the scan code by mapping the VK, then map the
|
|
* scan code back, if it is the same as the original VK assume
|
|
* the scan code is not extended otherwise assume it is.
|
|
* This is no where near full proof and only works at all
|
|
* because non-extended scan codes are matched first so extended
|
|
* scan codes normally fail to translate back.
|
|
*/
|
|
bScan = (BYTE)MapVirtualKey( pllhs->vkCode, 0 );
|
|
if( MapVirtualKey( bScan, 3 ) != pllhs->vkCode )
|
|
{
|
|
bScan |= 0x80;
|
|
}
|
|
}
|
|
else if (pllhs->flags & LLKHF_EXTENDED) {
|
|
bScan |= 0x80;
|
|
}
|
|
|
|
if (pllhs->flags & LLKHF_UP) {
|
|
bAction = 0;
|
|
} else {
|
|
bAction = 0x80;
|
|
}
|
|
|
|
bScan = g_pbKbdXlat[bScan];
|
|
if( s_fFarEastKbd )
|
|
{
|
|
/*
|
|
* Manually toggle these keys on make, ignore break
|
|
*/
|
|
if( ( bScan == DIK_KANA )
|
|
||( bScan == DIK_KANJI )
|
|
||( bScan == DIK_CAPITAL ) )
|
|
{
|
|
if( bAction )
|
|
{
|
|
bAction = s_rgbKbd[bScan] ^ 0x80;
|
|
}
|
|
else
|
|
{
|
|
D(SquirtSqflPtszV(sqflTrace | sqfl,
|
|
TEXT("KBD! vk=%02x, scan=%02x, fl=%08x, tm=%08x")
|
|
TEXT(" being skipped"),
|
|
pllhs->vkCode, pllhs->scanCode, pllhs->flags,
|
|
pllhs->time );)
|
|
goto LLKbdHook_Skip;
|
|
}
|
|
}
|
|
}
|
|
|
|
CEm_AddEvent(&s_edKbd, bAction, bScan, GetTickCount());
|
|
|
|
D(SquirtSqflPtszV(sqflTrace | sqfl,
|
|
TEXT("KBD! vk=%02x, scan=%02x, fl=%08x, tm=%08x, ")
|
|
TEXT("in=%08x, out=%08x"),
|
|
pllhs->vkCode, pllhs->scanCode, pllhs->flags,
|
|
pllhs->time, tmStart, GetTickCount()));
|
|
LLKbdHook_Skip:;
|
|
|
|
}
|
|
|
|
/*
|
|
* ISSUE-2001/03/29-timgill Need method for detecting Ctrl-Alt-Del
|
|
* If Ctrl+Alt+Del, then force global unacquire!
|
|
* Need to re-sync Ctrl, Alt, and Del on next keypress.
|
|
* Unfortunately, there is no way to find out if Ctrl+Alt+Del
|
|
* has been pressed...
|
|
*/
|
|
|
|
plts = g_plts;
|
|
if (plts) {
|
|
LRESULT lr;
|
|
|
|
lr = CallNextHookEx(plts->rglhs[LLTS_KBD].hhk, nCode, wp, lp);
|
|
|
|
if( fKbdCaptured ) {
|
|
if( ((pllhs->flags & LLKHF_ALTDOWN) && (pllhs->vkCode == VK_TAB)) ||
|
|
((pllhs->flags & LLKHF_UP) && (pllhs->vkCode == VK_LMENU || pllhs->vkCode == VK_RMENU))
|
|
) {
|
|
return lr;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
} else if (fNoWinKey) {
|
|
if( pllhs->vkCode == VK_LWIN || pllhs->vkCode == VK_RWIN ) {
|
|
return TRUE;
|
|
} else {
|
|
return lr;
|
|
}
|
|
} else {
|
|
return lr;
|
|
}
|
|
} else {
|
|
/*
|
|
* This can happen if a message gets posted to the hook after
|
|
* releasing the last acquire but before we completely unhook.
|
|
*/
|
|
RPF( "DINPUT: Keyboard hook not passed on to next hook" );
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* USE_SLOW_LL_HOOKS */
|