#ifndef __APPLE__ #include "qxtglobalshortcut_p.h" /**************************************************************************** ** Copyright (c) 2006 - 2011, the LibQxt project. ** See the Qxt AUTHORS file for a list of authors and copyright holders. ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** * Neither the name of the LibQxt project nor the ** names of its contributors may be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY ** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** ** *****************************************************************************/ // qt must go first or #error #include #include #include #include #include #include #include "qplatformnativeinterface.h" #include "x11-keymap.hpp" #include #include #include #include "compat/util.hpp" #include #include #include #include #include #include template struct powerset final { static_assert(std::is_integral::value, ""); using size_type = size_type_; static_assert(M > 0, ""); static_assert(M < sizeof(size_type[8]), ""); static_assert(std::is_unsigned::value || M < sizeof(size_type)*8 - 1, ""); using N = std::integral_constant; static_assert((N::value & (N::value + 1)) == 0, ""); using set_type = std::vector; using sets_type = std::array; using element_type = t; using element_count = std::integral_constant; using self_type = powerset; operator const sets_type&() const { return sets_; } operator sets_type&() { return sets_; } const sets_type& sets() const { return sets_; } sets_type& sets() { return sets_; } set_type& operator[](unsigned k) { return sets_[k]; } const set_type& operator[](unsigned k) const { return sets_[k]; } const set_type& elements() const { return elements_; } set_type& elements() { return elements_; } template operator QString() const { QString str; unsigned k = 0; for (const auto& set : sets_) { str.append(QStringLiteral("#%1: ").arg(++k)); for (const auto& x : set) str.append(QStringLiteral("%1 ").arg(x)); str.append('\n'); } return str.mid(0, str.size() - 1); } powerset() {} private: sets_type sets_; set_type elements_; }; template static auto make_powerset(const t& arg, const xs&... args) { using cnt = std::integral_constant; using p = powerset; using len = typename p::element_count; using vec = typename p::set_type; using size_type = typename p::size_type; p ret; vec v; v.reserve(len()); const typename p::set_type ts {{arg, static_cast(args)...}}; ret.elements() = std::vector(std::begin(ts), std::end(ts)); // no nullary set for (size_type i = 0; i < len(); i++) { v.clear(); size_type k = 1; for (const t& x : ts) { if ((i+1) & k) v.push_back(std::move(x)); k <<= 1; } ret[i] = vec(std::begin(v), std::end(v)); ret[i].shrink_to_fit(); } return ret; } 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; struct keybinding final { quint32 code, mods; int refcnt; static QHash list; static QMutex lock; static bool incf(quint32 code, quint32 mods); static bool decf(quint32 code, quint32 mods); ~keybinding(); private: keybinding(quint32 code, quint32 mods); }; static std::vector 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); } bool isValid() { return m_display != 0; } Display *display() { Q_ASSERT(isValid()); return m_display; } Window rootWindow() { return DefaultRootWindow(display()); } bool grabKey(quint32 code, quint32 mods) { const std::vector keycodes = native_key(Qt::Key(code), Qt::KeyboardModifiers(mods)); bool ret = true; for (pair x : keycodes) { int native_code = x.first, native_mods = x.second; if (keybinding::incf(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 (auto value : set) m |= value; XGrabKey(display(), native_code, m, rootWindow(), True, GrabModeAsync, GrabModeAsync); } if (errorHandler.error) { qDebug() << "qxt-mini: error while binding to" << code << mods; ungrabKey(code, mods); ret = false; } } } return ret; } bool ungrabKey(quint32 code, quint32 mods) { const std::vector keycodes = native_key(Qt::Key(code), Qt::KeyboardModifiers(mods)); bool ret = true; for (pair x : keycodes) { int native_code = x.first, native_mods = x.second; if (keybinding::decf(native_code, native_mods)) { QxtX11ErrorHandler errorHandler; XUngrabKey(display(), native_code, native_mods, rootWindow()); for (const auto& set : evil_mods.sets()) { quint32 m = mods; for (auto value : set) m |= value; XUngrabKey(display(), 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 native_key(Qt::Key key, Qt::KeyboardModifiers modifiers) { std::vector ret; QxtX11Data x11; if (!x11.isValid()) return ret; std::vector 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 && k1.mods == k2.mods; } inline bool operator!=(const keybinding& k1, const keybinding& k2) { return !(k1 == k2); } uint qHash(const keybinding& k) { return uint(k.code * 41) ^ qHash(k.mods); } uint qHash(const keybinding& k, uint seed) { return qHash(uint(k.code * 41) ^ qHash(k.mods), seed); } keybinding::keybinding(quint32 code, quint32 mods) : code(code), mods(mods), refcnt(0) { } keybinding::~keybinding() { } bool keybinding::incf(quint32 code, quint32 mods) { QMutexLocker l(&lock); keybinding k = list.value(pair(code, mods), keybinding(code, mods)); const bool ret = k.refcnt == 0; if (ret) { //qDebug() << "qxt-mini: registered keybinding" << code; } k.refcnt++; list.insert(pair(code, mods), k); //qDebug() << "qxt-mini: incf: refcount for" << code << "now" << k.refcnt; return ret; } bool keybinding::decf(quint32 code, quint32 mods) { QMutexLocker l(&lock); auto it = list.find(pair(code, mods)); if (it == list.end()) { qWarning() << "qxt-mini: spurious keybinding decf on" << code; return false; } keybinding& k = *it; k.refcnt--; if (k.refcnt == 0) { list.erase(it); //qDebug() << "qxt-mini: removed keybinding" << code; return true; } //qDebug() << "qxt-mini: decf: refcount for" << code << "now" << k.refcnt; return false; } QHash keybinding::list; QMutex keybinding::lock; bool QxtX11ErrorHandler::error = false; bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, void *message, long *) { QxtX11Data x11; if (!x11.isValid()) return false; { static bool once_ = false; if (!once_) { once_ = true; Bool val = False; (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(message); switch (ev->response_type & 127) { case XCB_KEY_RELEASE: is_release = true; /*FALLTHROUGH*/ case XCB_KEY_PRESS: kev = static_cast(message); /*FALLTHROUGH*/ default: break; } } 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; if (keycode == 0) return false; quint32 keystate = xcb_mods_to_x11(kev->state); keystate = filter_evil_mods(keystate); QPair sym_ = keycode_to_keysym(x11.display(), keycode, keystate, kev); KeySym sym = sym_.first, sym2 = sym_.second; Qt::Key k; Qt::KeyboardModifiers mods; { std::tie(k, mods) = x11_key_to_qt(x11.display(), sym, keystate); if (k != 0) activateShortcut(k, mods, !is_release); } { std::tie(k, mods) = x11_key_to_qt(x11.display(), sym2, keystate); if (k != 0) activateShortcut(k, mods, !is_release); } } return false; } quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) { modifiers = x11_mods_to_qt(filter_evil_mods(qt_mods_to_x11(modifiers))); return quint32(modifiers); } quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) { return quint32(key); } bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) { QxtX11Data x11; return x11.isValid() && x11.grabKey(nativeKey, nativeMods); } bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) { QxtX11Data x11; return x11.isValid() && x11.ungrabKey(nativeKey, nativeMods); } #endif