summaryrefslogtreecommitdiffhomepage
path: root/qxt-mini/qxtglobalshortcut_x11.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'qxt-mini/qxtglobalshortcut_x11.cpp')
-rw-r--r--qxt-mini/qxtglobalshortcut_x11.cpp461
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