From c24fb83460f5fe58421aea1f975d99a532fa60bf Mon Sep 17 00:00:00 2001 From: XMuli Date: Sat, 7 May 2022 19:54:30 +0800 Subject: [PATCH] feat: Shortcut key to summon a new window, Esc does not respond after pressing --- TestHotKey/CMakeLists.txt | 45 +++ TestHotKey/QHotkey/QHotkey | 1 + TestHotKey/QHotkey/QHotkey.pro | 16 ++ TestHotKey/QHotkey/qhotkey.cpp | 377 +++++++++++++++++++++++++ TestHotKey/QHotkey/qhotkey.h | 130 +++++++++ TestHotKey/QHotkey/qhotkey.pri | 1 + TestHotKey/QHotkey/qhotkey_mac.cpp.BAK | 291 +++++++++++++++++++ TestHotKey/QHotkey/qhotkey_p.h | 62 ++++ TestHotKey/QHotkey/qhotkey_win.cpp | 302 ++++++++++++++++++++ TestHotKey/QHotkey/qhotkey_x11.cpp.BAK | 268 ++++++++++++++++++ TestHotKey/Resources.qrc | 5 + TestHotKey/main.cpp | 23 ++ TestHotKey/resources/PicShot_32.svg | 1 + TestHotKey/tray.cpp | 55 ++++ TestHotKey/tray.h | 37 +++ TestHotKey/widget.cpp | 74 +++++ TestHotKey/widget.h | 35 +++ 17 files changed, 1723 insertions(+) create mode 100644 TestHotKey/CMakeLists.txt create mode 100644 TestHotKey/QHotkey/QHotkey create mode 100644 TestHotKey/QHotkey/QHotkey.pro create mode 100644 TestHotKey/QHotkey/qhotkey.cpp create mode 100644 TestHotKey/QHotkey/qhotkey.h create mode 100644 TestHotKey/QHotkey/qhotkey.pri create mode 100644 TestHotKey/QHotkey/qhotkey_mac.cpp.BAK create mode 100644 TestHotKey/QHotkey/qhotkey_p.h create mode 100644 TestHotKey/QHotkey/qhotkey_win.cpp create mode 100644 TestHotKey/QHotkey/qhotkey_x11.cpp.BAK create mode 100644 TestHotKey/Resources.qrc create mode 100644 TestHotKey/main.cpp create mode 100644 TestHotKey/resources/PicShot_32.svg create mode 100644 TestHotKey/tray.cpp create mode 100644 TestHotKey/tray.h create mode 100644 TestHotKey/widget.cpp create mode 100644 TestHotKey/widget.h diff --git a/TestHotKey/CMakeLists.txt b/TestHotKey/CMakeLists.txt new file mode 100644 index 0000000..903c6be --- /dev/null +++ b/TestHotKey/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.5) +set(PROJECT_NAME TestHotKey) + +project(${PROJECT_NAME} VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) + +file(GLOB_RECURSE USER_LIBS_PATH QHotkey/**) + +set(PROJECT_SOURCES + ${USER_LIBS_PATH} + main.cpp + widget.cpp + widget.h + tray.h + tray.cpp + Resources.qrc +) + +include_directories(${CMAKE_SOURCE_DIR}/QHotkey) + +add_executable(${PROJECT_NAME} + ${PROJECT_SOURCES}) + +target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) + +set_target_properties(${PROJECT_NAME} PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(${PROJECT_NAME}) +endif() diff --git a/TestHotKey/QHotkey/QHotkey b/TestHotKey/QHotkey/QHotkey new file mode 100644 index 0000000..21d5e57 --- /dev/null +++ b/TestHotKey/QHotkey/QHotkey @@ -0,0 +1 @@ +#include "qhotkey.h" diff --git a/TestHotKey/QHotkey/QHotkey.pro b/TestHotKey/QHotkey/QHotkey.pro new file mode 100644 index 0000000..3a79376 --- /dev/null +++ b/TestHotKey/QHotkey/QHotkey.pro @@ -0,0 +1,16 @@ +TEMPLATE = lib +win32: CONFIG += dll + +TARGET = QHotkey +VERSION = 1.5.0 + +include(../qhotkey.pri) + +DEFINES += QHOTKEY_SHARED QHOTKEY_LIBRARY + +# use INSTALL_ROOT to modify the install location +headers.files = $$PUBLIC_HEADERS +headers.path = $$[QT_INSTALL_HEADERS] +target.path = $$[QT_INSTALL_LIBS] +INSTALLS += target headers + diff --git a/TestHotKey/QHotkey/qhotkey.cpp b/TestHotKey/QHotkey/qhotkey.cpp new file mode 100644 index 0000000..3b76d9c --- /dev/null +++ b/TestHotKey/QHotkey/qhotkey.cpp @@ -0,0 +1,377 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(logQHotkey, "QHotkey") + +void QHotkey::addGlobalMapping(const QKeySequence &shortcut, QHotkey::NativeShortcut nativeShortcut) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const int key = shortcut[0].toCombined(); +#else + const int key = shortcut[0]; +#endif + + QMetaObject::invokeMethod(QHotkeyPrivate::instance(), "addMappingInvoked", Qt::QueuedConnection, + Q_ARG(Qt::Key, Qt::Key(key & ~Qt::KeyboardModifierMask)), + Q_ARG(Qt::KeyboardModifiers, Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)), + Q_ARG(QHotkey::NativeShortcut, nativeShortcut)); +} + +bool QHotkey::isPlatformSupported() +{ + return QHotkeyPrivate::isPlatformSupported(); +} + +QHotkey::QHotkey(QObject *parent) : + QObject(parent), + _keyCode(Qt::Key_unknown), + _modifiers(Qt::NoModifier), + _registered(false) +{} + +QHotkey::QHotkey(const QKeySequence &shortcut, bool autoRegister, QObject *parent) : + QHotkey(parent) +{ + setShortcut(shortcut, autoRegister); +} + +QHotkey::QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister, QObject *parent) : + QHotkey(parent) +{ + setShortcut(keyCode, modifiers, autoRegister); +} + +QHotkey::QHotkey(QHotkey::NativeShortcut shortcut, bool autoRegister, QObject *parent) : + QHotkey(parent) +{ + setNativeShortcut(shortcut, autoRegister); +} + +QHotkey::~QHotkey() +{ + if(_registered) + QHotkeyPrivate::instance()->removeShortcut(this); +} + +QKeySequence QHotkey::shortcut() const +{ + if(_keyCode == Qt::Key_unknown) + return QKeySequence(); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QKeySequence((_keyCode | _modifiers).toCombined()); +#else + return QKeySequence(static_cast(_keyCode | _modifiers)); +#endif +} + +Qt::Key QHotkey::keyCode() const +{ + return _keyCode; +} + +Qt::KeyboardModifiers QHotkey::modifiers() const +{ + return _modifiers; +} + +QHotkey::NativeShortcut QHotkey::currentNativeShortcut() const +{ + return _nativeShortcut; +} + +bool QHotkey::isRegistered() const +{ + return _registered; +} + +bool QHotkey::setShortcut(const QKeySequence &shortcut, bool autoRegister) +{ + if(shortcut.isEmpty()) + return resetShortcut(); + if(shortcut.count() > 1) { + qCWarning(logQHotkey, "Keysequences with multiple shortcuts are not allowed! " + "Only the first shortcut will be used!"); + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const int key = shortcut[0].toCombined(); +#else + const int key = shortcut[0]; +#endif + + return setShortcut(Qt::Key(key & ~Qt::KeyboardModifierMask), + Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask), + autoRegister); +} + +bool QHotkey::setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister) +{ + if(_registered) { + if(autoRegister) { + if(!QHotkeyPrivate::instance()->removeShortcut(this)) + return false; + } else + return false; + } + + if(keyCode == Qt::Key_unknown) { + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return true; + } + + _keyCode = keyCode; + _modifiers = modifiers; + _nativeShortcut = QHotkeyPrivate::instance()->nativeShortcut(keyCode, modifiers); + if(_nativeShortcut.isValid()) { + if(autoRegister) + return QHotkeyPrivate::instance()->addShortcut(this); + return true; + } + + qCWarning(logQHotkey) << "Unable to map shortcut to native keys. Key:" << keyCode << "Modifiers:" << modifiers; + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return false; +} + +bool QHotkey::resetShortcut() +{ + if(_registered && + !QHotkeyPrivate::instance()->removeShortcut(this)) { + return false; + } + + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return true; +} + +bool QHotkey::setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister) +{ + if(_registered) { + if(autoRegister) { + if(!QHotkeyPrivate::instance()->removeShortcut(this)) + return false; + } else + return false; + } + + if(nativeShortcut.isValid()) { + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = nativeShortcut; + if(autoRegister) + return QHotkeyPrivate::instance()->addShortcut(this); + return true; + } + + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return true; +} + +bool QHotkey::setRegistered(bool registered) +{ + if(_registered && !registered) + return QHotkeyPrivate::instance()->removeShortcut(this); + if(!_registered && registered) { + if(!_nativeShortcut.isValid()) + return false; + return QHotkeyPrivate::instance()->addShortcut(this); + } + return true; +} + + + +// ---------- QHotkeyPrivate implementation ---------- + +QHotkeyPrivate::QHotkeyPrivate() +{ + Q_ASSERT_X(qApp, Q_FUNC_INFO, "QHotkey requires QCoreApplication to be instantiated"); + qApp->eventDispatcher()->installNativeEventFilter(this); +} + +QHotkeyPrivate::~QHotkeyPrivate() +{ + if(!shortcuts.isEmpty()) + qCWarning(logQHotkey) << "QHotkeyPrivate destroyed with registered shortcuts!"; + if(qApp && qApp->eventDispatcher()) + qApp->eventDispatcher()->removeNativeEventFilter(this); +} + +QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers) +{ + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? + Qt::DirectConnection : + Qt::BlockingQueuedConnection); + QHotkey::NativeShortcut res; + if(!QMetaObject::invokeMethod(this, "nativeShortcutInvoked", conType, + Q_RETURN_ARG(QHotkey::NativeShortcut, res), + Q_ARG(Qt::Key, keycode), + Q_ARG(Qt::KeyboardModifiers, modifiers))) { + return QHotkey::NativeShortcut(); + } + return res; +} + +bool QHotkeyPrivate::addShortcut(QHotkey *hotkey) +{ + if(hotkey->_registered) + return false; + + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? + Qt::DirectConnection : + Qt::BlockingQueuedConnection); + bool res = false; + if(!QMetaObject::invokeMethod(this, "addShortcutInvoked", conType, + Q_RETURN_ARG(bool, res), + Q_ARG(QHotkey*, hotkey))) { + return false; + } + + if(res) + emit hotkey->registeredChanged(true); + return res; +} + +bool QHotkeyPrivate::removeShortcut(QHotkey *hotkey) +{ + if(!hotkey->_registered) + return false; + + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? + Qt::DirectConnection : + Qt::BlockingQueuedConnection); + bool res = false; + if(!QMetaObject::invokeMethod(this, "removeShortcutInvoked", conType, + Q_RETURN_ARG(bool, res), + Q_ARG(QHotkey*, hotkey))) { + return false; + } + + if(res) + emit hotkey->registeredChanged(false); + return res; +} + +void QHotkeyPrivate::activateShortcut(QHotkey::NativeShortcut shortcut) +{ + QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::activated); + for(QHotkey *hkey : shortcuts.values(shortcut)) + signal.invoke(hkey, Qt::QueuedConnection); +} + +void QHotkeyPrivate::releaseShortcut(QHotkey::NativeShortcut shortcut) +{ + QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::released); + for(QHotkey *hkey : shortcuts.values(shortcut)) + signal.invoke(hkey, Qt::QueuedConnection); +} + +void QHotkeyPrivate::addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut) +{ + mapping.insert({keycode, modifiers}, nativeShortcut); +} + +bool QHotkeyPrivate::addShortcutInvoked(QHotkey *hotkey) +{ + QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; + + if(!shortcuts.contains(shortcut)) { + if(!registerShortcut(shortcut)) { + qCWarning(logQHotkey) << QHotkey::tr("Failed to register %1. Error: %2").arg(hotkey->shortcut().toString(), error); + return false; + } + } + + shortcuts.insert(shortcut, hotkey); + hotkey->_registered = true; + return true; +} + +bool QHotkeyPrivate::removeShortcutInvoked(QHotkey *hotkey) +{ + QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; + + if(shortcuts.remove(shortcut, hotkey) == 0) + return false; + hotkey->_registered = false; + emit hotkey->registeredChanged(true); + if(shortcuts.count(shortcut) == 0) { + if (!unregisterShortcut(shortcut)) { + qCWarning(logQHotkey) << QHotkey::tr("Failed to unregister %1. Error: %2").arg(hotkey->shortcut().toString(), error); + return false; + } + return true; + } + return true; +} + +QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers) +{ + if(mapping.contains({keycode, modifiers})) + return mapping.value({keycode, modifiers}); + + bool ok1 = false; + auto k = nativeKeycode(keycode, ok1); + bool ok2 = false; + auto m = nativeModifiers(modifiers, ok2); + if(ok1 && ok2) + return {k, m}; + return {}; +} + + + +QHotkey::NativeShortcut::NativeShortcut() : + key(), + modifier(), + valid(false) +{} + +QHotkey::NativeShortcut::NativeShortcut(quint32 key, quint32 modifier) : + key(key), + modifier(modifier), + valid(true) +{} + +bool QHotkey::NativeShortcut::isValid() const +{ + return valid; +} + +bool QHotkey::NativeShortcut::operator ==(QHotkey::NativeShortcut other) const +{ + return (key == other.key) && + (modifier == other.modifier) && + valid == other.valid; +} + +bool QHotkey::NativeShortcut::operator !=(QHotkey::NativeShortcut other) const +{ + return (key != other.key) || + (modifier != other.modifier) || + valid != other.valid; +} + +QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key) +{ + return qHash(key.key) ^ qHash(key.modifier); +} + +QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed) +{ + return qHash(key.key, seed) ^ qHash(key.modifier, seed); +} diff --git a/TestHotKey/QHotkey/qhotkey.h b/TestHotKey/QHotkey/qhotkey.h new file mode 100644 index 0000000..3697c8e --- /dev/null +++ b/TestHotKey/QHotkey/qhotkey.h @@ -0,0 +1,130 @@ +#ifndef QHOTKEY_H +#define QHOTKEY_H + +#include +#include +#include +#include + +#ifdef QHOTKEY_SHARED +# ifdef QHOTKEY_LIBRARY +# define QHOTKEY_EXPORT Q_DECL_EXPORT +# else +# define QHOTKEY_EXPORT Q_DECL_IMPORT +# endif +#else +# define QHOTKEY_EXPORT +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + #define QHOTKEY_HASH_SEED size_t +#else + #define QHOTKEY_HASH_SEED uint +#endif + +//! A class to define global, systemwide Hotkeys +class QHOTKEY_EXPORT QHotkey : public QObject +{ + Q_OBJECT + //! @private + friend class QHotkeyPrivate; + + //! Specifies whether this hotkey is currently registered or not + Q_PROPERTY(bool registered READ isRegistered WRITE setRegistered NOTIFY registeredChanged) + //! Holds the shortcut this hotkey will be triggered on + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut RESET resetShortcut) + +public: + //! Defines shortcut with native keycodes + class QHOTKEY_EXPORT NativeShortcut { + public: + //! The native keycode + quint32 key; + //! The native modifiers + quint32 modifier; + + //! Creates an invalid native shortcut + NativeShortcut(); + //! Creates a valid native shortcut, with the given key and modifiers + NativeShortcut(quint32 key, quint32 modifier = 0); + + //! Checks, whether this shortcut is valid or not + bool isValid() const; + + //! Equality operator + bool operator ==(NativeShortcut other) const; + //! Inequality operator + bool operator !=(NativeShortcut other) const; + + private: + bool valid; + }; + + //! Adds a global mapping of a key sequence to a replacement native shortcut + static void addGlobalMapping(const QKeySequence &shortcut, NativeShortcut nativeShortcut); + + //! Checks if global shortcuts are supported by the current platform + static bool isPlatformSupported(); + + //! Default Constructor + explicit QHotkey(QObject *parent = nullptr); + //! Constructs a hotkey with a shortcut and optionally registers it + explicit QHotkey(const QKeySequence &shortcut, bool autoRegister = false, QObject *parent = nullptr); + //! Constructs a hotkey with a key and modifiers and optionally registers it + explicit QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false, QObject *parent = nullptr); + //! Constructs a hotkey from a native shortcut and optionally registers it + explicit QHotkey(NativeShortcut shortcut, bool autoRegister = false, QObject *parent = nullptr); + ~QHotkey() override; + + //! @readAcFn{QHotkey::registered} + bool isRegistered() const; + //! @readAcFn{QHotkey::shortcut} + QKeySequence shortcut() const; + //! @readAcFn{QHotkey::shortcut} - the key only + Qt::Key keyCode() const; + //! @readAcFn{QHotkey::shortcut} - the modifiers only + Qt::KeyboardModifiers modifiers() const; + + //! Get the current native shortcut + NativeShortcut currentNativeShortcut() const; + +public slots: + //! @writeAcFn{QHotkey::registered} + bool setRegistered(bool registered); + + //! @writeAcFn{QHotkey::shortcut} + bool setShortcut(const QKeySequence &shortcut, bool autoRegister = false); + //! @writeAcFn{QHotkey::shortcut} + bool setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false); + //! @resetAcFn{QHotkey::shortcut} + bool resetShortcut(); + + //! Set this hotkey to a native shortcut + bool setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister = false); + +signals: + //! Will be emitted if the shortcut is pressed + void activated(QPrivateSignal); + + //! Will be emitted if the shortcut press is released + void released(QPrivateSignal); + + //! @notifyAcFn{QHotkey::registered} + void registeredChanged(bool registered); + +private: + Qt::Key _keyCode; + Qt::KeyboardModifiers _modifiers; + + NativeShortcut _nativeShortcut; + bool _registered; +}; + +QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key); +QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed); + +QHOTKEY_EXPORT Q_DECLARE_LOGGING_CATEGORY(logQHotkey) + +Q_DECLARE_METATYPE(QHotkey::NativeShortcut) + +#endif // QHOTKEY_H diff --git a/TestHotKey/QHotkey/qhotkey.pri b/TestHotKey/QHotkey/qhotkey.pri new file mode 100644 index 0000000..a7c9725 --- /dev/null +++ b/TestHotKey/QHotkey/qhotkey.pri @@ -0,0 +1 @@ +message(The pri file has been moved one directory up! use that one instead) diff --git a/TestHotKey/QHotkey/qhotkey_mac.cpp.BAK b/TestHotKey/QHotkey/qhotkey_mac.cpp.BAK new file mode 100644 index 0000000..2cf0db4 --- /dev/null +++ b/TestHotKey/QHotkey/qhotkey_mac.cpp.BAK @@ -0,0 +1,291 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" +#include +#include + +class QHotkeyPrivateMac : public QHotkeyPrivate +{ +public: + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; + + static OSStatus hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); + static OSStatus hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); + +protected: + // QHotkeyPrivate interface + quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; + quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; + bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + +private: + static bool isHotkeyHandlerRegistered; + static QHash hotkeyRefs; +}; +NATIVE_INSTANCE(QHotkeyPrivateMac) + +bool QHotkeyPrivate::isPlatformSupported() +{ + return true; +} + +bool QHotkeyPrivateMac::isHotkeyHandlerRegistered = false; +QHash QHotkeyPrivateMac::hotkeyRefs; + +bool QHotkeyPrivateMac::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(message) + Q_UNUSED(result) + return false; +} + +quint32 QHotkeyPrivateMac::nativeKeycode(Qt::Key keycode, bool &ok) +{ + // Constants found in NSEvent.h from AppKit.framework + ok = true; + switch (keycode) { + 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_F17: + return kVK_F17; + case Qt::Key_VolumeUp: + return kVK_VolumeUp; + case Qt::Key_VolumeDown: + return kVK_VolumeDown; + case Qt::Key_F18: + return kVK_F18; + case Qt::Key_F19: + return kVK_F19; + case Qt::Key_F20: + return kVK_F20; + case Qt::Key_F5: + return kVK_F5; + case Qt::Key_F6: + return kVK_F6; + case Qt::Key_F7: + return kVK_F7; + case Qt::Key_F3: + return kVK_F3; + case Qt::Key_F8: + return kVK_F8; + case Qt::Key_F9: + return kVK_F9; + case Qt::Key_F11: + return kVK_F11; + case Qt::Key_F13: + return kVK_F13; + case Qt::Key_F16: + return kVK_F16; + case Qt::Key_F14: + return kVK_F14; + case Qt::Key_F10: + return kVK_F10; + case Qt::Key_F12: + return kVK_F12; + case Qt::Key_F15: + return kVK_F15; + 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_F4: + return kVK_F4; + case Qt::Key_End: + return kVK_End; + case Qt::Key_F2: + return kVK_F2; + case Qt::Key_PageDown: + return kVK_PageDown; + case Qt::Key_F1: + return kVK_F1; + 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: + ok = false; + break; + } + + UTF16Char ch = keycode; + + CFDataRef currentLayoutData; + TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + + if (currentKeyboard == NULL) + return 0; + + currentLayoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); + CFRelease(currentKeyboard); + if (currentLayoutData == NULL) + return 0; + + UCKeyboardLayout* header = (UCKeyboardLayout*)CFDataGetBytePtr(currentLayoutData); + UCKeyboardTypeHeader* table = header->keyboardTypeList; + + uint8_t *data = (uint8_t*)header; + for (quint32 i=0; i < header->keyboardTypeCount; i++) { + UCKeyStateRecordsIndex* stateRec = 0; + if (table[i].keyStateRecordsIndexOffset != 0) { + stateRec = reinterpret_cast(data + table[i].keyStateRecordsIndexOffset); + if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat) stateRec = 0; + } + + UCKeyToCharTableIndex* charTable = reinterpret_cast(data + table[i].keyToCharTableIndexOffset); + if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat) continue; + + for (quint32 j=0; j < charTable->keyToCharTableCount; j++) { + UCKeyOutput* keyToChar = reinterpret_cast(data + charTable->keyToCharTableOffsets[j]); + for (quint32 k=0; k < charTable->keyToCharTableSize; k++) { + if (keyToChar[k] & kUCKeyOutputTestForIndexMask) { + long idx = keyToChar[k] & kUCKeyOutputGetIndexMask; + if (stateRec && idx < stateRec->keyStateRecordCount) { + UCKeyStateRecord* rec = reinterpret_cast(data + stateRec->keyStateRecordOffsets[idx]); + if (rec->stateZeroCharData == ch) { + ok = true; + return k; + } + } + } + else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) { + if (keyToChar[k] == ch) { + ok = true; + return k; + } + } + } + } + } + return 0; +} + +quint32 QHotkeyPrivateMac::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) +{ + quint32 nMods = 0; + if (modifiers & Qt::ShiftModifier) + nMods |= shiftKey; + if (modifiers & Qt::ControlModifier) + nMods |= cmdKey; + if (modifiers & Qt::AltModifier) + nMods |= optionKey; + if (modifiers & Qt::MetaModifier) + nMods |= controlKey; + if (modifiers & Qt::KeypadModifier) + nMods |= kEventKeyModifierNumLockMask; + ok = true; + return nMods; +} + +bool QHotkeyPrivateMac::registerShortcut(QHotkey::NativeShortcut shortcut) +{ + if (!this->isHotkeyHandlerRegistered) + { + EventTypeSpec pressEventSpec; + pressEventSpec.eventClass = kEventClassKeyboard; + pressEventSpec.eventKind = kEventHotKeyPressed; + InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyPressEventHandler, 1, &pressEventSpec, NULL, NULL); + + EventTypeSpec releaseEventSpec; + releaseEventSpec.eventClass = kEventClassKeyboard; + releaseEventSpec.eventKind = kEventHotKeyReleased; + InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyReleaseEventHandler, 1, &releaseEventSpec, NULL, NULL); + } + + EventHotKeyID hkeyID; + hkeyID.signature = shortcut.key; + hkeyID.id = shortcut.modifier; + + EventHotKeyRef eventRef = 0; + OSStatus status = RegisterEventHotKey(shortcut.key, + shortcut.modifier, + hkeyID, + GetApplicationEventTarget(), + 0, + &eventRef); + if (status != noErr) { + error = QString::number(status); + return false; + } else { + this->hotkeyRefs.insert(shortcut, eventRef); + return true; + } +} + +bool QHotkeyPrivateMac::unregisterShortcut(QHotkey::NativeShortcut shortcut) +{ + EventHotKeyRef eventRef = QHotkeyPrivateMac::hotkeyRefs.value(shortcut); + OSStatus status = UnregisterEventHotKey(eventRef); + if (status != noErr) { + error = QString::number(status); + return false; + } else { + this->hotkeyRefs.remove(shortcut); + return true; + } +} + +OSStatus QHotkeyPrivateMac::hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) +{ + Q_UNUSED(nextHandler); + Q_UNUSED(data); + + if (GetEventClass(event) == kEventClassKeyboard && + GetEventKind(event) == kEventHotKeyPressed) { + EventHotKeyID hkeyID; + GetEventParameter(event, + kEventParamDirectObject, + typeEventHotKeyID, + NULL, + sizeof(EventHotKeyID), + NULL, + &hkeyID); + hotkeyPrivate->activateShortcut({hkeyID.signature, hkeyID.id}); + } + + return noErr; +} + +OSStatus QHotkeyPrivateMac::hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) +{ + Q_UNUSED(nextHandler); + Q_UNUSED(data); + + if (GetEventClass(event) == kEventClassKeyboard && + GetEventKind(event) == kEventHotKeyReleased) { + EventHotKeyID hkeyID; + GetEventParameter(event, + kEventParamDirectObject, + typeEventHotKeyID, + NULL, + sizeof(EventHotKeyID), + NULL, + &hkeyID); + hotkeyPrivate->releaseShortcut({hkeyID.signature, hkeyID.id}); + } + + return noErr; +} diff --git a/TestHotKey/QHotkey/qhotkey_p.h b/TestHotKey/QHotkey/qhotkey_p.h new file mode 100644 index 0000000..8bc5ab6 --- /dev/null +++ b/TestHotKey/QHotkey/qhotkey_p.h @@ -0,0 +1,62 @@ +#ifndef QHOTKEY_P_H +#define QHOTKEY_P_H + +#include "qhotkey.h" +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + #define _NATIVE_EVENT_RESULT qintptr +#else + #define _NATIVE_EVENT_RESULT long +#endif + +class QHOTKEY_EXPORT QHotkeyPrivate : public QObject, public QAbstractNativeEventFilter +{ + Q_OBJECT + +public: + QHotkeyPrivate();//singleton!!! + ~QHotkeyPrivate(); + + static QHotkeyPrivate *instance(); + static bool isPlatformSupported(); + + QHotkey::NativeShortcut nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers); + + bool addShortcut(QHotkey *hotkey); + bool removeShortcut(QHotkey *hotkey); + +protected: + void activateShortcut(QHotkey::NativeShortcut shortcut); + void releaseShortcut(QHotkey::NativeShortcut shortcut); + + virtual quint32 nativeKeycode(Qt::Key keycode, bool &ok) = 0;//platform implement + virtual quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) = 0;//platform implement + + virtual bool registerShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement + virtual bool unregisterShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement + + QString error; + +private: + QHash, QHotkey::NativeShortcut> mapping; + QMultiHash shortcuts; + + Q_INVOKABLE void addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut); + Q_INVOKABLE bool addShortcutInvoked(QHotkey *hotkey); + Q_INVOKABLE bool removeShortcutInvoked(QHotkey *hotkey); + Q_INVOKABLE QHotkey::NativeShortcut nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers); +}; + +#define NATIVE_INSTANCE(ClassName) \ + Q_GLOBAL_STATIC(ClassName, hotkeyPrivate) \ + \ + QHotkeyPrivate *QHotkeyPrivate::instance()\ + {\ + return hotkeyPrivate;\ + } + +#endif // QHOTKEY_P_H diff --git a/TestHotKey/QHotkey/qhotkey_win.cpp b/TestHotKey/QHotkey/qhotkey_win.cpp new file mode 100644 index 0000000..715b92e --- /dev/null +++ b/TestHotKey/QHotkey/qhotkey_win.cpp @@ -0,0 +1,302 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" +#include +#include +#include + +#define HKEY_ID(nativeShortcut) (((nativeShortcut.key ^ (nativeShortcut.modifier << 8)) & 0x0FFF) | 0x7000) + +#if !defined(MOD_NOREPEAT) +#define MOD_NOREPEAT 0x4000 +#endif + +class QHotkeyPrivateWin : public QHotkeyPrivate +{ +public: + QHotkeyPrivateWin(); + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; + +protected: + void pollForHotkeyRelease(); + // QHotkeyPrivate interface + quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; + quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; + bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + +private: + static QString formatWinError(DWORD winError); + QTimer pollTimer; + QHotkey::NativeShortcut polledShortcut; +}; +NATIVE_INSTANCE(QHotkeyPrivateWin) + +QHotkeyPrivateWin::QHotkeyPrivateWin(){ + pollTimer.setInterval(50); + connect(&pollTimer, &QTimer::timeout, this, &QHotkeyPrivateWin::pollForHotkeyRelease); +} + +bool QHotkeyPrivate::isPlatformSupported() +{ + return true; +} + +bool QHotkeyPrivateWin::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(result) + + MSG* msg = static_cast(message); + if(msg->message == WM_HOTKEY) { + QHotkey::NativeShortcut shortcut = {HIWORD(msg->lParam), LOWORD(msg->lParam)}; + this->activateShortcut(shortcut); + this->polledShortcut = shortcut; + this->pollTimer.start(); + } + + return false; +} + +void QHotkeyPrivateWin::pollForHotkeyRelease() +{ + bool pressed = (GetAsyncKeyState(this->polledShortcut.key) & (1 << 15)) != 0; + if(!pressed) { + this->pollTimer.stop(); + this->releaseShortcut(this->polledShortcut); + } +} + +quint32 QHotkeyPrivateWin::nativeKeycode(Qt::Key keycode, bool &ok) +{ + ok = true; + if(keycode <= 0xFFFF) {//Try to obtain the key from it's "character" + const SHORT vKey = VkKeyScanW(static_cast(keycode)); + if(vKey > -1) + return LOBYTE(vKey); + } + + //find key from switch/case --> Only finds a very small subset of keys + switch (keycode) + { + 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_Menu: + return VK_APPS; + case Qt::Key_Help: + return VK_HELP; + 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; + case Qt::Key_Mode_switch: + return VK_MODECHANGE; + case Qt::Key_Select: + return VK_SELECT; + case Qt::Key_Printer: + return VK_PRINT; + case Qt::Key_Execute: + return VK_EXECUTE; + case Qt::Key_Sleep: + return VK_SLEEP; + case Qt::Key_Period: + return VK_DECIMAL; + case Qt::Key_Play: + return VK_PLAY; + case Qt::Key_Cancel: + return VK_CANCEL; + + case Qt::Key_Forward: + return VK_BROWSER_FORWARD; + case Qt::Key_Refresh: + return VK_BROWSER_REFRESH; + case Qt::Key_Stop: + return VK_BROWSER_STOP; + case Qt::Key_Search: + return VK_BROWSER_SEARCH; + case Qt::Key_Favorites: + return VK_BROWSER_FAVORITES; + case Qt::Key_HomePage: + return VK_BROWSER_HOME; + + case Qt::Key_LaunchMail: + return VK_LAUNCH_MAIL; + case Qt::Key_LaunchMedia: + return VK_LAUNCH_MEDIA_SELECT; + case Qt::Key_Launch0: + return VK_LAUNCH_APP1; + case Qt::Key_Launch1: + return VK_LAUNCH_APP2; + + case Qt::Key_Massyo: + return VK_OEM_FJ_MASSHOU; + case Qt::Key_Touroku: + return VK_OEM_FJ_TOUROKU; + + default: + if(keycode <= 0xFFFF) + return (byte)keycode; + else { + ok = false; + return 0; + } + } +} + +quint32 QHotkeyPrivateWin::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) +{ + quint32 nMods = 0; + if (modifiers & Qt::ShiftModifier) + nMods |= MOD_SHIFT; + if (modifiers & Qt::ControlModifier) + nMods |= MOD_CONTROL; + if (modifiers & Qt::AltModifier) + nMods |= MOD_ALT; + if (modifiers & Qt::MetaModifier) + nMods |= MOD_WIN; + ok = true; + return nMods; +} + +bool QHotkeyPrivateWin::registerShortcut(QHotkey::NativeShortcut shortcut) +{ + BOOL ok = RegisterHotKey(NULL, + HKEY_ID(shortcut), + shortcut.modifier + MOD_NOREPEAT, + shortcut.key); + if(ok) + return true; + else { + error = QHotkeyPrivateWin::formatWinError(::GetLastError()); + return false; + } +} + +bool QHotkeyPrivateWin::unregisterShortcut(QHotkey::NativeShortcut shortcut) +{ + BOOL ok = UnregisterHotKey(NULL, HKEY_ID(shortcut)); + if(ok) + return true; + else { + error = QHotkeyPrivateWin::formatWinError(::GetLastError()); + return false; + } +} + +QString QHotkeyPrivateWin::formatWinError(DWORD winError) +{ + wchar_t *buffer = NULL; + DWORD num = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + winError, + 0, + (LPWSTR)&buffer, + 0, + NULL); + if(buffer) { + QString res = QString::fromWCharArray(buffer, num); + LocalFree(buffer); + return res; + } else + return QString(); +} diff --git a/TestHotKey/QHotkey/qhotkey_x11.cpp.BAK b/TestHotKey/QHotkey/qhotkey_x11.cpp.BAK new file mode 100644 index 0000000..ce408e6 --- /dev/null +++ b/TestHotKey/QHotkey/qhotkey_x11.cpp.BAK @@ -0,0 +1,268 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" + +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + #include +#else + #include + #include +#endif + +#include +#include +#include +#include + +//compability to pre Qt 5.8 +#ifndef Q_FALLTHROUGH +#define Q_FALLTHROUGH() (void)0 +#endif + +class QHotkeyPrivateX11 : public QHotkeyPrivate +{ +public: + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; + +protected: + // QHotkeyPrivate interface + quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; + quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; + static QString getX11String(Qt::Key keycode); + bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + +private: + static const QVector specialModifiers; + static const quint32 validModsMask; + xcb_key_press_event_t prevHandledEvent; + xcb_key_press_event_t prevEvent; + + static QString formatX11Error(Display *display, int errorCode); + + class HotkeyErrorHandler { + public: + HotkeyErrorHandler(); + ~HotkeyErrorHandler(); + + static bool hasError; + static QString errorString; + + private: + XErrorHandler prevHandler; + + static int handleError(Display *display, XErrorEvent *error); + }; +}; +NATIVE_INSTANCE(QHotkeyPrivateX11) + +bool QHotkeyPrivate::isPlatformSupported() +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + return qGuiApp->nativeInterface(); +#else + return QX11Info::isPlatformX11(); +#endif +} + +const QVector QHotkeyPrivateX11::specialModifiers = {0, Mod2Mask, LockMask, (Mod2Mask | LockMask)}; +const quint32 QHotkeyPrivateX11::validModsMask = ShiftMask | ControlMask | Mod1Mask | Mod4Mask; + +bool QHotkeyPrivateX11::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(result) + + auto *genericEvent = static_cast(message); + if (genericEvent->response_type == XCB_KEY_PRESS) { + xcb_key_press_event_t keyEvent = *static_cast(message); + this->prevEvent = keyEvent; + if (this->prevHandledEvent.response_type == XCB_KEY_RELEASE) { + if(this->prevHandledEvent.time == keyEvent.time) return false; + } + this->activateShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask}); + } else if (genericEvent->response_type == XCB_KEY_RELEASE) { + xcb_key_release_event_t keyEvent = *static_cast(message); + this->prevEvent = keyEvent; + QTimer::singleShot(50, [this, keyEvent] { + if(this->prevEvent.time == keyEvent.time && this->prevEvent.response_type == keyEvent.response_type && this->prevEvent.detail == keyEvent.detail){ + this->releaseShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask}); + } + }); + this->prevHandledEvent = keyEvent; + } + + return false; +} + +QString QHotkeyPrivateX11::getX11String(Qt::Key keycode) +{ + switch(keycode){ + + case Qt::Key_MediaLast : + case Qt::Key_MediaPrevious : + return QStringLiteral("XF86AudioPrev"); + case Qt::Key_MediaNext : + return QStringLiteral("XF86AudioNext"); + case Qt::Key_MediaPause : + case Qt::Key_MediaPlay : + case Qt::Key_MediaTogglePlayPause : + return QStringLiteral("XF86AudioPlay"); + case Qt::Key_MediaRecord : + return QStringLiteral("XF86AudioRecord"); + case Qt::Key_MediaStop : + return QStringLiteral("XF86AudioStop"); + default : + return QKeySequence(keycode).toString(QKeySequence::NativeText); + } +} + +quint32 QHotkeyPrivateX11::nativeKeycode(Qt::Key keycode, bool &ok) +{ + QString keyString = getX11String(keycode); + + KeySym keysym = XStringToKeysym(keyString.toLatin1().constData()); + if (keysym == NoSymbol) { + //not found -> just use the key + if(keycode <= 0xFFFF) + keysym = keycode; + else + return 0; + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface(); + Display *display = x11Interface->display(); +#else + const bool x11Interface = QX11Info::isPlatformX11(); + Display *display = QX11Info::display(); +#endif + + if(x11Interface) { + auto res = XKeysymToKeycode(display, keysym); + if(res != 0) + ok = true; + return res; + } + return 0; +} + +quint32 QHotkeyPrivateX11::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) +{ + quint32 nMods = 0; + if (modifiers & Qt::ShiftModifier) + nMods |= ShiftMask; + if (modifiers & Qt::ControlModifier) + nMods |= ControlMask; + if (modifiers & Qt::AltModifier) + nMods |= Mod1Mask; + if (modifiers & Qt::MetaModifier) + nMods |= Mod4Mask; + ok = true; + return nMods; +} + +bool QHotkeyPrivateX11::registerShortcut(QHotkey::NativeShortcut shortcut) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface(); + Display *display = x11Interface->display(); +#else + const bool x11Interface = QX11Info::isPlatformX11(); + Display *display = QX11Info::display(); +#endif + + if(!display || !x11Interface) + return false; + + HotkeyErrorHandler errorHandler; + for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { + XGrabKey(display, + shortcut.key, + shortcut.modifier | specialMod, + DefaultRootWindow(display), + True, + GrabModeAsync, + GrabModeAsync); + } + XSync(display, False); + + if(errorHandler.hasError) { + error = errorHandler.errorString; + this->unregisterShortcut(shortcut); + return false; + } + return true; +} + +bool QHotkeyPrivateX11::unregisterShortcut(QHotkey::NativeShortcut shortcut) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + Display *display = qGuiApp->nativeInterface()->display(); +#else + Display *display = QX11Info::display(); +#endif + + if(!display) + return false; + + HotkeyErrorHandler errorHandler; + for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { + XUngrabKey(display, + shortcut.key, + shortcut.modifier | specialMod, + XDefaultRootWindow(display)); + } + XSync(display, False); + + if(HotkeyErrorHandler::hasError) { + error = HotkeyErrorHandler::errorString; + return false; + } + return true; +} + +QString QHotkeyPrivateX11::formatX11Error(Display *display, int errorCode) +{ + char errStr[256]; + XGetErrorText(display, errorCode, errStr, 256); + return QString::fromLatin1(errStr); +} + + + +// ---------- QHotkeyPrivateX11::HotkeyErrorHandler implementation ---------- + +bool QHotkeyPrivateX11::HotkeyErrorHandler::hasError = false; +QString QHotkeyPrivateX11::HotkeyErrorHandler::errorString; + +QHotkeyPrivateX11::HotkeyErrorHandler::HotkeyErrorHandler() +{ + prevHandler = XSetErrorHandler(&HotkeyErrorHandler::handleError); +} + +QHotkeyPrivateX11::HotkeyErrorHandler::~HotkeyErrorHandler() +{ + XSetErrorHandler(prevHandler); + hasError = false; + errorString.clear(); +} + +int QHotkeyPrivateX11::HotkeyErrorHandler::handleError(Display *display, XErrorEvent *error) +{ + switch (error->error_code) { + case BadAccess: + case BadValue: + case BadWindow: + if (error->request_code == 33 || //grab key + error->request_code == 34) {// ungrab key + hasError = true; + errorString = QHotkeyPrivateX11::formatX11Error(display, error->error_code); + return 1; + } + Q_FALLTHROUGH(); + // fall through + default: + return 0; + } +} diff --git a/TestHotKey/Resources.qrc b/TestHotKey/Resources.qrc new file mode 100644 index 0000000..fba7c13 --- /dev/null +++ b/TestHotKey/Resources.qrc @@ -0,0 +1,5 @@ + + + resources/PicShot_32.svg + + diff --git a/TestHotKey/main.cpp b/TestHotKey/main.cpp new file mode 100644 index 0000000..6285fe3 --- /dev/null +++ b/TestHotKey/main.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019~2022 偕臧 All rights reserved. + * + * Author: XMuli xmulitech@gmail.com + * + * GitHub: https://github.com/XMuli + * Blogs: https://ifmet.cn + * CSDN: https://blog.csdn.net/qq_33154343 + */ +#include "widget.h" + +#include +#include "tray.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + Tray* tary = new Tray(); + QApplication::setQuitOnLastWindowClosed(false); + + return a.exec(); +} diff --git a/TestHotKey/resources/PicShot_32.svg b/TestHotKey/resources/PicShot_32.svg new file mode 100644 index 0000000..528939b --- /dev/null +++ b/TestHotKey/resources/PicShot_32.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/TestHotKey/tray.cpp b/TestHotKey/tray.cpp new file mode 100644 index 0000000..29bbb49 --- /dev/null +++ b/TestHotKey/tray.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019~2022 偕臧 All rights reserved. + * + * Author: XMuli xmulitech@gmail.com + * + * GitHub: https://github.com/XMuli + * Blogs: https://ifmet.cn + * CSDN: https://blog.csdn.net/qq_33154343 + */ +#include "tray.h" + +#include +#include +#include +#include "widget.h" + +Tray::Tray(QObject *parent) + : QObject(parent) + , m_screenShot(nullptr) + , m_quit(nullptr) + , m_menuTary(nullptr) + , m_sysTary(nullptr) + , m_hkScrnShot(new QHotkey(QKeySequence("f6"), true, qApp)) +{ + m_screenShot = new QAction(tr("ScreenShot"), this); + m_quit = new QAction(tr("Quit"), this); + + m_menuTary = new QMenu(); + m_menuTary->addAction(m_screenShot); + m_menuTary->addSeparator(); + m_menuTary->addAction(m_quit); + + m_sysTary = new QSystemTrayIcon(this); + m_sysTary->setIcon(QIcon(":/resources/PicShot_32.svg")); + m_sysTary->setToolTip(tr("PicShot Test")); + m_sysTary->setContextMenu(m_menuTary); + m_sysTary->setVisible(true); + + connect(m_hkScrnShot, &QHotkey::activated, this, &Tray::onScreenShot); + + connect(m_screenShot, &QAction::triggered, this, &Tray::onScreenShot); + connect(m_quit, &QAction::triggered, []() { + qApp->quit(); + }); +} + +void Tray::onScreenShot() +{ + auto& ins = Widget::instance(); + ins.show(); + + // 解决方案: show() 之后,设置为激活窗口即可 + if(!ins.isActiveWindow()) + ins.activateWindow(); +} diff --git a/TestHotKey/tray.h b/TestHotKey/tray.h new file mode 100644 index 0000000..d826da5 --- /dev/null +++ b/TestHotKey/tray.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019~2022 偕臧 All rights reserved. + * + * Author: XMuli xmulitech@gmail.com + * + * GitHub: https://github.com/XMuli + * Blog: https://ifmet.cn + * CSDN: https://blog.csdn.net/qq_33154343 + */ +#ifndef TRAY_H +#define TRAY_H + +#include +#include +#include +#include +#include + +class Tray : public QObject +{ + Q_OBJECT +public: + explicit Tray(QObject *parent = nullptr); + +public slots: + void onScreenShot(); + +private: + QAction* m_screenShot; + QAction* m_quit; + QMenu* m_menuTary; + QSystemTrayIcon* m_sysTary; + + QHotkey* m_hkScrnShot; // 热键 +}; + +#endif // TRAY_H diff --git a/TestHotKey/widget.cpp b/TestHotKey/widget.cpp new file mode 100644 index 0000000..258e9f5 --- /dev/null +++ b/TestHotKey/widget.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019~2022 偕臧 All rights reserved. + * + * Author: XMuli xmulitech@gmail.com + * + * GitHub: https://github.com/XMuli + * Blog: https://ifmet.cn + * CSDN: https://blog.csdn.net/qq_33154343 + */ +#include "widget.h" + +#include +#include +#include +#include +#include +#include + +Widget::Widget(QWidget *parent) + : QWidget(parent) +{ + setFocusPolicy(Qt::StrongFocus); + + QDesktopWidget *desktop = QApplication::desktop(); // 获取桌面的窗体对象 + const QRect geom = desktop->geometry(); // 多屏的矩形取并集 + setWindowFlags(Qt::FramelessWindowHint /*| Qt::WindowStaysOnTopHint */| windowFlags()); // 去掉标题栏 + 置顶 + +// setAttribute(Qt::WA_ShowWithoutActivating,true); +// setFocusPolicy(Qt::StrongFocus); +// setFixedSize(QSize(geom.size().width() / 4, geom.size().height())); + setFixedSize(QSize(512, geom.size().height())); + setMouseTracking(true); +} + +Widget &Widget::instance() +{ + static Widget m_instance; + return m_instance; +} + +void Widget::getScrnShots() +{ +// QWidget::setFocus(); + show(); +} + +Widget::~Widget() +{ +} + +void Widget::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) { + qDebug() << "Key_Escape"; + hide(); + //close(); // 销毁会有问题,已经排查:1. tray 有关,改用 qpushbutton 和 close即可; 2.单例有关,该市建议修改为 new 指针的比较合适 + } else if (event->key() == Qt::Key_A) { + qDebug() << "Key_A"; + } +} + +void Widget::mouseMoveEvent(QMouseEvent *event) +{ + m_pos = event->globalPos(); + + update(); +} + +void Widget::paintEvent(QPaintEvent *event) +{ + QPainter pa(this); + pa.drawText(100, 200, QString("m_pos(%1, %2)").arg(m_pos.x()).arg(m_pos.y())); +} + diff --git a/TestHotKey/widget.h b/TestHotKey/widget.h new file mode 100644 index 0000000..1397be8 --- /dev/null +++ b/TestHotKey/widget.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019~2022 偕臧 All rights reserved. + * + * Author: XMuli xmulitech@gmail.com + * + * GitHub: https://github.com/XMuli + * Blogs: https://ifmet.cn + * CSDN: https://blog.csdn.net/qq_33154343 + */ +#ifndef WIDGET_H +#define WIDGET_H + +#include + +class Widget : public QWidget +{ + Q_OBJECT + +public: + static Widget& instance(); + void getScrnShots(); + ~Widget(); + +private: + Widget(QWidget *parent = nullptr); + +protected: + virtual void keyPressEvent(QKeyEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + virtual void paintEvent(QPaintEvent *event) override; + +private: + QPoint m_pos; +}; +#endif // WIDGET_H