diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2017-05-30 02:30:47 +0200 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2017-05-30 02:30:47 +0200 |
commit | 5b819219889e78093e0dd32615adb6dcdc66cbcb (patch) | |
tree | 48dcc16ad34ab19a656ce862d8e4472e7ceb77e1 /qxt-mini | |
parent | a4836eac5872d06d533e0130230c99148aeb4c7e (diff) |
fix linux hotkeys
Requested-by: @miniskipper and many others
Diffstat (limited to 'qxt-mini')
-rw-r--r-- | qxt-mini/CMakeLists.txt | 2 | ||||
-rw-r--r-- | qxt-mini/qxtglobalshortcut.cpp | 36 | ||||
-rw-r--r-- | qxt-mini/qxtglobalshortcut.h | 4 | ||||
-rw-r--r-- | qxt-mini/qxtglobalshortcut_mac.cpp | 4 | ||||
-rw-r--r-- | qxt-mini/qxtglobalshortcut_p.h | 3 | ||||
-rw-r--r-- | qxt-mini/qxtglobalshortcut_x11.cpp | 221 | ||||
-rw-r--r-- | qxt-mini/x11-keymap.cpp | 136 | ||||
-rw-r--r-- | qxt-mini/x11-keymap.hpp | 15 |
8 files changed, 336 insertions, 85 deletions
diff --git a/qxt-mini/CMakeLists.txt b/qxt-mini/CMakeLists.txt index a6c2908e..84e66732 100644 --- a/qxt-mini/CMakeLists.txt +++ b/qxt-mini/CMakeLists.txt @@ -1,6 +1,8 @@ if(UNIX OR APPLE) otr_module(qxt-mini NO-COMPAT BIN) if(NOT APPLE) + pkg_check_modules(xprotopkg REQUIRED xproto) + target_include_directories(opentrack-qxt-mini SYSTEM PRIVATE ${xprotopkg_INCLUDE_DIRS}) target_link_libraries(opentrack-qxt-mini X11) else() otr_prop(TARGET opentrack-qxt-mini LINK_FLAGS "-framework Carbon -framework CoreFoundation") diff --git a/qxt-mini/qxtglobalshortcut.cpp b/qxt-mini/qxtglobalshortcut.cpp index 298472b5..fc0984b6 100644 --- a/qxt-mini/qxtglobalshortcut.cpp +++ b/qxt-mini/qxtglobalshortcut.cpp @@ -61,13 +61,13 @@ void QxtGlobalShortcutPrivate::event_filter_installer::ensure_event_filter() } QxtGlobalShortcutPrivate::QxtGlobalShortcutPrivate(QxtGlobalShortcutPrivate::tag) : - enabled(false), key(Qt::Key(0)), mods(Qt::NoModifier) + keystate(false), enabled(false), key(Qt::Key(0)), mods(Qt::NoModifier) { qDebug() << "qxt-mini: adding event filter"; } QxtGlobalShortcutPrivate::QxtGlobalShortcutPrivate() : - enabled(true), key(Qt::Key(0)), mods(Qt::NoModifier) + keystate(false), enabled(true), key(Qt::Key(0)), mods(Qt::NoModifier) { QxtGlobalShortcutPrivate::event_filter_installer::ensure_event_filter(); } @@ -143,25 +143,49 @@ bool QxtGlobalShortcutPrivate::unsetShortcut() return res; } -void QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativeMods) +void QxtGlobalShortcutPrivate::activateShortcut(quint32 nativeKey, quint32 nativeMods, bool is_down) { #ifndef Q_OS_MAC using IT = decltype(shortcuts.end()); const auto pair = qMakePair(nativeKey, nativeMods); IT it = shortcuts.find(pair); + bool once = false; + for (; it != shortcuts.end(); it++) { if (it.key() != pair) // DO NOT REMOVE break; + auto ptr = *it; + auto& priv = ptr->qxt_d(); + + if (priv.keystate == is_down) + { + continue; + } + + if (!once) + { + once = true; + qDebug() << "qxt-mini:" << (is_down ? "keydown" : "keyup") << priv.key << priv.mods; + } + + priv.keystate = is_down; + if (ptr->isEnabled()) - emit ptr->activated(); + emit ptr->activated(is_down); } #else QxtGlobalShortcut* shortcut = shortcuts.value(qMakePair(nativeKey, nativeMods)); - if (shortcut && shortcut->isEnabled()) - emit shortcut->activated(); + + if (shortcut) + { + shortcut->qxt_d().keystate = false; + + if (shortcut->isEnabled()) + emit shortcut->activated(true); + } #endif } diff --git a/qxt-mini/qxtglobalshortcut.h b/qxt-mini/qxtglobalshortcut.h index bf26180f..be62a984 100644 --- a/qxt-mini/qxtglobalshortcut.h +++ b/qxt-mini/qxtglobalshortcut.h @@ -43,6 +43,8 @@ class QXT_GUI_EXPORT QxtGlobalShortcut : public QObject Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) + bool keystate; + public: explicit QxtGlobalShortcut(QObject* parent = 0); explicit QxtGlobalShortcut(const QKeySequence& shortcut, QObject* parent = 0); @@ -58,7 +60,7 @@ public Q_SLOTS: void setDisabled(bool disabled = true); Q_SIGNALS: - void activated(); + void activated(bool keydown = true); }; #endif // QXTGLOBALSHORTCUT_H diff --git a/qxt-mini/qxtglobalshortcut_mac.cpp b/qxt-mini/qxtglobalshortcut_mac.cpp index c6b20fa2..55684667 100644 --- a/qxt-mini/qxtglobalshortcut_mac.cpp +++ b/qxt-mini/qxtglobalshortcut_mac.cpp @@ -55,7 +55,7 @@ OSStatus qxt_mac_handle_hot_key(EventHandlerCallRef nextHandler, EventRef event, GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(keyID), NULL, &keyID); Identifier id = keyIDs.key(keyID.id); if(id != Identifier()) - QxtGlobalShortcutPrivate::activateShortcut(id.second, id.first); + QxtGlobalShortcutPrivate::activateShortcut(id.second, id.first, true); } return noErr; } @@ -76,7 +76,7 @@ quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifier return native; } -quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) +quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key keys) { UTF16Char ch; // Constants found in NSEvent.h from AppKit.framework diff --git a/qxt-mini/qxtglobalshortcut_p.h b/qxt-mini/qxtglobalshortcut_p.h index 1835f956..7864a7d8 100644 --- a/qxt-mini/qxtglobalshortcut_p.h +++ b/qxt-mini/qxtglobalshortcut_p.h @@ -45,6 +45,7 @@ public: QxtGlobalShortcutPrivate(); ~QxtGlobalShortcutPrivate() override; + bool keystate; bool enabled; Qt::Key key; Qt::KeyboardModifiers mods; @@ -56,7 +57,7 @@ public: static bool eventFilter(void* message); bool nativeEventFilter(const QByteArray & eventType, void * message, long * result) override; - static void activateShortcut(quint32 nativeKey, quint32 nativeMods); + static void activateShortcut(quint32 nativeKey, quint32 nativeMods, bool is_down); private: struct event_filter_installer; diff --git a/qxt-mini/qxtglobalshortcut_x11.cpp b/qxt-mini/qxtglobalshortcut_x11.cpp index 0db1ece5..01dfe8df 100644 --- a/qxt-mini/qxtglobalshortcut_x11.cpp +++ b/qxt-mini/qxtglobalshortcut_x11.cpp @@ -32,40 +32,57 @@ #include <QHash> #include <QMutex> -#include <QMutexLocker> #include <QDebug> - +#include <QPair> +#include <QKeyEvent> #include <QApplication> -// include private header for great justice -sh 20131015 + #include <X11/Xlib.h> +#include <X11/XKBlib.h> #include <xcb/xcb.h> #include "qplatformnativeinterface.h" #include "compat/util.hpp" +#include <iterator> +#include "x11-keymap.hpp" + static constexpr quint32 AllMods = ShiftMask|LockMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask; +static constexpr quint32 evil_mods[] = { +#if 0 + LockMask, // caps lock + Mod3Mask, // scroll lock +#endif + Mod2Mask, // num lock + Mod5Mask, // altgr + + Mod2Mask | Mod5Mask, +}; + typedef int (*X11ErrorHandler)(Display *display, XErrorEvent *event); +using pair = QPair<quint32, quint32>; + struct keybinding final { - quint32 code; + 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); }; 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 +92,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 +110,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 +124,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,7 +158,7 @@ bool keybinding::decf(quint32 code) return false; } -QHash<quint32, keybinding> keybinding::list; +QHash<pair, keybinding> keybinding::list; QMutex keybinding::lock; class QxtX11ErrorHandler { @@ -206,57 +225,65 @@ public: return DefaultRootWindow(display()); } - bool grabKey(quint32 keycode, quint32 modifiers, Window window) + static constexpr quint32 filter_evil_mods(quint32 mods) { - //TODO: search keybinding by code and modifiers, so keys can be assigned multiple times using different modifiers - if (keybinding::incf(keycode)) + for (quint32 mod : evil_mods) + mods &= ~mod; + return mods; + } + + bool grabKey(quint32 code, quint32 mods, Window window) + { + // qDebug() << "grabbing key" << code << mods; + + mods = filter_evil_mods(mods); + + // qDebug() << "mods now" << mods; + + if (keybinding::incf(code, mods)) { QxtX11ErrorHandler errorHandler; - bool error = false; - XGrabKey(display(), keycode, modifiers, window, True, - GrabModeAsync, GrabModeAsync); - if (errorHandler.error) { - error=true; - } - //Also grab key with num lock = on - XGrabKey(display(), keycode, modifiers | Mod2Mask, window, True, - GrabModeAsync, GrabModeAsync); - if (errorHandler.error) { - error=true; - } + XGrabKey(display(), code, mods, window, True, GrabModeAsync, GrabModeAsync); - //...and with scroll lock = on - XGrabKey(display(), keycode, modifiers | Mod5Mask, window, True, - GrabModeAsync, GrabModeAsync); - if (errorHandler.error) { - error=true; - } + for (quint32 evil : evil_mods) + { + quint32 m = mods | evil; - //...and with bot = on - XGrabKey(display(), keycode, modifiers | Mod2Mask | Mod5Mask, window, True, - GrabModeAsync, GrabModeAsync); - if (errorHandler.error) { - error=true; + XGrabKey(display(), code, m, window, True, GrabModeAsync, GrabModeAsync); } - if(error){ - ungrabKey(keycode, window); + if (errorHandler.error) + { + qDebug() << "qxt-mini: error while binding to" << code << mods; + ungrabKey(code, mods, window); return false; } - } return true; } - bool ungrabKey(quint32 keycode, Window window) + bool ungrabKey(quint32 code, quint32 mods, Window window) { - if (keybinding::decf(keycode)) + mods = filter_evil_mods(mods); + + if (keybinding::decf(code, mods)) { QxtX11ErrorHandler errorHandler; - XUngrabKey(display(), keycode, AnyModifier, window); - return !errorHandler.error; + XUngrabKey(display(), code, mods, window); + + for (quint32 evil : evil_mods) + { + quint32 m = mods | evil; + XUngrabKey(display(), code, m, window); + } + + if (errorHandler.error) + { + qDebug() << "qxt-mini: error while unbinding" << code << mods; + return false; + } } return true; } @@ -270,14 +297,65 @@ bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, { Q_UNUSED(result); + { + static bool once_ = false; + if (!once_) + { + QxtX11Data x11; + if (x11.isValid()) + { + once_ = true; + Bool val; + + (void) XkbSetDetectableAutoRepeat(x11.display(), True, &val); + + if (val) + qDebug() << "qxt-mini: fixed x11 autorepeat"; + else + qDebug() << "qxt-mini: can't fix x11 autorepeat"; + } + } + } + + bool is_release = false; + 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); + 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); + /*FALLTHROUGH*/ + default: + break; + } } - if (kev != 0) { + if (kev) { +#if 0 + using event_type = decltype((xcb_key_press_event_t{}).detail); + + static event_type prev_event = 0; + static bool prev_is_release = false; + + if (is_release == prev_is_release && + prev_event != 0 && + prev_event == kev->detail) + { + // ignore repeated keystrokes + return false; + } + else + { + prev_event = kev->detail; + prev_is_release = is_release; + } +#endif + unsigned int keycode = kev->detail; unsigned int keystate = 0; if(kev->state & XCB_MOD_MASK_1) // alt @@ -293,14 +371,15 @@ bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, keystate |= AltGrMask; #endif - activateShortcut(keycode, keystate); + // qDebug() << "qxt-mini:" << (is_release ? "keyup" : "keydown") << keycode << keystate; + + activateShortcut(keycode, keystate, !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; @@ -312,11 +391,8 @@ quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifier native |= ShiftMask; if (modifiers & Qt::KeypadModifier) native |= Mod2Mask; - -#if 0 - if (modifiers & Qt::MetaModifier) // dunno the native mask + if (modifiers & Qt::MetaModifier) // Super aka Windows key native |= Mod4Mask; -#endif if (modifiers & Qt::KeypadModifier) // numlock native |= Mod2Mask; @@ -328,31 +404,26 @@ quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifier quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) { QxtX11Data x11; - if (!x11.isValid()) - return 0; - QByteArray tmp(QKeySequence(key).toString().toLatin1()); + if (x11.isValid()) + return qt_key_to_x11(x11.display(), key); - 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 unsigned(-1); } -bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, unused(quint32, nativeMods)) +bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) { QxtX11Data x11; + if (nativeKey == unsigned(-1)) + return false; return x11.isValid() && x11.grabKey(nativeKey, nativeMods, x11.rootWindow()); } -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()); + if (nativeKey == unsigned(-1)) + return false; + return x11.isValid() && x11.ungrabKey(nativeKey, nativeMods, x11.rootWindow()); } #endif diff --git a/qxt-mini/x11-keymap.cpp b/qxt-mini/x11-keymap.cpp new file mode 100644 index 00000000..b4438a3c --- /dev/null +++ b/qxt-mini/x11-keymap.cpp @@ -0,0 +1,136 @@ +#include "x11-keymap.hpp" + +#ifndef __APPLE__ + +#include <tuple> + +#define XK_MISCELLANY +#define XK_LATIN1 +#include <X11/keysymdef.h> + +using tt = std::tuple<Qt::Key, unsigned>; + +static tt keymap[] = +{ + { Qt::Key_Return, XK_Return }, + { Qt::Key_Enter, XK_Return }, + { Qt::Key_Delete, XK_Delete }, + { Qt::Key_Pause, XK_Pause }, + { Qt::Key_SysReq, XK_Sys_Req }, + { Qt::Key_Home, XK_Home }, + { Qt::Key_End, XK_End }, + { Qt::Key_Left, XK_Left }, + { Qt::Key_Up, XK_Up }, + { Qt::Key_Right, XK_Right }, + { Qt::Key_Down, XK_Down }, + { Qt::Key_PageUp, XK_Prior }, + { Qt::Key_PageDown, XK_Next }, + + { Qt::Key_F1, XK_F1 }, + { Qt::Key_F2, XK_F2 }, + { Qt::Key_F3, XK_F3 }, + { Qt::Key_F4, XK_F4 }, + { Qt::Key_F5, XK_F5 }, + { Qt::Key_F6, XK_F6 }, + { Qt::Key_F7, XK_F7 }, + { Qt::Key_F8, XK_F8 }, + { Qt::Key_F9, XK_F9 }, + { Qt::Key_F10, XK_F10 }, + { Qt::Key_F11, XK_F11 }, + { Qt::Key_F12, XK_F12 }, + + { Qt::Key_Space, XK_space }, + { Qt::Key_Exclam, XK_exclam }, + { Qt::Key_QuoteDbl, XK_quotedbl }, + { Qt::Key_NumberSign, XK_numbersign }, + { Qt::Key_Dollar, XK_dollar }, + { Qt::Key_Percent, XK_percent }, + { Qt::Key_Ampersand, XK_ampersand }, + { Qt::Key_Apostrophe, XK_apostrophe }, + { Qt::Key_ParenLeft, XK_parenleft }, + { Qt::Key_ParenRight, XK_parenright }, + { Qt::Key_Asterisk, XK_asterisk }, + { Qt::Key_Plus, XK_plus }, + { Qt::Key_Comma, XK_comma }, + { Qt::Key_Minus, XK_minus }, + { Qt::Key_Period, XK_period }, + { Qt::Key_Slash, XK_slash }, + + { Qt::Key_0, XK_0 }, + { Qt::Key_1, XK_1}, + { Qt::Key_2, XK_2 }, + { Qt::Key_3, XK_3 }, + { Qt::Key_4, XK_4 }, + { Qt::Key_5, XK_5 }, + { Qt::Key_6, XK_6 }, + { Qt::Key_7, XK_7 }, + { Qt::Key_8, XK_8 }, + { Qt::Key_9, XK_9 }, + + { Qt::Key_Colon, XK_colon }, + { Qt::Key_Semicolon, XK_semicolon }, + { Qt::Key_Less, XK_less }, + { Qt::Key_Equal, XK_equal }, + { Qt::Key_Greater, XK_greater }, + { Qt::Key_Question, XK_question }, + { Qt::Key_At, XK_at }, + + { Qt::Key_A, XK_a }, + { Qt::Key_B, XK_b }, + { Qt::Key_C, XK_c }, + { Qt::Key_D, XK_d }, + { Qt::Key_E, XK_e }, + { Qt::Key_F, XK_f }, + { Qt::Key_G, XK_g }, + { Qt::Key_H, XK_h }, + { Qt::Key_I, XK_i }, + { Qt::Key_J, XK_j }, + { Qt::Key_K, XK_k }, + { Qt::Key_L, XK_l }, + { Qt::Key_M, XK_m }, + { Qt::Key_N, XK_n }, + { Qt::Key_O, XK_o }, + { Qt::Key_P, XK_p }, + { Qt::Key_Q, XK_q }, + { Qt::Key_R, XK_r }, + { Qt::Key_S, XK_s }, + { Qt::Key_T, XK_t }, + { Qt::Key_U, XK_u }, + { Qt::Key_V, XK_v }, + { Qt::Key_W, XK_w }, + { Qt::Key_X, XK_x }, + { Qt::Key_Y, XK_y }, + { Qt::Key_Z, XK_z }, + + { Qt::Key_BracketLeft, XK_bracketleft }, + { Qt::Key_Backslash, XK_backslash }, + { Qt::Key_BracketRight, XK_bracketright }, + { Qt::Key_AsciiCircum, XK_asciicircum }, + { Qt::Key_Underscore, XK_underscore }, + { Qt::Key_QuoteLeft, XK_grave }, +}; + +unsigned qt_key_to_x11(Display* disp, Qt::Key k) +{ + Qt::Key k_; + unsigned keysym; + + for (const tt& tuple : keymap) + { + std::tie(k_, keysym) = tuple; + + if (k == k_) + { + const unsigned ret = XKeysymToKeycode(disp, keysym); + + if (ret == 0) + return unsigned(-1); + + return ret; + } + } + + qDebug() << "qxt-mini: no keysym for" << k; + return unsigned(-1); +} +#endif diff --git a/qxt-mini/x11-keymap.hpp b/qxt-mini/x11-keymap.hpp new file mode 100644 index 00000000..b35097bc --- /dev/null +++ b/qxt-mini/x11-keymap.hpp @@ -0,0 +1,15 @@ +#pragma once + +#ifndef BUILD_QXT_MINI +# error "internal header" +#endif + +#ifndef __APPLE__ + +#include <Qt> +#include <QDebug> +#include <X11/Xlib.h> + +unsigned qt_key_to_x11(Display* disp, Qt::Key k); + +#endif |