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