diff options
Diffstat (limited to 'qxt-mini/qxtglobalshortcut_x11.cpp')
| -rw-r--r-- | qxt-mini/qxtglobalshortcut_x11.cpp | 461 |
1 files changed, 292 insertions, 169 deletions
diff --git a/qxt-mini/qxtglobalshortcut_x11.cpp b/qxt-mini/qxtglobalshortcut_x11.cpp index a2e47ade..01894cfc 100644 --- a/qxt-mini/qxtglobalshortcut_x11.cpp +++ b/qxt-mini/qxtglobalshortcut_x11.cpp @@ -30,42 +30,234 @@ ** <http://libqxt.org> <foundation@libqxt.org> *****************************************************************************/ +// qt must go first or #error #include <QHash> #include <QMutex> -#include <QMutexLocker> #include <QDebug> - +#include <QPair> +#include <QKeyEvent> #include <QApplication> -// include private header for great justice -sh 20131015 +#include <qpa/qplatformnativeinterface.h> + +#include "x11-keymap.hpp" + #include <X11/Xlib.h> +#include <X11/XKBlib.h> #include <xcb/xcb.h> -#include "qplatformnativeinterface.h" -#include "compat/util.hpp" -static constexpr quint32 AllMods = ShiftMask|LockMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask; +#include "powerset.hpp" -typedef int (*X11ErrorHandler)(Display *display, XErrorEvent *event); +#include <iterator> +#include <vector> +#include <type_traits> +#include <utility> +#include <cinttypes> +#include <array> + +static auto evil_mods = make_powerset(LockMask, Mod5Mask, Mod2Mask); + +static inline quint32 filter_evil_mods(quint32 mods) +{ + for (auto mod : evil_mods.elements()) + { + mods &= quint32(~mod); + } + return mods; +} + +using pair = QPair<quint32, quint32>; struct keybinding final { - quint32 code; + keybinding(const keybinding&) = default; + keybinding& operator=(const keybinding&) = default; + + quint32 code, mods; int refcnt; - static QHash<quint32, keybinding> list; + static QHash<pair, keybinding> list; static QMutex lock; - static bool incf(quint32 code); - static bool decf(quint32 code); + static bool incf(quint32 code, quint32 mods); + static bool decf(quint32 code, quint32 mods); ~keybinding(); private: - keybinding(quint32 code); + keybinding(quint32 code, quint32 mods); }; +static std::vector<pair> native_key(Qt::Key key, Qt::KeyboardModifiers modifiers); +typedef int (*X11ErrorHandler)(Display *display, XErrorEvent *event); + +class QxtX11ErrorHandler { +public: + static bool error; + + static int qxtX11ErrorHandler(Display *display, XErrorEvent *event) + { + Q_UNUSED(display) + switch (event->error_code) + { + case BadAccess: + case BadValue: + case BadWindow: + if (event->request_code == 33 /* X_GrabKey */ || + event->request_code == 34 /* X_UngrabKey */) + { + error = true; + } + } + return 0; + } + + QxtX11ErrorHandler() + { + error = false; + m_previousErrorHandler = XSetErrorHandler(qxtX11ErrorHandler); + } + + ~QxtX11ErrorHandler() + { + XSetErrorHandler(m_previousErrorHandler); + } + +private: + X11ErrorHandler m_previousErrorHandler; +}; + +class QxtX11Data { +public: + QxtX11Data() + { + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + void *display = native->nativeResourceForScreen(QByteArray("display"), + QGuiApplication::primaryScreen()); + m_display = reinterpret_cast<Display *>(display); + } + + bool isValid() + { + return m_display; + } + + Display *display() + { + Q_ASSERT(isValid()); + return m_display; + } + + Window rootWindow() + { + return DefaultRootWindow(display()); + } + + bool grabKey(quint32 code, quint32 mods) + { + const std::vector<pair> keycodes = native_key(Qt::Key(code), Qt::KeyboardModifiers(mods)); + bool ret = true; + + for (const pair& x : keycodes) + { + auto [ native_sym, native_mods ] = x; + int native_code = XKeysymToKeycode(display(), native_sym); + + if (keybinding::incf((unsigned)native_code, native_mods)) + { + QxtX11ErrorHandler errorHandler; + + XGrabKey(display(), native_code, native_mods, rootWindow(), True, GrabModeAsync, GrabModeAsync); + + for (const auto& set : evil_mods.sets()) + { + quint32 m = native_mods; + + for (int value : set) + m |= (unsigned)value; + + XGrabKey(display(), native_code, m, rootWindow(), True, GrabModeAsync, GrabModeAsync); + } + + if (errorHandler.error) + { + qDebug() << "qxt-mini: error while binding to" << code << mods; + for (const auto& set : evil_mods.sets()) + { + quint32 m = mods; + + for (int value : set) + m |= (unsigned)value; + + XUngrabKey(display(), native_code, m, rootWindow()); + } + ret = false; + } + } + } + + return ret; + } + + bool ungrabKey(quint32 code, quint32 mods) + { + const std::vector<pair> keycodes = native_key(Qt::Key(code), Qt::KeyboardModifiers(mods)); + bool ret = true; + + for (const pair& x : keycodes) + { + auto [ native_sym, native_mods ] = x; + int native_code = XKeysymToKeycode(display(), native_sym); + + if (keybinding::decf((unsigned)native_code, native_mods)) + { + QxtX11ErrorHandler errorHandler; + XUngrabKey(display(), native_code, native_mods, rootWindow()); + + for (const auto& set : evil_mods.sets()) + { + quint32 m = mods; + + for (int value : set) + m |= (unsigned)value; + + XUngrabKey(display(), native_code, m, rootWindow()); + } + + if (errorHandler.error) + { + qDebug() << "qxt-mini: error while unbinding" << code << mods; + ret = false; + } + } + } + return ret; + } + +private: + Display *m_display; +}; + +static std::vector<pair> native_key(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + std::vector<pair> ret; + + QxtX11Data x11; + if (!x11.isValid()) + return ret; + + std::vector<quint32> keycodes = qt_key_to_x11(x11.display(), key, modifiers); + unsigned mods = qt_mods_to_x11(modifiers); + mods = filter_evil_mods(mods); + + for (quint32 code : keycodes) + ret.push_back(pair(code, mods)); + + return ret; +} + bool operator==(const keybinding& k1, const keybinding& k2) { - return k1.code == k2.code; + return k1.code == k2.code && k1.mods == k2.mods; } inline bool operator!=(const keybinding& k1, const keybinding& k2) @@ -75,15 +267,17 @@ inline bool operator!=(const keybinding& k1, const keybinding& k2) uint qHash(const keybinding& k) { - return qHash(k.code); + return uint(k.code * 41) ^ qHash(k.mods); } uint qHash(const keybinding& k, uint seed) { - return qHash(k.code, seed); + return qHash(uint(k.code * 41) ^ qHash(k.mods), seed); } -keybinding::keybinding(quint32 code) : code(code), refcnt(0) +keybinding::keybinding(quint32 code, quint32 mods) : + code(code), mods(mods), + refcnt(0) { } @@ -91,11 +285,11 @@ keybinding::~keybinding() { } -bool keybinding::incf(quint32 code) +bool keybinding::incf(quint32 code, quint32 mods) { QMutexLocker l(&lock); - keybinding k = list.value(code, keybinding(code)); + keybinding k = list.value(pair(code, mods), keybinding(code, mods)); const bool ret = k.refcnt == 0; @@ -105,18 +299,18 @@ bool keybinding::incf(quint32 code) } k.refcnt++; - list.insert(code, k); + list.insert(pair(code, mods), k); //qDebug() << "qxt-mini: incf: refcount for" << code << "now" << k.refcnt; return ret; } -bool keybinding::decf(quint32 code) +bool keybinding::decf(quint32 code, quint32 mods) { QMutexLocker l(&lock); - auto it = list.find(code); + auto it = list.find(pair(code, mods)); if (it == list.end()) { @@ -139,196 +333,125 @@ bool keybinding::decf(quint32 code) return false; } -QHash<quint32, keybinding> keybinding::list; +QHash<pair, keybinding> keybinding::list; QMutex keybinding::lock; -class QxtX11ErrorHandler { -public: - static bool error; +bool QxtX11ErrorHandler::error = false; + +bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, + void *message, long *) +{ + QxtX11Data x11; + + if (!x11.isValid()) + return false; - static int qxtX11ErrorHandler(Display *display, XErrorEvent *event) { - Q_UNUSED(display); - switch (event->error_code) + static bool once_ = false; + if (!once_) { - case BadAccess: - case BadValue: - case BadWindow: - if (event->request_code == 33 /* X_GrabKey */ || - event->request_code == 34 /* X_UngrabKey */) - { - error = true; - } - } - return 0; - } + once_ = true; + Bool val = False; - QxtX11ErrorHandler() - { - error = false; - m_previousErrorHandler = XSetErrorHandler(qxtX11ErrorHandler); - } + (void) XkbSetDetectableAutoRepeat(x11.display(), True, &val); - ~QxtX11ErrorHandler() - { - XSetErrorHandler(m_previousErrorHandler); + if (!val) + qDebug() << "qxt-mini: can't fix x11 autorepeat"; + } } -private: - X11ErrorHandler m_previousErrorHandler; -}; + bool is_release = false; -bool QxtX11ErrorHandler::error = false; - -class QxtX11Data { -public: - QxtX11Data() - { - QPlatformNativeInterface *native = qApp->platformNativeInterface(); - void *display = native->nativeResourceForScreen(QByteArray("display"), - QGuiApplication::primaryScreen()); - m_display = reinterpret_cast<Display *>(display); - } + static_assert(std::is_same_v<xcb_key_press_event_t, xcb_key_release_event_t>); - bool isValid() - { - return m_display != 0; + xcb_key_press_event_t *kev = nullptr; + if (eventType == "xcb_generic_event_t") { + xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message); + switch (ev->response_type & 127) + { + case XCB_KEY_RELEASE: + is_release = true; + [[fallthrough]]; + case XCB_KEY_PRESS: + kev = static_cast<xcb_key_press_event_t *>(message); + break; + + default: + break; + } } - Display *display() - { - Q_ASSERT(isValid()); - return m_display; - } + if (kev) { +#if 0 + using event_type = decltype((xcb_key_press_event_t{}).detail); - Window rootWindow() - { - return DefaultRootWindow(display()); - } + static event_type prev_event = 0; + static bool prev_is_release = false; - bool grabKey(quint32 keycode, Window window) - { - if (keybinding::incf(keycode)) + if (is_release == prev_is_release && + prev_event != 0 && + prev_event == kev->detail) { - QxtX11ErrorHandler errorHandler; + // ignore repeated keystrokes + return false; + } + else + { + prev_event = kev->detail; + prev_is_release = is_release; + } +#endif - XGrabKey(display(), keycode, AnyModifier, window, True, - GrabModeAsync, GrabModeAsync); + unsigned int keycode = kev->detail; - if (errorHandler.error) { - ungrabKey(keycode, window); - return false; - } - } + if (keycode == 0) + return false; - return true; - } + quint32 keystate = xcb_mods_to_x11(kev->state); - bool ungrabKey(quint32 keycode, Window window) - { - if (keybinding::decf(keycode)) - { - QxtX11ErrorHandler errorHandler; - XUngrabKey(display(), keycode, AnyModifier, window); - return !errorHandler.error; - } - return true; - } + keystate = filter_evil_mods(keystate); -private: - Display *m_display; -}; + auto [ sym, sym2 ] = keycode_to_keysym(x11.display(), keycode, keystate, kev); -bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, - void *message, long *result) -{ - Q_UNUSED(result); + Qt::Key k; Qt::KeyboardModifiers mods; - xcb_key_press_event_t *kev = 0; - if (eventType == "xcb_generic_event_t") { - xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message); - if ((ev->response_type & 127) == XCB_KEY_PRESS) - kev = static_cast<xcb_key_press_event_t *>(message); - } + { + std::tie(k, mods) = x11_key_to_qt(x11.display(), (quint32)sym, keystate); - if (kev != 0) { - unsigned int keycode = kev->detail; - unsigned int keystate = 0; - if(kev->state & XCB_MOD_MASK_1) // alt - keystate |= Mod1Mask; - if(kev->state & XCB_MOD_MASK_CONTROL) // ctrl - keystate |= ControlMask; - if(kev->state & XCB_MOD_MASK_4) // super aka win key - keystate |= Mod4Mask; - if(kev->state & XCB_MOD_MASK_SHIFT) //shift - keystate |= ShiftMask; - if(kev->state & XCB_MOD_MASK_2) //numlock - keystate |= Mod2Mask; -#if 0 - if(key->state & XCB_MOD_MASK_3) // alt gr aka right-alt or ctrl+left-alt -- what mask is it? - keystate |= AltGrMask; -#endif + if (k != 0) + activateShortcut(k, mods, !is_release); + } - activateShortcut(keycode, keystate); + { + std::tie(k, mods) = x11_key_to_qt(x11.display(), (quint32)sym2, keystate); + + if (k != 0) + activateShortcut(k, mods, !is_release); + } } return false; } quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) { - // XXX TODO make a lookup table - quint32 native = 0; - if (modifiers & Qt::AltModifier) - native |= Mod1Mask; - if (modifiers & Qt::ControlModifier) - native |= ControlMask; - if (modifiers & Qt::MetaModifier) - native |= Mod4Mask; - if (modifiers & Qt::ShiftModifier) - native |= ShiftMask; - if (modifiers & Qt::KeypadModifier) - native |= Mod2Mask; - -#if 0 - if (modifiers & Qt::MetaModifier) // dunno the native mask - native |= Mod4Mask; -#endif - if (modifiers & Qt::KeypadModifier) // numlock - native |= Mod2Mask; - - native &= AllMods; - - return native; + modifiers = x11_mods_to_qt(filter_evil_mods(qt_mods_to_x11(modifiers))); + return quint32(modifiers); } quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) { - QxtX11Data x11; - if (!x11.isValid()) - return 0; - - QByteArray tmp(QKeySequence(key).toString().toLatin1()); - - KeySym keysym = XStringToKeysym(tmp.data()); - if (keysym == NoSymbol) - keysym = static_cast<ushort>(key); - - const quint32 ret = XKeysymToKeycode(x11.display(), keysym); - - //qDebug() << "key is" << key << QKeySequence(key).toString(QKeySequence::PortableText) << ret; - - return ret; + return quint32(key); } -bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, unused(quint32, nativeMods)) +bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) { QxtX11Data x11; - return x11.isValid() && x11.grabKey(nativeKey, x11.rootWindow()); + return x11.isValid() && x11.grabKey(nativeKey, nativeMods); } -bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, unused(quint32, nativeMods)) +bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) { QxtX11Data x11; - return x11.isValid() && x11.ungrabKey(nativeKey, x11.rootWindow()); + return x11.isValid() && x11.ungrabKey(nativeKey, nativeMods); } #endif |
