diff options
Diffstat (limited to 'src/lib/platform/XWindowsScreen.cpp')
| -rw-r--r-- | src/lib/platform/XWindowsScreen.cpp | 2096 |
1 files changed, 2096 insertions, 0 deletions
diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp new file mode 100644 index 0000000..581c911 --- /dev/null +++ b/src/lib/platform/XWindowsScreen.cpp @@ -0,0 +1,2096 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 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/XWindowsScreen.h" + +#include "platform/XWindowsClipboard.h" +#include "platform/XWindowsEventQueueBuffer.h" +#include "platform/XWindowsKeyState.h" +#include "platform/XWindowsScreenSaver.h" +#include "platform/XWindowsUtil.h" +#include "barrier/Clipboard.h" +#include "barrier/KeyMap.h" +#include "barrier/XScreen.h" +#include "arch/XArch.h" +#include "arch/Arch.h" +#include "base/Log.h" +#include "base/Stopwatch.h" +#include "base/String.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +#include <cstring> +#include <cstdlib> +#include <algorithm> +#if X_DISPLAY_MISSING +# error X11 is required to build barrier +#else +# include <X11/X.h> +# include <X11/Xutil.h> +# define XK_MISCELLANY +# define XK_XKB_KEYS +# include <X11/keysymdef.h> +# if HAVE_X11_EXTENSIONS_DPMS_H + extern "C" { +# include <X11/extensions/dpms.h> + } +# endif +# if HAVE_X11_EXTENSIONS_XTEST_H +# include <X11/extensions/XTest.h> +# else +# error The XTest extension is required to build barrier +# endif +# if HAVE_X11_EXTENSIONS_XINERAMA_H + // Xinerama.h may lack extern "C" for inclusion by C++ + extern "C" { +# include <X11/extensions/Xinerama.h> + } +# endif +# if HAVE_X11_EXTENSIONS_XRANDR_H +# include <X11/extensions/Xrandr.h> +# endif +# if HAVE_XKB_EXTENSION +# include <X11/XKBlib.h> +# endif +# ifdef HAVE_XI2 +# include <X11/extensions/XInput2.h> +# endif +#endif + +static int xi_opcode; + +// +// XWindowsScreen +// + +// NOTE -- the X display is shared among several objects but is owned +// by the XWindowsScreen. Xlib is not reentrant so we must ensure +// that no two objects can simultaneously call Xlib with the display. +// this is easy since we only make X11 calls from the main thread. +// we must also ensure that these objects do not use the display in +// their destructors or, if they do, we can tell them not to. This +// is to handle unexpected disconnection of the X display, when any +// call on the display is invalid. In that situation we discard the +// display and the X11 event queue buffer, ignore any calls that try +// to use the display, and wait to be destroyed. + +XWindowsScreen* XWindowsScreen::s_screen = NULL; + +XWindowsScreen::XWindowsScreen( + const char* displayName, + bool isPrimary, + bool disableXInitThreads, + int mouseScrollDelta, + IEventQueue* events) : + m_isPrimary(isPrimary), + m_mouseScrollDelta(mouseScrollDelta), + m_display(NULL), + m_root(None), + m_window(None), + m_isOnScreen(m_isPrimary), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_xCursor(0), m_yCursor(0), + m_keyState(NULL), + m_lastFocus(None), + m_lastFocusRevert(RevertToNone), + m_im(NULL), + m_ic(NULL), + m_lastKeycode(0), + m_sequenceNumber(0), + m_screensaver(NULL), + m_screensaverNotify(false), + m_xtestIsXineramaUnaware(true), + m_preserveFocus(false), + m_xkb(false), + m_xi2detected(false), + m_xrandr(false), + m_events(events), + PlatformScreen(events) +{ + assert(s_screen == NULL); + + if (mouseScrollDelta==0) m_mouseScrollDelta=120; + s_screen = this; + + if (!disableXInitThreads) { + // initializes Xlib support for concurrent threads. + if (XInitThreads() == 0) + throw XArch("XInitThreads() returned zero"); + } else { + LOG((CLOG_DEBUG "skipping XInitThreads()")); + } + + // set the X I/O error handler so we catch the display disconnecting + XSetIOErrorHandler(&XWindowsScreen::ioErrorHandler); + + try { + m_display = openDisplay(displayName); + m_root = DefaultRootWindow(m_display); + saveShape(); + m_window = openWindow(); + m_screensaver = new XWindowsScreenSaver(m_display, + m_window, getEventTarget(), events); + m_keyState = new XWindowsKeyState(m_display, m_xkb, events, m_keyMap); + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_xinerama ? "(xinerama)" : "")); + LOG((CLOG_DEBUG "window is 0x%08x", m_window)); + } + catch (...) { + if (m_display != NULL) { + XCloseDisplay(m_display); + } + throw; + } + + // primary/secondary screen only initialization + if (m_isPrimary) { +#ifdef HAVE_XI2 + m_xi2detected = detectXI2(); + if (m_xi2detected) { + selectXIRawMotion(); + } else +#endif + { + // start watching for events on other windows + selectEvents(m_root); + } + + // prepare to use input methods + openIM(); + } + else { + // become impervious to server grabs + XTestGrabControl(m_display, True); + } + + // initialize the clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_clipboard[id] = new XWindowsClipboard(m_display, m_window, id); + } + + // install event handlers + m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(), + new TMethodEventJob<XWindowsScreen>(this, + &XWindowsScreen::handleSystemEvent)); + + // install the platform event queue + m_events->adoptBuffer(new XWindowsEventQueueBuffer( + m_display, m_window, m_events)); +} + +XWindowsScreen::~XWindowsScreen() +{ + assert(s_screen != NULL); + assert(m_display != NULL); + + m_events->adoptBuffer(NULL); + m_events->removeHandler(Event::kSystem, m_events->getSystemTarget()); + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + delete m_clipboard[id]; + } + delete m_keyState; + delete m_screensaver; + m_keyState = NULL; + m_screensaver = NULL; + if (m_display != NULL) { + // FIXME -- is it safe to clean up the IC and IM without a display? + if (m_ic != NULL) { + XDestroyIC(m_ic); + } + if (m_im != NULL) { + XCloseIM(m_im); + } + XDestroyWindow(m_display, m_window); + XCloseDisplay(m_display); + } + XSetIOErrorHandler(NULL); + + s_screen = NULL; +} + +void +XWindowsScreen::enable() +{ + if (!m_isPrimary) { + // get the keyboard control state + XKeyboardState keyControl; + XGetKeyboardControl(m_display, &keyControl); + m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn); + m_keyState->setAutoRepeat(keyControl); + + // move hider window under the cursor center + XMoveWindow(m_display, m_window, m_xCenter, m_yCenter); + + // raise and show the window + // FIXME -- take focus? + XMapRaised(m_display, m_window); + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); + } +} + +void +XWindowsScreen::disable() +{ + // release input context focus + if (m_ic != NULL) { + XUnsetICFocus(m_ic); + } + + // unmap the hider/grab window. this also ungrabs the mouse and + // keyboard if they're grabbed. + XUnmapWindow(m_display, m_window); + + // restore auto-repeat state + if (!m_isPrimary && m_autoRepeat) { + //XAutoRepeatOn(m_display); + } +} + +void +XWindowsScreen::enter() +{ + screensaver(false); + + // release input context focus + if (m_ic != NULL) { + XUnsetICFocus(m_ic); + } + + // set the input focus to what it had been when we took it + if (m_lastFocus != None) { + // the window may not exist anymore so ignore errors + XWindowsUtil::ErrorLock lock(m_display); + XSetInputFocus(m_display, m_lastFocus, m_lastFocusRevert, CurrentTime); + } + + #if HAVE_X11_EXTENSIONS_DPMS_H + // Force the DPMS to turn screen back on since we don't + // actually cause physical hardware input to trigger it + int dummy; + CARD16 powerlevel; + BOOL enabled; + if (DPMSQueryExtension(m_display, &dummy, &dummy) && + DPMSCapable(m_display) && + DPMSInfo(m_display, &powerlevel, &enabled)) + { + if (enabled && powerlevel != DPMSModeOn) + DPMSForceLevel(m_display, DPMSModeOn); + } + #endif + + // unmap the hider/grab window. this also ungrabs the mouse and + // keyboard if they're grabbed. + XUnmapWindow(m_display, m_window); + +/* maybe call this if entering for the screensaver + // set keyboard focus to root window. the screensaver should then + // pick up key events for when the user enters a password to unlock. + XSetInputFocus(m_display, PointerRoot, PointerRoot, CurrentTime); +*/ + + if (!m_isPrimary) { + // get the keyboard control state + XKeyboardState keyControl; + XGetKeyboardControl(m_display, &keyControl); + m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn); + m_keyState->setAutoRepeat(keyControl); + + // turn off auto-repeat. we do this so fake key press events don't + // cause the local server to generate their own auto-repeats of + // those keys. + //XAutoRepeatOff(m_display); + } + + // now on screen + m_isOnScreen = true; +} + +bool +XWindowsScreen::leave() +{ + if (!m_isPrimary) { + // restore the previous keyboard auto-repeat state. if the user + // changed the auto-repeat configuration while on the client then + // that state is lost. that's because we can't get notified by + // the X server when the auto-repeat configuration is changed so + // we can't track the desired configuration. + if (m_autoRepeat) { + //XAutoRepeatOn(m_display); + } + + // move hider window under the cursor center + XMoveWindow(m_display, m_window, m_xCenter, m_yCenter); + } + + // raise and show the window + XMapRaised(m_display, m_window); + + // grab the mouse and keyboard, if primary and possible + if (m_isPrimary && !grabMouseAndKeyboard()) { + XUnmapWindow(m_display, m_window); + return false; + } + + // save current focus + XGetInputFocus(m_display, &m_lastFocus, &m_lastFocusRevert); + + // take focus + if (m_isPrimary || !m_preserveFocus) { + XSetInputFocus(m_display, m_window, RevertToPointerRoot, CurrentTime); + } + + // now warp the mouse. we warp after showing the window so we're + // guaranteed to get the mouse leave event and to prevent the + // keyboard focus from changing under point-to-focus policies. + if (m_isPrimary) { + warpCursor(m_xCenter, m_yCenter); + } + else { + fakeMouseMove(m_xCenter, m_yCenter); + } + + // set input context focus to our window + if (m_ic != NULL) { + XmbResetIC(m_ic); + XSetICFocus(m_ic); + m_filtered.clear(); + } + + // now off screen + m_isOnScreen = false; + + return true; +} + +bool +XWindowsScreen::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; + } + + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = XWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); + + if (clipboard != NULL) { + // save clipboard data + return Clipboard::copy(m_clipboard[id], clipboard, timestamp); + } + else { + // assert clipboard ownership + if (!m_clipboard[id]->open(timestamp)) { + return false; + } + m_clipboard[id]->empty(); + m_clipboard[id]->close(); + return true; + } +} + +void +XWindowsScreen::checkClipboards() +{ + // do nothing, we're always up to date +} + +void +XWindowsScreen::openScreensaver(bool notify) +{ + m_screensaverNotify = notify; + if (!m_screensaverNotify) { + m_screensaver->disable(); + } +} + +void +XWindowsScreen::closeScreensaver() +{ + if (!m_screensaverNotify) { + m_screensaver->enable(); + } +} + +void +XWindowsScreen::screensaver(bool activate) +{ + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +XWindowsScreen::resetOptions() +{ + m_xtestIsXineramaUnaware = true; + m_preserveFocus = false; +} + +void +XWindowsScreen::setOptions(const OptionsList& options) +{ + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionXTestXineramaUnaware) { + m_xtestIsXineramaUnaware = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false")); + } + else if (options[i] == kOptionScreenPreserveFocus) { + m_preserveFocus = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "Preserve Focus = %s", m_preserveFocus ? "true" : "false")); + } + } +} + +void +XWindowsScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +XWindowsScreen::isPrimary() const +{ + return m_isPrimary; +} + +void* +XWindowsScreen::getEventTarget() const +{ + return const_cast<XWindowsScreen*>(this); +} + +bool +XWindowsScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + assert(clipboard != NULL); + + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; + } + + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = XWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); + + // copy the clipboard + return Clipboard::copy(clipboard, m_clipboard[id], timestamp); +} + +void +XWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +XWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + Window root, window; + int mx, my, xWindow, yWindow; + unsigned int mask; + if (XQueryPointer(m_display, m_root, &root, &window, + &mx, &my, &xWindow, &yWindow, &mask)) { + x = mx; + y = my; + } + else { + x = m_xCenter; + y = m_yCenter; + } +} + +void +XWindowsScreen::reconfigure(UInt32) +{ + // do nothing +} + +void +XWindowsScreen::warpCursor(SInt32 x, SInt32 y) +{ + // warp mouse + warpCursorNoFlush(x, y); + + // remove all input events before and including warp + XEvent event; + while (XCheckMaskEvent(m_display, PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask, + &event)) { + // do nothing + } + + // save position as last position + m_xCursor = x; + m_yCursor = y; +} + +UInt32 +XWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // only allow certain modifiers + if ((mask & ~(KeyModifierShift | KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) != 0) { + LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // fail if no keys + if (key == kKeyNone && mask == 0) { + return 0; + } + + // convert to X + unsigned int modifiers; + if (!m_keyState->mapModifiersToX(mask, modifiers)) { + // can't map all modifiers + LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + XWindowsKeyState::KeycodeList keycodes; + m_keyState->mapKeyToKeycodes(key, keycodes); + if (key != kKeyNone && keycodes.empty()) { + // can't map key + LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + id = m_hotKeys.size() + 1; + } + HotKeyList& hotKeys = m_hotKeys[id]; + + // all modifier hotkey must be treated specially. for each modifier + // we need to grab the modifier key in combination with all the other + // requested modifiers. + bool err = false; + { + XWindowsUtil::ErrorLock lock(m_display, &err); + if (key == kKeyNone) { + static const KeyModifierMask s_hotKeyModifiers[] = { + KeyModifierShift, + KeyModifierControl, + KeyModifierAlt, + KeyModifierMeta, + KeyModifierSuper + }; + + XModifierKeymap* modKeymap = XGetModifierMapping(m_display); + for (size_t j = 0; j < sizeof(s_hotKeyModifiers) / + sizeof(s_hotKeyModifiers[0]) && !err; ++j) { + // skip modifier if not in mask + if ((mask & s_hotKeyModifiers[j]) == 0) { + continue; + } + + // skip with error if we can't map remaining modifiers + unsigned int modifiers2; + KeyModifierMask mask2 = (mask & ~s_hotKeyModifiers[j]); + if (!m_keyState->mapModifiersToX(mask2, modifiers2)) { + err = true; + continue; + } + + // compute modifier index for modifier. there should be + // exactly one X modifier missing + int index; + switch (modifiers ^ modifiers2) { + case ShiftMask: + index = ShiftMapIndex; + break; + + case LockMask: + index = LockMapIndex; + break; + + case ControlMask: + index = ControlMapIndex; + break; + + case Mod1Mask: + index = Mod1MapIndex; + break; + + case Mod2Mask: + index = Mod2MapIndex; + break; + + case Mod3Mask: + index = Mod3MapIndex; + break; + + case Mod4Mask: + index = Mod4MapIndex; + break; + + case Mod5Mask: + index = Mod5MapIndex; + break; + + default: + err = true; + continue; + } + + // grab each key for the modifier + const KeyCode* modifiermap = + modKeymap->modifiermap + index * modKeymap->max_keypermod; + for (int k = 0; k < modKeymap->max_keypermod && !err; ++k) { + KeyCode code = modifiermap[k]; + if (modifiermap[k] != 0) { + XGrabKey(m_display, code, modifiers2, m_root, + False, GrabModeAsync, GrabModeAsync); + if (!err) { + hotKeys.push_back(std::make_pair(code, modifiers2)); + m_hotKeyToIDMap[HotKeyItem(code, modifiers2)] = id; + } + } + } + } + XFreeModifiermap(modKeymap); + } + + // a non-modifier key must be insensitive to CapsLock, NumLock and + // ScrollLock, so we have to grab the key with every combination of + // those. + else { + // collect available toggle modifiers + unsigned int modifier; + unsigned int toggleModifiers[3]; + size_t numToggleModifiers = 0; + if (m_keyState->mapModifiersToX(KeyModifierCapsLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + if (m_keyState->mapModifiersToX(KeyModifierNumLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + if (m_keyState->mapModifiersToX(KeyModifierScrollLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + + + for (XWindowsKeyState::KeycodeList::iterator j = keycodes.begin(); + j != keycodes.end() && !err; ++j) { + for (size_t i = 0; i < (1u << numToggleModifiers); ++i) { + // add toggle modifiers for index i + unsigned int tmpModifiers = modifiers; + if ((i & 1) != 0) { + tmpModifiers |= toggleModifiers[0]; + } + if ((i & 2) != 0) { + tmpModifiers |= toggleModifiers[1]; + } + if ((i & 4) != 0) { + tmpModifiers |= toggleModifiers[2]; + } + + // add grab + XGrabKey(m_display, *j, tmpModifiers, m_root, + False, GrabModeAsync, GrabModeAsync); + if (!err) { + hotKeys.push_back(std::make_pair(*j, tmpModifiers)); + m_hotKeyToIDMap[HotKeyItem(*j, tmpModifiers)] = id; + } + } + } + } + } + + if (err) { + // if any failed then unregister any we did get + for (HotKeyList::iterator j = hotKeys.begin(); + j != hotKeys.end(); ++j) { + XUngrabKey(m_display, j->first, j->second, m_root); + m_hotKeyToIDMap.erase(HotKeyItem(j->first, j->second)); + } + + m_oldHotKeyIDs.push_back(id); + m_hotKeys.erase(id); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +XWindowsScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool err = false; + { + XWindowsUtil::ErrorLock lock(m_display, &err); + HotKeyList& hotKeys = i->second; + for (HotKeyList::iterator j = hotKeys.begin(); + j != hotKeys.end(); ++j) { + XUngrabKey(m_display, j->first, j->second, m_root); + m_hotKeyToIDMap.erase(HotKeyItem(j->first, j->second)); + } + } + if (err) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); +} + +void +XWindowsScreen::fakeInputBegin() +{ + // FIXME -- not implemented +} + +void +XWindowsScreen::fakeInputEnd() +{ + // FIXME -- not implemented +} + +SInt32 +XWindowsScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +XWindowsScreen::isAnyMouseButtonDown(UInt32& buttonID) const +{ + // query the pointer to get the button state + Window root, window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int state; + if (XQueryPointer(m_display, m_root, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &state)) { + return ((state & (Button1Mask | Button2Mask | Button3Mask | + Button4Mask | Button5Mask)) != 0); + } + + return false; +} + +void +XWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +void +XWindowsScreen::fakeMouseButton(ButtonID button, bool press) +{ + const unsigned int xButton = mapButtonToX(button); + if (xButton > 0 && xButton < 11) { + XTestFakeButtonEvent(m_display, xButton, + press ? True : False, CurrentTime); + XFlush(m_display); + } +} + +void +XWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) +{ + if (m_xinerama && m_xtestIsXineramaUnaware) { + XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + } + else { + XTestFakeMotionEvent(m_display, DefaultScreen(m_display), + x, y, CurrentTime); + } + XFlush(m_display); +} + +void +XWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // FIXME -- ignore xinerama for now + if (false && m_xinerama && m_xtestIsXineramaUnaware) { +// XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + } + else { + XTestFakeRelativeMotionEvent(m_display, dx, dy, CurrentTime); + } + XFlush(m_display); +} + +void +XWindowsScreen::fakeMouseWheel(SInt32, SInt32 yDelta) const +{ + // XXX -- support x-axis scrolling + if (yDelta == 0) { + return; + } + + // choose button depending on rotation direction + const unsigned int xButton = mapButtonToX(static_cast<ButtonID>( + (yDelta >= 0) ? -1 : -2)); + if (xButton == 0) { + // If we get here, then the XServer does not support the scroll + // wheel buttons, so send PageUp/PageDown keystrokes instead. + // Patch by Tom Chadwick. + KeyCode keycode = 0; + if (yDelta >= 0) { + keycode = XKeysymToKeycode(m_display, XK_Page_Up); + } + else { + keycode = XKeysymToKeycode(m_display, XK_Page_Down); + } + if (keycode != 0) { + XTestFakeKeyEvent(m_display, keycode, True, CurrentTime); + XTestFakeKeyEvent(m_display, keycode, False, CurrentTime); + } + return; + } + + // now use absolute value of delta + if (yDelta < 0) { + yDelta = -yDelta; + } + + if (yDelta < m_mouseScrollDelta) { + LOG((CLOG_WARN "Wheel scroll delta (%d) smaller than threshold (%d)", yDelta, m_mouseScrollDelta)); + } + + // send as many clicks as necessary + for (; yDelta >= m_mouseScrollDelta; yDelta -= m_mouseScrollDelta) { + XTestFakeButtonEvent(m_display, xButton, True, CurrentTime); + XTestFakeButtonEvent(m_display, xButton, False, CurrentTime); + } + XFlush(m_display); +} + +Display* +XWindowsScreen::openDisplay(const char* displayName) +{ + // get the DISPLAY + if (displayName == NULL) { + displayName = getenv("DISPLAY"); + if (displayName == NULL) { + displayName = ":0.0"; + } + } + + // open the display + LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", displayName)); + Display* display = XOpenDisplay(displayName); + if (display == NULL) { + throw XScreenUnavailable(60.0); + } + + // verify the availability of the XTest extension + if (!m_isPrimary) { + int majorOpcode, firstEvent, firstError; + if (!XQueryExtension(display, XTestExtensionName, + &majorOpcode, &firstEvent, &firstError)) { + LOG((CLOG_ERR "XTEST extension not available")); + XCloseDisplay(display); + throw XScreenOpenFailure(); + } + } + +#if HAVE_XKB_EXTENSION + { + m_xkb = false; + int major = XkbMajorVersion, minor = XkbMinorVersion; + if (XkbLibraryVersion(&major, &minor)) { + int opcode, firstError; + if (XkbQueryExtension(display, &opcode, &m_xkbEventBase, + &firstError, &major, &minor)) { + m_xkb = true; + XkbSelectEvents(display, XkbUseCoreKbd, + XkbMapNotifyMask, XkbMapNotifyMask); + XkbSelectEventDetails(display, XkbUseCoreKbd, + XkbStateNotifyMask, + XkbGroupStateMask, XkbGroupStateMask); + } + } + } +#endif + +#if HAVE_X11_EXTENSIONS_XRANDR_H + // query for XRandR extension + int dummyError; + m_xrandr = XRRQueryExtension(display, &m_xrandrEventBase, &dummyError); + if (m_xrandr) { + // enable XRRScreenChangeNotifyEvent + XRRSelectInput(display, DefaultRootWindow(display), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask); + } +#endif + + return display; +} + +void +XWindowsScreen::saveShape() +{ + // get shape of default screen + m_x = 0; + m_y = 0; + + m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display)); + m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display)); + + // get center of default screen + m_xCenter = m_x + (m_w >> 1); + m_yCenter = m_y + (m_h >> 1); + + // check if xinerama is enabled and there is more than one screen. + // get center of first Xinerama screen. Xinerama appears to have + // a bug when XWarpPointer() is used in combination with + // XGrabPointer(). in that case, the warp is successful but the + // next pointer motion warps the pointer again, apparently to + // constrain it to some unknown region, possibly the region from + // 0,0 to Wm,Hm where Wm (Hm) is the minimum width (height) over + // all physical screens. this warp only seems to happen if the + // pointer wasn't in that region before the XWarpPointer(). the + // second (unexpected) warp causes barrier to think the pointer + // has been moved when it hasn't. to work around the problem, + // we warp the pointer to the center of the first physical + // screen instead of the logical screen. + m_xinerama = false; +#if HAVE_X11_EXTENSIONS_XINERAMA_H + int eventBase, errorBase; + if (XineramaQueryExtension(m_display, &eventBase, &errorBase) && + XineramaIsActive(m_display)) { + int numScreens; + XineramaScreenInfo* screens; + screens = XineramaQueryScreens(m_display, &numScreens); + if (screens != NULL) { + if (numScreens > 1) { + m_xinerama = true; + m_xCenter = screens[0].x_org + (screens[0].width >> 1); + m_yCenter = screens[0].y_org + (screens[0].height >> 1); + } + XFree(screens); + } + } +#endif +} + +Window +XWindowsScreen::openWindow() const +{ + // default window attributes. we don't want the window manager + // messing with our window and we don't want the cursor to be + // visible inside the window. + XSetWindowAttributes attr; + attr.do_not_propagate_mask = 0; + attr.override_redirect = True; + attr.cursor = createBlankCursor(); + + // adjust attributes and get size and shape + SInt32 x, y, w, h; + if (m_isPrimary) { + // grab window attributes. this window is used to capture user + // input when the user is focused on another client. it covers + // the whole screen. + attr.event_mask = PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask | PropertyChangeMask; + x = m_x; + y = m_y; + w = m_w; + h = m_h; + } + else { + // 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 mouse is + // moved. we'll reposition the window as necessary so its + // position here doesn't matter. it only needs to be 1x1 because + // it only needs to contain the cursor's hotspot. + attr.event_mask = LeaveWindowMask; + x = 0; + y = 0; + w = 1; + h = 1; + } + + // create and return the window + Window window = XCreateWindow(m_display, m_root, x, y, w, h, 0, 0, + InputOnly, CopyFromParent, + CWDontPropagate | CWEventMask | + CWOverrideRedirect | CWCursor, + &attr); + if (window == None) { + throw XScreenOpenFailure(); + } + return window; +} + +void +XWindowsScreen::openIM() +{ + // open the input methods + XIM im = XOpenIM(m_display, NULL, NULL, NULL); + if (im == NULL) { + LOG((CLOG_INFO "no support for IM")); + return; + } + + // find the appropriate style. barrier supports XIMPreeditNothing + // only at the moment. + XIMStyles* styles; + if (XGetIMValues(im, XNQueryInputStyle, &styles, NULL) != NULL || + styles == NULL) { + LOG((CLOG_WARN "cannot get IM styles")); + XCloseIM(im); + return; + } + XIMStyle style = 0; + for (unsigned short i = 0; i < styles->count_styles; ++i) { + style = styles->supported_styles[i]; + if ((style & XIMPreeditNothing) != 0) { + if ((style & (XIMStatusNothing | XIMStatusNone)) != 0) { + break; + } + } + } + XFree(styles); + if (style == 0) { + LOG((CLOG_INFO "no supported IM styles")); + XCloseIM(im); + return; + } + + // create an input context for the style and tell it about our window + XIC ic = XCreateIC(im, XNInputStyle, style, XNClientWindow, m_window, NULL); + if (ic == NULL) { + LOG((CLOG_WARN "cannot create IC")); + XCloseIM(im); + return; + } + + // find out the events we must select for and do so + unsigned long mask; + if (XGetICValues(ic, XNFilterEvents, &mask, NULL) != NULL) { + LOG((CLOG_WARN "cannot get IC filter events")); + XDestroyIC(ic); + XCloseIM(im); + return; + } + + // we have IM + m_im = im; + m_ic = ic; + m_lastKeycode = 0; + + // select events on our window that IM requires + XWindowAttributes attr; + XGetWindowAttributes(m_display, m_window, &attr); + XSelectInput(m_display, m_window, attr.your_event_mask | mask); +} + +void +XWindowsScreen::sendEvent(Event::Type type, void* data) +{ + m_events->addEvent(Event(type, getEventTarget(), data)); +} + +void +XWindowsScreen::sendClipboardEvent(Event::Type type, ClipboardID id) +{ + ClipboardInfo* info = (ClipboardInfo*)malloc(sizeof(ClipboardInfo)); + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +IKeyState* +XWindowsScreen::getKeyState() const +{ + return m_keyState; +} + +Bool +XWindowsScreen::findKeyEvent(Display*, XEvent* xevent, XPointer arg) +{ + KeyEventFilter* filter = reinterpret_cast<KeyEventFilter*>(arg); + return (xevent->type == filter->m_event && + xevent->xkey.window == filter->m_window && + xevent->xkey.time == filter->m_time && + xevent->xkey.keycode == filter->m_keycode) ? True : False; +} + +void +XWindowsScreen::handleSystemEvent(const Event& event, void*) +{ + XEvent* xevent = static_cast<XEvent*>(event.getData()); + assert(xevent != NULL); + + // update key state + bool isRepeat = false; + if (m_isPrimary) { + if (xevent->type == KeyRelease) { + // 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. + KeyEventFilter filter; + filter.m_event = KeyPress; + filter.m_window = xevent->xkey.window; + filter.m_time = xevent->xkey.time; + filter.m_keycode = xevent->xkey.keycode; + XEvent xevent2; + isRepeat = (XCheckIfEvent(m_display, &xevent2, + &XWindowsScreen::findKeyEvent, + (XPointer)&filter) == True); + } + + if (xevent->type == KeyPress || xevent->type == KeyRelease) { + if (xevent->xkey.window == m_root) { + // this is a hot key + onHotKey(xevent->xkey, isRepeat); + return; + } + else if (!m_isOnScreen) { + // this might be a hot key + if (onHotKey(xevent->xkey, isRepeat)) { + return; + } + } + + bool down = (isRepeat || xevent->type == KeyPress); + KeyModifierMask state = + m_keyState->mapModifiersFromX(xevent->xkey.state); + m_keyState->onKey(xevent->xkey.keycode, down, state); + } + } + + // let input methods try to handle event first + if (m_ic != NULL) { + // XFilterEvent() may eat the event and generate a new KeyPress + // event with a keycode of 0 because there isn't an actual key + // associated with the keysym. but the KeyRelease may pass + // through XFilterEvent() and keep its keycode. this means + // there's a mismatch between KeyPress and KeyRelease keycodes. + // since we use the keycode on the client to detect when a key + // is released this won't do. so we remember the keycode on + // the most recent KeyPress (and clear it on a matching + // KeyRelease) so we have a keycode for a synthesized KeyPress. + if (xevent->type == KeyPress && xevent->xkey.keycode != 0) { + m_lastKeycode = xevent->xkey.keycode; + } + else if (xevent->type == KeyRelease && + xevent->xkey.keycode == m_lastKeycode) { + m_lastKeycode = 0; + } + + // now filter the event + if (XFilterEvent(xevent, DefaultRootWindow(m_display))) { + if (xevent->type == KeyPress) { + // add filtered presses to the filtered list + m_filtered.insert(m_lastKeycode); + } + return; + } + + // discard matching key releases for key presses that were + // filtered and remove them from our filtered list. + else if (xevent->type == KeyRelease && + m_filtered.count(xevent->xkey.keycode) > 0) { + m_filtered.erase(xevent->xkey.keycode); + return; + } + } + + // let screen saver have a go + if (m_screensaver->handleXEvent(xevent)) { + // screen saver handled it + return; + } + +#ifdef HAVE_XI2 + if (m_xi2detected) { + // Process RawMotion + XGenericEventCookie *cookie = (XGenericEventCookie*)&xevent->xcookie; + if (XGetEventData(m_display, cookie) && + cookie->type == GenericEvent && + cookie->extension == xi_opcode) { + if (cookie->evtype == XI_RawMotion) { + // Get current pointer's position + Window root, child; + XMotionEvent xmotion; + xmotion.type = MotionNotify; + xmotion.send_event = False; // Raw motion + xmotion.display = m_display; + xmotion.window = m_window; + /* xmotion's time, state and is_hint are not used */ + unsigned int msk; + xmotion.same_screen = XQueryPointer( + m_display, m_root, &xmotion.root, &xmotion.subwindow, + &xmotion.x_root, + &xmotion.y_root, + &xmotion.x, + &xmotion.y, + &msk); + onMouseMove(xmotion); + XFreeEventData(m_display, cookie); + return; + } + XFreeEventData(m_display, cookie); + } + } +#endif + + // handle the event ourself + switch (xevent->type) { + case CreateNotify: + if (m_isPrimary && !m_xi2detected) { + // select events on new window + selectEvents(xevent->xcreatewindow.window); + } + break; + + case MappingNotify: + refreshKeyboard(xevent); + break; + + case LeaveNotify: + if (!m_isPrimary) { + // mouse moved out of hider window somehow. hide the window. + XUnmapWindow(m_display, m_window); + } + break; + + 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 receiver. + ClipboardID id = getClipboardID(xevent->xselectionclear.selection); + if (id != kClipboardEnd) { + m_clipboard[id]->lost(xevent->xselectionclear.time); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), id); + return; + } + } + 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) { + XDeleteProperty(m_display, + xevent->xselection.requestor, + xevent->xselection.property); + } + break; + + case SelectionRequest: + { + // somebody is asking for clipboard data + ClipboardID id = getClipboardID( + xevent->xselectionrequest.selection); + if (id != kClipboardEnd) { + m_clipboard[id]->addRequest( + xevent->xselectionrequest.owner, + xevent->xselectionrequest.requestor, + xevent->xselectionrequest.target, + xevent->xselectionrequest.time, + xevent->xselectionrequest.property); + return; + } + } + 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); + } + break; + + case DestroyNotify: + // looks like one of the windows that requested a clipboard + // transfer has gone bye-bye. + destroyClipboardRequest(xevent->xdestroywindow.window); + break; + + case KeyPress: + if (m_isPrimary) { + onKeyPress(xevent->xkey); + } + return; + + case KeyRelease: + if (m_isPrimary) { + onKeyRelease(xevent->xkey, isRepeat); + } + return; + + case ButtonPress: + if (m_isPrimary) { + onMousePress(xevent->xbutton); + } + return; + + case ButtonRelease: + if (m_isPrimary) { + onMouseRelease(xevent->xbutton); + } + return; + + case MotionNotify: + if (m_isPrimary) { + onMouseMove(xevent->xmotion); + } + return; + + default: +#if HAVE_XKB_EXTENSION + if (m_xkb && xevent->type == m_xkbEventBase) { + XkbEvent* xkbEvent = reinterpret_cast<XkbEvent*>(xevent); + switch (xkbEvent->any.xkb_type) { + case XkbMapNotify: + refreshKeyboard(xevent); + return; + + case XkbStateNotify: + LOG((CLOG_INFO "group change: %d", xkbEvent->state.group)); + m_keyState->setActiveGroup((SInt32)xkbEvent->state.group); + return; + } + } +#endif + +#if HAVE_X11_EXTENSIONS_XRANDR_H + if (m_xrandr) { + if (xevent->type == m_xrandrEventBase + RRScreenChangeNotify || + (xevent->type == m_xrandrEventBase + RRNotify && + reinterpret_cast<XRRNotifyEvent *>(xevent)->subtype == RRNotify_CrtcChange)) { + LOG((CLOG_INFO "XRRScreenChangeNotifyEvent or RRNotify_CrtcChange received")); + + // we're required to call back into XLib so XLib can update its internal state + XRRUpdateConfiguration(xevent); + + // requery/recalculate the screen shape + saveShape(); + + // we need to resize m_window, otherwise we'll get a weird problem where moving + // off the server onto the client causes the pointer to warp to the + // center of the server (so you can't move the pointer off the server) + if (m_isPrimary) { + XMoveWindow(m_display, m_window, m_x, m_y); + XResizeWindow(m_display, m_window, m_w, m_h); + } + + sendEvent(m_events->forIScreen().shapeChanged()); + } + } +#endif + + break; + } +} + +void +XWindowsScreen::onKeyPress(XKeyEvent& xkey) +{ + LOG((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xkey.keycode, xkey.state)); + const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state); + KeyID key = mapKeyFromX(&xkey); + if (key != kKeyNone) { + // check for ctrl+alt+del emulation + if ((key == kKeyPause || key == kKeyBreak) && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + // pretend it's ctrl+alt+del + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + key = kKeyDelete; + } + + // get which button. see call to XFilterEvent() in onEvent() + // for more info. + bool isFake = false; + KeyButton keycode = static_cast<KeyButton>(xkey.keycode); + if (keycode == 0) { + isFake = true; + keycode = static_cast<KeyButton>(m_lastKeycode); + if (keycode == 0) { + // no keycode + LOG((CLOG_DEBUG1 "event: KeyPress no keycode")); + return; + } + } + + // handle key + m_keyState->sendKeyEvent(getEventTarget(), + true, false, key, mask, 1, keycode); + + // do fake release if this is a fake press + if (isFake) { + m_keyState->sendKeyEvent(getEventTarget(), + false, false, key, mask, 1, keycode); + } + } + else { + LOG((CLOG_DEBUG1 "can't map keycode to key id")); + } +} + +void +XWindowsScreen::onKeyRelease(XKeyEvent& xkey, bool isRepeat) +{ + const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state); + KeyID key = mapKeyFromX(&xkey); + if (key != kKeyNone) { + // check for ctrl+alt+del emulation + if ((key == kKeyPause || key == kKeyBreak) && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + // pretend it's ctrl+alt+del and ignore autorepeat + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + key = kKeyDelete; + isRepeat = false; + } + + KeyButton keycode = static_cast<KeyButton>(xkey.keycode); + if (!isRepeat) { + // no press event follows so it's a plain release + LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", keycode, xkey.state)); + m_keyState->sendKeyEvent(getEventTarget(), + false, false, key, mask, 1, keycode); + } + 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", keycode, xkey.state)); + m_keyState->sendKeyEvent(getEventTarget(), + false, true, key, mask, 1, keycode); + } + } +} + +bool +XWindowsScreen::onHotKey(XKeyEvent& xkey, bool isRepeat) +{ + // find the hot key id + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(HotKeyItem(xkey.keycode, xkey.state)); + if (i == m_hotKeyToIDMap.end()) { + return false; + } + + // find what kind of event + Event::Type type; + if (xkey.type == KeyPress) { + type = m_events->forIPrimaryScreen().hotKeyDown(); + } + else if (xkey.type == KeyRelease) { + type = m_events->forIPrimaryScreen().hotKeyUp(); + } + else { + return false; + } + + // generate event (ignore key repeats) + if (!isRepeat) { + m_events->addEvent(Event(type, getEventTarget(), + HotKeyInfo::alloc(i->second))); + } + return true; +} + +void +XWindowsScreen::onMousePress(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonPress button=%d", xbutton.button)); + ButtonID button = mapButtonFromX(&xbutton); + KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state); + if (button != kButtonNone) { + sendEvent(m_events->forIPrimaryScreen().buttonDown(), ButtonInfo::alloc(button, mask)); + } +} + +void +XWindowsScreen::onMouseRelease(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonRelease button=%d", xbutton.button)); + ButtonID button = mapButtonFromX(&xbutton); + KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state); + if (button != kButtonNone) { + sendEvent(m_events->forIPrimaryScreen().buttonUp(), ButtonInfo::alloc(button, mask)); + } + else if (xbutton.button == 4) { + // wheel forward (away from user) + sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, 120)); + } + else if (xbutton.button == 5) { + // wheel backward (toward user) + sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, -120)); + } + // XXX -- support x-axis scrolling +} + +void +XWindowsScreen::onMouseMove(const XMotionEvent& xmotion) +{ + LOG((CLOG_DEBUG2 "event: MotionNotify %d,%d", xmotion.x_root, xmotion.y_root)); + + // compute motion delta (relative to the last known + // mouse position) + SInt32 x = xmotion.x_root - m_xCursor; + SInt32 y = xmotion.y_root - m_yCursor; + + // save position to compute delta of next motion + m_xCursor = xmotion.x_root; + m_yCursor = xmotion.y_root; + + if (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. + XEvent xevent; + char cntr = 0; + do { + XMaskEvent(m_display, PointerMotionMask, &xevent); + if (cntr++ > 10) { + LOG((CLOG_WARN "too many discarded events! %d", cntr)); + break; + } + } while (!xevent.xany.send_event); + cntr = 0; + } + else if (m_isOnScreen) { + // motion on primary screen + sendEvent(m_events->forIPrimaryScreen().motionOnPrimary(), + MotionInfo::alloc(m_xCursor, m_yCursor)); + } + 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 (xmotion.x_root - m_xCenter < -s_size || + xmotion.x_root - m_xCenter > s_size || + xmotion.y_root - m_yCenter < -s_size || + xmotion.y_root - m_yCenter > s_size) { + warpCursorNoFlush(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) { + sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y)); + } + } +} + +Cursor +XWindowsScreen::createBlankCursor() const +{ + // this seems just a bit more complicated than really necessary + + // get the closet cursor size to 1x1 + unsigned int w = 0, h = 0; + XQueryBestCursor(m_display, m_root, 1, 1, &w, &h); + w = std::max(1u, w); + h = std::max(1u, h); + + // make bitmap data for cursor of closet size. since the cursor + // is blank we can use the same bitmap for shape and mask: all + // zeros. + const int size = ((w + 7) >> 3) * h; + char* data = new char[size]; + memset(data, 0, size); + + // make bitmap + Pixmap bitmap = XCreateBitmapFromData(m_display, m_root, data, w, h); + + // need an arbitrary color for the cursor + XColor color; + color.pixel = 0; + color.red = color.green = color.blue = 0; + color.flags = DoRed | DoGreen | DoBlue; + + // make cursor from bitmap + Cursor cursor = XCreatePixmapCursor(m_display, bitmap, bitmap, + &color, &color, 0, 0); + + // don't need bitmap or the data anymore + delete[] data; + XFreePixmap(m_display, bitmap); + + return cursor; +} + +ClipboardID +XWindowsScreen::getClipboardID(Atom selection) const +{ + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->getSelection() == selection) { + return id; + } + } + return kClipboardEnd; +} + +void +XWindowsScreen::processClipboardRequest(Window requestor, + Time time, Atom property) +{ + // check every clipboard until one returns success + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->processRequest(requestor, time, property)) { + break; + } + } +} + +void +XWindowsScreen::destroyClipboardRequest(Window requestor) +{ + // check every clipboard until one returns success + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->destroyRequest(requestor)) { + break; + } + } +} + +void +XWindowsScreen::onError() +{ + // prevent further access to the X display + m_events->adoptBuffer(NULL); + m_screensaver->destroy(); + m_screensaver = NULL; + m_display = NULL; + + // notify of failure + sendEvent(m_events->forIScreen().error(), NULL); + + // FIXME -- should ensure that we ignore operations that involve + // m_display from now on. however, Xlib will simply exit the + // application in response to the X I/O error so there's no + // point in trying to really handle the error. if we did want + // to handle the error, it'd probably be easiest to delegate to + // one of two objects. one object would take the implementation + // from this class. the other object would be stub methods that + // don't use X11. on error, we'd switch to the latter. +} + +int +XWindowsScreen::ioErrorHandler(Display*) +{ + // the display has disconnected, probably because X is shutting + // down. X forces us to exit at this point which is annoying. + // we'll pretend as if we won't exit so we try to make sure we + // don't access the display anymore. + LOG((CLOG_CRIT "X display has unexpectedly disconnected")); + s_screen->onError(); + return 0; +} + +void +XWindowsScreen::selectEvents(Window w) const +{ + // ignore errors while we adjust event masks. windows could be + // destroyed at any time after the XQueryTree() in doSelectEvents() + // so we must ignore BadWindow errors. + XWindowsUtil::ErrorLock lock(m_display); + + // adjust event masks + doSelectEvents(w); +} + +void +XWindowsScreen::doSelectEvents(Window w) const +{ + // we want to track the mouse everywhere on the display. to achieve + // that we select PointerMotionMask on every window. we also select + // SubstructureNotifyMask in order to get CreateNotify events so we + // select events on new windows too. + + // we don't want to adjust our grab window + if (w == m_window) { + return; + } + + // X11 has a design flaw. If *no* client selected PointerMotionMask for + // a window, motion events will be delivered to that window's parent. + // If *any* client, not necessarily the owner, selects PointerMotionMask + // on such a window, X will stop propagating motion events to its + // parent. This breaks applications that rely on event propagation + // behavior. + // + // Avoid selecting PointerMotionMask unless some other client selected + // it already. + long mask = SubstructureNotifyMask; + XWindowAttributes attr; + XGetWindowAttributes(m_display, w, &attr); + if ((attr.all_event_masks & PointerMotionMask) == PointerMotionMask) { + mask |= PointerMotionMask; + } + + // select events of interest. do this before querying the tree so + // we'll get notifications of children created after the XQueryTree() + // so we won't miss them. + XSelectInput(m_display, w, mask); + + // recurse on child windows + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, w, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + doSelectEvents(cw[i]); + } + XFree(cw); + } +} + +KeyID +XWindowsScreen::mapKeyFromX(XKeyEvent* event) const +{ + // convert to a keysym + KeySym keysym; + if (event->type == KeyPress && m_ic != NULL) { + // do multibyte lookup. can only call XmbLookupString with a + // key press event and a valid XIC so we checked those above. + char scratch[32]; + int n = sizeof(scratch) / sizeof(scratch[0]); + char* buffer = scratch; + int status; + n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status); + if (status == XBufferOverflow) { + // not enough space. grow buffer and try again. + buffer = new char[n]; + n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status); + delete[] buffer; + } + + // see what we got. since we don't care about the string + // we'll just look for a keysym. + switch (status) { + default: + case XLookupNone: + case XLookupChars: + keysym = 0; + break; + + case XLookupKeySym: + case XLookupBoth: + break; + } + } + else { + // plain old lookup + char dummy[1]; + XLookupString(event, dummy, 0, &keysym, NULL); + } + + LOG((CLOG_DEBUG2 "mapped code=%d to keysym=0x%04x", event->keycode, keysym)); + + // convert key + KeyID result = XWindowsUtil::mapKeySymToKeyID(keysym); + LOG((CLOG_DEBUG2 "mapped keysym=0x%04x to keyID=%d", keysym, result)); + return result; +} + +ButtonID +XWindowsScreen::mapButtonFromX(const XButtonEvent* event) const +{ + unsigned int button = event->button; + + // first three buttons map to 1, 2, 3 (kButtonLeft, Middle, Right) + if (button >= 1 && button <= 3) { + return static_cast<ButtonID>(button); + } + + // buttons 4 and 5 are ignored here. they're used for the wheel. + // buttons 6, 7, etc and up map to 4, 5, etc. + else if (button >= 6) { + return static_cast<ButtonID>(button - 2); + } + + // unknown button + else { + return kButtonNone; + } +} + +unsigned int +XWindowsScreen::mapButtonToX(ButtonID id) const +{ + // map button -1 to button 4 (+wheel) + if (id == static_cast<ButtonID>(-1)) { + id = 4; + } + + // map button -2 to button 5 (-wheel) + else if (id == static_cast<ButtonID>(-2)) { + id = 5; + } + + // map buttons 4, 5, etc. to 6, 7, etc. to make room for buttons + // 4 and 5 used to simulate the mouse wheel. + else if (id >= 4) { + id += 2; + } + + // check button is in legal range + if (id < 1 || id > m_buttons.size()) { + // out of range + return 0; + } + + // map button + return static_cast<unsigned int>(id); +} + +void +XWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y) +{ + assert(m_window != None); + + // send an event that we can recognize before the mouse warp + XEvent eventBefore; + eventBefore.type = MotionNotify; + eventBefore.xmotion.display = m_display; + eventBefore.xmotion.window = m_window; + eventBefore.xmotion.root = m_root; + 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 = NotifyNormal; + eventBefore.xmotion.same_screen = True; + XEvent eventAfter = eventBefore; + XSendEvent(m_display, m_window, False, 0, &eventBefore); + + // warp mouse + XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + + // send an event that we can recognize after the mouse warp + XSendEvent(m_display, m_window, False, 0, &eventAfter); + XSync(m_display, False); + + LOG((CLOG_DEBUG2 "warped to %d,%d", x, y)); +} + +void +XWindowsScreen::updateButtons() +{ + // query the button mapping + UInt32 numButtons = XGetPointerMapping(m_display, NULL, 0); + unsigned char* tmpButtons = new unsigned char[numButtons]; + XGetPointerMapping(m_display, tmpButtons, numButtons); + + // find the largest logical button id + unsigned char maxButton = 0; + for (UInt32 i = 0; i < numButtons; ++i) { + if (tmpButtons[i] > maxButton) { + maxButton = tmpButtons[i]; + } + } + + // allocate button array + m_buttons.resize(maxButton); + + // fill in button array values. m_buttons[i] is the physical + // button number for logical button i+1. + for (UInt32 i = 0; i < numButtons; ++i) { + m_buttons[i] = 0; + } + for (UInt32 i = 0; i < numButtons; ++i) { + m_buttons[tmpButtons[i] - 1] = i + 1; + } + + // clean up + delete[] tmpButtons; +} + +bool +XWindowsScreen::grabMouseAndKeyboard() +{ + unsigned int event_mask = ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask; + + // 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; + Stopwatch timer; + do { + // keyboard first + do { + result = XGrabKeyboard(m_display, m_window, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + LOG((CLOG_DEBUG2 "waiting to grab keyboard")); + ARCH->sleep(0.05); + if (timer.getTime() >= s_timeout) { + LOG((CLOG_DEBUG2 "grab keyboard timed out")); + return false; + } + } + } while (result != GrabSuccess); + LOG((CLOG_DEBUG2 "grabbed keyboard")); + + // now the mouse --- use event_mask to get EnterNotify, LeaveNotify events + result = XGrabPointer(m_display, m_window, False, event_mask, + GrabModeAsync, GrabModeAsync, + m_window, None, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + // back off to avoid grab deadlock + XUngrabKeyboard(m_display, CurrentTime); + LOG((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer")); + ARCH->sleep(0.05); + if (timer.getTime() >= s_timeout) { + LOG((CLOG_DEBUG2 "grab pointer timed out")); + return false; + } + } + } while (result != GrabSuccess); + + LOG((CLOG_DEBUG1 "grabbed pointer and keyboard")); + return true; +} + +void +XWindowsScreen::refreshKeyboard(XEvent* event) +{ + if (XPending(m_display) > 0) { + XEvent tmpEvent; + XPeekEvent(m_display, &tmpEvent); + if (tmpEvent.type == MappingNotify) { + // discard this event since another follows. + // we tend to get a bunch of these in a row. + return; + } + } + + // keyboard mapping changed +#if HAVE_XKB_EXTENSION + if (m_xkb && event->type == m_xkbEventBase) { + XkbRefreshKeyboardMapping((XkbMapNotifyEvent*)event); + } + else +#else + { + XRefreshKeyboardMapping(&event->xmapping); + } +#endif + m_keyState->updateKeyMap(); + m_keyState->updateKeyState(); +} + + +// +// XWindowsScreen::HotKeyItem +// + +XWindowsScreen::HotKeyItem::HotKeyItem(int keycode, unsigned int mask) : + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +bool +XWindowsScreen::HotKeyItem::operator<(const HotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} + +bool +XWindowsScreen::detectXI2() +{ + int event, error; + return XQueryExtension(m_display, + "XInputExtension", &xi_opcode, &event, &error); +} + +#ifdef HAVE_XI2 +void +XWindowsScreen::selectXIRawMotion() +{ + XIEventMask mask; + + mask.deviceid = XIAllDevices; + mask.mask_len = XIMaskLen(XI_RawMotion); + mask.mask = (unsigned char*)calloc(mask.mask_len, sizeof(char)); + mask.deviceid = XIAllMasterDevices; + memset(mask.mask, 0, 2); + XISetMask(mask.mask, XI_RawKeyRelease); + XISetMask(mask.mask, XI_RawMotion); + XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1); + free(mask.mask); +} +#endif |
