Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members

CMSWindowsDesks.cpp

00001 /*
00002  * synergy -- mouse and keyboard sharing utility
00003  * Copyright (C) 2004 Chris Schoeneman
00004  * 
00005  * This package is free software; you can redistribute it and/or
00006  * modify it under the terms of the GNU General Public License
00007  * found in the file COPYING that should have accompanied this file.
00008  * 
00009  * This package is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  */
00014 
00015 #include "CMSWindowsDesks.h"
00016 #include "CMSWindowsScreen.h"
00017 #include "CSynergyHook.h"
00018 #include "IScreenSaver.h"
00019 #include "XScreen.h"
00020 #include "CLock.h"
00021 #include "CThread.h"
00022 #include "CLog.h"
00023 #include "IEventQueue.h"
00024 #include "IJob.h"
00025 #include "TMethodEventJob.h"
00026 #include "TMethodJob.h"
00027 #include "CArchMiscWindows.h"
00028 #include <malloc.h>
00029 
00030 // these are only defined when WINVER >= 0x0500
00031 #if !defined(SPI_GETMOUSESPEED)
00032 #define SPI_GETMOUSESPEED 112
00033 #endif
00034 #if !defined(SPI_SETMOUSESPEED)
00035 #define SPI_SETMOUSESPEED 113
00036 #endif
00037 #if !defined(SPI_GETSCREENSAVERRUNNING)
00038 #define SPI_GETSCREENSAVERRUNNING 114
00039 #endif
00040 
00041 // X button stuff
00042 #if !defined(WM_XBUTTONDOWN)
00043 #define WM_XBUTTONDOWN      0x020B
00044 #define WM_XBUTTONUP        0x020C
00045 #define WM_XBUTTONDBLCLK    0x020D
00046 #define WM_NCXBUTTONDOWN    0x00AB
00047 #define WM_NCXBUTTONUP      0x00AC
00048 #define WM_NCXBUTTONDBLCLK  0x00AD
00049 #define MOUSEEVENTF_XDOWN   0x0080
00050 #define MOUSEEVENTF_XUP     0x0100
00051 #define XBUTTON1            0x0001
00052 #define XBUTTON2            0x0002
00053 #endif
00054 #if !defined(VK_XBUTTON1)
00055 #define VK_XBUTTON1         0x05
00056 #define VK_XBUTTON2         0x06
00057 #endif
00058 
00059 // <unused>; <unused>
00060 #define SYNERGY_MSG_SWITCH          SYNERGY_HOOK_LAST_MSG + 1
00061 // <unused>; <unused>
00062 #define SYNERGY_MSG_ENTER           SYNERGY_HOOK_LAST_MSG + 2
00063 // <unused>; <unused>
00064 #define SYNERGY_MSG_LEAVE           SYNERGY_HOOK_LAST_MSG + 3
00065 // wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code
00066 #define SYNERGY_MSG_FAKE_KEY        SYNERGY_HOOK_LAST_MSG + 4
00067  // flags, XBUTTON id
00068 #define SYNERGY_MSG_FAKE_BUTTON     SYNERGY_HOOK_LAST_MSG + 5
00069 // x; y
00070 #define SYNERGY_MSG_FAKE_MOVE       SYNERGY_HOOK_LAST_MSG + 6
00071 // xDelta; yDelta
00072 #define SYNERGY_MSG_FAKE_WHEEL      SYNERGY_HOOK_LAST_MSG + 7
00073 // POINT*; <unused>
00074 #define SYNERGY_MSG_CURSOR_POS      SYNERGY_HOOK_LAST_MSG + 8
00075 // IKeyState*; <unused>
00076 #define SYNERGY_MSG_SYNC_KEYS       SYNERGY_HOOK_LAST_MSG + 9
00077 // install; <unused>
00078 #define SYNERGY_MSG_SCREENSAVER     SYNERGY_HOOK_LAST_MSG + 10
00079 // dx; dy
00080 #define SYNERGY_MSG_FAKE_REL_MOVE   SYNERGY_HOOK_LAST_MSG + 11
00081 // enable; <unused>
00082 #define SYNERGY_MSG_FAKE_INPUT      SYNERGY_HOOK_LAST_MSG + 12
00083 
00084 //
00085 // CMSWindowsDesks
00086 //
00087 
00088 CMSWindowsDesks::CMSWindowsDesks(
00089                 bool isPrimary, HINSTANCE hookLibrary,
00090                 const IScreenSaver* screensaver, IJob* updateKeys) :
00091     m_isPrimary(isPrimary),
00092     m_is95Family(CArchMiscWindows::isWindows95Family()),
00093     m_isModernFamily(CArchMiscWindows::isWindowsModern()),
00094     m_isOnScreen(m_isPrimary),
00095     m_x(0), m_y(0),
00096     m_w(0), m_h(0),
00097     m_xCenter(0), m_yCenter(0),
00098     m_multimon(false),
00099     m_timer(NULL),
00100     m_screensaver(screensaver),
00101     m_screensaverNotify(false),
00102     m_activeDesk(NULL),
00103     m_activeDeskName(),
00104     m_mutex(),
00105     m_deskReady(&m_mutex, false),
00106     m_updateKeys(updateKeys)
00107 {
00108     queryHookLibrary(hookLibrary);
00109     m_cursor    = createBlankCursor();
00110     m_deskClass = createDeskWindowClass(m_isPrimary);
00111     m_keyLayout = GetKeyboardLayout(GetCurrentThreadId());
00112     resetOptions();
00113 }
00114 
00115 CMSWindowsDesks::~CMSWindowsDesks()
00116 {
00117     disable();
00118     destroyClass(m_deskClass);
00119     destroyCursor(m_cursor);
00120     delete m_updateKeys;
00121 }
00122 
00123 void
00124 CMSWindowsDesks::enable()
00125 {
00126     m_threadID = GetCurrentThreadId();
00127 
00128     // set the active desk and (re)install the hooks
00129     checkDesk();
00130 
00131     // install the desk timer.  this timer periodically checks
00132     // which desk is active and reinstalls the hooks as necessary.
00133     // we wouldn't need this if windows notified us of a desktop
00134     // change but as far as i can tell it doesn't.
00135     m_timer = EVENTQUEUE->newTimer(0.2, NULL);
00136     EVENTQUEUE->adoptHandler(CEvent::kTimer, m_timer,
00137                             new TMethodEventJob<CMSWindowsDesks>(
00138                                 this, &CMSWindowsDesks::handleCheckDesk));
00139 
00140     updateKeys();
00141 }
00142 
00143 void
00144 CMSWindowsDesks::disable()
00145 {
00146     // remove timer
00147     if (m_timer != NULL) {
00148         EVENTQUEUE->removeHandler(CEvent::kTimer, m_timer);
00149         EVENTQUEUE->deleteTimer(m_timer);
00150         m_timer = NULL;
00151     }
00152 
00153     // destroy desks
00154     removeDesks();
00155 
00156     m_isOnScreen = m_isPrimary;
00157 }
00158 
00159 void
00160 CMSWindowsDesks::enter()
00161 {
00162     sendMessage(SYNERGY_MSG_ENTER, 0, 0);
00163 }
00164 
00165 void
00166 CMSWindowsDesks::leave(HKL keyLayout)
00167 {
00168     sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)keyLayout, 0);
00169 }
00170 
00171 void
00172 CMSWindowsDesks::resetOptions()
00173 {
00174     m_leaveForegroundOption = false;
00175 }
00176 
00177 void
00178 CMSWindowsDesks::setOptions(const COptionsList& options)
00179 {
00180     for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
00181         if (options[i] == kOptionWin32KeepForeground) {
00182             m_leaveForegroundOption = (options[i + 1] != 0);
00183             LOG((CLOG_DEBUG1 "%s the foreground window", m_leaveForegroundOption ? "Don\'t grab" : "Grab"));
00184         }
00185     }
00186 }
00187 
00188 void
00189 CMSWindowsDesks::updateKeys()
00190 {
00191     sendMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0);
00192 }
00193 
00194 void
00195 CMSWindowsDesks::setShape(SInt32 x, SInt32 y,
00196                 SInt32 width, SInt32 height,
00197                 SInt32 xCenter, SInt32 yCenter, bool isMultimon)
00198 {
00199     m_x        = x;
00200     m_y        = y;
00201     m_w        = width;
00202     m_h        = height;
00203     m_xCenter  = xCenter;
00204     m_yCenter  = yCenter;
00205     m_multimon = isMultimon;
00206 }
00207 
00208 void
00209 CMSWindowsDesks::installScreensaverHooks(bool install)
00210 {
00211     if (m_isPrimary && m_screensaverNotify != install) {
00212         m_screensaverNotify = install;
00213         sendMessage(SYNERGY_MSG_SCREENSAVER, install, 0);
00214     }
00215 }
00216 
00217 void
00218 CMSWindowsDesks::fakeInputBegin()
00219 {
00220     sendMessage(SYNERGY_MSG_FAKE_INPUT, 1, 0);
00221 }
00222 
00223 void
00224 CMSWindowsDesks::fakeInputEnd()
00225 {
00226     sendMessage(SYNERGY_MSG_FAKE_INPUT, 0, 0);
00227 }
00228 
00229 void
00230 CMSWindowsDesks::getCursorPos(SInt32& x, SInt32& y) const
00231 {
00232     POINT pos;
00233     sendMessage(SYNERGY_MSG_CURSOR_POS, reinterpret_cast<WPARAM>(&pos), 0);
00234     x = pos.x;
00235     y = pos.y;
00236 }
00237 
00238 void
00239 CMSWindowsDesks::fakeKeyEvent(
00240                 KeyButton button, UINT virtualKey,
00241                 bool press, bool /*isAutoRepeat*/) const
00242 {
00243     // win 95 family doesn't understand handed modifier virtual keys
00244     if (m_is95Family) {
00245         switch (virtualKey) {
00246         case VK_LSHIFT:
00247         case VK_RSHIFT:
00248             virtualKey = VK_SHIFT;
00249             break;
00250 
00251         case VK_LCONTROL:
00252         case VK_RCONTROL:
00253             virtualKey = VK_CONTROL;
00254             break;
00255 
00256         case VK_LMENU:
00257         case VK_RMENU:
00258             virtualKey = VK_MENU;
00259             break;
00260         }
00261     }
00262 
00263     // synthesize event
00264     DWORD flags = 0;
00265     if (((button & 0x100u) != 0)) {
00266         flags |= KEYEVENTF_EXTENDEDKEY;
00267     }
00268     if (!press) {
00269         flags |= KEYEVENTF_KEYUP;
00270     }
00271     sendMessage(SYNERGY_MSG_FAKE_KEY, flags,
00272                             MAKEWORD(static_cast<BYTE>(button & 0xffu),
00273                                 static_cast<BYTE>(virtualKey & 0xffu)));
00274 }
00275 
00276 void
00277 CMSWindowsDesks::fakeMouseButton(ButtonID button, bool press) const
00278 {
00279     // the system will swap the meaning of left/right for us if
00280     // the user has configured a left-handed mouse but we don't
00281     // want it to swap since we want the handedness of the
00282     // server's mouse.  so pre-swap for a left-handed mouse.
00283     if (GetSystemMetrics(SM_SWAPBUTTON)) {
00284         switch (button) {
00285         case kButtonLeft:
00286             button = kButtonRight;
00287             break;
00288 
00289         case kButtonRight:
00290             button = kButtonLeft;
00291             break;
00292         }
00293     }
00294 
00295     // map button id to button flag and button data
00296     DWORD data = 0;
00297     DWORD flags;
00298     switch (button) {
00299     case kButtonLeft:
00300         flags = press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
00301         break;
00302 
00303     case kButtonMiddle:
00304         flags = press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
00305         break;
00306 
00307     case kButtonRight:
00308         flags = press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
00309         break;
00310 
00311     case kButtonExtra0 + 0:
00312         data = XBUTTON1;
00313         flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
00314         break;
00315 
00316     case kButtonExtra0 + 1:
00317         data = XBUTTON2;
00318         flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
00319         break;
00320 
00321     default:
00322         return;
00323     }
00324 
00325     // do it
00326     sendMessage(SYNERGY_MSG_FAKE_BUTTON, flags, data);
00327 }
00328 
00329 void
00330 CMSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const
00331 {
00332     sendMessage(SYNERGY_MSG_FAKE_MOVE,
00333                             static_cast<WPARAM>(x),
00334                             static_cast<LPARAM>(y));
00335 }
00336 
00337 void
00338 CMSWindowsDesks::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
00339 {
00340     sendMessage(SYNERGY_MSG_FAKE_REL_MOVE,
00341                             static_cast<WPARAM>(dx),
00342                             static_cast<LPARAM>(dy));
00343 }
00344 
00345 void
00346 CMSWindowsDesks::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
00347 {
00348     sendMessage(SYNERGY_MSG_FAKE_WHEEL, xDelta, yDelta);
00349 }
00350 
00351 void
00352 CMSWindowsDesks::sendMessage(UINT msg, WPARAM wParam, LPARAM lParam) const
00353 {
00354     if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) {
00355         PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam);
00356         waitForDesk();
00357     }
00358 }
00359 
00360 void
00361 CMSWindowsDesks::queryHookLibrary(HINSTANCE hookLibrary)
00362 {
00363     // look up functions
00364     if (m_isPrimary) {
00365         m_install   = (InstallFunc)GetProcAddress(hookLibrary, "install");
00366         m_uninstall = (UninstallFunc)GetProcAddress(hookLibrary, "uninstall");
00367         m_installScreensaver   =
00368                   (InstallScreenSaverFunc)GetProcAddress(
00369                                 hookLibrary, "installScreenSaver");
00370         m_uninstallScreensaver =
00371                   (UninstallScreenSaverFunc)GetProcAddress(
00372                                 hookLibrary, "uninstallScreenSaver");
00373         if (m_install              == NULL ||
00374             m_uninstall            == NULL ||
00375             m_installScreensaver   == NULL ||
00376             m_uninstallScreensaver == NULL) {
00377             LOG((CLOG_ERR "Invalid hook library"));
00378             throw XScreenOpenFailure();
00379         }
00380     }
00381     else {
00382         m_install              = NULL;
00383         m_uninstall            = NULL;
00384         m_installScreensaver   = NULL;
00385         m_uninstallScreensaver = NULL;
00386     }
00387 }
00388 
00389 HCURSOR
00390 CMSWindowsDesks::createBlankCursor() const
00391 {
00392     // create a transparent cursor
00393     int cw = GetSystemMetrics(SM_CXCURSOR);
00394     int ch = GetSystemMetrics(SM_CYCURSOR);
00395     UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)];
00396     UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)];
00397     memset(cursorAND, 0xff, ch * ((cw + 31) >> 2));
00398     memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2));
00399     HCURSOR c = CreateCursor(CMSWindowsScreen::getInstance(),
00400                             0, 0, cw, ch, cursorAND, cursorXOR);
00401     delete[] cursorXOR;
00402     delete[] cursorAND;
00403     return c;
00404 }
00405 
00406 void
00407 CMSWindowsDesks::destroyCursor(HCURSOR cursor) const
00408 {
00409     if (cursor != NULL) {
00410         DestroyCursor(cursor);
00411     }
00412 }
00413 
00414 ATOM
00415 CMSWindowsDesks::createDeskWindowClass(bool isPrimary) const
00416 {
00417     WNDCLASSEX classInfo;
00418     classInfo.cbSize        = sizeof(classInfo);
00419     classInfo.style         = CS_DBLCLKS | CS_NOCLOSE;
00420     classInfo.lpfnWndProc   = isPrimary ?
00421                                 &CMSWindowsDesks::primaryDeskProc :
00422                                 &CMSWindowsDesks::secondaryDeskProc;
00423     classInfo.cbClsExtra    = 0;
00424     classInfo.cbWndExtra    = 0;
00425     classInfo.hInstance     = CMSWindowsScreen::getInstance();
00426     classInfo.hIcon         = NULL;
00427     classInfo.hCursor       = m_cursor;
00428     classInfo.hbrBackground = NULL;
00429     classInfo.lpszMenuName  = NULL;
00430     classInfo.lpszClassName = "SynergyDesk";
00431     classInfo.hIconSm       = NULL;
00432     return RegisterClassEx(&classInfo);
00433 }
00434 
00435 void
00436 CMSWindowsDesks::destroyClass(ATOM windowClass) const
00437 {
00438     if (windowClass != 0) {
00439         UnregisterClass(reinterpret_cast<LPCTSTR>(windowClass),
00440                             CMSWindowsScreen::getInstance());
00441     }
00442 }
00443 
00444 HWND
00445 CMSWindowsDesks::createWindow(ATOM windowClass, const char* name) const
00446 {
00447     HWND window = CreateWindowEx(WS_EX_TOPMOST |
00448                                     WS_EX_TRANSPARENT |
00449                                     WS_EX_TOOLWINDOW,
00450                                 reinterpret_cast<LPCTSTR>(windowClass),
00451                                 name,
00452                                 WS_POPUP,
00453                                 0, 0, 1, 1,
00454                                 NULL, NULL,
00455                                 CMSWindowsScreen::getInstance(),
00456                                 NULL);
00457     if (window == NULL) {
00458         LOG((CLOG_ERR "failed to create window: %d", GetLastError()));
00459         throw XScreenOpenFailure();
00460     }
00461     return window;
00462 }
00463 
00464 void
00465 CMSWindowsDesks::destroyWindow(HWND hwnd) const
00466 {
00467     if (hwnd != NULL) {
00468         DestroyWindow(hwnd);
00469     }
00470 }
00471 
00472 LRESULT CALLBACK
00473 CMSWindowsDesks::primaryDeskProc(
00474                 HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
00475 {
00476     return DefWindowProc(hwnd, msg, wParam, lParam);
00477 }
00478 
00479 LRESULT CALLBACK
00480 CMSWindowsDesks::secondaryDeskProc(
00481                 HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
00482 {
00483     // would like to detect any local user input and hide the hider
00484     // window but for now we just detect mouse motion.
00485     bool hide = false;
00486     switch (msg) {
00487     case WM_MOUSEMOVE:
00488         if (LOWORD(lParam) != 0 || HIWORD(lParam) != 0) {
00489             hide = true;
00490         }
00491         break;
00492     }
00493 
00494     if (hide && IsWindowVisible(hwnd)) {
00495         ReleaseCapture();
00496         SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0,
00497                             SWP_NOMOVE | SWP_NOSIZE |
00498                             SWP_NOACTIVATE | SWP_HIDEWINDOW);
00499     }
00500 
00501     return DefWindowProc(hwnd, msg, wParam, lParam);
00502 }
00503 
00504 void
00505 CMSWindowsDesks::deskMouseMove(SInt32 x, SInt32 y) const
00506 {
00507     // motion is simple (i.e. it's on the primary monitor) if there
00508     // is only one monitor.  it's also simple if we're not on the
00509     // windows 95 family since those platforms don't have a broken
00510     // mouse_event() function (see the comment below).
00511     bool simple = (!m_multimon || !m_is95Family);
00512     if (!simple) {
00513         // also simple if motion is within the primary monitor
00514         simple = (x >= 0 && x < GetSystemMetrics(SM_CXSCREEN) &&
00515                   y >= 0 && y < GetSystemMetrics(SM_CYSCREEN));
00516     }
00517 
00518     // move the mouse directly to target position if motion is simple
00519     if (simple) {
00520         // when using absolute positioning with mouse_event(),
00521         // the normalized device coordinates range over only
00522         // the primary screen.
00523         SInt32 w = GetSystemMetrics(SM_CXSCREEN);
00524         SInt32 h = GetSystemMetrics(SM_CYSCREEN);
00525         mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
00526                                 (DWORD)((65535.0f * x) / (w - 1) + 0.5f),
00527                                 (DWORD)((65535.0f * y) / (h - 1) + 0.5f),
00528                                 0, 0);
00529     }
00530 
00531     // windows 98 and Me are broken.  you cannot set the absolute
00532     // position of the mouse except on the primary monitor but you
00533     // can do relative moves onto any monitor.  this is, in microsoft's
00534     // words, "by design."  apparently the designers of windows 2000
00535     // we're a little less lazy and did it right.
00536     //
00537     // microsoft recommends in Q193003 to absolute position the cursor
00538     // somewhere on the primary monitor then relative move to the
00539     // desired location.  this doesn't work for us because when the
00540     // user drags a scrollbar, a window, etc. it causes the dragged
00541     // item to jump back and forth between the position on the primary
00542     // monitor and the desired position.  while it always ends up in
00543     // the right place, the effect is disconcerting.
00544     //
00545     // instead we'll get the cursor's current position and do just a
00546     // relative move from there to the desired position.
00547     else {
00548         POINT pos;
00549         GetCursorPos(&pos);
00550         deskMouseRelativeMove(x - pos.x, y - pos.y);
00551     }
00552 }
00553 
00554 void
00555 CMSWindowsDesks::deskMouseRelativeMove(SInt32 dx, SInt32 dy) const
00556 {
00557     // relative moves are subject to cursor acceleration which we don't
00558     // want.so we disable acceleration, do the relative move, then
00559     // restore acceleration.  there's a slight chance we'll end up in
00560     // the wrong place if the user moves the cursor using this system's
00561     // mouse while simultaneously moving the mouse on the server
00562     // system.  that defeats the purpose of synergy so we'll assume
00563     // that won't happen.  even if it does, the next mouse move will
00564     // correct the position.
00565 
00566     // save mouse speed & acceleration
00567     int oldSpeed[4];
00568     bool accelChanged =
00569                 SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed, 0) &&
00570                 SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0);
00571 
00572     // use 1:1 motion
00573     if (accelChanged) {
00574         int newSpeed[4] = { 0, 0, 0, 1 };
00575         accelChanged =
00576                 SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) ||
00577                 SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0);
00578     }
00579 
00580     // move relative to mouse position
00581     mouse_event(MOUSEEVENTF_MOVE, dx, dy, 0, 0);
00582 
00583     // restore mouse speed & acceleration
00584     if (accelChanged) {
00585         SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0);
00586         SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0);
00587     }
00588 }
00589 
00590 void
00591 CMSWindowsDesks::deskEnter(CDesk* desk)
00592 {
00593     if (!m_isPrimary) {
00594         ReleaseCapture();
00595     }
00596     ShowCursor(TRUE);
00597     SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0,
00598                             SWP_NOMOVE | SWP_NOSIZE |
00599                             SWP_NOACTIVATE | SWP_HIDEWINDOW);
00600 
00601     // restore the foreground window
00602     // XXX -- this raises the window to the top of the Z-order.  we
00603     // want it to stay wherever it was to properly support X-mouse
00604     // (mouse over activation) but i've no idea how to do that.
00605     // the obvious workaround of using SetWindowPos() to move it back
00606     // after being raised doesn't work.
00607     DWORD thisThread =
00608         GetWindowThreadProcessId(desk->m_window, NULL);
00609     DWORD thatThread =
00610         GetWindowThreadProcessId(desk->m_foregroundWindow, NULL);
00611     AttachThreadInput(thatThread, thisThread, TRUE);
00612     SetForegroundWindow(desk->m_foregroundWindow);
00613     AttachThreadInput(thatThread, thisThread, FALSE);
00614     EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE);
00615     desk->m_foregroundWindow = NULL;
00616 }
00617 
00618 void
00619 CMSWindowsDesks::deskLeave(CDesk* desk, HKL keyLayout)
00620 {
00621     ShowCursor(FALSE);
00622     if (m_isPrimary) {
00623         // map a window to hide the cursor and to use whatever keyboard
00624         // layout we choose rather than the keyboard layout of the last
00625         // active window.
00626         int x, y, w, h;
00627         if (desk->m_lowLevel) {
00628             // with a low level hook the cursor will never budge so
00629             // just a 1x1 window is sufficient.
00630             x = m_xCenter;
00631             y = m_yCenter;
00632             w = 1;
00633             h = 1;
00634         }
00635         else {
00636             // with regular hooks the cursor will jitter as it's moved
00637             // by the user then back to the center by us.  to be sure
00638             // we never lose it, cover all the monitors with the window.
00639             x = m_x;
00640             y = m_y;
00641             w = m_w;
00642             h = m_h;
00643         }
00644         SetWindowPos(desk->m_window, HWND_TOPMOST, x, y, w, h,
00645                             SWP_NOACTIVATE | SWP_SHOWWINDOW);
00646 
00647         // if not using low-level hooks we have to also activate the
00648         // window to ensure we don't lose keyboard focus.
00649         // FIXME -- see if this can be avoided.  if so then always
00650         // disable the window (see handling of SYNERGY_MSG_SWITCH).
00651         if (!desk->m_lowLevel) {
00652             SetActiveWindow(desk->m_window);
00653         }
00654 
00655         // if using low-level hooks then disable the foreground window
00656         // so it can't mess up any of our keyboard events.  the console
00657         // program, for example, will cause characters to be reported as
00658         // unshifted, regardless of the shift key state.  interestingly
00659         // we do see the shift key go down and up.
00660         //
00661         // note that we must enable the window to activate it and we
00662         // need to disable the window on deskEnter.
00663         else {
00664             desk->m_foregroundWindow = getForegroundWindow();
00665             if (desk->m_foregroundWindow != NULL) {
00666                 EnableWindow(desk->m_window, TRUE);
00667                 SetActiveWindow(desk->m_window);
00668                 DWORD thisThread =
00669                     GetWindowThreadProcessId(desk->m_window, NULL);
00670                 DWORD thatThread =
00671                     GetWindowThreadProcessId(desk->m_foregroundWindow, NULL);
00672                 AttachThreadInput(thatThread, thisThread, TRUE);
00673                 SetForegroundWindow(desk->m_window);
00674                 AttachThreadInput(thatThread, thisThread, FALSE);
00675             }
00676         }
00677 
00678         // switch to requested keyboard layout
00679         ActivateKeyboardLayout(keyLayout, 0);
00680     }
00681     else {
00682         // move hider window under the cursor center, raise, and show it
00683         SetWindowPos(desk->m_window, HWND_TOPMOST,
00684                             m_xCenter, m_yCenter, 1, 1,
00685                             SWP_NOACTIVATE | SWP_SHOWWINDOW);
00686 
00687         // watch for mouse motion.  if we see any then we hide the
00688         // hider window so the user can use the physically attached
00689         // mouse if desired.  we'd rather not capture the mouse but
00690         // we aren't notified when the mouse leaves our window.
00691         SetCapture(desk->m_window);
00692 
00693         // warp the mouse to the cursor center
00694         deskMouseMove(m_xCenter, m_yCenter);
00695     }
00696 }
00697 
00698 void
00699 CMSWindowsDesks::deskThread(void* vdesk)
00700 {
00701     MSG msg;
00702 
00703     // use given desktop for this thread
00704     CDesk* desk              = reinterpret_cast<CDesk*>(vdesk);
00705     desk->m_threadID         = GetCurrentThreadId();
00706     desk->m_window           = NULL;
00707     desk->m_foregroundWindow = NULL;
00708     if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) {
00709         // create a message queue
00710         PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE);
00711 
00712         // create a window.  we use this window to hide the cursor.
00713         try {
00714             desk->m_window = createWindow(m_deskClass, "SynergyDesk");
00715             LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window));
00716         }
00717         catch (...) {
00718             // ignore
00719             LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str()));
00720         }
00721     }
00722 
00723     // tell main thread that we're ready
00724     {
00725         CLock lock(&m_mutex);
00726         m_deskReady = true;
00727         m_deskReady.broadcast();
00728     }
00729 
00730     while (GetMessage(&msg, NULL, 0, 0)) {
00731         switch (msg.message) {
00732         default:
00733             TranslateMessage(&msg);
00734             DispatchMessage(&msg);
00735             continue;
00736 
00737         case SYNERGY_MSG_SWITCH:
00738             if (m_isPrimary) {
00739                 m_uninstall();
00740                 if (m_screensaverNotify) {
00741                     m_uninstallScreensaver();
00742                     m_installScreensaver();
00743                 }
00744                 switch (m_install()) {
00745                 case kHOOK_FAILED:
00746                     // we won't work on this desk
00747                     desk->m_lowLevel = false;
00748                     break;
00749 
00750                 case kHOOK_OKAY:
00751                     desk->m_lowLevel = false;
00752                     break;
00753 
00754                 case kHOOK_OKAY_LL:
00755                     desk->m_lowLevel = true;
00756                     break;
00757                 }
00758 
00759                 // a window on the primary screen with low-level hooks
00760                 // should never activate.
00761                 EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE);
00762             }
00763             break;
00764 
00765         case SYNERGY_MSG_ENTER:
00766             m_isOnScreen = true;
00767             deskEnter(desk);
00768             break;
00769 
00770         case SYNERGY_MSG_LEAVE:
00771             m_isOnScreen = false;
00772             m_keyLayout  = (HKL)msg.wParam;
00773             deskLeave(desk, m_keyLayout);
00774             break;
00775 
00776         case SYNERGY_MSG_FAKE_KEY:
00777             keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), msg.wParam, 0);
00778             break;
00779 
00780         case SYNERGY_MSG_FAKE_BUTTON:
00781             if (msg.wParam != 0) {
00782                 mouse_event(msg.wParam, 0, 0, msg.lParam, 0);
00783             }
00784             break;
00785 
00786         case SYNERGY_MSG_FAKE_MOVE:
00787             deskMouseMove(static_cast<SInt32>(msg.wParam),
00788                             static_cast<SInt32>(msg.lParam));
00789             break;
00790 
00791         case SYNERGY_MSG_FAKE_REL_MOVE:
00792             deskMouseRelativeMove(static_cast<SInt32>(msg.wParam),
00793                             static_cast<SInt32>(msg.lParam));
00794             break;
00795 
00796         case SYNERGY_MSG_FAKE_WHEEL:
00797             // XXX -- add support for x-axis scrolling
00798             if (msg.lParam != 0) {
00799                 mouse_event(MOUSEEVENTF_WHEEL, 0, 0, msg.lParam, 0);
00800             }
00801             break;
00802 
00803         case SYNERGY_MSG_CURSOR_POS: {
00804             POINT* pos = reinterpret_cast<POINT*>(msg.wParam);
00805             if (!GetCursorPos(pos)) {
00806                 pos->x = m_xCenter;
00807                 pos->y = m_yCenter;
00808             }
00809             break;
00810         }
00811 
00812         case SYNERGY_MSG_SYNC_KEYS:
00813             m_updateKeys->run();
00814             break;
00815 
00816         case SYNERGY_MSG_SCREENSAVER:
00817             if (msg.wParam != 0) {
00818                 m_installScreensaver();
00819             }
00820             else {
00821                 m_uninstallScreensaver();
00822             }
00823             break;
00824 
00825         case SYNERGY_MSG_FAKE_INPUT:
00826             keybd_event(SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY,
00827                                 SYNERGY_HOOK_FAKE_INPUT_SCANCODE,
00828                                 msg.wParam ? 0 : KEYEVENTF_KEYUP, 0);
00829             break;
00830         }
00831 
00832         // notify that message was processed
00833         CLock lock(&m_mutex);
00834         m_deskReady = true;
00835         m_deskReady.broadcast();
00836     }
00837 
00838     // clean up
00839     deskEnter(desk);
00840     if (desk->m_window != NULL) {
00841         DestroyWindow(desk->m_window);
00842     }
00843     if (desk->m_desk != NULL) {
00844         closeDesktop(desk->m_desk);
00845     }
00846 }
00847 
00848 CMSWindowsDesks::CDesk*
00849 CMSWindowsDesks::addDesk(const CString& name, HDESK hdesk)
00850 {
00851     CDesk* desk      = new CDesk;
00852     desk->m_name     = name;
00853     desk->m_desk     = hdesk;
00854     desk->m_targetID = GetCurrentThreadId();
00855     desk->m_thread   = new CThread(new TMethodJob<CMSWindowsDesks>(
00856                         this, &CMSWindowsDesks::deskThread, desk));
00857     waitForDesk();
00858     m_desks.insert(std::make_pair(name, desk));
00859     return desk;
00860 }
00861 
00862 void
00863 CMSWindowsDesks::removeDesks()
00864 {
00865     for (CDesks::iterator index = m_desks.begin();
00866                             index != m_desks.end(); ++index) {
00867         CDesk* desk = index->second;
00868         PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0);
00869         desk->m_thread->wait();
00870         delete desk->m_thread;
00871         delete desk;
00872     }
00873     m_desks.clear();
00874     m_activeDesk     = NULL;
00875     m_activeDeskName = "";
00876 }
00877 
00878 void
00879 CMSWindowsDesks::checkDesk()
00880 {
00881     // get current desktop.  if we already know about it then return.
00882     CDesk* desk;
00883     HDESK hdesk  = openInputDesktop();
00884     CString name = getDesktopName(hdesk);
00885     CDesks::const_iterator index = m_desks.find(name);
00886     if (index == m_desks.end()) {
00887         desk = addDesk(name, hdesk);
00888         // hold on to hdesk until thread exits so the desk can't
00889         // be removed by the system
00890     }
00891     else {
00892         closeDesktop(hdesk);
00893         desk = index->second;
00894     }
00895 
00896     // if active desktop changed then tell the old and new desk threads
00897     // about the change.  don't switch desktops when the screensaver is
00898     // active becaue we'd most likely switch to the screensaver desktop
00899     // which would have the side effect of forcing the screensaver to
00900     // stop.
00901     if (name != m_activeDeskName && !m_screensaver->isActive()) {
00902         // show cursor on previous desk
00903         bool wasOnScreen = m_isOnScreen;
00904         if (!wasOnScreen) {
00905             sendMessage(SYNERGY_MSG_ENTER, 0, 0);
00906         }
00907 
00908         // check for desk accessibility change.  we don't get events
00909         // from an inaccessible desktop so when we switch from an
00910         // inaccessible desktop to an accessible one we have to
00911         // update the keyboard state.
00912         LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str()));
00913         bool syncKeys = false;
00914         bool isAccessible = isDeskAccessible(desk);
00915         if (isDeskAccessible(m_activeDesk) != isAccessible) {
00916             if (isAccessible) {
00917                 LOG((CLOG_DEBUG "desktop is now accessible"));
00918                 syncKeys = true;
00919             }
00920             else {
00921                 LOG((CLOG_DEBUG "desktop is now inaccessible"));
00922             }
00923         }
00924 
00925         // switch desk
00926         m_activeDesk     = desk;
00927         m_activeDeskName = name;
00928         sendMessage(SYNERGY_MSG_SWITCH, 0, 0);
00929 
00930         // hide cursor on new desk
00931         if (!wasOnScreen) {
00932             sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0);
00933         }
00934 
00935         // update keys if necessary
00936         if (syncKeys) {
00937             updateKeys();
00938         }
00939     }
00940     else if (name != m_activeDeskName) {
00941         // screen saver might have started
00942         PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, TRUE, 0);
00943     }
00944 }
00945 
00946 bool
00947 CMSWindowsDesks::isDeskAccessible(const CDesk* desk) const
00948 {
00949     return (desk != NULL && desk->m_desk != NULL);
00950 }
00951 
00952 void
00953 CMSWindowsDesks::waitForDesk() const
00954 {
00955     CMSWindowsDesks* self = const_cast<CMSWindowsDesks*>(this);
00956 
00957     CLock lock(&m_mutex);
00958     while (!(bool)m_deskReady) {
00959         m_deskReady.wait();
00960     }
00961     self->m_deskReady = false;
00962 }
00963 
00964 void
00965 CMSWindowsDesks::handleCheckDesk(const CEvent&, void*)
00966 {
00967     checkDesk();
00968 
00969     // also check if screen saver is running if on a modern OS and
00970     // this is the primary screen.
00971     if (m_isPrimary && m_isModernFamily) {
00972         BOOL running;
00973         SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, FALSE);
00974         PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, running, 0);
00975     }
00976 }
00977 
00978 HDESK
00979 CMSWindowsDesks::openInputDesktop()
00980 {
00981     if (m_is95Family) {
00982         // there's only one desktop on windows 95 et al.
00983         return GetThreadDesktop(GetCurrentThreadId());
00984     }
00985     else {
00986         return OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, TRUE,
00987                                 DESKTOP_CREATEWINDOW |
00988                                     DESKTOP_HOOKCONTROL |
00989                                     GENERIC_WRITE);
00990     }
00991 }
00992 
00993 void
00994 CMSWindowsDesks::closeDesktop(HDESK desk)
00995 {
00996     // on 95/98/me we don't need to close the desktop returned by
00997     // openInputDesktop().
00998     if (desk != NULL && !m_is95Family) {
00999         CloseDesktop(desk);
01000     }
01001 }
01002 
01003 CString
01004 CMSWindowsDesks::getDesktopName(HDESK desk)
01005 {
01006     if (desk == NULL) {
01007         return CString();
01008     }
01009     else if (m_is95Family) {
01010         return "desktop";
01011     }
01012     else {
01013         DWORD size;
01014         GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size);
01015         TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR));
01016         GetUserObjectInformation(desk, UOI_NAME, name, size, &size);
01017         CString result(name);
01018         return result;
01019     }
01020 }
01021 
01022 HWND
01023 CMSWindowsDesks::getForegroundWindow() const
01024 {
01025     // Ideally we'd return NULL as much as possible, only returning
01026     // the actual foreground window when we know it's going to mess
01027     // up our keyboard input.  For now we'll just let the user
01028     // decide.
01029     if (m_leaveForegroundOption) {
01030         return NULL;
01031     }
01032     return GetForegroundWindow();
01033 }

Generated on Fri Nov 6 00:21:14 2009 for synergy-plus by  doxygen 1.3.9.1