/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2004 Chris Schoeneman * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file COPYING that should have accompanied this file. * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "CMSWindowsDesks.h" #include "CMSWindowsScreen.h" #include "CSynergyHook.h" #include "IScreenSaver.h" #include "XScreen.h" #include "CLock.h" #include "CThread.h" #include "CLog.h" #include "IEventQueue.h" #include "IJob.h" #include "TMethodEventJob.h" #include "TMethodJob.h" #include "CArchMiscWindows.h" #include // these are only defined when WINVER >= 0x0500 #if !defined(SPI_GETMOUSESPEED) #define SPI_GETMOUSESPEED 112 #endif #if !defined(SPI_SETMOUSESPEED) #define SPI_SETMOUSESPEED 113 #endif #if !defined(SPI_GETSCREENSAVERRUNNING) #define SPI_GETSCREENSAVERRUNNING 114 #endif // X button stuff #if !defined(WM_XBUTTONDOWN) #define WM_XBUTTONDOWN 0x020B #define WM_XBUTTONUP 0x020C #define WM_XBUTTONDBLCLK 0x020D #define WM_NCXBUTTONDOWN 0x00AB #define WM_NCXBUTTONUP 0x00AC #define WM_NCXBUTTONDBLCLK 0x00AD #define MOUSEEVENTF_XDOWN 0x0080 #define MOUSEEVENTF_XUP 0x0100 #define XBUTTON1 0x0001 #define XBUTTON2 0x0002 #endif #if !defined(VK_XBUTTON1) #define VK_XBUTTON1 0x05 #define VK_XBUTTON2 0x06 #endif // ; #define SYNERGY_MSG_SWITCH SYNERGY_HOOK_LAST_MSG + 1 // ; #define SYNERGY_MSG_ENTER SYNERGY_HOOK_LAST_MSG + 2 // ; #define SYNERGY_MSG_LEAVE SYNERGY_HOOK_LAST_MSG + 3 // wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code #define SYNERGY_MSG_FAKE_KEY SYNERGY_HOOK_LAST_MSG + 4 // flags, XBUTTON id #define SYNERGY_MSG_FAKE_BUTTON SYNERGY_HOOK_LAST_MSG + 5 // x; y #define SYNERGY_MSG_FAKE_MOVE SYNERGY_HOOK_LAST_MSG + 6 // xDelta; yDelta #define SYNERGY_MSG_FAKE_WHEEL SYNERGY_HOOK_LAST_MSG + 7 // POINT*; #define SYNERGY_MSG_CURSOR_POS SYNERGY_HOOK_LAST_MSG + 8 // IKeyState*; #define SYNERGY_MSG_SYNC_KEYS SYNERGY_HOOK_LAST_MSG + 9 // install; #define SYNERGY_MSG_SCREENSAVER SYNERGY_HOOK_LAST_MSG + 10 // dx; dy #define SYNERGY_MSG_FAKE_REL_MOVE SYNERGY_HOOK_LAST_MSG + 11 // enable; #define SYNERGY_MSG_FAKE_INPUT SYNERGY_HOOK_LAST_MSG + 12 // // CMSWindowsDesks // CMSWindowsDesks::CMSWindowsDesks( bool isPrimary, HINSTANCE hookLibrary, const IScreenSaver* screensaver, IJob* updateKeys) : m_isPrimary(isPrimary), m_is95Family(CArchMiscWindows::isWindows95Family()), m_isModernFamily(CArchMiscWindows::isWindowsModern()), m_isOnScreen(m_isPrimary), m_x(0), m_y(0), m_w(0), m_h(0), m_xCenter(0), m_yCenter(0), m_multimon(false), m_timer(NULL), m_screensaver(screensaver), m_screensaverNotify(false), m_activeDesk(NULL), m_activeDeskName(), m_mutex(), m_deskReady(&m_mutex, false), m_updateKeys(updateKeys) { queryHookLibrary(hookLibrary); m_cursor = createBlankCursor(); m_deskClass = createDeskWindowClass(m_isPrimary); m_keyLayout = GetKeyboardLayout(GetCurrentThreadId()); resetOptions(); } CMSWindowsDesks::~CMSWindowsDesks() { disable(); destroyClass(m_deskClass); destroyCursor(m_cursor); delete m_updateKeys; } void CMSWindowsDesks::enable() { m_threadID = GetCurrentThreadId(); // set the active desk and (re)install the hooks checkDesk(); // install the desk timer. this timer periodically checks // which desk is active and reinstalls the hooks as necessary. // we wouldn't need this if windows notified us of a desktop // change but as far as i can tell it doesn't. m_timer = EVENTQUEUE->newTimer(0.2, NULL); EVENTQUEUE->adoptHandler(CEvent::kTimer, m_timer, new TMethodEventJob( this, &CMSWindowsDesks::handleCheckDesk)); updateKeys(); } void CMSWindowsDesks::disable() { // remove timer if (m_timer != NULL) { EVENTQUEUE->removeHandler(CEvent::kTimer, m_timer); EVENTQUEUE->deleteTimer(m_timer); m_timer = NULL; } // destroy desks removeDesks(); m_isOnScreen = m_isPrimary; } void CMSWindowsDesks::enter() { sendMessage(SYNERGY_MSG_ENTER, 0, 0); } void CMSWindowsDesks::leave(HKL keyLayout) { sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)keyLayout, 0); } void CMSWindowsDesks::resetOptions() { m_leaveForegroundOption = false; } void CMSWindowsDesks::setOptions(const COptionsList& options) { for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) { if (options[i] == kOptionWin32KeepForeground) { m_leaveForegroundOption = (options[i + 1] != 0); LOG((CLOG_DEBUG1 "%s the foreground window", m_leaveForegroundOption ? "Don\'t grab" : "Grab")); } } } void CMSWindowsDesks::updateKeys() { sendMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0); } void CMSWindowsDesks::setShape(SInt32 x, SInt32 y, SInt32 width, SInt32 height, SInt32 xCenter, SInt32 yCenter, bool isMultimon) { m_x = x; m_y = y; m_w = width; m_h = height; m_xCenter = xCenter; m_yCenter = yCenter; m_multimon = isMultimon; } void CMSWindowsDesks::installScreensaverHooks(bool install) { if (m_isPrimary && m_screensaverNotify != install) { m_screensaverNotify = install; sendMessage(SYNERGY_MSG_SCREENSAVER, install, 0); } } void CMSWindowsDesks::fakeInputBegin() { sendMessage(SYNERGY_MSG_FAKE_INPUT, 1, 0); } void CMSWindowsDesks::fakeInputEnd() { sendMessage(SYNERGY_MSG_FAKE_INPUT, 0, 0); } void CMSWindowsDesks::getCursorPos(SInt32& x, SInt32& y) const { POINT pos; sendMessage(SYNERGY_MSG_CURSOR_POS, reinterpret_cast(&pos), 0); x = pos.x; y = pos.y; } void CMSWindowsDesks::fakeKeyEvent( KeyButton button, UINT virtualKey, bool press, bool /*isAutoRepeat*/) const { // win 95 family doesn't understand handed modifier virtual keys if (m_is95Family) { switch (virtualKey) { case VK_LSHIFT: case VK_RSHIFT: virtualKey = VK_SHIFT; break; case VK_LCONTROL: case VK_RCONTROL: virtualKey = VK_CONTROL; break; case VK_LMENU: case VK_RMENU: virtualKey = VK_MENU; break; } } // synthesize event DWORD flags = 0; if (((button & 0x100u) != 0)) { flags |= KEYEVENTF_EXTENDEDKEY; } if (!press) { flags |= KEYEVENTF_KEYUP; } sendMessage(SYNERGY_MSG_FAKE_KEY, flags, MAKEWORD(static_cast(button & 0xffu), static_cast(virtualKey & 0xffu))); } void CMSWindowsDesks::fakeMouseButton(ButtonID button, bool press) const { // the system will swap the meaning of left/right for us if // the user has configured a left-handed mouse but we don't // want it to swap since we want the handedness of the // server's mouse. so pre-swap for a left-handed mouse. if (GetSystemMetrics(SM_SWAPBUTTON)) { switch (button) { case kButtonLeft: button = kButtonRight; break; case kButtonRight: button = kButtonLeft; break; } } // map button id to button flag and button data DWORD data = 0; DWORD flags; switch (button) { case kButtonLeft: flags = press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; case kButtonMiddle: flags = press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; case kButtonRight: flags = press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; case kButtonExtra0 + 0: data = XBUTTON1; flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; break; case kButtonExtra0 + 1: data = XBUTTON2; flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; break; default: return; } // do it sendMessage(SYNERGY_MSG_FAKE_BUTTON, flags, data); } void CMSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const { sendMessage(SYNERGY_MSG_FAKE_MOVE, static_cast(x), static_cast(y)); } void CMSWindowsDesks::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const { sendMessage(SYNERGY_MSG_FAKE_REL_MOVE, static_cast(dx), static_cast(dy)); } void CMSWindowsDesks::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const { sendMessage(SYNERGY_MSG_FAKE_WHEEL, xDelta, yDelta); } void CMSWindowsDesks::sendMessage(UINT msg, WPARAM wParam, LPARAM lParam) const { if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) { PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam); waitForDesk(); } } void CMSWindowsDesks::queryHookLibrary(HINSTANCE hookLibrary) { // look up functions if (m_isPrimary) { m_install = (InstallFunc)GetProcAddress(hookLibrary, "install"); m_uninstall = (UninstallFunc)GetProcAddress(hookLibrary, "uninstall"); m_installScreensaver = (InstallScreenSaverFunc)GetProcAddress( hookLibrary, "installScreenSaver"); m_uninstallScreensaver = (UninstallScreenSaverFunc)GetProcAddress( hookLibrary, "uninstallScreenSaver"); if (m_install == NULL || m_uninstall == NULL || m_installScreensaver == NULL || m_uninstallScreensaver == NULL) { LOG((CLOG_ERR "Invalid hook library")); throw XScreenOpenFailure(); } } else { m_install = NULL; m_uninstall = NULL; m_installScreensaver = NULL; m_uninstallScreensaver = NULL; } } HCURSOR CMSWindowsDesks::createBlankCursor() const { // create a transparent cursor int cw = GetSystemMetrics(SM_CXCURSOR); int ch = GetSystemMetrics(SM_CYCURSOR); UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)]; UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)]; memset(cursorAND, 0xff, ch * ((cw + 31) >> 2)); memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); HCURSOR c = CreateCursor(CMSWindowsScreen::getInstance(), 0, 0, cw, ch, cursorAND, cursorXOR); delete[] cursorXOR; delete[] cursorAND; return c; } void CMSWindowsDesks::destroyCursor(HCURSOR cursor) const { if (cursor != NULL) { DestroyCursor(cursor); } } ATOM CMSWindowsDesks::createDeskWindowClass(bool isPrimary) const { WNDCLASSEX classInfo; classInfo.cbSize = sizeof(classInfo); classInfo.style = CS_DBLCLKS | CS_NOCLOSE; classInfo.lpfnWndProc = isPrimary ? &CMSWindowsDesks::primaryDeskProc : &CMSWindowsDesks::secondaryDeskProc; classInfo.cbClsExtra = 0; classInfo.cbWndExtra = 0; classInfo.hInstance = CMSWindowsScreen::getInstance(); classInfo.hIcon = NULL; classInfo.hCursor = m_cursor; classInfo.hbrBackground = NULL; classInfo.lpszMenuName = NULL; classInfo.lpszClassName = "SynergyDesk"; classInfo.hIconSm = NULL; return RegisterClassEx(&classInfo); } void CMSWindowsDesks::destroyClass(ATOM windowClass) const { if (windowClass != 0) { UnregisterClass(reinterpret_cast(windowClass), CMSWindowsScreen::getInstance()); } } HWND CMSWindowsDesks::createWindow(ATOM windowClass, const char* name) const { HWND window = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, reinterpret_cast(windowClass), name, WS_POPUP, 0, 0, 1, 1, NULL, NULL, CMSWindowsScreen::getInstance(), NULL); if (window == NULL) { LOG((CLOG_ERR "failed to create window: %d", GetLastError())); throw XScreenOpenFailure(); } return window; } void CMSWindowsDesks::destroyWindow(HWND hwnd) const { if (hwnd != NULL) { DestroyWindow(hwnd); } } LRESULT CALLBACK CMSWindowsDesks::primaryDeskProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hwnd, msg, wParam, lParam); } LRESULT CALLBACK CMSWindowsDesks::secondaryDeskProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // would like to detect any local user input and hide the hider // window but for now we just detect mouse motion. bool hide = false; switch (msg) { case WM_MOUSEMOVE: if (LOWORD(lParam) != 0 || HIWORD(lParam) != 0) { hide = true; } break; } if (hide && IsWindowVisible(hwnd)) { ReleaseCapture(); SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_HIDEWINDOW); } return DefWindowProc(hwnd, msg, wParam, lParam); } void CMSWindowsDesks::deskMouseMove(SInt32 x, SInt32 y) const { // motion is simple (i.e. it's on the primary monitor) if there // is only one monitor. it's also simple if we're not on the // windows 95 family since those platforms don't have a broken // mouse_event() function (see the comment below). bool simple = (!m_multimon || !m_is95Family); if (!simple) { // also simple if motion is within the primary monitor simple = (x >= 0 && x < GetSystemMetrics(SM_CXSCREEN) && y >= 0 && y < GetSystemMetrics(SM_CYSCREEN)); } // move the mouse directly to target position if motion is simple if (simple) { // when using absolute positioning with mouse_event(), // the normalized device coordinates range over only // the primary screen. SInt32 w = GetSystemMetrics(SM_CXSCREEN); SInt32 h = GetSystemMetrics(SM_CYSCREEN); mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, (DWORD)((65535.0f * x) / (w - 1) + 0.5f), (DWORD)((65535.0f * y) / (h - 1) + 0.5f), 0, 0); } // windows 98 and Me are broken. you cannot set the absolute // position of the mouse except on the primary monitor but you // can do relative moves onto any monitor. this is, in microsoft's // words, "by design." apparently the designers of windows 2000 // we're a little less lazy and did it right. // // microsoft recommends in Q193003 to absolute position the cursor // somewhere on the primary monitor then relative move to the // desired location. this doesn't work for us because when the // user drags a scrollbar, a window, etc. it causes the dragged // item to jump back and forth between the position on the primary // monitor and the desired position. while it always ends up in // the right place, the effect is disconcerting. // // instead we'll get the cursor's current position and do just a // relative move from there to the desired position. else { POINT pos; GetCursorPos(&pos); deskMouseRelativeMove(x - pos.x, y - pos.y); } } void CMSWindowsDesks::deskMouseRelativeMove(SInt32 dx, SInt32 dy) const { // relative moves are subject to cursor acceleration which we don't // want.so we disable acceleration, do the relative move, then // restore acceleration. there's a slight chance we'll end up in // the wrong place if the user moves the cursor using this system's // mouse while simultaneously moving the mouse on the server // system. that defeats the purpose of synergy so we'll assume // that won't happen. even if it does, the next mouse move will // correct the position. // save mouse speed & acceleration int oldSpeed[4]; bool accelChanged = SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed, 0) && SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0); // use 1:1 motion if (accelChanged) { int newSpeed[4] = { 0, 0, 0, 1 }; accelChanged = SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) || SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0); } // move relative to mouse position mouse_event(MOUSEEVENTF_MOVE, dx, dy, 0, 0); // restore mouse speed & acceleration if (accelChanged) { SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); } } void CMSWindowsDesks::deskEnter(CDesk* desk) { if (!m_isPrimary) { ReleaseCapture(); } ShowCursor(TRUE); SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_HIDEWINDOW); // restore the foreground window // XXX -- this raises the window to the top of the Z-order. we // want it to stay wherever it was to properly support X-mouse // (mouse over activation) but i've no idea how to do that. // the obvious workaround of using SetWindowPos() to move it back // after being raised doesn't work. DWORD thisThread = GetWindowThreadProcessId(desk->m_window, NULL); DWORD thatThread = GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); AttachThreadInput(thatThread, thisThread, TRUE); SetForegroundWindow(desk->m_foregroundWindow); AttachThreadInput(thatThread, thisThread, FALSE); EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE); desk->m_foregroundWindow = NULL; } void CMSWindowsDesks::deskLeave(CDesk* desk, HKL keyLayout) { ShowCursor(FALSE); if (m_isPrimary) { // 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) { // 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 | SWP_SHOWWINDOW); // if not using low-level hooks we have to also activate the // window to ensure we don't lose keyboard focus. // FIXME -- see if this can be avoided. if so then always // disable the window (see handling of SYNERGY_MSG_SWITCH). if (!desk->m_lowLevel) { SetActiveWindow(desk->m_window); } // if using low-level hooks then disable the foreground window // so it can't mess up any of our keyboard events. the console // program, for example, will cause characters to be reported as // unshifted, regardless of the shift key state. interestingly // we do see the shift key go down and up. // // note that we must enable the window to activate it and we // need to disable the window on deskEnter. else { desk->m_foregroundWindow = getForegroundWindow(); if (desk->m_foregroundWindow != NULL) { EnableWindow(desk->m_window, TRUE); SetActiveWindow(desk->m_window); DWORD thisThread = GetWindowThreadProcessId(desk->m_window, NULL); DWORD thatThread = GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); AttachThreadInput(thatThread, thisThread, TRUE); SetForegroundWindow(desk->m_window); AttachThreadInput(thatThread, thisThread, FALSE); } } // switch to requested keyboard layout ActivateKeyboardLayout(keyLayout, 0); } else { // move hider window under the cursor center, raise, and show it SetWindowPos(desk->m_window, HWND_TOPMOST, m_xCenter, m_yCenter, 1, 1, SWP_NOACTIVATE | SWP_SHOWWINDOW); // watch for mouse motion. if we see any then we hide the // hider window so the user can use the physically attached // mouse if desired. we'd rather not capture the mouse but // we aren't notified when the mouse leaves our window. SetCapture(desk->m_window); // warp the mouse to the cursor center deskMouseMove(m_xCenter, m_yCenter); } } void CMSWindowsDesks::deskThread(void* vdesk) { MSG msg; // use given desktop for this thread CDesk* desk = reinterpret_cast(vdesk); desk->m_threadID = GetCurrentThreadId(); desk->m_window = NULL; desk->m_foregroundWindow = NULL; if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) { // create a message queue PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE); // create a window. we use this window to hide the cursor. try { desk->m_window = createWindow(m_deskClass, "SynergyDesk"); LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window)); } catch (...) { // ignore LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str())); } } // tell main thread that we're ready { CLock lock(&m_mutex); m_deskReady = true; m_deskReady.broadcast(); } while (GetMessage(&msg, NULL, 0, 0)) { switch (msg.message) { default: TranslateMessage(&msg); DispatchMessage(&msg); continue; case SYNERGY_MSG_SWITCH: if (m_isPrimary) { m_uninstall(); if (m_screensaverNotify) { m_uninstallScreensaver(); m_installScreensaver(); } switch (m_install()) { case kHOOK_FAILED: // we won't work on this desk desk->m_lowLevel = false; break; case kHOOK_OKAY: desk->m_lowLevel = false; break; case kHOOK_OKAY_LL: desk->m_lowLevel = true; break; } // a window on the primary screen with low-level hooks // should never activate. if (desk->m_window) EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE); } break; case SYNERGY_MSG_ENTER: m_isOnScreen = true; deskEnter(desk); break; case SYNERGY_MSG_LEAVE: m_isOnScreen = false; m_keyLayout = (HKL)msg.wParam; deskLeave(desk, m_keyLayout); break; case SYNERGY_MSG_FAKE_KEY: keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), (DWORD)msg.wParam, 0); break; case SYNERGY_MSG_FAKE_BUTTON: if (msg.wParam != 0) { mouse_event((DWORD)msg.wParam, 0, 0, (DWORD)msg.lParam, 0); } break; case SYNERGY_MSG_FAKE_MOVE: deskMouseMove(static_cast(msg.wParam), static_cast(msg.lParam)); break; case SYNERGY_MSG_FAKE_REL_MOVE: deskMouseRelativeMove(static_cast(msg.wParam), static_cast(msg.lParam)); break; case SYNERGY_MSG_FAKE_WHEEL: // XXX -- add support for x-axis scrolling if (msg.lParam != 0) { mouse_event(MOUSEEVENTF_WHEEL, 0, 0, (DWORD)msg.lParam, 0); } break; case SYNERGY_MSG_CURSOR_POS: { POINT* pos = reinterpret_cast(msg.wParam); if (!GetCursorPos(pos)) { pos->x = m_xCenter; pos->y = m_yCenter; } break; } case SYNERGY_MSG_SYNC_KEYS: m_updateKeys->run(); break; case SYNERGY_MSG_SCREENSAVER: if (msg.wParam != 0) { m_installScreensaver(); } else { m_uninstallScreensaver(); } break; case SYNERGY_MSG_FAKE_INPUT: keybd_event(SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY, SYNERGY_HOOK_FAKE_INPUT_SCANCODE, msg.wParam ? 0 : KEYEVENTF_KEYUP, 0); break; } // notify that message was processed CLock lock(&m_mutex); m_deskReady = true; m_deskReady.broadcast(); } // clean up deskEnter(desk); if (desk->m_window != NULL) { DestroyWindow(desk->m_window); } if (desk->m_desk != NULL) { closeDesktop(desk->m_desk); } } CMSWindowsDesks::CDesk* CMSWindowsDesks::addDesk(const CString& name, HDESK hdesk) { CDesk* desk = new CDesk; desk->m_name = name; desk->m_desk = hdesk; desk->m_targetID = GetCurrentThreadId(); desk->m_thread = new CThread(new TMethodJob( this, &CMSWindowsDesks::deskThread, desk)); waitForDesk(); m_desks.insert(std::make_pair(name, desk)); return desk; } void CMSWindowsDesks::removeDesks() { for (CDesks::iterator index = m_desks.begin(); index != m_desks.end(); ++index) { CDesk* desk = index->second; PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0); desk->m_thread->wait(); delete desk->m_thread; delete desk; } m_desks.clear(); m_activeDesk = NULL; m_activeDeskName = ""; } void CMSWindowsDesks::checkDesk() { // get current desktop. if we already know about it then return. CDesk* desk; HDESK hdesk = openInputDesktop(); CString name = getDesktopName(hdesk); CDesks::const_iterator index = m_desks.find(name); if (index == m_desks.end()) { desk = addDesk(name, hdesk); // hold on to hdesk until thread exits so the desk can't // be removed by the system } else { closeDesktop(hdesk); desk = index->second; } // if active desktop changed then tell the old and new desk threads // about the change. don't switch desktops when the screensaver is // active becaue we'd most likely switch to the screensaver desktop // which would have the side effect of forcing the screensaver to // stop. if (name != m_activeDeskName && !m_screensaver->isActive()) { // show cursor on previous desk bool wasOnScreen = m_isOnScreen; if (!wasOnScreen) { sendMessage(SYNERGY_MSG_ENTER, 0, 0); } // check for desk accessibility change. we don't get events // from an inaccessible desktop so when we switch from an // inaccessible desktop to an accessible one we have to // update the keyboard state. LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str())); bool syncKeys = false; bool isAccessible = isDeskAccessible(desk); if (isDeskAccessible(m_activeDesk) != isAccessible) { if (isAccessible) { LOG((CLOG_DEBUG "desktop is now accessible")); syncKeys = true; } else { LOG((CLOG_DEBUG "desktop is now inaccessible")); } } // switch desk m_activeDesk = desk; m_activeDeskName = name; sendMessage(SYNERGY_MSG_SWITCH, 0, 0); // hide cursor on new desk if (!wasOnScreen) { sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0); } // update keys if necessary if (syncKeys) { updateKeys(); } } else if (name != m_activeDeskName) { // screen saver might have started PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, TRUE, 0); } } bool CMSWindowsDesks::isDeskAccessible(const CDesk* desk) const { return (desk != NULL && desk->m_desk != NULL); } void CMSWindowsDesks::waitForDesk() const { CMSWindowsDesks* self = const_cast(this); CLock lock(&m_mutex); while (!(bool)m_deskReady) { m_deskReady.wait(); } self->m_deskReady = false; } void CMSWindowsDesks::handleCheckDesk(const CEvent&, void*) { checkDesk(); // also check if screen saver is running if on a modern OS and // this is the primary screen. if (m_isPrimary && m_isModernFamily) { BOOL running; SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, FALSE); PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, running, 0); } } HDESK CMSWindowsDesks::openInputDesktop() { if (m_is95Family) { // there's only one desktop on windows 95 et al. return GetThreadDesktop(GetCurrentThreadId()); } else { return OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, TRUE, DESKTOP_CREATEWINDOW | DESKTOP_HOOKCONTROL | GENERIC_WRITE); } } void CMSWindowsDesks::closeDesktop(HDESK desk) { // on 95/98/me we don't need to close the desktop returned by // openInputDesktop(). if (desk != NULL && !m_is95Family) { CloseDesktop(desk); } } CString CMSWindowsDesks::getDesktopName(HDESK desk) { if (desk == NULL) { return CString(); } else if (m_is95Family) { return "desktop"; } else { DWORD size; GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR)); GetUserObjectInformation(desk, UOI_NAME, name, size, &size); CString result(name); return result; } } HWND CMSWindowsDesks::getForegroundWindow() const { // Ideally we'd return NULL as much as possible, only returning // the actual foreground window when we know it's going to mess // up our keyboard input. For now we'll just let the user // decide. if (m_leaveForegroundOption) { return NULL; } return GetForegroundWindow(); }