#include "globalaction.h" #include "debug.h" #include namespace service { QHash, QAction *> GlobalAction::actions_; void GlobalAction::init() { qApp->installNativeEventFilter(new GlobalAction); } bool GlobalAction::makeGlobal(QAction *action) { QKeySequence hotKey = action->shortcut(); if (hotKey.isEmpty()) return true; Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier; Qt::Key key = hotKey.isEmpty() ? Qt::Key(0) : Qt::Key((hotKey[0] ^ allMods) & hotKey[0]); Qt::KeyboardModifiers mods = hotKey.isEmpty() ? Qt::KeyboardModifiers(0) : Qt::KeyboardModifiers(hotKey[0] & allMods); const quint32 nativeKey = nativeKeycode(key); const quint32 nativeMods = nativeModifiers(mods); const bool res = registerHotKey(nativeKey, nativeMods); if (res) actions_.insert(qMakePair(nativeKey, nativeMods), action); else LERROR() << "Failed to register global hotkey:" << LARG(hotKey.toString()); return res; } bool GlobalAction::removeGlobal(QAction *action) { QKeySequence hotKey = action->shortcut(); if (hotKey.isEmpty()) return true; Qt::KeyboardModifiers allMods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier; Qt::Key key = hotKey.isEmpty() ? Qt::Key(0) : Qt::Key((hotKey[0] ^ allMods) & hotKey[0]); Qt::KeyboardModifiers mods = hotKey.isEmpty() ? Qt::KeyboardModifiers(0) : Qt::KeyboardModifiers(hotKey[0] & allMods); const quint32 nativeKey = nativeKeycode(key); const quint32 nativeMods = nativeModifiers(mods); if (!actions_.contains(qMakePair(nativeKey, nativeMods))) return true; const bool res = unregisterHotKey(nativeKey, nativeMods); if (res) actions_.remove(qMakePair(nativeKey, nativeMods)); else LERROR() << "Failed to unregister global hotkey:" << (hotKey.toString()); return res; } bool GlobalAction::update(QAction *action, const QKeySequence &newShortcut) { if (!action->shortcut().isEmpty()) removeGlobal(action); action->setShortcut(newShortcut); return newShortcut.isEmpty() ? true : makeGlobal(action); } void GlobalAction::triggerHotKey(quint32 nativeKey, quint32 nativeMods) { QAction *action = actions_.value(qMakePair(nativeKey, nativeMods)); if (action && action->isEnabled()) action->activate(QAction::Trigger); } } // namespace service #ifdef Q_OS_LINUX #include #include #include namespace service { static bool error = false; static int customHandler(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; } [[fallthrough]]; default: return 0; } } bool GlobalAction::registerHotKey(quint32 nativeKey, quint32 nativeMods) { auto nativeInterface = qApp->nativeInterface(); SOFT_ASSERT(nativeInterface, return false); Display *display = nativeInterface->display(); SOFT_ASSERT(display, return false); Bool owner = True; int pointer = GrabModeAsync; int keyboard = GrabModeAsync; error = false; int (*handler)(Display * display, XErrorEvent * event) = XSetErrorHandler(customHandler); XGrabKey(display, nativeKey, nativeMods, DefaultRootWindow(display), owner, pointer, keyboard); // allow numlock XGrabKey(display, nativeKey, nativeMods | Mod2Mask, DefaultRootWindow(display), owner, pointer, keyboard); XSync(display, False); XSetErrorHandler(handler); return !error; } bool GlobalAction::unregisterHotKey(quint32 nativeKey, quint32 nativeMods) { auto nativeInterface = qApp->nativeInterface(); SOFT_ASSERT(nativeInterface, return false); Display *display = nativeInterface->display(); SOFT_ASSERT(display, return false); error = false; int (*handler)(Display * display, XErrorEvent * event) = XSetErrorHandler(customHandler); XUngrabKey(display, nativeKey, nativeMods, DefaultRootWindow(display)); // allow numlock XUngrabKey(display, nativeKey, nativeMods | Mod2Mask, DefaultRootWindow(display)); XSync(display, False); XSetErrorHandler(handler); return !error; } bool GlobalAction::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) { Q_UNUSED(eventType); Q_UNUSED(result); xcb_generic_event_t *event = static_cast(message); if (event->response_type == XCB_KEY_PRESS) { xcb_key_press_event_t *keyEvent = static_cast(message); const quint32 keycode = keyEvent->detail; const quint32 modifiers = keyEvent->state & ~XCB_MOD_MASK_2; triggerHotKey(keycode, modifiers); } return false; } quint32 GlobalAction::nativeKeycode(Qt::Key key) { auto nativeInterface = qApp->nativeInterface(); Display *display = nativeInterface->display(); KeySym keySym = XStringToKeysym(qPrintable(QKeySequence(key).toString())); if (XKeysymToString(keySym) == nullptr) { keySym = QChar(key).unicode(); } return XKeysymToKeycode(display, keySym); } quint32 GlobalAction::nativeModifiers(Qt::KeyboardModifiers modifiers) { quint32 native = 0; if (modifiers & Qt::ShiftModifier) native |= ShiftMask; if (modifiers & Qt::ControlModifier) native |= ControlMask; if (modifiers & Qt::AltModifier) native |= Mod1Mask; if (modifiers & Qt::MetaModifier) native |= Mod4Mask; return native; } #endif // ifdef Q_OS_LINUX #ifdef Q_OS_WIN #include namespace service { bool GlobalAction::registerHotKey(quint32 nativeKey, quint32 nativeMods) { return RegisterHotKey(0, nativeMods ^ nativeKey, nativeMods, nativeKey); } bool GlobalAction::unregisterHotKey(quint32 nativeKey, quint32 nativeMods) { return UnregisterHotKey(0, nativeMods ^ nativeKey); } bool GlobalAction::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) { Q_UNUSED(eventType); Q_UNUSED(result); MSG *msg = static_cast(message); if (msg->message == WM_HOTKEY) { const quint32 keycode = HIWORD(msg->lParam); const quint32 modifiers = LOWORD(msg->lParam); triggerHotKey(keycode, modifiers); } return false; } quint32 GlobalAction::nativeKeycode(Qt::Key key) { switch (key) { case Qt::Key_Escape: return VK_ESCAPE; case Qt::Key_Tab: case Qt::Key_Backtab: return VK_TAB; case Qt::Key_Backspace: return VK_BACK; case Qt::Key_Return: case Qt::Key_Enter: return VK_RETURN; case Qt::Key_Insert: return VK_INSERT; case Qt::Key_Delete: return VK_DELETE; case Qt::Key_Pause: return VK_PAUSE; case Qt::Key_Print: return VK_PRINT; case Qt::Key_Clear: return VK_CLEAR; case Qt::Key_Home: return VK_HOME; case Qt::Key_End: return VK_END; case Qt::Key_Left: return VK_LEFT; case Qt::Key_Up: return VK_UP; case Qt::Key_Right: return VK_RIGHT; case Qt::Key_Down: return VK_DOWN; case Qt::Key_PageUp: return VK_PRIOR; case Qt::Key_PageDown: return VK_NEXT; case Qt::Key_CapsLock: return VK_CAPITAL; case Qt::Key_NumLock: return VK_NUMLOCK; case Qt::Key_ScrollLock: return VK_SCROLL; case Qt::Key_F1: return VK_F1; case Qt::Key_F2: return VK_F2; case Qt::Key_F3: return VK_F3; case Qt::Key_F4: return VK_F4; case Qt::Key_F5: return VK_F5; case Qt::Key_F6: return VK_F6; case Qt::Key_F7: return VK_F7; case Qt::Key_F8: return VK_F8; case Qt::Key_F9: return VK_F9; case Qt::Key_F10: return VK_F10; case Qt::Key_F11: return VK_F11; case Qt::Key_F12: return VK_F12; case Qt::Key_F13: return VK_F13; case Qt::Key_F14: return VK_F14; case Qt::Key_F15: return VK_F15; case Qt::Key_F16: return VK_F16; case Qt::Key_F17: return VK_F17; case Qt::Key_F18: return VK_F18; case Qt::Key_F19: return VK_F19; case Qt::Key_F20: return VK_F20; case Qt::Key_F21: return VK_F21; case Qt::Key_F22: return VK_F22; case Qt::Key_F23: return VK_F23; case Qt::Key_F24: return VK_F24; case Qt::Key_Space: return VK_SPACE; case Qt::Key_QuoteDbl: return VK_OEM_7; case Qt::Key_Apostrophe: return VK_OEM_7; case Qt::Key_Period: return VK_DECIMAL; case Qt::Key_Colon: return VK_OEM_1; case Qt::Key_Semicolon: return VK_OEM_1; case Qt::Key_Less: return VK_OEM_COMMA; case Qt::Key_Greater: return VK_OEM_PERIOD; case Qt::Key_Question: return VK_OEM_2; case Qt::Key_BracketLeft: return VK_OEM_4; case Qt::Key_Backslash: return VK_OEM_5; case Qt::Key_BracketRight: return VK_OEM_6; case Qt::Key_QuoteLeft: return VK_OEM_3; case Qt::Key_BraceLeft: return VK_OEM_4; case Qt::Key_Bar: return VK_OEM_5; case Qt::Key_BraceRight: return VK_OEM_6; case Qt::Key_Asterisk: return VK_MULTIPLY; case Qt::Key_Plus: return VK_OEM_PLUS; case Qt::Key_Comma: return VK_OEM_COMMA; case Qt::Key_Minus: return VK_OEM_MINUS; case Qt::Key_Slash: return VK_OEM_2; case Qt::Key_MediaNext: return VK_MEDIA_NEXT_TRACK; case Qt::Key_MediaPrevious: return VK_MEDIA_PREV_TRACK; case Qt::Key_MediaPlay: return VK_MEDIA_PLAY_PAUSE; case Qt::Key_MediaStop: return VK_MEDIA_STOP; case Qt::Key_VolumeDown: return VK_VOLUME_DOWN; case Qt::Key_VolumeUp: return VK_VOLUME_UP; case Qt::Key_VolumeMute: return VK_VOLUME_MUTE; // numbers case Qt::Key_0: case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: case Qt::Key_8: case Qt::Key_9: return key; // letters case Qt::Key_A: case Qt::Key_B: case Qt::Key_C: case Qt::Key_D: case Qt::Key_E: case Qt::Key_F: case Qt::Key_G: case Qt::Key_H: case Qt::Key_I: case Qt::Key_J: case Qt::Key_K: case Qt::Key_L: case Qt::Key_M: case Qt::Key_N: case Qt::Key_O: case Qt::Key_P: case Qt::Key_Q: case Qt::Key_R: case Qt::Key_S: case Qt::Key_T: case Qt::Key_U: case Qt::Key_V: case Qt::Key_W: case Qt::Key_X: case Qt::Key_Y: case Qt::Key_Z: return key; default: return 0; } } quint32 GlobalAction::nativeModifiers(Qt::KeyboardModifiers modifiers) { // MOD_ALT, MOD_CONTROL, (MOD_KEYUP), MOD_SHIFT, MOD_WIN quint32 native = 0; if (modifiers & Qt::ShiftModifier) native |= MOD_SHIFT; if (modifiers & Qt::ControlModifier) native |= MOD_CONTROL; if (modifiers & Qt::AltModifier) native |= MOD_ALT; if (modifiers & Qt::MetaModifier) native |= MOD_WIN; // if (modifiers & Qt::KeypadModifier) // if (modifiers & Qt::GroupSwitchModifier) return native; } #endif // ifdef Q_OS_WIN #ifdef Q_OS_MAC #include namespace service { static bool isInited = false; static QHash, EventHotKeyRef> hotkeyRefs; struct ActionAdapter { static OSStatus macHandler(EventHandlerCallRef /*nextHandler*/, EventRef event, void * /*userData*/) { EventHotKeyID id; GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(id), NULL, &id); GlobalAction::triggerHotKey(quint32(id.signature), quint32(id.id)); return noErr; } }; bool GlobalAction::registerHotKey(quint32 nativeKey, quint32 nativeMods) { if (!isInited) { EventTypeSpec spec; spec.eventClass = kEventClassKeyboard; spec.eventKind = kEventHotKeyPressed; InstallApplicationEventHandler(&ActionAdapter::macHandler, 1, &spec, NULL, NULL); isInited = true; } EventHotKeyID id; id.signature = nativeKey; id.id = nativeMods; EventHotKeyRef ref = NULL; OSStatus status = RegisterEventHotKey(nativeKey, nativeMods, id, GetApplicationEventTarget(), 0, &ref); if (status != noErr) { LERROR() << "RegisterEventHotKey error:" << LARG(status); return false; } else { hotkeyRefs.insert(qMakePair(nativeKey, nativeMods), ref); return true; } } bool GlobalAction::unregisterHotKey(quint32 nativeKey, quint32 nativeMods) { EventHotKeyRef ref = hotkeyRefs.value(qMakePair(nativeKey, nativeMods)); ASSERT(ref); OSStatus status = UnregisterEventHotKey(ref); if (status != noErr) { LERROR() << "UnregisterEventHotKey error:" << LARG(status); return false; } else { hotkeyRefs.remove(qMakePair(nativeKey, nativeMods)); return true; } } bool GlobalAction::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) { return false; } quint32 GlobalAction::nativeKeycode(Qt::Key key) { switch (key) { case Qt::Key_A: return kVK_ANSI_A; case Qt::Key_B: return kVK_ANSI_B; case Qt::Key_C: return kVK_ANSI_C; case Qt::Key_D: return kVK_ANSI_D; case Qt::Key_E: return kVK_ANSI_E; case Qt::Key_F: return kVK_ANSI_F; case Qt::Key_G: return kVK_ANSI_G; case Qt::Key_H: return kVK_ANSI_H; case Qt::Key_I: return kVK_ANSI_I; case Qt::Key_J: return kVK_ANSI_J; case Qt::Key_K: return kVK_ANSI_K; case Qt::Key_L: return kVK_ANSI_L; case Qt::Key_M: return kVK_ANSI_M; case Qt::Key_N: return kVK_ANSI_N; case Qt::Key_O: return kVK_ANSI_O; case Qt::Key_P: return kVK_ANSI_P; case Qt::Key_Q: return kVK_ANSI_Q; case Qt::Key_R: return kVK_ANSI_R; case Qt::Key_S: return kVK_ANSI_S; case Qt::Key_T: return kVK_ANSI_T; case Qt::Key_U: return kVK_ANSI_U; case Qt::Key_V: return kVK_ANSI_V; case Qt::Key_W: return kVK_ANSI_W; case Qt::Key_X: return kVK_ANSI_X; case Qt::Key_Y: return kVK_ANSI_Y; case Qt::Key_Z: return kVK_ANSI_Z; case Qt::Key_0: return kVK_ANSI_0; case Qt::Key_1: return kVK_ANSI_1; case Qt::Key_2: return kVK_ANSI_2; case Qt::Key_3: return kVK_ANSI_3; case Qt::Key_4: return kVK_ANSI_4; case Qt::Key_5: return kVK_ANSI_5; case Qt::Key_6: return kVK_ANSI_6; case Qt::Key_7: return kVK_ANSI_7; case Qt::Key_8: return kVK_ANSI_8; case Qt::Key_9: return kVK_ANSI_9; case Qt::Key_F1: return kVK_F1; case Qt::Key_F2: return kVK_F2; case Qt::Key_F3: return kVK_F3; case Qt::Key_F4: return kVK_F4; case Qt::Key_F5: return kVK_F5; case Qt::Key_F6: return kVK_F6; case Qt::Key_F7: return kVK_F7; case Qt::Key_F8: return kVK_F8; case Qt::Key_F9: return kVK_F9; case Qt::Key_F10: return kVK_F10; case Qt::Key_F11: return kVK_F11; case Qt::Key_F12: return kVK_F12; case Qt::Key_F13: return kVK_F13; case Qt::Key_F14: return kVK_F14; case Qt::Key_F15: return kVK_F15; case Qt::Key_F16: return kVK_F16; case Qt::Key_F17: return kVK_F17; case Qt::Key_F18: return kVK_F18; case Qt::Key_F19: return kVK_F19; case Qt::Key_F20: return kVK_F10; case Qt::Key_Return: return kVK_Return; case Qt::Key_Enter: return kVK_ANSI_KeypadEnter; case Qt::Key_Tab: return kVK_Tab; case Qt::Key_Space: return kVK_Space; case Qt::Key_Backspace: return kVK_Delete; case Qt::Key_Escape: return kVK_Escape; case Qt::Key_CapsLock: return kVK_CapsLock; case Qt::Key_Option: return kVK_Option; case Qt::Key_VolumeUp: return kVK_VolumeUp; case Qt::Key_VolumeDown: return kVK_VolumeDown; case Qt::Key_Help: return kVK_Help; case Qt::Key_Home: return kVK_Home; case Qt::Key_PageUp: return kVK_PageUp; case Qt::Key_Delete: return kVK_ForwardDelete; case Qt::Key_End: return kVK_End; case Qt::Key_PageDown: return kVK_PageDown; case Qt::Key_Left: return kVK_LeftArrow; case Qt::Key_Right: return kVK_RightArrow; case Qt::Key_Down: return kVK_DownArrow; case Qt::Key_Up: return kVK_UpArrow; default: return 0; } } quint32 GlobalAction::nativeModifiers(Qt::KeyboardModifiers modifiers) { quint32 native = 0; if (modifiers & Qt::ShiftModifier) native |= shiftKey; if (modifiers & Qt::ControlModifier) native |= cmdKey; if (modifiers & Qt::AltModifier) native |= optionKey; if (modifiers & Qt::MetaModifier) native |= controlKey; return native; } #endif // ifdef Q_OS_MAC } // namespace service