1 /*
2  * synergy -- mouse and keyboard sharing utility
3  * Copyright (C) 2012-2016 Symless Ltd.
4  * Copyright (C) 2004 Chris Schoeneman
5  *
6  * This package is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * found in the file LICENSE that should have accompanied this file.
9  *
10  * This package is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "platform/MSWindowsDesks.h"
20 
21 #include "platform/synwinhk.h"
22 #include "platform/MSWindowsScreen.h"
23 #include "synergy/IScreenSaver.h"
24 #include "synergy/XScreen.h"
25 #include "mt/Lock.h"
26 #include "mt/Thread.h"
27 #include "arch/win32/ArchMiscWindows.h"
28 #include "base/Log.h"
29 #include "base/IEventQueue.h"
30 #include "base/IJob.h"
31 #include "base/TMethodEventJob.h"
32 #include "base/TMethodJob.h"
33 #include "base/IEventQueue.h"
34 
35 #include <malloc.h>
36 
37 // these are only defined when WINVER >= 0x0500
38 #if !defined(SPI_GETMOUSESPEED)
39 #define SPI_GETMOUSESPEED 112
40 #endif
41 #if !defined(SPI_SETMOUSESPEED)
42 #define SPI_SETMOUSESPEED 113
43 #endif
44 #if !defined(SPI_GETSCREENSAVERRUNNING)
45 #define SPI_GETSCREENSAVERRUNNING 114
46 #endif
47 
48 // X button stuff
49 #if !defined(WM_XBUTTONDOWN)
50 #define WM_XBUTTONDOWN        0x020B
51 #define WM_XBUTTONUP        0x020C
52 #define WM_XBUTTONDBLCLK    0x020D
53 #define WM_NCXBUTTONDOWN    0x00AB
54 #define WM_NCXBUTTONUP        0x00AC
55 #define WM_NCXBUTTONDBLCLK    0x00AD
56 #define MOUSEEVENTF_XDOWN    0x0080
57 #define MOUSEEVENTF_XUP        0x0100
58 #define XBUTTON1            0x0001
59 #define XBUTTON2            0x0002
60 #endif
61 #if !defined(VK_XBUTTON1)
62 #define VK_XBUTTON1            0x05
63 #define VK_XBUTTON2            0x06
64 #endif
65 
66 // <unused>; <unused>
67 #define SYNERGY_MSG_SWITCH            SYNERGY_HOOK_LAST_MSG + 1
68 // <unused>; <unused>
69 #define SYNERGY_MSG_ENTER            SYNERGY_HOOK_LAST_MSG + 2
70 // <unused>; <unused>
71 #define SYNERGY_MSG_LEAVE            SYNERGY_HOOK_LAST_MSG + 3
72 // wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code
73 #define SYNERGY_MSG_FAKE_KEY        SYNERGY_HOOK_LAST_MSG + 4
74  // flags, XBUTTON id
75 #define SYNERGY_MSG_FAKE_BUTTON        SYNERGY_HOOK_LAST_MSG + 5
76 // x; y
77 #define SYNERGY_MSG_FAKE_MOVE        SYNERGY_HOOK_LAST_MSG + 6
78 // xDelta; yDelta
79 #define SYNERGY_MSG_FAKE_WHEEL        SYNERGY_HOOK_LAST_MSG + 7
80 // POINT*; <unused>
81 #define SYNERGY_MSG_CURSOR_POS        SYNERGY_HOOK_LAST_MSG + 8
82 // IKeyState*; <unused>
83 #define SYNERGY_MSG_SYNC_KEYS        SYNERGY_HOOK_LAST_MSG + 9
84 // install; <unused>
85 #define SYNERGY_MSG_SCREENSAVER        SYNERGY_HOOK_LAST_MSG + 10
86 // dx; dy
87 #define SYNERGY_MSG_FAKE_REL_MOVE    SYNERGY_HOOK_LAST_MSG + 11
88 // enable; <unused>
89 #define SYNERGY_MSG_FAKE_INPUT        SYNERGY_HOOK_LAST_MSG + 12
90 
91 
92 static void
send_keyboard_input(WORD wVk,WORD wScan,DWORD dwFlags)93 send_keyboard_input(WORD wVk, WORD wScan, DWORD dwFlags)
94 {
95 	INPUT inp;
96 	inp.type = INPUT_KEYBOARD;
97 	inp.ki.wVk = (dwFlags & KEYEVENTF_UNICODE) ? 0 : wVk; // 1..254 inclusive otherwise
98 	inp.ki.wScan = wScan;
99 	inp.ki.dwFlags = dwFlags & 0xF;
100 	inp.ki.time = 0;
101 	inp.ki.dwExtraInfo = 0;
102 	SendInput(1, &inp, sizeof(inp));
103 }
104 
105 static void
send_mouse_input(DWORD dwFlags,DWORD dx,DWORD dy,DWORD dwData)106 send_mouse_input(DWORD dwFlags, DWORD dx, DWORD dy, DWORD dwData)
107 {
108 	INPUT inp;
109 	inp.type = INPUT_MOUSE;
110 	inp.mi.dwFlags = dwFlags;
111 	inp.mi.dx = dx;
112 	inp.mi.dy = dy;
113 	inp.mi.mouseData = dwData;
114 	inp.mi.time = 0;
115 	inp.mi.dwExtraInfo = 0;
116 	SendInput(1, &inp, sizeof(inp));
117 }
118 
119 //
120 // MSWindowsDesks
121 //
122 
MSWindowsDesks(bool isPrimary,bool noHooks,const IScreenSaver * screensaver,IEventQueue * events,IJob * updateKeys,bool stopOnDeskSwitch)123 MSWindowsDesks::MSWindowsDesks(
124         bool isPrimary, bool noHooks,
125         const IScreenSaver* screensaver, IEventQueue* events,
126         IJob* updateKeys, bool stopOnDeskSwitch) :
127     m_isPrimary(isPrimary),
128     m_noHooks(noHooks),
129     m_isOnScreen(m_isPrimary),
130     m_x(0), m_y(0),
131     m_w(0), m_h(0),
132     m_xCenter(0), m_yCenter(0),
133     m_multimon(false),
134     m_timer(NULL),
135     m_screensaver(screensaver),
136     m_screensaverNotify(false),
137     m_activeDesk(NULL),
138     m_activeDeskName(),
139     m_mutex(),
140     m_deskReady(&m_mutex, false),
141     m_updateKeys(updateKeys),
142     m_events(events),
143     m_stopOnDeskSwitch(stopOnDeskSwitch)
144 {
145 
146     m_cursor    = createBlankCursor();
147     m_deskClass = createDeskWindowClass(m_isPrimary);
148     m_keyLayout = GetKeyboardLayout(GetCurrentThreadId());
149     resetOptions();
150 }
151 
~MSWindowsDesks()152 MSWindowsDesks::~MSWindowsDesks()
153 {
154     disable();
155     destroyClass(m_deskClass);
156     destroyCursor(m_cursor);
157     delete m_updateKeys;
158 }
159 
160 void
enable()161 MSWindowsDesks::enable()
162 {
163     m_threadID = GetCurrentThreadId();
164 
165     // set the active desk and (re)install the hooks
166     checkDesk();
167 
168     // install the desk timer.  this timer periodically checks
169     // which desk is active and reinstalls the hooks as necessary.
170     // we wouldn't need this if windows notified us of a desktop
171     // change but as far as i can tell it doesn't.
172     m_timer = m_events->newTimer(0.2, NULL);
173     m_events->adoptHandler(Event::kTimer, m_timer,
174                             new TMethodEventJob<MSWindowsDesks>(
175                                 this, &MSWindowsDesks::handleCheckDesk));
176 
177     updateKeys();
178 }
179 
180 void
disable()181 MSWindowsDesks::disable()
182 {
183     // remove timer
184     if (m_timer != NULL) {
185         m_events->removeHandler(Event::kTimer, m_timer);
186         m_events->deleteTimer(m_timer);
187         m_timer = NULL;
188     }
189 
190     // destroy desks
191     removeDesks();
192 
193     m_isOnScreen = m_isPrimary;
194 }
195 
196 void
enter()197 MSWindowsDesks::enter()
198 {
199     sendMessage(SYNERGY_MSG_ENTER, 0, 0);
200 }
201 
202 void
leave(HKL keyLayout)203 MSWindowsDesks::leave(HKL keyLayout)
204 {
205     sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)keyLayout, 0);
206 }
207 
208 void
resetOptions()209 MSWindowsDesks::resetOptions()
210 {
211     m_leaveForegroundOption = false;
212 }
213 
214 void
setOptions(const OptionsList & options)215 MSWindowsDesks::setOptions(const OptionsList& options)
216 {
217     for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) {
218         if (options[i] == kOptionWin32KeepForeground) {
219             m_leaveForegroundOption = (options[i + 1] != 0);
220             LOG((CLOG_DEBUG1 "%s the foreground window", m_leaveForegroundOption ? "don\'t grab" : "grab"));
221         }
222     }
223 }
224 
225 void
updateKeys()226 MSWindowsDesks::updateKeys()
227 {
228     sendMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0);
229 }
230 
231 void
setShape(SInt32 x,SInt32 y,SInt32 width,SInt32 height,SInt32 xCenter,SInt32 yCenter,bool isMultimon)232 MSWindowsDesks::setShape(SInt32 x, SInt32 y,
233                 SInt32 width, SInt32 height,
234                 SInt32 xCenter, SInt32 yCenter, bool isMultimon)
235 {
236     m_x        = x;
237     m_y        = y;
238     m_w        = width;
239     m_h        = height;
240     m_xCenter  = xCenter;
241     m_yCenter  = yCenter;
242     m_multimon = isMultimon;
243 }
244 
245 void
installScreensaverHooks(bool install)246 MSWindowsDesks::installScreensaverHooks(bool install)
247 {
248     if (m_isPrimary && m_screensaverNotify != install) {
249         m_screensaverNotify = install;
250         sendMessage(SYNERGY_MSG_SCREENSAVER, install, 0);
251     }
252 }
253 
254 void
fakeInputBegin()255 MSWindowsDesks::fakeInputBegin()
256 {
257     sendMessage(SYNERGY_MSG_FAKE_INPUT, 1, 0);
258 }
259 
260 void
fakeInputEnd()261 MSWindowsDesks::fakeInputEnd()
262 {
263     sendMessage(SYNERGY_MSG_FAKE_INPUT, 0, 0);
264 }
265 
266 void
getCursorPos(SInt32 & x,SInt32 & y) const267 MSWindowsDesks::getCursorPos(SInt32& x, SInt32& y) const
268 {
269     POINT pos;
270     sendMessage(SYNERGY_MSG_CURSOR_POS, reinterpret_cast<WPARAM>(&pos), 0);
271     x = pos.x;
272     y = pos.y;
273 }
274 
275 void
fakeKeyEvent(WORD virtualKey,WORD scanCode,DWORD flags,bool) const276 MSWindowsDesks::fakeKeyEvent(WORD virtualKey, WORD scanCode, DWORD flags, bool /*isAutoRepeat*/) const
277 {
278     sendMessage(SYNERGY_MSG_FAKE_KEY, flags, MAKELPARAM(scanCode, virtualKey));
279 }
280 
281 void
fakeMouseButton(ButtonID button,bool press)282 MSWindowsDesks::fakeMouseButton(ButtonID button, bool press)
283 {
284     // the system will swap the meaning of left/right for us if
285     // the user has configured a left-handed mouse but we don't
286     // want it to swap since we want the handedness of the
287     // server's mouse.  so pre-swap for a left-handed mouse.
288     if (GetSystemMetrics(SM_SWAPBUTTON)) {
289         switch (button) {
290         case kButtonLeft:
291             button = kButtonRight;
292             break;
293 
294         case kButtonRight:
295             button = kButtonLeft;
296             break;
297         }
298     }
299 
300     // map button id to button flag and button data
301     DWORD data = 0;
302     DWORD flags;
303     switch (button) {
304     case kButtonLeft:
305         flags = press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
306         break;
307 
308     case kButtonMiddle:
309         flags = press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
310         break;
311 
312     case kButtonRight:
313         flags = press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
314         break;
315 
316     case kButtonExtra0 + 0:
317         data = XBUTTON1;
318         flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
319         break;
320 
321     case kButtonExtra0 + 1:
322         data = XBUTTON2;
323         flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
324         break;
325 
326     default:
327         return;
328     }
329 
330     // do it
331     sendMessage(SYNERGY_MSG_FAKE_BUTTON, flags, data);
332 }
333 
334 void
fakeMouseMove(SInt32 x,SInt32 y) const335 MSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const
336 {
337     sendMessage(SYNERGY_MSG_FAKE_MOVE,
338                             static_cast<WPARAM>(x),
339                             static_cast<LPARAM>(y));
340 }
341 
342 void
fakeMouseRelativeMove(SInt32 dx,SInt32 dy) const343 MSWindowsDesks::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
344 {
345     sendMessage(SYNERGY_MSG_FAKE_REL_MOVE,
346                             static_cast<WPARAM>(dx),
347                             static_cast<LPARAM>(dy));
348 }
349 
350 void
fakeMouseWheel(SInt32 xDelta,SInt32 yDelta) const351 MSWindowsDesks::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
352 {
353     sendMessage(SYNERGY_MSG_FAKE_WHEEL, xDelta, yDelta);
354 }
355 
356 void
sendMessage(UINT msg,WPARAM wParam,LPARAM lParam) const357 MSWindowsDesks::sendMessage(UINT msg, WPARAM wParam, LPARAM lParam) const
358 {
359     if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) {
360         PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam);
361         waitForDesk();
362     }
363 }
364 
365 HCURSOR
createBlankCursor() const366 MSWindowsDesks::createBlankCursor() const
367 {
368     // create a transparent cursor
369     int cw = GetSystemMetrics(SM_CXCURSOR);
370     int ch = GetSystemMetrics(SM_CYCURSOR);
371     UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)];
372     UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)];
373     memset(cursorAND, 0xff, ch * ((cw + 31) >> 2));
374     memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2));
375     HCURSOR c = CreateCursor(MSWindowsScreen::getWindowInstance(),
376                             0, 0, cw, ch, cursorAND, cursorXOR);
377     delete[] cursorXOR;
378     delete[] cursorAND;
379     return c;
380 }
381 
382 void
destroyCursor(HCURSOR cursor) const383 MSWindowsDesks::destroyCursor(HCURSOR cursor) const
384 {
385     if (cursor != NULL) {
386         DestroyCursor(cursor);
387     }
388 }
389 
390 ATOM
createDeskWindowClass(bool isPrimary) const391 MSWindowsDesks::createDeskWindowClass(bool isPrimary) const
392 {
393     WNDCLASSEX classInfo;
394     classInfo.cbSize        = sizeof(classInfo);
395     classInfo.style         = CS_DBLCLKS | CS_NOCLOSE;
396     classInfo.lpfnWndProc   = isPrimary ?
397                                 &MSWindowsDesks::primaryDeskProc :
398                                 &MSWindowsDesks::secondaryDeskProc;
399     classInfo.cbClsExtra    = 0;
400     classInfo.cbWndExtra    = 0;
401     classInfo.hInstance     = MSWindowsScreen::getWindowInstance();
402     classInfo.hIcon         = NULL;
403     classInfo.hCursor       = m_cursor;
404     classInfo.hbrBackground = NULL;
405     classInfo.lpszMenuName  = NULL;
406     classInfo.lpszClassName = "SynergyDesk";
407     classInfo.hIconSm       = NULL;
408     return RegisterClassEx(&classInfo);
409 }
410 
411 void
destroyClass(ATOM windowClass) const412 MSWindowsDesks::destroyClass(ATOM windowClass) const
413 {
414     if (windowClass != 0) {
415         UnregisterClass(MAKEINTATOM(windowClass),
416                             MSWindowsScreen::getWindowInstance());
417     }
418 }
419 
420 HWND
createWindow(ATOM windowClass,const char * name) const421 MSWindowsDesks::createWindow(ATOM windowClass, const char* name) const
422 {
423     HWND window = CreateWindowEx(WS_EX_TRANSPARENT |
424                                     WS_EX_TOOLWINDOW,
425                                 MAKEINTATOM(windowClass),
426                                 name,
427                                 WS_POPUP,
428                                 0, 0, 1, 1,
429                                 NULL, NULL,
430                                 MSWindowsScreen::getWindowInstance(),
431                                 NULL);
432     if (window == NULL) {
433         LOG((CLOG_ERR "failed to create window: %d", GetLastError()));
434         throw XScreenOpenFailure();
435     }
436     return window;
437 }
438 
439 void
destroyWindow(HWND hwnd) const440 MSWindowsDesks::destroyWindow(HWND hwnd) const
441 {
442     if (hwnd != NULL) {
443         DestroyWindow(hwnd);
444     }
445 }
446 
447 LRESULT CALLBACK
primaryDeskProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)448 MSWindowsDesks::primaryDeskProc(
449                 HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
450 {
451     return DefWindowProc(hwnd, msg, wParam, lParam);
452 }
453 
454 LRESULT CALLBACK
secondaryDeskProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)455 MSWindowsDesks::secondaryDeskProc(
456                 HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
457 {
458     // would like to detect any local user input and hide the hider
459     // window but for now we just detect mouse motion.
460     bool hide = false;
461     switch (msg) {
462     case WM_MOUSEMOVE:
463         if (LOWORD(lParam) != 0 || HIWORD(lParam) != 0) {
464             hide = true;
465         }
466         break;
467     }
468 
469     if (hide && IsWindowVisible(hwnd)) {
470         ReleaseCapture();
471         SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0,
472                             SWP_NOMOVE | SWP_NOSIZE |
473                             SWP_NOACTIVATE | SWP_HIDEWINDOW);
474     }
475 
476     return DefWindowProc(hwnd, msg, wParam, lParam);
477 }
478 
479 void
deskMouseMove(SInt32 x,SInt32 y) const480 MSWindowsDesks::deskMouseMove(SInt32 x, SInt32 y) const
481 {
482     // when using absolute positioning with mouse_event(),
483     // the normalized device coordinates range over only
484     // the primary screen.
485     SInt32 w = GetSystemMetrics(SM_CXSCREEN);
486     SInt32 h = GetSystemMetrics(SM_CYSCREEN);
487     send_mouse_input(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
488                             (DWORD)((65535.0f * x) / (w - 1) + 0.5f),
489                             (DWORD)((65535.0f * y) / (h - 1) + 0.5f),
490                             0);
491 }
492 
493 void
deskMouseRelativeMove(SInt32 dx,SInt32 dy) const494 MSWindowsDesks::deskMouseRelativeMove(SInt32 dx, SInt32 dy) const
495 {
496     // relative moves are subject to cursor acceleration which we don't
497     // want.so we disable acceleration, do the relative move, then
498     // restore acceleration.  there's a slight chance we'll end up in
499     // the wrong place if the user moves the cursor using this system's
500     // mouse while simultaneously moving the mouse on the server
501     // system.  that defeats the purpose of synergy so we'll assume
502     // that won't happen.  even if it does, the next mouse move will
503     // correct the position.
504 
505     // save mouse speed & acceleration
506     int oldSpeed[4];
507     bool accelChanged =
508                 SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed, 0) &&
509                 SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0);
510 
511     // use 1:1 motion
512     if (accelChanged) {
513         int newSpeed[4] = { 0, 0, 0, 1 };
514         accelChanged =
515                 SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) ||
516                 SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0);
517     }
518 
519     // move relative to mouse position
520     send_mouse_input(MOUSEEVENTF_MOVE, dx, dy, 0);
521 
522     // restore mouse speed & acceleration
523     if (accelChanged) {
524         SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0);
525         SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0);
526     }
527 }
528 
529 void
deskEnter(Desk * desk)530 MSWindowsDesks::deskEnter(Desk* desk)
531 {
532     if (!m_isPrimary) {
533         ReleaseCapture();
534     }
535     ShowCursor(TRUE);
536     SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0,
537                             SWP_NOMOVE | SWP_NOSIZE |
538                             SWP_NOACTIVATE | SWP_HIDEWINDOW);
539 
540     // restore the foreground window
541     // XXX -- this raises the window to the top of the Z-order.  we
542     // want it to stay wherever it was to properly support X-mouse
543     // (mouse over activation) but i've no idea how to do that.
544     // the obvious workaround of using SetWindowPos() to move it back
545     // after being raised doesn't work.
546     DWORD thisThread =
547         GetWindowThreadProcessId(desk->m_window, NULL);
548     DWORD thatThread =
549         GetWindowThreadProcessId(desk->m_foregroundWindow, NULL);
550     AttachThreadInput(thatThread, thisThread, TRUE);
551     SetForegroundWindow(desk->m_foregroundWindow);
552     AttachThreadInput(thatThread, thisThread, FALSE);
553     EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE);
554     desk->m_foregroundWindow = NULL;
555 }
556 
557 void
deskLeave(Desk * desk,HKL keyLayout)558 MSWindowsDesks::deskLeave(Desk* desk, HKL keyLayout)
559 {
560     ShowCursor(FALSE);
561     if (m_isPrimary) {
562         // map a window to hide the cursor and to use whatever keyboard
563         // layout we choose rather than the keyboard layout of the last
564         // active window.
565         int x, y, w, h;
566         if (desk->m_lowLevel) {
567             // with a low level hook the cursor will never budge so
568             // just a 1x1 window is sufficient.
569             x = m_xCenter;
570             y = m_yCenter;
571             w = 1;
572             h = 1;
573         }
574         else {
575             // with regular hooks the cursor will jitter as it's moved
576             // by the user then back to the center by us.  to be sure
577             // we never lose it, cover all the monitors with the window.
578             x = m_x;
579             y = m_y;
580             w = m_w;
581             h = m_h;
582         }
583         SetWindowPos(desk->m_window, HWND_TOP, x, y, w, h,
584                             SWP_NOACTIVATE | SWP_SHOWWINDOW);
585 
586         // if not using low-level hooks we have to also activate the
587         // window to ensure we don't lose keyboard focus.
588         // FIXME -- see if this can be avoided.  if so then always
589         // disable the window (see handling of SYNERGY_MSG_SWITCH).
590         if (!desk->m_lowLevel) {
591             SetActiveWindow(desk->m_window);
592         }
593 
594         // if using low-level hooks then disable the foreground window
595         // so it can't mess up any of our keyboard events.  the console
596         // program, for example, will cause characters to be reported as
597         // unshifted, regardless of the shift key state.  interestingly
598         // we do see the shift key go down and up.
599         //
600         // note that we must enable the window to activate it and we
601         // need to disable the window on deskEnter.
602         else {
603             desk->m_foregroundWindow = getForegroundWindow();
604             if (desk->m_foregroundWindow != NULL) {
605                 EnableWindow(desk->m_window, TRUE);
606                 SetActiveWindow(desk->m_window);
607                 DWORD thisThread =
608                     GetWindowThreadProcessId(desk->m_window, NULL);
609                 DWORD thatThread =
610                     GetWindowThreadProcessId(desk->m_foregroundWindow, NULL);
611                 AttachThreadInput(thatThread, thisThread, TRUE);
612                 SetForegroundWindow(desk->m_window);
613                 AttachThreadInput(thatThread, thisThread, FALSE);
614             }
615         }
616 
617         // switch to requested keyboard layout
618         ActivateKeyboardLayout(keyLayout, 0);
619     }
620     else {
621         // move hider window under the cursor center, raise, and show it
622         SetWindowPos(desk->m_window, HWND_TOP,
623                             m_xCenter, m_yCenter, 1, 1,
624                             SWP_NOACTIVATE | SWP_SHOWWINDOW);
625 
626         // watch for mouse motion.  if we see any then we hide the
627         // hider window so the user can use the physically attached
628         // mouse if desired.  we'd rather not capture the mouse but
629         // we aren't notified when the mouse leaves our window.
630         SetCapture(desk->m_window);
631 
632         // warp the mouse to the cursor center
633         LOG((CLOG_DEBUG2 "warping cursor to center: %+d,%+d", m_xCenter, m_yCenter));
634         deskMouseMove(m_xCenter, m_yCenter);
635     }
636 }
637 
638 void
deskThread(void * vdesk)639 MSWindowsDesks::deskThread(void* vdesk)
640 {
641     MSG msg;
642 
643     // use given desktop for this thread
644     Desk* desk              = static_cast<Desk*>(vdesk);
645     desk->m_threadID         = GetCurrentThreadId();
646     desk->m_window           = NULL;
647     desk->m_foregroundWindow = NULL;
648     if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) {
649         // create a message queue
650         PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE);
651 
652         // create a window.  we use this window to hide the cursor.
653         try {
654             desk->m_window = createWindow(m_deskClass, "SynergyDesk");
655             LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window));
656         }
657         catch (...) {
658             // ignore
659             LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str()));
660         }
661     }
662 
663     // tell main thread that we're ready
664     {
665         Lock lock(&m_mutex);
666         m_deskReady = true;
667         m_deskReady.broadcast();
668     }
669 
670     while (GetMessage(&msg, NULL, 0, 0)) {
671         switch (msg.message) {
672         default:
673             TranslateMessage(&msg);
674             DispatchMessage(&msg);
675             continue;
676 
677         case SYNERGY_MSG_SWITCH:
678             if (!m_noHooks) {
679                 MSWindowsHook::uninstall();
680                 if (m_screensaverNotify) {
681                     MSWindowsHook::uninstallScreenSaver();
682                     MSWindowsHook::installScreenSaver();
683                 }
684                 switch (MSWindowsHook::install()) {
685                 case kHOOK_FAILED:
686                     // we won't work on this desk
687                     desk->m_lowLevel = false;
688                     break;
689 
690                 case kHOOK_OKAY:
691                     desk->m_lowLevel = false;
692                     break;
693 
694                 case kHOOK_OKAY_LL:
695                     desk->m_lowLevel = true;
696                     break;
697                 }
698 
699                 // a window on the primary screen with low-level hooks
700                 // should never activate.
701                 if (desk->m_window)
702                     EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE);
703             }
704             break;
705 
706         case SYNERGY_MSG_ENTER:
707             m_isOnScreen = true;
708             deskEnter(desk);
709             break;
710 
711         case SYNERGY_MSG_LEAVE:
712             m_isOnScreen = false;
713             m_keyLayout  = (HKL)msg.wParam;
714             deskLeave(desk, m_keyLayout);
715             break;
716 
717         case SYNERGY_MSG_FAKE_KEY:
718             // Note, this is intended to be HI/LOWORD and not HI/LOBYTE
719             send_keyboard_input(
720                 HIWORD(msg.lParam),
721                 LOWORD(msg.lParam),
722                 (DWORD)msg.wParam);
723             break;
724 
725         case SYNERGY_MSG_FAKE_BUTTON:
726            if (msg.wParam != 0) {
727                 send_mouse_input((DWORD)msg.wParam, 0, 0, (DWORD)msg.lParam);
728             }
729             break;
730 
731         case SYNERGY_MSG_FAKE_MOVE:
732             deskMouseMove(static_cast<SInt32>(msg.wParam),
733                             static_cast<SInt32>(msg.lParam));
734             break;
735 
736         case SYNERGY_MSG_FAKE_REL_MOVE:
737             deskMouseRelativeMove(static_cast<SInt32>(msg.wParam),
738                             static_cast<SInt32>(msg.lParam));
739             break;
740 
741         case SYNERGY_MSG_FAKE_WHEEL:
742             // XXX -- add support for x-axis scrolling
743             if (msg.lParam != 0) {
744                 send_mouse_input(MOUSEEVENTF_WHEEL, 0, 0, (DWORD)msg.lParam);
745             }
746             break;
747 
748         case SYNERGY_MSG_CURSOR_POS: {
749             POINT* pos = reinterpret_cast<POINT*>(msg.wParam);
750             if (!GetCursorPos(pos)) {
751                 pos->x = m_xCenter;
752                 pos->y = m_yCenter;
753             }
754             break;
755         }
756 
757         case SYNERGY_MSG_SYNC_KEYS:
758             m_updateKeys->run();
759             break;
760 
761         case SYNERGY_MSG_SCREENSAVER:
762             if (!m_noHooks) {
763                 if (msg.wParam != 0) {
764                     MSWindowsHook::installScreenSaver();
765                 }
766                 else {
767                     MSWindowsHook::uninstallScreenSaver();
768                 }
769             }
770             break;
771 
772         case SYNERGY_MSG_FAKE_INPUT:
773             send_keyboard_input(
774                 SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY,
775                 SYNERGY_HOOK_FAKE_INPUT_SCANCODE,
776                 msg.wParam ? 0 : KEYEVENTF_KEYUP);
777             break;
778         }
779 
780         // notify that message was processed
781         Lock lock(&m_mutex);
782         m_deskReady = true;
783         m_deskReady.broadcast();
784     }
785 
786     // clean up
787     deskEnter(desk);
788     if (desk->m_window != NULL) {
789         DestroyWindow(desk->m_window);
790     }
791     if (desk->m_desk != NULL) {
792         closeDesktop(desk->m_desk);
793     }
794 }
795 
796 MSWindowsDesks::Desk*
addDesk(const String & name,HDESK hdesk)797 MSWindowsDesks::addDesk(const String& name, HDESK hdesk)
798 {
799     Desk* desk      = new Desk;
800     desk->m_name     = name;
801     desk->m_desk     = hdesk;
802     desk->m_targetID = GetCurrentThreadId();
803     desk->m_thread   = new Thread(new TMethodJob<MSWindowsDesks>(
804                         this, &MSWindowsDesks::deskThread, desk));
805     waitForDesk();
806     m_desks.insert(std::make_pair(name, desk));
807     return desk;
808 }
809 
810 void
removeDesks()811 MSWindowsDesks::removeDesks()
812 {
813     for (Desks::iterator index = m_desks.begin();
814                             index != m_desks.end(); ++index) {
815         Desk* desk = index->second;
816         PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0);
817         desk->m_thread->wait();
818         delete desk->m_thread;
819         delete desk;
820     }
821     m_desks.clear();
822     m_activeDesk     = NULL;
823     m_activeDeskName = "";
824 }
825 
826 void
checkDesk()827 MSWindowsDesks::checkDesk()
828 {
829     // get current desktop.  if we already know about it then return.
830     Desk* desk;
831     HDESK hdesk  = openInputDesktop();
832     String name = getDesktopName(hdesk);
833     Desks::const_iterator index = m_desks.find(name);
834     if (index == m_desks.end()) {
835         desk = addDesk(name, hdesk);
836         // hold on to hdesk until thread exits so the desk can't
837         // be removed by the system
838     }
839     else {
840         closeDesktop(hdesk);
841         desk = index->second;
842     }
843 
844     // if we are told to shut down on desk switch, and this is not the
845     // first switch, then shut down.
846     if (m_stopOnDeskSwitch && m_activeDesk != NULL && name != m_activeDeskName) {
847         LOG((CLOG_DEBUG "shutting down because of desk switch to \"%s\"", name.c_str()));
848         m_events->addEvent(Event(Event::kQuit));
849         return;
850     }
851 
852     // if active desktop changed then tell the old and new desk threads
853     // about the change.  don't switch desktops when the screensaver is
854     // active becaue we'd most likely switch to the screensaver desktop
855     // which would have the side effect of forcing the screensaver to
856     // stop.
857     if (name != m_activeDeskName && !m_screensaver->isActive()) {
858         // show cursor on previous desk
859         bool wasOnScreen = m_isOnScreen;
860         if (!wasOnScreen) {
861             sendMessage(SYNERGY_MSG_ENTER, 0, 0);
862         }
863 
864         // check for desk accessibility change.  we don't get events
865         // from an inaccessible desktop so when we switch from an
866         // inaccessible desktop to an accessible one we have to
867         // update the keyboard state.
868         LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str()));
869         bool syncKeys = false;
870         bool isAccessible = isDeskAccessible(desk);
871         if (isDeskAccessible(m_activeDesk) != isAccessible) {
872             if (isAccessible) {
873                 LOG((CLOG_DEBUG "desktop is now accessible"));
874                 syncKeys = true;
875             }
876             else {
877                 LOG((CLOG_DEBUG "desktop is now inaccessible"));
878             }
879         }
880 
881         // switch desk
882         m_activeDesk     = desk;
883         m_activeDeskName = name;
884         sendMessage(SYNERGY_MSG_SWITCH, 0, 0);
885 
886         // hide cursor on new desk
887         if (!wasOnScreen) {
888             sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0);
889         }
890 
891         // update keys if necessary
892         if (syncKeys) {
893             updateKeys();
894         }
895     }
896     else if (name != m_activeDeskName) {
897         // screen saver might have started
898         PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, TRUE, 0);
899     }
900 }
901 
902 bool
isDeskAccessible(const Desk * desk) const903 MSWindowsDesks::isDeskAccessible(const Desk* desk) const
904 {
905     return (desk != NULL && desk->m_desk != NULL);
906 }
907 
908 void
waitForDesk() const909 MSWindowsDesks::waitForDesk() const
910 {
911     MSWindowsDesks* self = const_cast<MSWindowsDesks*>(this);
912 
913     Lock lock(&m_mutex);
914     while (!(bool)m_deskReady) {
915         m_deskReady.wait();
916     }
917     self->m_deskReady = false;
918 }
919 
920 void
handleCheckDesk(const Event &,void *)921 MSWindowsDesks::handleCheckDesk(const Event&, void*)
922 {
923     checkDesk();
924 
925     // also check if screen saver is running if on a modern OS and
926     // this is the primary screen.
927     if (m_isPrimary) {
928         BOOL running;
929         SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, FALSE);
930         PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, running, 0);
931     }
932 }
933 
934 HDESK
openInputDesktop()935 MSWindowsDesks::openInputDesktop()
936 {
937     return OpenInputDesktop(
938         DF_ALLOWOTHERACCOUNTHOOK, TRUE,
939         DESKTOP_CREATEWINDOW | DESKTOP_HOOKCONTROL | GENERIC_WRITE);
940 }
941 
942 void
closeDesktop(HDESK desk)943 MSWindowsDesks::closeDesktop(HDESK desk)
944 {
945     if (desk != NULL) {
946         CloseDesktop(desk);
947     }
948 }
949 
950 String
getDesktopName(HDESK desk)951 MSWindowsDesks::getDesktopName(HDESK desk)
952 {
953     if (desk == NULL) {
954         return String();
955     }
956     else {
957         DWORD size;
958         GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size);
959         TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR));
960         GetUserObjectInformation(desk, UOI_NAME, name, size, &size);
961         String result(name);
962         return result;
963     }
964 }
965 
966 HWND
getForegroundWindow() const967 MSWindowsDesks::getForegroundWindow() const
968 {
969     // Ideally we'd return NULL as much as possible, only returning
970     // the actual foreground window when we know it's going to mess
971     // up our keyboard input.  For now we'll just let the user
972     // decide.
973     if (m_leaveForegroundOption) {
974         return NULL;
975     }
976     return GetForegroundWindow();
977 }
978