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 new file mode 100644 index 0000000..d34519e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,115 @@ +cmake_minimum_required(VERSION 3.1) + +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" OFF) +option(QHOTKEY_INSTALL "Enable install rule" ON) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_AUTOMOC ON) + +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() + +# 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) + +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) + +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) + + target_sources(qhotkey PRIVATE QHotkey/qhotkey_mac.cpp) + target_link_libraries(qhotkey PRIVATE ${CARBON_LIBRARY}) +elseif(WIN32) + target_sources(qhotkey PRIVATE QHotkey/qhotkey_win.cpp) +else() + find_package(X11 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}) + target_sources(qhotkey PRIVATE QHotkey/qhotkey_x11.cpp) +endif() + +include(GNUInstallDirs) + +target_include_directories(qhotkey + PUBLIC + $ + $) + +include(CMakePackageConfigHelpers) + +set_target_properties(qhotkey PROPERTIES + SOVERSION ${PROJECT_VERSION_MAJOR} + 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() + +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 new file mode 100644 index 0000000..714585d --- /dev/null +++ b/HotkeyTest/CMakeLists.txt @@ -0,0 +1,11 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOUIC ON) + +add_executable(HotkeyTest + main.cpp + hottestwidget.cpp + 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 e193b8f..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/qhotkey.pri) - -SOURCES += main.cpp\ - hottestwidget.cpp - -HEADERS += hottestwidget.h - -FORMS += hottestwidget.ui diff --git a/HotkeyTest/hottestwidget.cpp b/HotkeyTest/hottestwidget.cpp index 0574ddb..63faa24 100644 --- a/HotkeyTest/hottestwidget.cpp +++ b/HotkeyTest/hottestwidget.cpp @@ -1,6 +1,8 @@ #include "hottestwidget.h" #include "ui_hottestwidget.h" +//#define TEST_MAPPING + HotTestWidget::HotTestWidget(QWidget *parent) : QWidget(parent), ui(new Ui::HotTestWidget), @@ -11,12 +13,18 @@ HotTestWidget::HotTestWidget(QWidget *parent) : hotkey_5(new QHotkey(NULL)), thread4(new QThread(this)), thread5(new QThread(this)), - testHotkeys() + testHotkeys(), + nativeHotkey(new QHotkey(this)) { ui->setupUi(this); this->thread4->start(); this->thread5->start(); +#ifdef TEST_MAPPING + //shortcut mapping override + QHotkey::addGlobalMapping(QKeySequence("X"), QHotkey::NativeShortcut());// add invalid mapping to test if the overwrite works for all platforms +#endif + //1 connect(this->ui->hotkeyCheckbox_1, &QCheckBox::toggled, this->hotkey_1, &QHotkey::setRegistered); @@ -91,6 +99,10 @@ HotTestWidget::HotTestWidget(QWidget *parent) : this->testHotkeys += new QHotkey(Qt::Key_K, Qt::ShiftModifier | Qt::AltModifier, false, this); connect(this->testHotkeys.last(), &QHotkey::activated, this->ui->hotkeyShiftAltKCheckBox_2, &QCheckBox::toggle); + + //native + connect(this->nativeHotkey, &QHotkey::activated, + this, &HotTestWidget::increase_native); } HotTestWidget::~HotTestWidget() @@ -217,3 +229,19 @@ void HotTestWidget::on_threadEnableCheckBox_clicked() this->ui->tabWidget->setCurrentIndex(0); } + +void HotTestWidget::on_registeredCheckBox_toggled(bool checked) +{ + if(checked) { + this->nativeHotkey->setNativeShortcut({ + (quint32)this->ui->nativeKeySpinBox->value(), + (quint32)this->ui->nativeModifiersSpinBox->value() + }, true); + } else + this->nativeHotkey->setRegistered(false); +} + +void HotTestWidget::increase_native() +{ + this->ui->nativeCount->display(this->ui->nativeCount->intValue() + 1); +} diff --git a/HotkeyTest/hottestwidget.h b/HotkeyTest/hottestwidget.h index 8aaa78e..d2f44e9 100644 --- a/HotkeyTest/hottestwidget.h +++ b/HotkeyTest/hottestwidget.h @@ -39,6 +39,9 @@ private slots: void on_groupBox_toggled(bool checked); void on_threadEnableCheckBox_clicked(); + void on_registeredCheckBox_toggled(bool checked); + void increase_native(); + private: Ui::HotTestWidget *ui; @@ -52,6 +55,8 @@ private slots: QThread *thread5; QList testHotkeys; + + QHotkey *nativeHotkey; }; #endif // HOTTESTWIDGET_H diff --git a/HotkeyTest/hottestwidget.ui b/HotkeyTest/hottestwidget.ui index 745422f..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 @@ -965,6 +965,194 @@ + + + Native Shortcut + + + + + + <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 + + + + + + + + + Key: + + + + + + + 0x + + + 999999999 + + + 16 + + + + + + + Modifiers: + + + + + + + 0x + + + 999999999 + + + 16 + + + + + + + Count: + + + + + + + false + + + + + + + + 85 + 255 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + + + 85 + 255 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + + + 120 + 120 + 120 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + + true + + + QFrame::Panel + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + + + + Registered: + + + + + + + + + + @@ -996,12 +1184,12 @@ setEnabled(bool) - 36 - 52 + 31 + 45 - 375 - 62 + 417 + 43 @@ -1012,12 +1200,12 @@ setEnabled(bool) - 44 - 82 + 31 + 74 - 359 - 82 + 417 + 72 @@ -1028,12 +1216,12 @@ setEnabled(bool) - 52 - 113 + 31 + 103 - 375 - 120 + 417 + 101 @@ -1044,12 +1232,12 @@ setEnabled(bool) - 58 - 140 + 31 + 132 - 375 - 149 + 417 + 130 @@ -1060,12 +1248,12 @@ setEnabled(bool) - 28 - 163 + 31 + 161 - 375 - 178 + 417 + 159 @@ -1076,12 +1264,12 @@ setDisabled(bool) - 62 - 50 + 31 + 45 - 127 - 52 + 109 + 43 @@ -1092,12 +1280,12 @@ setDisabled(bool) - 83 - 79 + 31 + 74 - 161 - 81 + 109 + 72 @@ -1108,12 +1296,12 @@ setDisabled(bool) - 75 - 106 + 31 + 103 - 116 - 109 + 109 + 101 @@ -1124,12 +1312,12 @@ setDisabled(bool) - 69 - 135 + 31 + 132 - 117 - 141 + 109 + 130 @@ -1140,12 +1328,60 @@ setDisabled(bool) - 81 - 168 + 31 + 161 + + + 109 + 159 + + + + + registeredCheckBox + toggled(bool) + nativeCount + setEnabled(bool) + + + 93 + 326 + + + 150 + 349 + + + + + registeredCheckBox + toggled(bool) + nativeModifiersSpinBox + setDisabled(bool) + + + 108 + 327 + + + 111 + 305 + + + + + registeredCheckBox + toggled(bool) + nativeKeySpinBox + setDisabled(bool) + + + 183 + 327 - 145 - 164 + 194 + 279 diff --git a/HotkeyTest/main.cpp b/HotkeyTest/main.cpp index 3640393..f95d6fd 100644 --- a/HotkeyTest/main.cpp +++ b/HotkeyTest/main.cpp @@ -1,11 +1,20 @@ #include "hottestwidget.h" #include +//#define START_BACKGROUND + int main(int argc, char *argv[]) { QApplication a(argc, argv); HotTestWidget w; + +#ifdef START_BACKGROUND + auto startKey = new QHotkey(QKeySequence(Qt::MetaModifier | Qt::ControlModifier | Qt::Key_S), true, &w); + QObject::connect(startKey, &QHotkey::activated, + &w, &QWidget::show); +#else w.show(); +#endif return a.exec(); } diff --git a/QHotkey.pro b/QHotkey.pro deleted file mode 100644 index 4cb37f4..0000000 --- a/QHotkey.pro +++ /dev/null @@ -1,9 +0,0 @@ -TEMPLATE = subdirs - -SUBDIRS += \ - HotkeyTest - -DISTFILES += README.md \ - LICENSE \ - doc/qhotkey.doxy \ - doc/qhotkey.dox diff --git a/QHotkey/qhotkey.cpp b/QHotkey/qhotkey.cpp index 55f74df..3b76d9c 100644 --- a/QHotkey/qhotkey.cpp +++ b/QHotkey/qhotkey.cpp @@ -8,72 +8,111 @@ 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), - key(Qt::Key_unknown), - mods(Qt::NoModifier), - nativeShortcut(), - registered(false) + _keyCode(Qt::Key_unknown), + _modifiers(Qt::NoModifier), + _registered(false) {} -QHotkey::QHotkey(const QKeySequence &sequence, bool autoRegister, QObject *parent) : +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) { - this->setShortcut(sequence, autoRegister); + setShortcut(keyCode, modifiers, autoRegister); } -QHotkey::QHotkey(Qt::Key key, Qt::KeyboardModifiers modifiers, bool autoRegister, QObject *parent) : +QHotkey::QHotkey(QHotkey::NativeShortcut shortcut, bool autoRegister, QObject *parent) : QHotkey(parent) { - this->setShortcut(key, modifiers, autoRegister); + setNativeShortcut(shortcut, autoRegister); } QHotkey::~QHotkey() { - if(this->registered) + if(_registered) QHotkeyPrivate::instance()->removeShortcut(this); } QKeySequence QHotkey::shortcut() const { - if(this->key == Qt::Key_unknown) + if(_keyCode == Qt::Key_unknown) return QKeySequence(); - else - return QKeySequence(this->key | this->mods); + +#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 this->key; + return _keyCode; } Qt::KeyboardModifiers QHotkey::modifiers() const { - return this->mods; + return _modifiers; +} + +QHotkey::NativeShortcut QHotkey::currentNativeShortcut() const +{ + return _nativeShortcut; } bool QHotkey::isRegistered() const { - return this->registered; + return _registered; } bool QHotkey::setShortcut(const QKeySequence &shortcut, bool autoRegister) { - if(shortcut.isEmpty()) { - return this->resetShortcut(); - } else if(shortcut.count() > 1) { + 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!"); } - return this->setShortcut(Qt::Key(shortcut[0] & ~Qt::KeyboardModifierMask), - Qt::KeyboardModifiers(shortcut[0] & Qt::KeyboardModifierMask), - autoRegister); +#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 key, Qt::KeyboardModifiers modifiers, bool autoRegister) +bool QHotkey::setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister) { - if(this->registered) { + if(_registered) { if(autoRegister) { if(!QHotkeyPrivate::instance()->removeShortcut(this)) return false; @@ -81,62 +120,84 @@ bool QHotkey::setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, bool aut return false; } - if(key == Qt::Key_unknown) { - this->key = Qt::Key_unknown; - this->mods = Qt::NoModifier; - this->nativeShortcut = NativeShortcut(); + if(keyCode == Qt::Key_unknown) { + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); return true; } - this->key = key; - this->mods = modifiers; - this->nativeShortcut = QHotkeyPrivate::instance()->nativeShortcut(key, modifiers); - if(QHotkeyPrivate::testValid(this->nativeShortcut)) { + _keyCode = keyCode; + _modifiers = modifiers; + _nativeShortcut = QHotkeyPrivate::instance()->nativeShortcut(keyCode, modifiers); + if(_nativeShortcut.isValid()) { if(autoRegister) return QHotkeyPrivate::instance()->addShortcut(this); - else - return true; - } else { - qCWarning(logQHotkey) << "Unable to map shortcut to native keys. Key:" << key << "Modifiers:" << modifiers; - this->key = Qt::Key_unknown; - this->mods = Qt::NoModifier; - this->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() { - if(this->registered && + if(_registered && !QHotkeyPrivate::instance()->removeShortcut(this)) { return false; } - this->key = Qt::Key_unknown; - this->mods = Qt::NoModifier; - this->nativeShortcut = NativeShortcut(); + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); return true; } -bool QHotkey::setRegistered(bool registered) +bool QHotkey::setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister) { - if(this->registered && !registered) - return QHotkeyPrivate::instance()->removeShortcut(this); - else if(!this->registered && registered) { - if(!QHotkeyPrivate::testValid(this->nativeShortcut)) + if(_registered) { + if(autoRegister) { + if(!QHotkeyPrivate::instance()->removeShortcut(this)) + return false; + } else return false; - else + } + + if(nativeShortcut.isValid()) { + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = nativeShortcut; + if(autoRegister) return QHotkeyPrivate::instance()->addShortcut(this); - } else 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() : - shortcuts() +QHotkeyPrivate::QHotkeyPrivate() { Q_ASSERT_X(qApp, Q_FUNC_INFO, "QHotkey requires QCoreApplication to be instantiated"); qApp->eventDispatcher()->installNativeEventFilter(this); @@ -144,15 +205,15 @@ QHotkeyPrivate::QHotkeyPrivate() : QHotkeyPrivate::~QHotkeyPrivate() { - if(!this->shortcuts.isEmpty()) - qCWarning(logQHotkey, "QHotkeyPrivate destroyed with registered shortcuts!"); + 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() == this->thread() ? + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? Qt::DirectConnection : Qt::BlockingQueuedConnection); QHotkey::NativeShortcut res; @@ -161,16 +222,16 @@ 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) { - if(hotkey->registered) + if(hotkey->_registered) return false; - Qt::ConnectionType conType = (QThread::currentThread() == this->thread() ? + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? Qt::DirectConnection : Qt::BlockingQueuedConnection); bool res = false; @@ -178,19 +239,19 @@ 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) { - if(!hotkey->registered) + if(!hotkey->_registered) return false; - Qt::ConnectionType conType = (QThread::currentThread() == this->thread() ? + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? Qt::DirectConnection : Qt::BlockingQueuedConnection); bool res = false; @@ -198,44 +259,119 @@ 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) { QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::activated); - for(QHotkey *hkey : this->shortcuts.values(shortcut)) + 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; + QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; - if(!this->shortcuts.contains(shortcut)) { - if(!this->registerShortcut(shortcut)) + if(!shortcuts.contains(shortcut)) { + if(!registerShortcut(shortcut)) { + qCWarning(logQHotkey) << QHotkey::tr("Failed to register %1. Error: %2").arg(hotkey->shortcut().toString(), error); return false; + } } - this->shortcuts.insert(shortcut, hotkey); - hotkey->registered = true; + shortcuts.insert(shortcut, hotkey); + hotkey->_registered = true; return true; } bool QHotkeyPrivate::removeShortcutInvoked(QHotkey *hotkey) { - QHotkey::NativeShortcut shortcut = hotkey->nativeShortcut; + QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; - if(this->shortcuts.remove(shortcut, hotkey) == 0) + if(shortcuts.remove(shortcut, hotkey) == 0) return false; - hotkey->registered = false; + hotkey->_registered = false; emit hotkey->registeredChanged(true); - if(this->shortcuts.count(shortcut) == 0) - return this->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) +{ + 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/QHotkey/qhotkey.h b/QHotkey/qhotkey.h index 3f92d77..3697c8e 100644 --- a/QHotkey/qhotkey.h +++ b/QHotkey/qhotkey.h @@ -6,10 +6,27 @@ #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 : public QObject +class QHOTKEY_EXPORT QHotkey : public QObject { Q_OBJECT + //! @private friend class QHotkeyPrivate; //! Specifies whether this hotkey is currently registered or not @@ -19,52 +36,95 @@ class QHotkey : public QObject public: //! Defines shortcut with native keycodes - typedef QPair NativeShortcut; + 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); - //! Constructor - explicit QHotkey(QObject *parent = 0); + //! 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 = 0); + 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 key, Qt::KeyboardModifiers modifiers, bool autoRegister = false, QObject *parent = 0); - //! Destructor - ~QHotkey(); + 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; - //! READ-Accessor for QHotkey::registered + //! @readAcFn{QHotkey::registered} bool isRegistered() const; - //! READ-Accessor for QHotkey::shortcut - the key and modifiers as a QKeySequence + //! @readAcFn{QHotkey::shortcut} QKeySequence shortcut() const; - //! READ-Accessor for QHotkey::shortcut - the key only + //! @readAcFn{QHotkey::shortcut} - the key only Qt::Key keyCode() const; - //! READ-Accessor for QHotkey::shortcut - the modifiers only + //! @readAcFn{QHotkey::shortcut} - the modifiers only Qt::KeyboardModifiers modifiers() const; + //! Get the current native shortcut + NativeShortcut currentNativeShortcut() const; + public slots: - //! WRITE-Accessor for QHotkey::registered + //! @writeAcFn{QHotkey::registered} bool setRegistered(bool registered); - //! WRITE-Accessor for QHotkey::shortcut + //! @writeAcFn{QHotkey::shortcut} bool setShortcut(const QKeySequence &shortcut, bool autoRegister = false); - //! WRITE-Accessor for QHotkey::shortcut - bool setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers, bool autoRegister = false); - //! RESET-Accessor for QHotkey::shortcut + //! @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); - //! NOTIFY-Accessor for QHotkey::registered + //! Will be emitted if the shortcut press is released + void released(QPrivateSignal); + + //! @notifyAcFn{QHotkey::registered} void registeredChanged(bool registered); private: - Qt::Key key; - Qt::KeyboardModifiers mods; + Qt::Key _keyCode; + Qt::KeyboardModifiers _modifiers; - NativeShortcut nativeShortcut; - bool registered; + NativeShortcut _nativeShortcut; + bool _registered; }; -Q_DECLARE_LOGGING_CATEGORY(logQHotkey) +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/QHotkey/qhotkey.pri b/QHotkey/qhotkey.pri deleted file mode 100644 index 9b4fb20..0000000 --- a/QHotkey/qhotkey.pri +++ /dev/null @@ -1,20 +0,0 @@ -CONFIG += C++11 - -HEADERS += $$PWD/qhotkey.h \ - $$PWD/qhotkey_p.h - -SOURCES += $$PWD/qhotkey.cpp - -mac { - SOURCES += $$PWD/qhotkey_mac.cpp - LIBS += -framework Carbon -} else:win32 { - SOURCES += $$PWD/qhotkey_win.cpp - LIBS += -lUser32 -} else:unix { - SOURCES += $$PWD/qhotkey_x11.cpp - QT += x11extras - LIBS += -lX11 -} - -INCLUDEPATH += $$PWD diff --git a/QHotkey/qhotkey_mac.cpp b/QHotkey/qhotkey_mac.cpp index 743df5d..799f515 100644 --- a/QHotkey/qhotkey_mac.cpp +++ b/QHotkey/qhotkey_mac.cpp @@ -7,14 +7,15 @@ 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 - quint32 nativeKeycode(Qt::Key keycode) Q_DECL_OVERRIDE; - quint32 nativeModifiers(Qt::KeyboardModifiers modifiers) Q_DECL_OVERRIDE; + 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; @@ -24,20 +25,26 @@ class QHotkeyPrivateMac : public QHotkeyPrivate }; NATIVE_INSTANCE(QHotkeyPrivateMac) +bool QHotkeyPrivate::isPlatformSupported() +{ + return true; +} + 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); - Q_UNUSED(result); + Q_UNUSED(eventType) + Q_UNUSED(message) + Q_UNUSED(result) return false; } -quint32 QHotkeyPrivateMac::nativeKeycode(Qt::Key keycode) +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; @@ -120,13 +127,14 @@ quint32 QHotkeyPrivateMac::nativeKeycode(Qt::Key keycode) case Qt::Key_Up: return kVK_UpArrow; default: - ; + ok = false; + break; } UTF16Char ch = keycode; CFDataRef currentLayoutData; - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + TISInputSourceRef currentKeyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); if (currentKeyboard == NULL) return 0; @@ -157,19 +165,25 @@ quint32 QHotkeyPrivateMac::nativeKeycode(Qt::Key keycode) long idx = keyToChar[k] & kUCKeyOutputGetIndexMask; if (stateRec && idx < stateRec->keyStateRecordCount) { UCKeyStateRecord* rec = reinterpret_cast(data + stateRec->keyStateRecordOffsets[idx]); - if (rec->stateZeroCharData == ch) return k; + if (rec->stateZeroCharData == ch) { + ok = true; + return k; + } } } else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) { - if (keyToChar[k] == ch) return k; + if (keyToChar[k] == ch) { + ok = true; + return k; + } } - } // for k - } // for j - } // for i + } + } + } return 0; } -quint32 QHotkeyPrivateMac::nativeModifiers(Qt::KeyboardModifiers modifiers) +quint32 QHotkeyPrivateMac::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) { quint32 nMods = 0; if (modifiers & Qt::ShiftModifier) @@ -182,6 +196,7 @@ quint32 QHotkeyPrivateMac::nativeModifiers(Qt::KeyboardModifiers modifiers) nMods |= controlKey; if (modifiers & Qt::KeypadModifier) nMods |= kEventKeyModifierNumLockMask; + ok = true; return nMods; } @@ -189,26 +204,30 @@ 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; - hkeyID.signature = shortcut.first; - hkeyID.id = shortcut.second; + hkeyID.signature = shortcut.key; + hkeyID.id = shortcut.modifier; EventHotKeyRef eventRef = 0; - OSStatus status = RegisterEventHotKey(shortcut.first, - shortcut.second, + OSStatus status = RegisterEventHotKey(shortcut.key, + shortcut.modifier, hkeyID, GetApplicationEventTarget(), 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); @@ -221,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); @@ -230,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); @@ -250,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 12e7349..8bc5ab6 100644 --- a/QHotkey/qhotkey_p.h +++ b/QHotkey/qhotkey_p.h @@ -7,7 +7,13 @@ #include #include -class 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 @@ -16,7 +22,7 @@ class QHotkeyPrivate : public QObject, public QAbstractNativeEventFilter ~QHotkeyPrivate(); static QHotkeyPrivate *instance(); - static inline bool testValid(QHotkey::NativeShortcut nativeShortcut); + static bool isPlatformSupported(); QHotkey::NativeShortcut nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers); @@ -25,20 +31,24 @@ class QHotkeyPrivate : public QObject, public QAbstractNativeEventFilter protected: void activateShortcut(QHotkey::NativeShortcut shortcut); + void releaseShortcut(QHotkey::NativeShortcut shortcut); - virtual quint32 nativeKeycode(Qt::Key keycode) = 0;//platform implement - virtual quint32 nativeModifiers(Qt::KeyboardModifiers modifiers) = 0;//platform implement + 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 -private:/*functions*/ - Q_INVOKABLE bool addShortcutInvoked(QHotkey *hotkey); - Q_INVOKABLE bool removeShortcutInvoked(QHotkey *hotkey); - Q_INVOKABLE inline QHotkey::NativeShortcut nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers); + 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) \ @@ -49,13 +59,4 @@ class QHotkeyPrivate : public QObject, public QAbstractNativeEventFilter return hotkeyPrivate;\ } -inline bool QHotkeyPrivate::testValid(QHotkey::NativeShortcut nativeShortcut) -{ - return (nativeShortcut.first != 0); -} - -inline QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers) { - return {this->nativeKeycode(keycode), this->nativeModifiers(modifiers)}; -} - #endif // QHOTKEY_P_H diff --git a/QHotkey/qhotkey_win.cpp b/QHotkey/qhotkey_win.cpp index 64945eb..949d87a 100644 --- a/QHotkey/qhotkey_win.cpp +++ b/QHotkey/qhotkey_win.cpp @@ -1,44 +1,84 @@ #include "qhotkey.h" #include "qhotkey_p.h" #include +#include #include +#include +#include -#define HKEY_ID(nativeShortcut) (((nativeShortcut.first ^ (nativeShortcut.second << 8)) & 0x0FFF) | 0x7000) +#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) Q_DECL_OVERRIDE; - quint32 nativeModifiers(Qt::KeyboardModifiers modifiers) Q_DECL_OVERRIDE; + 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; + QList polledShortcuts; }; NATIVE_INSTANCE(QHotkeyPrivateWin) -bool QHotkeyPrivateWin::nativeEventFilter(const QByteArray &eventType, void *message, long *result) +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); + 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; } -quint32 QHotkeyPrivateWin::nativeKeycode(Qt::Key keycode) +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); } @@ -201,11 +241,16 @@ quint32 QHotkeyPrivateWin::nativeKeycode(Qt::Key keycode) return VK_OEM_FJ_TOUROKU; default: - return 0; + if(keycode <= 0xFFFF) + return static_cast(keycode); + else { + ok = false; + return 0; + } } } -quint32 QHotkeyPrivateWin::nativeModifiers(Qt::KeyboardModifiers modifiers) +quint32 QHotkeyPrivateWin::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) { quint32 nMods = 0; if (modifiers & Qt::ShiftModifier) @@ -216,6 +261,7 @@ quint32 QHotkeyPrivateWin::nativeModifiers(Qt::KeyboardModifiers modifiers) nMods |= MOD_ALT; if (modifiers & Qt::MetaModifier) nMods |= MOD_WIN; + ok = true; return nMods; } @@ -223,13 +269,12 @@ bool QHotkeyPrivateWin::registerShortcut(QHotkey::NativeShortcut shortcut) { BOOL ok = RegisterHotKey(NULL, HKEY_ID(shortcut), - shortcut.second, - shortcut.first); + 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; } } @@ -240,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 cebab34..d9d73f7 100644 --- a/QHotkey/qhotkey_x11.cpp +++ b/QHotkey/qhotkey_x11.cpp @@ -1,149 +1,235 @@ #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 +//compatibility 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, long *result) Q_DECL_OVERRIDE; + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; protected: - // QHotkeyPrivate interface - quint32 nativeKeycode(Qt::Key keycode) Q_DECL_OVERRIDE; - quint32 nativeModifiers(Qt::KeyboardModifiers modifiers) Q_DECL_OVERRIDE; - bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; - bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + // 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; + 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); + static QString formatX11Error(Display *display, int errorCode); - class HotkeyErrorHandler { - public: - HotkeyErrorHandler(); - ~HotkeyErrorHandler(); + class HotkeyErrorHandler { + public: + HotkeyErrorHandler(); + ~HotkeyErrorHandler(); static bool hasError; static QString errorString; - private: - XErrorHandler prevHandler; + private: + XErrorHandler prevHandler; - static int handleError(Display *display, XErrorEvent *error); - }; + 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, long *result) +bool QHotkeyPrivateX11::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) { - Q_UNUSED(eventType); - Q_UNUSED(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; + } - xcb_generic_event_t *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}); - } + return false; +} + +QString QHotkeyPrivateX11::getX11String(Qt::Key keycode) +{ + switch(keycode){ - return false; + 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) +quint32 QHotkeyPrivateX11::nativeKeycode(Qt::Key keycode, bool &ok) { - KeySym keysym = XStringToKeysym(QKeySequence(keycode).toString(QKeySequence::NativeText).toLatin1().constData()); - if (keysym == NoSymbol) { - //not found -> just use the key - if(keycode <= 0xFFFF) - keysym = keycode; - else - return 0; - } - - Display *display = QX11Info::display(); - if(display) - return XKeysymToKeycode(QX11Info::display(), keysym); - else - return 0; + 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(); +#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; + } + return 0; } -quint32 QHotkeyPrivateX11::nativeModifiers(Qt::KeyboardModifiers modifiers) +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; - return nMods; + 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) { - Display *display = QX11Info::display(); - if(!display) - return false; - - HotkeyErrorHandler errorHandler; - for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { - XGrabKey(display, - shortcut.first, - shortcut.second | specialMod, - DefaultRootWindow(display), - True, - GrabModeAsync, - GrabModeAsync); - } - - if(errorHandler.hasError) { - qCWarning(logQHotkey) << "[QHotkey] Failed to register hotkey. Error:" - << qPrintable(errorHandler.errorString); - this->unregisterShortcut(shortcut); - return false; - } else - return true; +#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) { - Display *display = QX11Info::display(); - if(!display) - return false; - - HotkeyErrorHandler errorHandler; - for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { - XUngrabKey(display, - shortcut.first, - shortcut.second | specialMod, - DefaultRootWindow(display)); - } +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + Display *display = qGuiApp->nativeInterface()->display(); +#else + Display *display = QX11Info::display(); +#endif - if(errorHandler.hasError) { - qCWarning(logQHotkey) << "Failed to unregister hotkey. Error:" - << qPrintable(errorHandler.errorString); - this->unregisterShortcut(shortcut); - return false; - } else - return true; + 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); + char errStr[256]; + XGetErrorText(display, errorCode, errStr, 256); + return QString::fromLatin1(errStr); } @@ -155,27 +241,31 @@ QString QHotkeyPrivateX11::HotkeyErrorHandler::errorString; QHotkeyPrivateX11::HotkeyErrorHandler::HotkeyErrorHandler() { - this->prevHandler = XSetErrorHandler(&HotkeyErrorHandler::handleError); + prevHandler = XSetErrorHandler(&HotkeyErrorHandler::handleError); } QHotkeyPrivateX11::HotkeyErrorHandler::~HotkeyErrorHandler() { - XSetErrorHandler(this->prevHandler); + 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 - HotkeyErrorHandler::hasError = true; - HotkeyErrorHandler::errorString = QHotkeyPrivateX11::formatX11Error(display, error->error_code); - return 1; - } - default: - return 0; - } + 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/README.md b/README.md index 2062867..4058dfd 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,55 @@ A global shortcut/hotkey for Desktop Qt-Applications. The QHotkey is a class that can be used to create hotkeys/global shortcuts, aka shortcuts that work everywhere, independent of the application state. This means your application can be active, inactive, minimized or not visible at all and still receive the shortcuts. ## Features - - Works on Windows, Mac and X11 - - Easy to use, can use `QKeySequence` for easy shortcut input - - Supports almost all common keys (Depends on OS & Keyboard-Layout) - - Allows direct input of Key/Modifier-Combinations - - Supports multiple QHotkey-instances for the same shortcut (with optimisations) - - Thread-Safe - Can be used on all threads (See section Thread safety) +- Works on Windows, Mac and X11 +- Easy to use, can use `QKeySequence` for easy shortcut input +- Supports almost all common keys (Depends on OS & Keyboard-Layout) +- Allows direct input of Key/Modifier-Combinations +- Supports multiple QHotkey-instances for the same shortcut (with optimisations) +- Thread-Safe - Can be used on all threads (See section Thread safety) +- Allows usage of native keycodes and modifiers, if needed + +**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. + +### Via qpmx +[qpmx](https://github.com/Skycoder42/qpmx) is a frontend for qpm (and other tools) with additional features, and is the preferred way to install packages. To use it: + +1. Install qpmx (See [GitHub - Installation](https://github.com/Skycoder42/qpmx#installation)) +2. Install qpm (See [GitHub - Installing](https://github.com/Cutehacks/qpm/blob/master/README.md#installing), for **windows** see below) +3. In your projects root directory, run `qpmx install de.skycoder42.qhotkey` + +### Via qpm + +1. Install qpm (See [GitHub - Installing](https://github.com/Cutehacks/qpm/blob/master/README.md#installing), for **windows** see below) +2. In your projects root directory, run `qpm install de.skycoder42.qhotkey` +3. Include qpm to your project by adding `include(vendor/vendor.pri)` to your `.pro` file + +Check their [GitHub - Usage for App Developers](https://github.com/Cutehacks/qpm/blob/master/README.md#usage-for-app-developers) to learn more about qpm. + +**Important for Windows users:** QPM Version *0.10.0* (the one you can download on the website) is currently broken on windows! It's already fixed in master, but not released yet. Until a newer versions gets released, you can download the latest dev build from here: +- https://storage.googleapis.com/www.qpm.io/download/latest/windows_amd64/qpm.exe +- https://storage.googleapis.com/www.qpm.io/download/latest/windows_386/qpm.exe ## Usage -Just copy the `./QHotkey` folder into you application and add the line `include(./QHotkey/qhotkey.pri)` to your .pro-file. This way all files and required libraries will automatically be added. Use `#include ` to access the class. +The general usage is to create QHotkey instances for specific hotkeys, register them and then simply connect to the signal emitted once the hotkey is pressed. ### Example The following example shows a simple application that will run without a window in the background until you press the key-combination Ctrl+Alt+Q (++Q on Mac). This will quit the application. The debug output will tell if the hotkey was successfully registered and that it was pressed. @@ -23,55 +63,57 @@ 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); - QHotkey hotkey(QKeySequence("ctrl+alt+Q"), true);//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, [&](){ - qDebug() << "Hotkey Activated - the application will quit now"; - qApp->quit(); - }); + QObject::connect(&hotkey, &QHotkey::activated, qApp, [&](){ + qDebug() << "Hotkey Activated - the application will quit now"; + qApp->quit(); + }); - return a.exec(); + return app.exec(); } ``` **Note:** You need the .pri include for this to work. ### Testing -By running the example in `./HotkeyTest` you can test out the QHotkey class. There are 3 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. +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 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`. For you this means: QHotkey instances on other threads than the main thread may take a little longer to register/unregister/translate hotkeys, because they have to wait for the main thread to do this for them. **Important:** there is however, one additional limitation that comes with that feature: QHotkey instances on other threads but the main thread *must* be unregistered or destroyed *before* the main eventloop ends. Otherwise, your application will hangup on destruction of the hotkey. This limitation does not apply for instances on the main thread. Furthermore, the same happens if you change the shortcut or register/unregister before the loop started, until it actually starts. ## Documentation -The documentation is available within the releases and on [github pages](https://skycoder42.github.io/QHotkey/). +The documentation is available as release and on [github pages](https://skycoder42.github.io/QHotkey/). The documentation was created using [doxygen](http://www.doxygen.org). It includes an HTML-documentation and Qt-Help files that can be included into QtCreator (QtAssistant) to show F1-Help (See [Adding External Documentation](https://doc.qt.io/qtcreator/creator-help.html#adding-external-documentation) for more details). ## Technical ### Requirements - - I built it with Qt 5.5.1, but may work with earlier versions, too - - At least the QtGui-Module (a QGuiApplication). Hotkeys on console based applications are not supported. (By the operating systems) + - Explicit support is only given down to the latest Qt LTS, but may work with earlier versions, too + - At least the QtGui-Module (a QGuiApplication). Hotkeys on console based applications are not supported (By the operating systems). You can however create a gui application without any visible window. - C++11 ### Known Limitations - Only single key/modifier combinations are possible. If using QKeySequence, only the first key+modifier of the sequence will be used. - - Qt::Key makes no difference between normal numbers and the Numpad numbers. Most keyboards however require this. Thus, you can't register shortcuts for the numpad. - - Supports not all keys, but most of the common ones. There are differences between platforms and it depends on the Keyboard-Layout. "Delete", for example, works on windows and mac, but not on X11 (At least on my test machines). I tried to use OS-Functions where possible, but since the Qt::Key values need to be converted into native keys, there are some limitations. + - Qt::Key makes no difference between normal numbers and the Numpad numbers. Most keyboards however require this. Thus, you can't register shortcuts for the numpad, unless you use a native shortcut. + - Supports not all keys, but most of the common ones. There are differences between platforms and it depends on the Keyboard-Layout. "Delete", for example, works on windows and mac, but not on X11 (At least on my test machines). I tried to use OS-Functions where possible, but since the Qt::Key values need to be converted into native keys, there are some limitations. I can use need such a key, try using the native shortcut. - The registered keys will be "taken" by QHotkey. This means after a hotkey was cosumend by your application, it will not be sent to the active application. This is done this way by the operating systems and cannot be changed. +- If you get a `QHotkey: Failed to register hotkey. Error: BadAccess (attempt to access private resource denied)` error on X11, this means you are trying to register a hotkey that is private to X11. Those keys simply cannot be registered using the normal API diff --git a/doc/qhotkey.doxy b/doc/Doxyfile similarity index 92% rename from doc/qhotkey.doxy rename to doc/Doxyfile index 495c78f..e65e660 100644 --- a/doc/qhotkey.doxy +++ b/doc/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.11 +# Doxyfile 1.8.14 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -20,8 +20,8 @@ # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. +# built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 @@ -38,7 +38,7 @@ PROJECT_NAME = QHotkey # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.0.0 +PROJECT_NUMBER = 1.2.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = ../../QHotkey_doc_generated +OUTPUT_DIRECTORY = /tmp/qthotkeydoc # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -236,7 +236,8 @@ TAB_SIZE = 4 # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. ALIASES = "accessors{1}=\1
Accessors
" \ readAc{1}=READ\1 \ @@ -251,11 +252,14 @@ ALIASES = "accessors{1}=< userAc{1}= \ "constantAc=" \ "finalAc=" \ - "default{1}=Default: \1
" \ - overloads{1}=Overloads:
    \1
