Win32 fixes. Fixed slightly off cursor positioning when using

absolute mouse_event().  Improved keyboard handling:  now using
keyboard layout of last foreground window when leaving server
so users can meaningfully choose the locale, moved dead key
handling into hook library so there should be no more race
conditions involving the keyboard dead key buffer, simplified
keyboard and cursor handling by using a full screen transparent
window when not using low level hooks, fixed error in restoring
buffered dead key when checking for dead keys.  This hopefully
fixes all known keyboard bugs on win32.
This commit is contained in:
crs 2004-03-08 20:53:32 +00:00
parent f068232643
commit 9e995bedbf
6 changed files with 353 additions and 277 deletions

View File

@ -706,7 +706,7 @@ const KeyButton CMSWindowsKeyMapper::s_mapEF00[] =
CMSWindowsKeyMapper::CMSWindowsKeyMapper() : m_deadKey(0) CMSWindowsKeyMapper::CMSWindowsKeyMapper() : m_deadKey(0)
{ {
// do nothing m_keyLayout = GetKeyboardLayout(0);
} }
CMSWindowsKeyMapper::~CMSWindowsKeyMapper() CMSWindowsKeyMapper::~CMSWindowsKeyMapper()
@ -872,6 +872,12 @@ CMSWindowsKeyMapper::updateKey(KeyButton key, bool pressed)
} }
} }
void
CMSWindowsKeyMapper::setKeyLayout(HKL keyLayout)
{
m_keyLayout = keyLayout;
}
KeyButton KeyButton
CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys,
const IKeyState& keyState, KeyID id, const IKeyState& keyState, KeyID id,
@ -1057,8 +1063,8 @@ CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys,
} }
KeyID KeyID
CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM vkCode, LPARAM info, CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM charAndVirtKey,
KeyModifierMask* maskOut, bool* altgr) const LPARAM info, KeyModifierMask* maskOut, bool* altgr) const
{ {
// note: known microsoft bugs // note: known microsoft bugs
// Q72583 -- MapVirtualKey() maps keypad keys incorrectly // Q72583 -- MapVirtualKey() maps keypad keys incorrectly
@ -1066,59 +1072,18 @@ CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM vkCode, LPARAM info,
// 95,98,NT4: num pad scan code -> bad vk code except // 95,98,NT4: num pad scan code -> bad vk code except
// SEPARATOR, MULTIPLY, SUBTRACT, ADD // SEPARATOR, MULTIPLY, SUBTRACT, ADD
HKL hkl = GetKeyboardLayout(0); char c = (char)((charAndVirtKey & 0xff00u) >> 8);
UINT vkCode = (charAndVirtKey & 0xffu);
// get the scan code and the extended keyboard flag // get the scan code and the extended keyboard flag
UINT scanCode = static_cast<UINT>((info & 0x00ff0000u) >> 16); UINT scanCode = static_cast<UINT>((info & 0x00ff0000u) >> 16);
int extended = ((info & 0x01000000) == 0) ? 0 : 1; int extended = ((info & 0x01000000) == 0) ? 0 : 1;
bool press = ((info & 0x80000000) == 0);
LOG((CLOG_DEBUG1 "key vk=%d info=0x%08x ext=%d scan=%d", vkCode, info, extended, scanCode)); LOG((CLOG_DEBUG1 "key vk=%d info=0x%08x ext=%d scan=%d", vkCode, info, extended, scanCode));
// handle some keys via table lookup // handle some keys via table lookup
char c = 0;
KeyID id = s_virtualKey[vkCode][extended]; KeyID id = s_virtualKey[vkCode][extended];
if (id == kKeyNone) { if (id == kKeyNone) {
// not in table // not in table; map character to key id
// save the control state then clear it. ToAscii() maps ctrl+letter
// to the corresponding control code and ctrl+backspace to delete.
// we don't want that translation so we clear the control modifier
// state. however, if we want to simulate AltGr (which is ctrl+alt)
// then we must not clear it.
BYTE keys[256];
memcpy(keys, m_keys, sizeof(keys));
BYTE control = keys[VK_CONTROL];
BYTE menu = keys[VK_MENU];
if ((control & 0x80) == 0 || (menu & 0x80) == 0) {
keys[VK_LCONTROL] = 0;
keys[VK_RCONTROL] = 0;
keys[VK_CONTROL] = 0;
}
else {
keys[VK_LCONTROL] = 0x80;
keys[VK_CONTROL] = 0x80;
keys[VK_LMENU] = 0x80;
keys[VK_MENU] = 0x80;
}
// map to a character
bool isMenu = ((menu & 0x80) != 0);
c = mapToCharacter(vkCode, scanCode, keys, press, isMenu, hkl);
// if mapping failed and ctrl and alt are pressed then try again
// with both not pressed. this handles the case where ctrl and
// alt are being used as individual modifiers rather than AltGr.
if (c == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) {
keys[VK_LCONTROL] = 0;
keys[VK_RCONTROL] = 0;
keys[VK_CONTROL] = 0;
keys[VK_LMENU] = 0;
keys[VK_RMENU] = 0;
keys[VK_MENU] = 0;
c = mapToCharacter(vkCode, scanCode, keys, press, isMenu, hkl);
}
// map character to key id
if (c != 0) { if (c != 0) {
if ((c & 0x80u) != 0) { if ((c & 0x80u) != 0) {
// character is not really ASCII. instead it's some // character is not really ASCII. instead it's some
@ -1155,7 +1120,7 @@ CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM vkCode, LPARAM info,
// required (only because it solves the problems we've seen // required (only because it solves the problems we've seen
// so far). in the second, we'll use whatever the keyboard // so far). in the second, we'll use whatever the keyboard
// state says. // state says.
WORD virtualKeyAndModifierState = VkKeyScanEx(c, hkl); WORD virtualKeyAndModifierState = VkKeyScanEx(c, m_keyLayout);
if (virtualKeyAndModifierState == 0xffff) { if (virtualKeyAndModifierState == 0xffff) {
// there is no mapping. assume AltGr. // there is no mapping. assume AltGr.
LOG((CLOG_DEBUG1 "no VkKeyScan() mapping")); LOG((CLOG_DEBUG1 "no VkKeyScan() mapping"));
@ -1233,8 +1198,7 @@ UINT
CMSWindowsKeyMapper::keyToScanCode(KeyButton* virtualKey) const CMSWindowsKeyMapper::keyToScanCode(KeyButton* virtualKey) const
{ {
// try mapping given virtual key // try mapping given virtual key
HKL hkl = GetKeyboardLayout(0); UINT code = MapVirtualKeyEx((*virtualKey) & 0xffu, 0, m_keyLayout);
UINT code = MapVirtualKeyEx((*virtualKey) & 0xffu, 0, hkl);
if (code != 0) { if (code != 0) {
return code; return code;
} }
@ -1265,17 +1229,17 @@ CMSWindowsKeyMapper::keyToScanCode(KeyButton* virtualKey) const
case VK_LSHIFT: case VK_LSHIFT:
case VK_RSHIFT: case VK_RSHIFT:
*virtualKey = VK_SHIFT; *virtualKey = VK_SHIFT;
return MapVirtualKeyEx(VK_SHIFT, 0, hkl); return MapVirtualKeyEx(VK_SHIFT, 0, m_keyLayout);
case VK_LCONTROL: case VK_LCONTROL:
case VK_RCONTROL: case VK_RCONTROL:
*virtualKey = VK_CONTROL; *virtualKey = VK_CONTROL;
return MapVirtualKeyEx(VK_CONTROL, 0, hkl); return MapVirtualKeyEx(VK_CONTROL, 0, m_keyLayout);
case VK_LMENU: case VK_LMENU:
case VK_RMENU: case VK_RMENU:
*virtualKey = VK_MENU; *virtualKey = VK_MENU;
return MapVirtualKeyEx(VK_MENU, 0, hkl); return MapVirtualKeyEx(VK_MENU, 0, m_keyLayout);
default: default:
return 0; return 0;
@ -1528,93 +1492,9 @@ CMSWindowsKeyMapper::isDeadChar(TCHAR c, HKL hkl, bool menu) const
toAscii(' ', hkl, 0, &dummy); toAscii(' ', hkl, 0, &dummy);
// put old dead key back if there was one // put old dead key back if there was one
if (old == 2) { if (old == 1 && ascii != ' ') {
toAscii(static_cast<TCHAR>(ascii & 0xffu), hkl, menu, &dummy); toAscii(static_cast<TCHAR>(ascii & 0xffu), hkl, menu, &dummy);
} }
return isDead; return isDead;
} }
bool
CMSWindowsKeyMapper::putBackDeadChar(TCHAR c, HKL hkl, bool menu) const
{
return (toAscii(c, hkl, menu, NULL) < 0);
}
TCHAR
CMSWindowsKeyMapper::getSavedDeadChar(HKL hkl) const
{
WORD old;
int nOld = toAscii(' ', hkl, false, &old);
if (nOld == 1 || nOld == 2) {
TCHAR c = static_cast<TCHAR>(old & 0xffu);
if (nOld == 2 || isDeadChar(c, hkl, false)) {
return c;
}
}
return 0;
}
char
CMSWindowsKeyMapper::mapToCharacter(UINT vkCode, UINT scanCode,
BYTE* keys, bool press, bool isMenu, HKL hkl) const
{
// get contents of keyboard layout buffer and clear out that
// buffer. we don't want anything placed there by some other
// app interfering and we need to put anything there back in
// place when we're done.
TCHAR oldDeadKey = getSavedDeadChar(hkl);
// put our previous dead key, if any, in the layout buffer
putBackDeadChar(m_deadKey, hkl, false);
m_deadKey = 0;
// process key
WORD ascii;
int result = ToAsciiEx(vkCode, scanCode, keys, &ascii,
isMenu ? 1 : 0, hkl);
// if result is less than zero then it was a dead key
char c = 0;
if (result < 0) {
// save dead key if a key press. we catch the dead key
// release in the result == 2 case below.
if (press) {
m_deadKey = static_cast<TCHAR>(ascii & 0xffu);
}
}
// if result is 1 then the key was succesfully converted
else if (result == 1) {
c = static_cast<char>(ascii & 0xff);
}
// if result is 2 and the two characters are the same and this
// is a key release then a dead key was released. save the
// dead key. if the two characters are the same and this is
// not a release then a dead key was pressed twice. send the
// dead key.
else if (result == 2) {
if (((ascii & 0xff00u) >> 8) == (ascii & 0x00ffu)) {
if (!press) {
m_deadKey = static_cast<TCHAR>(ascii & 0xffu);
}
else {
putBackDeadChar(oldDeadKey, hkl, false);
result = toAscii(' ', hkl, false, &ascii);
c = static_cast<char>((ascii >> 8) & 0xffu);
}
}
}
// clear keyboard layout buffer. this removes any dead key we
// may have just put there.
toAscii(' ', hkl, false, NULL);
// restore keyboard layout buffer so a dead key inserted by
// another app doesn't disappear mysteriously (from its point
// of view).
putBackDeadChar(oldDeadKey, hkl, false);
return c;
}

View File

@ -44,6 +44,13 @@ public:
*/ */
void updateKey(KeyButton key, bool pressed); void updateKey(KeyButton key, bool pressed);
//! Set the active keyboard layout
/*!
Uses \p keyLayout when finding scan codes via \c keyToScanCode()
and mapping keys in \c mapKeyFromEvent().
*/
void setKeyLayout(HKL keyLayout);
//@} //@}
//! @name accessors //! @name accessors
//@{ //@{
@ -67,7 +74,7 @@ public:
to a modifier mask. If \c altgr is non-NULL it's set to true if to a modifier mask. If \c altgr is non-NULL it's set to true if
the key requires AltGr and false otherwise. the key requires AltGr and false otherwise.
*/ */
KeyID mapKeyFromEvent(WPARAM vkCode, LPARAM info, KeyID mapKeyFromEvent(WPARAM charAndVirtKey, LPARAM info,
KeyModifierMask* maskOut, bool* altgr) const; KeyModifierMask* maskOut, bool* altgr) const;
//! Test shadow key state //! Test shadow key state
@ -127,19 +134,6 @@ private:
// return true iff \c c is a dead character // return true iff \c c is a dead character
bool isDeadChar(TCHAR c, HKL hkl, bool menu) const; bool isDeadChar(TCHAR c, HKL hkl, bool menu) const;
// put back dead key into ToAscii() internal buffer. returns true
// iff the character was a dead key.
bool putBackDeadChar(TCHAR c, HKL hkl, bool menu) const;
// get the dead key saved in the given keyboard layout, or 0 if none
TCHAR getSavedDeadChar(HKL hkl) const;
// map the given virtual key, scan code, and keyboard state to a
// character, if possible. this has the side effect of updating
// m_deadKey.
char mapToCharacter(UINT vkCode, UINT scanCode,
BYTE* keys, bool press, bool isMenu, HKL hkl) const;
private: private:
class CModifierKeys { class CModifierKeys {
public: public:
@ -150,6 +144,7 @@ private:
BYTE m_keys[256]; BYTE m_keys[256];
mutable TCHAR m_deadKey; mutable TCHAR m_deadKey;
HKL m_keyLayout;
static const CModifierKeys s_modifiers[]; static const CModifierKeys s_modifiers[];
static const char* s_vkToName[]; static const char* s_vkToName[];

View File

@ -128,6 +128,7 @@ CMSWindowsScreen::CMSWindowsScreen(bool isPrimary) :
m_sequenceNumber(0), m_sequenceNumber(0),
m_mark(0), m_mark(0),
m_markReceived(0), m_markReceived(0),
m_keyLayout(NULL),
m_timer(NULL), m_timer(NULL),
m_screensaver(NULL), m_screensaver(NULL),
m_screensaverNotify(false), m_screensaverNotify(false),
@ -290,7 +291,17 @@ CMSWindowsScreen::enter()
bool bool
CMSWindowsScreen::leave() CMSWindowsScreen::leave()
{ {
sendDeskMessage(SYNERGY_MSG_LEAVE, 0, 0); // get keyboard layout of foreground window. we'll use this
// keyboard layout for translating keys sent to clients.
HWND window = GetForegroundWindow();
DWORD thread = GetWindowThreadProcessId(window, NULL);
m_keyLayout = GetKeyboardLayout(thread);
// tell the key mapper about the keyboard layout
m_keyMapper.setKeyLayout(m_keyLayout);
// tell desk that we're leaving and tell it the keyboard layout
sendDeskMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0);
if (m_isPrimary) { if (m_isPrimary) {
/* XXX /* XXX
@ -543,7 +554,7 @@ CMSWindowsScreen::fakeKeyEvent(KeyButton virtualKey, bool press) const
if (!press) { if (!press) {
flags |= KEYEVENTF_KEYUP; flags |= KEYEVENTF_KEYUP;
} }
const UINT code = m_keyMapper.keyToScanCode(&virtualKey); UINT code = m_keyMapper.keyToScanCode(&virtualKey);
sendDeskMessage(SYNERGY_MSG_FAKE_KEY, flags, sendDeskMessage(SYNERGY_MSG_FAKE_KEY, flags,
MAKEWORD(static_cast<BYTE>(code), MAKEWORD(static_cast<BYTE>(code),
static_cast<BYTE>(virtualKey & 0xffu))); static_cast<BYTE>(virtualKey & 0xffu)));
@ -977,6 +988,9 @@ CMSWindowsScreen::onMark(UInt32 mark)
bool bool
CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
{ {
WPARAM charAndVirtKey = wParam;
wParam &= 0xffu;
// ignore message if posted prior to last mark change // ignore message if posted prior to last mark change
if (!ignore()) { if (!ignore()) {
// check for ctrl+alt+del emulation // check for ctrl+alt+del emulation
@ -992,8 +1006,8 @@ CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
// process key normally // process key normally
bool altgr; bool altgr;
KeyModifierMask mask; KeyModifierMask mask;
const KeyID key = m_keyMapper.mapKeyFromEvent(wParam, const KeyID key = m_keyMapper.mapKeyFromEvent(
lParam, &mask, &altgr); charAndVirtKey, lParam, &mask, &altgr);
KeyButton button = static_cast<KeyButton>( KeyButton button = static_cast<KeyButton>(
(lParam & 0x00ff0000u) >> 16); (lParam & 0x00ff0000u) >> 16);
if (key != kKeyNone && key != kKeyMultiKey) { if (key != kKeyNone && key != kKeyMultiKey) {
@ -1654,8 +1668,8 @@ CMSWindowsScreen::deskMouseMove(SInt32 x, SInt32 y) const
SInt32 w = GetSystemMetrics(SM_CXSCREEN); SInt32 w = GetSystemMetrics(SM_CXSCREEN);
SInt32 h = GetSystemMetrics(SM_CYSCREEN); SInt32 h = GetSystemMetrics(SM_CYSCREEN);
mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
(DWORD)((65536.0 * x) / w), (DWORD)((65535.0f * x) / (w - 1) + 0.5f),
(DWORD)((65536.0 * y) / h), (DWORD)((65535.0f * y) / (h - 1) + 0.5f),
0, 0); 0, 0);
} }
@ -1712,43 +1726,45 @@ CMSWindowsScreen::deskMouseMove(SInt32 x, SInt32 y) const
} }
void void
CMSWindowsScreen::deskEnter(CDesk* desk, DWORD& cursorThreadID) CMSWindowsScreen::deskEnter(CDesk* desk)
{ {
if (m_isPrimary) { if (m_isPrimary) {
if (desk->m_lowLevel) {
if (cursorThreadID != 0) {
AttachThreadInput(desk->m_threadID, cursorThreadID, TRUE);
ShowCursor(TRUE); ShowCursor(TRUE);
AttachThreadInput(desk->m_threadID, cursorThreadID, FALSE);
cursorThreadID = 0;
}
}
} }
ShowWindow(desk->m_window, SW_HIDE); ShowWindow(desk->m_window, SW_HIDE);
} }
void void
CMSWindowsScreen::deskLeave(CDesk* desk, DWORD& cursorThreadID) CMSWindowsScreen::deskLeave(CDesk* desk, HKL keyLayout)
{ {
if (m_isPrimary) { if (m_isPrimary) {
// we don't need a window to capture input but we need a window // map a window to hide the cursor and to use whatever keyboard
// to hide the cursor when using low-level hooks. also take the // layout we choose rather than the keyboard layout of the last
// activation so we use our keyboard layout, not the layout of // active window.
// whatever window was active. int x, y, w, h;
if (desk->m_lowLevel) { if (desk->m_lowLevel) {
SetWindowPos(desk->m_window, HWND_TOPMOST, // with a low level hook the cursor will never budge so
m_xCenter, m_yCenter, 1, 1, SWP_NOACTIVATE); // just a 1x1 window is sufficient.
x = m_xCenter;
y = m_yCenter;
w = 1;
h = 1;
}
else {
// with regular hooks the cursor will jitter as it's moved
// by the user then back to the center by us. to be sure
// we never lose it, cover all the monitors with the window.
x = m_x;
y = m_y;
w = m_w;
h = m_h;
}
SetWindowPos(desk->m_window, HWND_TOPMOST, x, y, w, h, SWP_NOACTIVATE);
ShowWindow(desk->m_window, SW_SHOW); ShowWindow(desk->m_window, SW_SHOW);
if (cursorThreadID == 0) {
HWND hwnd = GetForegroundWindow();
cursorThreadID = GetWindowThreadProcessId(hwnd, NULL);
if (cursorThreadID != 0) {
AttachThreadInput(desk->m_threadID, cursorThreadID, TRUE);
ShowCursor(FALSE); ShowCursor(FALSE);
AttachThreadInput(desk->m_threadID, cursorThreadID, FALSE);
} // switch to requested keyboard layout
} ActivateKeyboardLayout(keyLayout, 0);
}
} }
else { else {
// move hider window under the cursor center // move hider window under the cursor center
@ -1767,9 +1783,6 @@ CMSWindowsScreen::deskThread(void* vdesk)
{ {
MSG msg; MSG msg;
// id of thread that had cursor when we were last told to hide it
DWORD cursorThreadID = 0;
// use given desktop for this thread // use given desktop for this thread
CDesk* desk = reinterpret_cast<CDesk*>(vdesk); CDesk* desk = reinterpret_cast<CDesk*>(vdesk);
desk->m_threadID = GetCurrentThreadId(); desk->m_threadID = GetCurrentThreadId();
@ -1833,11 +1846,11 @@ CMSWindowsScreen::deskThread(void* vdesk)
break; break;
case SYNERGY_MSG_ENTER: case SYNERGY_MSG_ENTER:
deskEnter(desk, cursorThreadID); deskEnter(desk);
break; break;
case SYNERGY_MSG_LEAVE: case SYNERGY_MSG_LEAVE:
deskLeave(desk, cursorThreadID); deskLeave(desk, (HKL)msg.wParam);
break; break;
case SYNERGY_MSG_FAKE_KEY: case SYNERGY_MSG_FAKE_KEY:
@ -1969,7 +1982,7 @@ CMSWindowsScreen::checkDesk()
// hide cursor on new desk // hide cursor on new desk
if (!m_isOnScreen) { if (!m_isOnScreen) {
sendDeskMessage(SYNERGY_MSG_LEAVE, 0, 0); sendDeskMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0);
} }
} }
} }

View File

@ -186,8 +186,8 @@ private:
static LRESULT CALLBACK secondaryDeskProc(HWND, UINT, WPARAM, LPARAM); static LRESULT CALLBACK secondaryDeskProc(HWND, UINT, WPARAM, LPARAM);
void deskMouseMove(SInt32 x, SInt32 y) const; void deskMouseMove(SInt32 x, SInt32 y) const;
void deskEnter(CDesk* desk, DWORD& cursorThreadID); void deskEnter(CDesk* desk);
void deskLeave(CDesk* desk, DWORD& cursorThreadID); void deskLeave(CDesk* desk, HKL keyLayout);
void deskThread(void* vdesk); void deskThread(void* vdesk);
CDesk* addDesk(const CString& name, HDESK hdesk); CDesk* addDesk(const CString& name, HDESK hdesk);
void removeDesks(); void removeDesks();
@ -234,6 +234,9 @@ private:
// the main loop's thread id // the main loop's thread id
DWORD m_threadID; DWORD m_threadID;
// the keyboard layout to use when off primary screen
HKL m_keyLayout;
// the timer used to check for desktop switching // the timer used to check for desktop switching
CEventQueueTimer* m_timer; CEventQueueTimer* m_timer;

View File

@ -24,6 +24,12 @@
// //
#define NO_GRAB_KEYBOARD 0 #define NO_GRAB_KEYBOARD 0
//
// debugging compile flag. when not zero the server will not
// install low level hooks.
//
#define NO_LOWLEVEL_HOOKS 0
// //
// extra mouse wheel stuff // extra mouse wheel stuff
// //
@ -86,8 +92,11 @@ static SInt32 g_xScreen = 0;
static SInt32 g_yScreen = 0; static SInt32 g_yScreen = 0;
static SInt32 g_wScreen = 0; static SInt32 g_wScreen = 0;
static SInt32 g_hScreen = 0; static SInt32 g_hScreen = 0;
static HCURSOR g_cursor = NULL; static WPARAM g_deadVirtKey = 0;
static DWORD g_cursorThread = 0; static LPARAM g_deadLParam = 0;
static BYTE g_deadKeyState[256] = { 0 };
static DWORD g_hookThread = 0;
static DWORD g_attachedThread = 0;
#pragma data_seg() #pragma data_seg()
@ -105,49 +114,217 @@ extern "C" int _fltused=0;
static static
void void
hideCursor(DWORD thread) attachThreadToForeground()
{ {
// we should be running the context of the window who's cursor // only attach threads if using low level hooks. a low level hook
// we want to hide so we shouldn't have to attach thread input // runs in the thread that installed the hook but we have to make
// but we'll check to make sure. // changes that require being attached to the target thread (which
g_cursorThread = thread; // should be the foreground window). a regular hook runs in the
if (g_cursorThread != 0) { // thread that just removed the event from its queue so we're
DWORD myThread = GetCurrentThreadId(); // already in the right thread.
if (myThread != g_cursorThread) if (g_hookThread != 0) {
AttachThreadInput(myThread, g_cursorThread, TRUE); HWND window = GetForegroundWindow();
g_cursor = SetCursor(NULL); DWORD threadID = GetWindowThreadProcessId(window, NULL);
if (myThread != g_cursorThread) // skip if no change
AttachThreadInput(myThread, g_cursorThread, FALSE); if (g_attachedThread != threadID) {
// detach from previous thread
if (g_attachedThread != 0 && g_attachedThread != g_hookThread) {
AttachThreadInput(g_hookThread, g_attachedThread, FALSE);
}
// attach to new thread
g_attachedThread = threadID;
if (g_attachedThread != 0 && g_attachedThread != g_hookThread) {
AttachThreadInput(g_hookThread, g_attachedThread, TRUE);
}
}
} }
} }
static static
void void
restoreCursor() detachThread()
{ {
// restore the show cursor in the window we hid it last if (g_attachedThread != 0) {
if (g_cursor != NULL && g_cursorThread != 0) { AttachThreadInput(g_hookThread, g_attachedThread, FALSE);
DWORD myThread = GetCurrentThreadId(); g_attachedThread = 0;
if (myThread != g_cursorThread)
AttachThreadInput(myThread, g_cursorThread, TRUE);
SetCursor(g_cursor);
if (myThread != g_cursorThread)
AttachThreadInput(myThread, g_cursorThread, FALSE);
} }
g_cursor = NULL;
g_cursorThread = 0;
} }
#if !NO_GRAB_KEYBOARD #if !NO_GRAB_KEYBOARD
static
WPARAM
makeKeyMsg(UINT virtKey, char c)
{
return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), 0);
}
static
void
keyboardGetState(BYTE keys[256])
{
if (g_hookThread != 0) {
GetKeyboardState(keys);
}
else {
SHORT key;
for (int i = 0; i < 256; ++i) {
key = GetAsyncKeyState(i);
keys[i] = (BYTE)((key < 0) ? 0x80u : 0);
}
key = GetKeyState(VK_CAPITAL);
keys[VK_CAPITAL] = (BYTE)(((key < 0) ? 0x80 : 0) | (key & 1));
}
}
static static
bool bool
keyboardHookHandler(WPARAM wParam, LPARAM lParam) keyboardHookHandler(WPARAM wParam, LPARAM lParam)
{ {
attachThreadToForeground();
// check for dead keys. we don't forward those to our window.
// instead we'll leave the key in the keyboard layout (a buffer
// internal to the system) for translation when the next key is
// pressed.
UINT c = MapVirtualKey(wParam, 2);
if ((c & 0x80000000u) != 0) {
if ((lParam & 0x80000000u) == 0) {
if (g_deadVirtKey == 0) {
// dead key press, no dead key in the buffer
g_deadVirtKey = wParam;
g_deadLParam = lParam;
keyboardGetState(g_deadKeyState);
return false;
}
// second dead key press in a row so let it pass
}
else {
// dead key release
return false;
}
}
// convert key to a character. this combines a saved dead key,
// if any, with this key. however, the dead key must remain in
// the keyboard layout for the application receiving this event
// so it can also convert the key to a character. we only do
// this on a key press.
WPARAM charAndVirtKey = (wParam & 0xffu);
if (c != 0) {
// we need the keyboard state for ToAscii()
BYTE keys[256];
keyboardGetState(keys);
// ToAscii() maps ctrl+letter to the corresponding control code
// and ctrl+backspace to delete. we don't want those translations
// so clear the control modifier state. however, if we want to
// simulate AltGr (which is ctrl+alt) then we must not clear it.
BYTE control = keys[VK_CONTROL];
BYTE menu = keys[VK_MENU];
if ((control & 0x80) == 0 || (menu & 0x80) == 0) {
keys[VK_LCONTROL] = 0;
keys[VK_RCONTROL] = 0;
keys[VK_CONTROL] = 0;
}
else {
keys[VK_LCONTROL] = 0x80;
keys[VK_CONTROL] = 0x80;
keys[VK_LMENU] = 0x80;
keys[VK_MENU] = 0x80;
}
// ToAscii() needs to know if a menu is active for some reason.
// we don't know and there doesn't appear to be any way to find
// out. so we'll just assume a menu is active if the menu key
// is down.
// XXX -- figure out some way to check if a menu is active
UINT flags = 0;
if ((menu & 0x80) != 0)
flags |= 1;
// map the key event to a character. this has the side
// effect of removing the dead key from the system's keyboard
// layout buffer.
WORD c = 0;
UINT scanCode = ((lParam & 0x00ff0000u) >> 16);
int n = ToAscii(wParam, scanCode, keys, &c, flags);
// if mapping failed and ctrl and alt are pressed then try again
// with both not pressed. this handles the case where ctrl and
// alt are being used as individual modifiers rather than AltGr.
// we have to put the dead key back first, if there was one.
if (n == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) {
if (g_deadVirtKey != 0) {
ToAscii(g_deadVirtKey, (g_deadLParam & 0x00ff0000u) >> 16,
g_deadKeyState, &c, flags);
}
keys[VK_LCONTROL] = 0;
keys[VK_RCONTROL] = 0;
keys[VK_CONTROL] = 0;
keys[VK_LMENU] = 0;
keys[VK_RMENU] = 0;
keys[VK_MENU] = 0;
n = ToAscii(wParam, scanCode, keys, &c, flags);
}
switch (n) {
default:
// key is a dead key; we're not expecting this since we
// bailed out above for any dead key.
g_deadVirtKey = wParam;
g_deadLParam = lParam;
break;
case 0:
// key doesn't map to a character. this can happen if
// non-character keys are pressed after a dead key.
break;
case 1:
// key maps to a character composed with dead key
charAndVirtKey = makeKeyMsg(wParam, (char)LOBYTE(c));
break;
case 2: {
// previous dead key not composed. send a fake key press
// and release for the dead key to our window.
WPARAM deadCharAndVirtKey =
makeKeyMsg(g_deadVirtKey, (char)LOBYTE(c));
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY,
deadCharAndVirtKey, g_deadLParam & 0x7fffffffu);
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY,
deadCharAndVirtKey, g_deadLParam | 0x80000000u);
// use uncomposed character
charAndVirtKey = makeKeyMsg(wParam, (char)HIBYTE(c));
break;
}
}
// put back the dead key, if any, for the application to use
if (g_deadVirtKey != 0) {
ToAscii(g_deadVirtKey, (g_deadLParam & 0x00ff0000u) >> 16,
g_deadKeyState, &c, flags);
}
// clear out old dead key state
g_deadVirtKey = 0;
g_deadLParam = 0;
}
// forward message to our window. do this whether or not we're // forward message to our window. do this whether or not we're
// forwarding events to clients because this'll keep our thread's // forwarding events to clients because this'll keep our thread's
// key state table up to date. that's important for querying // key state table up to date. that's important for querying
// the scroll lock toggle state. // the scroll lock toggle state.
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam); PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, charAndVirtKey, lParam);
// send fake key release if the user just pressed two dead keys
// in a row, otherwise we'll lose the release because we always
// return from the top of this function for all dead key releases.
if ((c & 0x80000000u) != 0) {
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY,
charAndVirtKey, lParam | 0x80000000u);
}
if (g_mode == kHOOK_RELAY_EVENTS) { if (g_mode == kHOOK_RELAY_EVENTS) {
// let certain keys pass through // let certain keys pass through
@ -160,8 +337,21 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam)
// lights may not stay synchronized. // lights may not stay synchronized.
break; break;
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
case VK_HANGUL:
// always pass the shift modifiers
break;
default: default:
// discard event // discard
return true; return true;
} }
} }
@ -174,6 +364,8 @@ static
bool bool
mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data)
{ {
attachThreadToForeground();
switch (wParam) { switch (wParam) {
case WM_LBUTTONDOWN: case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN: case WM_MBUTTONDOWN:
@ -213,19 +405,6 @@ mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data)
case WM_NCMOUSEMOVE: case WM_NCMOUSEMOVE:
case WM_MOUSEMOVE: case WM_MOUSEMOVE:
if (g_mode == kHOOK_RELAY_EVENTS) { if (g_mode == kHOOK_RELAY_EVENTS) {
// we want the cursor to be hidden at all times so we
// hide the cursor on whatever window has it. but then
// we have to show the cursor whenever we leave that
// window (or at some later time before we stop relaying).
// so check the window with the cursor. if it's not the
// same window that had it before then show the cursor
// in the last window and hide it in this window.
DWORD thread = GetCurrentThreadId();
if (thread != g_cursorThread) {
restoreCursor();
hideCursor(thread);
}
// relay and eat event // relay and eat event
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
return true; return true;
@ -347,7 +526,7 @@ getMessageHook(int code, WPARAM wParam, LPARAM lParam)
return CallNextHookEx(g_getMessage, code, wParam, lParam); return CallNextHookEx(g_getMessage, code, wParam, lParam);
} }
#if (_WIN32_WINNT >= 0x0400) #if (_WIN32_WINNT >= 0x0400) && !NO_LOWLEVEL_HOOKS
// //
// low-level keyboard hook -- this allows us to capture and handle // low-level keyboard hook -- this allows us to capture and handle
@ -531,8 +710,6 @@ init(DWORD threadID)
g_yScreen = 0; g_yScreen = 0;
g_wScreen = 0; g_wScreen = 0;
g_hScreen = 0; g_hScreen = 0;
g_cursor = NULL;
g_cursorThread = 0;
return 1; return 1;
} }
@ -562,6 +739,10 @@ install()
return kHOOK_FAILED; return kHOOK_FAILED;
} }
// discard old dead keys
g_deadVirtKey = 0;
g_deadLParam = 0;
// check for mouse wheel support // check for mouse wheel support
g_wheelSupport = getWheelSupport(); g_wheelSupport = getWheelSupport();
@ -573,35 +754,43 @@ install()
0); 0);
} }
// install keyboard hook // install low-level hooks. we require that they both get installed.
#if !NO_GRAB_KEYBOARD #if (_WIN32_WINNT >= 0x0400) && !NO_LOWLEVEL_HOOKS
#if (_WIN32_WINNT >= 0x0400)
g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL,
&keyboardLLHook,
g_hinstance,
0);
#endif
if (g_keyboardLL == NULL) {
g_keyboard = SetWindowsHookEx(WH_KEYBOARD,
&keyboardHook,
g_hinstance,
0);
}
#endif
// install mouse hook
#if (_WIN32_WINNT >= 0x0400)
g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL, g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL,
&mouseLLHook, &mouseLLHook,
g_hinstance, g_hinstance,
0); 0);
#if !NO_GRAB_KEYBOARD
g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL,
&keyboardLLHook,
g_hinstance,
0);
if (g_mouseLL == NULL || g_keyboardLL == NULL) {
if (g_keyboardLL != NULL) {
UnhookWindowsHookEx(g_keyboardLL);
g_keyboardLL = NULL;
}
if (g_mouseLL != NULL) {
UnhookWindowsHookEx(g_mouseLL);
g_mouseLL = NULL;
}
}
#endif #endif
#endif
// install regular hooks
if (g_mouseLL == NULL) { if (g_mouseLL == NULL) {
g_mouse = SetWindowsHookEx(WH_MOUSE, g_mouse = SetWindowsHookEx(WH_MOUSE,
&mouseHook, &mouseHook,
g_hinstance, g_hinstance,
0); 0);
} }
if (g_keyboardLL == NULL) {
g_keyboard = SetWindowsHookEx(WH_KEYBOARD,
&keyboardHook,
g_hinstance,
0);
}
// check that we got all the hooks we wanted // check that we got all the hooks we wanted
if ((g_getMessage == NULL && g_wheelSupport == kWheelOld) || if ((g_getMessage == NULL && g_wheelSupport == kWheelOld) ||
@ -614,6 +803,7 @@ install()
} }
if (g_keyboardLL != NULL || g_mouseLL != NULL) { if (g_keyboardLL != NULL || g_mouseLL != NULL) {
g_hookThread = GetCurrentThreadId();
return kHOOK_OKAY_LL; return kHOOK_OKAY_LL;
} }
@ -625,6 +815,13 @@ uninstall(void)
{ {
assert(g_hinstance != NULL); assert(g_hinstance != NULL);
// discard old dead keys
g_deadVirtKey = 0;
g_deadLParam = 0;
// detach from thread
detachThread();
// uninstall hooks // uninstall hooks
if (g_keyboardLL != NULL) { if (g_keyboardLL != NULL) {
UnhookWindowsHookEx(g_keyboardLL); UnhookWindowsHookEx(g_keyboardLL);
@ -648,9 +845,6 @@ uninstall(void)
} }
g_wheelSupport = kWheelNone; g_wheelSupport = kWheelNone;
// show the cursor
restoreCursor();
return 1; return 1;
} }
@ -719,9 +913,6 @@ setMode(EHookMode mode)
return; return;
} }
g_mode = mode; g_mode = mode;
if (g_mode != kHOOK_RELAY_EVENTS) {
restoreCursor();
}
} }
} }

View File

@ -1153,7 +1153,6 @@ void
CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button) CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button)
{ {
LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button)); LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button));
LOG((CLOG_INFO "onKeyDown: id=%d mask=0x%04x button=0x%04x", id, mask, button));
assert(m_active != NULL); assert(m_active != NULL);
// handle command keys // handle command keys
@ -1169,7 +1168,6 @@ void
CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button) CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button)
{ {
LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button)); LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button));
LOG((CLOG_INFO "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button));
assert(m_active != NULL); assert(m_active != NULL);
// handle command keys // handle command keys
@ -1186,7 +1184,6 @@ CServer::onKeyRepeat(KeyID id, KeyModifierMask mask,
SInt32 count, KeyButton button) SInt32 count, KeyButton button)
{ {
LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button));
LOG((CLOG_INFO "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button));
assert(m_active != NULL); assert(m_active != NULL);
// handle command keys // handle command keys
@ -1225,7 +1222,10 @@ CServer::onMouseMovePrimary(SInt32 x, SInt32 y)
LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y)); LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y));
// mouse move on primary (server's) screen // mouse move on primary (server's) screen
assert(m_active == m_primaryClient); if (m_active != m_primaryClient) {
// stale event -- we're actually on a secondary screen
return false;
}
// save position // save position
m_x = x; m_x = x;
@ -1282,13 +1282,7 @@ CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
// mouse move on secondary (client's) screen // mouse move on secondary (client's) screen
assert(m_active != NULL); assert(m_active != NULL);
if (m_active == m_primaryClient) { if (m_active == m_primaryClient) {
// we're actually on the primary screen. this can happen // stale event -- we're actually on the primary screen
// when the primary screen begins processing a mouse move
// for a secondary screen, then the active (secondary)
// screen disconnects causing us to jump to the primary
// screen, and finally the primary screen finishes
// processing the mouse move, still thinking it's for
// a secondary screen. we just ignore the motion.
return; return;
} }