aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/platform/XWindowsScreen.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/platform/XWindowsScreen.cpp')
-rw-r--r--src/lib/platform/XWindowsScreen.cpp2096
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