\ - ovElem{1}=
  • \1
  • \ - "overload=This is an overloaded function." \ - "povElem{1}=
  • \1 (primary, check for more details)
  • " + "default{1}=Default: \1
    " \ + "readAcFn{1}=READ accessor for \1" \ + "writeAcFn{1}=WRITE accessor for \1" \ + "notifyAcFn{1}=NOTIFY accessor for \1" \ + "resetAcFn{1}=RESET accessor for \1" \ + "memberAcFn{1}=MEMBER accessor for \1" \ + "inherit{1}=Inherits \1" \ + "privsig=This is a private signal. It can be connect to as usual, but can only be emitted by the class it belongs to" # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" @@ -320,6 +324,15 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -345,7 +358,7 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. @@ -716,7 +729,7 @@ LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. @@ -764,7 +777,7 @@ WARN_IF_DOC_ERROR = YES # parameter documentation, but not about the absence of documentation. # The default value is: NO. -WARN_NO_PARAMDOC = YES +WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. @@ -805,7 +818,7 @@ INPUT = ../QHotkey/qhotkey.h \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of # possible encodings. # The default value is: UTF-8. @@ -822,8 +835,8 @@ INPUT_ENCODING = UTF-8 # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl, -# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js. +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. FILE_PATTERNS = *.c \ *.cc \ @@ -912,7 +925,21 @@ EXCLUDE_PATTERNS = moc_* \ # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = QAbstractAspect \ + QBasicAtomicInteger \ + QBasicAtomicPointer \ + QBasicMutex \ + QComponent \ + QEntity \ + QFutureWatcherBase \ + QGeometry \ + QGeometryRenderer \ + QMaterial \ + QNode \ + QNodeCreatedChangeBase \ + QPaintEngineEx \ + QQmlExtensionInterface \ + QTechniqueFilter # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include @@ -1055,7 +1082,7 @@ SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version +# (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: @@ -1082,25 +1109,6 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse-libclang=ON option for CMake. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1219,7 +1227,7 @@ HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. @@ -1255,6 +1263,17 @@ HTML_COLORSTYLE_GAMMA = 89 HTML_TIMESTAMP = YES +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via Javascript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have Javascript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. @@ -1278,12 +1297,12 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# environment (see: https://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1399,40 +1418,37 @@ QCH_FILE = QHotkeyHelp.qch # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_NAMESPACE = com.github.Skycoder42.QHotkey +QHP_NAMESPACE = de.skycoder42.QHotkey.122 # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_VIRTUAL_FOLDER = doc_QHotkey +QHP_VIRTUAL_FOLDER = doc_qhotkey # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_NAME = "QHotkey 1.2.2" # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_ATTRS = +QHP_CUST_FILTER_ATTRS = "qhotkey 1.2.2" # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = @@ -1442,7 +1458,7 @@ QHP_SECT_FILTER_ATTRS = # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. -QHG_LOCATION = C:/Qt/5.5/msvc2013_64/bin/qhelpgenerator.exe +QHG_LOCATION = /usr/bin/qhelpgenerator # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To @@ -1525,7 +1541,7 @@ EXT_LINKS_IN_WINDOW = YES FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # @@ -1537,7 +1553,7 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side Javascript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1564,8 +1580,8 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest @@ -1626,7 +1642,7 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). +# Xapian (see: https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1639,7 +1655,7 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Xapian (see: https://xapian.org/). See the section "External Indexing and # Searching" for details. # This tag requires that the tag SEARCHENGINE is set to YES. @@ -1826,7 +1842,7 @@ LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. # The default value is: plain. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2009,9 +2025,9 @@ DOCBOOK_PROGRAMLISTING = NO #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sf.net) file that captures the -# structure of the code including all documentation. Note that this feature is -# still experimental and incomplete at the moment. +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO @@ -2093,7 +2109,7 @@ SEARCH_INCLUDES = YES # preprocessor. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = C:/Qt/5.5/msvc2013_64/include +INCLUDE_PATH = /usr/include/qt # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -2113,7 +2129,7 @@ INCLUDE_FILE_PATTERNS = *.h \ # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = __cplusplus=201402L # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2151,42 +2167,50 @@ SKIP_FUNCTION_MACROS = YES # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = C:/Qt/Docs/Qt-5.5/qtwebengine/qtwebengine.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtgraphicaleffects/qtgraphicaleffects.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qt3dcollision/qt3dcollision.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qt3dlogic/qt3dlogic.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qt3dcore/qt3dcore.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qt3drenderer/qt3drenderer.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtwebchannel/qtwebchannel.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtwebsockets/qtwebsockets.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtbluetooth/qtbluetooth.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtnfc/qtnfc.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtsensors/qtsensors.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtlocation/qtlocation.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtpositioning/qtpositioning.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/activeqt/activeqt.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtquickcontrols/qtquickcontrols.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtqml/qtqml.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtquick/qtquick.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtxmlpatterns/qtxmlpatterns.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtsvg/qtsvg.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qdoc/qdoc.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtprintsupport/qtprintsupport.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtconcurrent/qtconcurrent.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtgui/qtgui.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qttestlib/qttestlib.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtwidgets/qtwidgets.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtcore/qtcore.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtnetwork/qtnetwork.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtsql/qtsql.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtxml/qtxml.tags=http://doc.qt.io/qt-5 \ - C:/Qt/Docs/Qt-5.5/qtwebenginewidgets/qtwebenginewidgets.tags=http://doc.qt.io/qt-5 +TAGFILES = "/home/sky/Qt/Docs/Qt-5.10.1/qdoc/qdoc.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtlabsplatform/qtlabsplatform.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtqml/qtqml.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtprintsupport/qtprintsupport.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qttestlib/qttestlib.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtautoupdater/qtautoupdater.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtlabscalendar/qtlabscalendar.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtdatasync/qtdatasync.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtwebchannel/qtwebchannel.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtpositioning/qtpositioning.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtrestclient/qtrestclient.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtcore/qtcore.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtnfc/qtnfc.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtquickcontrols/qtquickcontrols.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtwidgets/qtwidgets.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtgraphicaleffects/qtgraphicaleffects.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtlocation/qtlocation.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtgui/qtgui.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtwebengine/qtwebengine.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtnetwork/qtnetwork.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtxml/qtxml.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtscxml/qtscxml.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/activeqt/activeqt.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtjsonserializer/qtjsonserializer.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtsensors/qtsensors.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtnetworkauth/qtnetworkauth.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtxmlpatterns/qtxmlpatterns.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtspeech/qtspeech.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtsvg/qtsvg.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtserialbus/qtserialbus.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtbluetooth/qtbluetooth.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtsql/qtsql.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtwebsockets/qtwebsockets.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtquick/qtquick.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qt3d/qt3d.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtquickcontrols2/qtquickcontrols2.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtremoteobjects/qtremoteobjects.tags=https://doc.qt.io/qt-5" \ + "/home/sky/Qt/Docs/Qt-5.10.1/qtconcurrent/qtconcurrent.tags=https://doc.qt.io/qt-5" # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = ../../QHotkey_doc_generated/QHotkey.tag +GENERATE_TAGFILE = /tmp/qthotkeydoc/QHotkey.tag # If the ALLEXTERNALS tag is set to YES, all external class will be listed in # the class index. If set to NO, only the inherited external classes will be @@ -2464,6 +2488,11 @@ DIAFILE_DIRS = PLANTUML_JAR_PATH = +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + # When using plantuml, the specified paths are searched for files specified by # the !include statement in a plantuml block. diff --git a/doc/qhotkey.dox b/doc/qhotkey.dox index 450defa..dabde3c 100644 --- a/doc/qhotkey.dox +++ b/doc/qhotkey.dox @@ -22,9 +22,9 @@ thread until the applications eventloop has the time to handle it. If the loop i it does. This does not happen if used from the main thread. @accessors{ - @readAc{isRegistered()} - @writeAc{setRegistered()} - @notifyAc{registeredChanged()} + @readAc{isRegistered()} + @writeAc{setRegistered()} + @notifyAc{registeredChanged()} } In addition to the normal accessors, a modification of the QHotkey::shortcut property can change this property, too. @@ -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, @@ -49,101 +49,84 @@ thread until the applications eventloop has the time to handle it. If the loop i it does. This does not happen if used from the main thread. @accessors{ - @readAc{ - shortcut()
    - keyCode()
    - modifiers() - } - @writeAc{ - setShortcut(const QKeySequence &, bool)
    - setShortcut(Qt::Key, Qt::KeyboardModifiers, bool) - } + @readAc{ + shortcut()
    + keyCode()
    + modifiers() + } + @writeAc{ + setShortcut(const QKeySequence &, bool)
    + setShortcut(Qt::Key, Qt::KeyboardModifiers, bool) + } } -@sa QHotkey::registered, QHotkey::isKeyCaptured -*/ - -/*! -@typedef QHotkey::NativeShortcut - -
    Accessors
    USER\1
    CONSTANT
    FINAL
    - - - - - - - - - - - - -
    MemberDetails
    firstThe keycode of the key to be pressed
    secondThe modifiers that have to be pressed together with the key
    - -The NativeShortcut is used internally only and holds the native keycode and modifiers, after they have been translated -from Qt::Key and Qt::Modifiers into the native ones. +@sa QHotkey::registered, QHotkey::isKeyCaptured, QHotkey::currentNativeShortcut, QHotkey::setNativeShortcut */ /*! @fn QHotkey::QHotkey(QObject *) -@overload @param parent The parent object - -@overloads{ - @povElem{QHotkey::QHotkey(QObject *)} - @ovElem{QHotkey::QHotkey(const QKeySequence &, bool, QObject *)} - @ovElem{QHotkey::QHotkey(Qt::Key, Qt::KeyboardModifiers, bool, QObject *)} -} */ /*! @fn QHotkey::QHotkey(const QKeySequence &, bool, QObject *) -@overload @param parent The parent object @param shortcut The shortcut this hotkey should be registered for @param autoRegister Specifies, whether the hotkey should be automatically registered or not -@overloads{ - @povElem{QHotkey::QHotkey(QObject *)} - @ovElem{QHotkey::QHotkey(const QKeySequence &, bool, QObject *)} - @ovElem{QHotkey::QHotkey(Qt::Key, Qt::KeyboardModifiers, bool, QObject *)} -} - @sa QHotkey::shortcut, QHotkey::registered */ /*! @fn QHotkey::QHotkey(Qt::Key, Qt::KeyboardModifiers, bool, QObject *) -@overload @param parent The parent object -@param key The key this hotkey should be registered for +@param keyCode The key this hotkey should be registered for @param modifiers The modifiers that have to be pressed together with the `key` @param autoRegister Specifies, whether the hotkey should be automatically registered or not -@overloads{ - @povElem{QHotkey::QHotkey(QObject *)} - @ovElem{QHotkey::QHotkey(const QKeySequence &, bool, QObject *)} - @ovElem{QHotkey::QHotkey(Qt::Key, Qt::KeyboardModifiers, bool, QObject *)} -} - @sa QHotkey::shortcut, QHotkey::registered */ +/*! +@fn QHotkey::QHotkey(const NativeShortcut &, bool, QObject *) + +@param parent The parent object +@param shortcut The native shortcut this hotkey should be registered for +@param autoRegister Specifies, whether the hotkey should be automatically registered or not + +@note Please check QHotkey::setNativeShortcut for important hints! + +@sa QHotkey::setNativeShortcut, QHotkey::registered +*/ + /*! @fn QHotkey::~QHotkey 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 */ +/*! +@fn QHotkey::setNativeShortcut + +@param nativeShortcut The native shortcut to be set +@param autoRegister Specifies, whether the hotkey should be automatically registered, or not +@returns `true`, if the shortcut could be successfully set/registered, `false` if not + +@warning Setting the native shortcut will cause the shortcut(), keyCode() and modifiers() functions +to return "invalid values", i.e. if a hotkey has an explicitly set native shortcut, it will not be able +to get the Qt values for them. + +@sa QHotkey::currentNativeShortcut, QHotkey::shortcut, QHotkey::registered +*/ + /*! @fn QHotkey::activated @@ -152,3 +135,31 @@ from the thread this instance lives on. @note This is a private signal. It can be used in signal connections but cannot be emitted by the user. */ + +/*! +@fn QHotkey::addGlobalMapping + +@param shortcut The keysequence to add the mapping for +@param nativeShortcut The native shortcut to overwrite the sequence with + +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 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. + +The advantage of using this approach via simply registering it as native key directly, is that +these mappings work for cases where users input their own hotkeys. +*/ + +/*! +@class QHotkey::NativeShortcut + +The NativeShortcut holds the native keycode and modifiers, after they have been translated +from Qt::Key and Qt::Modifiers into the native ones. + +It can be used to find out how a current hotkey is mapped to the system, or to explicitly create +a shortcut from those native values + +@sa QHotkey::setNativeShortcut +*/ diff --git a/qpm.json b/qpm.json new file mode 100644 index 0000000..61cbd48 --- /dev/null +++ b/qpm.json @@ -0,0 +1,22 @@ +{ + "author": { + "email": "skycoder42.de@gmx.de", + "name": "Skycoder" + }, + "dependencies": [ + ], + "description": "A global shortcut/hotkey for Desktop Qt-Applications", + "license": "BSD_3_CLAUSE", + "name": "de.skycoder42.qhotkey", + "pri_filename": "qhotkey.pri", + "repository": { + "type": "GITHUB", + "url": "https://github.com/Skycoder42/QHotkey.git" + }, + "version": { + "fingerprint": "", + "label": "1.2.2", + "revision": "" + }, + "webpage": "https://github.com/Skycoder42/QHotkey" +} diff --git a/qpmx.json b/qpmx.json new file mode 100644 index 0000000..bea438f --- /dev/null +++ b/qpmx.json @@ -0,0 +1,28 @@ +{ + "dependencies": [ + ], + "license": { + "file": "LICENSE", + "name": "BSD_3_CLAUSE" + }, + "prcFile": "qhotkey.prc", + "priFile": "qhotkey.pri", + "priIncludes": [ + ], + "publishers": { + "qpm": { + "author": { + "email": "skycoder42.de@gmx.de", + "name": "Skycoder" + }, + "description": "A global shortcut/hotkey for Desktop Qt-Applications", + "name": "de.skycoder42.qhotkey", + "repository": { + "type": "GITHUB", + "url": "https://github.com/Skycoder42/QHotkey.git" + }, + "webpage": "https://github.com/Skycoder42/QHotkey" + } + }, + "source": false +} 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