diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..89d7c90 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,56 @@ +name: Build + +on: + push: + branches-ignore: + - "releases/**" + paths-ignore: + - "**.md" + pull_request: + paths-ignore: + - "**.md" + +jobs: + build: + strategy: + matrix: + qt_version: [5.12.12, 5.15.2, 6.2.2] + platform: [ubuntu-20.04, windows-latest, macos-latest] + include: + - qt_version: 6.2.2 + additional_arguments: -D QT_DEFAULT_MAJOR_VERSION=6 + - platform: ubuntu-20.04 + make: make + CXXFLAGS: -Wall -Wextra -pedantic -Werror + MAKEFLAGS: -j2 + - platform: macos-latest + make: make + CXXFLAGS: -Wall -Wextra -pedantic -Werror -Wno-gnu-zero-variadic-macro-arguments # Ignore false-positive warning for qCWarning + MAKEFLAGS: -j3 + - platform: windows-latest + make: nmake + CXXFLAGS: /W4 /WX /MP + + runs-on: ${{ matrix.platform }} + env: + CXXFLAGS: ${{ matrix.CXXFLAGS }} + MAKEFLAGS: ${{ matrix.MAKEFLAGS }} + + steps: + - name: Clone repo + uses: actions/checkout@v2.3.4 + + - name: Install Qt + uses: jurplel/install-qt-action@v3.3.0 + with: + version: ${{ matrix.qt_version }} + + - name: Build with CMake as static + run: | + cmake . -D QHOTKEY_EXAMPLES=ON -D CMAKE_OSX_ARCHITECTURES="x86_64" ${{ matrix.additional_arguments }} + cmake --build . + + - name: Build with CMake as shared + run: | + cmake . -D BUILD_SHARED_LIBS=ON -D QHOTKEY_EXAMPLES=ON -D CMAKE_OSX_ARCHITECTURES="x86_64" ${{ matrix.additional_arguments }} + cmake --build . diff --git a/CMakeLists.txt b/CMakeLists.txt index 41f2037..32cda4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,70 +1,117 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.5) -project(qhotkey VERSION 1.2.2 LANGUAGES CXX) +project(qhotkey + VERSION 1.5.0 + DESCRIPTION "Global hotkey library for Qt software" + HOMEPAGE_URL "https://skycoder42.github.io/QHotkey/" + LANGUAGES CXX) -option(QHOTKEY_EXAMPLES "Build examples" ON) +option(QHOTKEY_EXAMPLES "Build examples" OFF) +option(QHOTKEY_INSTALL "Enable install rule" ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_AUTOMOC ON) -find_package(Qt5 COMPONENTS Core Widgets REQUIRED) +if(NOT QT_DEFAULT_MAJOR_VERSION) + set(QT_DEFAULT_MAJOR_VERSION 5 CACHE STRING "Qt version to use (5 or 6), defaults to 5") +endif() + +if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + find_package(Qt${QT_DEFAULT_MAJOR_VERSION} 6.2.0 COMPONENTS Core Gui REQUIRED) +else() + find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Core Gui REQUIRED) +endif() -qt5_wrap_cpp(MOC_HEADERS - QHotkey/qhotkey.h - QHotkey/qhotkey_p.h) +# General settings +set(CPACK_PACKAGE_VENDOR "Skycoder42") +set(CPACK_PACKAGE_CONTACT "Shatur") +set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") +# CPACK: DEB Specific Settings +set(CPACK_DEBIAN_PACKAGE_NAME "libqhotkey") +set(CPACK_DEBIAN_PACKAGE_SECTION "Libraries") +# Set dependencies +if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6x11extras6 (>= 6.2.0)") +else() + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5x11extras5 (>= 5.15.2)") +endif() +include(CPack) -set(LIBS - Qt5::Core - Qt5::Widgets) +add_library(qhotkey QHotkey/qhotkey.cpp) +add_library(QHotkey::QHotkey ALIAS qhotkey) +target_link_libraries(qhotkey PUBLIC Qt${QT_DEFAULT_MAJOR_VERSION}::Core Qt${QT_DEFAULT_MAJOR_VERSION}::Gui) -set(SRC_FILES - QHotkey/qhotkey.cpp) +target_compile_definitions(qhotkey PRIVATE QT_NO_SIGNALS_SLOTS_KEYWORDS) + +if(BUILD_SHARED_LIBS) + target_compile_definitions(qhotkey PRIVATE QHOTKEY_LIBRARY) + target_compile_definitions(qhotkey PUBLIC QHOTKEY_SHARED) +endif() if(APPLE) find_library(CARBON_LIBRARY Carbon) mark_as_advanced(CARBON_LIBRARY) - set(SRC_FILES ${SRC_FILES} QHotkey/qhotkey_mac.cpp) - set(LIBS ${LIBS} ${CARBON_LIBRARY}) + target_sources(qhotkey PRIVATE QHotkey/qhotkey_mac.cpp) + target_link_libraries(qhotkey PRIVATE ${CARBON_LIBRARY}) elseif(WIN32) - set(SRC_FILES ${SRC_FILES} QHotkey/qhotkey_win.cpp) + target_sources(qhotkey PRIVATE QHotkey/qhotkey_win.cpp) else() find_package(X11 REQUIRED) - find_package(Qt5X11Extras REQUIRED) + if(QT_DEFAULT_MAJOR_VERSION GREATER_EQUAL 6) + target_link_libraries(qhotkey PRIVATE ${X11_LIBRARIES}) + else() + find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS X11Extras REQUIRED) + target_link_libraries(qhotkey + PRIVATE + ${X11_LIBRARIES} + Qt${QT_DEFAULT_MAJOR_VERSION}::X11Extras) + endif() include_directories(${X11_INCLUDE_DIR}) - set(LIBS ${LIBS} ${X11_LIBRARIES} Qt5::X11Extras) - set(SRC_FILES ${SRC_FILES} QHotkey/qhotkey_x11.cpp) + target_sources(qhotkey PRIVATE QHotkey/qhotkey_x11.cpp) endif() -add_library(qhotkey ${SRC_FILES} ${MOC_HEADERS}) -add_library(QHotkey::QHotkey ALIAS qhotkey) -target_link_libraries(qhotkey ${LIBS}) +include(GNUInstallDirs) target_include_directories(qhotkey PUBLIC - $ - $) + $ + $) + +include(CMakePackageConfigHelpers) set_target_properties(qhotkey PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR} - VERSION ${PROJECT_VERSION}) + VERSION ${PROJECT_VERSION} + INTERFACE_QHotkey_MAJOR_VERSION ${PROJECT_VERSION_MAJOR} + COMPATIBLE_INTERFACE_STRING QHotkey_MAJOR_VERSION) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/QHotkeyConfigVersion.cmake + VERSION "${PROJECT_VERSION}" + COMPATIBILITY AnyNewerVersion) if(QHOTKEY_EXAMPLES) add_subdirectory(HotkeyTest) endif() -include(GNUInstallDirs) -set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/QHotkey) - -install( - TARGETS qhotkey EXPORT QHotkeyConfig - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(FILES - ${CMAKE_SOURCE_DIR}/QHotkey/qhotkey.h - ${CMAKE_SOURCE_DIR}/QHotkey/QHotkey - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/) -install(EXPORT QHotkeyConfig DESTINATION ${INSTALL_CONFIGDIR}) - -export(TARGETS qhotkey FILE QHotkeyConfig.cmake) +if(QHOTKEY_INSTALL) + set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/QHotkey) + + install( + TARGETS qhotkey EXPORT QHotkeyConfig + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/QHotkey/qhotkey.h + ${CMAKE_CURRENT_SOURCE_DIR}/QHotkey/QHotkey + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/QHotkeyConfigVersion.cmake + DESTINATION ${INSTALL_CONFIGDIR}) + install(EXPORT QHotkeyConfig DESTINATION ${INSTALL_CONFIGDIR}) + + export(TARGETS qhotkey FILE QHotkeyConfig.cmake) +endif() diff --git a/HotkeyTest/CMakeLists.txt b/HotkeyTest/CMakeLists.txt index 67dfc8e..714585d 100644 --- a/HotkeyTest/CMakeLists.txt +++ b/HotkeyTest/CMakeLists.txt @@ -1,12 +1,11 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) -qt5_wrap_ui(test_UI_HEADERS hottestwidget.ui) -qt5_wrap_cpp(test_MOC_HEADERS hottestwidget.h) +set(CMAKE_AUTOUIC ON) add_executable(HotkeyTest main.cpp hottestwidget.cpp - ${test_UI_HEADERS} - ${test_MOC_HEADERS}) -target_link_libraries(HotkeyTest Qt5::Widgets qhotkey) -target_include_directories(HotkeyTest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + hottestwidget.ui) + +find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Widgets REQUIRED) +target_link_libraries(HotkeyTest Qt${QT_DEFAULT_MAJOR_VERSION}::Widgets QHotkey::QHotkey) diff --git a/HotkeyTest/HotkeyTest.pro b/HotkeyTest/HotkeyTest.pro deleted file mode 100644 index a43f662..0000000 --- a/HotkeyTest/HotkeyTest.pro +++ /dev/null @@ -1,21 +0,0 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2016-02-05T22:01:03 -# -#------------------------------------------------- - -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = HotkeyTest -TEMPLATE = app - -include(../qhotkey.pri) - -SOURCES += main.cpp\ - hottestwidget.cpp - -HEADERS += hottestwidget.h - -FORMS += hottestwidget.ui diff --git a/HotkeyTest/hottestwidget.ui b/HotkeyTest/hottestwidget.ui index c1db840..8da0d92 100644 --- a/HotkeyTest/hottestwidget.ui +++ b/HotkeyTest/hottestwidget.ui @@ -769,7 +769,7 @@ - <b>Testing:</b> Please press the combinations listed below to check whether they work properly or not. Everytime a shortcut is triggered, the checkbox will toggle it's value. Set the test active to begin. + <b>Testing:</b> Please press the combinations listed below to check whether they work properly or not. Every time a shortcut is triggered, the checkbox will toggle it's value. Set the test active to begin. true @@ -927,7 +927,7 @@ - <html><head/><body><p>This test was designed to try out multi-threaded shortcuts. The QHotkey class is completly <span style=" font-weight:600;">threadsafe</span>, but this test can help to see if it acutally works (It does).</p><p>If activated, <span style=" font-style:italic;">Hotkey 4 and Hotkey 5 </span>of the Playground will each run on their own thread. This means:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" text-decoration: underline;">Mainthread:</span> Hotkey 1, 2, 3</li><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" text-decoration: underline;">Second thread:</span> Hotkey 4</li><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" text-decoration: underline;">Third thread:</span> Hotkey 5</li></ul><p><span style=" font-weight:600;">Note:</span> The two hotkeys will be moved to the threads. For simplicity-reasons, you can't move them back in this test (But its possible, just not done here). Restart the test to get them back.</p></body></html> + <html><head/><body><p>This test was designed to try out multi-threaded shortcuts. The QHotkey class is completely <span style=" font-weight:600;">threadsafe</span>, but this test can help to see if it actually works (It does).</p><p>If activated, <span style=" font-style:italic;">Hotkey 4 and Hotkey 5 </span>of the Playground will each run on their own thread. This means:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" text-decoration: underline;">Mainthread:</span> Hotkey 1, 2, 3</li><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" text-decoration: underline;">Second thread:</span> Hotkey 4</li><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" text-decoration: underline;">Third thread:</span> Hotkey 5</li></ul><p><span style=" font-weight:600;">Note:</span> The two hotkeys will be moved to the threads. For simplicity-reasons, you can't move them back in this test (But its possible, just not done here). Restart the test to get them back.</p></body></html> Qt::RichText @@ -973,7 +973,7 @@ - <html><head/><body><p>QHotkey allows you to set native shortcuts explicitly. These, of course, only work on the platform they were choosen for. All platform use special constants for their key codes and modifiers, which makes it pretty simple to use them from code. If you want to test them out here, google for the tables.</p><p>In most cases, you will not need to specify native shortcuts directly. However, as explaind on previos tabs, some shotcuts may not be creatable from Qt's key (e.g. Numblock numbers). In that case, you can set the directly.</p><p><span style=" text-decoration: underline;">Example: Ctrl+A</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Windows:</span> Key: <span style=" font-style:italic;">0x0041</span>, Modifier: <span style=" font-style:italic;">0x0002</span></li><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">X11:</span> Key: <span style=" font-style:italic;">0x0026</span>, Modifier: <span style=" font-style:italic;">0x0004</span></li><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">OsX:</span> Key: <span style=" font-style:italic;">0x0000</span>, Modifier: <span style=" font-style:italic;">0x0100</span><span style=" text-decoration: underline;"><br/></span></li></ul></body></html> + <html><head/><body><p>QHotkey allows you to set native shortcuts explicitly. These, of course, only work on the platform they were chosen for. All platform use special constants for their key codes and modifiers, which makes it pretty simple to use them from code. If you want to test them out here, google for the tables.</p><p>In most cases, you will not need to specify native shortcuts directly. However, as explained on previous tabs, some shortcuts may not be creatable from Qt's key (e.g. Numblock numbers). In that case, you can set the directly.</p><p><span style=" text-decoration: underline;">Example: Ctrl+A</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Windows:</span> Key: <span style=" font-style:italic;">0x0041</span>, Modifier: <span style=" font-style:italic;">0x0002</span></li><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">X11:</span> Key: <span style=" font-style:italic;">0x0026</span>, Modifier: <span style=" font-style:italic;">0x0004</span></li><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">OsX:</span> Key: <span style=" font-style:italic;">0x0000</span>, Modifier: <span style=" font-style:italic;">0x0100</span><span style=" text-decoration: underline;"><br/></span></li></ul></body></html> true diff --git a/QHotkey.pro b/QHotkey.pro deleted file mode 100644 index 5fa7cbe..0000000 --- a/QHotkey.pro +++ /dev/null @@ -1,10 +0,0 @@ -TEMPLATE = subdirs - -SUBDIRS += \ - HotkeyTest \ - QHotkey - -DISTFILES += README.md \ - LICENSE \ - doc/qhotkey.doxy \ - doc/qhotkey.dox diff --git a/QHotkey/QHotkey.pro b/QHotkey/QHotkey.pro deleted file mode 100644 index 44e937b..0000000 --- a/QHotkey/QHotkey.pro +++ /dev/null @@ -1,16 +0,0 @@ -TEMPLATE = lib -win32: CONFIG += dll - -TARGET = QHotkey -VERSION = 1.2.1 - -include(../qhotkey.pri) - -DEFINES += QHOTKEY_LIB QHOTKEY_LIB_BUILD - -# 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/QHotkey/qhotkey.cpp b/QHotkey/qhotkey.cpp index 8dadc08..3b76d9c 100644 --- a/QHotkey/qhotkey.cpp +++ b/QHotkey/qhotkey.cpp @@ -8,11 +8,17 @@ Q_LOGGING_CATEGORY(logQHotkey, "QHotkey") -void QHotkey::addGlobalMapping(const QKeySequence &shortcut, const QHotkey::NativeShortcut &nativeShortcut) +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(shortcut[0] & ~Qt::KeyboardModifierMask)), - Q_ARG(Qt::KeyboardModifiers, Qt::KeyboardModifiers(shortcut[0] & Qt::KeyboardModifierMask)), + Q_ARG(Qt::Key, Qt::Key(key & ~Qt::KeyboardModifierMask)), + Q_ARG(Qt::KeyboardModifiers, Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)), Q_ARG(QHotkey::NativeShortcut, nativeShortcut)); } @@ -25,14 +31,13 @@ QHotkey::QHotkey(QObject *parent) : QObject(parent), _keyCode(Qt::Key_unknown), _modifiers(Qt::NoModifier), - _nativeShortcut(), _registered(false) {} -QHotkey::QHotkey(const QKeySequence &sequence, bool autoRegister, QObject *parent) : +QHotkey::QHotkey(const QKeySequence &shortcut, bool autoRegister, QObject *parent) : QHotkey(parent) { - setShortcut(sequence, autoRegister); + setShortcut(shortcut, autoRegister); } QHotkey::QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister, QObject *parent) : @@ -41,7 +46,7 @@ QHotkey::QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegi setShortcut(keyCode, modifiers, autoRegister); } -QHotkey::QHotkey(const QHotkey::NativeShortcut &shortcut, bool autoRegister, QObject *parent) : +QHotkey::QHotkey(QHotkey::NativeShortcut shortcut, bool autoRegister, QObject *parent) : QHotkey(parent) { setNativeShortcut(shortcut, autoRegister); @@ -57,8 +62,12 @@ QKeySequence QHotkey::shortcut() const { if(_keyCode == Qt::Key_unknown) return QKeySequence(); - else - return QKeySequence(_keyCode | _modifiers); + +#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 @@ -83,15 +92,21 @@ bool QHotkey::isRegistered() const bool QHotkey::setShortcut(const QKeySequence &shortcut, bool autoRegister) { - if(shortcut.isEmpty()) { + if(shortcut.isEmpty()) return resetShortcut(); - } else if(shortcut.count() > 1) { + if(shortcut.count() > 1) { qCWarning(logQHotkey, "Keysequences with multiple shortcuts are not allowed! " "Only the first shortcut will be used!"); } - return setShortcut(Qt::Key(shortcut[0] & ~Qt::KeyboardModifierMask), - Qt::KeyboardModifiers(shortcut[0] & Qt::KeyboardModifierMask), +#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); } @@ -118,15 +133,14 @@ bool QHotkey::setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool if(_nativeShortcut.isValid()) { if(autoRegister) return QHotkeyPrivate::instance()->addShortcut(this); - else - return true; - } else { - qCWarning(logQHotkey) << "Unable to map shortcut to native keys. Key:" << keyCode << "Modifiers:" << modifiers; - _keyCode = Qt::Key_unknown; - _modifiers = Qt::NoModifier; - _nativeShortcut = NativeShortcut(); - return false; + 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() @@ -158,35 +172,32 @@ bool QHotkey::setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool aut _nativeShortcut = nativeShortcut; if(autoRegister) return QHotkeyPrivate::instance()->addShortcut(this); - else - return true; - } else { - _keyCode = Qt::Key_unknown; - _modifiers = Qt::NoModifier; - _nativeShortcut = NativeShortcut(); 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); - else if(!_registered && registered) { + if(!_registered && registered) { if(!_nativeShortcut.isValid()) return false; - else - return QHotkeyPrivate::instance()->addShortcut(this); - } else - return true; + return QHotkeyPrivate::instance()->addShortcut(this); + } + return true; } // ---------- QHotkeyPrivate implementation ---------- -QHotkeyPrivate::QHotkeyPrivate() : - shortcuts() +QHotkeyPrivate::QHotkeyPrivate() { Q_ASSERT_X(qApp, Q_FUNC_INFO, "QHotkey requires QCoreApplication to be instantiated"); qApp->eventDispatcher()->installNativeEventFilter(this); @@ -211,8 +222,8 @@ QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcut(Qt::Key keycode, Qt::Keyb Q_ARG(Qt::Key, keycode), Q_ARG(Qt::KeyboardModifiers, modifiers))) { return QHotkey::NativeShortcut(); - } else - return res; + } + return res; } bool QHotkeyPrivate::addShortcut(QHotkey *hotkey) @@ -228,11 +239,11 @@ bool QHotkeyPrivate::addShortcut(QHotkey *hotkey) Q_RETURN_ARG(bool, res), Q_ARG(QHotkey*, hotkey))) { return false; - } else { - if(res) - emit hotkey->registeredChanged(true); - return res; } + + if(res) + emit hotkey->registeredChanged(true); + return res; } bool QHotkeyPrivate::removeShortcut(QHotkey *hotkey) @@ -248,11 +259,11 @@ bool QHotkeyPrivate::removeShortcut(QHotkey *hotkey) Q_RETURN_ARG(bool, res), Q_ARG(QHotkey*, hotkey))) { return false; - } else { - if(res) - emit hotkey->registeredChanged(false); - return res; } + + if(res) + emit hotkey->registeredChanged(false); + return res; } void QHotkeyPrivate::activateShortcut(QHotkey::NativeShortcut shortcut) @@ -262,7 +273,14 @@ void QHotkeyPrivate::activateShortcut(QHotkey::NativeShortcut shortcut) signal.invoke(hkey, Qt::QueuedConnection); } -void QHotkeyPrivate::addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, const QHotkey::NativeShortcut &nativeShortcut) +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); } @@ -272,8 +290,10 @@ bool QHotkeyPrivate::addShortcutInvoked(QHotkey *hotkey) QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; if(!shortcuts.contains(shortcut)) { - if(!registerShortcut(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); @@ -289,10 +309,14 @@ bool QHotkeyPrivate::removeShortcutInvoked(QHotkey *hotkey) return false; hotkey->_registered = false; emit hotkey->registeredChanged(true); - if(shortcuts.count(shortcut) == 0) - return unregisterShortcut(shortcut); - else + 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) @@ -300,13 +324,13 @@ QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcutInvoked(Qt::Key keycode, Q if(mapping.contains({keycode, modifiers})) return mapping.value({keycode, modifiers}); - bool ok1, ok2 = false; + bool ok1 = false; auto k = nativeKeycode(keycode, ok1); + bool ok2 = false; auto m = nativeModifiers(modifiers, ok2); if(ok1 && ok2) return {k, m}; - else - return {}; + return {}; } @@ -328,26 +352,26 @@ bool QHotkey::NativeShortcut::isValid() const return valid; } -bool QHotkey::NativeShortcut::operator ==(const QHotkey::NativeShortcut &other) const +bool QHotkey::NativeShortcut::operator ==(QHotkey::NativeShortcut other) const { return (key == other.key) && (modifier == other.modifier) && valid == other.valid; } -bool QHotkey::NativeShortcut::operator !=(const QHotkey::NativeShortcut &other) const +bool QHotkey::NativeShortcut::operator !=(QHotkey::NativeShortcut other) const { return (key != other.key) || (modifier != other.modifier) || valid != other.valid; } -uint qHash(const QHotkey::NativeShortcut &key) +QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key) { return qHash(key.key) ^ qHash(key.modifier); } -uint qHash(const QHotkey::NativeShortcut &key, uint seed) +QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed) { return qHash(key.key, seed) ^ qHash(key.modifier, seed); } diff --git a/QHotkey/qhotkey.h b/QHotkey/qhotkey.h index 479dff5..12f5fec 100644 --- a/QHotkey/qhotkey.h +++ b/QHotkey/qhotkey.h @@ -6,18 +6,24 @@ #include #include -#ifdef QHOTKEY_LIB - #ifdef QHOTKEY_LIB_BUILD - #define QHOTKEY_SHARED_EXPORT Q_DECL_EXPORT - #else - #define QHOTKEY_SHARED_EXPORT Q_DECL_IMPORT - #endif +#ifdef QHOTKEY_SHARED +# ifdef QHOTKEY_LIBRARY +# define QHOTKEY_EXPORT Q_DECL_EXPORT +# else +# define QHOTKEY_EXPORT Q_DECL_IMPORT +# endif #else - #define QHOTKEY_SHARED_EXPORT +# 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_SHARED_EXPORT QHotkey : public QObject +class QHOTKEY_EXPORT QHotkey : public QObject { Q_OBJECT //! @private @@ -30,7 +36,7 @@ class QHOTKEY_SHARED_EXPORT QHotkey : public QObject public: //! Defines shortcut with native keycodes - class QHOTKEY_SHARED_EXPORT NativeShortcut { + class QHOTKEY_EXPORT NativeShortcut { public: //! The native keycode quint32 key; @@ -46,16 +52,16 @@ class QHOTKEY_SHARED_EXPORT QHotkey : public QObject bool isValid() const; //! Equality operator - bool operator ==(const NativeShortcut &other) const; + bool operator ==(NativeShortcut other) const; //! Inequality operator - bool operator !=(const NativeShortcut &other) const; + 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, const NativeShortcut &nativeShortcut); + static void addGlobalMapping(const QKeySequence &shortcut, NativeShortcut nativeShortcut); //! Checks if global shortcuts are supported by the current platform static bool isPlatformSupported(); @@ -67,8 +73,8 @@ class QHOTKEY_SHARED_EXPORT QHotkey : public QObject //! 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(const NativeShortcut &shortcut, bool autoRegister = false, QObject *parent = nullptr); - ~QHotkey(); + explicit QHotkey(NativeShortcut shortcut, bool autoRegister = false, QObject *parent = nullptr); + ~QHotkey() override; //! @readAcFn{QHotkey::registered} bool isRegistered() const; @@ -82,7 +88,7 @@ class QHOTKEY_SHARED_EXPORT QHotkey : public QObject //! Get the current native shortcut NativeShortcut currentNativeShortcut() const; -public slots: +public Q_SLOTS: //! @writeAcFn{QHotkey::registered} bool setRegistered(bool registered); @@ -94,12 +100,15 @@ public slots: bool resetShortcut(); //! Set this hotkey to a native shortcut - bool setNativeShortcut(NativeShortcut nativeShortcut, bool autoRegister = false); + bool setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister = false); -signals: +Q_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); @@ -111,10 +120,10 @@ public slots: bool _registered; }; -uint QHOTKEY_SHARED_EXPORT qHash(const QHotkey::NativeShortcut &key); -uint QHOTKEY_SHARED_EXPORT qHash(const QHotkey::NativeShortcut &key, uint seed); +QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key); +QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed); -QHOTKEY_SHARED_EXPORT Q_DECLARE_LOGGING_CATEGORY(logQHotkey) +QHOTKEY_EXPORT Q_DECLARE_LOGGING_CATEGORY(logQHotkey) Q_DECLARE_METATYPE(QHotkey::NativeShortcut) diff --git a/QHotkey/qhotkey.pri b/QHotkey/qhotkey.pri deleted file mode 100644 index a7c9725..0000000 --- a/QHotkey/qhotkey.pri +++ /dev/null @@ -1 +0,0 @@ -message(The pri file has been moved one directory up! use that one instead) diff --git a/QHotkey/qhotkey_mac.cpp b/QHotkey/qhotkey_mac.cpp index a410373..799f515 100644 --- a/QHotkey/qhotkey_mac.cpp +++ b/QHotkey/qhotkey_mac.cpp @@ -7,9 +7,10 @@ class QHotkeyPrivateMac : public QHotkeyPrivate { public: // QAbstractNativeEventFilter interface - bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) Q_DECL_OVERRIDE; + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; - static OSStatus hotkeyEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); + static OSStatus hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); + static OSStatus hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); protected: // QHotkeyPrivate interface @@ -32,7 +33,7 @@ bool QHotkeyPrivate::isPlatformSupported() bool QHotkeyPrivateMac::isHotkeyHandlerRegistered = false; QHash QHotkeyPrivateMac::hotkeyRefs; -bool QHotkeyPrivateMac::nativeEventFilter(const QByteArray &eventType, void *message, long *result) +bool QHotkeyPrivateMac::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) { Q_UNUSED(eventType) Q_UNUSED(message) @@ -133,7 +134,7 @@ quint32 QHotkeyPrivateMac::nativeKeycode(Qt::Key keycode, bool &ok) UTF16Char ch = keycode; CFDataRef currentLayoutData; - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + TISInputSourceRef currentKeyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); if (currentKeyboard == NULL) return 0; @@ -203,10 +204,15 @@ bool QHotkeyPrivateMac::registerShortcut(QHotkey::NativeShortcut shortcut) { if (!this->isHotkeyHandlerRegistered) { - EventTypeSpec eventSpec; - eventSpec.eventClass = kEventClassKeyboard; - eventSpec.eventKind = kEventHotKeyPressed; - InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyEventHandler, 1, &eventSpec, NULL, NULL); + 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; @@ -221,8 +227,7 @@ bool QHotkeyPrivateMac::registerShortcut(QHotkey::NativeShortcut shortcut) 0, &eventRef); if (status != noErr) { - qCWarning(logQHotkey) << "Failed to register hotkey. Error:" - << status; + error = QString::number(status); return false; } else { this->hotkeyRefs.insert(shortcut, eventRef); @@ -235,8 +240,7 @@ bool QHotkeyPrivateMac::unregisterShortcut(QHotkey::NativeShortcut shortcut) EventHotKeyRef eventRef = QHotkeyPrivateMac::hotkeyRefs.value(shortcut); OSStatus status = UnregisterEventHotKey(eventRef); if (status != noErr) { - qCWarning(logQHotkey) << "Failed to unregister hotkey. Error:" - << status; + error = QString::number(status); return false; } else { this->hotkeyRefs.remove(shortcut); @@ -244,7 +248,7 @@ bool QHotkeyPrivateMac::unregisterShortcut(QHotkey::NativeShortcut shortcut) } } -OSStatus QHotkeyPrivateMac::hotkeyEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) +OSStatus QHotkeyPrivateMac::hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) { Q_UNUSED(nextHandler); Q_UNUSED(data); @@ -264,3 +268,24 @@ OSStatus QHotkeyPrivateMac::hotkeyEventHandler(EventHandlerCallRef nextHandler, 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/QHotkey/qhotkey_p.h b/QHotkey/qhotkey_p.h index 847f7db..8bc5ab6 100644 --- a/QHotkey/qhotkey_p.h +++ b/QHotkey/qhotkey_p.h @@ -7,7 +7,13 @@ #include #include -class QHOTKEY_SHARED_EXPORT QHotkeyPrivate : public QObject, public QAbstractNativeEventFilter +#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 @@ -25,6 +31,7 @@ class QHOTKEY_SHARED_EXPORT QHotkeyPrivate : public QObject, public QAbstractNat 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 @@ -32,11 +39,13 @@ class QHOTKEY_SHARED_EXPORT QHotkeyPrivate : public QObject, public QAbstractNat 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, const QHotkey::NativeShortcut &nativeShortcut); + 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); @@ -50,7 +59,4 @@ class QHOTKEY_SHARED_EXPORT QHotkeyPrivate : public QObject, public QAbstractNat return hotkeyPrivate;\ } -Q_DECLARE_METATYPE(Qt::Key) -Q_DECLARE_METATYPE(Qt::KeyboardModifiers) - #endif // QHOTKEY_P_H diff --git a/QHotkey/qhotkey_win.cpp b/QHotkey/qhotkey_win.cpp index ead6763..949d87a 100644 --- a/QHotkey/qhotkey_win.cpp +++ b/QHotkey/qhotkey_win.cpp @@ -1,17 +1,26 @@ #include "qhotkey.h" #include "qhotkey_p.h" #include +#include #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, long *result) Q_DECL_OVERRIDE; + 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; @@ -20,31 +29,56 @@ class QHotkeyPrivateWin : public QHotkeyPrivate private: static QString formatWinError(DWORD winError); + QTimer pollTimer; + QList polledShortcuts; }; 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, long *result) +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) - this->activateShortcut({HIWORD(msg->lParam), LOWORD(msg->lParam)}); + if(msg->message == WM_HOTKEY) { + QHotkey::NativeShortcut shortcut = {HIWORD(msg->lParam), LOWORD(msg->lParam)}; + this->activateShortcut(shortcut); + if (this->polledShortcuts.empty()) + this->pollTimer.start(); + this->polledShortcuts.append(shortcut); + } return false; } +void QHotkeyPrivateWin::pollForHotkeyRelease() +{ + auto it = std::remove_if(this->polledShortcuts.begin(), this->polledShortcuts.end(), [this](const QHotkey::NativeShortcut &shortcut) { + bool pressed = (GetAsyncKeyState(shortcut.key) & (1 << 15)) != 0; + if (!pressed) + this->releaseShortcut(shortcut); + return !pressed; + }); + this->polledShortcuts.erase(it, this->polledShortcuts.end()); + if (this->polledShortcuts.empty()) + this->pollTimer.stop(); +} + 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(keycode); + const SHORT vKey = VkKeyScanW(static_cast(keycode)); if(vKey > -1) return LOBYTE(vKey); } @@ -208,7 +242,7 @@ quint32 QHotkeyPrivateWin::nativeKeycode(Qt::Key keycode, bool &ok) default: if(keycode <= 0xFFFF) - return (byte)keycode; + return static_cast(keycode); else { ok = false; return 0; @@ -235,13 +269,12 @@ bool QHotkeyPrivateWin::registerShortcut(QHotkey::NativeShortcut shortcut) { BOOL ok = RegisterHotKey(NULL, HKEY_ID(shortcut), - shortcut.modifier, + shortcut.modifier + MOD_NOREPEAT, shortcut.key); if(ok) return true; else { - qCWarning(logQHotkey) << "Failed to register hotkey. Error:" - << qPrintable(QHotkeyPrivateWin::formatWinError(::GetLastError())); + error = QHotkeyPrivateWin::formatWinError(::GetLastError()); return false; } } @@ -252,8 +285,7 @@ bool QHotkeyPrivateWin::unregisterShortcut(QHotkey::NativeShortcut shortcut) if(ok) return true; else { - qCWarning(logQHotkey) << "Failed to unregister hotkey. Error:" - << qPrintable(QHotkeyPrivateWin::formatWinError(::GetLastError())); + error = QHotkeyPrivateWin::formatWinError(::GetLastError()); return false; } } diff --git a/QHotkey/qhotkey_x11.cpp b/QHotkey/qhotkey_x11.cpp index 43f8956..d9d73f7 100644 --- a/QHotkey/qhotkey_x11.cpp +++ b/QHotkey/qhotkey_x11.cpp @@ -1,12 +1,19 @@ #include "qhotkey.h" #include "qhotkey_p.h" -#include -#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + #include +#else + #include + #include +#endif + #include +#include #include #include -//compability to pre Qt 5.8 +//compatibility to pre Qt 5.8 #ifndef Q_FALLTHROUGH #define Q_FALLTHROUGH() (void)0 #endif @@ -15,19 +22,21 @@ class QHotkeyPrivateX11 : public QHotkeyPrivate { public: // QAbstractNativeEventFilter interface - bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) Q_DECL_OVERRIDE; + 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; - QString getX11String(Qt::Key keycode); + 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); @@ -49,21 +58,38 @@ 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, long *result) +bool QHotkeyPrivateX11::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) { Q_UNUSED(eventType) Q_UNUSED(result) - xcb_generic_event_t *genericEvent = static_cast(message); + auto *genericEvent = static_cast(message); if (genericEvent->response_type == XCB_KEY_PRESS) { - xcb_key_press_event_t *keyEvent = static_cast(message); - this->activateShortcut({keyEvent->detail, keyEvent->state & QHotkeyPrivateX11::validModsMask}); + 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; @@ -73,19 +99,19 @@ QString QHotkeyPrivateX11::getX11String(Qt::Key keycode) { switch(keycode){ - case Qt::Key_MediaLast : - case Qt::Key_MediaPrevious : - return "XF86AudioPrev"; - case Qt::Key_MediaNext : - return "XF86AudioNext"; - case Qt::Key_MediaPause : - case Qt::Key_MediaPlay : + 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 "XF86AudioPlay"; + return QStringLiteral("XF86AudioPlay"); case Qt::Key_MediaRecord : - return "XF86AudioRecord"; - case Qt::Key_MediaStop : - return "XF86AudioStop"; + return QStringLiteral("XF86AudioRecord"); + case Qt::Key_MediaStop : + return QStringLiteral("XF86AudioStop"); default : return QKeySequence(keycode).toString(QKeySequence::NativeText); } @@ -104,13 +130,24 @@ quint32 QHotkeyPrivateX11::nativeKeycode(Qt::Key keycode, bool &ok) return 0; } - if(QX11Info::isPlatformX11()) { - auto res = XKeysymToKeycode(QX11Info::display(), keysym); +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface(); +#else + const bool x11Interface = QX11Info::isPlatformX11(); +#endif + + if(x11Interface) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + Display *display = x11Interface->display(); +#else + Display *display = QX11Info::display(); +#endif + auto res = XKeysymToKeycode(display, keysym); if(res != 0) ok = true; return res; - } else - return 0; + } + return 0; } quint32 QHotkeyPrivateX11::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) @@ -130,8 +167,15 @@ quint32 QHotkeyPrivateX11::nativeModifiers(Qt::KeyboardModifiers modifiers, bool 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(); - if(!display) +#endif + + if(!display || !x11Interface) return false; HotkeyErrorHandler errorHandler; @@ -147,17 +191,21 @@ bool QHotkeyPrivateX11::registerShortcut(QHotkey::NativeShortcut shortcut) XSync(display, False); if(errorHandler.hasError) { - qCWarning(logQHotkey) << "Failed to register hotkey. Error:" - << qPrintable(errorHandler.errorString); + error = errorHandler.errorString; this->unregisterShortcut(shortcut); return false; - } else - return true; + } + 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; @@ -166,16 +214,15 @@ bool QHotkeyPrivateX11::unregisterShortcut(QHotkey::NativeShortcut shortcut) XUngrabKey(display, shortcut.key, shortcut.modifier | specialMod, - DefaultRootWindow(display)); + XDefaultRootWindow(display)); } XSync(display, False); - if(errorHandler.hasError) { - qCWarning(logQHotkey) << "Failed to unregister hotkey. Error:" - << qPrintable(errorHandler.errorString); + if(HotkeyErrorHandler::hasError) { + error = HotkeyErrorHandler::errorString; return false; - } else - return true; + } + return true; } QString QHotkeyPrivateX11::formatX11Error(Display *display, int errorCode) diff --git a/README.md b/README.md index e4f3f78..4058dfd 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,21 @@ The QHotkey is a class that can be used to create hotkeys/global shortcuts, aka **Note:** For now Wayland is not supported, as it is simply not possible to register a global shortcut with wayland. For more details, or possible Ideas on how to get Hotkeys working on wayland, see [Issue #14](https://github.com/Skycoder42/QHotkey/issues/14). +## Building + +QHotkey supports both Qt6 and Qt5. When using Qt6, version 6.2.0 or later required. It can be built using the CMake building system. + +### CMake + +The CMake `QT_DEFAULT_MAJOR_VERSION` variable controls which major version of Qt is used for building, and defaults to `5`. For example, use the CMake command line option `-DQT_DEFAULT_MAJOR_VERSION=6` for building with Qt6. To build the testing application `QHotkeyTest`, specify `-DQHOTKEY_EXAMPLES=ON`. CMake example usage: + +``` +$ cd QHotkey +$ cmake -B build -S . -DQT_DEFAULT_MAJOR_VERSION=6 +$ cmake --build build +# cmake --install build +``` + ## Installation The package is providet as qpm package, [`de.skycoder42.qhotkey`](https://www.qpm.io/packages/de.skycoder42.qhotkey/index.html). You can install it either via qpmx (preferred) or directly via qpm. @@ -48,17 +63,17 @@ The following example shows a simple application that will run without a window int main(int argc, char *argv[]) { - QApplication a(argc, argv); + QApplication app(argc, argv); - auto hotkey = new QHotkey(QKeySequence("ctrl+alt+Q"), true, &a);//The hotkey will be automatically registered - qDebug() << "Is Registered: " << hotkey->isRegistered(); + QHotkey hotkey(QKeySequence("Ctrl+Alt+Q"), true, &app); //The hotkey will be automatically registered + qDebug() << "Is segistered:" << hotkey.isRegistered(); - QObject::connect(hotkey, &QHotkey::activated, qApp, [&](){ + QObject::connect(&hotkey, &QHotkey::activated, qApp, [&](){ qDebug() << "Hotkey Activated - the application will quit now"; qApp->quit(); }); - return a.exec(); + return app.exec(); } ``` @@ -68,18 +83,18 @@ int main(int argc, char *argv[]) By running the example in `./HotkeyTest` you can test out the QHotkey class. There are 4 sections: - **Playground:** You can enter some sequences here and try it out with different key combinations. - **Testings:** A list of selected hotkeys. Activate it and try out which ones work for you (*Hint:* Depending on OS and keyboard layout, it's very possible that a few don't work). -- **Threading:** Activate the checkbox to move 2 Hotkeys of the playground to seperate threads. It should work without a difference. +- **Threading:** Activate the checkbox to move 2 Hotkeys of the playground to separate threads. It should work without a difference. - **Native Shortcut**: Allows you to try out the direct usage of native shortcuts ### Logging -By default, QHotkey prints some warning messages if something goes wrong (For example, a key that cannot be translated). All messages of QHotkey are grouped into the [QLoggingCategory](https://doc.qt.io/qt-5/qloggingcategory.html) `"QHotkey"`. If you want to simply disable the logging, call the folling function somewhere in your code: +By default, QHotkey prints some warning messages if something goes wrong (For example, a key that cannot be translated). All messages of QHotkey are grouped into the [QLoggingCategory](https://doc.qt.io/qt-5/qloggingcategory.html) `"QHotkey"`. If you want to simply disable the logging, call the following function somewhere in your code: ```cpp QLoggingCategory::setFilterRules(QStringLiteral("QHotkey.warning=false")); ``` -This will turn all warnings of QHotkey of (It only uses warnings for now, thats why this is enough). For more information about all the things you can do with the logging categories, check the Qt-Documentation +This will turn all warnings of QHotkey of (It only uses warnings for now, that's why this is enough). For more information about all the things you can do with the logging categories, check the Qt-Documentation -## Thread saftey -The QHotkey class itself is reentrant - wich means you can create as many instances as required on any thread. This allows you to use the QHotkey on all threads. **But** you should never use the QHotkey instance on a thread that is different from the one the instance belongs to! Internally the system uses a singleton instance that handles the hotkey events and distributes them to the QHotkey instances. This internal class is completley threadsafe. +## Thread safety +The QHotkey class itself is reentrant - which means you can create as many instances as required on any thread. This allows you to use the QHotkey on all threads. **But** you should never use the QHotkey instance on a thread that is different from the one the instance belongs to! Internally the system uses a singleton instance that handles the hotkey events and distributes them to the QHotkey instances. This internal class is completely threadsafe. However, this singleton instance only runs on the main thread. (One reason is that some of the OS-Functions are not thread safe). To make threaded hotkeys possible, the critical functions (registering/unregistering hotkeys and keytranslation) are all run on the mainthread too. The QHotkey instances on other threads use `QMetaObject::invokeMethod` with a `Qt::BlockingQueuedConnection`. diff --git a/doc/qhotkey.dox b/doc/qhotkey.dox index fe184f3..dabde3c 100644 --- a/doc/qhotkey.dox +++ b/doc/qhotkey.dox @@ -38,7 +38,7 @@ In addition to the normal accessors, a modification of the QHotkey::shortcut pro @default{`QKeySequence()` (`Qt::Key_unknown` + `Qt::NoModifier`)} Holds the shortcut this hotkey will be registered on. It can be used as QKeySequence or as a combination of -a Qt::Key and Qt::KeyboardModifiers. All write-accessors specifiy an additional parameter to immediatly register +a Qt::Key and Qt::KeyboardModifiers. All write-accessors specify an additional parameter to immediately register the hotkey. @note Since the operating systems do not support hotkeys that consist of multiple key-combinations in a sequence, @@ -107,7 +107,7 @@ it does. This does not happen if used from the main thread. If the hotkey is still registered on destruction, it will automatically unregister itself. -@warning If using a hotkey on a thread other than the main thread, make shure the QApplication is still running it's eventloop. +@warning If using a hotkey on a thread other than the main thread, make sure the QApplication is still running it's eventloop. Otherwise your application will hang up. @sa QHotkey::registered @@ -144,7 +144,7 @@ from the thread this instance lives on. This method can be used to remap specific hotkey to a different native representation than the one that would be used by default. This can be useful if specific key combinations work fine -on allmost all platforms, but on one you need a different keycode for the same effect. See +on almost all platforms, but on one you need a different keycode for the same effect. See [Issue #15](https://github.com/Skycoder42/QHotkey/issues/15) for an example where this is the case. diff --git a/qhotkey.prc b/qhotkey.prc deleted file mode 100644 index 30e2b04..0000000 --- a/qhotkey.prc +++ /dev/null @@ -1,6 +0,0 @@ -mac: LIBS += -framework Carbon -else:win32: LIBS += -luser32 -else:unix { - QT += x11extras - LIBS += -lX11 -} diff --git a/qhotkey.pri b/qhotkey.pri deleted file mode 100644 index 1dc2ae9..0000000 --- a/qhotkey.pri +++ /dev/null @@ -1,17 +0,0 @@ -CONFIG += C++11 - -PUBLIC_HEADERS += $$PWD/QHotkey/qhotkey.h \ - $$PWD/QHotkey/QHotkey - -HEADERS += $$PUBLIC_HEADERS \ - $$PWD/QHotkey/qhotkey_p.h - -SOURCES += $$PWD/QHotkey/qhotkey.cpp - -mac: SOURCES += $$PWD/QHotkey/qhotkey_mac.cpp -else:win32: SOURCES += $$PWD/QHotkey/qhotkey_win.cpp -else:unix: SOURCES += $$PWD/QHotkey/qhotkey_x11.cpp - -INCLUDEPATH += $$PWD/QHotkey - -include($$PWD/qhotkey.prc) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy