diff --git a/base/CLog.cpp b/base/CLog.cpp index 8d11d44c..de22098e 100644 --- a/base/CLog.cpp +++ b/base/CLog.cpp @@ -1,5 +1,4 @@ #include "CLog.h" -#include "BasicTypes.h" #include #include #include @@ -139,14 +138,15 @@ void CLog::output(int priority, char* msg) #endif // print it - if (s_outputter) + if (s_outputter) { s_outputter(msg + g_maxPriorityLength - n); - else + } + else { #if defined(CONFIG_PLATFORM_WIN32) - OutputDebugString(msg + g_maxPriorityLength - n); -#else - fprintf(stderr, "%s", msg + g_maxPriorityLength - n); + openConsole(); #endif + fprintf(stderr, "%s", msg + g_maxPriorityLength - n); + } } } @@ -174,3 +174,63 @@ char* CLog::vsprint(int pad, char* buffer, int len, return buffer; } + +#if defined(CONFIG_PLATFORM_WIN32) + +static DWORD s_thread = 0; + +static BOOL WINAPI CLogSignalHandler(DWORD) +{ + // terminate cleanly and skip remaining handlers + PostThreadMessage(s_thread, WM_QUIT, 0, 0); + return TRUE; +} + +void CLog::openConsole() +{ + static bool s_hasConsole = false; + + // ignore if already created + if (s_hasConsole) + return; + + // remember the current thread. when we get a ctrl+break or the + // console is closed we'll post WM_QUIT to this thread to shutdown + // cleanly. + // note -- win95/98/me are broken and will not receive a signal + // when the console is closed nor during logoff or shutdown, + // see microsoft articles Q130717 and Q134284. we could work + // around this in a painful way using hooks and hidden windows + // (as apache does) but it's not worth it. the app will still + // quit, just not cleanly. users in-the-know can use ctrl+c. + s_thread = GetCurrentThreadId(); + + // open a console + if (!AllocConsole()) + return; + + // get the handle for error output + HANDLE herr = GetStdHandle(STD_ERROR_HANDLE); + + // prep console. windows 95 and its ilk have braindead + // consoles that can't even resize independently of the + // buffer size. use a 25 line buffer for those systems. + OSVERSIONINFO osInfo; + COORD size = { 80, 1000 }; + osInfo.dwOSVersionInfoSize = sizeof(osInfo); + if (GetVersionEx(&osInfo) && + osInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) + size.Y = 25; + SetConsoleScreenBufferSize(herr, size); + SetConsoleTextAttribute(herr, + FOREGROUND_RED | + FOREGROUND_GREEN | + FOREGROUND_BLUE); + SetConsoleCtrlHandler(CLogSignalHandler, TRUE); + + // reopen stderr to point at console + freopen("con", "w", stderr); + s_hasConsole = true; +} + +#endif diff --git a/base/CLog.h b/base/CLog.h index ae8fdc10..66cfa799 100644 --- a/base/CLog.h +++ b/base/CLog.h @@ -1,6 +1,7 @@ #ifndef CLOG_H #define CLOG_H +#include "BasicTypes.h" #include class CLog { @@ -15,6 +16,9 @@ private: static void output(int priority, char* msg); static char* vsprint(int pad, char*, int len, const char*, va_list); static int nprint(const char*, va_list); +#if defined(CONFIG_PLATFORM_WIN32) + static void openConsole(); +#endif private: static Outputter s_outputter; diff --git a/client/CMSWindowsSecondaryScreen.cpp b/client/CMSWindowsSecondaryScreen.cpp index e593c56f..cdca03c4 100644 --- a/client/CMSWindowsSecondaryScreen.cpp +++ b/client/CMSWindowsSecondaryScreen.cpp @@ -24,49 +24,18 @@ CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen() assert(m_window == NULL); } -static CString s_log; -static CString s_logMore; -static HWND s_debug = NULL; -static HWND s_debugLog = NULL; -static DWORD s_thread = 0; -static BOOL CALLBACK WINAPI debugProc(HWND, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: - return TRUE; - - case WM_CLOSE: - PostQuitMessage(0); - return TRUE; - - case WM_APP: - if (!s_logMore.empty()) { - if (s_log.size() > 40000) - s_log = s_logMore; - else - s_log += s_logMore; - s_logMore = ""; - SendMessage(s_debugLog, WM_SETTEXT, FALSE, (LPARAM)(LPCTSTR)s_log.c_str()); - SendMessage(s_debugLog, EM_SETSEL, s_log.size(), s_log.size()); - SendMessage(s_debugLog, EM_SCROLLCARET, 0, 0); - } - return TRUE; - } - return FALSE; -} -static void debugOutput(const char* msg) -{ - s_logMore += msg; - PostMessage(s_debug, WM_APP, 0, 0); -} - void CMSWindowsSecondaryScreen::run() { -CLog::setOutputter(&debugOutput); + // change our priority + CThread::getCurrentThread().setPriority(-7); + + // save thread id + m_threadID = GetCurrentThreadId(); + + // run event loop log((CLOG_INFO "entering event loop")); doRun(); log((CLOG_INFO "exiting event loop")); -CLog::setOutputter(NULL); } void CMSWindowsSecondaryScreen::stop() @@ -117,6 +86,24 @@ void CMSWindowsSecondaryScreen::enter( log((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask)); + // attach thread input queues + AttachThreadInput(GetCurrentThreadId(), m_threadID, TRUE); + + // update our keyboard state to reflect the local state + updateKeys(); + updateModifiers(); + + // toggle modifiers that don't match the desired state + if ((mask & KeyModifierCapsLock) != (m_mask & KeyModifierCapsLock)) { + toggleKey(VK_CAPITAL, KeyModifierCapsLock); + } + if ((mask & KeyModifierNumLock) != (m_mask & KeyModifierNumLock)) { + toggleKey(VK_NUMLOCK | 0x100, KeyModifierNumLock); + } + if ((mask & KeyModifierScrollLock) != (m_mask & KeyModifierScrollLock)) { + toggleKey(VK_SCROLL, KeyModifierScrollLock); + } + // warp to requested location SInt32 w, h; getScreenSize(&w, &h); @@ -128,21 +115,6 @@ void CMSWindowsSecondaryScreen::enter( // show cursor log((CLOG_INFO "show cursor")); ShowWindow(m_window, SW_HIDE); - - // update our keyboard state to reflect the local state - updateKeys(); - updateModifiers(); - - // toggle modifiers that don't match the desired state - if ((mask & KeyModifierCapsLock) != (m_mask & KeyModifierCapsLock)) { - toggleKey(VK_CAPITAL, KeyModifierCapsLock); - } - if ((mask & KeyModifierNumLock) != (m_mask & KeyModifierNumLock)) { - toggleKey(VK_NUMLOCK, KeyModifierNumLock); - } - if ((mask & KeyModifierScrollLock) != (m_mask & KeyModifierScrollLock)) { - toggleKey(VK_SCROLL, KeyModifierScrollLock); - } } void CMSWindowsSecondaryScreen::leave() @@ -363,19 +335,10 @@ void CMSWindowsSecondaryScreen::getClipboard( CClipboard::copy(dst, &src); } -#include "resource.h" // FIXME - void CMSWindowsSecondaryScreen::onOpenDisplay() { assert(m_window == NULL); -// create debug dialog -s_thread = GetCurrentThreadId();; -s_debug = CreateDialog(getInstance(), MAKEINTRESOURCE(IDD_SYNERGY), NULL, &debugProc); -s_debugLog = ::GetDlgItem(s_debug, IDC_LOG); -CLog::setOutputter(&debugOutput); -ShowWindow(s_debug, SW_SHOWNORMAL); - // initialize clipboard owner to current owner. we don't want // to take ownership of the clipboard just by starting up. m_clipboardOwner = GetClipboardOwner(); @@ -410,22 +373,13 @@ void CMSWindowsSecondaryScreen::onCloseDisplay() // destroy window DestroyWindow(m_window); m_window = NULL; - -CLog::setOutputter(NULL); -DestroyWindow(s_debug); -s_debug = NULL; -s_thread = 0; } bool CMSWindowsSecondaryScreen::onPreTranslate(MSG* msg) { -if (IsDialogMessage(s_debug, msg)) { - return true; -} return false; } - LRESULT CMSWindowsSecondaryScreen::onEvent( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) @@ -467,9 +421,14 @@ LRESULT CMSWindowsSecondaryScreen::onEvent( SendMessage(m_nextClipboardWindow, msg, wParam, lParam); return 0; } + return DefWindowProc(hwnd, msg, wParam, lParam); } +// these tables map KeyID (a X windows KeySym) to virtual key codes. +// if the key is an extended key then the entry is the virtual key +// code | 0x100. keys that map to normal characters have a 0 entry +// and the conversion is done elsewhere. static const UINT g_latin1[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, @@ -859,7 +818,7 @@ static const UINT g_function[] = static const UINT g_miscellany[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x08 */ VK_BACK, VK_TAB, /*0x100 +*/ VK_RETURN, VK_CLEAR, 0, VK_RETURN, 0, 0, + /* 0x08 */ VK_BACK, VK_TAB, 0, VK_CLEAR, 0, VK_RETURN, 0, 0, /* 0x10 */ 0, 0, 0, VK_PAUSE, VK_SCROLL, 0/*sys-req*/, 0, 0, /* 0x18 */ 0, 0, 0, VK_ESCAPE, 0, 0, 0, 0, /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, @@ -868,21 +827,22 @@ static const UINT g_miscellany[] = /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x50 */ VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, - /* 0x54 */ VK_DOWN, VK_PRIOR, VK_NEXT, VK_END, + /* 0x50 */ VK_HOME|0x100, VK_LEFT|0x100, VK_UP|0x100, VK_RIGHT|0x100, + /* 0x54 */ VK_DOWN|0x100, VK_PRIOR|0x100, VK_NEXT|0x100, VK_END|0x100, /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x60 */ VK_SELECT, VK_SNAPSHOT, VK_EXECUTE, VK_INSERT, 0, 0, 0, VK_APPS, - /* 0x68 */ 0, 0, VK_HELP, VK_CANCEL, 0, 0, 0, 0, + /* 0x60 */ VK_SELECT|0x100, VK_SNAPSHOT|0x100, VK_EXECUTE|0x100, VK_INSERT|0x100, + /* 0x64 */ 0, 0, 0, VK_APPS|0x100, + /* 0x68 */ 0, 0, VK_HELP|0x100, VK_CANCEL|0x100, 0, 0, 0, 0, /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x78 */ 0, 0, 0, 0, 0, 0, VK_MODECHANGE, VK_NUMLOCK, + /* 0x78 */ 0, 0, 0, 0, 0, 0, VK_MODECHANGE|0x100, VK_NUMLOCK|0x100, /* 0x80 */ VK_SPACE, 0, 0, 0, 0, 0, 0, 0, - /* 0x88 */ 0, VK_TAB, 0, 0, 0, VK_RETURN, 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, /* 0x98 */ VK_RIGHT, VK_DOWN, VK_PRIOR, VK_NEXT, /* 0x9c */ VK_END, 0, VK_INSERT, VK_DELETE, /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa8 */ 0, 0, VK_MULTIPLY, VK_ADD, - /* 0xac */ VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE, + /* 0xac */ VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE|0x100, /* 0xb0 */ VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, /* 0xb4 */ VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, /* 0xb8 */ VK_NUMPAD8, VK_NUMPAD9, 0, 0, 0, 0, VK_F1, VK_F2, @@ -891,10 +851,10 @@ static const UINT g_miscellany[] = /* 0xd0 */ VK_F19, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, 0, 0, /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 */ 0, VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, - /* 0xe4 */ VK_RCONTROL, VK_CAPITAL, 0, VK_LWIN, - /* 0xe8 */ VK_RWIN, VK_LMENU, VK_RMENU, 0, 0, 0, 0, 0, + /* 0xe4 */ VK_RCONTROL|0x100, VK_CAPITAL, 0, VK_LWIN|0x100, + /* 0xe8 */ VK_RWIN|0x100, VK_LMENU, VK_RMENU|0x100, 0, 0, 0, 0, 0, /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, VK_DELETE + /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, VK_DELETE|0x100 }; static const UINT* g_katakana = NULL; static const UINT* g_arabic = NULL; @@ -1001,6 +961,10 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey( KeyModifierMeta)); log((CLOG_DEBUG2 "key id %d -> virtual key %d", id, virtualKey)); + // extract extended key flag + const bool isExtended = ((virtualKey & 0x100) != 0); + virtualKey &= ~0x100; + // if not in map then ask system to convert ascii character if (virtualKey == 0) { if (mapID != 0) { @@ -1010,7 +974,7 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey( } // translate. return no keys if unknown key. - SHORT vk = VkKeyScan(code); + SHORT vk = VkKeyScan(static_cast(code)); if (vk == 0xffff) { log((CLOG_DEBUG2 "no virtual key for character %d", code)); return m_mask; @@ -1033,24 +997,12 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey( // 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 shift reverses its meaning (for keys that are - // subject to case conversion). - if ((m_mask & KeyModifierCapsLock) != 0) { - // caps-lock is on. note if shift is required. - log((CLOG_DEBUG2 "caps-lock is on")); - const bool needShift = ((outMask & KeyModifierShift) != 0); - - // if needShift is true then see if the key is subject to - // case conversion. if it is then caps-lock and shift - // cancel out. if not then caps-lock is ignored and shift - // is required. - if (needShift) { - // FIXME -- there should be some system call to test - // if a key is subject to case conversion. - if (tolower(code) != toupper(code)) { - log((CLOG_DEBUG2 "turn off shift")); - outMask &= ~KeyModifierShift; - } + // locally then it reverses the meaning of shift for keys that + // are subject to case conversion. + if ((outMask & KeyModifierCapsLock) != 0) { + if (tolower(code) != toupper(code)) { + log((CLOG_DEBUG2 "flip shift")); + outMask ^= KeyModifierShift; } } @@ -1096,13 +1048,13 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey( bool m_isToggle; }; static const CModifierInfo s_modifier[] = { - { KeyModifierShift, VK_LSHIFT, VK_RSHIFT, false }, - { KeyModifierControl, VK_LCONTROL, VK_RCONTROL,false }, - { KeyModifierAlt, VK_LMENU, VK_RMENU, false }, - { KeyModifierMeta, VK_LWIN, VK_RWIN, false }, - { KeyModifierCapsLock, VK_CAPITAL, 0, true }, - { KeyModifierNumLock, VK_NUMLOCK, 0, true }, - { KeyModifierScrollLock, VK_SCROLL, 0, true } + { KeyModifierShift, VK_LSHIFT, VK_RSHIFT, false }, + { KeyModifierControl, VK_LCONTROL, VK_RCONTROL | 0x100,false }, + { KeyModifierAlt, VK_LMENU, VK_RMENU | 0x100, false }, + { KeyModifierMeta, VK_LWIN | 0x100, VK_RWIN | 0x100, false }, + { KeyModifierCapsLock, VK_CAPITAL, 0, true }, + { KeyModifierNumLock, VK_NUMLOCK | 0x100, 0, true }, + { KeyModifierScrollLock,VK_SCROLL, 0, true } }; static const unsigned int s_numModifiers = sizeof(s_modifier) / sizeof(s_modifier[0]); @@ -1203,7 +1155,7 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey( } else { UINT key = s_modifier[i].m_virtualKey; - if ((m_keys[key] & 0x80) != 0) { + if ((m_keys[key & 0xff] & 0x80) != 0) { keystroke.m_virtualKey = key; keystroke.m_press = false; keystroke.m_repeat = false; @@ -1212,7 +1164,7 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey( undo.push_back(keystroke); } key = s_modifier[i].m_virtualKey2; - if (key != 0 && (m_keys[key] & 0x80) != 0) { + if (key != 0 && (m_keys[key & 0xff] & 0x80) != 0) { keystroke.m_virtualKey = key; keystroke.m_press = false; keystroke.m_repeat = false; @@ -1227,23 +1179,23 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey( } // add the key event + keystroke.m_virtualKey = virtualKey; + if (isExtended) + keystroke.m_virtualKey |= 0x100; switch (action) { case kPress: - keystroke.m_virtualKey = virtualKey; keystroke.m_press = true; keystroke.m_repeat = false; keys.push_back(keystroke); break; case kRelease: - keystroke.m_virtualKey = virtualKey; keystroke.m_press = false; keystroke.m_repeat = false; keys.push_back(keystroke); break; case kRepeat: - keystroke.m_virtualKey = virtualKey; keystroke.m_press = true; keystroke.m_repeat = true; keys.push_back(keystroke); @@ -1276,13 +1228,13 @@ KeyModifierMask CMSWindowsSecondaryScreen::mapKey( // can't reset bit until all keys that set it are released. // scan those keys to see if any are pressed. bool down = false; - if (virtualKey != modifier.m_virtualKey && - (m_keys[modifier.m_virtualKey] & 0x80) != 0) { + if (virtualKey != (modifier.m_virtualKey & 0xff) && + (m_keys[modifier.m_virtualKey & 0xff] & 0x80) != 0) { down = true; } if (modifier.m_virtualKey2 != 0 && - virtualKey != modifier.m_virtualKey2 && - (m_keys[modifier.m_virtualKey2] & 0x80) != 0) { + virtualKey != (modifier.m_virtualKey2 & 0xff) && + (m_keys[modifier.m_virtualKey2 & 0xff] & 0x80) != 0) { down = true; } if (!down) @@ -1310,9 +1262,7 @@ void CMSWindowsSecondaryScreen::doKeystrokes( for (; count > 0; --count) { // send repeating events for (k = start; k != keys.end() && k->m_repeat; ++k) { - const UINT code = MapVirtualKey(k->m_virtualKey, 0); - keybd_event(k->m_virtualKey, code, - k->m_press ? 0 : KEYEVENTF_KEYUP, 0); + sendKeyEvent(k->m_virtualKey, k->m_press); } } @@ -1321,9 +1271,7 @@ void CMSWindowsSecondaryScreen::doKeystrokes( } else { // send event - const UINT code = MapVirtualKey(k->m_virtualKey, 0); - keybd_event(k->m_virtualKey, code, - k->m_press ? 0 : KEYEVENTF_KEYUP, 0); + sendKeyEvent(k->m_virtualKey, k->m_press); // next key ++k; @@ -1333,22 +1281,25 @@ void CMSWindowsSecondaryScreen::doKeystrokes( void CMSWindowsSecondaryScreen::updateKeys() { - // GetKeyboardKeys() doesn't seem to do the expected thing + // clear key state memset(m_keys, 0, sizeof(m_keys)); - m_keys[VK_LSHIFT] = GetKeyState(VK_LSHIFT); - m_keys[VK_RSHIFT] = GetKeyState(VK_RSHIFT); - m_keys[VK_SHIFT] = GetKeyState(VK_SHIFT); - m_keys[VK_LCONTROL] = GetKeyState(VK_LCONTROL); - m_keys[VK_RCONTROL] = GetKeyState(VK_RCONTROL); - m_keys[VK_CONTROL] = GetKeyState(VK_CONTROL); - m_keys[VK_LMENU] = GetKeyState(VK_LMENU); - m_keys[VK_RMENU] = GetKeyState(VK_RMENU); - m_keys[VK_MENU] = GetKeyState(VK_MENU); - m_keys[VK_LWIN] = GetKeyState(VK_LWIN); - m_keys[VK_RWIN] = GetKeyState(VK_RWIN); - m_keys[VK_CAPITAL] = GetKeyState(VK_CAPITAL); - m_keys[VK_NUMLOCK] = GetKeyState(VK_NUMLOCK); - m_keys[VK_SCROLL] = GetKeyState(VK_SCROLL); + + // we only care about the modifier key states + m_keys[VK_LSHIFT] = static_cast(GetKeyState(VK_LSHIFT)); + m_keys[VK_RSHIFT] = static_cast(GetKeyState(VK_RSHIFT)); + m_keys[VK_SHIFT] = static_cast(GetKeyState(VK_SHIFT)); + m_keys[VK_LCONTROL] = static_cast(GetKeyState(VK_LCONTROL)); + m_keys[VK_RCONTROL] = static_cast(GetKeyState(VK_RCONTROL)); + m_keys[VK_CONTROL] = static_cast(GetKeyState(VK_CONTROL)); + m_keys[VK_LMENU] = static_cast(GetKeyState(VK_LMENU)); + m_keys[VK_RMENU] = static_cast(GetKeyState(VK_RMENU)); + m_keys[VK_MENU] = static_cast(GetKeyState(VK_MENU)); + m_keys[VK_LWIN] = static_cast(GetKeyState(VK_LWIN)); + m_keys[VK_RWIN] = static_cast(GetKeyState(VK_RWIN)); + m_keys[VK_APPS] = static_cast(GetKeyState(VK_APPS)); + m_keys[VK_CAPITAL] = static_cast(GetKeyState(VK_CAPITAL)); + m_keys[VK_NUMLOCK] = static_cast(GetKeyState(VK_NUMLOCK)); + m_keys[VK_SCROLL] = static_cast(GetKeyState(VK_SCROLL)); } void CMSWindowsSecondaryScreen::updateModifiers() @@ -1376,11 +1327,97 @@ void CMSWindowsSecondaryScreen::toggleKey( UINT virtualKey, KeyModifierMask mask) { // send key events to simulate a press and release - const UINT code = MapVirtualKey(virtualKey, 0); - keybd_event(virtualKey, code, 0, 0); - keybd_event(virtualKey, code, KEYEVENTF_KEYUP, 0); + sendKeyEvent(virtualKey, true); + sendKeyEvent(virtualKey, false); // toggle shadow state - m_mask ^= mask; - m_keys[virtualKey] ^= 0x01; + m_mask ^= mask; + m_keys[virtualKey & 0xff] ^= 0x01; +} + +UINT CMSWindowsSecondaryScreen::virtualKeyToScanCode( + UINT& virtualKey) +{ + // try mapping given virtual key + UINT code = MapVirtualKey(virtualKey & 0xff, 0); + if (code != 0) + return code; + + // no dice. if the virtual key distinguishes between left/right + // then try the one that doesn't distinguish sides. windows (or + // keyboard drivers) are inconsistent in their treatment of these + // virtual keys. the following behaviors have been observed: + // + // win2k (gateway desktop): + // MapVirtualKey(vk, 0): + // VK_SHIFT == VK_LSHIFT != VK_RSHIFT + // VK_CONTROL == VK_LCONTROL == VK_RCONTROL + // VK_MENU == VK_LMENU == VK_RMENU + // MapVirtualKey(sc, 3): + // VK_LSHIFT and VK_RSHIFT mapped independently + // VK_LCONTROL is mapped but not VK_RCONTROL + // VK_LMENU is mapped but not VK_RMENU + // + // win me (sony vaio laptop): + // MapVirtualKey(vk, 0): + // VK_SHIFT mapped; VK_LSHIFT, VK_RSHIFT not mapped + // VK_CONTROL mapped; VK_LCONTROL, VK_RCONTROL not mapped + // VK_MENU mapped; VK_LMENU, VK_RMENU not mapped + // MapVirtualKey(sc, 3): + // all scan codes unmapped (function apparently unimplemented) + switch (virtualKey & 0xff) { + case VK_LSHIFT: + case VK_RSHIFT: + virtualKey = VK_SHIFT; + return MapVirtualKey(VK_SHIFT, 0); + + case VK_LCONTROL: + case VK_RCONTROL: + virtualKey = VK_CONTROL; + return MapVirtualKey(VK_CONTROL, 0); + + case VK_LMENU: + case VK_RMENU: + virtualKey = VK_MENU; + return MapVirtualKey(VK_MENU, 0); + + default: + return 0; + } +} + +bool CMSWindowsSecondaryScreen::isExtendedKey( + UINT virtualKey) +{ + // see if we've already encoded the extended flag + if ((virtualKey & 0x100) != 0) + return true; + + // check known virtual keys + switch (virtualKey & 0xff) { + case VK_NUMLOCK: + case VK_RCONTROL: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + case VK_APPS: + return true; + + default: + return false; + } +} + +void CMSWindowsSecondaryScreen::sendKeyEvent( + UINT virtualKey, bool press) +{ + DWORD flags = 0; + if (isExtendedKey(virtualKey)) + flags |= KEYEVENTF_EXTENDEDKEY; + if (!press) + flags |= KEYEVENTF_KEYUP; + const UINT code = virtualKeyToScanCode(virtualKey); + keybd_event(static_cast(virtualKey & 0xff), + static_cast(code), flags, 0); + log((CLOG_DEBUG2 "send key %d, 0x%04x, %s%s", virtualKey & 0xff, code, ((flags & KEYEVENTF_KEYUP) ? "release" : "press"), ((flags & KEYEVENTF_EXTENDEDKEY) ? " extended" : ""))); } diff --git a/client/CMSWindowsSecondaryScreen.h b/client/CMSWindowsSecondaryScreen.h index d05f44ab..8149ea92 100644 --- a/client/CMSWindowsSecondaryScreen.h +++ b/client/CMSWindowsSecondaryScreen.h @@ -57,6 +57,9 @@ private: void updateKeys(); void updateModifiers(); void toggleKey(UINT virtualKey, KeyModifierMask mask); + UINT virtualKeyToScanCode(UINT& virtualKey); + bool isExtendedKey(UINT virtualKey); + void sendKeyEvent(UINT virtualKey, bool press); private: CClient* m_client; @@ -64,6 +67,9 @@ private: HWND m_nextClipboardWindow; HWND m_clipboardOwner; + // thread id of the event loop thread + DWORD m_threadID; + // virtual key states BYTE m_keys[256]; diff --git a/server/CMSWindowsPrimaryScreen.cpp b/server/CMSWindowsPrimaryScreen.cpp index f5f67872..5d3a36b6 100644 --- a/server/CMSWindowsPrimaryScreen.cpp +++ b/server/CMSWindowsPrimaryScreen.cpp @@ -31,47 +31,15 @@ CMSWindowsPrimaryScreen::~CMSWindowsPrimaryScreen() assert(m_hookLibrary == NULL); } -static CString s_log; -static CString s_logMore; -static HWND s_debug = NULL; -static HWND s_debugLog = NULL; -static DWORD s_thread = 0; -static BOOL CALLBACK WINAPI debugProc(HWND, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: - return TRUE; - - case WM_CLOSE: - PostQuitMessage(0); - return TRUE; - - case WM_APP: - if (!s_logMore.empty()) { - if (s_log.size() > 20000) - s_log = s_logMore; - else - s_log += s_logMore; - s_logMore = ""; - SendMessage(s_debugLog, WM_SETTEXT, FALSE, (LPARAM)(LPCTSTR)s_log.c_str()); - SendMessage(s_debugLog, EM_SETSEL, s_log.size(), s_log.size()); - SendMessage(s_debugLog, EM_SCROLLCARET, 0, 0); - } - return TRUE; - } - return FALSE; -} -static void debugOutput(const char* msg) -{ - s_logMore += msg; - PostMessage(s_debug, WM_APP, 0, 0); -} - void CMSWindowsPrimaryScreen::run() { -CLog::setOutputter(&debugOutput); + // change our priority + CThread::getCurrentThread().setPriority(-3); + + // run event loop + log((CLOG_INFO "entering event loop")); doRun(); -CLog::setOutputter(NULL); + log((CLOG_INFO "exiting event loop")); } void CMSWindowsPrimaryScreen::stop() @@ -87,12 +55,12 @@ void CMSWindowsPrimaryScreen::open(CServer* server) // set the server m_server = server; - // get keyboard state - updateKeys(); - // open the display openDisplay(); + // get keyboard state + updateKeys(); + // enter the screen doEnter(); } @@ -113,7 +81,6 @@ void CMSWindowsPrimaryScreen::enter(SInt32 x, SInt32 y) log((CLOG_INFO "entering primary at %d,%d", x, y)); assert(m_active == true); - // do non-warp enter stuff doEnter(); // warp to requested location @@ -122,12 +89,8 @@ void CMSWindowsPrimaryScreen::enter(SInt32 x, SInt32 y) void CMSWindowsPrimaryScreen::doEnter() { - // release the capture - ReleaseCapture(); - - // hide our window and restore the foreground window - SetForegroundWindow(m_lastActive); - ShowWindow(m_window, SW_HIDE); + // not active anymore + m_active = false; // set the zones that should cause a jump SInt32 w, h; @@ -136,11 +99,23 @@ void CMSWindowsPrimaryScreen::doEnter() m_hookLibrary, "setZone"); setZone(m_server->getActivePrimarySides(), w, h, getJumpZoneSize()); + // restore the active window and hide our window. we can only set + // the active window for another thread if we first attach our input + // to that thread. + ReleaseCapture(); + if (m_lastActiveWindow != NULL) { + DWORD myThread = GetCurrentThreadId(); + if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) { + // FIXME -- shouldn't raise window if X-Mouse is enabled + // but i have no idea how to do that or check if enabled. + SetActiveWindow(m_lastActiveWindow); + AttachThreadInput(myThread, m_lastActiveThread, FALSE); + } + } + ShowWindow(m_window, SW_HIDE); + // all messages prior to now are invalid nextMark(); - - // not active anymore - m_active = false; } void CMSWindowsPrimaryScreen::leave() @@ -148,30 +123,50 @@ void CMSWindowsPrimaryScreen::leave() log((CLOG_INFO "leaving primary")); assert(m_active == false); + // do non-warp enter stuff + // get state of keys as we leave + updateKeys(); + // all messages prior to now are invalid nextMark(); - // remember the active window before we leave - m_lastActive = GetForegroundWindow(); + // remember the active window before we leave. GetActiveWindow() + // will only return the active window for the thread's queue (i.e. + // our app) but we need the globally active window. get that by + // attaching input to the foreground window's thread then calling + // GetActiveWindow() and then detaching our input. + m_lastActiveWindow = NULL; + m_lastForegroundWindow = GetForegroundWindow(); + m_lastActiveThread = GetWindowThreadProcessId( + m_lastForegroundWindow, NULL); + if (m_lastActiveThread != 0) { + DWORD myThread = GetCurrentThreadId(); + if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) { + m_lastActiveWindow = GetActiveWindow(); + AttachThreadInput(myThread, m_lastActiveThread, FALSE); + } + } - // show our window and put it in the foreground + // show our window ShowWindow(m_window, SW_SHOW); - SetForegroundWindow(m_window); - - // capture the cursor so we don't lose keyboard input - SetCapture(m_window); // relay all mouse and keyboard events SetRelayFunc setRelay = (SetRelayFunc)GetProcAddress( m_hookLibrary, "setRelay"); setRelay(); - // warp the mouse to the center of the screen - SInt32 w, h; - getScreenSize(&w, &h); - warpCursor(w >> 1, h >> 1); + // get keyboard input and capture mouse + SetActiveWindow(m_window); + SetFocus(m_window); + SetCapture(m_window); - // warp is also invalid + // warp the mouse to the center of the screen + getScreenSize(&m_xCenter, &m_yCenter); + m_xCenter >>= 1; + m_yCenter >>= 1; + warpCursor(m_xCenter, m_yCenter); + + // discard messages until after the warp nextMark(); // local client now active @@ -205,12 +200,8 @@ void CMSWindowsPrimaryScreen::leave() void CMSWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y) { - SInt32 w, h; - getScreenSize(&w, &h); - mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, - (DWORD)((65535.99 * x) / (w - 1)), - (DWORD)((65535.99 * y) / (h - 1)), - 0, 0); + // set the cursor position without generating an event + SetCursorPos(x, y); } void CMSWindowsPrimaryScreen::setClipboard( @@ -254,7 +245,7 @@ void CMSWindowsPrimaryScreen::getClipboard( KeyModifierMask CMSWindowsPrimaryScreen::getToggleMask() const { - KeyModifierMask mask; + KeyModifierMask mask = 0; if ((m_keys[VK_CAPITAL] & 0x01) != 0) mask |= KeyModifierCapsLock; if ((m_keys[VK_NUMLOCK] & 0x01) != 0) @@ -264,30 +255,30 @@ KeyModifierMask CMSWindowsPrimaryScreen::getToggleMask() const return mask; } -#include "resource.h" // FIXME - void CMSWindowsPrimaryScreen::onOpenDisplay() { assert(m_window == NULL); assert(m_server != NULL); -// create debug dialog -s_thread = GetCurrentThreadId();; -s_debug = CreateDialog(getInstance(), MAKEINTRESOURCE(IDD_SYNERGY), NULL, &debugProc); -s_debugLog = ::GetDlgItem(s_debug, IDC_LOG); -CLog::setOutputter(&debugOutput); -ShowWindow(s_debug, SW_SHOWNORMAL); - // initialize clipboard owner to current owner. we don't want // to take ownership of the clipboard just by starting up. m_clipboardOwner = GetClipboardOwner(); + // get screen size + // note -- we use a fullscreen window to grab input. it should + // be possible to use a 1x1 window but i've run into problems + // with losing keyboard input (focus?) in that case. + // unfortunately, hiding the full screen window causes all other + // windows to redraw. + SInt32 w, h; + getScreenSize(&w, &h); + // create the window m_window = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, (LPCTSTR)getClass(), "Synergy", WS_POPUP, - 0, 0, 1, 1, NULL, NULL, + 0, 0, w, h, NULL, NULL, getInstance(), NULL); assert(m_window != NULL); @@ -307,6 +298,7 @@ ShowWindow(s_debug, SW_SHOWNORMAL); } } if (!hooked) { + log((CLOG_ERR "failed to install hooks")); ChangeClipboardChain(m_window, m_nextClipboardWindow); m_nextClipboardWindow = NULL; DestroyWindow(m_window); @@ -342,19 +334,10 @@ void CMSWindowsPrimaryScreen::onCloseDisplay() // destroy window DestroyWindow(m_window); m_window = NULL; - -CLog::setOutputter(NULL); -DestroyWindow(s_debug); -s_debug = NULL; -s_thread = 0; } bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg) { -if (IsDialogMessage(s_debug, msg)) { - return true; -} - // handle event switch (msg->message) { case SYNERGY_MSG_MARK: @@ -391,7 +374,9 @@ if (IsDialogMessage(s_debug, msg)) { updateKey(msg->wParam, false); } } - + else { + log((CLOG_DEBUG2 "event: cannot map key wParam=%d lParam=0x%08x", msg->wParam, msg->lParam)); + } } return true; @@ -431,29 +416,16 @@ if (IsDialogMessage(s_debug, msg)) { m_server->onMouseMovePrimary(x, y); } else { + // get mouse deltas + x -= m_xCenter; + y -= m_yCenter; log((CLOG_DEBUG2 "event: active move %d,%d", x, y)); - // get screen size - SInt32 w, h; - getScreenSize(&w, &h); + // warp mouse back to center + warpCursor(m_xCenter, m_yCenter); - // get center pixel - w >>= 1; - h >>= 1; - - // ignore and discard message if motion is to center of - // screen. those are caused by us warping the mouse. - if (x != w || y != h) { - // get mouse deltas - x -= w; - y -= h; - - // warp mouse back to center - warpCursor(w, h); - - // send motion - m_server->onMouseMoveSecondary(x, y); - } + // send motion + m_server->onMouseMoveSecondary(x, y); } } return true; @@ -467,7 +439,7 @@ LRESULT CMSWindowsPrimaryScreen::onEvent( WPARAM wParam, LPARAM lParam) { switch (msg) { - // FIXME -- handle display changes + // FIXME -- handle display changes (and resize full-screen window) case WM_PAINT: ValidateRect(hwnd, NULL); return 0; @@ -500,6 +472,7 @@ LRESULT CMSWindowsPrimaryScreen::onEvent( SendMessage(m_nextClipboardWindow, msg, wParam, lParam); return 0; } + return DefWindowProc(hwnd, msg, wParam, lParam); } @@ -808,6 +781,7 @@ KeyID CMSWindowsPrimaryScreen::mapKey( if ((m_keys[VK_SCROLL] & 0x01) != 0) mask |= KeyModifierScrollLock; *maskOut = mask; + log((CLOG_DEBUG2 "key in vk=%d info=0x%08x mask=0x%04x", vkCode, info, mask)); // get the scan code UINT scanCode = static_cast((info & 0xff0000) >> 16); @@ -827,6 +801,10 @@ KeyID CMSWindowsPrimaryScreen::mapKey( else if (vkCode >= VK_LWIN && vkCode <= VK_APPS) vkCode2 = vkCode; + // if MapVirtualKey failed then use original virtual key + else if (vkCode2 == 0) + vkCode2 = vkCode; + // sadly, win32 will not distinguish between the left and right // control and alt keys using the above function. however, we // can check for those: if bit 24 of info is set then the key @@ -834,10 +812,12 @@ KeyID CMSWindowsPrimaryScreen::mapKey( // keys. if ((info & 0x1000000) != 0) { switch (vkCode2) { + case VK_CONTROL: case VK_LCONTROL: vkCode2 = VK_RCONTROL; break; + case VK_MENU: case VK_LMENU: vkCode2 = VK_RMENU; break; @@ -898,7 +878,7 @@ KeyID CMSWindowsPrimaryScreen::mapKey( else if (result == 2) { // get the scan code of the dead key and the shift state // required to generate it. - vkCode = VkKeyScan(ascii & 0x00ff); + vkCode = VkKeyScan(static_cast(ascii & 0x00ff)); // set shift state required to generate key BYTE keys[256]; @@ -948,25 +928,29 @@ ButtonID CMSWindowsPrimaryScreen::mapButton( void CMSWindowsPrimaryScreen::updateKeys() { + // not using GetKeyboardState() because that doesn't seem to give + // up-to-date results. i don't know why that is or why GetKeyState() + // should give different results. + // clear key state memset(m_keys, 0, sizeof(m_keys)); // we only care about the modifier key states - m_keys[VK_LSHIFT] = GetKeyState(VK_LSHIFT); - m_keys[VK_RSHIFT] = GetKeyState(VK_RSHIFT); - m_keys[VK_SHIFT] = GetKeyState(VK_SHIFT); - m_keys[VK_LCONTROL] = GetKeyState(VK_LCONTROL); - m_keys[VK_RCONTROL] = GetKeyState(VK_RCONTROL); - m_keys[VK_CONTROL] = GetKeyState(VK_CONTROL); - m_keys[VK_LMENU] = GetKeyState(VK_LMENU); - m_keys[VK_RMENU] = GetKeyState(VK_RMENU); - m_keys[VK_MENU] = GetKeyState(VK_MENU); - m_keys[VK_LWIN] = GetKeyState(VK_LWIN); - m_keys[VK_RWIN] = GetKeyState(VK_RWIN); - m_keys[VK_APPS] = GetKeyState(VK_APPS); - m_keys[VK_CAPITAL] = GetKeyState(VK_CAPITAL); - m_keys[VK_NUMLOCK] = GetKeyState(VK_NUMLOCK); - m_keys[VK_SCROLL] = GetKeyState(VK_SCROLL); + m_keys[VK_LSHIFT] = static_cast(GetKeyState(VK_LSHIFT)); + m_keys[VK_RSHIFT] = static_cast(GetKeyState(VK_RSHIFT)); + m_keys[VK_SHIFT] = static_cast(GetKeyState(VK_SHIFT)); + m_keys[VK_LCONTROL] = static_cast(GetKeyState(VK_LCONTROL)); + m_keys[VK_RCONTROL] = static_cast(GetKeyState(VK_RCONTROL)); + m_keys[VK_CONTROL] = static_cast(GetKeyState(VK_CONTROL)); + m_keys[VK_LMENU] = static_cast(GetKeyState(VK_LMENU)); + m_keys[VK_RMENU] = static_cast(GetKeyState(VK_RMENU)); + m_keys[VK_MENU] = static_cast(GetKeyState(VK_MENU)); + m_keys[VK_LWIN] = static_cast(GetKeyState(VK_LWIN)); + m_keys[VK_RWIN] = static_cast(GetKeyState(VK_RWIN)); + m_keys[VK_APPS] = static_cast(GetKeyState(VK_APPS)); + m_keys[VK_CAPITAL] = static_cast(GetKeyState(VK_CAPITAL)); + m_keys[VK_NUMLOCK] = static_cast(GetKeyState(VK_NUMLOCK)); + m_keys[VK_SCROLL] = static_cast(GetKeyState(VK_SCROLL)); } void CMSWindowsPrimaryScreen::updateKey( diff --git a/server/CMSWindowsPrimaryScreen.h b/server/CMSWindowsPrimaryScreen.h index f24d51a2..35bb7167 100644 --- a/server/CMSWindowsPrimaryScreen.h +++ b/server/CMSWindowsPrimaryScreen.h @@ -52,11 +52,14 @@ private: HWND m_window; HWND m_nextClipboardWindow; HWND m_clipboardOwner; - HWND m_lastActive; + HWND m_lastForegroundWindow; + HWND m_lastActiveWindow; + DWORD m_lastActiveThread; HINSTANCE m_hookLibrary; UInt32 m_mark; UInt32 m_markReceived; BYTE m_keys[256]; + SInt32 m_xCenter, m_yCenter; }; #endif