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:
parent
f068232643
commit
9e995bedbf
|
@ -706,7 +706,7 @@ const KeyButton CMSWindowsKeyMapper::s_mapEF00[] =
|
|||
|
||||
CMSWindowsKeyMapper::CMSWindowsKeyMapper() : m_deadKey(0)
|
||||
{
|
||||
// do nothing
|
||||
m_keyLayout = GetKeyboardLayout(0);
|
||||
}
|
||||
|
||||
CMSWindowsKeyMapper::~CMSWindowsKeyMapper()
|
||||
|
@ -872,6 +872,12 @@ CMSWindowsKeyMapper::updateKey(KeyButton key, bool pressed)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
CMSWindowsKeyMapper::setKeyLayout(HKL keyLayout)
|
||||
{
|
||||
m_keyLayout = keyLayout;
|
||||
}
|
||||
|
||||
KeyButton
|
||||
CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys,
|
||||
const IKeyState& keyState, KeyID id,
|
||||
|
@ -1057,8 +1063,8 @@ CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys,
|
|||
}
|
||||
|
||||
KeyID
|
||||
CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM vkCode, LPARAM info,
|
||||
KeyModifierMask* maskOut, bool* altgr) const
|
||||
CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM charAndVirtKey,
|
||||
LPARAM info, KeyModifierMask* maskOut, bool* altgr) const
|
||||
{
|
||||
// note: known microsoft bugs
|
||||
// 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
|
||||
// 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
|
||||
UINT scanCode = static_cast<UINT>((info & 0x00ff0000u) >> 16);
|
||||
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));
|
||||
|
||||
// handle some keys via table lookup
|
||||
char c = 0;
|
||||
KeyID id = s_virtualKey[vkCode][extended];
|
||||
if (id == kKeyNone) {
|
||||
// not in table
|
||||
|
||||
// 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
|
||||
// not in table; map character to key id
|
||||
if (c != 0) {
|
||||
if ((c & 0x80u) != 0) {
|
||||
// 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
|
||||
// so far). in the second, we'll use whatever the keyboard
|
||||
// state says.
|
||||
WORD virtualKeyAndModifierState = VkKeyScanEx(c, hkl);
|
||||
WORD virtualKeyAndModifierState = VkKeyScanEx(c, m_keyLayout);
|
||||
if (virtualKeyAndModifierState == 0xffff) {
|
||||
// there is no mapping. assume AltGr.
|
||||
LOG((CLOG_DEBUG1 "no VkKeyScan() mapping"));
|
||||
|
@ -1233,8 +1198,7 @@ UINT
|
|||
CMSWindowsKeyMapper::keyToScanCode(KeyButton* virtualKey) const
|
||||
{
|
||||
// try mapping given virtual key
|
||||
HKL hkl = GetKeyboardLayout(0);
|
||||
UINT code = MapVirtualKeyEx((*virtualKey) & 0xffu, 0, hkl);
|
||||
UINT code = MapVirtualKeyEx((*virtualKey) & 0xffu, 0, m_keyLayout);
|
||||
if (code != 0) {
|
||||
return code;
|
||||
}
|
||||
|
@ -1265,17 +1229,17 @@ CMSWindowsKeyMapper::keyToScanCode(KeyButton* virtualKey) const
|
|||
case VK_LSHIFT:
|
||||
case VK_RSHIFT:
|
||||
*virtualKey = VK_SHIFT;
|
||||
return MapVirtualKeyEx(VK_SHIFT, 0, hkl);
|
||||
return MapVirtualKeyEx(VK_SHIFT, 0, m_keyLayout);
|
||||
|
||||
case VK_LCONTROL:
|
||||
case VK_RCONTROL:
|
||||
*virtualKey = VK_CONTROL;
|
||||
return MapVirtualKeyEx(VK_CONTROL, 0, hkl);
|
||||
return MapVirtualKeyEx(VK_CONTROL, 0, m_keyLayout);
|
||||
|
||||
case VK_LMENU:
|
||||
case VK_RMENU:
|
||||
*virtualKey = VK_MENU;
|
||||
return MapVirtualKeyEx(VK_MENU, 0, hkl);
|
||||
return MapVirtualKeyEx(VK_MENU, 0, m_keyLayout);
|
||||
|
||||
default:
|
||||
return 0;
|
||||
|
@ -1528,93 +1492,9 @@ CMSWindowsKeyMapper::isDeadChar(TCHAR c, HKL hkl, bool menu) const
|
|||
toAscii(' ', hkl, 0, &dummy);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,13 @@ public:
|
|||
*/
|
||||
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
|
||||
//@{
|
||||
|
@ -67,7 +74,7 @@ public:
|
|||
to a modifier mask. If \c altgr is non-NULL it's set to true if
|
||||
the key requires AltGr and false otherwise.
|
||||
*/
|
||||
KeyID mapKeyFromEvent(WPARAM vkCode, LPARAM info,
|
||||
KeyID mapKeyFromEvent(WPARAM charAndVirtKey, LPARAM info,
|
||||
KeyModifierMask* maskOut, bool* altgr) const;
|
||||
|
||||
//! Test shadow key state
|
||||
|
@ -127,19 +134,6 @@ private:
|
|||
// return true iff \c c is a dead character
|
||||
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:
|
||||
class CModifierKeys {
|
||||
public:
|
||||
|
@ -150,6 +144,7 @@ private:
|
|||
|
||||
BYTE m_keys[256];
|
||||
mutable TCHAR m_deadKey;
|
||||
HKL m_keyLayout;
|
||||
|
||||
static const CModifierKeys s_modifiers[];
|
||||
static const char* s_vkToName[];
|
||||
|
|
|
@ -128,6 +128,7 @@ CMSWindowsScreen::CMSWindowsScreen(bool isPrimary) :
|
|||
m_sequenceNumber(0),
|
||||
m_mark(0),
|
||||
m_markReceived(0),
|
||||
m_keyLayout(NULL),
|
||||
m_timer(NULL),
|
||||
m_screensaver(NULL),
|
||||
m_screensaverNotify(false),
|
||||
|
@ -290,7 +291,17 @@ CMSWindowsScreen::enter()
|
|||
bool
|
||||
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) {
|
||||
/* XXX
|
||||
|
@ -543,7 +554,7 @@ CMSWindowsScreen::fakeKeyEvent(KeyButton virtualKey, bool press) const
|
|||
if (!press) {
|
||||
flags |= KEYEVENTF_KEYUP;
|
||||
}
|
||||
const UINT code = m_keyMapper.keyToScanCode(&virtualKey);
|
||||
UINT code = m_keyMapper.keyToScanCode(&virtualKey);
|
||||
sendDeskMessage(SYNERGY_MSG_FAKE_KEY, flags,
|
||||
MAKEWORD(static_cast<BYTE>(code),
|
||||
static_cast<BYTE>(virtualKey & 0xffu)));
|
||||
|
@ -977,6 +988,9 @@ CMSWindowsScreen::onMark(UInt32 mark)
|
|||
bool
|
||||
CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
WPARAM charAndVirtKey = wParam;
|
||||
wParam &= 0xffu;
|
||||
|
||||
// ignore message if posted prior to last mark change
|
||||
if (!ignore()) {
|
||||
// check for ctrl+alt+del emulation
|
||||
|
@ -992,8 +1006,8 @@ CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
|
|||
// process key normally
|
||||
bool altgr;
|
||||
KeyModifierMask mask;
|
||||
const KeyID key = m_keyMapper.mapKeyFromEvent(wParam,
|
||||
lParam, &mask, &altgr);
|
||||
const KeyID key = m_keyMapper.mapKeyFromEvent(
|
||||
charAndVirtKey, lParam, &mask, &altgr);
|
||||
KeyButton button = static_cast<KeyButton>(
|
||||
(lParam & 0x00ff0000u) >> 16);
|
||||
if (key != kKeyNone && key != kKeyMultiKey) {
|
||||
|
@ -1654,8 +1668,8 @@ CMSWindowsScreen::deskMouseMove(SInt32 x, SInt32 y) const
|
|||
SInt32 w = GetSystemMetrics(SM_CXSCREEN);
|
||||
SInt32 h = GetSystemMetrics(SM_CYSCREEN);
|
||||
mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
|
||||
(DWORD)((65536.0 * x) / w),
|
||||
(DWORD)((65536.0 * y) / h),
|
||||
(DWORD)((65535.0f * x) / (w - 1) + 0.5f),
|
||||
(DWORD)((65535.0f * y) / (h - 1) + 0.5f),
|
||||
0, 0);
|
||||
}
|
||||
|
||||
|
@ -1712,43 +1726,45 @@ CMSWindowsScreen::deskMouseMove(SInt32 x, SInt32 y) const
|
|||
}
|
||||
|
||||
void
|
||||
CMSWindowsScreen::deskEnter(CDesk* desk, DWORD& cursorThreadID)
|
||||
CMSWindowsScreen::deskEnter(CDesk* desk)
|
||||
{
|
||||
if (m_isPrimary) {
|
||||
if (desk->m_lowLevel) {
|
||||
if (cursorThreadID != 0) {
|
||||
AttachThreadInput(desk->m_threadID, cursorThreadID, TRUE);
|
||||
ShowCursor(TRUE);
|
||||
AttachThreadInput(desk->m_threadID, cursorThreadID, FALSE);
|
||||
cursorThreadID = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
ShowWindow(desk->m_window, SW_HIDE);
|
||||
}
|
||||
|
||||
void
|
||||
CMSWindowsScreen::deskLeave(CDesk* desk, DWORD& cursorThreadID)
|
||||
CMSWindowsScreen::deskLeave(CDesk* desk, HKL keyLayout)
|
||||
{
|
||||
if (m_isPrimary) {
|
||||
// we don't need a window to capture input but we need a window
|
||||
// to hide the cursor when using low-level hooks. also take the
|
||||
// activation so we use our keyboard layout, not the layout of
|
||||
// whatever window was active.
|
||||
// map a window to hide the cursor and to use whatever keyboard
|
||||
// layout we choose rather than the keyboard layout of the last
|
||||
// active window.
|
||||
int x, y, w, h;
|
||||
if (desk->m_lowLevel) {
|
||||
SetWindowPos(desk->m_window, HWND_TOPMOST,
|
||||
m_xCenter, m_yCenter, 1, 1, SWP_NOACTIVATE);
|
||||
// with a low level hook the cursor will never budge so
|
||||
// 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);
|
||||
if (cursorThreadID == 0) {
|
||||
HWND hwnd = GetForegroundWindow();
|
||||
cursorThreadID = GetWindowThreadProcessId(hwnd, NULL);
|
||||
if (cursorThreadID != 0) {
|
||||
AttachThreadInput(desk->m_threadID, cursorThreadID, TRUE);
|
||||
ShowCursor(FALSE);
|
||||
AttachThreadInput(desk->m_threadID, cursorThreadID, FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// switch to requested keyboard layout
|
||||
ActivateKeyboardLayout(keyLayout, 0);
|
||||
}
|
||||
else {
|
||||
// move hider window under the cursor center
|
||||
|
@ -1767,9 +1783,6 @@ CMSWindowsScreen::deskThread(void* vdesk)
|
|||
{
|
||||
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
|
||||
CDesk* desk = reinterpret_cast<CDesk*>(vdesk);
|
||||
desk->m_threadID = GetCurrentThreadId();
|
||||
|
@ -1833,11 +1846,11 @@ CMSWindowsScreen::deskThread(void* vdesk)
|
|||
break;
|
||||
|
||||
case SYNERGY_MSG_ENTER:
|
||||
deskEnter(desk, cursorThreadID);
|
||||
deskEnter(desk);
|
||||
break;
|
||||
|
||||
case SYNERGY_MSG_LEAVE:
|
||||
deskLeave(desk, cursorThreadID);
|
||||
deskLeave(desk, (HKL)msg.wParam);
|
||||
break;
|
||||
|
||||
case SYNERGY_MSG_FAKE_KEY:
|
||||
|
@ -1969,7 +1982,7 @@ CMSWindowsScreen::checkDesk()
|
|||
|
||||
// hide cursor on new desk
|
||||
if (!m_isOnScreen) {
|
||||
sendDeskMessage(SYNERGY_MSG_LEAVE, 0, 0);
|
||||
sendDeskMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,8 +186,8 @@ private:
|
|||
static LRESULT CALLBACK secondaryDeskProc(HWND, UINT, WPARAM, LPARAM);
|
||||
|
||||
void deskMouseMove(SInt32 x, SInt32 y) const;
|
||||
void deskEnter(CDesk* desk, DWORD& cursorThreadID);
|
||||
void deskLeave(CDesk* desk, DWORD& cursorThreadID);
|
||||
void deskEnter(CDesk* desk);
|
||||
void deskLeave(CDesk* desk, HKL keyLayout);
|
||||
void deskThread(void* vdesk);
|
||||
CDesk* addDesk(const CString& name, HDESK hdesk);
|
||||
void removeDesks();
|
||||
|
@ -234,6 +234,9 @@ private:
|
|||
// the main loop's thread id
|
||||
DWORD m_threadID;
|
||||
|
||||
// the keyboard layout to use when off primary screen
|
||||
HKL m_keyLayout;
|
||||
|
||||
// the timer used to check for desktop switching
|
||||
CEventQueueTimer* m_timer;
|
||||
|
||||
|
|
|
@ -24,6 +24,12 @@
|
|||
//
|
||||
#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
|
||||
//
|
||||
|
@ -86,8 +92,11 @@ static SInt32 g_xScreen = 0;
|
|||
static SInt32 g_yScreen = 0;
|
||||
static SInt32 g_wScreen = 0;
|
||||
static SInt32 g_hScreen = 0;
|
||||
static HCURSOR g_cursor = NULL;
|
||||
static DWORD g_cursorThread = 0;
|
||||
static WPARAM g_deadVirtKey = 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()
|
||||
|
||||
|
@ -105,49 +114,217 @@ extern "C" int _fltused=0;
|
|||
|
||||
static
|
||||
void
|
||||
hideCursor(DWORD thread)
|
||||
attachThreadToForeground()
|
||||
{
|
||||
// we should be running the context of the window who's cursor
|
||||
// we want to hide so we shouldn't have to attach thread input
|
||||
// but we'll check to make sure.
|
||||
g_cursorThread = thread;
|
||||
if (g_cursorThread != 0) {
|
||||
DWORD myThread = GetCurrentThreadId();
|
||||
if (myThread != g_cursorThread)
|
||||
AttachThreadInput(myThread, g_cursorThread, TRUE);
|
||||
g_cursor = SetCursor(NULL);
|
||||
if (myThread != g_cursorThread)
|
||||
AttachThreadInput(myThread, g_cursorThread, FALSE);
|
||||
// only attach threads if using low level hooks. a low level hook
|
||||
// runs in the thread that installed the hook but we have to make
|
||||
// changes that require being attached to the target thread (which
|
||||
// should be the foreground window). a regular hook runs in the
|
||||
// thread that just removed the event from its queue so we're
|
||||
// already in the right thread.
|
||||
if (g_hookThread != 0) {
|
||||
HWND window = GetForegroundWindow();
|
||||
DWORD threadID = GetWindowThreadProcessId(window, NULL);
|
||||
// skip if no change
|
||||
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
|
||||
void
|
||||
restoreCursor()
|
||||
detachThread()
|
||||
{
|
||||
// restore the show cursor in the window we hid it last
|
||||
if (g_cursor != NULL && g_cursorThread != 0) {
|
||||
DWORD myThread = GetCurrentThreadId();
|
||||
if (myThread != g_cursorThread)
|
||||
AttachThreadInput(myThread, g_cursorThread, TRUE);
|
||||
SetCursor(g_cursor);
|
||||
if (myThread != g_cursorThread)
|
||||
AttachThreadInput(myThread, g_cursorThread, FALSE);
|
||||
if (g_attachedThread != 0) {
|
||||
AttachThreadInput(g_hookThread, g_attachedThread, FALSE);
|
||||
g_attachedThread = 0;
|
||||
}
|
||||
g_cursor = NULL;
|
||||
g_cursorThread = 0;
|
||||
}
|
||||
|
||||
#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
|
||||
bool
|
||||
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
|
||||
// forwarding events to clients because this'll keep our thread's
|
||||
// key state table up to date. that's important for querying
|
||||
// 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) {
|
||||
// let certain keys pass through
|
||||
|
@ -160,8 +337,21 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam)
|
|||
// lights may not stay synchronized.
|
||||
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:
|
||||
// discard event
|
||||
// discard
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +364,8 @@ static
|
|||
bool
|
||||
mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data)
|
||||
{
|
||||
attachThreadToForeground();
|
||||
|
||||
switch (wParam) {
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
|
@ -213,19 +405,6 @@ mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data)
|
|||
case WM_NCMOUSEMOVE:
|
||||
case WM_MOUSEMOVE:
|
||||
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
|
||||
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
|
||||
return true;
|
||||
|
@ -347,7 +526,7 @@ getMessageHook(int code, WPARAM wParam, LPARAM 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
|
||||
|
@ -531,8 +710,6 @@ init(DWORD threadID)
|
|||
g_yScreen = 0;
|
||||
g_wScreen = 0;
|
||||
g_hScreen = 0;
|
||||
g_cursor = NULL;
|
||||
g_cursorThread = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -562,6 +739,10 @@ install()
|
|||
return kHOOK_FAILED;
|
||||
}
|
||||
|
||||
// discard old dead keys
|
||||
g_deadVirtKey = 0;
|
||||
g_deadLParam = 0;
|
||||
|
||||
// check for mouse wheel support
|
||||
g_wheelSupport = getWheelSupport();
|
||||
|
||||
|
@ -573,35 +754,43 @@ install()
|
|||
0);
|
||||
}
|
||||
|
||||
// install keyboard hook
|
||||
#if !NO_GRAB_KEYBOARD
|
||||
#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)
|
||||
// install low-level hooks. we require that they both get installed.
|
||||
#if (_WIN32_WINNT >= 0x0400) && !NO_LOWLEVEL_HOOKS
|
||||
g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL,
|
||||
&mouseLLHook,
|
||||
g_hinstance,
|
||||
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
|
||||
|
||||
// install regular hooks
|
||||
if (g_mouseLL == NULL) {
|
||||
g_mouse = SetWindowsHookEx(WH_MOUSE,
|
||||
&mouseHook,
|
||||
g_hinstance,
|
||||
0);
|
||||
}
|
||||
if (g_keyboardLL == NULL) {
|
||||
g_keyboard = SetWindowsHookEx(WH_KEYBOARD,
|
||||
&keyboardHook,
|
||||
g_hinstance,
|
||||
0);
|
||||
}
|
||||
|
||||
// check that we got all the hooks we wanted
|
||||
if ((g_getMessage == NULL && g_wheelSupport == kWheelOld) ||
|
||||
|
@ -614,6 +803,7 @@ install()
|
|||
}
|
||||
|
||||
if (g_keyboardLL != NULL || g_mouseLL != NULL) {
|
||||
g_hookThread = GetCurrentThreadId();
|
||||
return kHOOK_OKAY_LL;
|
||||
}
|
||||
|
||||
|
@ -625,6 +815,13 @@ uninstall(void)
|
|||
{
|
||||
assert(g_hinstance != NULL);
|
||||
|
||||
// discard old dead keys
|
||||
g_deadVirtKey = 0;
|
||||
g_deadLParam = 0;
|
||||
|
||||
// detach from thread
|
||||
detachThread();
|
||||
|
||||
// uninstall hooks
|
||||
if (g_keyboardLL != NULL) {
|
||||
UnhookWindowsHookEx(g_keyboardLL);
|
||||
|
@ -648,9 +845,6 @@ uninstall(void)
|
|||
}
|
||||
g_wheelSupport = kWheelNone;
|
||||
|
||||
// show the cursor
|
||||
restoreCursor();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -719,9 +913,6 @@ setMode(EHookMode mode)
|
|||
return;
|
||||
}
|
||||
g_mode = mode;
|
||||
if (g_mode != kHOOK_RELAY_EVENTS) {
|
||||
restoreCursor();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1153,7 +1153,6 @@ void
|
|||
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_INFO "onKeyDown: id=%d mask=0x%04x button=0x%04x", id, mask, button));
|
||||
assert(m_active != NULL);
|
||||
|
||||
// handle command keys
|
||||
|
@ -1169,7 +1168,6 @@ void
|
|||
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_INFO "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button));
|
||||
assert(m_active != NULL);
|
||||
|
||||
// handle command keys
|
||||
|
@ -1186,7 +1184,6 @@ CServer::onKeyRepeat(KeyID id, KeyModifierMask mask,
|
|||
SInt32 count, KeyButton 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);
|
||||
|
||||
// handle command keys
|
||||
|
@ -1225,7 +1222,10 @@ CServer::onMouseMovePrimary(SInt32 x, SInt32 y)
|
|||
LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y));
|
||||
|
||||
// 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
|
||||
m_x = x;
|
||||
|
@ -1282,13 +1282,7 @@ CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy)
|
|||
// mouse move on secondary (client's) screen
|
||||
assert(m_active != NULL);
|
||||
if (m_active == m_primaryClient) {
|
||||
// we're actually on the primary screen. this can happen
|
||||
// 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.
|
||||
// stale event -- we're actually on the primary screen
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue