├── .github └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── HotkeyTest ├── CMakeLists.txt ├── hottestwidget.cpp ├── hottestwidget.h ├── hottestwidget.ui └── main.cpp ├── LICENSE ├── QHotkey ├── QHotkey ├── qhotkey.cpp ├── qhotkey.h ├── qhotkey_mac.cpp ├── qhotkey_p.h ├── qhotkey_win.cpp └── qhotkey_x11.cpp ├── README.md ├── doc ├── Doxyfile └── qhotkey.dox ├── qpm.json └── qpmx.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - "releases/**" 7 | paths-ignore: 8 | - "**.md" 9 | pull_request: 10 | paths-ignore: 11 | - "**.md" 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | qt_version: [5.12.12, 5.15.2, 6.2.2] 18 | platform: [ubuntu-20.04, windows-latest, macos-latest] 19 | include: 20 | - qt_version: 6.2.2 21 | additional_arguments: -D QT_DEFAULT_MAJOR_VERSION=6 22 | - platform: ubuntu-20.04 23 | make: make 24 | CXXFLAGS: -Wall -Wextra -pedantic -Werror 25 | MAKEFLAGS: -j2 26 | - platform: macos-latest 27 | make: make 28 | CXXFLAGS: -Wall -Wextra -pedantic -Werror -Wno-gnu-zero-variadic-macro-arguments # Ignore false-positive warning for qCWarning 29 | MAKEFLAGS: -j3 30 | - platform: windows-latest 31 | make: nmake 32 | CXXFLAGS: /W4 /WX /MP 33 | 34 | runs-on: ${{ matrix.platform }} 35 | env: 36 | CXXFLAGS: ${{ matrix.CXXFLAGS }} 37 | MAKEFLAGS: ${{ matrix.MAKEFLAGS }} 38 | 39 | steps: 40 | - name: Clone repo 41 | uses: actions/checkout@v2.3.4 42 | 43 | - name: Install Qt 44 | uses: jurplel/install-qt-action@v3.3.0 45 | with: 46 | version: ${{ matrix.qt_version }} 47 | 48 | - name: Build with CMake as static 49 | run: | 50 | cmake . -D QHOTKEY_EXAMPLES=ON -D CMAKE_OSX_ARCHITECTURES="x86_64" ${{ matrix.additional_arguments }} 51 | cmake --build . 52 | 53 | - name: Build with CMake as shared 54 | run: | 55 | cmake . -D BUILD_SHARED_LIBS=ON -D QHOTKEY_EXAMPLES=ON -D CMAKE_OSX_ARCHITECTURES="x86_64" ${{ matrix.additional_arguments }} 56 | cmake --build . 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | qrc_*.cpp 24 | ui_*.h 25 | Makefile* 26 | *build-* 27 | 28 | # QtCreator 29 | 30 | *.autosave 31 | 32 | #QtCtreator Qml 33 | *.qmlproject.user 34 | *.qmlproject.user.* 35 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(qhotkey 4 | VERSION 1.5.0 5 | DESCRIPTION "Global hotkey library for Qt software" 6 | HOMEPAGE_URL "https://skycoder42.github.io/QHotkey/" 7 | LANGUAGES CXX) 8 | 9 | option(QHOTKEY_EXAMPLES "Build examples" OFF) 10 | option(QHOTKEY_INSTALL "Enable install rule" ON) 11 | 12 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 13 | set(CMAKE_AUTOMOC ON) 14 | 15 | if(NOT QT_DEFAULT_MAJOR_VERSION) 16 | set(QT_DEFAULT_MAJOR_VERSION 5 CACHE STRING "Qt version to use (5 or 6), defaults to 5") 17 | endif() 18 | 19 | if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) 20 | find_package(Qt${QT_DEFAULT_MAJOR_VERSION} 6.2.0 COMPONENTS Core Gui REQUIRED) 21 | else() 22 | find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Core Gui REQUIRED) 23 | endif() 24 | 25 | # General settings 26 | set(CPACK_PACKAGE_VENDOR "Skycoder42") 27 | set(CPACK_PACKAGE_CONTACT "Shatur") 28 | set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") 29 | # CPACK: DEB Specific Settings 30 | set(CPACK_DEBIAN_PACKAGE_NAME "libqhotkey") 31 | set(CPACK_DEBIAN_PACKAGE_SECTION "Libraries") 32 | # Set dependencies 33 | if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) 34 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6x11extras6 (>= 6.2.0)") 35 | else() 36 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5x11extras5 (>= 5.15.2)") 37 | endif() 38 | include(CPack) 39 | 40 | add_library(qhotkey QHotkey/qhotkey.cpp) 41 | add_library(QHotkey::QHotkey ALIAS qhotkey) 42 | target_link_libraries(qhotkey PUBLIC Qt${QT_DEFAULT_MAJOR_VERSION}::Core Qt${QT_DEFAULT_MAJOR_VERSION}::Gui) 43 | 44 | if(BUILD_SHARED_LIBS) 45 | target_compile_definitions(qhotkey PRIVATE QHOTKEY_LIBRARY) 46 | target_compile_definitions(qhotkey PUBLIC QHOTKEY_SHARED) 47 | endif() 48 | 49 | if(APPLE) 50 | find_library(CARBON_LIBRARY Carbon) 51 | mark_as_advanced(CARBON_LIBRARY) 52 | 53 | target_sources(qhotkey PRIVATE QHotkey/qhotkey_mac.cpp) 54 | target_link_libraries(qhotkey PRIVATE ${CARBON_LIBRARY}) 55 | elseif(WIN32) 56 | target_sources(qhotkey PRIVATE QHotkey/qhotkey_win.cpp) 57 | else() 58 | find_package(X11 REQUIRED) 59 | if(QT_DEFAULT_MAJOR_VERSION GREATER_EQUAL 6) 60 | target_link_libraries(qhotkey PRIVATE ${X11_LIBRARIES}) 61 | else() 62 | find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS X11Extras REQUIRED) 63 | target_link_libraries(qhotkey 64 | PRIVATE 65 | ${X11_LIBRARIES} 66 | Qt${QT_DEFAULT_MAJOR_VERSION}::X11Extras) 67 | endif() 68 | 69 | include_directories(${X11_INCLUDE_DIR}) 70 | target_sources(qhotkey PRIVATE QHotkey/qhotkey_x11.cpp) 71 | endif() 72 | 73 | include(GNUInstallDirs) 74 | 75 | target_include_directories(qhotkey 76 | PUBLIC 77 | $ 78 | $) 79 | 80 | include(CMakePackageConfigHelpers) 81 | 82 | set_target_properties(qhotkey PROPERTIES 83 | SOVERSION ${PROJECT_VERSION_MAJOR} 84 | VERSION ${PROJECT_VERSION} 85 | INTERFACE_QHotkey_MAJOR_VERSION ${PROJECT_VERSION_MAJOR} 86 | COMPATIBLE_INTERFACE_STRING QHotkey_MAJOR_VERSION) 87 | 88 | write_basic_package_version_file( 89 | ${CMAKE_CURRENT_BINARY_DIR}/QHotkeyConfigVersion.cmake 90 | VERSION "${PROJECT_VERSION}" 91 | COMPATIBILITY AnyNewerVersion) 92 | 93 | if(QHOTKEY_EXAMPLES) 94 | add_subdirectory(HotkeyTest) 95 | endif() 96 | 97 | if(QHOTKEY_INSTALL) 98 | set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/QHotkey) 99 | 100 | install( 101 | TARGETS qhotkey EXPORT QHotkeyConfig 102 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 103 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 104 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 105 | install(FILES 106 | ${CMAKE_CURRENT_SOURCE_DIR}/QHotkey/qhotkey.h 107 | ${CMAKE_CURRENT_SOURCE_DIR}/QHotkey/QHotkey 108 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 109 | install(FILES 110 | ${CMAKE_CURRENT_BINARY_DIR}/QHotkeyConfigVersion.cmake 111 | DESTINATION ${INSTALL_CONFIGDIR}) 112 | install(EXPORT QHotkeyConfig DESTINATION ${INSTALL_CONFIGDIR}) 113 | 114 | export(TARGETS qhotkey FILE QHotkeyConfig.cmake) 115 | endif() 116 | -------------------------------------------------------------------------------- /HotkeyTest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 2 | 3 | set(CMAKE_AUTOUIC ON) 4 | 5 | add_executable(HotkeyTest 6 | main.cpp 7 | hottestwidget.cpp 8 | hottestwidget.ui) 9 | 10 | find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Widgets REQUIRED) 11 | target_link_libraries(HotkeyTest Qt${QT_DEFAULT_MAJOR_VERSION}::Widgets QHotkey::QHotkey) 12 | -------------------------------------------------------------------------------- /HotkeyTest/hottestwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "hottestwidget.h" 2 | #include "ui_hottestwidget.h" 3 | 4 | //#define TEST_MAPPING 5 | 6 | HotTestWidget::HotTestWidget(QWidget *parent) : 7 | QWidget(parent), 8 | ui(new Ui::HotTestWidget), 9 | hotkey_1(new QHotkey(this)), 10 | hotkey_2(new QHotkey(this)), 11 | hotkey_3(new QHotkey(this)), 12 | hotkey_4(new QHotkey(NULL)), 13 | hotkey_5(new QHotkey(NULL)), 14 | thread4(new QThread(this)), 15 | thread5(new QThread(this)), 16 | testHotkeys(), 17 | nativeHotkey(new QHotkey(this)) 18 | { 19 | ui->setupUi(this); 20 | this->thread4->start(); 21 | this->thread5->start(); 22 | 23 | #ifdef TEST_MAPPING 24 | //shortcut mapping override 25 | QHotkey::addGlobalMapping(QKeySequence("X"), QHotkey::NativeShortcut());// add invalid mapping to test if the overwrite works for all platforms 26 | #endif 27 | 28 | //1 29 | connect(this->ui->hotkeyCheckbox_1, &QCheckBox::toggled, 30 | this->hotkey_1, &QHotkey::setRegistered); 31 | connect(this->ui->hotkeySequenceEdit_1, &QKeySequenceEdit::keySequenceChanged, 32 | this, &HotTestWidget::setShortcut_1); 33 | connect(this->hotkey_1, &QHotkey::activated, 34 | this, &HotTestWidget::increase_1); 35 | 36 | //2 37 | connect(this->ui->hotkeyCheckbox_2, &QCheckBox::toggled, 38 | this->hotkey_2, &QHotkey::setRegistered); 39 | connect(this->ui->hotkeySequenceEdit_2, &QKeySequenceEdit::keySequenceChanged, 40 | this, &HotTestWidget::setShortcut_2); 41 | connect(this->hotkey_2, &QHotkey::activated, 42 | this, &HotTestWidget::increase_2); 43 | 44 | //3 45 | connect(this->ui->hotkeyCheckbox_3, &QCheckBox::toggled, 46 | this->hotkey_3, &QHotkey::setRegistered); 47 | connect(this->ui->hotkeySequenceEdit_3, &QKeySequenceEdit::keySequenceChanged, 48 | this, &HotTestWidget::setShortcut_3); 49 | connect(this->hotkey_3, &QHotkey::activated, 50 | this, &HotTestWidget::increase_3); 51 | 52 | //4 53 | connect(this->ui->hotkeyCheckbox_4, &QCheckBox::toggled, 54 | this->hotkey_4, &QHotkey::setRegistered); 55 | connect(this->ui->hotkeySequenceEdit_4, &QKeySequenceEdit::keySequenceChanged, 56 | this, &HotTestWidget::setShortcut_4); 57 | connect(this->hotkey_4, &QHotkey::activated, 58 | this, &HotTestWidget::increase_4); 59 | 60 | //5 61 | connect(this->ui->hotkeyCheckbox_5, &QCheckBox::toggled, 62 | this->hotkey_5, &QHotkey::setRegistered); 63 | connect(this->ui->hotkeySequenceEdit_5, &QKeySequenceEdit::keySequenceChanged, 64 | this, &HotTestWidget::setShortcut_5); 65 | connect(this->hotkey_5, &QHotkey::activated, 66 | this, &HotTestWidget::increase_5); 67 | 68 | //test connections 69 | this->testHotkeys += new QHotkey(Qt::Key_F, Qt::NoModifier, false, this); 70 | connect(this->testHotkeys.last(), &QHotkey::activated, 71 | this->ui->hotkeyFCheckBox, &QCheckBox::toggle); 72 | this->testHotkeys += new QHotkey(Qt::Key_F12, Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier, false, this); 73 | connect(this->testHotkeys.last(), &QHotkey::activated, 74 | this->ui->hotkeyCtrlAltMetaF12CheckBox, &QCheckBox::toggle); 75 | this->testHotkeys += new QHotkey(Qt::Key_Cancel, Qt::ControlModifier | Qt::ShiftModifier, false, this); 76 | connect(this->testHotkeys.last(), &QHotkey::activated, 77 | this->ui->hotkeyCtrlShiftCancelCheckBox, &QCheckBox::toggle); 78 | this->testHotkeys += new QHotkey(Qt::Key_Delete, Qt::MetaModifier, false, this); 79 | connect(this->testHotkeys.last(), &QHotkey::activated, 80 | this->ui->hotkeyMetaDelCheckBox, &QCheckBox::toggle); 81 | this->testHotkeys += new QHotkey(Qt::Key_NumLock, Qt::NoModifier, false, this); 82 | connect(this->testHotkeys.last(), &QHotkey::activated, 83 | this->ui->hotkeyNumlockCheckBox, &QCheckBox::toggle); 84 | this->testHotkeys += new QHotkey(Qt::Key_5, Qt::ControlModifier, false, this); 85 | connect(this->testHotkeys.last(), &QHotkey::activated, 86 | this->ui->hotkeyCtrl5CheckBox, &QCheckBox::toggle); 87 | this->testHotkeys += new QHotkey(Qt::Key_Tab, Qt::ShiftModifier, false, this); 88 | connect(this->testHotkeys.last(), &QHotkey::activated, 89 | this->ui->hotkeyShiftTabCheckBox, &QCheckBox::toggle); 90 | this->testHotkeys += new QHotkey(Qt::Key_Comma, Qt::ShiftModifier, false, this); 91 | connect(this->testHotkeys.last(), &QHotkey::activated, 92 | this->ui->hotkeyShiftCheckBox, &QCheckBox::toggle); 93 | this->testHotkeys += new QHotkey(Qt::Key_Semicolon, Qt::ShiftModifier, false, this); 94 | connect(this->testHotkeys.last(), &QHotkey::activated, 95 | this->ui->hotkeyShiftCheckBox_2, &QCheckBox::toggle); 96 | this->testHotkeys += new QHotkey(Qt::Key_K, Qt::ShiftModifier | Qt::AltModifier, false, this); 97 | connect(this->testHotkeys.last(), &QHotkey::activated, 98 | this->ui->hotkeyShiftAltKCheckBox, &QCheckBox::toggle); 99 | this->testHotkeys += new QHotkey(Qt::Key_K, Qt::ShiftModifier | Qt::AltModifier, false, this); 100 | connect(this->testHotkeys.last(), &QHotkey::activated, 101 | this->ui->hotkeyShiftAltKCheckBox_2, &QCheckBox::toggle); 102 | 103 | //native 104 | connect(this->nativeHotkey, &QHotkey::activated, 105 | this, &HotTestWidget::increase_native); 106 | } 107 | 108 | HotTestWidget::~HotTestWidget() 109 | { 110 | this->thread4->quit(); 111 | this->thread4->wait(); 112 | this->thread5->quit(); 113 | this->thread5->wait(); 114 | 115 | delete this->hotkey_4; 116 | delete this->hotkey_5; 117 | 118 | delete ui; 119 | } 120 | 121 | void HotTestWidget::setShortcut_1(const QKeySequence &sequence) 122 | { 123 | this->hotkey_1->setShortcut(sequence, false); 124 | } 125 | 126 | void HotTestWidget::setShortcut_2(const QKeySequence &sequence) 127 | { 128 | this->hotkey_2->setShortcut(sequence, false); 129 | } 130 | 131 | void HotTestWidget::setShortcut_3(const QKeySequence &sequence) 132 | { 133 | this->hotkey_3->setShortcut(sequence, false); 134 | } 135 | 136 | void HotTestWidget::setShortcut_4(const QKeySequence &sequence) 137 | { 138 | this->hotkey_4->setShortcut(sequence, false); 139 | } 140 | 141 | void HotTestWidget::setShortcut_5(const QKeySequence &sequence) 142 | { 143 | this->hotkey_5->setShortcut(sequence, false); 144 | } 145 | 146 | void HotTestWidget::increase_1() 147 | { 148 | this->ui->hotkeyCount_1->display(this->ui->hotkeyCount_1->intValue() + 1); 149 | } 150 | 151 | void HotTestWidget::increase_2() 152 | { 153 | this->ui->hotkeyCount_2->display(this->ui->hotkeyCount_2->intValue() + 1); 154 | } 155 | 156 | void HotTestWidget::increase_3() 157 | { 158 | this->ui->hotkeyCount_3->display(this->ui->hotkeyCount_3->intValue() + 1); 159 | } 160 | 161 | void HotTestWidget::increase_4() 162 | { 163 | this->ui->hotkeyCount_4->display(this->ui->hotkeyCount_4->intValue() + 1); 164 | } 165 | 166 | void HotTestWidget::increase_5() 167 | { 168 | this->ui->hotkeyCount_5->display(this->ui->hotkeyCount_5->intValue() + 1); 169 | } 170 | 171 | void HotTestWidget::on_resetButton_1_clicked() 172 | { 173 | this->ui->hotkeyCount_1->display(0); 174 | } 175 | 176 | void HotTestWidget::on_resetButton_2_clicked() 177 | { 178 | this->ui->hotkeyCount_2->display(0); 179 | } 180 | 181 | void HotTestWidget::on_resetButton_3_clicked() 182 | { 183 | this->ui->hotkeyCount_3->display(0); 184 | } 185 | 186 | void HotTestWidget::on_resetButton_4_clicked() 187 | { 188 | this->ui->hotkeyCount_4->display(0); 189 | } 190 | 191 | void HotTestWidget::on_resetButton_5_clicked() 192 | { 193 | this->ui->hotkeyCount_5->display(0); 194 | } 195 | 196 | void HotTestWidget::on_groupBox_toggled(bool checked) 197 | { 198 | for(QHotkey *hotkey : this->testHotkeys) 199 | hotkey->setRegistered(checked); 200 | } 201 | 202 | void HotTestWidget::on_threadEnableCheckBox_clicked() 203 | { 204 | this->ui->threadEnableCheckBox->setEnabled(false); 205 | this->ui->hotkeyCheckbox_1->setChecked(false); 206 | this->ui->hotkeyCheckbox_2->setChecked(false); 207 | this->ui->hotkeyCheckbox_3->setChecked(false); 208 | this->ui->hotkeyCheckbox_4->setChecked(false); 209 | this->ui->hotkeyCheckbox_5->setChecked(false); 210 | 211 | QApplication::processEvents(); 212 | 213 | Q_ASSERT(!this->hotkey_4->isRegistered()); 214 | Q_ASSERT(!this->hotkey_5->isRegistered()); 215 | 216 | this->hotkey_4->moveToThread(this->thread4); 217 | this->hotkey_5->moveToThread(this->thread5); 218 | 219 | QApplication::processEvents(); 220 | Q_ASSERT(this->hotkey_4->thread() == this->thread4); 221 | Q_ASSERT(this->hotkey_5->thread() == this->thread5); 222 | 223 | connect(this->thread4, &QThread::finished, this, [this](){ 224 | this->hotkey_4->moveToThread(qApp->thread()); 225 | }); 226 | connect(this->thread5, &QThread::finished, this, [this](){ 227 | this->hotkey_5->moveToThread(qApp->thread()); 228 | }); 229 | 230 | this->ui->tabWidget->setCurrentIndex(0); 231 | } 232 | 233 | void HotTestWidget::on_registeredCheckBox_toggled(bool checked) 234 | { 235 | if(checked) { 236 | this->nativeHotkey->setNativeShortcut({ 237 | (quint32)this->ui->nativeKeySpinBox->value(), 238 | (quint32)this->ui->nativeModifiersSpinBox->value() 239 | }, true); 240 | } else 241 | this->nativeHotkey->setRegistered(false); 242 | } 243 | 244 | void HotTestWidget::increase_native() 245 | { 246 | this->ui->nativeCount->display(this->ui->nativeCount->intValue() + 1); 247 | } 248 | -------------------------------------------------------------------------------- /HotkeyTest/hottestwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef HOTTESTWIDGET_H 2 | #define HOTTESTWIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Ui { 9 | class HotTestWidget; 10 | } 11 | 12 | class HotTestWidget : public QWidget 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit HotTestWidget(QWidget *parent = 0); 18 | ~HotTestWidget(); 19 | 20 | private slots: 21 | void setShortcut_1(const QKeySequence &sequence); 22 | void setShortcut_2(const QKeySequence &sequence); 23 | void setShortcut_3(const QKeySequence &sequence); 24 | void setShortcut_4(const QKeySequence &sequence); 25 | void setShortcut_5(const QKeySequence &sequence); 26 | 27 | void increase_1(); 28 | void increase_2(); 29 | void increase_3(); 30 | void increase_4(); 31 | void increase_5(); 32 | 33 | void on_resetButton_1_clicked(); 34 | void on_resetButton_2_clicked(); 35 | void on_resetButton_3_clicked(); 36 | void on_resetButton_4_clicked(); 37 | void on_resetButton_5_clicked(); 38 | 39 | void on_groupBox_toggled(bool checked); 40 | void on_threadEnableCheckBox_clicked(); 41 | 42 | void on_registeredCheckBox_toggled(bool checked); 43 | void increase_native(); 44 | 45 | private: 46 | Ui::HotTestWidget *ui; 47 | 48 | QHotkey *hotkey_1; 49 | QHotkey *hotkey_2; 50 | QHotkey *hotkey_3; 51 | QHotkey *hotkey_4; 52 | QHotkey *hotkey_5; 53 | 54 | QThread *thread4; 55 | QThread *thread5; 56 | 57 | QList testHotkeys; 58 | 59 | QHotkey *nativeHotkey; 60 | }; 61 | 62 | #endif // HOTTESTWIDGET_H 63 | -------------------------------------------------------------------------------- /HotkeyTest/hottestwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | HotTestWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 520 10 | 426 11 | 12 | 13 | 14 | HotTestWidget 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | Playground 25 | 26 | 27 | 28 | 29 | 30 | Hotkey &1: 31 | 32 | 33 | 34 | 35 | 36 | 37 | Hotkey &2: 38 | 39 | 40 | 41 | 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 85 53 | 255 54 | 0 55 | 56 | 57 | 58 | 59 | 60 | 61 | 255 62 | 255 63 | 255 64 | 65 | 66 | 67 | 68 | 69 | 70 | 0 71 | 0 72 | 0 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 85 82 | 255 83 | 0 84 | 85 | 86 | 87 | 88 | 89 | 90 | 255 91 | 255 92 | 255 93 | 94 | 95 | 96 | 97 | 98 | 99 | 0 100 | 0 101 | 0 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 120 111 | 120 112 | 120 113 | 114 | 115 | 116 | 117 | 118 | 119 | 0 120 | 0 121 | 0 122 | 123 | 124 | 125 | 126 | 127 | 128 | 0 129 | 0 130 | 0 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | true 139 | 140 | 141 | QFrame::Panel 142 | 143 | 144 | QFrame::Sunken 145 | 146 | 147 | QLCDNumber::Flat 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | Count: 158 | 159 | 160 | 161 | 162 | 163 | 164 | Qt::LeftArrow 165 | 166 | 167 | 168 | 169 | 170 | 171 | false 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 85 180 | 255 181 | 0 182 | 183 | 184 | 185 | 186 | 187 | 188 | 255 189 | 255 190 | 255 191 | 192 | 193 | 194 | 195 | 196 | 197 | 0 198 | 0 199 | 0 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 85 209 | 255 210 | 0 211 | 212 | 213 | 214 | 215 | 216 | 217 | 255 218 | 255 219 | 255 220 | 221 | 222 | 223 | 224 | 225 | 226 | 0 227 | 0 228 | 0 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 120 238 | 120 239 | 120 240 | 241 | 242 | 243 | 244 | 245 | 246 | 0 247 | 0 248 | 0 249 | 250 | 251 | 252 | 253 | 254 | 255 | 0 256 | 0 257 | 0 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | true 266 | 267 | 268 | QFrame::Panel 269 | 270 | 271 | QFrame::Sunken 272 | 273 | 274 | QLCDNumber::Flat 275 | 276 | 277 | 278 | 279 | 280 | 281 | Hotkey &3: 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | Count: 292 | 293 | 294 | 295 | 296 | 297 | 298 | Qt::LeftArrow 299 | 300 | 301 | 302 | 303 | 304 | 305 | Hotkey &4: 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | false 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 85 324 | 255 325 | 0 326 | 327 | 328 | 329 | 330 | 331 | 332 | 255 333 | 255 334 | 255 335 | 336 | 337 | 338 | 339 | 340 | 341 | 0 342 | 0 343 | 0 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 85 353 | 255 354 | 0 355 | 356 | 357 | 358 | 359 | 360 | 361 | 255 362 | 255 363 | 255 364 | 365 | 366 | 367 | 368 | 369 | 370 | 0 371 | 0 372 | 0 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 120 382 | 120 383 | 120 384 | 385 | 386 | 387 | 388 | 389 | 390 | 0 391 | 0 392 | 0 393 | 394 | 395 | 396 | 397 | 398 | 399 | 0 400 | 0 401 | 0 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | true 410 | 411 | 412 | QFrame::Panel 413 | 414 | 415 | QFrame::Sunken 416 | 417 | 418 | QLCDNumber::Flat 419 | 420 | 421 | 422 | 423 | 424 | 425 | Count: 426 | 427 | 428 | 429 | 430 | 431 | 432 | Qt::LeftArrow 433 | 434 | 435 | 436 | 437 | 438 | 439 | Count: 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | Qt::LeftArrow 450 | 451 | 452 | 453 | 454 | 455 | 456 | false 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 85 465 | 255 466 | 0 467 | 468 | 469 | 470 | 471 | 472 | 473 | 255 474 | 255 475 | 255 476 | 477 | 478 | 479 | 480 | 481 | 482 | 0 483 | 0 484 | 0 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 85 494 | 255 495 | 0 496 | 497 | 498 | 499 | 500 | 501 | 502 | 255 503 | 255 504 | 255 505 | 506 | 507 | 508 | 509 | 510 | 511 | 0 512 | 0 513 | 0 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 120 523 | 120 524 | 120 525 | 526 | 527 | 528 | 529 | 530 | 531 | 0 532 | 0 533 | 0 534 | 535 | 536 | 537 | 538 | 539 | 540 | 0 541 | 0 542 | 0 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | true 551 | 552 | 553 | QFrame::Panel 554 | 555 | 556 | QFrame::Sunken 557 | 558 | 559 | QLCDNumber::Flat 560 | 561 | 562 | 563 | 564 | 565 | 566 | Hotkey &5: 567 | 568 | 569 | 570 | 571 | 572 | 573 | Qt::Vertical 574 | 575 | 576 | 577 | 20 578 | 41 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | Qt::Vertical 587 | 588 | 589 | 590 | 20 591 | 41 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | Count: 600 | 601 | 602 | 603 | 604 | 605 | 606 | false 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 85 615 | 255 616 | 0 617 | 618 | 619 | 620 | 621 | 622 | 623 | 255 624 | 255 625 | 255 626 | 627 | 628 | 629 | 630 | 631 | 632 | 0 633 | 0 634 | 0 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 85 644 | 255 645 | 0 646 | 647 | 648 | 649 | 650 | 651 | 652 | 255 653 | 255 654 | 255 655 | 656 | 657 | 658 | 659 | 660 | 661 | 0 662 | 0 663 | 0 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 120 673 | 120 674 | 120 675 | 676 | 677 | 678 | 679 | 680 | 681 | 0 682 | 0 683 | 0 684 | 685 | 686 | 687 | 688 | 689 | 690 | 0 691 | 0 692 | 0 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | true 701 | 702 | 703 | QFrame::Panel 704 | 705 | 706 | QFrame::Sunken 707 | 708 | 709 | QLCDNumber::Flat 710 | 711 | 712 | 713 | 714 | 715 | 716 | Qt::LeftArrow 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | Qt::Vertical 727 | 728 | 729 | 730 | 20 731 | 41 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | Qt::Vertical 740 | 741 | 742 | 743 | 20 744 | 41 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | Qt::Vertical 753 | 754 | 755 | 756 | 20 757 | 41 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | Testings 767 | 768 | 769 | 770 | 771 | 772 | <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. 773 | 774 | 775 | true 776 | 777 | 778 | 779 | 780 | 781 | 782 | Test Active: 783 | 784 | 785 | true 786 | 787 | 788 | false 789 | 790 | 791 | 792 | 793 | 794 | Hotkey: F 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | Hotkey: ctrl+alt+meta+F12 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | Hotkey: ctrl+shift+cancel 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | Hotkey: meta+del 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | Hotkey: numlock 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | Hotkey: ctrl+5 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | Hotkey: shift+Tab 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | Hotkey: shift+, 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | Hotkey: shift+; 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | Hotkey: shift+alt+K 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | Hotkey: shift+alt+K 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | Qt::Vertical 908 | 909 | 910 | 911 | 20 912 | 0 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | Threading 922 | 923 | 924 | 925 | 12 926 | 927 | 928 | 929 | 930 | <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> 931 | 932 | 933 | Qt::RichText 934 | 935 | 936 | true 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 75 945 | true 946 | 947 | 948 | 949 | Enable Threaded Hotkeys 950 | 951 | 952 | 953 | 954 | 955 | 956 | Qt::Vertical 957 | 958 | 959 | 960 | 20 961 | 40 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | Native Shortcut 971 | 972 | 973 | 974 | 975 | 976 | <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> 977 | 978 | 979 | true 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | Key: 989 | 990 | 991 | 992 | 993 | 994 | 995 | 0x 996 | 997 | 998 | 999999999 999 | 1000 | 1001 | 16 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | Modifiers: 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 0x 1016 | 1017 | 1018 | 999999999 1019 | 1020 | 1021 | 16 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | Count: 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | false 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 85 1044 | 255 1045 | 0 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 255 1053 | 255 1054 | 255 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 0 1062 | 0 1063 | 0 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 85 1073 | 255 1074 | 0 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 255 1082 | 255 1083 | 255 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 0 1091 | 0 1092 | 0 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 120 1102 | 120 1103 | 120 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 0 1111 | 0 1112 | 0 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 0 1120 | 0 1121 | 0 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | true 1130 | 1131 | 1132 | QFrame::Panel 1133 | 1134 | 1135 | QFrame::Sunken 1136 | 1137 | 1138 | QLCDNumber::Flat 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | Registered: 1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | hotkeyCheckbox_1 1163 | hotkeySequenceEdit_1 1164 | resetButton_1 1165 | hotkeyCheckbox_2 1166 | hotkeySequenceEdit_2 1167 | resetButton_2 1168 | hotkeyCheckbox_3 1169 | hotkeySequenceEdit_3 1170 | resetButton_3 1171 | hotkeyCheckbox_4 1172 | hotkeySequenceEdit_4 1173 | resetButton_4 1174 | hotkeyCheckbox_5 1175 | hotkeySequenceEdit_5 1176 | resetButton_5 1177 | 1178 | 1179 | 1180 | 1181 | hotkeyCheckbox_1 1182 | toggled(bool) 1183 | hotkeyCount_1 1184 | setEnabled(bool) 1185 | 1186 | 1187 | 31 1188 | 45 1189 | 1190 | 1191 | 417 1192 | 43 1193 | 1194 | 1195 | 1196 | 1197 | hotkeyCheckbox_2 1198 | toggled(bool) 1199 | hotkeyCount_2 1200 | setEnabled(bool) 1201 | 1202 | 1203 | 31 1204 | 74 1205 | 1206 | 1207 | 417 1208 | 72 1209 | 1210 | 1211 | 1212 | 1213 | hotkeyCheckbox_3 1214 | toggled(bool) 1215 | hotkeyCount_3 1216 | setEnabled(bool) 1217 | 1218 | 1219 | 31 1220 | 103 1221 | 1222 | 1223 | 417 1224 | 101 1225 | 1226 | 1227 | 1228 | 1229 | hotkeyCheckbox_4 1230 | toggled(bool) 1231 | hotkeyCount_4 1232 | setEnabled(bool) 1233 | 1234 | 1235 | 31 1236 | 132 1237 | 1238 | 1239 | 417 1240 | 130 1241 | 1242 | 1243 | 1244 | 1245 | hotkeyCheckbox_5 1246 | toggled(bool) 1247 | hotkeyCount_5 1248 | setEnabled(bool) 1249 | 1250 | 1251 | 31 1252 | 161 1253 | 1254 | 1255 | 417 1256 | 159 1257 | 1258 | 1259 | 1260 | 1261 | hotkeyCheckbox_1 1262 | toggled(bool) 1263 | hotkeySequenceEdit_1 1264 | setDisabled(bool) 1265 | 1266 | 1267 | 31 1268 | 45 1269 | 1270 | 1271 | 109 1272 | 43 1273 | 1274 | 1275 | 1276 | 1277 | hotkeyCheckbox_2 1278 | toggled(bool) 1279 | hotkeySequenceEdit_2 1280 | setDisabled(bool) 1281 | 1282 | 1283 | 31 1284 | 74 1285 | 1286 | 1287 | 109 1288 | 72 1289 | 1290 | 1291 | 1292 | 1293 | hotkeyCheckbox_3 1294 | toggled(bool) 1295 | hotkeySequenceEdit_3 1296 | setDisabled(bool) 1297 | 1298 | 1299 | 31 1300 | 103 1301 | 1302 | 1303 | 109 1304 | 101 1305 | 1306 | 1307 | 1308 | 1309 | hotkeyCheckbox_4 1310 | toggled(bool) 1311 | hotkeySequenceEdit_4 1312 | setDisabled(bool) 1313 | 1314 | 1315 | 31 1316 | 132 1317 | 1318 | 1319 | 109 1320 | 130 1321 | 1322 | 1323 | 1324 | 1325 | hotkeyCheckbox_5 1326 | toggled(bool) 1327 | hotkeySequenceEdit_5 1328 | setDisabled(bool) 1329 | 1330 | 1331 | 31 1332 | 161 1333 | 1334 | 1335 | 109 1336 | 159 1337 | 1338 | 1339 | 1340 | 1341 | registeredCheckBox 1342 | toggled(bool) 1343 | nativeCount 1344 | setEnabled(bool) 1345 | 1346 | 1347 | 93 1348 | 326 1349 | 1350 | 1351 | 150 1352 | 349 1353 | 1354 | 1355 | 1356 | 1357 | registeredCheckBox 1358 | toggled(bool) 1359 | nativeModifiersSpinBox 1360 | setDisabled(bool) 1361 | 1362 | 1363 | 108 1364 | 327 1365 | 1366 | 1367 | 111 1368 | 305 1369 | 1370 | 1371 | 1372 | 1373 | registeredCheckBox 1374 | toggled(bool) 1375 | nativeKeySpinBox 1376 | setDisabled(bool) 1377 | 1378 | 1379 | 183 1380 | 327 1381 | 1382 | 1383 | 194 1384 | 279 1385 | 1386 | 1387 | 1388 | 1389 | 1390 | -------------------------------------------------------------------------------- /HotkeyTest/main.cpp: -------------------------------------------------------------------------------- 1 | #include "hottestwidget.h" 2 | #include 3 | 4 | //#define START_BACKGROUND 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication a(argc, argv); 9 | HotTestWidget w; 10 | 11 | #ifdef START_BACKGROUND 12 | auto startKey = new QHotkey(QKeySequence(Qt::MetaModifier | Qt::ControlModifier | Qt::Key_S), true, &w); 13 | QObject::connect(startKey, &QHotkey::activated, 14 | &w, &QWidget::show); 15 | #else 16 | w.show(); 17 | #endif 18 | 19 | return a.exec(); 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Felix Barz 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of QHotkey nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /QHotkey/QHotkey: -------------------------------------------------------------------------------- 1 | #include "qhotkey.h" 2 | -------------------------------------------------------------------------------- /QHotkey/qhotkey.cpp: -------------------------------------------------------------------------------- 1 | #include "qhotkey.h" 2 | #include "qhotkey_p.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Q_LOGGING_CATEGORY(logQHotkey, "QHotkey") 10 | 11 | void QHotkey::addGlobalMapping(const QKeySequence &shortcut, QHotkey::NativeShortcut nativeShortcut) 12 | { 13 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 14 | const int key = shortcut[0].toCombined(); 15 | #else 16 | const int key = shortcut[0]; 17 | #endif 18 | 19 | QMetaObject::invokeMethod(QHotkeyPrivate::instance(), "addMappingInvoked", Qt::QueuedConnection, 20 | Q_ARG(Qt::Key, Qt::Key(key & ~Qt::KeyboardModifierMask)), 21 | Q_ARG(Qt::KeyboardModifiers, Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)), 22 | Q_ARG(QHotkey::NativeShortcut, nativeShortcut)); 23 | } 24 | 25 | bool QHotkey::isPlatformSupported() 26 | { 27 | return QHotkeyPrivate::isPlatformSupported(); 28 | } 29 | 30 | QHotkey::QHotkey(QObject *parent) : 31 | QObject(parent), 32 | _keyCode(Qt::Key_unknown), 33 | _modifiers(Qt::NoModifier), 34 | _registered(false) 35 | {} 36 | 37 | QHotkey::QHotkey(const QKeySequence &shortcut, bool autoRegister, QObject *parent) : 38 | QHotkey(parent) 39 | { 40 | setShortcut(shortcut, autoRegister); 41 | } 42 | 43 | QHotkey::QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister, QObject *parent) : 44 | QHotkey(parent) 45 | { 46 | setShortcut(keyCode, modifiers, autoRegister); 47 | } 48 | 49 | QHotkey::QHotkey(QHotkey::NativeShortcut shortcut, bool autoRegister, QObject *parent) : 50 | QHotkey(parent) 51 | { 52 | setNativeShortcut(shortcut, autoRegister); 53 | } 54 | 55 | QHotkey::~QHotkey() 56 | { 57 | if(_registered) 58 | QHotkeyPrivate::instance()->removeShortcut(this); 59 | } 60 | 61 | QKeySequence QHotkey::shortcut() const 62 | { 63 | if(_keyCode == Qt::Key_unknown) 64 | return QKeySequence(); 65 | 66 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 67 | return QKeySequence((_keyCode | _modifiers).toCombined()); 68 | #else 69 | return QKeySequence(static_cast(_keyCode | _modifiers)); 70 | #endif 71 | } 72 | 73 | Qt::Key QHotkey::keyCode() const 74 | { 75 | return _keyCode; 76 | } 77 | 78 | Qt::KeyboardModifiers QHotkey::modifiers() const 79 | { 80 | return _modifiers; 81 | } 82 | 83 | QHotkey::NativeShortcut QHotkey::currentNativeShortcut() const 84 | { 85 | return _nativeShortcut; 86 | } 87 | 88 | bool QHotkey::isRegistered() const 89 | { 90 | return _registered; 91 | } 92 | 93 | bool QHotkey::setShortcut(const QKeySequence &shortcut, bool autoRegister) 94 | { 95 | if(shortcut.isEmpty()) 96 | return resetShortcut(); 97 | if(shortcut.count() > 1) { 98 | qCWarning(logQHotkey, "Keysequences with multiple shortcuts are not allowed! " 99 | "Only the first shortcut will be used!"); 100 | } 101 | 102 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 103 | const int key = shortcut[0].toCombined(); 104 | #else 105 | const int key = shortcut[0]; 106 | #endif 107 | 108 | return setShortcut(Qt::Key(key & ~Qt::KeyboardModifierMask), 109 | Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask), 110 | autoRegister); 111 | } 112 | 113 | bool QHotkey::setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister) 114 | { 115 | if(_registered) { 116 | if(autoRegister) { 117 | if(!QHotkeyPrivate::instance()->removeShortcut(this)) 118 | return false; 119 | } else 120 | return false; 121 | } 122 | 123 | if(keyCode == Qt::Key_unknown) { 124 | _keyCode = Qt::Key_unknown; 125 | _modifiers = Qt::NoModifier; 126 | _nativeShortcut = NativeShortcut(); 127 | return true; 128 | } 129 | 130 | _keyCode = keyCode; 131 | _modifiers = modifiers; 132 | _nativeShortcut = QHotkeyPrivate::instance()->nativeShortcut(keyCode, modifiers); 133 | if(_nativeShortcut.isValid()) { 134 | if(autoRegister) 135 | return QHotkeyPrivate::instance()->addShortcut(this); 136 | return true; 137 | } 138 | 139 | qCWarning(logQHotkey) << "Unable to map shortcut to native keys. Key:" << keyCode << "Modifiers:" << modifiers; 140 | _keyCode = Qt::Key_unknown; 141 | _modifiers = Qt::NoModifier; 142 | _nativeShortcut = NativeShortcut(); 143 | return false; 144 | } 145 | 146 | bool QHotkey::resetShortcut() 147 | { 148 | if(_registered && 149 | !QHotkeyPrivate::instance()->removeShortcut(this)) { 150 | return false; 151 | } 152 | 153 | _keyCode = Qt::Key_unknown; 154 | _modifiers = Qt::NoModifier; 155 | _nativeShortcut = NativeShortcut(); 156 | return true; 157 | } 158 | 159 | bool QHotkey::setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister) 160 | { 161 | if(_registered) { 162 | if(autoRegister) { 163 | if(!QHotkeyPrivate::instance()->removeShortcut(this)) 164 | return false; 165 | } else 166 | return false; 167 | } 168 | 169 | if(nativeShortcut.isValid()) { 170 | _keyCode = Qt::Key_unknown; 171 | _modifiers = Qt::NoModifier; 172 | _nativeShortcut = nativeShortcut; 173 | if(autoRegister) 174 | return QHotkeyPrivate::instance()->addShortcut(this); 175 | return true; 176 | } 177 | 178 | _keyCode = Qt::Key_unknown; 179 | _modifiers = Qt::NoModifier; 180 | _nativeShortcut = NativeShortcut(); 181 | return true; 182 | } 183 | 184 | bool QHotkey::setRegistered(bool registered) 185 | { 186 | if(_registered && !registered) 187 | return QHotkeyPrivate::instance()->removeShortcut(this); 188 | if(!_registered && registered) { 189 | if(!_nativeShortcut.isValid()) 190 | return false; 191 | return QHotkeyPrivate::instance()->addShortcut(this); 192 | } 193 | return true; 194 | } 195 | 196 | 197 | 198 | // ---------- QHotkeyPrivate implementation ---------- 199 | 200 | QHotkeyPrivate::QHotkeyPrivate() 201 | { 202 | Q_ASSERT_X(qApp, Q_FUNC_INFO, "QHotkey requires QCoreApplication to be instantiated"); 203 | qApp->eventDispatcher()->installNativeEventFilter(this); 204 | } 205 | 206 | QHotkeyPrivate::~QHotkeyPrivate() 207 | { 208 | if(!shortcuts.isEmpty()) 209 | qCWarning(logQHotkey) << "QHotkeyPrivate destroyed with registered shortcuts!"; 210 | if(qApp && qApp->eventDispatcher()) 211 | qApp->eventDispatcher()->removeNativeEventFilter(this); 212 | } 213 | 214 | QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers) 215 | { 216 | Qt::ConnectionType conType = (QThread::currentThread() == thread() ? 217 | Qt::DirectConnection : 218 | Qt::BlockingQueuedConnection); 219 | QHotkey::NativeShortcut res; 220 | if(!QMetaObject::invokeMethod(this, "nativeShortcutInvoked", conType, 221 | Q_RETURN_ARG(QHotkey::NativeShortcut, res), 222 | Q_ARG(Qt::Key, keycode), 223 | Q_ARG(Qt::KeyboardModifiers, modifiers))) { 224 | return QHotkey::NativeShortcut(); 225 | } 226 | return res; 227 | } 228 | 229 | bool QHotkeyPrivate::addShortcut(QHotkey *hotkey) 230 | { 231 | if(hotkey->_registered) 232 | return false; 233 | 234 | Qt::ConnectionType conType = (QThread::currentThread() == thread() ? 235 | Qt::DirectConnection : 236 | Qt::BlockingQueuedConnection); 237 | bool res = false; 238 | if(!QMetaObject::invokeMethod(this, "addShortcutInvoked", conType, 239 | Q_RETURN_ARG(bool, res), 240 | Q_ARG(QHotkey*, hotkey))) { 241 | return false; 242 | } 243 | 244 | if(res) 245 | emit hotkey->registeredChanged(true); 246 | return res; 247 | } 248 | 249 | bool QHotkeyPrivate::removeShortcut(QHotkey *hotkey) 250 | { 251 | if(!hotkey->_registered) 252 | return false; 253 | 254 | Qt::ConnectionType conType = (QThread::currentThread() == thread() ? 255 | Qt::DirectConnection : 256 | Qt::BlockingQueuedConnection); 257 | bool res = false; 258 | if(!QMetaObject::invokeMethod(this, "removeShortcutInvoked", conType, 259 | Q_RETURN_ARG(bool, res), 260 | Q_ARG(QHotkey*, hotkey))) { 261 | return false; 262 | } 263 | 264 | if(res) 265 | emit hotkey->registeredChanged(false); 266 | return res; 267 | } 268 | 269 | void QHotkeyPrivate::activateShortcut(QHotkey::NativeShortcut shortcut) 270 | { 271 | QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::activated); 272 | for(QHotkey *hkey : shortcuts.values(shortcut)) 273 | signal.invoke(hkey, Qt::QueuedConnection); 274 | } 275 | 276 | void QHotkeyPrivate::releaseShortcut(QHotkey::NativeShortcut shortcut) 277 | { 278 | QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::released); 279 | for(QHotkey *hkey : shortcuts.values(shortcut)) 280 | signal.invoke(hkey, Qt::QueuedConnection); 281 | } 282 | 283 | void QHotkeyPrivate::addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut) 284 | { 285 | mapping.insert({keycode, modifiers}, nativeShortcut); 286 | } 287 | 288 | bool QHotkeyPrivate::addShortcutInvoked(QHotkey *hotkey) 289 | { 290 | QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; 291 | 292 | if(!shortcuts.contains(shortcut)) { 293 | if(!registerShortcut(shortcut)) { 294 | qCWarning(logQHotkey) << QHotkey::tr("Failed to register %1. Error: %2").arg(hotkey->shortcut().toString(), error); 295 | return false; 296 | } 297 | } 298 | 299 | shortcuts.insert(shortcut, hotkey); 300 | hotkey->_registered = true; 301 | return true; 302 | } 303 | 304 | bool QHotkeyPrivate::removeShortcutInvoked(QHotkey *hotkey) 305 | { 306 | QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; 307 | 308 | if(shortcuts.remove(shortcut, hotkey) == 0) 309 | return false; 310 | hotkey->_registered = false; 311 | emit hotkey->registeredChanged(true); 312 | if(shortcuts.count(shortcut) == 0) { 313 | if (!unregisterShortcut(shortcut)) { 314 | qCWarning(logQHotkey) << QHotkey::tr("Failed to unregister %1. Error: %2").arg(hotkey->shortcut().toString(), error); 315 | return false; 316 | } 317 | return true; 318 | } 319 | return true; 320 | } 321 | 322 | QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers) 323 | { 324 | if(mapping.contains({keycode, modifiers})) 325 | return mapping.value({keycode, modifiers}); 326 | 327 | bool ok1 = false; 328 | auto k = nativeKeycode(keycode, ok1); 329 | bool ok2 = false; 330 | auto m = nativeModifiers(modifiers, ok2); 331 | if(ok1 && ok2) 332 | return {k, m}; 333 | return {}; 334 | } 335 | 336 | 337 | 338 | QHotkey::NativeShortcut::NativeShortcut() : 339 | key(), 340 | modifier(), 341 | valid(false) 342 | {} 343 | 344 | QHotkey::NativeShortcut::NativeShortcut(quint32 key, quint32 modifier) : 345 | key(key), 346 | modifier(modifier), 347 | valid(true) 348 | {} 349 | 350 | bool QHotkey::NativeShortcut::isValid() const 351 | { 352 | return valid; 353 | } 354 | 355 | bool QHotkey::NativeShortcut::operator ==(QHotkey::NativeShortcut other) const 356 | { 357 | return (key == other.key) && 358 | (modifier == other.modifier) && 359 | valid == other.valid; 360 | } 361 | 362 | bool QHotkey::NativeShortcut::operator !=(QHotkey::NativeShortcut other) const 363 | { 364 | return (key != other.key) || 365 | (modifier != other.modifier) || 366 | valid != other.valid; 367 | } 368 | 369 | QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key) 370 | { 371 | return qHash(key.key) ^ qHash(key.modifier); 372 | } 373 | 374 | QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed) 375 | { 376 | return qHash(key.key, seed) ^ qHash(key.modifier, seed); 377 | } 378 | -------------------------------------------------------------------------------- /QHotkey/qhotkey.h: -------------------------------------------------------------------------------- 1 | #ifndef QHOTKEY_H 2 | #define QHOTKEY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef QHOTKEY_SHARED 10 | # ifdef QHOTKEY_LIBRARY 11 | # define QHOTKEY_EXPORT Q_DECL_EXPORT 12 | # else 13 | # define QHOTKEY_EXPORT Q_DECL_IMPORT 14 | # endif 15 | #else 16 | # define QHOTKEY_EXPORT 17 | #endif 18 | 19 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 20 | #define QHOTKEY_HASH_SEED size_t 21 | #else 22 | #define QHOTKEY_HASH_SEED uint 23 | #endif 24 | 25 | //! A class to define global, systemwide Hotkeys 26 | class QHOTKEY_EXPORT QHotkey : public QObject 27 | { 28 | Q_OBJECT 29 | //! @private 30 | friend class QHotkeyPrivate; 31 | 32 | //! Specifies whether this hotkey is currently registered or not 33 | Q_PROPERTY(bool registered READ isRegistered WRITE setRegistered NOTIFY registeredChanged) 34 | //! Holds the shortcut this hotkey will be triggered on 35 | Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut RESET resetShortcut) 36 | 37 | public: 38 | //! Defines shortcut with native keycodes 39 | class QHOTKEY_EXPORT NativeShortcut { 40 | public: 41 | //! The native keycode 42 | quint32 key; 43 | //! The native modifiers 44 | quint32 modifier; 45 | 46 | //! Creates an invalid native shortcut 47 | NativeShortcut(); 48 | //! Creates a valid native shortcut, with the given key and modifiers 49 | NativeShortcut(quint32 key, quint32 modifier = 0); 50 | 51 | //! Checks, whether this shortcut is valid or not 52 | bool isValid() const; 53 | 54 | //! Equality operator 55 | bool operator ==(NativeShortcut other) const; 56 | //! Inequality operator 57 | bool operator !=(NativeShortcut other) const; 58 | 59 | private: 60 | bool valid; 61 | }; 62 | 63 | //! Adds a global mapping of a key sequence to a replacement native shortcut 64 | static void addGlobalMapping(const QKeySequence &shortcut, NativeShortcut nativeShortcut); 65 | 66 | //! Checks if global shortcuts are supported by the current platform 67 | static bool isPlatformSupported(); 68 | 69 | //! Default Constructor 70 | explicit QHotkey(QObject *parent = nullptr); 71 | //! Constructs a hotkey with a shortcut and optionally registers it 72 | explicit QHotkey(const QKeySequence &shortcut, bool autoRegister = false, QObject *parent = nullptr); 73 | //! Constructs a hotkey with a key and modifiers and optionally registers it 74 | explicit QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false, QObject *parent = nullptr); 75 | //! Constructs a hotkey from a native shortcut and optionally registers it 76 | explicit QHotkey(NativeShortcut shortcut, bool autoRegister = false, QObject *parent = nullptr); 77 | ~QHotkey() override; 78 | 79 | //! @readAcFn{QHotkey::registered} 80 | bool isRegistered() const; 81 | //! @readAcFn{QHotkey::shortcut} 82 | QKeySequence shortcut() const; 83 | //! @readAcFn{QHotkey::shortcut} - the key only 84 | Qt::Key keyCode() const; 85 | //! @readAcFn{QHotkey::shortcut} - the modifiers only 86 | Qt::KeyboardModifiers modifiers() const; 87 | 88 | //! Get the current native shortcut 89 | NativeShortcut currentNativeShortcut() const; 90 | 91 | public slots: 92 | //! @writeAcFn{QHotkey::registered} 93 | bool setRegistered(bool registered); 94 | 95 | //! @writeAcFn{QHotkey::shortcut} 96 | bool setShortcut(const QKeySequence &shortcut, bool autoRegister = false); 97 | //! @writeAcFn{QHotkey::shortcut} 98 | bool setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false); 99 | //! @resetAcFn{QHotkey::shortcut} 100 | bool resetShortcut(); 101 | 102 | //! Set this hotkey to a native shortcut 103 | bool setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister = false); 104 | 105 | signals: 106 | //! Will be emitted if the shortcut is pressed 107 | void activated(QPrivateSignal); 108 | 109 | //! Will be emitted if the shortcut press is released 110 | void released(QPrivateSignal); 111 | 112 | //! @notifyAcFn{QHotkey::registered} 113 | void registeredChanged(bool registered); 114 | 115 | private: 116 | Qt::Key _keyCode; 117 | Qt::KeyboardModifiers _modifiers; 118 | 119 | NativeShortcut _nativeShortcut; 120 | bool _registered; 121 | }; 122 | 123 | QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key); 124 | QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed); 125 | 126 | QHOTKEY_EXPORT Q_DECLARE_LOGGING_CATEGORY(logQHotkey) 127 | 128 | Q_DECLARE_METATYPE(QHotkey::NativeShortcut) 129 | 130 | #endif // QHOTKEY_H 131 | -------------------------------------------------------------------------------- /QHotkey/qhotkey_mac.cpp: -------------------------------------------------------------------------------- 1 | #include "qhotkey.h" 2 | #include "qhotkey_p.h" 3 | #include 4 | #include 5 | 6 | class QHotkeyPrivateMac : public QHotkeyPrivate 7 | { 8 | public: 9 | // QAbstractNativeEventFilter interface 10 | bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; 11 | 12 | static OSStatus hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); 13 | static OSStatus hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); 14 | 15 | protected: 16 | // QHotkeyPrivate interface 17 | quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; 18 | quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; 19 | bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; 20 | bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; 21 | 22 | private: 23 | static bool isHotkeyHandlerRegistered; 24 | static QHash hotkeyRefs; 25 | }; 26 | NATIVE_INSTANCE(QHotkeyPrivateMac) 27 | 28 | bool QHotkeyPrivate::isPlatformSupported() 29 | { 30 | return true; 31 | } 32 | 33 | bool QHotkeyPrivateMac::isHotkeyHandlerRegistered = false; 34 | QHash QHotkeyPrivateMac::hotkeyRefs; 35 | 36 | bool QHotkeyPrivateMac::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) 37 | { 38 | Q_UNUSED(eventType) 39 | Q_UNUSED(message) 40 | Q_UNUSED(result) 41 | return false; 42 | } 43 | 44 | quint32 QHotkeyPrivateMac::nativeKeycode(Qt::Key keycode, bool &ok) 45 | { 46 | // Constants found in NSEvent.h from AppKit.framework 47 | ok = true; 48 | switch (keycode) { 49 | case Qt::Key_Return: 50 | return kVK_Return; 51 | case Qt::Key_Enter: 52 | return kVK_ANSI_KeypadEnter; 53 | case Qt::Key_Tab: 54 | return kVK_Tab; 55 | case Qt::Key_Space: 56 | return kVK_Space; 57 | case Qt::Key_Backspace: 58 | return kVK_Delete; 59 | case Qt::Key_Escape: 60 | return kVK_Escape; 61 | case Qt::Key_CapsLock: 62 | return kVK_CapsLock; 63 | case Qt::Key_Option: 64 | return kVK_Option; 65 | case Qt::Key_F17: 66 | return kVK_F17; 67 | case Qt::Key_VolumeUp: 68 | return kVK_VolumeUp; 69 | case Qt::Key_VolumeDown: 70 | return kVK_VolumeDown; 71 | case Qt::Key_F18: 72 | return kVK_F18; 73 | case Qt::Key_F19: 74 | return kVK_F19; 75 | case Qt::Key_F20: 76 | return kVK_F20; 77 | case Qt::Key_F5: 78 | return kVK_F5; 79 | case Qt::Key_F6: 80 | return kVK_F6; 81 | case Qt::Key_F7: 82 | return kVK_F7; 83 | case Qt::Key_F3: 84 | return kVK_F3; 85 | case Qt::Key_F8: 86 | return kVK_F8; 87 | case Qt::Key_F9: 88 | return kVK_F9; 89 | case Qt::Key_F11: 90 | return kVK_F11; 91 | case Qt::Key_F13: 92 | return kVK_F13; 93 | case Qt::Key_F16: 94 | return kVK_F16; 95 | case Qt::Key_F14: 96 | return kVK_F14; 97 | case Qt::Key_F10: 98 | return kVK_F10; 99 | case Qt::Key_F12: 100 | return kVK_F12; 101 | case Qt::Key_F15: 102 | return kVK_F15; 103 | case Qt::Key_Help: 104 | return kVK_Help; 105 | case Qt::Key_Home: 106 | return kVK_Home; 107 | case Qt::Key_PageUp: 108 | return kVK_PageUp; 109 | case Qt::Key_Delete: 110 | return kVK_ForwardDelete; 111 | case Qt::Key_F4: 112 | return kVK_F4; 113 | case Qt::Key_End: 114 | return kVK_End; 115 | case Qt::Key_F2: 116 | return kVK_F2; 117 | case Qt::Key_PageDown: 118 | return kVK_PageDown; 119 | case Qt::Key_F1: 120 | return kVK_F1; 121 | case Qt::Key_Left: 122 | return kVK_LeftArrow; 123 | case Qt::Key_Right: 124 | return kVK_RightArrow; 125 | case Qt::Key_Down: 126 | return kVK_DownArrow; 127 | case Qt::Key_Up: 128 | return kVK_UpArrow; 129 | default: 130 | ok = false; 131 | break; 132 | } 133 | 134 | UTF16Char ch = keycode; 135 | 136 | CFDataRef currentLayoutData; 137 | TISInputSourceRef currentKeyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); 138 | 139 | if (currentKeyboard == NULL) 140 | return 0; 141 | 142 | currentLayoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); 143 | CFRelease(currentKeyboard); 144 | if (currentLayoutData == NULL) 145 | return 0; 146 | 147 | UCKeyboardLayout* header = (UCKeyboardLayout*)CFDataGetBytePtr(currentLayoutData); 148 | UCKeyboardTypeHeader* table = header->keyboardTypeList; 149 | 150 | uint8_t *data = (uint8_t*)header; 151 | for (quint32 i=0; i < header->keyboardTypeCount; i++) { 152 | UCKeyStateRecordsIndex* stateRec = 0; 153 | if (table[i].keyStateRecordsIndexOffset != 0) { 154 | stateRec = reinterpret_cast(data + table[i].keyStateRecordsIndexOffset); 155 | if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat) stateRec = 0; 156 | } 157 | 158 | UCKeyToCharTableIndex* charTable = reinterpret_cast(data + table[i].keyToCharTableIndexOffset); 159 | if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat) continue; 160 | 161 | for (quint32 j=0; j < charTable->keyToCharTableCount; j++) { 162 | UCKeyOutput* keyToChar = reinterpret_cast(data + charTable->keyToCharTableOffsets[j]); 163 | for (quint32 k=0; k < charTable->keyToCharTableSize; k++) { 164 | if (keyToChar[k] & kUCKeyOutputTestForIndexMask) { 165 | long idx = keyToChar[k] & kUCKeyOutputGetIndexMask; 166 | if (stateRec && idx < stateRec->keyStateRecordCount) { 167 | UCKeyStateRecord* rec = reinterpret_cast(data + stateRec->keyStateRecordOffsets[idx]); 168 | if (rec->stateZeroCharData == ch) { 169 | ok = true; 170 | return k; 171 | } 172 | } 173 | } 174 | else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) { 175 | if (keyToChar[k] == ch) { 176 | ok = true; 177 | return k; 178 | } 179 | } 180 | } 181 | } 182 | } 183 | return 0; 184 | } 185 | 186 | quint32 QHotkeyPrivateMac::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) 187 | { 188 | quint32 nMods = 0; 189 | if (modifiers & Qt::ShiftModifier) 190 | nMods |= shiftKey; 191 | if (modifiers & Qt::ControlModifier) 192 | nMods |= cmdKey; 193 | if (modifiers & Qt::AltModifier) 194 | nMods |= optionKey; 195 | if (modifiers & Qt::MetaModifier) 196 | nMods |= controlKey; 197 | if (modifiers & Qt::KeypadModifier) 198 | nMods |= kEventKeyModifierNumLockMask; 199 | ok = true; 200 | return nMods; 201 | } 202 | 203 | bool QHotkeyPrivateMac::registerShortcut(QHotkey::NativeShortcut shortcut) 204 | { 205 | if (!this->isHotkeyHandlerRegistered) 206 | { 207 | EventTypeSpec pressEventSpec; 208 | pressEventSpec.eventClass = kEventClassKeyboard; 209 | pressEventSpec.eventKind = kEventHotKeyPressed; 210 | InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyPressEventHandler, 1, &pressEventSpec, NULL, NULL); 211 | 212 | EventTypeSpec releaseEventSpec; 213 | releaseEventSpec.eventClass = kEventClassKeyboard; 214 | releaseEventSpec.eventKind = kEventHotKeyReleased; 215 | InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyReleaseEventHandler, 1, &releaseEventSpec, NULL, NULL); 216 | } 217 | 218 | EventHotKeyID hkeyID; 219 | hkeyID.signature = shortcut.key; 220 | hkeyID.id = shortcut.modifier; 221 | 222 | EventHotKeyRef eventRef = 0; 223 | OSStatus status = RegisterEventHotKey(shortcut.key, 224 | shortcut.modifier, 225 | hkeyID, 226 | GetApplicationEventTarget(), 227 | 0, 228 | &eventRef); 229 | if (status != noErr) { 230 | error = QString::number(status); 231 | return false; 232 | } else { 233 | this->hotkeyRefs.insert(shortcut, eventRef); 234 | return true; 235 | } 236 | } 237 | 238 | bool QHotkeyPrivateMac::unregisterShortcut(QHotkey::NativeShortcut shortcut) 239 | { 240 | EventHotKeyRef eventRef = QHotkeyPrivateMac::hotkeyRefs.value(shortcut); 241 | OSStatus status = UnregisterEventHotKey(eventRef); 242 | if (status != noErr) { 243 | error = QString::number(status); 244 | return false; 245 | } else { 246 | this->hotkeyRefs.remove(shortcut); 247 | return true; 248 | } 249 | } 250 | 251 | OSStatus QHotkeyPrivateMac::hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) 252 | { 253 | Q_UNUSED(nextHandler); 254 | Q_UNUSED(data); 255 | 256 | if (GetEventClass(event) == kEventClassKeyboard && 257 | GetEventKind(event) == kEventHotKeyPressed) { 258 | EventHotKeyID hkeyID; 259 | GetEventParameter(event, 260 | kEventParamDirectObject, 261 | typeEventHotKeyID, 262 | NULL, 263 | sizeof(EventHotKeyID), 264 | NULL, 265 | &hkeyID); 266 | hotkeyPrivate->activateShortcut({hkeyID.signature, hkeyID.id}); 267 | } 268 | 269 | return noErr; 270 | } 271 | 272 | OSStatus QHotkeyPrivateMac::hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) 273 | { 274 | Q_UNUSED(nextHandler); 275 | Q_UNUSED(data); 276 | 277 | if (GetEventClass(event) == kEventClassKeyboard && 278 | GetEventKind(event) == kEventHotKeyReleased) { 279 | EventHotKeyID hkeyID; 280 | GetEventParameter(event, 281 | kEventParamDirectObject, 282 | typeEventHotKeyID, 283 | NULL, 284 | sizeof(EventHotKeyID), 285 | NULL, 286 | &hkeyID); 287 | hotkeyPrivate->releaseShortcut({hkeyID.signature, hkeyID.id}); 288 | } 289 | 290 | return noErr; 291 | } 292 | -------------------------------------------------------------------------------- /QHotkey/qhotkey_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QHOTKEY_P_H 2 | #define QHOTKEY_P_H 3 | 4 | #include "qhotkey.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 11 | #define _NATIVE_EVENT_RESULT qintptr 12 | #else 13 | #define _NATIVE_EVENT_RESULT long 14 | #endif 15 | 16 | class QHOTKEY_EXPORT QHotkeyPrivate : public QObject, public QAbstractNativeEventFilter 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | QHotkeyPrivate();//singleton!!! 22 | ~QHotkeyPrivate(); 23 | 24 | static QHotkeyPrivate *instance(); 25 | static bool isPlatformSupported(); 26 | 27 | QHotkey::NativeShortcut nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers); 28 | 29 | bool addShortcut(QHotkey *hotkey); 30 | bool removeShortcut(QHotkey *hotkey); 31 | 32 | protected: 33 | void activateShortcut(QHotkey::NativeShortcut shortcut); 34 | void releaseShortcut(QHotkey::NativeShortcut shortcut); 35 | 36 | virtual quint32 nativeKeycode(Qt::Key keycode, bool &ok) = 0;//platform implement 37 | virtual quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) = 0;//platform implement 38 | 39 | virtual bool registerShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement 40 | virtual bool unregisterShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement 41 | 42 | QString error; 43 | 44 | private: 45 | QHash, QHotkey::NativeShortcut> mapping; 46 | QMultiHash shortcuts; 47 | 48 | Q_INVOKABLE void addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut); 49 | Q_INVOKABLE bool addShortcutInvoked(QHotkey *hotkey); 50 | Q_INVOKABLE bool removeShortcutInvoked(QHotkey *hotkey); 51 | Q_INVOKABLE QHotkey::NativeShortcut nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers); 52 | }; 53 | 54 | #define NATIVE_INSTANCE(ClassName) \ 55 | Q_GLOBAL_STATIC(ClassName, hotkeyPrivate) \ 56 | \ 57 | QHotkeyPrivate *QHotkeyPrivate::instance()\ 58 | {\ 59 | return hotkeyPrivate;\ 60 | } 61 | 62 | #endif // QHOTKEY_P_H 63 | -------------------------------------------------------------------------------- /QHotkey/qhotkey_win.cpp: -------------------------------------------------------------------------------- 1 | #include "qhotkey.h" 2 | #include "qhotkey_p.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define HKEY_ID(nativeShortcut) (((nativeShortcut.key ^ (nativeShortcut.modifier << 8)) & 0x0FFF) | 0x7000) 10 | 11 | #if !defined(MOD_NOREPEAT) 12 | #define MOD_NOREPEAT 0x4000 13 | #endif 14 | 15 | class QHotkeyPrivateWin : public QHotkeyPrivate 16 | { 17 | public: 18 | QHotkeyPrivateWin(); 19 | // QAbstractNativeEventFilter interface 20 | bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; 21 | 22 | protected: 23 | void pollForHotkeyRelease(); 24 | // QHotkeyPrivate interface 25 | quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; 26 | quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; 27 | bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; 28 | bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; 29 | 30 | private: 31 | static QString formatWinError(DWORD winError); 32 | QTimer pollTimer; 33 | QList polledShortcuts; 34 | }; 35 | NATIVE_INSTANCE(QHotkeyPrivateWin) 36 | 37 | QHotkeyPrivateWin::QHotkeyPrivateWin(){ 38 | pollTimer.setInterval(50); 39 | connect(&pollTimer, &QTimer::timeout, this, &QHotkeyPrivateWin::pollForHotkeyRelease); 40 | } 41 | 42 | bool QHotkeyPrivate::isPlatformSupported() 43 | { 44 | return true; 45 | } 46 | 47 | bool QHotkeyPrivateWin::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) 48 | { 49 | Q_UNUSED(eventType) 50 | Q_UNUSED(result) 51 | 52 | MSG* msg = static_cast(message); 53 | if(msg->message == WM_HOTKEY) { 54 | QHotkey::NativeShortcut shortcut = {HIWORD(msg->lParam), LOWORD(msg->lParam)}; 55 | this->activateShortcut(shortcut); 56 | if (this->polledShortcuts.empty()) 57 | this->pollTimer.start(); 58 | this->polledShortcuts.append(shortcut); 59 | } 60 | 61 | return false; 62 | } 63 | 64 | void QHotkeyPrivateWin::pollForHotkeyRelease() 65 | { 66 | auto it = std::remove_if(this->polledShortcuts.begin(), this->polledShortcuts.end(), [this](const QHotkey::NativeShortcut &shortcut) { 67 | bool pressed = (GetAsyncKeyState(shortcut.key) & (1 << 15)) != 0; 68 | if (!pressed) 69 | this->releaseShortcut(shortcut); 70 | return !pressed; 71 | }); 72 | this->polledShortcuts.erase(it, this->polledShortcuts.end()); 73 | if (this->polledShortcuts.empty()) 74 | this->pollTimer.stop(); 75 | } 76 | 77 | quint32 QHotkeyPrivateWin::nativeKeycode(Qt::Key keycode, bool &ok) 78 | { 79 | ok = true; 80 | if(keycode <= 0xFFFF) {//Try to obtain the key from it's "character" 81 | const SHORT vKey = VkKeyScanW(static_cast(keycode)); 82 | if(vKey > -1) 83 | return LOBYTE(vKey); 84 | } 85 | 86 | //find key from switch/case --> Only finds a very small subset of keys 87 | switch (keycode) 88 | { 89 | case Qt::Key_Escape: 90 | return VK_ESCAPE; 91 | case Qt::Key_Tab: 92 | case Qt::Key_Backtab: 93 | return VK_TAB; 94 | case Qt::Key_Backspace: 95 | return VK_BACK; 96 | case Qt::Key_Return: 97 | case Qt::Key_Enter: 98 | return VK_RETURN; 99 | case Qt::Key_Insert: 100 | return VK_INSERT; 101 | case Qt::Key_Delete: 102 | return VK_DELETE; 103 | case Qt::Key_Pause: 104 | return VK_PAUSE; 105 | case Qt::Key_Print: 106 | return VK_PRINT; 107 | case Qt::Key_Clear: 108 | return VK_CLEAR; 109 | case Qt::Key_Home: 110 | return VK_HOME; 111 | case Qt::Key_End: 112 | return VK_END; 113 | case Qt::Key_Left: 114 | return VK_LEFT; 115 | case Qt::Key_Up: 116 | return VK_UP; 117 | case Qt::Key_Right: 118 | return VK_RIGHT; 119 | case Qt::Key_Down: 120 | return VK_DOWN; 121 | case Qt::Key_PageUp: 122 | return VK_PRIOR; 123 | case Qt::Key_PageDown: 124 | return VK_NEXT; 125 | case Qt::Key_CapsLock: 126 | return VK_CAPITAL; 127 | case Qt::Key_NumLock: 128 | return VK_NUMLOCK; 129 | case Qt::Key_ScrollLock: 130 | return VK_SCROLL; 131 | 132 | case Qt::Key_F1: 133 | return VK_F1; 134 | case Qt::Key_F2: 135 | return VK_F2; 136 | case Qt::Key_F3: 137 | return VK_F3; 138 | case Qt::Key_F4: 139 | return VK_F4; 140 | case Qt::Key_F5: 141 | return VK_F5; 142 | case Qt::Key_F6: 143 | return VK_F6; 144 | case Qt::Key_F7: 145 | return VK_F7; 146 | case Qt::Key_F8: 147 | return VK_F8; 148 | case Qt::Key_F9: 149 | return VK_F9; 150 | case Qt::Key_F10: 151 | return VK_F10; 152 | case Qt::Key_F11: 153 | return VK_F11; 154 | case Qt::Key_F12: 155 | return VK_F12; 156 | case Qt::Key_F13: 157 | return VK_F13; 158 | case Qt::Key_F14: 159 | return VK_F14; 160 | case Qt::Key_F15: 161 | return VK_F15; 162 | case Qt::Key_F16: 163 | return VK_F16; 164 | case Qt::Key_F17: 165 | return VK_F17; 166 | case Qt::Key_F18: 167 | return VK_F18; 168 | case Qt::Key_F19: 169 | return VK_F19; 170 | case Qt::Key_F20: 171 | return VK_F20; 172 | case Qt::Key_F21: 173 | return VK_F21; 174 | case Qt::Key_F22: 175 | return VK_F22; 176 | case Qt::Key_F23: 177 | return VK_F23; 178 | case Qt::Key_F24: 179 | return VK_F24; 180 | 181 | case Qt::Key_Menu: 182 | return VK_APPS; 183 | case Qt::Key_Help: 184 | return VK_HELP; 185 | case Qt::Key_MediaNext: 186 | return VK_MEDIA_NEXT_TRACK; 187 | case Qt::Key_MediaPrevious: 188 | return VK_MEDIA_PREV_TRACK; 189 | case Qt::Key_MediaPlay: 190 | return VK_MEDIA_PLAY_PAUSE; 191 | case Qt::Key_MediaStop: 192 | return VK_MEDIA_STOP; 193 | case Qt::Key_VolumeDown: 194 | return VK_VOLUME_DOWN; 195 | case Qt::Key_VolumeUp: 196 | return VK_VOLUME_UP; 197 | case Qt::Key_VolumeMute: 198 | return VK_VOLUME_MUTE; 199 | case Qt::Key_Mode_switch: 200 | return VK_MODECHANGE; 201 | case Qt::Key_Select: 202 | return VK_SELECT; 203 | case Qt::Key_Printer: 204 | return VK_PRINT; 205 | case Qt::Key_Execute: 206 | return VK_EXECUTE; 207 | case Qt::Key_Sleep: 208 | return VK_SLEEP; 209 | case Qt::Key_Period: 210 | return VK_DECIMAL; 211 | case Qt::Key_Play: 212 | return VK_PLAY; 213 | case Qt::Key_Cancel: 214 | return VK_CANCEL; 215 | 216 | case Qt::Key_Forward: 217 | return VK_BROWSER_FORWARD; 218 | case Qt::Key_Refresh: 219 | return VK_BROWSER_REFRESH; 220 | case Qt::Key_Stop: 221 | return VK_BROWSER_STOP; 222 | case Qt::Key_Search: 223 | return VK_BROWSER_SEARCH; 224 | case Qt::Key_Favorites: 225 | return VK_BROWSER_FAVORITES; 226 | case Qt::Key_HomePage: 227 | return VK_BROWSER_HOME; 228 | 229 | case Qt::Key_LaunchMail: 230 | return VK_LAUNCH_MAIL; 231 | case Qt::Key_LaunchMedia: 232 | return VK_LAUNCH_MEDIA_SELECT; 233 | case Qt::Key_Launch0: 234 | return VK_LAUNCH_APP1; 235 | case Qt::Key_Launch1: 236 | return VK_LAUNCH_APP2; 237 | 238 | case Qt::Key_Massyo: 239 | return VK_OEM_FJ_MASSHOU; 240 | case Qt::Key_Touroku: 241 | return VK_OEM_FJ_TOUROKU; 242 | 243 | default: 244 | if(keycode <= 0xFFFF) 245 | return static_cast(keycode); 246 | else { 247 | ok = false; 248 | return 0; 249 | } 250 | } 251 | } 252 | 253 | quint32 QHotkeyPrivateWin::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) 254 | { 255 | quint32 nMods = 0; 256 | if (modifiers & Qt::ShiftModifier) 257 | nMods |= MOD_SHIFT; 258 | if (modifiers & Qt::ControlModifier) 259 | nMods |= MOD_CONTROL; 260 | if (modifiers & Qt::AltModifier) 261 | nMods |= MOD_ALT; 262 | if (modifiers & Qt::MetaModifier) 263 | nMods |= MOD_WIN; 264 | ok = true; 265 | return nMods; 266 | } 267 | 268 | bool QHotkeyPrivateWin::registerShortcut(QHotkey::NativeShortcut shortcut) 269 | { 270 | BOOL ok = RegisterHotKey(NULL, 271 | HKEY_ID(shortcut), 272 | shortcut.modifier + MOD_NOREPEAT, 273 | shortcut.key); 274 | if(ok) 275 | return true; 276 | else { 277 | error = QHotkeyPrivateWin::formatWinError(::GetLastError()); 278 | return false; 279 | } 280 | } 281 | 282 | bool QHotkeyPrivateWin::unregisterShortcut(QHotkey::NativeShortcut shortcut) 283 | { 284 | BOOL ok = UnregisterHotKey(NULL, HKEY_ID(shortcut)); 285 | if(ok) 286 | return true; 287 | else { 288 | error = QHotkeyPrivateWin::formatWinError(::GetLastError()); 289 | return false; 290 | } 291 | } 292 | 293 | QString QHotkeyPrivateWin::formatWinError(DWORD winError) 294 | { 295 | wchar_t *buffer = NULL; 296 | DWORD num = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 297 | NULL, 298 | winError, 299 | 0, 300 | (LPWSTR)&buffer, 301 | 0, 302 | NULL); 303 | if(buffer) { 304 | QString res = QString::fromWCharArray(buffer, num); 305 | LocalFree(buffer); 306 | return res; 307 | } else 308 | return QString(); 309 | } 310 | -------------------------------------------------------------------------------- /QHotkey/qhotkey_x11.cpp: -------------------------------------------------------------------------------- 1 | #include "qhotkey.h" 2 | #include "qhotkey_p.h" 3 | 4 | #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) 5 | #include 6 | #else 7 | #include 8 | #include 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | //compatibility to pre Qt 5.8 17 | #ifndef Q_FALLTHROUGH 18 | #define Q_FALLTHROUGH() (void)0 19 | #endif 20 | 21 | class QHotkeyPrivateX11 : public QHotkeyPrivate 22 | { 23 | public: 24 | // QAbstractNativeEventFilter interface 25 | bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; 26 | 27 | protected: 28 | // QHotkeyPrivate interface 29 | quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; 30 | quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; 31 | static QString getX11String(Qt::Key keycode); 32 | bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; 33 | bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; 34 | 35 | private: 36 | static const QVector specialModifiers; 37 | static const quint32 validModsMask; 38 | xcb_key_press_event_t prevHandledEvent; 39 | xcb_key_press_event_t prevEvent; 40 | 41 | static QString formatX11Error(Display *display, int errorCode); 42 | 43 | class HotkeyErrorHandler { 44 | public: 45 | HotkeyErrorHandler(); 46 | ~HotkeyErrorHandler(); 47 | 48 | static bool hasError; 49 | static QString errorString; 50 | 51 | private: 52 | XErrorHandler prevHandler; 53 | 54 | static int handleError(Display *display, XErrorEvent *error); 55 | }; 56 | }; 57 | NATIVE_INSTANCE(QHotkeyPrivateX11) 58 | 59 | bool QHotkeyPrivate::isPlatformSupported() 60 | { 61 | #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) 62 | return qGuiApp->nativeInterface(); 63 | #else 64 | return QX11Info::isPlatformX11(); 65 | #endif 66 | } 67 | 68 | const QVector QHotkeyPrivateX11::specialModifiers = {0, Mod2Mask, LockMask, (Mod2Mask | LockMask)}; 69 | const quint32 QHotkeyPrivateX11::validModsMask = ShiftMask | ControlMask | Mod1Mask | Mod4Mask; 70 | 71 | bool QHotkeyPrivateX11::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) 72 | { 73 | Q_UNUSED(eventType) 74 | Q_UNUSED(result) 75 | 76 | auto *genericEvent = static_cast(message); 77 | if (genericEvent->response_type == XCB_KEY_PRESS) { 78 | xcb_key_press_event_t keyEvent = *static_cast(message); 79 | this->prevEvent = keyEvent; 80 | if (this->prevHandledEvent.response_type == XCB_KEY_RELEASE) { 81 | if(this->prevHandledEvent.time == keyEvent.time) return false; 82 | } 83 | this->activateShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask}); 84 | } else if (genericEvent->response_type == XCB_KEY_RELEASE) { 85 | xcb_key_release_event_t keyEvent = *static_cast(message); 86 | this->prevEvent = keyEvent; 87 | QTimer::singleShot(50, [this, keyEvent] { 88 | if(this->prevEvent.time == keyEvent.time && this->prevEvent.response_type == keyEvent.response_type && this->prevEvent.detail == keyEvent.detail){ 89 | this->releaseShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask}); 90 | } 91 | }); 92 | this->prevHandledEvent = keyEvent; 93 | } 94 | 95 | return false; 96 | } 97 | 98 | QString QHotkeyPrivateX11::getX11String(Qt::Key keycode) 99 | { 100 | switch(keycode){ 101 | 102 | case Qt::Key_MediaLast : 103 | case Qt::Key_MediaPrevious : 104 | return QStringLiteral("XF86AudioPrev"); 105 | case Qt::Key_MediaNext : 106 | return QStringLiteral("XF86AudioNext"); 107 | case Qt::Key_MediaPause : 108 | case Qt::Key_MediaPlay : 109 | case Qt::Key_MediaTogglePlayPause : 110 | return QStringLiteral("XF86AudioPlay"); 111 | case Qt::Key_MediaRecord : 112 | return QStringLiteral("XF86AudioRecord"); 113 | case Qt::Key_MediaStop : 114 | return QStringLiteral("XF86AudioStop"); 115 | default : 116 | return QKeySequence(keycode).toString(QKeySequence::NativeText); 117 | } 118 | } 119 | 120 | quint32 QHotkeyPrivateX11::nativeKeycode(Qt::Key keycode, bool &ok) 121 | { 122 | QString keyString = getX11String(keycode); 123 | 124 | KeySym keysym = XStringToKeysym(keyString.toLatin1().constData()); 125 | if (keysym == NoSymbol) { 126 | //not found -> just use the key 127 | if(keycode <= 0xFFFF) 128 | keysym = keycode; 129 | else 130 | return 0; 131 | } 132 | 133 | #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) 134 | const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface(); 135 | #else 136 | const bool x11Interface = QX11Info::isPlatformX11(); 137 | #endif 138 | 139 | if(x11Interface) { 140 | #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) 141 | Display *display = x11Interface->display(); 142 | #else 143 | Display *display = QX11Info::display(); 144 | #endif 145 | auto res = XKeysymToKeycode(display, keysym); 146 | if(res != 0) 147 | ok = true; 148 | return res; 149 | } 150 | return 0; 151 | } 152 | 153 | quint32 QHotkeyPrivateX11::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) 154 | { 155 | quint32 nMods = 0; 156 | if (modifiers & Qt::ShiftModifier) 157 | nMods |= ShiftMask; 158 | if (modifiers & Qt::ControlModifier) 159 | nMods |= ControlMask; 160 | if (modifiers & Qt::AltModifier) 161 | nMods |= Mod1Mask; 162 | if (modifiers & Qt::MetaModifier) 163 | nMods |= Mod4Mask; 164 | ok = true; 165 | return nMods; 166 | } 167 | 168 | bool QHotkeyPrivateX11::registerShortcut(QHotkey::NativeShortcut shortcut) 169 | { 170 | #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) 171 | const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface(); 172 | Display *display = x11Interface->display(); 173 | #else 174 | const bool x11Interface = QX11Info::isPlatformX11(); 175 | Display *display = QX11Info::display(); 176 | #endif 177 | 178 | if(!display || !x11Interface) 179 | return false; 180 | 181 | HotkeyErrorHandler errorHandler; 182 | for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { 183 | XGrabKey(display, 184 | shortcut.key, 185 | shortcut.modifier | specialMod, 186 | DefaultRootWindow(display), 187 | True, 188 | GrabModeAsync, 189 | GrabModeAsync); 190 | } 191 | XSync(display, False); 192 | 193 | if(errorHandler.hasError) { 194 | error = errorHandler.errorString; 195 | this->unregisterShortcut(shortcut); 196 | return false; 197 | } 198 | return true; 199 | } 200 | 201 | bool QHotkeyPrivateX11::unregisterShortcut(QHotkey::NativeShortcut shortcut) 202 | { 203 | #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) 204 | Display *display = qGuiApp->nativeInterface()->display(); 205 | #else 206 | Display *display = QX11Info::display(); 207 | #endif 208 | 209 | if(!display) 210 | return false; 211 | 212 | HotkeyErrorHandler errorHandler; 213 | for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { 214 | XUngrabKey(display, 215 | shortcut.key, 216 | shortcut.modifier | specialMod, 217 | XDefaultRootWindow(display)); 218 | } 219 | XSync(display, False); 220 | 221 | if(HotkeyErrorHandler::hasError) { 222 | error = HotkeyErrorHandler::errorString; 223 | return false; 224 | } 225 | return true; 226 | } 227 | 228 | QString QHotkeyPrivateX11::formatX11Error(Display *display, int errorCode) 229 | { 230 | char errStr[256]; 231 | XGetErrorText(display, errorCode, errStr, 256); 232 | return QString::fromLatin1(errStr); 233 | } 234 | 235 | 236 | 237 | // ---------- QHotkeyPrivateX11::HotkeyErrorHandler implementation ---------- 238 | 239 | bool QHotkeyPrivateX11::HotkeyErrorHandler::hasError = false; 240 | QString QHotkeyPrivateX11::HotkeyErrorHandler::errorString; 241 | 242 | QHotkeyPrivateX11::HotkeyErrorHandler::HotkeyErrorHandler() 243 | { 244 | prevHandler = XSetErrorHandler(&HotkeyErrorHandler::handleError); 245 | } 246 | 247 | QHotkeyPrivateX11::HotkeyErrorHandler::~HotkeyErrorHandler() 248 | { 249 | XSetErrorHandler(prevHandler); 250 | hasError = false; 251 | errorString.clear(); 252 | } 253 | 254 | int QHotkeyPrivateX11::HotkeyErrorHandler::handleError(Display *display, XErrorEvent *error) 255 | { 256 | switch (error->error_code) { 257 | case BadAccess: 258 | case BadValue: 259 | case BadWindow: 260 | if (error->request_code == 33 || //grab key 261 | error->request_code == 34) {// ungrab key 262 | hasError = true; 263 | errorString = QHotkeyPrivateX11::formatX11Error(display, error->error_code); 264 | return 1; 265 | } 266 | Q_FALLTHROUGH(); 267 | // fall through 268 | default: 269 | return 0; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QHotkey 2 | A global shortcut/hotkey for Desktop Qt-Applications. 3 | 4 | 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. 5 | 6 | ## Features 7 | - Works on Windows, Mac and X11 8 | - Easy to use, can use `QKeySequence` for easy shortcut input 9 | - Supports almost all common keys (Depends on OS & Keyboard-Layout) 10 | - Allows direct input of Key/Modifier-Combinations 11 | - Supports multiple QHotkey-instances for the same shortcut (with optimisations) 12 | - Thread-Safe - Can be used on all threads (See section Thread safety) 13 | - Allows usage of native keycodes and modifiers, if needed 14 | 15 | **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). 16 | 17 | ## Building 18 | 19 | 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. 20 | 21 | ### CMake 22 | 23 | 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: 24 | 25 | ``` 26 | $ cd QHotkey 27 | $ cmake -B build -S . -DQT_DEFAULT_MAJOR_VERSION=6 28 | $ cmake --build build 29 | # cmake --install build 30 | ``` 31 | 32 | ## Installation 33 | 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. 34 | 35 | ### Via qpmx 36 | [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: 37 | 38 | 1. Install qpmx (See [GitHub - Installation](https://github.com/Skycoder42/qpmx#installation)) 39 | 2. Install qpm (See [GitHub - Installing](https://github.com/Cutehacks/qpm/blob/master/README.md#installing), for **windows** see below) 40 | 3. In your projects root directory, run `qpmx install de.skycoder42.qhotkey` 41 | 42 | ### Via qpm 43 | 44 | 1. Install qpm (See [GitHub - Installing](https://github.com/Cutehacks/qpm/blob/master/README.md#installing), for **windows** see below) 45 | 2. In your projects root directory, run `qpm install de.skycoder42.qhotkey` 46 | 3. Include qpm to your project by adding `include(vendor/vendor.pri)` to your `.pro` file 47 | 48 | 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. 49 | 50 | **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: 51 | - https://storage.googleapis.com/www.qpm.io/download/latest/windows_amd64/qpm.exe 52 | - https://storage.googleapis.com/www.qpm.io/download/latest/windows_386/qpm.exe 53 | 54 | ## Usage 55 | 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. 56 | 57 | ### Example 58 | 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. 59 | ```cpp 60 | #include 61 | #include 62 | #include 63 | 64 | int main(int argc, char *argv[]) 65 | { 66 | QApplication app(argc, argv); 67 | 68 | QHotkey hotkey(QKeySequence("Ctrl+Alt+Q"), true, &app); //The hotkey will be automatically registered 69 | qDebug() << "Is segistered:" << hotkey.isRegistered(); 70 | 71 | QObject::connect(&hotkey, &QHotkey::activated, qApp, [&](){ 72 | qDebug() << "Hotkey Activated - the application will quit now"; 73 | qApp->quit(); 74 | }); 75 | 76 | return app.exec(); 77 | } 78 | ``` 79 | 80 | **Note:** You need the .pri include for this to work. 81 | 82 | ### Testing 83 | By running the example in `./HotkeyTest` you can test out the QHotkey class. There are 4 sections: 84 | - **Playground:** You can enter some sequences here and try it out with different key combinations. 85 | - **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). 86 | - **Threading:** Activate the checkbox to move 2 Hotkeys of the playground to separate threads. It should work without a difference. 87 | - **Native Shortcut**: Allows you to try out the direct usage of native shortcuts 88 | 89 | ### Logging 90 | 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: 91 | ```cpp 92 | QLoggingCategory::setFilterRules(QStringLiteral("QHotkey.warning=false")); 93 | ``` 94 | 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 95 | 96 | ## Thread safety 97 | 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. 98 | 99 | 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`. 100 | 101 | 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. 102 | 103 | ## Documentation 104 | The documentation is available as release and on [github pages](https://skycoder42.github.io/QHotkey/). 105 | 106 | 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). 107 | 108 | ## Technical 109 | ### Requirements 110 | - Explicit support is only given down to the latest Qt LTS, but may work with earlier versions, too 111 | - 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. 112 | - C++11 113 | 114 | ### Known Limitations 115 | - Only single key/modifier combinations are possible. If using QKeySequence, only the first key+modifier of the sequence will be used. 116 | - 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. 117 | - 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. 118 | - 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. 119 | - 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 120 | -------------------------------------------------------------------------------- /doc/qhotkey.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @class QHotkey 3 | 4 | Each instance of QHotkey represents one shortcut, i.e. one key + modifiers. These shortcuts, once registered, will be activated if the 5 | user presses that combination - no matter in which application he currently is. 6 | 7 | For more details and an example, check the main page. 8 | 9 | @sa @ref index "Mainpage" 10 | */ 11 | 12 | /*! 13 | @property QHotkey::registered 14 | 15 | @default{`false`} 16 | 17 | If a hotkey is registered, this means that it is set active in the OS and hotkey events will be send to the application. 18 | If it is registered and the keys are pressed, the activated() signal will be emitted. 19 | 20 | @warning Registering/Unregistering hotkeys on other threads but the main thread is allowed, but will block the calling 21 | thread until the applications eventloop has the time to handle it. If the loop is not running, the function will block until 22 | it does. This does not happen if used from the main thread. 23 | 24 | @accessors{ 25 | @readAc{isRegistered()} 26 | @writeAc{setRegistered()} 27 | @notifyAc{registeredChanged()} 28 | } 29 | 30 | In addition to the normal accessors, a modification of the QHotkey::shortcut property can change this property, too. 31 | 32 | @sa QHotkey::shortcut, QHotkey::activated, QHotkey::isKeyCaptured 33 | */ 34 | 35 | /*! 36 | @property QHotkey::shortcut 37 | 38 | @default{`QKeySequence()` (`Qt::Key_unknown` + `Qt::NoModifier`)} 39 | 40 | Holds the shortcut this hotkey will be registered on. It can be used as QKeySequence or as a combination of 41 | a Qt::Key and Qt::KeyboardModifiers. All write-accessors specify an additional parameter to immediately register 42 | the hotkey. 43 | 44 | @note Since the operating systems do not support hotkeys that consist of multiple key-combinations in a sequence, 45 | only the first key/modifier combination of a QKeySequence will be used. 46 | 47 | @warning changing the shortcut on other threads but the main thread is allowed, but will block the calling 48 | thread until the applications eventloop has the time to handle it. If the loop is not running, the function will block until 49 | it does. This does not happen if used from the main thread. 50 | 51 | @accessors{ 52 | @readAc{ 53 | shortcut()
54 | keyCode()
55 | modifiers() 56 | } 57 | @writeAc{ 58 | setShortcut(const QKeySequence &, bool)
59 | setShortcut(Qt::Key, Qt::KeyboardModifiers, bool) 60 | } 61 | } 62 | 63 | @sa QHotkey::registered, QHotkey::isKeyCaptured, QHotkey::currentNativeShortcut, QHotkey::setNativeShortcut 64 | */ 65 | 66 | /*! 67 | @fn QHotkey::QHotkey(QObject *) 68 | 69 | @param parent The parent object 70 | */ 71 | 72 | /*! 73 | @fn QHotkey::QHotkey(const QKeySequence &, bool, QObject *) 74 | 75 | @param parent The parent object 76 | @param shortcut The shortcut this hotkey should be registered for 77 | @param autoRegister Specifies, whether the hotkey should be automatically registered or not 78 | 79 | @sa QHotkey::shortcut, QHotkey::registered 80 | */ 81 | 82 | /*! 83 | @fn QHotkey::QHotkey(Qt::Key, Qt::KeyboardModifiers, bool, QObject *) 84 | 85 | @param parent The parent object 86 | @param keyCode The key this hotkey should be registered for 87 | @param modifiers The modifiers that have to be pressed together with the `key` 88 | @param autoRegister Specifies, whether the hotkey should be automatically registered or not 89 | 90 | @sa QHotkey::shortcut, QHotkey::registered 91 | */ 92 | 93 | /*! 94 | @fn QHotkey::QHotkey(const NativeShortcut &, bool, QObject *) 95 | 96 | @param parent The parent object 97 | @param shortcut The native shortcut this hotkey should be registered for 98 | @param autoRegister Specifies, whether the hotkey should be automatically registered or not 99 | 100 | @note Please check QHotkey::setNativeShortcut for important hints! 101 | 102 | @sa QHotkey::setNativeShortcut, QHotkey::registered 103 | */ 104 | 105 | /*! 106 | @fn QHotkey::~QHotkey 107 | 108 | If the hotkey is still registered on destruction, it will automatically unregister itself. 109 | 110 | @warning If using a hotkey on a thread other than the main thread, make sure the QApplication is still running it's eventloop. 111 | Otherwise your application will hang up. 112 | 113 | @sa QHotkey::registered 114 | */ 115 | 116 | /*! 117 | @fn QHotkey::setNativeShortcut 118 | 119 | @param nativeShortcut The native shortcut to be set 120 | @param autoRegister Specifies, whether the hotkey should be automatically registered, or not 121 | @returns `true`, if the shortcut could be successfully set/registered, `false` if not 122 | 123 | @warning Setting the native shortcut will cause the shortcut(), keyCode() and modifiers() functions 124 | to return "invalid values", i.e. if a hotkey has an explicitly set native shortcut, it will not be able 125 | to get the Qt values for them. 126 | 127 | @sa QHotkey::currentNativeShortcut, QHotkey::shortcut, QHotkey::registered 128 | */ 129 | 130 | /*! 131 | @fn QHotkey::activated 132 | 133 | This signal can only be emitted if Hotkey::registered is set to true (with a valid key-sequence). The signal will always be emitted 134 | from the thread this instance lives on. 135 | 136 | @note This is a private signal. It can be used in signal connections but cannot be emitted by the user. 137 | */ 138 | 139 | /*! 140 | @fn QHotkey::addGlobalMapping 141 | 142 | @param shortcut The keysequence to add the mapping for 143 | @param nativeShortcut The native shortcut to overwrite the sequence with 144 | 145 | This method can be used to remap specific hotkey to a different native representation than the 146 | one that would be used by default. This can be useful if specific key combinations work fine 147 | on almost all platforms, but on one you need a different keycode for the same effect. See 148 | [Issue #15](https://github.com/Skycoder42/QHotkey/issues/15) for an example where this is the 149 | case. 150 | 151 | The advantage of using this approach via simply registering it as native key directly, is that 152 | these mappings work for cases where users input their own hotkeys. 153 | */ 154 | 155 | /*! 156 | @class QHotkey::NativeShortcut 157 | 158 | The NativeShortcut holds the native keycode and modifiers, after they have been translated 159 | from Qt::Key and Qt::Modifiers into the native ones. 160 | 161 | It can be used to find out how a current hotkey is mapped to the system, or to explicitly create 162 | a shortcut from those native values 163 | 164 | @sa QHotkey::setNativeShortcut 165 | */ 166 | -------------------------------------------------------------------------------- /qpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "email": "skycoder42.de@gmx.de", 4 | "name": "Skycoder" 5 | }, 6 | "dependencies": [ 7 | ], 8 | "description": "A global shortcut/hotkey for Desktop Qt-Applications", 9 | "license": "BSD_3_CLAUSE", 10 | "name": "de.skycoder42.qhotkey", 11 | "pri_filename": "qhotkey.pri", 12 | "repository": { 13 | "type": "GITHUB", 14 | "url": "https://github.com/Skycoder42/QHotkey.git" 15 | }, 16 | "version": { 17 | "fingerprint": "", 18 | "label": "1.2.2", 19 | "revision": "" 20 | }, 21 | "webpage": "https://github.com/Skycoder42/QHotkey" 22 | } 23 | -------------------------------------------------------------------------------- /qpmx.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | ], 4 | "license": { 5 | "file": "LICENSE", 6 | "name": "BSD_3_CLAUSE" 7 | }, 8 | "prcFile": "qhotkey.prc", 9 | "priFile": "qhotkey.pri", 10 | "priIncludes": [ 11 | ], 12 | "publishers": { 13 | "qpm": { 14 | "author": { 15 | "email": "skycoder42.de@gmx.de", 16 | "name": "Skycoder" 17 | }, 18 | "description": "A global shortcut/hotkey for Desktop Qt-Applications", 19 | "name": "de.skycoder42.qhotkey", 20 | "repository": { 21 | "type": "GITHUB", 22 | "url": "https://github.com/Skycoder42/QHotkey.git" 23 | }, 24 | "webpage": "https://github.com/Skycoder42/QHotkey" 25 | } 26 | }, 27 | "source": false 28 | } 29 | --------------------------------------------------------------------------------