Attempt to improve key event synthesis. This change adds support

for dead keys and attempts to choose the correct code page for the
thread that will (probably) receive synthesized events.
This commit is contained in:
crs 2003-05-20 19:15:58 +00:00
parent 01fab82edc
commit bdecca0bcc
2 changed files with 258 additions and 113 deletions

View File

@ -747,7 +747,7 @@ static const UINT g_mapEF00[] =
/* 0x64 */ 0, 0, 0, VK_APPS|0x100, /* 0x64 */ 0, 0, 0, VK_APPS|0x100,
/* 0x68 */ 0, 0, VK_HELP|0x100, VK_CANCEL|0x100, 0, 0, 0, 0, /* 0x68 */ 0, 0, VK_HELP|0x100, VK_CANCEL|0x100, 0, 0, 0, 0,
/* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x78 */ 0, 0, 0, 0, 0, 0, VK_MODECHANGE|0x100, VK_NUMLOCK|0x100, /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, VK_NUMLOCK|0x100,
/* 0x80 */ VK_SPACE, 0, 0, 0, 0, 0, 0, 0, /* 0x80 */ VK_SPACE, 0, 0, 0, 0, 0, 0, 0,
/* 0x88 */ 0, VK_TAB, 0, 0, 0, VK_RETURN|0x100, 0, 0, /* 0x88 */ 0, VK_TAB, 0, 0, 0, VK_RETURN|0x100, 0, 0,
/* 0x90 */ 0, 0, 0, 0, 0, VK_HOME, VK_LEFT, VK_UP, /* 0x90 */ 0, 0, 0, 0, 0, VK_HOME, VK_LEFT, VK_UP,
@ -867,9 +867,11 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
return m_mask; return m_mask;
} }
// get output mask. default output mask carries over the current // handle other special keys
// toggle modifier states and includes desired shift, control, alt, if (virtualKey != 0) {
// meta, and super states. // compute the final desired modifier mask. special keys use
// the desired modifiers as given except we keep the caps lock,
// num lock, and scroll lock as is.
KeyModifierMask outMask = (m_mask & KeyModifierMask outMask = (m_mask &
(KeyModifierCapsLock | (KeyModifierCapsLock |
KeyModifierNumLock | KeyModifierNumLock |
@ -881,80 +883,16 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
KeyModifierMeta | KeyModifierMeta |
KeyModifierSuper)); KeyModifierSuper));
// set control and alt if mode shift (AltGr) is requested // strip out extended key flag
if ((mask & KeyModifierModeSwitch) != 0) { UINT virtualKey2 = (virtualKey & ~0x100);
outMask |= KeyModifierControl | KeyModifierAlt;
}
// extract extended key flag // check numeric keypad. note that virtual keys do not distinguish
const bool isExtended = ((virtualKey & 0x100) != 0); // between the keypad and non-keypad movement keys. however, the
virtualKey &= ~0x100; // virtual keys do distinguish between keypad numbers and operators
// (e.g. add, multiply) and their main keyboard counterparts.
// if not in map then ask system to convert character // therefore, we can ignore the num-lock state for movement virtual
if (virtualKey == 0) { // keys but not for numeric keys.
// translate. return no keys if unknown key. if (virtualKey2 >= VK_NUMPAD0 && virtualKey2 <= VK_DIVIDE) {
char ascii;
wchar_t unicode = static_cast<wchar_t>(id & 0x0000ffff);
BOOL error;
if (WideCharToMultiByte(CP_THREAD_ACP,
#if defined(WC_NO_BEST_FIT_CHARS)
WC_NO_BEST_FIT_CHARS |
#endif
WC_COMPOSITECHECK |
WC_DEFAULTCHAR,
&unicode, 1,
&ascii, 1, NULL, &error) == 0 || error) {
LOG((CLOG_DEBUG2 "character %d not in code page", id));
return m_mask;
}
SHORT vk = VkKeyScan(ascii);
if (vk == 0xffff) {
LOG((CLOG_DEBUG2 "no virtual key for character %d", id));
return m_mask;
}
// use whatever shift state VkKeyScan says
// FIXME -- also for control and alt, but it's more difficult
// to determine if control and alt must be off or if it just
// doesn't matter.
outMask &= ~KeyModifierShift;
// convert system modifier mask to our mask
if (HIBYTE(vk) & 1) {
outMask |= KeyModifierShift;
}
if (HIBYTE(vk) & 2) {
outMask |= KeyModifierControl;
}
if (HIBYTE(vk) & 4) {
outMask |= KeyModifierAlt;
}
// handle combination of caps-lock and shift. if caps-lock is
// off locally then use shift as necessary. if caps-lock is on
// locally then it reverses the meaning of shift for keys that
// are subject to case conversion.
if ((outMask & KeyModifierCapsLock) != 0) {
if (tolower(ascii) != toupper(ascii)) {
LOG((CLOG_DEBUG2 "flip shift"));
outMask ^= KeyModifierShift;
}
}
// get virtual key
virtualKey = LOBYTE(vk);
}
// if in map then figure out correct modifier state
else {
// check numeric keypad. note that while KeyID distinguishes
// between the keypad movement keys (e.g. Home, left arrow),
// the virtual keys do not. however, the virtual keys do
// distinguish between keypad numbers and operators (e.g.
// add, multiply) and their main keyboard counterparts.
// therefore, we can ignore the num-lock state for movement
// virtual keys but not for numeric keys.
if (virtualKey >= VK_NUMPAD0 && virtualKey <= VK_DIVIDE) {
// set required shift state based on current numlock state // set required shift state based on current numlock state
if ((outMask & KeyModifierNumLock) == 0) { if ((outMask & KeyModifierNumLock) == 0) {
if ((m_mask & KeyModifierNumLock) == 0) { if ((m_mask & KeyModifierNumLock) == 0) {
@ -968,13 +906,184 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
} }
} }
// check for left tab // check for left tab. that requires the shift key.
else if (id == kKeyLeftTab) { if (id == kKeyLeftTab) {
mask |= KeyModifierShift;
}
// now generate the keystrokes and return the resulting modifier mask
LOG((CLOG_DEBUG2 "KeyID 0x%08x to virtual key %d mask 0x%04x", id, virtualKey2, outMask));
return mapToKeystrokes(keys, virtualKey, m_mask, outMask, action);
}
// determine the thread that'll receive this event
// FIXME -- we can't be sure we'll get the right thread here
HWND targetWindow = GetForegroundWindow();
DWORD targetThread = GetWindowThreadProcessId(targetWindow, NULL);
// figure out the code page for the target thread. i'm just
// guessing here. get the target thread's keyboard layout,
// extract the language id from that, and choose the code page
// based on that language.
HKL hkl = GetKeyboardLayout(targetThread);
LANGID langID = static_cast<LANGID>(LOWORD(hkl));
UINT codePage = getCodePageFromLangID(langID);
LOG((CLOG_DEBUG2 "using code page %d and language id 0x%04x for thread 0x%08x", codePage, langID, targetThread));
// regular characters are complicated by dead keys. it may not be
// possible to generate a desired character directly. we may need
// to generate a dead key first then some other character. the
// app receiving the events will compose these two characters into
// a single precomposed character.
//
// as best as i can tell this is the simplest way to convert a
// character into its uncomposed version. along the way we'll
// discover if the key cannot be handled at all. we convert
// from wide char to multibyte, then from multibyte to wide char
// forcing conversion to composite characters, then from wide
// char back to multibyte without making precomposed characters.
BOOL error;
char multiByte[2 * MB_LEN_MAX];
wchar_t unicode[2];
unicode[0] = static_cast<wchar_t>(id & 0x0000ffff);
int nChars = WideCharToMultiByte(codePage,
WC_COMPOSITECHECK | WC_DEFAULTCHAR,
unicode, 1,
multiByte, sizeof(multiByte),
NULL, &error);
if (nChars == 0 || error) {
LOG((CLOG_DEBUG2 "KeyID 0x%08x not in code page", id));
return m_mask;
}
nChars = MultiByteToWideChar(codePage,
MB_COMPOSITE | MB_ERR_INVALID_CHARS,
multiByte, nChars,
unicode, 2);
if (nChars == 0) {
LOG((CLOG_DEBUG2 "KeyID 0x%08x mb->wc mapping failed", id));
return m_mask;
}
nChars = WideCharToMultiByte(codePage,
0,
unicode, nChars,
multiByte, sizeof(multiByte),
NULL, &error);
if (nChars == 0 || error) {
LOG((CLOG_DEBUG2 "KeyID 0x%08x wc->mb mapping failed", id));
return m_mask;
}
// we expect one or two characters in multiByte. if there are two
// then the *second* is a dead key. process the dead key if there.
// FIXME -- we assume each character is one byte here
if (nChars > 2) {
LOG((CLOG_DEBUG2 "multibyte characters not supported for character 0x%04x", id));
return m_mask;
}
if (nChars == 2) {
LOG((CLOG_DEBUG2 "KeyID 0x%08x needs dead key %u", id, (unsigned char)multiByte[1]));
mapCharacter(keys, multiByte[1], hkl, m_mask, mask, action);
}
// process character
LOG((CLOG_DEBUG2 "KeyID 0x%08x maps to character %u", id, (unsigned char)multiByte[0]));
virtualKey = mapCharacter(keys, multiByte[0], hkl, m_mask, mask, action);
// non-special key cannot modify the modifier mask
return m_mask;
}
UINT
CMSWindowsSecondaryScreen::mapCharacter(Keystrokes& keys,
char c, HKL hkl,
KeyModifierMask currentMask,
KeyModifierMask desiredMask, EKeyAction action) const
{
// translate the character into its virtual key and its required
// modifier state.
SHORT virtualKeyAndModifierState = VkKeyScanEx(c, hkl);
// get virtual key
UINT virtualKey = LOBYTE(virtualKeyAndModifierState);
// get the required modifier state
BYTE modifierState = HIBYTE(virtualKeyAndModifierState);
// compute the final desired modifier mask. this is the
// desired modifier mask except that the system might require
// that certain modifiers be up or down in order to generate
// the character. to start with, we know that we want to keep
// the caps lock, num lock, scroll lock modifiers as is. also,
// the system never requires the meta or super modifiers so we
// can set those however we like.
KeyModifierMask outMask = (currentMask &
(KeyModifierCapsLock |
KeyModifierNumLock |
KeyModifierScrollLock));
outMask |= (desiredMask &
(KeyModifierMeta |
KeyModifierSuper));
// win32 does not permit ctrl and alt used together to
// modify a character because ctrl and alt together mean
// AltGr. if the desired mask has both ctrl and alt then
// strip them both out.
if ((desiredMask & (KeyModifierControl | KeyModifierAlt)) ==
(KeyModifierControl | KeyModifierAlt)) {
outMask &= ~(KeyModifierControl | KeyModifierAlt);
}
// strip out the desired shift state. we're forced to use
// a particular shift state to generate the desired character.
outMask &= ~KeyModifierShift;
// use the required modifiers. if AltGr is required then
// modifierState will indicate control and alt.
if ((modifierState & 1) != 0) {
outMask |= KeyModifierShift; outMask |= KeyModifierShift;
} }
if ((modifierState & 2) != 0) {
outMask |= KeyModifierControl;
}
if ((modifierState & 4) != 0) {
outMask |= KeyModifierAlt;
} }
LOG((CLOG_DEBUG2 "KeyID %d to virtual key %d mask 0x%04x", id, virtualKey, outMask));
// handle combination of caps-lock and shift. if caps-lock is
// off locally then use shift as necessary. if caps-lock is on
// locally then it reverses the meaning of shift for keys that
// are subject to case conversion.
if ((outMask & KeyModifierCapsLock) != 0) {
// there doesn't seem to be a simple way to test if a
// character respects the caps lock key. for normal
// characters it's easy enough but CharLower() and
// CharUpper() don't map dead keys even though they
// do respect caps lock for some unfathomable reason.
// first check the easy way. if that doesn't work
// then see if it's a dead key.
unsigned char uc = static_cast<unsigned char>(c);
if (CharLower((LPTSTR)uc) != CharUpper((LPTSTR)uc) ||
(MapVirtualKey(virtualKey, 2) & 0x80000000lu) != 0) {
LOG((CLOG_DEBUG2 "flip shift"));
outMask ^= KeyModifierShift;
}
}
// now generate the keystrokes. ignore the resulting modifier
// mask since it can't have changed (because we don't call this
// method for modifier keys).
LOG((CLOG_DEBUG2 "character %d to virtual key %d mask 0x%04x", (unsigned char)c, virtualKey, outMask));
mapToKeystrokes(keys, virtualKey, currentMask, outMask, action);
return virtualKey;
}
KeyModifierMask
CMSWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys,
UINT virtualKey,
KeyModifierMask currentMask,
KeyModifierMask desiredMask, EKeyAction action) const
{
// a list of modifier key info // a list of modifier key info
class CModifierInfo { class CModifierInfo {
public: public:
@ -996,9 +1105,12 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
static const unsigned int s_numModifiers = static const unsigned int s_numModifiers =
sizeof(s_modifier) / sizeof(s_modifier[0]); sizeof(s_modifier) / sizeof(s_modifier[0]);
// strip out extended key flag
UINT virtualKey2 = (virtualKey & ~0x100);
// note if the key is a modifier // note if the key is a modifier
unsigned int modifierIndex; unsigned int modifierIndex;
switch (virtualKey) { switch (virtualKey2) {
case VK_SHIFT: case VK_SHIFT:
case VK_LSHIFT: case VK_LSHIFT:
case VK_RSHIFT: case VK_RSHIFT:
@ -1040,18 +1152,17 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
} }
const bool isModifier = (modifierIndex != s_numModifiers); const bool isModifier = (modifierIndex != s_numModifiers);
// add the key events required to get to the modifier state // add the key events required to get to the desired modifier state.
// necessary to generate an event yielding id. also save the // also save the key events required to restore the current state.
// key events required to restore the state. if the key is // if the key is a modifier key then skip this because modifiers
// a modifier key then skip this because modifiers should not // should not modify modifiers.
// modify modifiers.
Keystrokes undo; Keystrokes undo;
Keystroke keystroke; Keystroke keystroke;
if (outMask != m_mask && !isModifier) { if (desiredMask != currentMask && !isModifier) {
for (unsigned int i = 0; i < s_numModifiers; ++i) { for (unsigned int i = 0; i < s_numModifiers; ++i) {
KeyModifierMask bit = s_modifier[i].m_mask; KeyModifierMask bit = s_modifier[i].m_mask;
if ((outMask & bit) != (m_mask & bit)) { if ((desiredMask & bit) != (currentMask & bit)) {
if ((outMask & bit) != 0) { if ((desiredMask & bit) != 0) {
// modifier is not active but should be. if the // modifier is not active but should be. if the
// modifier is a toggle then toggle it on with a // modifier is a toggle then toggle it on with a
// press/release, otherwise activate it with a // press/release, otherwise activate it with a
@ -1117,9 +1228,6 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
// add the key event // add the key event
keystroke.m_virtualKey = virtualKey; keystroke.m_virtualKey = virtualKey;
if (isExtended) {
keystroke.m_virtualKey |= 0x100;
}
switch (action) { switch (action) {
case kPress: case kPress:
keystroke.m_press = true; keystroke.m_press = true;
@ -1149,7 +1257,7 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
// if the key is a modifier key then compute the modifier mask after // if the key is a modifier key then compute the modifier mask after
// this key is pressed. // this key is pressed.
mask = m_mask; KeyModifierMask mask = currentMask;
if (isModifier && action != kRepeat) { if (isModifier && action != kRepeat) {
// toggle keys modify the state on release. other keys set // toggle keys modify the state on release. other keys set
// the bit on press and clear the bit on release. // the bit on press and clear the bit on release.
@ -1166,12 +1274,12 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
// can't reset bit until all keys that set it are released. // can't reset bit until all keys that set it are released.
// scan those keys to see if any are pressed. // scan those keys to see if any are pressed.
bool down = false; bool down = false;
if (virtualKey != (modifier.m_virtualKey & 0xff) && if (virtualKey2 != (modifier.m_virtualKey & 0xff) &&
(m_keys[modifier.m_virtualKey & 0xff] & 0x80) != 0) { (m_keys[modifier.m_virtualKey & 0xff] & 0x80) != 0) {
down = true; down = true;
} }
if (modifier.m_virtualKey2 != 0 && if (modifier.m_virtualKey2 != 0 &&
virtualKey != (modifier.m_virtualKey2 & 0xff) && virtualKey2 != (modifier.m_virtualKey2 & 0xff) &&
(m_keys[modifier.m_virtualKey2 & 0xff] & 0x80) != 0) { (m_keys[modifier.m_virtualKey2 & 0xff] & 0x80) != 0) {
down = true; down = true;
} }
@ -1180,7 +1288,7 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey,
} }
} }
LOG((CLOG_DEBUG2 "previous modifiers 0x%04x, final modifiers 0x%04x", m_mask, mask)); LOG((CLOG_DEBUG2 "previous modifiers 0x%04x, final modifiers 0x%04x", currentMask, mask));
return mask; return mask;
} }
@ -1376,3 +1484,28 @@ CMSWindowsSecondaryScreen::sendKeyEvent(UINT virtualKey, bool press)
static_cast<BYTE>(code), flags, 0); static_cast<BYTE>(code), flags, 0);
LOG((CLOG_DEBUG1 "send key %d, 0x%04x, %s%s", virtualKey & 0xff, code, ((flags & KEYEVENTF_KEYUP) ? "release" : "press"), ((flags & KEYEVENTF_EXTENDEDKEY) ? " extended" : ""))); LOG((CLOG_DEBUG1 "send key %d, 0x%04x, %s%s", virtualKey & 0xff, code, ((flags & KEYEVENTF_KEYUP) ? "release" : "press"), ((flags & KEYEVENTF_EXTENDEDKEY) ? " extended" : "")));
} }
UINT
CMSWindowsSecondaryScreen::getCodePageFromLangID(LANGID langid) const
{
// construct a locale id from the language id
LCID lcid = MAKELCID(langid, SORT_DEFAULT);
// get the ANSI code page for this locale
char data[6];
if (GetLocaleInfoA(lcid, LOCALE_IDEFAULTANSICODEPAGE, data, 6) == 0) {
// can't get code page
LOG((CLOG_DEBUG1 "can't find code page for langid 0x%04x", langid));
return CP_ACP;
}
// convert stringified code page into a number
UINT codePage = static_cast<UINT>(atoi(data));
if (codePage == 0) {
// parse failed
LOG((CLOG_DEBUG1 "can't parse code page %s for langid 0x%04x", data, langid));
return CP_ACP;
}
return codePage;
}

View File

@ -103,6 +103,16 @@ private:
bool press, DWORD* data) const; bool press, DWORD* data) const;
KeyModifierMask mapKey(Keystrokes&, UINT& virtualKey, KeyID, KeyModifierMask mapKey(Keystrokes&, UINT& virtualKey, KeyID,
KeyModifierMask, EKeyAction) const; KeyModifierMask, EKeyAction) const;
UINT mapCharacter(Keystrokes& keys,
char c, HKL hkl,
KeyModifierMask currentMask,
KeyModifierMask desiredMask,
EKeyAction action) const;
KeyModifierMask mapToKeystrokes(Keystrokes& keys,
UINT virtualKey,
KeyModifierMask currentMask,
KeyModifierMask desiredMask,
EKeyAction action) const;
void doKeystrokes(const Keystrokes&, SInt32 count); void doKeystrokes(const Keystrokes&, SInt32 count);
void toggleKey(UINT virtualKey, KeyModifierMask mask); void toggleKey(UINT virtualKey, KeyModifierMask mask);
@ -110,6 +120,8 @@ private:
bool isExtendedKey(UINT virtualKey) const; bool isExtendedKey(UINT virtualKey) const;
void sendKeyEvent(UINT virtualKey, bool press); void sendKeyEvent(UINT virtualKey, bool press);
UINT getCodePageFromLangID(LANGID) const;
private: private:
CMutex m_mutex; CMutex m_mutex;
CMSWindowsScreen* m_screen; CMSWindowsScreen* m_screen;