From 52b60d51753e39c74731386f3f6a751579bdcf56 Mon Sep 17 00:00:00 2001 From: crs Date: Fri, 12 Jul 2002 20:41:23 +0000 Subject: [PATCH] refactoring. refactored stuff in client (with changes to server as necessary). --- client/CMSWindowsSecondaryScreen.cpp | 532 ++++++++++-------- client/CMSWindowsSecondaryScreen.h | 33 +- client/CXWindowsSecondaryScreen.cpp | 350 +++++++----- client/CXWindowsSecondaryScreen.h | 26 +- platform/CMSWindowsScreen.cpp | 59 +- platform/CMSWindowsScreen.h | 30 +- platform/CXWindowsScreen.cpp | 250 ++++----- platform/CXWindowsScreen.h | 35 +- platform/CXWindowsScreenSaver.cpp | 2 +- platform/CXWindowsScreenSaver.h | 6 +- server/CMSWindowsPrimaryScreen.cpp | 234 ++++---- server/CMSWindowsPrimaryScreen.h | 23 +- server/CXWindowsPrimaryScreen.cpp | 774 ++++++++++++++------------- server/CXWindowsPrimaryScreen.h | 16 +- 14 files changed, 1325 insertions(+), 1045 deletions(-) diff --git a/client/CMSWindowsSecondaryScreen.cpp b/client/CMSWindowsSecondaryScreen.cpp index 3b0ff758..e49ec3d4 100644 --- a/client/CMSWindowsSecondaryScreen.cpp +++ b/client/CMSWindowsSecondaryScreen.cpp @@ -50,53 +50,60 @@ CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen() void CMSWindowsSecondaryScreen::run() { + assert(m_window != NULL); + // must call run() from same thread as open() assert(m_threadID == GetCurrentThreadId()); // change our priority CThread::getCurrentThread().setPriority(-7); - // poll input desktop to see if it changes (onPreTranslate() - // handles WM_TIMER) - UINT timer = 0; - if (!m_is95Family) { - SetTimer(NULL, 0, 200, NULL); - } - // run event loop - log((CLOG_INFO "entering event loop")); - doRun(); - log((CLOG_INFO "exiting event loop")); - - // remove timer - if (!m_is95Family) { - KillTimer(NULL, timer); + try { + log((CLOG_INFO "entering event loop")); + mainLoop(); + log((CLOG_INFO "exiting event loop")); + } + catch (...) { + log((CLOG_INFO "exiting event loop")); + throw; } } void CMSWindowsSecondaryScreen::stop() { - doStop(); + exitMainLoop(); } void CMSWindowsSecondaryScreen::open() { - // open the display - openDisplay(); + assert(m_window == NULL); - // update key state - updateKeys(); - updateModifiers(); + try { + // open the display + openDisplay(); - // assume primary has all clipboards - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - grabClipboard(id); + // create and prepare our window + createWindow(); + + // initialize the clipboards; assume primary has all clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + grabClipboard(id); + } + + // get keyboard state + updateKeys(); + updateModifiers(); + + // disable the screen saver + installScreenSaver(); + } + catch (...) { + close(); + throw; } - - // disable the screen saver - getScreenSaver()->disable(); // hide the cursor m_active = true; @@ -106,13 +113,8 @@ CMSWindowsSecondaryScreen::open() void CMSWindowsSecondaryScreen::close() { - // release keys that are logically pressed - releaseKeys(); - - // restore the screen saver settings - getScreenSaver()->enable(); - - // close the display + uninstallScreenSaver(); + destroyWindow(); closeDisplay(); } @@ -145,8 +147,11 @@ CMSWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask) toggleKey(VK_SCROLL, KeyModifierScrollLock); } + // warp to requested location + warpCursor(x, y); + // show mouse - onEnter(x, y); + hideWindow(); } void @@ -161,30 +166,13 @@ CMSWindowsSecondaryScreen::leave() syncDesktop(); // hide mouse - onLeave(); + showWindow(); // not active anymore m_active = false; - // if we think we own the clipboard but we don't then somebody - // grabbed the clipboard on this screen without us knowing. - // tell the server that this screen grabbed the clipboard. - // - // this works around bugs in the clipboard viewer chain. - // sometimes NT will simply never send WM_DRAWCLIPBOARD - // messages for no apparent reason and rebooting fixes the - // problem. since we don't want a broken clipboard until the - // next reboot we do this double check. clipboard ownership - // won't be reflected on other screens until we leave but at - // least the clipboard itself will work. - HWND clipboardOwner = GetClipboardOwner(); - if (m_clipboardOwner != clipboardOwner) { - m_clipboardOwner = clipboardOwner; - if (m_clipboardOwner != m_window) { - m_receiver->onGrabClipboard(kClipboardClipboard); - m_receiver->onGrabClipboard(kClipboardSelection); - } - } + // make sure our idea of clipboard ownership is correct + checkClipboard(); } void @@ -402,15 +390,7 @@ CMSWindowsSecondaryScreen::getMousePos(SInt32& x, SInt32& y) const assert(m_window != NULL); syncDesktop(); - POINT pos; - if (GetCursorPos(&pos)) { - x = pos.x; - y = pos.y; - } - else { - x = 0; - y = 0; - } + getCursorPos(x, y); } void @@ -437,52 +417,18 @@ CMSWindowsSecondaryScreen::getClipboard(ClipboardID /*id*/, CClipboard::copy(dst, &src); } -void -CMSWindowsSecondaryScreen::onOpenDisplay() -{ - assert(m_window == NULL); - - // note if using multiple monitors - m_multimon = isMultimon(); - - // save thread id. we'll need to pass this to the hook library. - m_threadID = GetCurrentThreadId(); - - // get the input desktop and switch to it - if (m_is95Family) { - if (!openDesktop()) { - throw XScreenOpenFailure(); - } - } - else { - if (!switchDesktop(openInputDesktop())) { - throw XScreenOpenFailure(); - } - } -} - -void -CMSWindowsSecondaryScreen::onCloseDisplay() -{ - // disconnect from desktop - if (m_is95Family) { - closeDesktop(); - } - else { - switchDesktop(NULL); - } - - // clear thread id - m_threadID = 0; - - assert(m_window == NULL); - assert(m_desk == NULL); -} - bool -CMSWindowsSecondaryScreen::onPreTranslate(MSG* msg) +CMSWindowsSecondaryScreen::onPreDispatch(const CEvent* event) { + assert(event != NULL); + + // forward to superclass + if (CMSWindowsScreen::onPreDispatch(event)) { + return true; + } + // handle event + const MSG* msg = &event->m_msg; switch (msg->message) { case WM_TIMER: // if current desktop is not the input desktop then switch to it @@ -503,32 +449,35 @@ CMSWindowsSecondaryScreen::onPreTranslate(MSG* msg) return false; } -LRESULT -CMSWindowsSecondaryScreen::onEvent(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +bool +CMSWindowsSecondaryScreen::onEvent(CEvent* event) { - switch (msg) { + assert(event != NULL); + + const MSG& msg = event->msg; + switch (msg.message) { case WM_QUERYENDSESSION: if (m_is95Family) { - return TRUE; + event->m_result = TRUE; + return true; } break; case WM_ENDSESSION: if (m_is95Family) { - if (wParam == TRUE && lParam == 0) { + if (msg.wParam == TRUE && msg.lParam == 0) { stop(); } - return 0; + return true; } break; case WM_PAINT: - ValidateRect(hwnd, NULL); - return 0; + ValidateRect(msg.hwnd, NULL); + return true; case WM_ACTIVATEAPP: - if (wParam == FALSE) { + if (msg.wParam == FALSE) { // some other app activated. hide the hider window. ShowWindow(m_window, SW_HIDE); } @@ -538,67 +487,265 @@ CMSWindowsSecondaryScreen::onEvent(HWND hwnd, UINT msg, log((CLOG_DEBUG "clipboard was taken")); // first pass it on - SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, + msg.message, msg.wParam, msg.lParam); + } // now notify client that somebody changed the clipboard (unless // we're now the owner, in which case it's because we took // ownership, or now it's owned by nobody, which will happen if // we owned it and switched desktops because we destroy our // window to do that). - m_clipboardOwner = GetClipboardOwner(); - if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) { - m_receiver->onGrabClipboard(kClipboardClipboard); - m_receiver->onGrabClipboard(kClipboardSelection); + try { + m_clipboardOwner = GetClipboardOwner(); + if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) { + m_receiver->onGrabClipboard(kClipboardClipboard); + m_receiver->onGrabClipboard(kClipboardSelection); + } } - return 0; + catch (XBadClient&) { + // ignore. this can happen if we receive this event + // before we've fully started up. + } + return true; case WM_CHANGECBCHAIN: - if (m_nextClipboardWindow == (HWND)wParam) { - m_nextClipboardWindow = (HWND)lParam; + if (m_nextClipboardWindow == (HWND)msg.wParam) { + m_nextClipboardWindow = (HWND)msg.lParam; } - else { - SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + else if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, + msg.message, msg.wParam, msg.lParam); } - return 0; + return true; case WM_DISPLAYCHANGE: - // screen resolution has changed - updateScreenShape(); - m_multimon = isMultimon(); + { + // screen resolution may have changed. get old shape. + SInt32 xOld, yOld, wOld, hOld; + getScreenShape(xOld, yOld, wOld, hOld); - // send new info - CClientInfo info; - getShape(info.m_x, info.m_y, info.m_w, info.m_h); - getMousePos(info.m_mx, info.m_my); - info.m_zoneSize = getJumpZoneSize(); - m_receiver->onInfoChanged(info); - return 0; + // update shape + updateScreenShape(); + m_multimon = isMultimon(); + + // collect new screen info + CClientInfo info; + getScreenShape(info.m_x, info.m_y, info.m_w, info.m_h); + getCursorPos(info.m_mx, info.m_my); + info.m_zoneSize = getJumpZoneSize(); + + // do nothing if resolution hasn't changed + if (info.m_x != xOld || info.m_y != yOld || + info.m_w != wOld || info.m_h != hOld) { + // send new screen info + m_receiver->onInfoChanged(info); + } + + return true; + } } - return DefWindowProc(hwnd, msg, wParam, lParam); + return false; +} + +CString +CMSWindowsSecondaryScreen::getCurrentDesktopName() const +{ + return m_deskName; } void -CMSWindowsSecondaryScreen::onEnter(SInt32 x, SInt32 y) +CMSWindowsSecondaryScreen::showWindow() { - // warp to requested location - warpCursor(x, y); + // move hider window under the mouse (rather than moving the mouse + // somewhere else on the screen) + SInt32 x, y; + getCursorPos(x, y); + MoveWindow(m_window, x, y, 1, 1, FALSE); - // show cursor + // raise and show the hider window. take activation. + ShowWindow(m_window, SW_SHOWNORMAL); +} + +void +CMSWindowsSecondaryScreen::hideWindow() +{ ShowWindow(m_window, SW_HIDE); } void -CMSWindowsSecondaryScreen::onLeave() +CMSWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y) { - // move hider window under the mouse (rather than moving the mouse - // somewhere else on the screen) - POINT point; - GetCursorPos(&point); - MoveWindow(m_window, point.x, point.y, 1, 1, FALSE); + // move the mouse directly to target position on NT family or if + // not using multiple monitors. + if (!m_multimon || !m_is95Family) { + SInt32 x0, y0, w, h; + getScreenShape(x0, y0, w, h); + mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, + (DWORD)((65535.99 * (x - x0)) / (w - 1)), + (DWORD)((65535.99 * (y - y0)) / (h - 1)), + 0, 0); + } - // raise and show the hider window. take activation. - ShowWindow(m_window, SW_SHOWNORMAL); + // windows 98 (and Me?) is broken. you cannot set the absolute + // position of the mouse except on the primary monitor but you + // can do relative moves onto any monitor. this is, in microsoft's + // words, "by design." apparently the designers of windows 2000 + // we're a little less lazy and did it right. + // + // we use the microsoft recommendation (Q193003): set the absolute + // position on the primary monitor, disable mouse acceleration, + // relative move the mouse to the final location, restore mouse + // acceleration. to avoid one kind of race condition (the user + // clicking the mouse or pressing a key between the absolute and + // relative move) we'll use SendInput() which guarantees that the + // events are delivered uninterrupted. we cannot prevent changes + // to the mouse acceleration at inopportune times, though. + // + // point-to-activate (x-mouse) doesn't seem to be bothered by the + // absolute/relative combination. a window over the absolute + // position (0,0) does *not* get activated (at least not on win2k) + // if the relative move puts the cursor elsewhere. similarly, the + // app under the final mouse position does *not* get deactivated + // by the absolute move to 0,0. + else { + // 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); + } + + // send events + INPUT events[2]; + events[0].type = INPUT_MOUSE; + events[0].mi.dx = 0; + events[0].mi.dy = 0; + events[0].mi.mouseData = 0; + events[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[0].mi.time = GetTickCount(); + events[0].mi.dwExtraInfo = 0; + events[1].type = INPUT_MOUSE; + events[1].mi.dx = x; + events[1].mi.dy = y; + events[1].mi.mouseData = 0; + events[1].mi.dwFlags = MOUSEEVENTF_MOVE; + events[1].mi.time = events[0].mi.time; + events[1].mi.dwExtraInfo = 0; + SendInput(sizeof(events) / sizeof(events[0]), + events, sizeof(events[0])); + + // restore mouse speed & acceleration + if (accelChanged) { + SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); + SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); + } + } +} + +void +CMSWindowsSecondaryScreen::checkClipboard() +{ + // if we think we own the clipboard but we don't then somebody + // grabbed the clipboard on this screen without us knowing. + // tell the server that this screen grabbed the clipboard. + // + // this works around bugs in the clipboard viewer chain. + // sometimes NT will simply never send WM_DRAWCLIPBOARD + // messages for no apparent reason and rebooting fixes the + // problem. since we don't want a broken clipboard until the + // next reboot we do this double check. clipboard ownership + // won't be reflected on other screens until we leave but at + // least the clipboard itself will work. + HWND clipboardOwner = GetClipboardOwner(); + if (m_clipboardOwner != clipboardOwner) { + try { + m_clipboardOwner = clipboardOwner; + if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) { + m_receiver->onGrabClipboard(kClipboardClipboard); + m_receiver->onGrabClipboard(kClipboardSelection); + } + } + catch (XBadClient&) { + // ignore + } + } +} + +void +CMSWindowsPrimaryScreen::createWindow() +{ + // save thread id + m_threadID = GetCurrentThreadId(); + + // note if using multiple monitors + m_multimon = isMultimon(); + + // get the input desktop and switch to it + if (m_is95Family) { + if (!openDesktop()) { + throw XScreenOpenFailure(); + } + } + else { + if (!switchDesktop(openInputDesktop())) { + throw XScreenOpenFailure(); + } + } + + // poll input desktop to see if it changes (onPreDispatch() + // handles WM_TIMER) + m_timer = 0; + if (!m_is95Family) { + m_timer = SetTimer(NULL, 0, 200, NULL); + } +} + +void +CMSWindowsPrimaryScreen::destroyWindow() +{ + // remove timer + if (m_timer != 0) { + KillTimer(NULL, m_timer); + } + + // release keys that are logically pressed + releaseKeys(); + + // disconnect from desktop + if (m_is95Family) { + closeDesktop(); + } + else { + switchDesktop(NULL); + } + + // clear thread id + m_threadID = 0; + + assert(m_window == NULL); + assert(m_desk == NULL); +} + +void +CMSWindowsSecondaryScreen::installScreenSaver() +{ + getScreenSaver()->disable(); +} + +void +CMSWindowsSecondaryScreen::uninstallScreenSaver() +{ + getScreenSaver()->enable(); } bool @@ -720,7 +867,7 @@ CMSWindowsSecondaryScreen::switchDesktop(HDESK desk) // get desktop up to date if (!m_active) { - onLeave(); + showWindow(); } return true; @@ -750,89 +897,6 @@ CMSWindowsSecondaryScreen::syncDesktop() const } } -CString -CMSWindowsSecondaryScreen::getCurrentDesktopName() const -{ - return m_deskName; -} - -void -CMSWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y) -{ - // move the mouse directly to target position on NT family or if - // not using multiple monitors. - if (!m_multimon || !m_is95Family) { - SInt32 x0, y0, w, h; - getScreenShape(x0, y0, w, h); - mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, - (DWORD)((65535.99 * (x - x0)) / (w - 1)), - (DWORD)((65535.99 * (y - y0)) / (h - 1)), - 0, 0); - } - - // windows 98 (and Me?) is broken. you cannot set the absolute - // position of the mouse except on the primary monitor but you - // can do relative moves onto any monitor. this is, in microsoft's - // words, "by design." apparently the designers of windows 2000 - // we're a little less lazy and did it right. - // - // we use the microsoft recommendation (Q193003): set the absolute - // position on the primary monitor, disable mouse acceleration, - // relative move the mouse to the final location, restore mouse - // acceleration. to avoid one kind of race condition (the user - // clicking the mouse or pressing a key between the absolute and - // relative move) we'll use SendInput() which guarantees that the - // events are delivered uninterrupted. we cannot prevent changes - // to the mouse acceleration at inopportune times, though. - // - // point-to-activate (x-mouse) doesn't seem to be bothered by the - // absolute/relative combination. a window over the absolute - // position (0,0) does *not* get activated (at least not on win2k) - // if the relative move puts the cursor elsewhere. similarly, the - // app under the final mouse position does *not* get deactivated - // by the absolute move to 0,0. - else { - // 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); - } - - // send events - INPUT events[2]; - events[0].type = INPUT_MOUSE; - events[0].mi.dx = 0; - events[0].mi.dy = 0; - events[0].mi.mouseData = 0; - events[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; - events[0].mi.time = GetTickCount(); - events[0].mi.dwExtraInfo = 0; - events[1].type = INPUT_MOUSE; - events[1].mi.dx = x; - events[1].mi.dy = y; - events[1].mi.mouseData = 0; - events[1].mi.dwFlags = MOUSEEVENTF_MOVE; - events[1].mi.time = events[0].mi.time; - events[1].mi.dwExtraInfo = 0; - SendInput(sizeof(events) / sizeof(events[0]), - events, sizeof(events[0])); - - // restore mouse speed & acceleration - if (accelChanged) { - SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); - SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); - } - } -} - bool CMSWindowsSecondaryScreen::isMultimon() const { diff --git a/client/CMSWindowsSecondaryScreen.h b/client/CMSWindowsSecondaryScreen.h index 8feb6d3f..c6f9c5e3 100644 --- a/client/CMSWindowsSecondaryScreen.h +++ b/client/CMSWindowsSecondaryScreen.h @@ -46,10 +46,8 @@ public: protected: // CMSWindowsScreen overrides - virtual bool onPreTranslate(MSG*); - virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM); - virtual void onOpenDisplay(); - virtual void onCloseDisplay(); + virtual bool onPreDispatch(const CEvent* event); + virtual bool onEvent(CEvent* event); virtual CString getCurrentDesktopName() const; private: @@ -62,8 +60,25 @@ private: }; typedef std::vector Keystrokes; - void onEnter(SInt32 x, SInt32 y); - void onLeave(); + void showWindow(); + void hideWindow(); + + // warp the mouse to the specified position + void warpCursor(SInt32 x, SInt32 y); + + // check clipboard ownership and, if necessary, tell the receiver + // of a grab. + void checkClipboard(); + + // create/destroy window + // also attach to desktop; this destroys and recreates the window + // as necessary. + void createWindow(); + void destroyWindow(); + + // start/stop watch for screen saver changes + void installScreenSaver(); + void uninstallScreenSaver(); // open/close desktop (for windows 95/98/me) bool openDesktop(); @@ -75,9 +90,6 @@ private: // get calling thread to use the input desktop void syncDesktop() const; - // warp the mouse to the specified position - void warpCursor(SInt32 x, SInt32 y); - // returns true iff there appear to be multiple monitors bool isMultimon() const; @@ -108,6 +120,9 @@ private: // the main loop's thread id DWORD m_threadID; + // the timer used to check for desktop switching + UINT m_timer; + // the thread id of the last attached thread mutable DWORD m_lastThreadID; diff --git a/client/CXWindowsSecondaryScreen.cpp b/client/CXWindowsSecondaryScreen.cpp index 2eaa1fd8..aea09204 100644 --- a/client/CXWindowsSecondaryScreen.cpp +++ b/client/CXWindowsSecondaryScreen.cpp @@ -1,8 +1,9 @@ #include "CXWindowsSecondaryScreen.h" -#include "CClient.h" +#include "IScreenReceiver.h" #include "CXWindowsClipboard.h" #include "CXWindowsScreenSaver.h" #include "CXWindowsUtil.h" +#include "XScreen.h" #include "CThread.h" #include "CLog.h" #if defined(X_DISPLAY_MISSING) @@ -26,9 +27,10 @@ CXWindowsSecondaryScreen::CXWindowsSecondaryScreen(IScreenReceiver* receiver) : m_receiver(receiver), - m_window(None) + m_window(None), + m_active(false) { - // do nothing + assert(m_receiver != NULL); } CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen() @@ -41,135 +43,79 @@ CXWindowsSecondaryScreen::run() { assert(m_window != None); - for (;;) { - // wait for and get the next event - XEvent xevent; - if (!getEvent(&xevent)) { - break; - } + // change our priority + CThread::getCurrentThread().setPriority(-7); - // handle event - switch (xevent.type) { - case MappingNotify: - { - // keyboard mapping changed - CDisplayLock display(this); - XRefreshKeyboardMapping(&xevent.xmapping); - updateKeys(display); - updateKeycodeMap(display); - updateModifierMap(display); - updateModifiers(display); - } - break; - - case LeaveNotify: - { - // mouse moved out of hider window somehow. hide the window. - assert(m_window != None); - CDisplayLock display(this); - XUnmapWindow(display, m_window); - } - break; - } + // run event loop + try { + log((CLOG_INFO "entering event loop")); + mainLoop(); + log((CLOG_INFO "exiting event loop")); + } + catch (...) { + log((CLOG_INFO "exiting event loop")); + throw; } } void CXWindowsSecondaryScreen::stop() { - CDisplayLock display(this); - doStop(); + exitMainLoop(); } void CXWindowsSecondaryScreen::open() { - assert(m_receiver != NULL); assert(m_window == None); - // open the display - openDisplay(); + try { + // open the display + openDisplay(); - { - CDisplayLock display(this); + // create and prepare our window + createWindow(); - // verify the availability of the XTest extension - int majorOpcode, firstEvent, firstError; - if (!XQueryExtension(display, XTestExtensionName, - &majorOpcode, &firstEvent, &firstError)) { - throw int(6); // FIXME -- make exception for this + // initialize the clipboards; assume primary has all clipboards + initClipboards(m_window); + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + grabClipboard(id); } - // create the cursor hiding window. this window is used to hide the - // cursor when it's not on the screen. the window is hidden as soon - // as the cursor enters the screen or the display's real cursor is - // moved. - XSetWindowAttributes attr; - attr.event_mask = LeaveWindowMask; - attr.do_not_propagate_mask = 0; - attr.override_redirect = True; - attr.cursor = getBlankCursor(); - m_window = XCreateWindow(display, getRoot(), 0, 0, 1, 1, 0, 0, - InputOnly, CopyFromParent, - CWDontPropagate | CWEventMask | - CWOverrideRedirect | CWCursor, - &attr); - log((CLOG_DEBUG "window is 0x%08x", m_window)); + // check for peculiarities + // FIXME -- may have to get these from some database + m_numLockHalfDuplex = false; + m_capsLockHalfDuplex = false; +// m_numLockHalfDuplex = true; +// m_capsLockHalfDuplex = true; - // become impervious to server grabs - XTestGrabControl(display, True); - - // hide the cursor - leaveNoLock(display); - - // initialize the clipboards - initClipboards(m_window); + // get the display + CDisplayLock display(this); // update key state updateKeys(display); updateKeycodeMap(display); updateModifierMap(display); updateModifiers(display); + + // disable the screen saver + installScreenSaver(); + } + catch (...) { + close(); + throw; } - // check for peculiarities - // FIXME -- may have to get these from some database - m_numLockHalfDuplex = false; - m_capsLockHalfDuplex = false; -// m_numLockHalfDuplex = true; -// m_capsLockHalfDuplex = true; - - // assume primary has all clipboards - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - grabClipboard(id); - } - - // disable the screen saver - getScreenSaver()->disable(); + // hide the cursor + m_active = true; + leave(); } void CXWindowsSecondaryScreen::close() { - // release keys that are logically pressed - releaseKeys(); - - // restore the screen saver settings - getScreenSaver()->enable(); - - { - CDisplayLock display(this); - if (display != NULL) { - // no longer impervious to server grabs - XTestGrabControl(display, False); - - // destroy window - XDestroyWindow(display, m_window); - } - m_window = None; - } - - // close the display + uninstallScreenSaver(); + destroyWindow(); closeDisplay(); } @@ -177,15 +123,14 @@ void CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask) { assert(m_window != None); + assert(m_active == false); + + log((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask)); CDisplayLock display(this); - // warp to requested location - XTestFakeMotionEvent(display, getScreen(), x, y, CurrentTime); - XSync(display, False); - - // show cursor - XUnmapWindow(display, m_window); + // now active + m_active = true; // update our keyboard state to reflect the local state updateKeys(display); @@ -202,14 +147,32 @@ CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask) if ((xMask & m_scrollLockMask) != (m_mask & m_scrollLockMask)) { toggleKey(display, XK_Scroll_Lock, m_scrollLockMask); } - XSync(display, False); + + // warp to requested location + warpCursor(x, y); + + // show mouse + hideWindow(); } void CXWindowsSecondaryScreen::leave() { + assert(m_window != None); + assert(m_active == true); + + log((CLOG_INFO "leaving screen")); + CDisplayLock display(this); - leaveNoLock(display); + + // hide mouse + showWindow(); + + // not active anymore + m_active = false; + + // make sure our idea of clipboard ownership is correct + checkClipboard(); } void @@ -290,8 +253,7 @@ void CXWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y) { CDisplayLock display(this); - XTestFakeMotionEvent(display, getScreen(), x, y, CurrentTime); - XSync(display, False); + warpCursor(x, y); } void @@ -366,6 +328,44 @@ CXWindowsSecondaryScreen::getClipboard(ClipboardID id, getDisplayClipboard(id, clipboard); } +bool +CXWindowsSecondaryScreen::onPreDispatch(const CEvent* event) +{ + // forward to superclass + return CXWindowsScreen::onPreDispatch(event); +} + +bool +CXWindowsSecondaryScreen::onEvent(CEvent* event) +{ + assert(event != NULL); + XEvent& xevent = event->m_event; + + // handle event + switch (xevent.type) { + case MappingNotify: + { + // keyboard mapping changed + CDisplayLock display(this); + XRefreshKeyboardMapping(&xevent.xmapping); + updateKeys(display); + updateKeycodeMap(display); + updateModifierMap(display); + updateModifiers(display); + } + return true; + + case LeaveNotify: + { + // mouse moved out of hider window somehow. hide the window. + assert(m_window != None); + CDisplayLock display(this); + hideWindow(); + } + return true; + } +} + void CXWindowsSecondaryScreen::onLostClipboard(ClipboardID id) { @@ -374,25 +374,110 @@ CXWindowsSecondaryScreen::onLostClipboard(ClipboardID id) } void -CXWindowsSecondaryScreen::leaveNoLock(Display* display) +CXWindowsSecondaryScreen::showWindow() { - assert(display != NULL); - assert(m_window != None); - // move hider window under the mouse (rather than moving the mouse // somewhere else on the screen) - int x, y, dummy; - unsigned int dummyMask; - Window dummyWindow; - XQueryPointer(display, getRoot(), &dummyWindow, &dummyWindow, - &x, &y, &dummy, &dummy, &dummyMask); - XMoveWindow(display, m_window, x, y); + SInt32 x, y; + getCursorPos(x, y); + XMoveWindow(getDisplay(), m_window, x, y); - // raise and show the hider window - XMapRaised(display, m_window); + // raise and show the hider window. take activation. + // FIXME -- take focus? + XMapRaised(getDisplay(), m_window); +/* XXX -- this should have no effect // hide cursor by moving it into the hider window - XWarpPointer(display, None, m_window, 0, 0, 0, 0, 0, 0); + XWarpPointer(getDisplay(), None, m_window, 0, 0, 0, 0, 0, 0); +*/ +} + +void +CXWindowsSecondaryScreen::hideWindow() +{ + XUnmapWindow(getDisplay(), m_window); +} + +void +CXWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y) +{ + XTestFakeMotionEvent(getDisplay(), getScreen(), x, y, CurrentTime); + XSync(getDisplay(), False); +} + +void +CXWindowsSecondaryScreen::checkClipboard() +{ + // do nothing, we're always up to date +} + +void +CXWindowsSecondaryScreen::createWindow() +{ + CDisplayLock display(this); + + // verify the availability of the XTest extension + int majorOpcode, firstEvent, firstError; + if (!XQueryExtension(display, XTestExtensionName, + &majorOpcode, &firstEvent, &firstError)) { + // FIXME -- subclass exception for more info? + throw XScreenOpenFailure(); + } + + // cursor hider window attributes. this window is used to hide the + // cursor when it's not on the screen. the window is hidden as soon + // as the cursor enters the screen or the display's real cursor is + // moved. + XSetWindowAttributes attr; + attr.event_mask = LeaveWindowMask; + attr.do_not_propagate_mask = 0; + attr.override_redirect = True; + attr.cursor = getBlankCursor(); + + // create the cursor hider window + m_window = XCreateWindow(display, getRoot(), + 0, 0, 1, 1, 0, 0, + InputOnly, CopyFromParent, + CWDontPropagate | CWEventMask | + CWOverrideRedirect | CWCursor, + &attr); + if (m_window == None) { + throw XScreenOpenFailure(); + } + log((CLOG_DEBUG "window is 0x%08x", m_window)); + + // become impervious to server grabs + XTestGrabControl(display, True); +} + +void +CXWindowsSecondaryScreen::destroyWindow() +{ + releaseKeys(); + + CDisplayLock display(this); + if (display != NULL) { + // no longer impervious to server grabs + XTestGrabControl(display, False); + + // destroy window + if (m_window != None) { + XDestroyWindow(display, m_window); + m_window = None; + } + } +} + +void +CXWindowsSecondaryScreen::installScreenSaver() +{ + getScreenSaver()->disable(); +} + +void +CXWindowsSecondaryScreen::uninstallScreenSaver() +{ + getScreenSaver()->enable(); } unsigned int @@ -829,17 +914,18 @@ CXWindowsSecondaryScreen::releaseKeys() { CDisplayLock display(this); - // key up for each key that's down - for (UInt32 i = 0; i < 256; ++i) { - if (m_keys[i]) { - XTestFakeKeyEvent(display, i, False, CurrentTime); - m_keys[i] = false; + if (display != NULL) { + // key up for each key that's down + for (UInt32 i = 0; i < 256; ++i) { + if (m_keys[i]) { + XTestFakeKeyEvent(display, i, False, CurrentTime); + m_keys[i] = false; + } } + + // update + XSync(display, False); } - - // update - XSync(display, False); - } void diff --git a/client/CXWindowsSecondaryScreen.h b/client/CXWindowsSecondaryScreen.h index aeba39ec..558b7526 100644 --- a/client/CXWindowsSecondaryScreen.h +++ b/client/CXWindowsSecondaryScreen.h @@ -39,6 +39,8 @@ public: protected: // CXWindowsScreen overrides + virtual bool onPreDispatch(const CEvent* event); + virtual bool onEvent(CEvent* event); virtual void onLostClipboard(ClipboardID); private: @@ -60,7 +62,26 @@ private: typedef std::map KeyCodeMap; typedef std::map ModifierMap; - void leaveNoLock(Display*); + void showWindow(); + void hideWindow(); + + // warp the mouse to the specified position + void warpCursor(SInt32 x, SInt32 y); + + // check clipboard ownership and, if necessary, tell the receiver + // of a grab. + void checkClipboard(); + + // create/destroy window + // also attach to desktop; this destroys and recreates the window + // as necessary. + void createWindow(); + void destroyWindow(); + + // start/stop watch for screen saver changes + void installScreenSaver(); + void uninstallScreenSaver(); + unsigned int mapButton(ButtonID button) const; unsigned int mapKey(Keystrokes&, KeyCode&, KeyID, @@ -82,6 +103,9 @@ private: IScreenReceiver* m_receiver; Window m_window; + // m_active is true if this screen has been entered + bool m_active; + // note toggle keys that toggles on up/down (false) or on // transition (true) bool m_numLockHalfDuplex; diff --git a/platform/CMSWindowsScreen.cpp b/platform/CMSWindowsScreen.cpp index f7ed98ac..3a6afd57 100644 --- a/platform/CMSWindowsScreen.cpp +++ b/platform/CMSWindowsScreen.cpp @@ -48,36 +48,44 @@ CMSWindowsScreen::init(HINSTANCE instance) } void -CMSWindowsScreen::doRun() +CMSWindowsScreen::mainLoop() { // save thread id for posting quit message m_thread = GetCurrentThreadId(); // event loop + CEvent event; + event.m_result = 0; for (;;) { - // wait for and get the next event - MSG msg; - getEvent(&msg); + // wait for an event in a cancellable way + CThread::waitForEvent(); + GetMessage(&event.m_msg, NULL, 0, 0); // handle quit message - if (msg.message == WM_QUIT) { + if (event.m_msg.message == WM_QUIT) { break; } // dispatch message - if (!onPreTranslate(&msg)) { - TranslateMessage(&msg); - DispatchMessage(&msg); + if (!onPreDispatch(&event)) { + TranslateMessage(&event.m_msg); + DispatchMessage(&event.m_msg); } } } void -CMSWindowsScreen::doStop() +CMSWindowsScreen::exitMainLoop() { PostThreadMessage(m_thread, WM_QUIT, 0, 0); } +bool +CMSWindowsScreen::onPreDispatch(const CEvent*) +{ + return false; +} + void CMSWindowsScreen::openDisplay() { @@ -172,9 +180,13 @@ void CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const { POINT pos; - GetCursorPos(&pos); - x = pos.x; - y = pos.y; + if (GetCursorPos(&pos)) { + x = pos.x; + y = pos.y; + } + else { + getCursorCenter(x, y); + } } void @@ -244,17 +256,22 @@ CMSWindowsScreen::getScreenSaver() const return m_screenSaver; } -void -CMSWindowsScreen::getEvent(MSG* msg) const -{ - // wait for an event in a cancellable way - CThread::waitForEvent(); - GetMessage(msg, NULL, 0, 0); -} - LRESULT CALLBACK CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { assert(s_screen != NULL); - return s_screen->onEvent(hwnd, msg, wParam, lParam); + + CEvent event; + event.m_msg.hwnd = hwnd; + event.m_msg.message = msg; + event.m_msg.wParam = wParam; + event.m_msg.lParam = lParam; + event.m_result = 0; + + if (s_screen->onEvent(&event)) { + return event.m_result; + } + else { + return DefWindowProc(hwnd, msg, wParam, lParam); + } } diff --git a/platform/CMSWindowsScreen.h b/platform/CMSWindowsScreen.h index 08a95d73..c45b258a 100644 --- a/platform/CMSWindowsScreen.h +++ b/platform/CMSWindowsScreen.h @@ -10,6 +10,12 @@ class CMSWindowsScreenSaver; class CThread; +class CEvent { +public: + MSG m_msg; + LRESULT m_result; +}; + class CMSWindowsScreen { public: CMSWindowsScreen(); @@ -25,11 +31,11 @@ public: static HINSTANCE getInstance(); protected: - // runs an event loop and returns when WM_QUIT is received - void doRun(); + // runs an event loop and returns when exitMainLoop() is called + void mainLoop(); - // sends WM_QUIT to force doRun() to return - void doStop(); + // force mainLoop() to return + void exitMainLoop(); // open the X display. calls onOpenDisplay() after opening the display, // getting the screen, its size, and root window. then it starts the @@ -72,15 +78,14 @@ protected: CMSWindowsScreenSaver* getScreenSaver() const; - // wait for and get the next message. cancellable. - void getEvent(MSG*) const; + // called for each event before event translation and dispatch. return + // true to skip translation and dispatch. subclasses should call the + // superclass's version first and return true if it returns true. + virtual bool onPreDispatch(const CEvent* event); - // called by doRun() to handle an event. return true to skip - // event translation and dispatch. - virtual bool onPreTranslate(MSG*) = 0; - - // called by window proc. subclass must call DefWindowProc() if necessary - virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM) = 0; + // called by mainLoop(). iff the event was handled return true and + // store the result, if any, in m_result, which defaults to zero. + virtual bool onEvent(CEvent* event) = 0; // called by isCurrentDesktop() to get the current desktop name virtual CString getCurrentDesktopName() const = 0; @@ -89,6 +94,7 @@ private: // create the transparent cursor void createBlankCursor(); + // our window proc static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM); private: diff --git a/platform/CXWindowsScreen.cpp b/platform/CXWindowsScreen.cpp index f54decde..3176ce69 100644 --- a/platform/CXWindowsScreen.cpp +++ b/platform/CXWindowsScreen.cpp @@ -120,6 +120,129 @@ CXWindowsScreen::removeTimerNoLock(IJob* job) m_timers.swap(tmp); } +void +CXWindowsScreen::mainLoop() +{ + // wait for an event in a cancellable way and don't lock the + // display while we're waiting. + CEvent event; + m_mutex.lock(); + while (!m_stop) { + while (!m_stop && XPending(m_display) == 0) { + // check timers + if (processTimers()) { + continue; + } + + // wait + m_mutex.unlock(); + CThread::sleep(0.01); + m_mutex.lock(); + } + if (!m_stop) { + // get the event + XNextEvent(m_display, &event.m_event); + + // process the event. if unhandled then let the subclass + // have a go at it. + m_mutex.unlock(); + if (!onPreDispatch(&event)) { + onEvent(&event); + } + m_mutex.lock(); + } + } + m_mutex.unlock(); +} + +void +CXWindowsScreen::exitMainLoop() +{ + CLock lock(&m_mutex); + m_stop = true; +} + +bool +CXWindowsScreen::onPreDispatch(const CEvent* event) +{ + assert(event != NULL); + const XEvent* xevent = &event->m_event; + + switch (xevent->type) { + case SelectionClear: + { + // we just lost the selection. that means someone else + // grabbed the selection so this screen is now the + // selection owner. report that to the subclass. + ClipboardID id = getClipboardID(xevent->xselectionclear.selection); + if (id != kClipboardEnd) { + log((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); + m_clipboard[id]->lost(xevent->xselectionclear.time); + onLostClipboard(id); + return true; + } + } + break; + + case SelectionNotify: + // notification of selection transferred. we shouldn't + // get this here because we handle them in the selection + // retrieval methods. we'll just delete the property + // with the data (satisfying the usual ICCCM protocol). + if (xevent->xselection.property != None) { + CLock lock(&m_mutex); + XDeleteProperty(m_display, + xevent->xselection.requestor, + xevent->xselection.property); + } + return true; + + case SelectionRequest: + { + // somebody is asking for clipboard data + ClipboardID id = getClipboardID( + xevent->xselectionrequest.selection); + if (id != kClipboardEnd) { + CLock lock(&m_mutex); + m_clipboard[id]->addRequest( + xevent->xselectionrequest.owner, + xevent->xselectionrequest.requestor, + xevent->xselectionrequest.target, + xevent->xselectionrequest.time, + xevent->xselectionrequest.property); + return true; + } + } + break; + + case PropertyNotify: + // property delete may be part of a selection conversion + if (xevent->xproperty.state == PropertyDelete) { + processClipboardRequest(xevent->xproperty.window, + xevent->xproperty.time, + xevent->xproperty.atom); + return true; + } + break; + + case DestroyNotify: + // looks like one of the windows that requested a clipboard + // transfer has gone bye-bye. + destroyClipboardRequest(xevent->xdestroywindow.window); + + // we don't know if the event was handled or not so continue + break; + } + + // let screen saver have a go + { + CLock lock(&m_mutex); + m_screenSaver->onPreDispatch(xevent); + } + + return false; +} + void CXWindowsScreen::openDisplay() { @@ -179,6 +302,12 @@ CXWindowsScreen::closeDisplay() XSetIOErrorHandler(NULL); } +Display* +CXWindowsScreen::getDisplay() const +{ + return m_display; +} + int CXWindowsScreen::getScreen() const { @@ -294,49 +423,6 @@ CXWindowsScreen::getBlankCursor() const return m_cursor; } -bool -CXWindowsScreen::getEvent(XEvent* xevent) const -{ - // wait for an event in a cancellable way and don't lock the - // display while we're waiting. - m_mutex.lock(); - for (;;) { - while (!m_stop && XPending(m_display) == 0) { - // check timers - if (const_cast(this)->processTimers()) { - continue; - } - - // wait - m_mutex.unlock(); - CThread::sleep(0.01); - m_mutex.lock(); - } - if (m_stop) { - m_mutex.unlock(); - return false; - } - else { - // get the event - XNextEvent(m_display, xevent); - - // process the event. return the event if unhandled. - m_mutex.unlock(); - if (!const_cast(this)->processEvent(xevent)) { - return true; - } - m_mutex.lock(); - } - } -} - -void -CXWindowsScreen::doStop() -{ - // caller must have locked display - m_stop = true; -} - ClipboardID CXWindowsScreen::getClipboardID(Atom selection) const { @@ -355,84 +441,6 @@ CXWindowsScreen::onUnexpectedClose() // do nothing } -bool -CXWindowsScreen::processEvent(XEvent* xevent) -{ - switch (xevent->type) { - case SelectionClear: - { - // we just lost the selection. that means someone else - // grabbed the selection so this screen is now the - // selection owner. report that to the subclass. - ClipboardID id = getClipboardID(xevent->xselectionclear.selection); - if (id != kClipboardEnd) { - log((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); - m_clipboard[id]->lost(xevent->xselectionclear.time); - onLostClipboard(id); - return true; - } - } - break; - - case SelectionNotify: - // notification of selection transferred. we shouldn't - // get this here because we handle them in the selection - // retrieval methods. we'll just delete the property - // with the data (satisfying the usual ICCCM protocol). - if (xevent->xselection.property != None) { - CLock lock(&m_mutex); - XDeleteProperty(m_display, - xevent->xselection.requestor, - xevent->xselection.property); - } - return true; - - case SelectionRequest: - { - // somebody is asking for clipboard data - ClipboardID id = getClipboardID( - xevent->xselectionrequest.selection); - if (id != kClipboardEnd) { - CLock lock(&m_mutex); - m_clipboard[id]->addRequest( - xevent->xselectionrequest.owner, - xevent->xselectionrequest.requestor, - xevent->xselectionrequest.target, - xevent->xselectionrequest.time, - xevent->xselectionrequest.property); - return true; - } - } - break; - - case PropertyNotify: - // property delete may be part of a selection conversion - if (xevent->xproperty.state == PropertyDelete) { - processClipboardRequest(xevent->xproperty.window, - xevent->xproperty.time, - xevent->xproperty.atom); - return true; - } - break; - - case DestroyNotify: - // looks like one of the windows that requested a clipboard - // transfer has gone bye-bye. - destroyClipboardRequest(xevent->xdestroywindow.window); - - // we don't know if the event was handled or not so continue - break; - } - - // let screen saver have a go - { - CLock lock(&m_mutex); - m_screenSaver->processEvent(xevent); - } - - return false; -} - bool CXWindowsScreen::processTimers() { diff --git a/platform/CXWindowsScreen.h b/platform/CXWindowsScreen.h index 8431c990..9900b7ec 100644 --- a/platform/CXWindowsScreen.h +++ b/platform/CXWindowsScreen.h @@ -19,6 +19,12 @@ class IScreenSaver; class CXWindowsClipboard; class CXWindowsScreenSaver; +class CEvent { +public: + XEvent m_event; + SInt32 m_result; +}; + class CXWindowsScreen { public: CXWindowsScreen(); @@ -47,6 +53,12 @@ protected: }; friend class CDisplayLock; + // runs an event loop and returns when exitMainLoop() is called + void mainLoop(); + + // force mainLoop() to return + void exitMainLoop(); + // open the X display. calls onOpenDisplay() after opening the display, // getting the screen, its size, and root window. then it starts the // event thread. @@ -57,6 +69,10 @@ protected: // is closed. void closeDisplay(); + // get the Display*. only use this when you know the display is + // locked but don't have the CDisplayLock available. + Display* getDisplay() const; + // get the opened screen and its root window. to get the display // create a CDisplayLock object passing this. while the object // exists no other threads may access the display. do not save @@ -83,13 +99,6 @@ protected: // get a cursor that is transparent everywhere Cursor getBlankCursor() const; - // wait for and get the next X event. cancellable. - bool getEvent(XEvent*) const; - - // cause getEvent() to return false immediately and forever after. - // the caller must have locked the display. - void doStop(); - // set the contents of the clipboard (i.e. primary selection) bool setDisplayClipboard(ClipboardID, const IClipboard* clipboard); @@ -102,6 +111,15 @@ protected: CXWindowsScreenSaver* getScreenSaver() const; + // called for each event before event translation and dispatch. return + // true to skip translation and dispatch. subclasses should call the + // superclass's version first and return true if it returns true. + virtual bool onPreDispatch(const CEvent* event) = 0; + + // called by mainLoop(). iff the event was handled return true and + // store the result, if any, in m_result, which defaults to zero. + virtual bool onEvent(CEvent* event) = 0; + // called if the display is unexpectedly closing. default does nothing. virtual void onUnexpectedClose(); @@ -115,9 +133,6 @@ private: // remove a timer without locking void removeTimerNoLock(IJob*); - // internal event processing - bool processEvent(XEvent*); - // process timers bool processTimers(); diff --git a/platform/CXWindowsScreenSaver.cpp b/platform/CXWindowsScreenSaver.cpp index 6e6c7116..a3914860 100644 --- a/platform/CXWindowsScreenSaver.cpp +++ b/platform/CXWindowsScreenSaver.cpp @@ -97,7 +97,7 @@ CXWindowsScreenSaver::~CXWindowsScreenSaver() } bool -CXWindowsScreenSaver::processEvent(XEvent* xevent) +CXWindowsScreenSaver::onPreDispatch(const XEvent* xevent) { switch (xevent->type) { case CreateNotify: diff --git a/platform/CXWindowsScreenSaver.h b/platform/CXWindowsScreenSaver.h index c97eba39..78712ab0 100644 --- a/platform/CXWindowsScreenSaver.h +++ b/platform/CXWindowsScreenSaver.h @@ -20,8 +20,10 @@ public: CXWindowsScreenSaver(CXWindowsScreen*, Display*); virtual ~CXWindowsScreenSaver(); - // process X event. returns true if the event was handled. - bool processEvent(XEvent*); + // called for each event before event translation and dispatch. return + // true to skip translation and dispatch. subclasses should call the + // superclass's version first and return true if it returns true. + bool onPreDispatch(const XEvent*); // tells this object to send a ClientMessage to the given window // when the screen saver activates or deactivates. only one diff --git a/server/CMSWindowsPrimaryScreen.cpp b/server/CMSWindowsPrimaryScreen.cpp index 3db7ebd1..f1a2dad7 100644 --- a/server/CMSWindowsPrimaryScreen.cpp +++ b/server/CMSWindowsPrimaryScreen.cpp @@ -22,6 +22,7 @@ CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen( m_receiver(receiver), m_primaryReceiver(primaryReceiver), m_threadID(0), + m_timer(0), m_desk(NULL), m_deskName(), m_window(NULL), @@ -95,28 +96,22 @@ CMSWindowsPrimaryScreen::run() // change our priority CThread::getCurrentThread().setPriority(-3); - // poll input desktop to see if it changes (preTranslateMessage() - // handles WM_TIMER) - UINT timer = 0; - if (!m_is95Family) { - SetTimer(NULL, 0, 200, NULL); - } - // run event loop - log((CLOG_INFO "entering event loop")); - doRun(); - log((CLOG_INFO "exiting event loop")); - - // remove timer - if (!m_is95Family) { - KillTimer(NULL, timer); + try { + log((CLOG_INFO "entering event loop")); + mainLoop(); + log((CLOG_INFO "exiting event loop")); + } + catch (...) { + log((CLOG_INFO "exiting event loop")); + throw; } } void CMSWindowsPrimaryScreen::stop() { - doStop(); + exitMainLoop(); } void @@ -269,50 +264,6 @@ CMSWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y) m_y = y; } -void -CMSWindowsPrimaryScreen::warpCursorToCenter() -{ - // warp to center. the extra info tells the hook DLL to send - // SYNERGY_MSG_POST_WARP instead of SYNERGY_MSG_MOUSE_MOVE. - SInt32 x, y, w, h; - getScreenShape(x, y, w, h); - mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, - (DWORD)((65535.99 * (m_xCenter - x)) / (w - 1)), - (DWORD)((65535.99 * (m_yCenter - y)) / (h - 1)), - 0, - 0x12345678); -// FIXME -- ignore mouse until we get warp notification? -} - -void -CMSWindowsPrimaryScreen::checkClipboard() -{ - // if we think we own the clipboard but we don't then somebody - // grabbed the clipboard on this screen without us knowing. - // tell the server that this screen grabbed the clipboard. - // - // this works around bugs in the clipboard viewer chain. - // sometimes NT will simply never send WM_DRAWCLIPBOARD - // messages for no apparent reason and rebooting fixes the - // problem. since we don't want a broken clipboard until the - // next reboot we do this double check. clipboard ownership - // won't be reflected on other screens until we leave but at - // least the clipboard itself will work. - HWND clipboardOwner = GetClipboardOwner(); - if (m_clipboardOwner != clipboardOwner) { - try { - m_clipboardOwner = clipboardOwner; - if (m_clipboardOwner != m_window) { - m_receiver->onGrabClipboard(kClipboardClipboard); - m_receiver->onGrabClipboard(kClipboardSelection); - } - } - catch (XBadClient&) { - // ignore - } - } -} - void CMSWindowsPrimaryScreen::setClipboard(ClipboardID /*id*/, const IClipboard* src) @@ -408,9 +359,17 @@ CMSWindowsPrimaryScreen::isLockedToScreen() const } bool -CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg) +CMSWindowsPrimaryScreen::onPreTranslate(const CEvent* event) { + assert(event != NULL); + + // forward to superclass + if (CMSWindowsScreen::onPreTranslate(event)) { + return true; + } + // handle event + const MSG* msg = &event->m_msg; switch (msg->message) { case SYNERGY_MSG_MARK: m_markReceived = msg->wParam; @@ -567,43 +526,47 @@ CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg) return false; } -LRESULT -CMSWindowsPrimaryScreen::onEvent(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) +bool +CMSWindowsPrimaryScreen::onEvent(CEvent* event) { - switch (msg) { + assert(event != NULL); + + const MSG& msg = event->msg; + switch (msg.message) { case WM_QUERYENDSESSION: if (m_is95Family) { - return TRUE; + event->m_result = TRUE; + return true; } break; case WM_ENDSESSION: if (m_is95Family) { - if (wParam == TRUE && lParam == 0) { + if (msg.wParam == TRUE && msg.lParam == 0) { stop(); } - return 0; + return true; } break; case WM_PAINT: - ValidateRect(hwnd, NULL); - return 0; + ValidateRect(msg.hwnd, NULL); + return true; case WM_DRAWCLIPBOARD: log((CLOG_DEBUG "clipboard was taken")); // first pass it on if (m_nextClipboardWindow != NULL) { - SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + SendMessage(m_nextClipboardWindow, + msg.message, msg.wParam, msg.lParam); } // now notify server that somebody changed the clipboard. // skip that if we're the new owner. try { m_clipboardOwner = GetClipboardOwner(); - if (m_clipboardOwner != m_window) { + if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) { m_receiver->onGrabClipboard(kClipboardClipboard); m_receiver->onGrabClipboard(kClipboardSelection); } @@ -612,28 +575,36 @@ CMSWindowsPrimaryScreen::onEvent(HWND hwnd, UINT msg, // ignore. this can happen if we receive this event // before we've fully started up. } - return 0; + return true; case WM_CHANGECBCHAIN: - if (m_nextClipboardWindow == (HWND)wParam) { - m_nextClipboardWindow = (HWND)lParam; + if (m_nextClipboardWindow == (HWND)msg.wParam) { + m_nextClipboardWindow = (HWND)msg.lParam; } else if (m_nextClipboardWindow != NULL) { - SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + SendMessage(m_nextClipboardWindow, + msg.message, msg.wParam, msg.lParam); } - return 0; + return true; case WM_DISPLAYCHANGE: { - // screen resolution may have changed + // screen resolution may have changed. get old shape. SInt32 xOld, yOld, wOld, hOld; getScreenShape(xOld, yOld, wOld, hOld); + + // update shape updateScreenShape(); - SInt32 x, y, w, h; - getScreenShape(x, y, w, h); + + // collect new screen info + CClientInfo info; + getScreenShape(info.m_x, info.m_y, info.m_w, info.m_h); + getCursorPos(info.m_mx, info.m_my); + info.m_zoneSize = getJumpZoneSize(); // do nothing if resolution hasn't changed - if (x != xOld || y != yOld || w != wOld || h != hOld) { + if (info.m_x != xOld || info.m_y != yOld || + info.m_w != wOld || info.m_h != hOld) { // recompute center pixel of primary screen getCursorCenter(m_xCenter, m_yCenter); @@ -644,28 +615,46 @@ CMSWindowsPrimaryScreen::onEvent(HWND hwnd, UINT msg, // tell hook about resize if not active else { - m_setZone(x, y, w, h, getJumpZoneSize()); + m_setZone(info.m_x, info.m_y, + info.m_w, info.m_h, info.m_zoneSize); } // send new screen info - POINT pos; - GetCursorPos(&pos); - CClientInfo info; - info.m_x = x; - info.m_y = y; - info.m_w = w; - info.m_h = h; - info.m_zoneSize = getJumpZoneSize(); - info.m_mx = pos.x; - info.m_my = pos.y; m_receiver->onInfoChanged(info); } - return 0; + return true; } } - return DefWindowProc(hwnd, msg, wParam, lParam); + return false; +} + +CString +CMSWindowsPrimaryScreen::getCurrentDesktopName() const +{ + return m_deskName; +} + +SInt32 +CMSWindowsPrimaryScreen::getJumpZoneSize() const +{ + return 1; +} + +void +CMSWindowsPrimaryScreen::warpCursorToCenter() +{ + // warp to center. the extra info tells the hook DLL to send + // SYNERGY_MSG_POST_WARP instead of SYNERGY_MSG_MOUSE_MOVE. + SInt32 x, y, w, h; + getScreenShape(x, y, w, h); + mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, + (DWORD)((65535.99 * (m_xCenter - x)) / (w - 1)), + (DWORD)((65535.99 * (m_yCenter - y)) / (h - 1)), + 0, + 0x12345678); +// FIXME -- ignore mouse until we get warp notification? } void @@ -743,20 +732,33 @@ CMSWindowsPrimaryScreen::hideWindow() ShowWindow(m_window, SW_HIDE); } -SInt32 -CMSWindowsPrimaryScreen::getJumpZoneSize() const -{ - return 1; -} - void -CMSWindowsPrimaryScreen::nextMark() +CMSWindowsPrimaryScreen::checkClipboard() { - // next mark - ++m_mark; - - // mark point in message queue where the mark was changed - PostThreadMessage(m_threadID, SYNERGY_MSG_MARK, m_mark, 0); + // if we think we own the clipboard but we don't then somebody + // grabbed the clipboard on this screen without us knowing. + // tell the server that this screen grabbed the clipboard. + // + // this works around bugs in the clipboard viewer chain. + // sometimes NT will simply never send WM_DRAWCLIPBOARD + // messages for no apparent reason and rebooting fixes the + // problem. since we don't want a broken clipboard until the + // next reboot we do this double check. clipboard ownership + // won't be reflected on other screens until we leave but at + // least the clipboard itself will work. + HWND clipboardOwner = GetClipboardOwner(); + if (m_clipboardOwner != clipboardOwner) { + try { + m_clipboardOwner = clipboardOwner; + if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) { + m_receiver->onGrabClipboard(kClipboardClipboard); + m_receiver->onGrabClipboard(kClipboardSelection); + } + } + catch (XBadClient&) { + // ignore + } + } } void @@ -773,11 +775,23 @@ CMSWindowsPrimaryScreen::createWindow() throw XScreenOpenFailure(); } } + + // poll input desktop to see if it changes (preTranslateMessage() + // handles WM_TIMER) + m_timer = 0; + if (!m_is95Family) { + m_timer = SetTimer(NULL, 0, 200, NULL); + } } void CMSWindowsPrimaryScreen::destroyWindow() { + // remove timer + if (m_timer != 0) { + KillTimer(NULL, m_timer); + } + // disconnect from desktop if (m_is95Family) { closeDesktop(); @@ -977,10 +991,14 @@ CMSWindowsPrimaryScreen::switchDesktop(HDESK desk) return true; } -CString -CMSWindowsPrimaryScreen::getCurrentDesktopName() const +void +CMSWindowsPrimaryScreen::nextMark() { - return m_deskName; + // next mark + ++m_mark; + + // mark point in message queue where the mark was changed + PostThreadMessage(m_threadID, SYNERGY_MSG_MARK, m_mark, 0); } static const KeyID g_virtualKey[] = diff --git a/server/CMSWindowsPrimaryScreen.h b/server/CMSWindowsPrimaryScreen.h index 8b083fd5..7856dccb 100644 --- a/server/CMSWindowsPrimaryScreen.h +++ b/server/CMSWindowsPrimaryScreen.h @@ -34,30 +34,25 @@ public: protected: // CMSWindowsScreen overrides - virtual bool onPreTranslate(MSG*); - virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM); - virtual void onOpenDisplay(); - virtual void onCloseDisplay(); + virtual bool onPreTranslate(const CEvent* event); + virtual bool onEvent(CEvent* event); virtual CString getCurrentDesktopName() const; private: - void enterNoWarp(); - bool showWindow(); - void hideWindow(); - SInt32 getJumpZoneSize() const; // warp mouse to center of primary display (used when computing // motion deltas while mouse is on secondary screen). void warpCursorToCenter(); + void enterNoWarp(); + bool showWindow(); + void hideWindow(); + // check clipboard ownership and, if necessary, tell the receiver // of a grab. void checkClipboard(); - // discard posted messages - void nextMark(); - // create/destroy window // also attach to desktop; this destroys and recreates the window // as necessary. @@ -75,6 +70,9 @@ private: // make desk the thread desktop (for windows NT/2000/XP) bool switchDesktop(HDESK desk); + // discard posted messages + void nextMark(); + // key and button queries KeyID mapKey(WPARAM keycode, LPARAM info, KeyModifierMask* maskOut); @@ -92,6 +90,9 @@ private: // the main loop's thread id DWORD m_threadID; + // the timer used to check for desktop switching + UINT m_timer; + // the current desk and it's name HDESK m_desk; CString m_deskName; diff --git a/server/CXWindowsPrimaryScreen.cpp b/server/CXWindowsPrimaryScreen.cpp index fd490586..a4cdd4b1 100644 --- a/server/CXWindowsPrimaryScreen.cpp +++ b/server/CXWindowsPrimaryScreen.cpp @@ -42,199 +42,25 @@ CXWindowsPrimaryScreen::~CXWindowsPrimaryScreen() void CXWindowsPrimaryScreen::run() { - for (;;) { - // wait for and get the next event - XEvent xevent; - if (!getEvent(&xevent)) { - break; - } + // change our priority + CThread::getCurrentThread().setPriority(-3); - // handle event - switch (xevent.type) { - case CreateNotify: - { - // select events on new window - CDisplayLock display(this); - selectEvents(display, xevent.xcreatewindow.window); - } - break; - - case MappingNotify: - { - // keyboard mapping changed - CDisplayLock display(this); - XRefreshKeyboardMapping(&xevent.xmapping); - updateModifierMap(display); - } - break; - - case ClientMessage: - if (xevent.xclient.message_type == m_atomScreenSaver || - xevent.xclient.format == 32) { - // screen saver activation/deactivation event - m_primaryReceiver->onScreenSaver(xevent.xclient.data.l[0] != 0); - } - break; - - case KeyPress: - { - log((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); - const KeyModifierMask mask = mapModifier(xevent.xkey.state); - const KeyID key = mapKey(&xevent.xkey); - if (key != kKeyNone) { - m_primaryReceiver->onKeyDown(key, mask); - if (key == XK_Caps_Lock && m_capsLockHalfDuplex) { - m_primaryReceiver->onKeyUp(key, mask | KeyModifierCapsLock); - } - else if (key == XK_Num_Lock && m_numLockHalfDuplex) { - m_primaryReceiver->onKeyUp(key, mask | KeyModifierNumLock); - } - } - } - break; - - case KeyRelease: - { - const KeyModifierMask mask = mapModifier(xevent.xkey.state); - const KeyID key = mapKey(&xevent.xkey); - if (key != kKeyNone) { - // check if this is a key repeat by getting the next - // KeyPress event that has the same key and time as - // this release event, if any. first prepare the - // filter info. - CKeyEventInfo filter; - filter.m_event = KeyPress; - filter.m_window = xevent.xkey.window; - filter.m_time = xevent.xkey.time; - filter.m_keycode = xevent.xkey.keycode; - - // now check for event - XEvent xevent2; - CDisplayLock display(this); - if (XCheckIfEvent(display, &xevent2, - &CXWindowsPrimaryScreen::findKeyEvent, - (XPointer)&filter) != True) { - // no press event follows so it's a plain release - log((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); - if (key == XK_Caps_Lock && m_capsLockHalfDuplex) { - m_primaryReceiver->onKeyDown(key, mask); - } - else if (key == XK_Num_Lock && m_numLockHalfDuplex) { - m_primaryReceiver->onKeyDown(key, mask); - } - m_primaryReceiver->onKeyUp(key, mask); - } - else { - // found a press event following so it's a repeat. - // we could attempt to count the already queued - // repeats but we'll just send a repeat of 1. - // note that we discard the press event. - log((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); - m_primaryReceiver->onKeyRepeat(key, mask, 1); - } - } - } - break; - - case ButtonPress: - { - log((CLOG_DEBUG1 "event: ButtonPress button=%d", xevent.xbutton.button)); - const ButtonID button = mapButton(xevent.xbutton.button); - if (button != kButtonNone) { - m_primaryReceiver->onMouseDown(button); - } - } - break; - - case ButtonRelease: - { - log((CLOG_DEBUG1 "event: ButtonRelease button=%d", xevent.xbutton.button)); - const ButtonID button = mapButton(xevent.xbutton.button); - if (button != kButtonNone) { - m_primaryReceiver->onMouseUp(button); - } - else if (xevent.xbutton.button == 4) { - // wheel forward (away from user) - m_primaryReceiver->onMouseWheel(120); - } - else if (xevent.xbutton.button == 5) { - // wheel backward (toward user) - m_primaryReceiver->onMouseWheel(-120); - } - } - break; - - case MotionNotify: - { - log((CLOG_DEBUG2 "event: MotionNotify %d,%d", xevent.xmotion.x_root, xevent.xmotion.y_root)); - - // compute motion delta (relative to the last known - // mouse position) - SInt32 x = xevent.xmotion.x_root - m_x; - SInt32 y = xevent.xmotion.y_root - m_y; - - // save position to compute delta of next motion - m_x = xevent.xmotion.x_root; - m_y = xevent.xmotion.y_root; - - if (xevent.xmotion.send_event) { - // we warped the mouse. discard events until we - // find the matching sent event. see - // warpCursorNoFlush() for where the events are - // sent. we discard the matching sent event and - // can be sure we've skipped the warp event. - CDisplayLock display(this); - do { - XMaskEvent(display, PointerMotionMask, &xevent); - } while (!xevent.xmotion.send_event); - } - else if (!m_active) { - // motion on primary screen - m_primaryReceiver->onMouseMovePrimary(m_x, m_y); - } - else { - // motion on secondary screen. warp mouse back to - // center. - // - // my lombard (powerbook g3) running linux and - // using the adbmouse driver has two problems: - // first, the driver only sends motions of +/-2 - // pixels and, second, it seems to discard some - // physical input after a warp. the former isn't a - // big deal (we're just limited to every other - // pixel) but the latter is a PITA. to work around - // it we only warp when the mouse has moved more - // than s_size pixels from the center. - static const SInt32 s_size = 32; - if (xevent.xmotion.x_root - m_xCenter < -s_size || - xevent.xmotion.x_root - m_xCenter > s_size || - xevent.xmotion.y_root - m_yCenter < -s_size || - xevent.xmotion.y_root - m_yCenter > s_size) { - CDisplayLock display(this); - warpCursorNoFlush(display, m_xCenter, m_yCenter); - } - - // send event if mouse moved. do this after warping - // back to center in case the motion takes us onto - // the primary screen. if we sent the event first - // in that case then the warp would happen after - // warping to the primary screen's enter position, - // effectively overriding it. - if (x != 0 || y != 0) { - m_primaryReceiver->onMouseMoveSecondary(x, y); - } - } - } - break; - } + // run event loop + try { + log((CLOG_INFO "entering event loop")); + mainLoop(); + log((CLOG_INFO "exiting event loop")); + } + catch (...) { + log((CLOG_INFO "exiting event loop")); + throw; } } void CXWindowsPrimaryScreen::stop() { - CDisplayLock display(this); - doStop(); + exitMainLoop(); } void @@ -387,196 +213,6 @@ CXWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y) m_y = y; } -void -CXWindowsPrimaryScreen::warpCursorToCenter() -{ - warpCursor(m_xCenter, m_yCenter); -} - -void -CXWindowsPrimaryScreen::warpCursorNoFlush( - Display* display, SInt32 x, SInt32 y) -{ - assert(display != NULL); - assert(m_window != None); - - // send an event that we can recognize before the mouse warp - XEvent eventBefore; - eventBefore.type = MotionNotify; - eventBefore.xmotion.display = display; - eventBefore.xmotion.window = m_window; - eventBefore.xmotion.root = getRoot(); - eventBefore.xmotion.subwindow = m_window; - eventBefore.xmotion.time = CurrentTime; - eventBefore.xmotion.x = x; - eventBefore.xmotion.y = y; - eventBefore.xmotion.x_root = x; - eventBefore.xmotion.y_root = y; - eventBefore.xmotion.state = 0; - eventBefore.xmotion.is_hint = False; - eventBefore.xmotion.same_screen = True; - XEvent eventAfter = eventBefore; - XSendEvent(display, m_window, False, 0, &eventBefore); - - // warp mouse - XWarpPointer(display, None, getRoot(), 0, 0, 0, 0, x, y); - - // send an event that we can recognize after the mouse warp - XSendEvent(display, m_window, False, 0, &eventAfter); - XSync(display, False); - - log((CLOG_DEBUG2 "warped to %d,%d", x, y)); -} - -void -CXWindowsPrimaryScreen::checkClipboard() -{ - // do nothing, we're always up to date -} - -void -CXWindowsPrimaryScreen::enterNoWarp() -{ - m_active = false; - hideWindow(); -} - -bool -CXWindowsPrimaryScreen::showWindow() -{ - CDisplayLock display(this); - - // raise and show the input window - XMapRaised(display, m_window); - - // grab the mouse and keyboard. keep trying until we get them. - // if we can't grab one after grabbing the other then ungrab - // and wait before retrying. give up after s_timeout seconds. - static const double s_timeout = 1.0; - int result; - CStopwatch timer; - do { - // keyboard first - do { - result = XGrabKeyboard(display, m_window, True, - GrabModeAsync, GrabModeAsync, CurrentTime); - assert(result != GrabNotViewable); - if (result != GrabSuccess) { - log((CLOG_DEBUG2 "waiting to grab keyboard")); - CThread::sleep(0.05); - if (timer.getTime() >= s_timeout) { - log((CLOG_DEBUG2 "grab keyboard timed out")); - XUnmapWindow(display, m_window); - return false; - } - } - } while (result != GrabSuccess); - log((CLOG_DEBUG2 "grabbed keyboard")); - - // now the mouse - result = XGrabPointer(display, m_window, True, 0, - GrabModeAsync, GrabModeAsync, - m_window, None, CurrentTime); - assert(result != GrabNotViewable); - if (result != GrabSuccess) { - // back off to avoid grab deadlock - XUngrabKeyboard(display, CurrentTime); - log((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer")); - CThread::sleep(0.05); - if (timer.getTime() >= s_timeout) { - log((CLOG_DEBUG2 "grab pointer timed out")); - XUnmapWindow(display, m_window); - return false; - } - } - } while (result != GrabSuccess); - log((CLOG_DEBUG1 "grabbed pointer and keyboard")); - - return true; -} - -void -CXWindowsPrimaryScreen::hideWindow() -{ - CDisplayLock display(this); - - // unmap the grab window. this also ungrabs the mouse and keyboard. - XUnmapWindow(display, m_window); -} - -SInt32 -CXWindowsPrimaryScreen::getJumpZoneSize() const -{ - return 1; -} - -void -CXWindowsPrimaryScreen::createWindow() -{ - assert(m_window == None); - - // get size of screen - SInt32 x, y, w, h; - getScreenShape(x, y, w, h); - - // grab window attributes. this window is used to capture user - // input when the user is focused on another client. don't let - // the window manager mess with it. - XSetWindowAttributes attr; - attr.event_mask = PointerMotionMask | - ButtonPressMask | ButtonReleaseMask | - KeyPressMask | KeyReleaseMask | - KeymapStateMask | PropertyChangeMask; - attr.do_not_propagate_mask = 0; - attr.override_redirect = True; - attr.cursor = getBlankCursor(); - - // create the grab window - CDisplayLock display(this); - m_window = XCreateWindow(display, getRoot(), - x, y, w, h, 0, 0, - InputOnly, CopyFromParent, - CWDontPropagate | CWEventMask | - CWOverrideRedirect | CWCursor, - &attr); - if (m_window == None) { - throw XScreenOpenFailure(); - } - log((CLOG_DEBUG "window is 0x%08x", m_window)); - - // start watching for events on other windows - selectEvents(display, getRoot()); -} - -void -CXWindowsPrimaryScreen::destroyWindow() -{ - // display can be NULL if the server unexpectedly disconnected - CDisplayLock display(this); - if (display != NULL && m_window != None) { - XDestroyWindow(display, m_window); - } - m_window = None; -} - -void -CXWindowsPrimaryScreen::installScreenSaver() -{ - assert(getScreenSaver() != NULL); - - getScreenSaver()->setNotify(m_window); -} - -void -CXWindowsPrimaryScreen::uninstallScreenSaver() -{ - // stop being notified of screen saver activation/deactivation - if (getScreenSaver() != NULL) { - getScreenSaver()->setNotify(None); - } - m_atomScreenSaver = None; -} - void CXWindowsPrimaryScreen::setClipboard(ClipboardID id, const IClipboard* clipboard) @@ -659,6 +295,202 @@ CXWindowsPrimaryScreen::isLockedToScreen() const return false; } +bool +CXWindowsPrimaryScreen::onPreDispatch(const CEvent* event) +{ + // forward to superclass + return CXWindowsScreen::onPreDispatch(event); +} + +bool +CXWindowsPrimaryScreen::onEvent(CEvent* event) +{ + assert(event != NULL); + XEvent& xevent = event->m_event; + + // handle event + switch (xevent.type) { + case CreateNotify: + { + // select events on new window + CDisplayLock display(this); + selectEvents(display, xevent.xcreatewindow.window); + } + return true; + + case MappingNotify: + { + // keyboard mapping changed + CDisplayLock display(this); + XRefreshKeyboardMapping(&xevent.xmapping); + updateModifierMap(display); + } + return true; + + case ClientMessage: + if (xevent.xclient.message_type == m_atomScreenSaver || + xevent.xclient.format == 32) { + // screen saver activation/deactivation event + m_primaryReceiver->onScreenSaver(xevent.xclient.data.l[0] != 0); + return true; + } + break; + + case KeyPress: + { + log((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); + const KeyModifierMask mask = mapModifier(xevent.xkey.state); + const KeyID key = mapKey(&xevent.xkey); + if (key != kKeyNone) { + m_primaryReceiver->onKeyDown(key, mask); + if (key == XK_Caps_Lock && m_capsLockHalfDuplex) { + m_primaryReceiver->onKeyUp(key, mask | KeyModifierCapsLock); + } + else if (key == XK_Num_Lock && m_numLockHalfDuplex) { + m_primaryReceiver->onKeyUp(key, mask | KeyModifierNumLock); + } + } + } + return true; + + case KeyRelease: + { + const KeyModifierMask mask = mapModifier(xevent.xkey.state); + const KeyID key = mapKey(&xevent.xkey); + if (key != kKeyNone) { + // check if this is a key repeat by getting the next + // KeyPress event that has the same key and time as + // this release event, if any. first prepare the + // filter info. + CKeyEventInfo filter; + filter.m_event = KeyPress; + filter.m_window = xevent.xkey.window; + filter.m_time = xevent.xkey.time; + filter.m_keycode = xevent.xkey.keycode; + + // now check for event + XEvent xevent2; + CDisplayLock display(this); + if (XCheckIfEvent(display, &xevent2, + &CXWindowsPrimaryScreen::findKeyEvent, + (XPointer)&filter) != True) { + // no press event follows so it's a plain release + log((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); + if (key == XK_Caps_Lock && m_capsLockHalfDuplex) { + m_primaryReceiver->onKeyDown(key, mask); + } + else if (key == XK_Num_Lock && m_numLockHalfDuplex) { + m_primaryReceiver->onKeyDown(key, mask); + } + m_primaryReceiver->onKeyUp(key, mask); + } + else { + // found a press event following so it's a repeat. + // we could attempt to count the already queued + // repeats but we'll just send a repeat of 1. + // note that we discard the press event. + log((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); + m_primaryReceiver->onKeyRepeat(key, mask, 1); + } + } + } + return true; + + case ButtonPress: + { + log((CLOG_DEBUG1 "event: ButtonPress button=%d", xevent.xbutton.button)); + const ButtonID button = mapButton(xevent.xbutton.button); + if (button != kButtonNone) { + m_primaryReceiver->onMouseDown(button); + } + } + return true; + + case ButtonRelease: + { + log((CLOG_DEBUG1 "event: ButtonRelease button=%d", xevent.xbutton.button)); + const ButtonID button = mapButton(xevent.xbutton.button); + if (button != kButtonNone) { + m_primaryReceiver->onMouseUp(button); + } + else if (xevent.xbutton.button == 4) { + // wheel forward (away from user) + m_primaryReceiver->onMouseWheel(120); + } + else if (xevent.xbutton.button == 5) { + // wheel backward (toward user) + m_primaryReceiver->onMouseWheel(-120); + } + } + return true; + + case MotionNotify: + { + log((CLOG_DEBUG2 "event: MotionNotify %d,%d", xevent.xmotion.x_root, xevent.xmotion.y_root)); + + // compute motion delta (relative to the last known + // mouse position) + SInt32 x = xevent.xmotion.x_root - m_x; + SInt32 y = xevent.xmotion.y_root - m_y; + + // save position to compute delta of next motion + m_x = xevent.xmotion.x_root; + m_y = xevent.xmotion.y_root; + + if (xevent.xmotion.send_event) { + // we warped the mouse. discard events until we + // find the matching sent event. see + // warpCursorNoFlush() for where the events are + // sent. we discard the matching sent event and + // can be sure we've skipped the warp event. + CDisplayLock display(this); + do { + XMaskEvent(display, PointerMotionMask, &xevent); + } while (!xevent.xmotion.send_event); + } + else if (!m_active) { + // motion on primary screen + m_primaryReceiver->onMouseMovePrimary(m_x, m_y); + } + else { + // motion on secondary screen. warp mouse back to + // center. + // + // my lombard (powerbook g3) running linux and + // using the adbmouse driver has two problems: + // first, the driver only sends motions of +/-2 + // pixels and, second, it seems to discard some + // physical input after a warp. the former isn't a + // big deal (we're just limited to every other + // pixel) but the latter is a PITA. to work around + // it we only warp when the mouse has moved more + // than s_size pixels from the center. + static const SInt32 s_size = 32; + if (xevent.xmotion.x_root - m_xCenter < -s_size || + xevent.xmotion.x_root - m_xCenter > s_size || + xevent.xmotion.y_root - m_yCenter < -s_size || + xevent.xmotion.y_root - m_yCenter > s_size) { + CDisplayLock display(this); + warpCursorNoFlush(display, m_xCenter, m_yCenter); + } + + // send event if mouse moved. do this after warping + // back to center in case the motion takes us onto + // the primary screen. if we sent the event first + // in that case then the warp would happen after + // warping to the primary screen's enter position, + // effectively overriding it. + if (x != 0 || y != 0) { + m_primaryReceiver->onMouseMoveSecondary(x, y); + } + } + } + return true; + } + + return false; +} + void CXWindowsPrimaryScreen::onUnexpectedClose() { @@ -673,6 +505,196 @@ CXWindowsPrimaryScreen::onLostClipboard(ClipboardID id) m_receiver->onGrabClipboard(id); } +SInt32 +CXWindowsPrimaryScreen::getJumpZoneSize() const +{ + return 1; +} + +void +CXWindowsPrimaryScreen::warpCursorToCenter() +{ + warpCursor(m_xCenter, m_yCenter); +} + +void +CXWindowsPrimaryScreen::warpCursorNoFlush( + Display* display, SInt32 x, SInt32 y) +{ + assert(display != NULL); + assert(m_window != None); + + // send an event that we can recognize before the mouse warp + XEvent eventBefore; + eventBefore.type = MotionNotify; + eventBefore.xmotion.display = display; + eventBefore.xmotion.window = m_window; + eventBefore.xmotion.root = getRoot(); + eventBefore.xmotion.subwindow = m_window; + eventBefore.xmotion.time = CurrentTime; + eventBefore.xmotion.x = x; + eventBefore.xmotion.y = y; + eventBefore.xmotion.x_root = x; + eventBefore.xmotion.y_root = y; + eventBefore.xmotion.state = 0; + eventBefore.xmotion.is_hint = False; + eventBefore.xmotion.same_screen = True; + XEvent eventAfter = eventBefore; + XSendEvent(display, m_window, False, 0, &eventBefore); + + // warp mouse + XWarpPointer(display, None, getRoot(), 0, 0, 0, 0, x, y); + + // send an event that we can recognize after the mouse warp + XSendEvent(display, m_window, False, 0, &eventAfter); + XSync(display, False); + + log((CLOG_DEBUG2 "warped to %d,%d", x, y)); +} + +void +CXWindowsPrimaryScreen::enterNoWarp() +{ + m_active = false; + hideWindow(); +} + +bool +CXWindowsPrimaryScreen::showWindow() +{ + CDisplayLock display(this); + + // raise and show the input window + XMapRaised(display, m_window); + + // grab the mouse and keyboard. keep trying until we get them. + // if we can't grab one after grabbing the other then ungrab + // and wait before retrying. give up after s_timeout seconds. + static const double s_timeout = 1.0; + int result; + CStopwatch timer; + do { + // keyboard first + do { + result = XGrabKeyboard(display, m_window, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + log((CLOG_DEBUG2 "waiting to grab keyboard")); + CThread::sleep(0.05); + if (timer.getTime() >= s_timeout) { + log((CLOG_DEBUG2 "grab keyboard timed out")); + XUnmapWindow(display, m_window); + return false; + } + } + } while (result != GrabSuccess); + log((CLOG_DEBUG2 "grabbed keyboard")); + + // now the mouse + result = XGrabPointer(display, m_window, True, 0, + GrabModeAsync, GrabModeAsync, + m_window, None, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + // back off to avoid grab deadlock + XUngrabKeyboard(display, CurrentTime); + log((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer")); + CThread::sleep(0.05); + if (timer.getTime() >= s_timeout) { + log((CLOG_DEBUG2 "grab pointer timed out")); + XUnmapWindow(display, m_window); + return false; + } + } + } while (result != GrabSuccess); + log((CLOG_DEBUG1 "grabbed pointer and keyboard")); + + return true; +} + +void +CXWindowsPrimaryScreen::hideWindow() +{ + CDisplayLock display(this); + + // unmap the grab window. this also ungrabs the mouse and keyboard. + XUnmapWindow(display, m_window); +} + +void +CXWindowsPrimaryScreen::checkClipboard() +{ + // do nothing, we're always up to date +} + +void +CXWindowsPrimaryScreen::createWindow() +{ + assert(m_window == None); + + // get size of screen + SInt32 x, y, w, h; + getScreenShape(x, y, w, h); + + // grab window attributes. this window is used to capture user + // input when the user is focused on another client. don't let + // the window manager mess with it. + XSetWindowAttributes attr; + attr.event_mask = PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask | PropertyChangeMask; + attr.do_not_propagate_mask = 0; + attr.override_redirect = True; + attr.cursor = getBlankCursor(); + + // create the grab window + CDisplayLock display(this); + m_window = XCreateWindow(display, getRoot(), + x, y, w, h, 0, 0, + InputOnly, CopyFromParent, + CWDontPropagate | CWEventMask | + CWOverrideRedirect | CWCursor, + &attr); + if (m_window == None) { + throw XScreenOpenFailure(); + } + log((CLOG_DEBUG "window is 0x%08x", m_window)); + + // start watching for events on other windows + selectEvents(display, getRoot()); +} + +void +CXWindowsPrimaryScreen::destroyWindow() +{ + // display can be NULL if the server unexpectedly disconnected + CDisplayLock display(this); + if (display != NULL && m_window != None) { + XDestroyWindow(display, m_window); + } + m_window = None; +} + +void +CXWindowsPrimaryScreen::installScreenSaver() +{ + assert(getScreenSaver() != NULL); + + getScreenSaver()->setNotify(m_window); +} + +void +CXWindowsPrimaryScreen::uninstallScreenSaver() +{ + // stop being notified of screen saver activation/deactivation + if (getScreenSaver() != NULL) { + getScreenSaver()->setNotify(None); + } + m_atomScreenSaver = None; +} + void CXWindowsPrimaryScreen::selectEvents(Display* display, Window w) const { diff --git a/server/CXWindowsPrimaryScreen.h b/server/CXWindowsPrimaryScreen.h index b7eb345a..8bf41373 100644 --- a/server/CXWindowsPrimaryScreen.h +++ b/server/CXWindowsPrimaryScreen.h @@ -30,23 +30,22 @@ public: protected: // CXWindowsScreen overrides + virtual bool onPreDispatch(const CEvent* event); + virtual bool onEvent(CEvent* event); virtual void onUnexpectedClose(); virtual void onLostClipboard(ClipboardID); private: - void selectEvents(Display*, Window) const; - void doSelectEvents(Display*, Window) const; - - void enterNoWarp(); - bool showWindow(); - void hideWindow(); - SInt32 getJumpZoneSize() const; void warpCursorToCenter(); void warpCursorNoFlush(Display*, SInt32 xAbsolute, SInt32 yAbsolute); + void enterNoWarp(); + bool showWindow(); + void hideWindow(); + // check clipboard ownership and, if necessary, tell the receiver // of a grab. void checkClipboard(); @@ -59,6 +58,9 @@ private: void installScreenSaver(); void uninstallScreenSaver(); + void selectEvents(Display*, Window) const; + void doSelectEvents(Display*, Window) const; + KeyModifierMask mapModifier(unsigned int state) const; KeyID mapKey(XKeyEvent*) const; ButtonID mapButton(unsigned int button) const;