diff options
Diffstat (limited to 'src/lib/platform/MSWindowsDesks.cpp')
| -rw-r--r-- | src/lib/platform/MSWindowsDesks.cpp | 923 |
1 files changed, 923 insertions, 0 deletions
diff --git a/src/lib/platform/MSWindowsDesks.cpp b/src/lib/platform/MSWindowsDesks.cpp new file mode 100644 index 0000000..b43a218 --- /dev/null +++ b/src/lib/platform/MSWindowsDesks.cpp @@ -0,0 +1,923 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2018 Debauchee Open Source Group + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "platform/MSWindowsDesks.h" + +#include "platform/MSWindowsScreen.h" +#include "barrier/IScreenSaver.h" +#include "barrier/XScreen.h" +#include "mt/Lock.h" +#include "mt/Thread.h" +#include "arch/win32/ArchMiscWindows.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/IJob.h" +#include "base/TMethodEventJob.h" +#include "base/TMethodJob.h" +#include "base/IEventQueue.h" + +#include <malloc.h> + +// these are only defined when WINVER >= 0x0500 +#if !defined(SPI_GETMOUSESPEED) +#define SPI_GETMOUSESPEED 112 +#endif +#if !defined(SPI_SETMOUSESPEED) +#define SPI_SETMOUSESPEED 113 +#endif +#if !defined(SPI_GETSCREENSAVERRUNNING) +#define SPI_GETSCREENSAVERRUNNING 114 +#endif + +// X button stuff +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_NCXBUTTONDOWN 0x00AB +#define WM_NCXBUTTONUP 0x00AC +#define WM_NCXBUTTONDBLCLK 0x00AD +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// <unused>; <unused> +#define BARRIER_MSG_SWITCH BARRIER_HOOK_LAST_MSG + 1 +// <unused>; <unused> +#define BARRIER_MSG_ENTER BARRIER_HOOK_LAST_MSG + 2 +// <unused>; <unused> +#define BARRIER_MSG_LEAVE BARRIER_HOOK_LAST_MSG + 3 +// wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code +#define BARRIER_MSG_FAKE_KEY BARRIER_HOOK_LAST_MSG + 4 + // flags, XBUTTON id +#define BARRIER_MSG_FAKE_BUTTON BARRIER_HOOK_LAST_MSG + 5 +// x; y +#define BARRIER_MSG_FAKE_MOVE BARRIER_HOOK_LAST_MSG + 6 +// xDelta; yDelta +#define BARRIER_MSG_FAKE_WHEEL BARRIER_HOOK_LAST_MSG + 7 +// POINT*; <unused> +#define BARRIER_MSG_CURSOR_POS BARRIER_HOOK_LAST_MSG + 8 +// IKeyState*; <unused> +#define BARRIER_MSG_SYNC_KEYS BARRIER_HOOK_LAST_MSG + 9 +// install; <unused> +#define BARRIER_MSG_SCREENSAVER BARRIER_HOOK_LAST_MSG + 10 +// dx; dy +#define BARRIER_MSG_FAKE_REL_MOVE BARRIER_HOOK_LAST_MSG + 11 +// enable; <unused> +#define BARRIER_MSG_FAKE_INPUT BARRIER_HOOK_LAST_MSG + 12 + +// +// MSWindowsDesks +// + +MSWindowsDesks::MSWindowsDesks( + bool isPrimary, bool noHooks, + const IScreenSaver* screensaver, IEventQueue* events, + IJob* updateKeys, bool stopOnDeskSwitch) : + m_isPrimary(isPrimary), + m_noHooks(noHooks), + m_isOnScreen(m_isPrimary), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_multimon(false), + m_timer(NULL), + m_screensaver(screensaver), + m_screensaverNotify(false), + m_activeDesk(NULL), + m_activeDeskName(), + m_mutex(), + m_deskReady(&m_mutex, false), + m_updateKeys(updateKeys), + m_events(events), + m_stopOnDeskSwitch(stopOnDeskSwitch) +{ + m_cursor = createBlankCursor(); + m_deskClass = createDeskWindowClass(m_isPrimary); + m_keyLayout = GetKeyboardLayout(GetCurrentThreadId()); + resetOptions(); +} + +MSWindowsDesks::~MSWindowsDesks() +{ + disable(); + destroyClass(m_deskClass); + destroyCursor(m_cursor); + delete m_updateKeys; +} + +void +MSWindowsDesks::enable() +{ + m_threadID = GetCurrentThreadId(); + + // set the active desk and (re)install the hooks + checkDesk(); + + // install the desk timer. this timer periodically checks + // which desk is active and reinstalls the hooks as necessary. + // we wouldn't need this if windows notified us of a desktop + // change but as far as i can tell it doesn't. + m_timer = m_events->newTimer(0.2, NULL); + m_events->adoptHandler(Event::kTimer, m_timer, + new TMethodEventJob<MSWindowsDesks>( + this, &MSWindowsDesks::handleCheckDesk)); + + updateKeys(); +} + +void +MSWindowsDesks::disable() +{ + // remove timer + if (m_timer != NULL) { + m_events->removeHandler(Event::kTimer, m_timer); + m_events->deleteTimer(m_timer); + m_timer = NULL; + } + + // destroy desks + removeDesks(); + + m_isOnScreen = m_isPrimary; +} + +void +MSWindowsDesks::enter() +{ + sendMessage(BARRIER_MSG_ENTER, 0, 0); +} + +void +MSWindowsDesks::leave(HKL keyLayout) +{ + sendMessage(BARRIER_MSG_LEAVE, (WPARAM)keyLayout, 0); +} + +void +MSWindowsDesks::resetOptions() +{ + m_leaveForegroundOption = false; +} + +void +MSWindowsDesks::setOptions(const OptionsList& options) +{ + for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) { + if (options[i] == kOptionWin32KeepForeground) { + m_leaveForegroundOption = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "%s the foreground window", m_leaveForegroundOption ? "don\'t grab" : "grab")); + } + } +} + +void +MSWindowsDesks::updateKeys() +{ + sendMessage(BARRIER_MSG_SYNC_KEYS, 0, 0); +} + +void +MSWindowsDesks::setShape(SInt32 x, SInt32 y, + SInt32 width, SInt32 height, + SInt32 xCenter, SInt32 yCenter, bool isMultimon) +{ + m_x = x; + m_y = y; + m_w = width; + m_h = height; + m_xCenter = xCenter; + m_yCenter = yCenter; + m_multimon = isMultimon; +} + +void +MSWindowsDesks::installScreensaverHooks(bool install) +{ + if (m_isPrimary && m_screensaverNotify != install) { + m_screensaverNotify = install; + sendMessage(BARRIER_MSG_SCREENSAVER, install, 0); + } +} + +void +MSWindowsDesks::fakeInputBegin() +{ + sendMessage(BARRIER_MSG_FAKE_INPUT, 1, 0); +} + +void +MSWindowsDesks::fakeInputEnd() +{ + sendMessage(BARRIER_MSG_FAKE_INPUT, 0, 0); +} + +void +MSWindowsDesks::getCursorPos(SInt32& x, SInt32& y) const +{ + POINT pos; + sendMessage(BARRIER_MSG_CURSOR_POS, reinterpret_cast<WPARAM>(&pos), 0); + x = pos.x; + y = pos.y; +} + +void +MSWindowsDesks::fakeKeyEvent( + KeyButton button, UINT virtualKey, + bool press, bool /*isAutoRepeat*/) const +{ + // synthesize event + DWORD flags = 0; + if (((button & 0x100u) != 0)) { + flags |= KEYEVENTF_EXTENDEDKEY; + } + if (!press) { + flags |= KEYEVENTF_KEYUP; + } + sendMessage(BARRIER_MSG_FAKE_KEY, flags, + MAKEWORD(static_cast<BYTE>(button & 0xffu), + static_cast<BYTE>(virtualKey & 0xffu))); +} + +void +MSWindowsDesks::fakeMouseButton(ButtonID button, bool press) +{ + // the system will swap the meaning of left/right for us if + // the user has configured a left-handed mouse but we don't + // want it to swap since we want the handedness of the + // server's mouse. so pre-swap for a left-handed mouse. + if (GetSystemMetrics(SM_SWAPBUTTON)) { + switch (button) { + case kButtonLeft: + button = kButtonRight; + break; + + case kButtonRight: + button = kButtonLeft; + break; + } + } + + // map button id to button flag and button data + DWORD data = 0; + DWORD flags; + switch (button) { + case kButtonLeft: + flags = press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; + break; + + case kButtonMiddle: + flags = press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; + break; + + case kButtonRight: + flags = press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; + break; + + case kButtonExtra0 + 0: + data = XBUTTON1; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + case kButtonExtra0 + 1: + data = XBUTTON2; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + default: + return; + } + + // do it + sendMessage(BARRIER_MSG_FAKE_BUTTON, flags, data); +} + +void +MSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const +{ + sendMessage(BARRIER_MSG_FAKE_MOVE, + static_cast<WPARAM>(x), + static_cast<LPARAM>(y)); +} + +void +MSWindowsDesks::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + sendMessage(BARRIER_MSG_FAKE_REL_MOVE, + static_cast<WPARAM>(dx), + static_cast<LPARAM>(dy)); +} + +void +MSWindowsDesks::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + sendMessage(BARRIER_MSG_FAKE_WHEEL, xDelta, yDelta); +} + +void +MSWindowsDesks::sendMessage(UINT msg, WPARAM wParam, LPARAM lParam) const +{ + if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) { + PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam); + waitForDesk(); + } +} + +HCURSOR +MSWindowsDesks::createBlankCursor() const +{ + // create a transparent cursor + int cw = GetSystemMetrics(SM_CXCURSOR); + int ch = GetSystemMetrics(SM_CYCURSOR); + UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)]; + UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)]; + memset(cursorAND, 0xff, ch * ((cw + 31) >> 2)); + memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); + HCURSOR c = CreateCursor(MSWindowsScreen::getWindowInstance(), + 0, 0, cw, ch, cursorAND, cursorXOR); + delete[] cursorXOR; + delete[] cursorAND; + return c; +} + +void +MSWindowsDesks::destroyCursor(HCURSOR cursor) const +{ + if (cursor != NULL) { + DestroyCursor(cursor); + } +} + +ATOM +MSWindowsDesks::createDeskWindowClass(bool isPrimary) const +{ + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_DBLCLKS | CS_NOCLOSE; + classInfo.lpfnWndProc = isPrimary ? + &MSWindowsDesks::primaryDeskProc : + &MSWindowsDesks::secondaryDeskProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = 0; + classInfo.hInstance = MSWindowsScreen::getWindowInstance(); + classInfo.hIcon = NULL; + classInfo.hCursor = m_cursor; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = "BarrierDesk"; + classInfo.hIconSm = NULL; + return RegisterClassEx(&classInfo); +} + +void +MSWindowsDesks::destroyClass(ATOM windowClass) const +{ + if (windowClass != 0) { + UnregisterClass(MAKEINTATOM(windowClass), + MSWindowsScreen::getWindowInstance()); + } +} + +HWND +MSWindowsDesks::createWindow(ATOM windowClass, const char* name) const +{ + HWND window = CreateWindowEx(WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + MAKEINTATOM(windowClass), + name, + WS_POPUP, + 0, 0, 1, 1, + NULL, NULL, + MSWindowsScreen::getWindowInstance(), + NULL); + if (window == NULL) { + LOG((CLOG_ERR "failed to create window: %d", GetLastError())); + throw XScreenOpenFailure(); + } + return window; +} + +void +MSWindowsDesks::destroyWindow(HWND hwnd) const +{ + if (hwnd != NULL) { + DestroyWindow(hwnd); + } +} + +LRESULT CALLBACK +MSWindowsDesks::primaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK +MSWindowsDesks::secondaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + // would like to detect any local user input and hide the hider + // window but for now we just detect mouse motion. + bool hide = false; + switch (msg) { + case WM_MOUSEMOVE: + if (LOWORD(lParam) != 0 || HIWORD(lParam) != 0) { + hide = true; + } + break; + } + + if (hide && IsWindowVisible(hwnd)) { + ReleaseCapture(); + SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +void +MSWindowsDesks::deskMouseMove(SInt32 x, SInt32 y) const +{ + // when using absolute positioning with mouse_event(), + // the normalized device coordinates range over only + // the primary screen. + SInt32 w = GetSystemMetrics(SM_CXSCREEN); + SInt32 h = GetSystemMetrics(SM_CYSCREEN); + mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, + (DWORD)((65535.0f * x) / (w - 1) + 0.5f), + (DWORD)((65535.0f * y) / (h - 1) + 0.5f), + 0, 0); +} + +void +MSWindowsDesks::deskMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // relative moves are subject to cursor acceleration which we don't + // want.so we disable acceleration, do the relative move, then + // restore acceleration. there's a slight chance we'll end up in + // the wrong place if the user moves the cursor using this system's + // mouse while simultaneously moving the mouse on the server + // system. that defeats the purpose of barrier so we'll assume + // that won't happen. even if it does, the next mouse move will + // correct the position. + + // save mouse speed & acceleration + int oldSpeed[4]; + bool accelChanged = + SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed, 0) && + SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0); + + // use 1:1 motion + if (accelChanged) { + int newSpeed[4] = { 0, 0, 0, 1 }; + accelChanged = + SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) || + SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0); + } + + // move relative to mouse position + mouse_event(MOUSEEVENTF_MOVE, dx, dy, 0, 0); + + // restore mouse speed & acceleration + if (accelChanged) { + SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); + SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); + } +} + +void +MSWindowsDesks::deskEnter(Desk* desk) +{ + if (!m_isPrimary) { + ReleaseCapture(); + } + ShowCursor(TRUE); + SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); + + // restore the foreground window + // XXX -- this raises the window to the top of the Z-order. we + // want it to stay wherever it was to properly support X-mouse + // (mouse over activation) but i've no idea how to do that. + // the obvious workaround of using SetWindowPos() to move it back + // after being raised doesn't work. + DWORD thisThread = + GetWindowThreadProcessId(desk->m_window, NULL); + DWORD thatThread = + GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); + AttachThreadInput(thatThread, thisThread, TRUE); + SetForegroundWindow(desk->m_foregroundWindow); + AttachThreadInput(thatThread, thisThread, FALSE); + EnableWindow(desk->m_window, FALSE); + desk->m_foregroundWindow = NULL; +} + +void +MSWindowsDesks::deskLeave(Desk* desk, HKL keyLayout) +{ + ShowCursor(FALSE); + if (m_isPrimary) { + // map a window to hide the cursor and to use whatever keyboard + // layout we choose rather than the keyboard layout of the last + // active window. + int x, y, w, h; + // with a low level hook the cursor will never budge so + // just a 1x1 window is sufficient. + x = m_xCenter; + y = m_yCenter; + w = 1; + h = 1; + SetWindowPos(desk->m_window, HWND_TOP, x, y, w, h, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // since we're using low-level hooks, disable the foreground window + // so it can't mess up any of our keyboard events. the console + // program, for example, will cause characters to be reported as + // unshifted, regardless of the shift key state. interestingly + // we do see the shift key go down and up. + // + // note that we must enable the window to activate it and we + // need to disable the window on deskEnter. + desk->m_foregroundWindow = getForegroundWindow(); + if (desk->m_foregroundWindow != NULL) { + EnableWindow(desk->m_window, TRUE); + SetActiveWindow(desk->m_window); + DWORD thisThread = + GetWindowThreadProcessId(desk->m_window, NULL); + DWORD thatThread = + GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); + AttachThreadInput(thatThread, thisThread, TRUE); + SetForegroundWindow(desk->m_window); + AttachThreadInput(thatThread, thisThread, FALSE); + } + + // switch to requested keyboard layout + ActivateKeyboardLayout(keyLayout, 0); + } + else { + // move hider window under the cursor center, raise, and show it + SetWindowPos(desk->m_window, HWND_TOP, + m_xCenter, m_yCenter, 1, 1, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // watch for mouse motion. if we see any then we hide the + // hider window so the user can use the physically attached + // mouse if desired. we'd rather not capture the mouse but + // we aren't notified when the mouse leaves our window. + SetCapture(desk->m_window); + + // warp the mouse to the cursor center + LOG((CLOG_DEBUG2 "warping cursor to center: %+d,%+d", m_xCenter, m_yCenter)); + deskMouseMove(m_xCenter, m_yCenter); + } +} + +void +MSWindowsDesks::deskThread(void* vdesk) +{ + MSG msg; + + // use given desktop for this thread + Desk* desk = static_cast<Desk*>(vdesk); + desk->m_threadID = GetCurrentThreadId(); + desk->m_window = NULL; + desk->m_foregroundWindow = NULL; + if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) { + // create a message queue + PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE); + + // create a window. we use this window to hide the cursor. + try { + desk->m_window = createWindow(m_deskClass, "BarrierDesk"); + LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window)); + } + catch (...) { + // ignore + LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str())); + } + } + + // tell main thread that we're ready + { + Lock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + while (GetMessage(&msg, NULL, 0, 0)) { + switch (msg.message) { + default: + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + + case BARRIER_MSG_SWITCH: + if (m_isPrimary && !m_noHooks) { + MSWindowsHook::uninstall(); + if (m_screensaverNotify) { + MSWindowsHook::uninstallScreenSaver(); + MSWindowsHook::installScreenSaver(); + } + if (!MSWindowsHook::install()) { + // we won't work on this desk + LOG((CLOG_DEBUG "Cannot hook on this desk")); + } + // a window on the primary screen with low-level hooks + // should never activate. + if (desk->m_window) + EnableWindow(desk->m_window, FALSE); + } + break; + + case BARRIER_MSG_ENTER: + m_isOnScreen = true; + deskEnter(desk); + break; + + case BARRIER_MSG_LEAVE: + m_isOnScreen = false; + m_keyLayout = (HKL)msg.wParam; + deskLeave(desk, m_keyLayout); + break; + + case BARRIER_MSG_FAKE_KEY: + keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), (DWORD)msg.wParam, 0); + break; + + case BARRIER_MSG_FAKE_BUTTON: + if (msg.wParam != 0) { + mouse_event((DWORD)msg.wParam, 0, 0, (DWORD)msg.lParam, 0); + } + break; + + case BARRIER_MSG_FAKE_MOVE: + deskMouseMove(static_cast<SInt32>(msg.wParam), + static_cast<SInt32>(msg.lParam)); + break; + + case BARRIER_MSG_FAKE_REL_MOVE: + deskMouseRelativeMove(static_cast<SInt32>(msg.wParam), + static_cast<SInt32>(msg.lParam)); + break; + + case BARRIER_MSG_FAKE_WHEEL: + // XXX -- add support for x-axis scrolling + if (msg.lParam != 0) { + mouse_event(MOUSEEVENTF_WHEEL, 0, 0, (DWORD)msg.lParam, 0); + } + break; + + case BARRIER_MSG_CURSOR_POS: { + POINT* pos = reinterpret_cast<POINT*>(msg.wParam); + if (!GetCursorPos(pos)) { + pos->x = m_xCenter; + pos->y = m_yCenter; + } + break; + } + + case BARRIER_MSG_SYNC_KEYS: + m_updateKeys->run(); + break; + + case BARRIER_MSG_SCREENSAVER: + if (!m_noHooks) { + if (msg.wParam != 0) { + MSWindowsHook::installScreenSaver(); + } + else { + MSWindowsHook::uninstallScreenSaver(); + } + } + break; + + case BARRIER_MSG_FAKE_INPUT: + keybd_event(BARRIER_HOOK_FAKE_INPUT_VIRTUAL_KEY, + BARRIER_HOOK_FAKE_INPUT_SCANCODE, + msg.wParam ? 0 : KEYEVENTF_KEYUP, 0); + break; + } + + // notify that message was processed + Lock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + // clean up + deskEnter(desk); + if (desk->m_window != NULL) { + DestroyWindow(desk->m_window); + } + if (desk->m_desk != NULL) { + closeDesktop(desk->m_desk); + } +} + +MSWindowsDesks::Desk* +MSWindowsDesks::addDesk(const String& name, HDESK hdesk) +{ + Desk* desk = new Desk; + desk->m_name = name; + desk->m_desk = hdesk; + desk->m_targetID = GetCurrentThreadId(); + desk->m_thread = new Thread(new TMethodJob<MSWindowsDesks>( + this, &MSWindowsDesks::deskThread, desk)); + waitForDesk(); + m_desks.insert(std::make_pair(name, desk)); + return desk; +} + +void +MSWindowsDesks::removeDesks() +{ + for (Desks::iterator index = m_desks.begin(); + index != m_desks.end(); ++index) { + Desk* desk = index->second; + PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0); + desk->m_thread->wait(); + delete desk->m_thread; + delete desk; + } + m_desks.clear(); + m_activeDesk = NULL; + m_activeDeskName = ""; +} + +void +MSWindowsDesks::checkDesk() +{ + // get current desktop. if we already know about it then return. + Desk* desk; + HDESK hdesk = openInputDesktop(); + String name = getDesktopName(hdesk); + Desks::const_iterator index = m_desks.find(name); + if (index == m_desks.end()) { + desk = addDesk(name, hdesk); + // hold on to hdesk until thread exits so the desk can't + // be removed by the system + } + else { + closeDesktop(hdesk); + desk = index->second; + } + + // if we are told to shut down on desk switch, and this is not the + // first switch, then shut down. + if (m_stopOnDeskSwitch && m_activeDesk != NULL && name != m_activeDeskName) { + LOG((CLOG_DEBUG "shutting down because of desk switch to \"%s\"", name.c_str())); + m_events->addEvent(Event(Event::kQuit)); + return; + } + + // if active desktop changed then tell the old and new desk threads + // about the change. don't switch desktops when the screensaver is + // active becaue we'd most likely switch to the screensaver desktop + // which would have the side effect of forcing the screensaver to + // stop. + if (name != m_activeDeskName && !m_screensaver->isActive()) { + // show cursor on previous desk + bool wasOnScreen = m_isOnScreen; + if (!wasOnScreen) { + sendMessage(BARRIER_MSG_ENTER, 0, 0); + } + + // check for desk accessibility change. we don't get events + // from an inaccessible desktop so when we switch from an + // inaccessible desktop to an accessible one we have to + // update the keyboard state. + LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str())); + bool syncKeys = false; + bool isAccessible = isDeskAccessible(desk); + if (isDeskAccessible(m_activeDesk) != isAccessible) { + if (isAccessible) { + LOG((CLOG_DEBUG "desktop is now accessible")); + syncKeys = true; + } + else { + LOG((CLOG_DEBUG "desktop is now inaccessible")); + } + } + + // switch desk + m_activeDesk = desk; + m_activeDeskName = name; + sendMessage(BARRIER_MSG_SWITCH, 0, 0); + + // hide cursor on new desk + if (!wasOnScreen) { + sendMessage(BARRIER_MSG_LEAVE, (WPARAM)m_keyLayout, 0); + } + + // update keys if necessary + if (syncKeys) { + updateKeys(); + } + } + else if (name != m_activeDeskName) { + // screen saver might have started + PostThreadMessage(m_threadID, BARRIER_MSG_SCREEN_SAVER, TRUE, 0); + } +} + +bool +MSWindowsDesks::isDeskAccessible(const Desk* desk) const +{ + return (desk != NULL && desk->m_desk != NULL); +} + +void +MSWindowsDesks::waitForDesk() const +{ + MSWindowsDesks* self = const_cast<MSWindowsDesks*>(this); + + Lock lock(&m_mutex); + while (!(bool)m_deskReady) { + m_deskReady.wait(); + } + self->m_deskReady = false; +} + +void +MSWindowsDesks::handleCheckDesk(const Event&, void*) +{ + checkDesk(); + + // also check if screen saver is running if on a modern OS and + // this is the primary screen. + if (m_isPrimary) { + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, FALSE); + PostThreadMessage(m_threadID, BARRIER_MSG_SCREEN_SAVER, running, 0); + } +} + +HDESK +MSWindowsDesks::openInputDesktop() +{ + return OpenInputDesktop( + DF_ALLOWOTHERACCOUNTHOOK, TRUE, + DESKTOP_CREATEWINDOW | DESKTOP_HOOKCONTROL | GENERIC_WRITE); +} + +void +MSWindowsDesks::closeDesktop(HDESK desk) +{ + if (desk != NULL) { + CloseDesktop(desk); + } +} + +String +MSWindowsDesks::getDesktopName(HDESK desk) +{ + if (desk == NULL) { + return String(); + } + else { + DWORD size; + GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); + TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR)); + GetUserObjectInformation(desk, UOI_NAME, name, size, &size); + String result(name); + return result; + } +} + +HWND +MSWindowsDesks::getForegroundWindow() const +{ + // Ideally we'd return NULL as much as possible, only returning + // the actual foreground window when we know it's going to mess + // up our keyboard input. For now we'll just let the user + // decide. + if (m_leaveForegroundOption) { + return NULL; + } + return GetForegroundWindow(); +} |
