add hotkey

This commit is contained in:
朱子楚\zhuzi 2024-05-16 00:35:04 +08:00
parent 876b230141
commit 354f7f2e3e
26 changed files with 2083 additions and 114 deletions

View File

@ -34,6 +34,38 @@ providing powerful tools and support for this project.
For more information about the Qt project,
please visit the official Qt website (https://www.qt.io/).
************************************************************************************
QHotkey
Copyright (c) 2016, Felix Barz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of QHotkey nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************************
framelesshelper

View File

@ -209,6 +209,7 @@
<file>qml/window/FluentInitializrWindow.qml</file>
<file>qml/page/T_OpenGL.qml</file>
<file>qml/page/T_Icons.qml</file>
<file>qml/window/HotkeyWindow.qml</file>
</qresource>
<qresource prefix="/"/>
</RCC>

View File

@ -1,6 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>App</name>
<message>
<location filename="qml/App.qml" line="61"/>
<source>Quit</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/App.qml" line="68"/>
<source>Test1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/App.qml" line="75"/>
<source>Test2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/App.qml" line="82"/>
<source>Test3</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/App.qml" line="89"/>
<source>Test4</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/App.qml" line="96"/>
<source>Test5</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/App.qml" line="103"/>
<source>Test6</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/App.qml" line="110"/>
<source>Test7</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/App.qml" line="117"/>
<source>Test8</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CodeExpander</name>
<message>
@ -71,6 +119,14 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>HotkeyWindow</name>
<message>
<location filename="qml/window/HotkeyWindow.qml" line="11"/>
<source>Hotkey</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>HotloadWindow</name>
<message>

View File

@ -1,6 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>App</name>
<message>
<location filename="qml/App.qml" line="61"/>
<source>Quit</source>
<translation type="unfinished">退</translation>
</message>
<message>
<location filename="qml/App.qml" line="68"/>
<source>Test1</source>
<translation type="unfinished">1</translation>
</message>
<message>
<location filename="qml/App.qml" line="75"/>
<source>Test2</source>
<translation type="unfinished">2</translation>
</message>
<message>
<location filename="qml/App.qml" line="82"/>
<source>Test3</source>
<translation type="unfinished">3</translation>
</message>
<message>
<location filename="qml/App.qml" line="89"/>
<source>Test4</source>
<translation type="unfinished">4</translation>
</message>
<message>
<location filename="qml/App.qml" line="96"/>
<source>Test5</source>
<translation type="unfinished">5</translation>
</message>
<message>
<location filename="qml/App.qml" line="103"/>
<source>Test6</source>
<translation type="unfinished">6</translation>
</message>
<message>
<location filename="qml/App.qml" line="110"/>
<source>Test7</source>
<translation type="unfinished">7</translation>
</message>
<message>
<location filename="qml/App.qml" line="117"/>
<source>Test8</source>
<translation type="unfinished">8</translation>
</message>
</context>
<context>
<name>CodeExpander</name>
<message>
@ -71,6 +119,14 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>HotkeyWindow</name>
<message>
<location filename="qml/window/HotkeyWindow.qml" line="11"/>
<source>Hotkey</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>HotloadWindow</name>
<message>
@ -2043,6 +2099,10 @@ Some contents...</source>
<source>ShortcutPicker</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Quit</source>
<translation type="obsolete">退</translation>
</message>
<message>
<source>Activate the Shortcut</source>
<translation type="obsolete"></translation>

View File

@ -43,7 +43,8 @@ FluLauncher {
"/singleTaskWindow":"qrc:/example/qml/window/SingleTaskWindow.qml",
"/standardWindow":"qrc:/example/qml/window/StandardWindow.qml",
"/singleInstanceWindow":"qrc:/example/qml/window/SingleInstanceWindow.qml",
"/pageWindow":"qrc:/example/qml/window/PageWindow.qml"
"/pageWindow":"qrc:/example/qml/window/PageWindow.qml",
"/hotkey":"qrc:/example/qml/window/HotkeyWindow.qml"
}
var args = Qt.application.arguments
if(args.length>=2 && args[1].startsWith("-crashed=")){
@ -52,4 +53,73 @@ FluLauncher {
FluRouter.navigate("/")
}
}
property alias hotkeys: object_hotkey
FluObject{
id: object_hotkey
FluHotkey{
name: qsTr("Quit")
sequence: "Ctrl+Alt+Q"
onActivated: {
FluRouter.exit()
}
}
FluHotkey{
name: qsTr("Test1")
sequence: "Alt+A"
onActivated: {
FluRouter.navigate("/hotkey",{sequence:sequence})
}
}
FluHotkey{
name: qsTr("Test2")
sequence: "Alt+B"
onActivated: {
FluRouter.navigate("/hotkey",{sequence:sequence})
}
}
FluHotkey{
name: qsTr("Test3")
sequence: "Alt+C"
onActivated: {
FluRouter.navigate("/hotkey",{sequence:sequence})
}
}
FluHotkey{
name: qsTr("Test4")
sequence: "Alt+D"
onActivated: {
FluRouter.navigate("/hotkey",{sequence:sequence})
}
}
FluHotkey{
name: qsTr("Test5")
sequence: "Alt+E"
onActivated: {
FluRouter.navigate("/hotkey",{sequence:sequence})
}
}
FluHotkey{
name: qsTr("Test6")
sequence: "Alt+F"
onActivated: {
FluRouter.navigate("/hotkey",{sequence:sequence})
}
}
FluHotkey{
name: qsTr("Test7")
sequence: "Alt+G"
onActivated: {
FluRouter.navigate("/hotkey",{sequence:sequence})
}
}
FluHotkey{
name: qsTr("Test8")
sequence: "Alt+H"
onActivated: {
FluRouter.navigate("/hotkey",{sequence:sequence})
}
}
}
}

View File

@ -11,12 +11,26 @@ FluScrollablePage{
FluFrame{
Layout.fillWidth: true
Layout.preferredHeight: 100
padding: 10
FluShortcutPicker{
Layout.preferredHeight: childrenRect.height
ColumnLayout{
anchors.verticalCenter: parent.verticalCenter
Item{
Layout.preferredHeight: 15
}
Repeater{
model: FluApp.launcher.hotkeys.children
delegate: FluShortcutPicker{
text: model.name
syncHotkey: FluApp.launcher.hotkeys.children[index]
Layout.leftMargin: 15
}
}
Item{
Layout.preferredHeight: 15
}
}
}
CodeExpander{
Layout.fillWidth: true
Layout.topMargin: -6

View File

@ -0,0 +1,26 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import FluentUI 1.0
import "../component"
FluWindow {
id: window
property string sequence: ""
title: qsTr("Hotkey")
width: 250
height: 250
fixSize: true
launchMode: FluWindowType.SingleInstance
onInitArgument:
(argument)=>{
window.sequence = argument.sequence
}
FluText{
anchors.centerIn: parent
color: FluTheme.primaryColor
font: FluTextStyle.Title
text: window.sequence
}
}

View File

@ -61,9 +61,28 @@ file(COPY ${QM_FILE_PATHS} DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/Qt${QT_VERSI
file(GLOB_RECURSE CPP_FILES *.cpp *.h *.cxx)
foreach (filepath ${CPP_FILES})
string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath})
message(${filename})
list(APPEND sources_files ${filename})
endforeach (filepath)
list(REMOVE_ITEM sources_files qhotkey/qhotkey_mac.cpp qhotkey/qhotkey_win.cpp qhotkey/qhotkey_x11.cpp)
if (WIN32)
list(APPEND sources_files qhotkey/qhotkey_win.cpp)
elseif (APPLE)
list(APPEND sources_files qhotkey/qhotkey_mac.cpp)
elseif (UNIX)
list(APPEND sources_files qhotkey/qhotkey_x11.cpp)
endif()
if (WIN32)
set(FLUENTUI_VERSION_RC_PATH ${CMAKE_CURRENT_BINARY_DIR}/version_${PROJECT_NAME}.rc)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/.cmake/version_dll.rc.in
${FLUENTUI_VERSION_RC_PATH}
)
endif ()
if (QT_VERSION VERSION_GREATER_EQUAL "6.2")
#fluentuiplugin.cppfluentuiplugin.hQt5使Qt6
list(REMOVE_ITEM sources_files fluentuiplugin.h fluentuiplugin.cpp)

View File

@ -16,9 +16,10 @@ FluApp::FluApp(QObject *parent) : QObject{parent} {
FluApp::~FluApp() = default;
void FluApp::init(QObject *target, QLocale locale) {
void FluApp::init(QObject *launcher, QLocale locale) {
this->launcher(launcher);
_locale = std::move(locale);
_engine = qmlEngine(target);
_engine = qmlEngine(launcher);
_translator = new QTranslator(this);
QGuiApplication::installTranslator(_translator);
const QStringList uiLanguages = _locale.uiLanguages();

View File

@ -21,6 +21,7 @@ Q_OBJECT
Q_PROPERTY_AUTO(bool, useSystemAppBar)
Q_PROPERTY_AUTO(QString, windowIcon)
Q_PROPERTY_AUTO(QLocale, locale)
Q_PROPERTY_AUTO_P(QObject*,launcher)
QML_NAMED_ELEMENT(FluApp)
QML_SINGLETON
@ -34,7 +35,7 @@ SINGLETON(FluApp)
static FluApp *create(QQmlEngine *, QJSEngine *) { return getInstance(); }
Q_INVOKABLE void init(QObject *target, QLocale locale = QLocale::system());
Q_INVOKABLE void init(QObject *launcher, QLocale locale = QLocale::system());
[[maybe_unused]] Q_INVOKABLE static QJsonArray iconData(const QString &keyword = "");

32
src/FluHotkey.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "FluHotkey.h"
#include "QGuiApplication"
FluHotkey::FluHotkey(QObject *parent)
: QObject{parent}
{
_sequence = "";
_isRegistered = false;
connect(this,&FluHotkey::sequenceChanged,this,[=]{
if(_hotkey){
delete _hotkey;
_hotkey = nullptr;
}
_hotkey = new QHotkey(QKeySequence(_sequence), true, qApp);
this->isRegistered(_hotkey->isRegistered());
QObject::connect(_hotkey, &QHotkey::activated, qApp, [=](){
Q_EMIT this->activated();
});
QObject::connect(_hotkey, &QHotkey::registeredChanged, qApp, [=](){
this->isRegistered(_hotkey->isRegistered());
});
});
}
FluHotkey::~FluHotkey(){
if(_hotkey){
delete _hotkey;
_hotkey = nullptr;
}
}

24
src/FluHotkey.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef FLUHOTKEY_H
#define FLUHOTKEY_H
#include <QObject>
#include <QQuickItem>
#include "qhotkey/qhotkey.h"
#include "stdafx.h"
class FluHotkey : public QObject
{
Q_OBJECT
Q_PROPERTY_AUTO(QString,sequence)
Q_PROPERTY_AUTO(QString,name)
Q_PROPERTY_READONLY_AUTO(bool,isRegistered)
QML_NAMED_ELEMENT(FluHotkey)
public:
explicit FluHotkey(QObject *parent = nullptr);
~FluHotkey();
Q_SIGNAL void activated();
private:
QHotkey* _hotkey = nullptr;
};
#endif // FLUHOTKEY_H

View File

@ -16,6 +16,7 @@
#include "FluTableSortProxyModel.h"
#include "FluFrameless.h"
#include "FluTableModel.h"
#include "FluHotkey.h"
void FluentUI::registerTypes(QQmlEngine *engine) {
initializeEngine(engine, _uri);
@ -36,6 +37,7 @@ void FluentUI::registerTypes(const char *uri) const {
qmlRegisterType<FluTableModel>(uri, major, minor, "FluTableModel");
qmlRegisterType<FluRectangle>(uri, major, minor, "FluRectangle");
qmlRegisterType<FluFrameless>(uri, major, minor, "FluFrameless");
qmlRegisterType<FluHotkey>(uri, major, minor, "FluHotkey");
qmlRegisterType<FluTableSortProxyModel>(uri, major, minor, "FluTableSortProxyModel");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluAcrylic.qml"), uri, major, minor, "FluAcrylic");

View File

@ -39,7 +39,7 @@ T.Menu {
: false
clip: true
currentIndex: control.currentIndex
ScrollIndicator.vertical: ScrollIndicator {}
ScrollBar.vertical: FluScrollBar{}
}
background: Rectangle {
implicitWidth: 150

View File

@ -10,7 +10,30 @@ FluIconButton {
property string positiveText: qsTr("Save")
property string neutralText: qsTr("Cancel")
property string negativeText: qsTr("Reset")
property bool registered: true
property color errorColor: Qt.rgba(250/255,85/255,85/255,1)
property FluHotkey syncHotkey: undefined
signal accepted()
padding: 0
verticalPadding: 0
horizontalPadding: 0
onSyncHotkeyChanged: {
current = syncHotkey.sequence.split("+")
control.registered = syncHotkey.isRegistered
control.registered = Qt.binding(function(){
return syncHotkey.isRegistered
})
}
text: ""
color: {
if(!enabled){
return disableColor
}
if(pressed){
return pressedColor
}
return hovered ? hoverColor : normalColor
}
QtObject{
id: d
function keyToString(key_code,shift = true)
@ -112,37 +135,46 @@ FluIconButton {
return "";
}
}
background: Rectangle{
background: Item{
implicitHeight: 42
implicitWidth: 42
}
contentItem: Item{
implicitWidth: childrenRect.width
implicitHeight: layout_row.height
FluText{
id: text_title
text: control.text
visible: control.text !== ""
rightPadding: 8
anchors{
verticalCenter: layout_rect.verticalCenter
}
}
Rectangle{
id: layout_rect
border.color: FluTheme.dark ? "#505050" : "#DFDFDF"
border.width: 1
implicitHeight: 42
implicitWidth: layout_row.width+28
radius: control.radius
color:control.color
color: control.color
height: control.height
width: layout_row.width
anchors{
left: text_title.right
}
FluFocusRectangle{
visible: control.activeFocus
}
}
Component{
id:com_item_key
Rectangle{
id:item_key_control
color:FluTheme.primaryColor
width: Math.max(item_text.implicitWidth+12,28)
height: Math.max(item_text.implicitHeight,28)
radius: 4
FluText{
id:item_text
color: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1)
text: keyText
anchors.centerIn: parent
}
}
}
Row{
id:layout_row
spacing: 5
anchors.centerIn: parent
Item{
width: 8
height: 1
}
Repeater{
model: control.current
delegate: Loader{
@ -161,6 +193,39 @@ FluIconButton {
verticalCenter: parent.verticalCenter
}
}
Item{
width: 8
height: 1
}
}
}
FluText{
id: text_error
text: qsTr("Conflict")
color: control.errorColor
visible: !control.registered
anchors{
verticalCenter: layout_rect.verticalCenter
left: layout_rect.right
leftMargin: 4
}
}
}
Component{
id:com_item_key
Rectangle{
id:item_key_control
color:FluTheme.primaryColor
width: Math.max(item_text.implicitWidth+12,28)
height: Math.max(item_text.implicitHeight,28)
radius: 4
FluText{
id:item_text
color: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1)
text: keyText
anchors.centerIn: parent
}
}
}
FluContentDialog{
id:content_dialog
@ -179,6 +244,9 @@ FluIconButton {
onPositiveClicked: {
control.current = content_dialog.keysModel
control.accepted()
if(control.syncHotkey){
control.syncHotkey.sequence = control.current.join("+")
}
}
onNegativeClickListener: function(){
content_dialog.keysModel = control.current

View File

@ -31,14 +31,15 @@ Module {
Property { name: "useSystemAppBar"; type: "bool" }
Property { name: "windowIcon"; type: "string" }
Property { name: "locale"; type: "QLocale" }
Property { name: "launcher"; type: "QObject"; isPointer: true }
Method {
name: "init"
Parameter { name: "target"; type: "QObject"; isPointer: true }
Parameter { name: "launcher"; type: "QObject"; isPointer: true }
Parameter { name: "locale"; type: "QLocale" }
}
Method {
name: "init"
Parameter { name: "target"; type: "QObject"; isPointer: true }
Parameter { name: "launcher"; type: "QObject"; isPointer: true }
}
Method {
name: "iconData"
@ -159,6 +160,16 @@ Module {
}
Method { name: "onDestruction" }
}
Component {
name: "FluHotkey"
prototype: "QObject"
exports: ["FluentUI/FluHotkey 1.0"]
exportMetaObjectRevisions: [0]
Property { name: "sequence"; type: "string" }
Property { name: "name"; type: "string" }
Property { name: "isRegistered"; type: "bool"; isReadonly: true }
Signal { name: "activated" }
}
Component {
name: "FluNavigationViewType"
exports: ["FluentUI/FluNavigationViewType 1.0"]
@ -273,7 +284,6 @@ Module {
prototype: "QAbstractTableModel"
exports: ["FluentUI/FluTableModel 1.0"]
exportMetaObjectRevisions: [0]
Property { name: "dataSourceSize"; type: "int" }
Property { name: "columnSource"; type: "QList<QVariantMap>" }
Property { name: "rows"; type: "QList<QVariantMap>" }
Property { name: "rowCount"; type: "int"; isReadonly: true }
@ -2497,37 +2507,37 @@ Module {
Property { name: "darkClickListener"; type: "QVariant" }
Property {
name: "buttonStayTop"
type: "FluIconButton_QMLTYPE_20"
type: "FluIconButton_QMLTYPE_18"
isReadonly: true
isPointer: true
}
Property {
name: "buttonMinimize"
type: "FluIconButton_QMLTYPE_20"
type: "FluIconButton_QMLTYPE_18"
isReadonly: true
isPointer: true
}
Property {
name: "buttonMaximize"
type: "FluIconButton_QMLTYPE_20"
type: "FluIconButton_QMLTYPE_18"
isReadonly: true
isPointer: true
}
Property {
name: "buttonClose"
type: "FluIconButton_QMLTYPE_20"
type: "FluIconButton_QMLTYPE_18"
isReadonly: true
isPointer: true
}
Property {
name: "buttonDark"
type: "FluIconButton_QMLTYPE_20"
type: "FluIconButton_QMLTYPE_18"
isReadonly: true
isPointer: true
}
Property {
name: "layoutMacosButtons"
type: "FluLoader_QMLTYPE_13"
type: "FluLoader_QMLTYPE_16"
isReadonly: true
isPointer: true
}
@ -3100,6 +3110,7 @@ Module {
Parameter { name: "itemcomponent"; type: "QVariant" }
Parameter { name: "duration"; type: "QVariant" }
}
Method { name: "clearAllInfo"; type: "QVariant" }
Property { name: "children"; type: "QObject"; isList: true; isReadonly: true }
}
Component {
@ -3219,15 +3230,15 @@ Module {
defaultProperty: "data"
Property { name: "logo"; type: "QUrl" }
Property { name: "title"; type: "string" }
Property { name: "items"; type: "FluObject_QMLTYPE_173"; isPointer: true }
Property { name: "footerItems"; type: "FluObject_QMLTYPE_173"; isPointer: true }
Property { name: "items"; type: "FluObject_QMLTYPE_164"; isPointer: true }
Property { name: "footerItems"; type: "FluObject_QMLTYPE_164"; isPointer: true }
Property { name: "displayMode"; type: "int" }
Property { name: "autoSuggestBox"; type: "QQmlComponent"; isPointer: true }
Property { name: "actionItem"; type: "QQmlComponent"; isPointer: true }
Property { name: "topPadding"; type: "int" }
Property { name: "pageMode"; type: "int" }
Property { name: "navItemRightMenu"; type: "FluMenu_QMLTYPE_49"; isPointer: true }
Property { name: "navItemExpanderRightMenu"; type: "FluMenu_QMLTYPE_49"; isPointer: true }
Property { name: "navItemRightMenu"; type: "FluMenu_QMLTYPE_36"; isPointer: true }
Property { name: "navItemExpanderRightMenu"; type: "FluMenu_QMLTYPE_36"; isPointer: true }
Property { name: "navCompactWidth"; type: "int" }
Property { name: "navTopMargin"; type: "int" }
Property { name: "cellHeight"; type: "int" }
@ -3235,13 +3246,13 @@ Module {
Property { name: "hideNavAppBar"; type: "bool" }
Property {
name: "buttonMenu"
type: "FluIconButton_QMLTYPE_20"
type: "FluIconButton_QMLTYPE_18"
isReadonly: true
isPointer: true
}
Property {
name: "buttonBack"
type: "FluIconButton_QMLTYPE_20"
type: "FluIconButton_QMLTYPE_18"
isReadonly: true
isPointer: true
}
@ -3539,6 +3550,8 @@ Module {
Property { name: "currentIndex"; type: "int" }
Property { name: "spacing"; type: "int" }
Property { name: "orientation"; type: "int" }
Property { name: "disabled"; type: "bool" }
Property { name: "manuallyDisabled"; type: "bool" }
}
Component {
prototype: "QQuickRangeSlider"
@ -3693,6 +3706,9 @@ Module {
Property { name: "positiveText"; type: "string" }
Property { name: "neutralText"; type: "string" }
Property { name: "negativeText"; type: "string" }
Property { name: "registered"; type: "bool" }
Property { name: "errorColor"; type: "QColor" }
Property { name: "syncHotkey"; type: "FluHotkey"; isPointer: true }
Signal { name: "accepted" }
Property { name: "iconSize"; type: "int" }
Property { name: "iconSource"; type: "int" }
@ -3826,9 +3842,12 @@ Module {
Property { name: "verticalHeaderVisible"; type: "bool" }
Property { name: "selectedBorderColor"; type: "QColor" }
Property { name: "selectedColor"; type: "QColor" }
Property { name: "columnWidthProvider"; type: "QVariant" }
Property { name: "rowHeightProvider"; type: "QVariant" }
Property { name: "rows"; type: "int"; isReadonly: true }
Property { name: "columns"; type: "int"; isReadonly: true }
Property { name: "current"; type: "QVariant"; isReadonly: true }
Property { name: "view"; type: "QQuickTableView"; isReadonly: true; isPointer: true }
Method { name: "closeEditor"; type: "QVariant" }
Method { name: "resetPosition"; type: "QVariant" }
Method {
@ -4081,6 +4100,7 @@ Module {
Property { name: "selectedBorderColor"; type: "QColor" }
Property { name: "selectedColor"; type: "QColor" }
Property { name: "current"; type: "QVariant"; isReadonly: true }
Property { name: "view"; type: "QQuickTableView"; isReadonly: true; isPointer: true }
Method { name: "count"; type: "QVariant" }
Method { name: "visibleCount"; type: "QVariant" }
Method {
@ -4172,6 +4192,7 @@ Module {
Parameter { name: "duration"; type: "QVariant" }
Parameter { name: "moremsg"; type: "QVariant" }
}
Method { name: "clearAllInfo"; type: "QVariant" }
Method { name: "moveWindowToDesktopCenter"; type: "QVariant" }
Method { name: "fixWindowSize"; type: "QVariant" }
Method {
@ -4274,6 +4295,7 @@ Module {
Parameter { name: "duration"; type: "QVariant" }
Parameter { name: "moremsg"; type: "QVariant" }
}
Method { name: "clearAllInfo"; type: "QVariant" }
Method { name: "moveWindowToDesktopCenter"; type: "QVariant" }
Method { name: "fixWindowSize"; type: "QVariant" }
Method {

View File

@ -39,7 +39,7 @@ T.Menu {
: false
clip: true
currentIndex: control.currentIndex
ScrollIndicator.vertical: ScrollIndicator {}
ScrollBar.vertical: FluScrollBar{}
}
background: Rectangle {
implicitWidth: 150

View File

@ -10,7 +10,30 @@ FluIconButton {
property string positiveText: qsTr("Save")
property string neutralText: qsTr("Cancel")
property string negativeText: qsTr("Reset")
property bool registered: true
property color errorColor: Qt.rgba(250/255,85/255,85/255,1)
property FluHotkey syncHotkey: undefined
signal accepted()
padding: 0
verticalPadding: 0
horizontalPadding: 0
onSyncHotkeyChanged: {
current = syncHotkey.sequence.split("+")
control.registered = syncHotkey.isRegistered
control.registered = Qt.binding(function(){
return syncHotkey.isRegistered
})
}
text: ""
color: {
if(!enabled){
return disableColor
}
if(pressed){
return pressedColor
}
return hovered ? hoverColor : normalColor
}
QtObject{
id: d
function keyToString(key_code,shift = true)
@ -112,37 +135,46 @@ FluIconButton {
return "";
}
}
background: Rectangle{
background: Item{
implicitHeight: 42
implicitWidth: 42
}
contentItem: Item{
implicitWidth: childrenRect.width
implicitHeight: layout_row.height
FluText{
id: text_title
text: control.text
visible: control.text !== ""
rightPadding: 8
anchors{
verticalCenter: layout_rect.verticalCenter
}
}
Rectangle{
id: layout_rect
border.color: FluTheme.dark ? "#505050" : "#DFDFDF"
border.width: 1
implicitHeight: 42
implicitWidth: layout_row.width+28
radius: control.radius
color:control.color
color: control.color
height: control.height
width: layout_row.width
anchors{
left: text_title.right
}
FluFocusRectangle{
visible: control.activeFocus
}
}
Component{
id:com_item_key
Rectangle{
id:item_key_control
color:FluTheme.primaryColor
width: Math.max(item_text.implicitWidth+12,28)
height: Math.max(item_text.implicitHeight,28)
radius: 4
FluText{
id:item_text
color: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1)
text: keyText
anchors.centerIn: parent
}
}
}
Row{
id:layout_row
spacing: 5
anchors.centerIn: parent
Item{
width: 8
height: 1
}
Repeater{
model: control.current
delegate: Loader{
@ -161,6 +193,39 @@ FluIconButton {
verticalCenter: parent.verticalCenter
}
}
Item{
width: 8
height: 1
}
}
}
FluText{
id: text_error
text: qsTr("Conflict")
color: control.errorColor
visible: !control.registered
anchors{
verticalCenter: layout_rect.verticalCenter
left: layout_rect.right
leftMargin: 4
}
}
}
Component{
id:com_item_key
Rectangle{
id:item_key_control
color:FluTheme.primaryColor
width: Math.max(item_text.implicitWidth+12,28)
height: Math.max(item_text.implicitHeight,28)
radius: 4
FluText{
id:item_text
color: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1)
text: keyText
anchors.centerIn: parent
}
}
}
FluContentDialog{
id:content_dialog
@ -179,6 +244,9 @@ FluIconButton {
onPositiveClicked: {
control.current = content_dialog.keysModel
control.accepted()
if(control.syncHotkey){
control.syncHotkey.sequence = control.current.join("+")
}
}
onNegativeClickListener: function(){
content_dialog.keysModel = control.current

View File

@ -74,49 +74,49 @@
<name>FluColorPicker</name>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="16"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="16"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="17"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="17"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="17"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="18"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="18"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="18"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="19"/>
<source>Color Picker</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="19"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="19"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="20"/>
<source>Edit Color</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="20"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="20"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="21"/>
<source>Red</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="21"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="21"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="22"/>
<source>Green</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="22"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="22"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="23"/>
<source>Blue</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="23"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="23"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="24"/>
<source>Opacity</source>
<translation type="unfinished"></translation>
</message>
@ -187,14 +187,14 @@
<context>
<name>FluPagination</name>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluPagination.qml" line="8"/>
<location filename="Qt6/imports/FluentUI/Controls/FluPagination.qml" line="8"/>
<location filename="Qt5/imports/FluentUI/Controls/FluPagination.qml" line="10"/>
<location filename="Qt6/imports/FluentUI/Controls/FluPagination.qml" line="9"/>
<source>&lt;Previous</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluPagination.qml" line="9"/>
<location filename="Qt6/imports/FluentUI/Controls/FluPagination.qml" line="9"/>
<location filename="Qt5/imports/FluentUI/Controls/FluPagination.qml" line="11"/>
<location filename="Qt6/imports/FluentUI/Controls/FluPagination.qml" line="10"/>
<source>Next&gt;</source>
<translation type="unfinished"></translation>
</message>
@ -231,6 +231,11 @@
<source>Reset</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt6/imports/FluentUI/Controls/FluShortcutPicker.qml" line="205"/>
<source>Conflict</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FluStatusLayout</name>
@ -349,10 +354,23 @@
<context>
<name>FluWindow</name>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluWindow.qml" line="335"/>
<location filename="Qt6/imports/FluentUI/Controls/FluWindow.qml" line="334"/>
<location filename="Qt5/imports/FluentUI/Controls/FluWindow.qml" line="347"/>
<location filename="Qt6/imports/FluentUI/Controls/FluWindow.qml" line="346"/>
<source>Loading...</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QHotkey</name>
<message>
<location filename="qhotkey/qhotkey.cpp" line="294"/>
<source>Failed to register %1. Error: %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qhotkey/qhotkey.cpp" line="314"/>
<source>Failed to unregister %1. Error: %2</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -74,49 +74,49 @@
<name>FluColorPicker</name>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="16"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="16"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="17"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="17"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="17"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="18"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="18"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="18"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="19"/>
<source>Color Picker</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="19"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="19"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="20"/>
<source>Edit Color</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="20"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="20"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="21"/>
<source>Red</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="21"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="21"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="22"/>
<source>Green</source>
<translation type="unfinished">绿</translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="22"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="22"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="23"/>
<source>Blue</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluColorPicker.qml" line="23"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="23"/>
<location filename="Qt6/imports/FluentUI/Controls/FluColorPicker.qml" line="24"/>
<source>Opacity</source>
<translation type="unfinished"></translation>
</message>
@ -187,14 +187,14 @@
<context>
<name>FluPagination</name>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluPagination.qml" line="8"/>
<location filename="Qt6/imports/FluentUI/Controls/FluPagination.qml" line="8"/>
<location filename="Qt5/imports/FluentUI/Controls/FluPagination.qml" line="10"/>
<location filename="Qt6/imports/FluentUI/Controls/FluPagination.qml" line="9"/>
<source>&lt;Previous</source>
<translation type="unfinished">&lt;</translation>
</message>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluPagination.qml" line="9"/>
<location filename="Qt6/imports/FluentUI/Controls/FluPagination.qml" line="9"/>
<location filename="Qt5/imports/FluentUI/Controls/FluPagination.qml" line="11"/>
<location filename="Qt6/imports/FluentUI/Controls/FluPagination.qml" line="10"/>
<source>Next&gt;</source>
<translation type="unfinished">&gt;</translation>
</message>
@ -231,6 +231,11 @@
<source>Reset</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="Qt6/imports/FluentUI/Controls/FluShortcutPicker.qml" line="205"/>
<source>Conflict</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FluStatusLayout</name>
@ -349,10 +354,23 @@
<context>
<name>FluWindow</name>
<message>
<location filename="Qt5/imports/FluentUI/Controls/FluWindow.qml" line="335"/>
<location filename="Qt6/imports/FluentUI/Controls/FluWindow.qml" line="334"/>
<location filename="Qt5/imports/FluentUI/Controls/FluWindow.qml" line="347"/>
<location filename="Qt6/imports/FluentUI/Controls/FluWindow.qml" line="346"/>
<source>Loading...</source>
<translation type="unfinished">...</translation>
</message>
</context>
<context>
<name>QHotkey</name>
<message>
<location filename="qhotkey/qhotkey.cpp" line="294"/>
<source>Failed to register %1. Error: %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qhotkey/qhotkey.cpp" line="314"/>
<source>Failed to unregister %1. Error: %2</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

377
src/qhotkey/qhotkey.cpp Normal file
View File

@ -0,0 +1,377 @@
#include "qhotkey.h"
#include "qhotkey_p.h"
#include <QCoreApplication>
#include <QAbstractEventDispatcher>
#include <QMetaMethod>
#include <QThread>
#include <QDebug>
Q_LOGGING_CATEGORY(logQHotkey, "QHotkey")
void QHotkey::addGlobalMapping(const QKeySequence &shortcut, QHotkey::NativeShortcut nativeShortcut)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const int key = shortcut[0].toCombined();
#else
const int key = shortcut[0];
#endif
QMetaObject::invokeMethod(QHotkeyPrivate::instance(), "addMappingInvoked", Qt::QueuedConnection,
Q_ARG(Qt::Key, Qt::Key(key & ~Qt::KeyboardModifierMask)),
Q_ARG(Qt::KeyboardModifiers, Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)),
Q_ARG(QHotkey::NativeShortcut, nativeShortcut));
}
bool QHotkey::isPlatformSupported()
{
return QHotkeyPrivate::isPlatformSupported();
}
QHotkey::QHotkey(QObject *parent) :
QObject(parent),
_keyCode(Qt::Key_unknown),
_modifiers(Qt::NoModifier),
_registered(false)
{}
QHotkey::QHotkey(const QKeySequence &shortcut, bool autoRegister, QObject *parent) :
QHotkey(parent)
{
setShortcut(shortcut, autoRegister);
}
QHotkey::QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister, QObject *parent) :
QHotkey(parent)
{
setShortcut(keyCode, modifiers, autoRegister);
}
QHotkey::QHotkey(QHotkey::NativeShortcut shortcut, bool autoRegister, QObject *parent) :
QHotkey(parent)
{
setNativeShortcut(shortcut, autoRegister);
}
QHotkey::~QHotkey()
{
if(_registered)
QHotkeyPrivate::instance()->removeShortcut(this);
}
QKeySequence QHotkey::shortcut() const
{
if(_keyCode == Qt::Key_unknown)
return QKeySequence();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return QKeySequence((_keyCode | _modifiers).toCombined());
#else
return QKeySequence(static_cast<int>(_keyCode | _modifiers));
#endif
}
Qt::Key QHotkey::keyCode() const
{
return _keyCode;
}
Qt::KeyboardModifiers QHotkey::modifiers() const
{
return _modifiers;
}
QHotkey::NativeShortcut QHotkey::currentNativeShortcut() const
{
return _nativeShortcut;
}
bool QHotkey::isRegistered() const
{
return _registered;
}
bool QHotkey::setShortcut(const QKeySequence &shortcut, bool autoRegister)
{
if(shortcut.isEmpty())
return resetShortcut();
if(shortcut.count() > 1) {
qCWarning(logQHotkey, "Keysequences with multiple shortcuts are not allowed! "
"Only the first shortcut will be used!");
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const int key = shortcut[0].toCombined();
#else
const int key = shortcut[0];
#endif
return setShortcut(Qt::Key(key & ~Qt::KeyboardModifierMask),
Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask),
autoRegister);
}
bool QHotkey::setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister)
{
if(_registered) {
if(autoRegister) {
if(!QHotkeyPrivate::instance()->removeShortcut(this))
return false;
} else
return false;
}
if(keyCode == Qt::Key_unknown) {
_keyCode = Qt::Key_unknown;
_modifiers = Qt::NoModifier;
_nativeShortcut = NativeShortcut();
return true;
}
_keyCode = keyCode;
_modifiers = modifiers;
_nativeShortcut = QHotkeyPrivate::instance()->nativeShortcut(keyCode, modifiers);
if(_nativeShortcut.isValid()) {
if(autoRegister)
return QHotkeyPrivate::instance()->addShortcut(this);
return true;
}
qCWarning(logQHotkey) << "Unable to map shortcut to native keys. Key:" << keyCode << "Modifiers:" << modifiers;
_keyCode = Qt::Key_unknown;
_modifiers = Qt::NoModifier;
_nativeShortcut = NativeShortcut();
return false;
}
bool QHotkey::resetShortcut()
{
if(_registered &&
!QHotkeyPrivate::instance()->removeShortcut(this)) {
return false;
}
_keyCode = Qt::Key_unknown;
_modifiers = Qt::NoModifier;
_nativeShortcut = NativeShortcut();
return true;
}
bool QHotkey::setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister)
{
if(_registered) {
if(autoRegister) {
if(!QHotkeyPrivate::instance()->removeShortcut(this))
return false;
} else
return false;
}
if(nativeShortcut.isValid()) {
_keyCode = Qt::Key_unknown;
_modifiers = Qt::NoModifier;
_nativeShortcut = nativeShortcut;
if(autoRegister)
return QHotkeyPrivate::instance()->addShortcut(this);
return true;
}
_keyCode = Qt::Key_unknown;
_modifiers = Qt::NoModifier;
_nativeShortcut = NativeShortcut();
return true;
}
bool QHotkey::setRegistered(bool registered)
{
if(_registered && !registered)
return QHotkeyPrivate::instance()->removeShortcut(this);
if(!_registered && registered) {
if(!_nativeShortcut.isValid())
return false;
return QHotkeyPrivate::instance()->addShortcut(this);
}
return true;
}
// ---------- QHotkeyPrivate implementation ----------
QHotkeyPrivate::QHotkeyPrivate()
{
Q_ASSERT_X(qApp, Q_FUNC_INFO, "QHotkey requires QCoreApplication to be instantiated");
qApp->eventDispatcher()->installNativeEventFilter(this);
}
QHotkeyPrivate::~QHotkeyPrivate()
{
if(!shortcuts.isEmpty())
qCWarning(logQHotkey) << "QHotkeyPrivate destroyed with registered shortcuts!";
if(qApp && qApp->eventDispatcher())
qApp->eventDispatcher()->removeNativeEventFilter(this);
}
QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers)
{
Qt::ConnectionType conType = (QThread::currentThread() == thread() ?
Qt::DirectConnection :
Qt::BlockingQueuedConnection);
QHotkey::NativeShortcut res;
if(!QMetaObject::invokeMethod(this, "nativeShortcutInvoked", conType,
Q_RETURN_ARG(QHotkey::NativeShortcut, res),
Q_ARG(Qt::Key, keycode),
Q_ARG(Qt::KeyboardModifiers, modifiers))) {
return QHotkey::NativeShortcut();
}
return res;
}
bool QHotkeyPrivate::addShortcut(QHotkey *hotkey)
{
if(hotkey->_registered)
return false;
Qt::ConnectionType conType = (QThread::currentThread() == thread() ?
Qt::DirectConnection :
Qt::BlockingQueuedConnection);
bool res = false;
if(!QMetaObject::invokeMethod(this, "addShortcutInvoked", conType,
Q_RETURN_ARG(bool, res),
Q_ARG(QHotkey*, hotkey))) {
return false;
}
if(res)
emit hotkey->registeredChanged(true);
return res;
}
bool QHotkeyPrivate::removeShortcut(QHotkey *hotkey)
{
if(!hotkey->_registered)
return false;
Qt::ConnectionType conType = (QThread::currentThread() == thread() ?
Qt::DirectConnection :
Qt::BlockingQueuedConnection);
bool res = false;
if(!QMetaObject::invokeMethod(this, "removeShortcutInvoked", conType,
Q_RETURN_ARG(bool, res),
Q_ARG(QHotkey*, hotkey))) {
return false;
}
if(res)
emit hotkey->registeredChanged(false);
return res;
}
void QHotkeyPrivate::activateShortcut(QHotkey::NativeShortcut shortcut)
{
QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::activated);
for(QHotkey *hkey : shortcuts.values(shortcut))
signal.invoke(hkey, Qt::QueuedConnection);
}
void QHotkeyPrivate::releaseShortcut(QHotkey::NativeShortcut shortcut)
{
QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::released);
for(QHotkey *hkey : shortcuts.values(shortcut))
signal.invoke(hkey, Qt::QueuedConnection);
}
void QHotkeyPrivate::addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut)
{
mapping.insert({keycode, modifiers}, nativeShortcut);
}
bool QHotkeyPrivate::addShortcutInvoked(QHotkey *hotkey)
{
QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut;
if(!shortcuts.contains(shortcut)) {
if(!registerShortcut(shortcut)) {
qCWarning(logQHotkey) << QHotkey::tr("Failed to register %1. Error: %2").arg(hotkey->shortcut().toString(), error);
return false;
}
}
shortcuts.insert(shortcut, hotkey);
hotkey->_registered = true;
return true;
}
bool QHotkeyPrivate::removeShortcutInvoked(QHotkey *hotkey)
{
QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut;
if(shortcuts.remove(shortcut, hotkey) == 0)
return false;
hotkey->_registered = false;
emit hotkey->registeredChanged(true);
if(shortcuts.count(shortcut) == 0) {
if (!unregisterShortcut(shortcut)) {
qCWarning(logQHotkey) << QHotkey::tr("Failed to unregister %1. Error: %2").arg(hotkey->shortcut().toString(), error);
return false;
}
return true;
}
return true;
}
QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers)
{
if(mapping.contains({keycode, modifiers}))
return mapping.value({keycode, modifiers});
bool ok1 = false;
auto k = nativeKeycode(keycode, ok1);
bool ok2 = false;
auto m = nativeModifiers(modifiers, ok2);
if(ok1 && ok2)
return {k, m};
return {};
}
QHotkey::NativeShortcut::NativeShortcut() :
key(),
modifier(),
valid(false)
{}
QHotkey::NativeShortcut::NativeShortcut(quint32 key, quint32 modifier) :
key(key),
modifier(modifier),
valid(true)
{}
bool QHotkey::NativeShortcut::isValid() const
{
return valid;
}
bool QHotkey::NativeShortcut::operator ==(QHotkey::NativeShortcut other) const
{
return (key == other.key) &&
(modifier == other.modifier) &&
valid == other.valid;
}
bool QHotkey::NativeShortcut::operator !=(QHotkey::NativeShortcut other) const
{
return (key != other.key) ||
(modifier != other.modifier) ||
valid != other.valid;
}
QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key)
{
return qHash(key.key) ^ qHash(key.modifier);
}
QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed)
{
return qHash(key.key, seed) ^ qHash(key.modifier, seed);
}

130
src/qhotkey/qhotkey.h Normal file
View File

@ -0,0 +1,130 @@
#ifndef QHOTKEY_H
#define QHOTKEY_H
#include <QObject>
#include <QKeySequence>
#include <QPair>
#include <QLoggingCategory>
#ifdef QHOTKEY_SHARED
# ifdef QHOTKEY_LIBRARY
# define QHOTKEY_EXPORT Q_DECL_EXPORT
# else
# define QHOTKEY_EXPORT Q_DECL_IMPORT
# endif
#else
# define QHOTKEY_EXPORT
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#define QHOTKEY_HASH_SEED size_t
#else
#define QHOTKEY_HASH_SEED uint
#endif
//! A class to define global, systemwide Hotkeys
class QHOTKEY_EXPORT QHotkey : public QObject
{
Q_OBJECT
//! @private
friend class QHotkeyPrivate;
//! Specifies whether this hotkey is currently registered or not
Q_PROPERTY(bool registered READ isRegistered WRITE setRegistered NOTIFY registeredChanged)
//! Holds the shortcut this hotkey will be triggered on
Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut RESET resetShortcut)
public:
//! Defines shortcut with native keycodes
class QHOTKEY_EXPORT NativeShortcut {
public:
//! The native keycode
quint32 key;
//! The native modifiers
quint32 modifier;
//! Creates an invalid native shortcut
NativeShortcut();
//! Creates a valid native shortcut, with the given key and modifiers
NativeShortcut(quint32 key, quint32 modifier = 0);
//! Checks, whether this shortcut is valid or not
bool isValid() const;
//! Equality operator
bool operator ==(NativeShortcut other) const;
//! Inequality operator
bool operator !=(NativeShortcut other) const;
private:
bool valid;
};
//! Adds a global mapping of a key sequence to a replacement native shortcut
static void addGlobalMapping(const QKeySequence &shortcut, NativeShortcut nativeShortcut);
//! Checks if global shortcuts are supported by the current platform
static bool isPlatformSupported();
//! Default Constructor
explicit QHotkey(QObject *parent = nullptr);
//! Constructs a hotkey with a shortcut and optionally registers it
explicit QHotkey(const QKeySequence &shortcut, bool autoRegister = false, QObject *parent = nullptr);
//! Constructs a hotkey with a key and modifiers and optionally registers it
explicit QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false, QObject *parent = nullptr);
//! Constructs a hotkey from a native shortcut and optionally registers it
explicit QHotkey(NativeShortcut shortcut, bool autoRegister = false, QObject *parent = nullptr);
~QHotkey() override;
//! @readAcFn{QHotkey::registered}
bool isRegistered() const;
//! @readAcFn{QHotkey::shortcut}
QKeySequence shortcut() const;
//! @readAcFn{QHotkey::shortcut} - the key only
Qt::Key keyCode() const;
//! @readAcFn{QHotkey::shortcut} - the modifiers only
Qt::KeyboardModifiers modifiers() const;
//! Get the current native shortcut
NativeShortcut currentNativeShortcut() const;
public slots:
//! @writeAcFn{QHotkey::registered}
bool setRegistered(bool registered);
//! @writeAcFn{QHotkey::shortcut}
bool setShortcut(const QKeySequence &shortcut, bool autoRegister = false);
//! @writeAcFn{QHotkey::shortcut}
bool setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false);
//! @resetAcFn{QHotkey::shortcut}
bool resetShortcut();
//! Set this hotkey to a native shortcut
bool setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister = false);
signals:
//! Will be emitted if the shortcut is pressed
void activated(QPrivateSignal);
//! Will be emitted if the shortcut press is released
void released(QPrivateSignal);
//! @notifyAcFn{QHotkey::registered}
void registeredChanged(bool registered);
private:
Qt::Key _keyCode;
Qt::KeyboardModifiers _modifiers;
NativeShortcut _nativeShortcut;
bool _registered;
};
QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key);
QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed);
QHOTKEY_EXPORT Q_DECLARE_LOGGING_CATEGORY(logQHotkey)
Q_DECLARE_METATYPE(QHotkey::NativeShortcut)
#endif // QHOTKEY_H

291
src/qhotkey/qhotkey_mac.cpp Normal file
View File

@ -0,0 +1,291 @@
#include "qhotkey.h"
#include "qhotkey_p.h"
#include <Carbon/Carbon.h>
#include <QDebug>
class QHotkeyPrivateMac : public QHotkeyPrivate
{
public:
// QAbstractNativeEventFilter interface
bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override;
static OSStatus hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data);
static OSStatus hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data);
protected:
// QHotkeyPrivate interface
quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE;
quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE;
bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE;
bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE;
private:
static bool isHotkeyHandlerRegistered;
static QHash<QHotkey::NativeShortcut, EventHotKeyRef> hotkeyRefs;
};
NATIVE_INSTANCE(QHotkeyPrivateMac)
bool QHotkeyPrivate::isPlatformSupported()
{
return true;
}
bool QHotkeyPrivateMac::isHotkeyHandlerRegistered = false;
QHash<QHotkey::NativeShortcut, EventHotKeyRef> QHotkeyPrivateMac::hotkeyRefs;
bool QHotkeyPrivateMac::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result)
{
Q_UNUSED(eventType)
Q_UNUSED(message)
Q_UNUSED(result)
return false;
}
quint32 QHotkeyPrivateMac::nativeKeycode(Qt::Key keycode, bool &ok)
{
// Constants found in NSEvent.h from AppKit.framework
ok = true;
switch (keycode) {
case Qt::Key_Return:
return kVK_Return;
case Qt::Key_Enter:
return kVK_ANSI_KeypadEnter;
case Qt::Key_Tab:
return kVK_Tab;
case Qt::Key_Space:
return kVK_Space;
case Qt::Key_Backspace:
return kVK_Delete;
case Qt::Key_Escape:
return kVK_Escape;
case Qt::Key_CapsLock:
return kVK_CapsLock;
case Qt::Key_Option:
return kVK_Option;
case Qt::Key_F17:
return kVK_F17;
case Qt::Key_VolumeUp:
return kVK_VolumeUp;
case Qt::Key_VolumeDown:
return kVK_VolumeDown;
case Qt::Key_F18:
return kVK_F18;
case Qt::Key_F19:
return kVK_F19;
case Qt::Key_F20:
return kVK_F20;
case Qt::Key_F5:
return kVK_F5;
case Qt::Key_F6:
return kVK_F6;
case Qt::Key_F7:
return kVK_F7;
case Qt::Key_F3:
return kVK_F3;
case Qt::Key_F8:
return kVK_F8;
case Qt::Key_F9:
return kVK_F9;
case Qt::Key_F11:
return kVK_F11;
case Qt::Key_F13:
return kVK_F13;
case Qt::Key_F16:
return kVK_F16;
case Qt::Key_F14:
return kVK_F14;
case Qt::Key_F10:
return kVK_F10;
case Qt::Key_F12:
return kVK_F12;
case Qt::Key_F15:
return kVK_F15;
case Qt::Key_Help:
return kVK_Help;
case Qt::Key_Home:
return kVK_Home;
case Qt::Key_PageUp:
return kVK_PageUp;
case Qt::Key_Delete:
return kVK_ForwardDelete;
case Qt::Key_F4:
return kVK_F4;
case Qt::Key_End:
return kVK_End;
case Qt::Key_F2:
return kVK_F2;
case Qt::Key_PageDown:
return kVK_PageDown;
case Qt::Key_F1:
return kVK_F1;
case Qt::Key_Left:
return kVK_LeftArrow;
case Qt::Key_Right:
return kVK_RightArrow;
case Qt::Key_Down:
return kVK_DownArrow;
case Qt::Key_Up:
return kVK_UpArrow;
default:
ok = false;
break;
}
UTF16Char ch = keycode;
CFDataRef currentLayoutData;
TISInputSourceRef currentKeyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
if (currentKeyboard == NULL)
return 0;
currentLayoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
CFRelease(currentKeyboard);
if (currentLayoutData == NULL)
return 0;
UCKeyboardLayout* header = (UCKeyboardLayout*)CFDataGetBytePtr(currentLayoutData);
UCKeyboardTypeHeader* table = header->keyboardTypeList;
uint8_t *data = (uint8_t*)header;
for (quint32 i=0; i < header->keyboardTypeCount; i++) {
UCKeyStateRecordsIndex* stateRec = 0;
if (table[i].keyStateRecordsIndexOffset != 0) {
stateRec = reinterpret_cast<UCKeyStateRecordsIndex*>(data + table[i].keyStateRecordsIndexOffset);
if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat) stateRec = 0;
}
UCKeyToCharTableIndex* charTable = reinterpret_cast<UCKeyToCharTableIndex*>(data + table[i].keyToCharTableIndexOffset);
if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat) continue;
for (quint32 j=0; j < charTable->keyToCharTableCount; j++) {
UCKeyOutput* keyToChar = reinterpret_cast<UCKeyOutput*>(data + charTable->keyToCharTableOffsets[j]);
for (quint32 k=0; k < charTable->keyToCharTableSize; k++) {
if (keyToChar[k] & kUCKeyOutputTestForIndexMask) {
long idx = keyToChar[k] & kUCKeyOutputGetIndexMask;
if (stateRec && idx < stateRec->keyStateRecordCount) {
UCKeyStateRecord* rec = reinterpret_cast<UCKeyStateRecord*>(data + stateRec->keyStateRecordOffsets[idx]);
if (rec->stateZeroCharData == ch) {
ok = true;
return k;
}
}
}
else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) {
if (keyToChar[k] == ch) {
ok = true;
return k;
}
}
}
}
}
return 0;
}
quint32 QHotkeyPrivateMac::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok)
{
quint32 nMods = 0;
if (modifiers & Qt::ShiftModifier)
nMods |= shiftKey;
if (modifiers & Qt::ControlModifier)
nMods |= cmdKey;
if (modifiers & Qt::AltModifier)
nMods |= optionKey;
if (modifiers & Qt::MetaModifier)
nMods |= controlKey;
if (modifiers & Qt::KeypadModifier)
nMods |= kEventKeyModifierNumLockMask;
ok = true;
return nMods;
}
bool QHotkeyPrivateMac::registerShortcut(QHotkey::NativeShortcut shortcut)
{
if (!this->isHotkeyHandlerRegistered)
{
EventTypeSpec pressEventSpec;
pressEventSpec.eventClass = kEventClassKeyboard;
pressEventSpec.eventKind = kEventHotKeyPressed;
InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyPressEventHandler, 1, &pressEventSpec, NULL, NULL);
EventTypeSpec releaseEventSpec;
releaseEventSpec.eventClass = kEventClassKeyboard;
releaseEventSpec.eventKind = kEventHotKeyReleased;
InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyReleaseEventHandler, 1, &releaseEventSpec, NULL, NULL);
}
EventHotKeyID hkeyID;
hkeyID.signature = shortcut.key;
hkeyID.id = shortcut.modifier;
EventHotKeyRef eventRef = 0;
OSStatus status = RegisterEventHotKey(shortcut.key,
shortcut.modifier,
hkeyID,
GetApplicationEventTarget(),
0,
&eventRef);
if (status != noErr) {
error = QString::number(status);
return false;
} else {
this->hotkeyRefs.insert(shortcut, eventRef);
return true;
}
}
bool QHotkeyPrivateMac::unregisterShortcut(QHotkey::NativeShortcut shortcut)
{
EventHotKeyRef eventRef = QHotkeyPrivateMac::hotkeyRefs.value(shortcut);
OSStatus status = UnregisterEventHotKey(eventRef);
if (status != noErr) {
error = QString::number(status);
return false;
} else {
this->hotkeyRefs.remove(shortcut);
return true;
}
}
OSStatus QHotkeyPrivateMac::hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data)
{
Q_UNUSED(nextHandler);
Q_UNUSED(data);
if (GetEventClass(event) == kEventClassKeyboard &&
GetEventKind(event) == kEventHotKeyPressed) {
EventHotKeyID hkeyID;
GetEventParameter(event,
kEventParamDirectObject,
typeEventHotKeyID,
NULL,
sizeof(EventHotKeyID),
NULL,
&hkeyID);
hotkeyPrivate->activateShortcut({hkeyID.signature, hkeyID.id});
}
return noErr;
}
OSStatus QHotkeyPrivateMac::hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data)
{
Q_UNUSED(nextHandler);
Q_UNUSED(data);
if (GetEventClass(event) == kEventClassKeyboard &&
GetEventKind(event) == kEventHotKeyReleased) {
EventHotKeyID hkeyID;
GetEventParameter(event,
kEventParamDirectObject,
typeEventHotKeyID,
NULL,
sizeof(EventHotKeyID),
NULL,
&hkeyID);
hotkeyPrivate->releaseShortcut({hkeyID.signature, hkeyID.id});
}
return noErr;
}

62
src/qhotkey/qhotkey_p.h Normal file
View File

@ -0,0 +1,62 @@
#ifndef QHOTKEY_P_H
#define QHOTKEY_P_H
#include "qhotkey.h"
#include <QAbstractNativeEventFilter>
#include <QMultiHash>
#include <QMutex>
#include <QGlobalStatic>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#define _NATIVE_EVENT_RESULT qintptr
#else
#define _NATIVE_EVENT_RESULT long
#endif
class QHOTKEY_EXPORT QHotkeyPrivate : public QObject, public QAbstractNativeEventFilter
{
Q_OBJECT
public:
QHotkeyPrivate();//singleton!!!
~QHotkeyPrivate();
static QHotkeyPrivate *instance();
static bool isPlatformSupported();
QHotkey::NativeShortcut nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers);
bool addShortcut(QHotkey *hotkey);
bool removeShortcut(QHotkey *hotkey);
protected:
void activateShortcut(QHotkey::NativeShortcut shortcut);
void releaseShortcut(QHotkey::NativeShortcut shortcut);
virtual quint32 nativeKeycode(Qt::Key keycode, bool &ok) = 0;//platform implement
virtual quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) = 0;//platform implement
virtual bool registerShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement
virtual bool unregisterShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement
QString error;
private:
QHash<QPair<Qt::Key, Qt::KeyboardModifiers>, QHotkey::NativeShortcut> mapping;
QMultiHash<QHotkey::NativeShortcut, QHotkey*> shortcuts;
Q_INVOKABLE void addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut);
Q_INVOKABLE bool addShortcutInvoked(QHotkey *hotkey);
Q_INVOKABLE bool removeShortcutInvoked(QHotkey *hotkey);
Q_INVOKABLE QHotkey::NativeShortcut nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers);
};
#define NATIVE_INSTANCE(ClassName) \
Q_GLOBAL_STATIC(ClassName, hotkeyPrivate) \
\
QHotkeyPrivate *QHotkeyPrivate::instance()\
{\
return hotkeyPrivate;\
}
#endif // QHOTKEY_P_H

309
src/qhotkey/qhotkey_win.cpp Normal file
View File

@ -0,0 +1,309 @@
#include "qhotkey.h"
#include "qhotkey_p.h"
#include <qt_windows.h>
#include <algorithm>
#include <QDebug>
#include <QList>
#include <QTimer>
#define HKEY_ID(nativeShortcut) (((nativeShortcut.key ^ (nativeShortcut.modifier << 8)) & 0x0FFF) | 0x7000)
#if !defined(MOD_NOREPEAT)
#define MOD_NOREPEAT 0x4000
#endif
class QHotkeyPrivateWin : public QHotkeyPrivate
{
public:
QHotkeyPrivateWin();
// QAbstractNativeEventFilter interface
bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override;
protected:
void pollForHotkeyRelease();
// QHotkeyPrivate interface
quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE;
quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE;
bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE;
bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE;
private:
static QString formatWinError(DWORD winError);
QTimer pollTimer;
QList<QHotkey::NativeShortcut> polledShortcuts;
};
NATIVE_INSTANCE(QHotkeyPrivateWin)
QHotkeyPrivateWin::QHotkeyPrivateWin(){
pollTimer.setInterval(50);
connect(&pollTimer, &QTimer::timeout, this, &QHotkeyPrivateWin::pollForHotkeyRelease);
}
bool QHotkeyPrivate::isPlatformSupported()
{
return true;
}
bool QHotkeyPrivateWin::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result)
{
Q_UNUSED(eventType)
Q_UNUSED(result)
MSG* msg = static_cast<MSG*>(message);
if(msg->message == WM_HOTKEY) {
QHotkey::NativeShortcut shortcut = {HIWORD(msg->lParam), LOWORD(msg->lParam)};
this->activateShortcut(shortcut);
if (this->polledShortcuts.empty())
this->pollTimer.start();
this->polledShortcuts.append(shortcut);
}
return false;
}
void QHotkeyPrivateWin::pollForHotkeyRelease()
{
auto it = std::remove_if(this->polledShortcuts.begin(), this->polledShortcuts.end(), [this](const QHotkey::NativeShortcut &shortcut) {
bool pressed = (GetAsyncKeyState(shortcut.key) & (1 << 15)) != 0;
if (!pressed)
this->releaseShortcut(shortcut);
return !pressed;
});
this->polledShortcuts.erase(it, this->polledShortcuts.end());
if (this->polledShortcuts.empty())
this->pollTimer.stop();
}
quint32 QHotkeyPrivateWin::nativeKeycode(Qt::Key keycode, bool &ok)
{
ok = true;
if(keycode <= 0xFFFF) {//Try to obtain the key from it's "character"
const SHORT vKey = VkKeyScanW(static_cast<WCHAR>(keycode));
if(vKey > -1)
return LOBYTE(vKey);
}
//find key from switch/case --> Only finds a very small subset of keys
switch (keycode)
{
case Qt::Key_Escape:
return VK_ESCAPE;
case Qt::Key_Tab:
case Qt::Key_Backtab:
return VK_TAB;
case Qt::Key_Backspace:
return VK_BACK;
case Qt::Key_Return:
case Qt::Key_Enter:
return VK_RETURN;
case Qt::Key_Insert:
return VK_INSERT;
case Qt::Key_Delete:
return VK_DELETE;
case Qt::Key_Pause:
return VK_PAUSE;
case Qt::Key_Print:
return VK_PRINT;
case Qt::Key_Clear:
return VK_CLEAR;
case Qt::Key_Home:
return VK_HOME;
case Qt::Key_End:
return VK_END;
case Qt::Key_Left:
return VK_LEFT;
case Qt::Key_Up:
return VK_UP;
case Qt::Key_Right:
return VK_RIGHT;
case Qt::Key_Down:
return VK_DOWN;
case Qt::Key_PageUp:
return VK_PRIOR;
case Qt::Key_PageDown:
return VK_NEXT;
case Qt::Key_CapsLock:
return VK_CAPITAL;
case Qt::Key_NumLock:
return VK_NUMLOCK;
case Qt::Key_ScrollLock:
return VK_SCROLL;
case Qt::Key_F1:
return VK_F1;
case Qt::Key_F2:
return VK_F2;
case Qt::Key_F3:
return VK_F3;
case Qt::Key_F4:
return VK_F4;
case Qt::Key_F5:
return VK_F5;
case Qt::Key_F6:
return VK_F6;
case Qt::Key_F7:
return VK_F7;
case Qt::Key_F8:
return VK_F8;
case Qt::Key_F9:
return VK_F9;
case Qt::Key_F10:
return VK_F10;
case Qt::Key_F11:
return VK_F11;
case Qt::Key_F12:
return VK_F12;
case Qt::Key_F13:
return VK_F13;
case Qt::Key_F14:
return VK_F14;
case Qt::Key_F15:
return VK_F15;
case Qt::Key_F16:
return VK_F16;
case Qt::Key_F17:
return VK_F17;
case Qt::Key_F18:
return VK_F18;
case Qt::Key_F19:
return VK_F19;
case Qt::Key_F20:
return VK_F20;
case Qt::Key_F21:
return VK_F21;
case Qt::Key_F22:
return VK_F22;
case Qt::Key_F23:
return VK_F23;
case Qt::Key_F24:
return VK_F24;
case Qt::Key_Menu:
return VK_APPS;
case Qt::Key_Help:
return VK_HELP;
case Qt::Key_MediaNext:
return VK_MEDIA_NEXT_TRACK;
case Qt::Key_MediaPrevious:
return VK_MEDIA_PREV_TRACK;
case Qt::Key_MediaPlay:
return VK_MEDIA_PLAY_PAUSE;
case Qt::Key_MediaStop:
return VK_MEDIA_STOP;
case Qt::Key_VolumeDown:
return VK_VOLUME_DOWN;
case Qt::Key_VolumeUp:
return VK_VOLUME_UP;
case Qt::Key_VolumeMute:
return VK_VOLUME_MUTE;
case Qt::Key_Mode_switch:
return VK_MODECHANGE;
case Qt::Key_Select:
return VK_SELECT;
case Qt::Key_Printer:
return VK_PRINT;
case Qt::Key_Execute:
return VK_EXECUTE;
case Qt::Key_Sleep:
return VK_SLEEP;
case Qt::Key_Period:
return VK_DECIMAL;
case Qt::Key_Play:
return VK_PLAY;
case Qt::Key_Cancel:
return VK_CANCEL;
case Qt::Key_Forward:
return VK_BROWSER_FORWARD;
case Qt::Key_Refresh:
return VK_BROWSER_REFRESH;
case Qt::Key_Stop:
return VK_BROWSER_STOP;
case Qt::Key_Search:
return VK_BROWSER_SEARCH;
case Qt::Key_Favorites:
return VK_BROWSER_FAVORITES;
case Qt::Key_HomePage:
return VK_BROWSER_HOME;
case Qt::Key_LaunchMail:
return VK_LAUNCH_MAIL;
case Qt::Key_LaunchMedia:
return VK_LAUNCH_MEDIA_SELECT;
case Qt::Key_Launch0:
return VK_LAUNCH_APP1;
case Qt::Key_Launch1:
return VK_LAUNCH_APP2;
case Qt::Key_Massyo:
return VK_OEM_FJ_MASSHOU;
case Qt::Key_Touroku:
return VK_OEM_FJ_TOUROKU;
default:
if(keycode <= 0xFFFF)
return static_cast<BYTE>(keycode);
else {
ok = false;
return 0;
}
}
}
quint32 QHotkeyPrivateWin::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok)
{
quint32 nMods = 0;
if (modifiers & Qt::ShiftModifier)
nMods |= MOD_SHIFT;
if (modifiers & Qt::ControlModifier)
nMods |= MOD_CONTROL;
if (modifiers & Qt::AltModifier)
nMods |= MOD_ALT;
if (modifiers & Qt::MetaModifier)
nMods |= MOD_WIN;
ok = true;
return nMods;
}
bool QHotkeyPrivateWin::registerShortcut(QHotkey::NativeShortcut shortcut)
{
BOOL ok = RegisterHotKey(NULL,
HKEY_ID(shortcut),
shortcut.modifier + MOD_NOREPEAT,
shortcut.key);
if(ok)
return true;
else {
error = QHotkeyPrivateWin::formatWinError(::GetLastError());
return false;
}
}
bool QHotkeyPrivateWin::unregisterShortcut(QHotkey::NativeShortcut shortcut)
{
BOOL ok = UnregisterHotKey(NULL, HKEY_ID(shortcut));
if(ok)
return true;
else {
error = QHotkeyPrivateWin::formatWinError(::GetLastError());
return false;
}
}
QString QHotkeyPrivateWin::formatWinError(DWORD winError)
{
wchar_t *buffer = NULL;
DWORD num = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
winError,
0,
(LPWSTR)&buffer,
0,
NULL);
if(buffer) {
QString res = QString::fromWCharArray(buffer, num);
LocalFree(buffer);
return res;
} else
return QString();
}

268
src/qhotkey/qhotkey_x11.cpp Normal file
View File

@ -0,0 +1,268 @@
#include "qhotkey.h"
#include "qhotkey_p.h"
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
#include <QGuiApplication>
#else
#include <QDebug>
#include <QX11Info>
#endif
#include <QThreadStorage>
#include <QTimer>
#include <X11/Xlib.h>
#include <xcb/xcb.h>
//compatibility to pre Qt 5.8
#ifndef Q_FALLTHROUGH
#define Q_FALLTHROUGH() (void)0
#endif
class QHotkeyPrivateX11 : public QHotkeyPrivate
{
public:
// QAbstractNativeEventFilter interface
bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override;
protected:
// QHotkeyPrivate interface
quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE;
quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE;
static QString getX11String(Qt::Key keycode);
bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE;
bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE;
private:
static const QVector<quint32> specialModifiers;
static const quint32 validModsMask;
xcb_key_press_event_t prevHandledEvent;
xcb_key_press_event_t prevEvent;
static QString formatX11Error(Display *display, int errorCode);
class HotkeyErrorHandler {
public:
HotkeyErrorHandler();
~HotkeyErrorHandler();
static bool hasError;
static QString errorString;
private:
XErrorHandler prevHandler;
static int handleError(Display *display, XErrorEvent *error);
};
};
NATIVE_INSTANCE(QHotkeyPrivateX11)
bool QHotkeyPrivate::isPlatformSupported()
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
return qGuiApp->nativeInterface<QNativeInterface::QX11Application>();
#else
return QX11Info::isPlatformX11();
#endif
}
const QVector<quint32> QHotkeyPrivateX11::specialModifiers = {0, Mod2Mask, LockMask, (Mod2Mask | LockMask)};
const quint32 QHotkeyPrivateX11::validModsMask = ShiftMask | ControlMask | Mod1Mask | Mod4Mask;
bool QHotkeyPrivateX11::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result)
{
Q_UNUSED(eventType)
Q_UNUSED(result)
auto *genericEvent = static_cast<xcb_generic_event_t *>(message);
if (genericEvent->response_type == XCB_KEY_PRESS) {
xcb_key_press_event_t keyEvent = *static_cast<xcb_key_press_event_t *>(message);
this->prevEvent = keyEvent;
if (this->prevHandledEvent.response_type == XCB_KEY_RELEASE) {
if(this->prevHandledEvent.time == keyEvent.time) return false;
}
this->activateShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask});
} else if (genericEvent->response_type == XCB_KEY_RELEASE) {
xcb_key_release_event_t keyEvent = *static_cast<xcb_key_release_event_t *>(message);
this->prevEvent = keyEvent;
QTimer::singleShot(50, [this, keyEvent] {
if(this->prevEvent.time == keyEvent.time && this->prevEvent.response_type == keyEvent.response_type && this->prevEvent.detail == keyEvent.detail){
this->releaseShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask});
}
});
this->prevHandledEvent = keyEvent;
}
return false;
}
QString QHotkeyPrivateX11::getX11String(Qt::Key keycode)
{
switch(keycode){
case Qt::Key_MediaLast :
case Qt::Key_MediaPrevious :
return QStringLiteral("XF86AudioPrev");
case Qt::Key_MediaNext :
return QStringLiteral("XF86AudioNext");
case Qt::Key_MediaPause :
case Qt::Key_MediaPlay :
case Qt::Key_MediaTogglePlayPause :
return QStringLiteral("XF86AudioPlay");
case Qt::Key_MediaRecord :
return QStringLiteral("XF86AudioRecord");
case Qt::Key_MediaStop :
return QStringLiteral("XF86AudioStop");
default :
return QKeySequence(keycode).toString(QKeySequence::NativeText);
}
}
quint32 QHotkeyPrivateX11::nativeKeycode(Qt::Key keycode, bool &ok)
{
QString keyString = getX11String(keycode);
KeySym keysym = XStringToKeysym(keyString.toLatin1().constData());
if (keysym == NoSymbol) {
//not found -> just use the key
if(keycode <= 0xFFFF)
keysym = keycode;
else
return 0;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface<QNativeInterface::QX11Application>();
Display *display = x11Interface->display();
#else
const bool x11Interface = QX11Info::isPlatformX11();
Display *display = QX11Info::display();
#endif
if(x11Interface) {
auto res = XKeysymToKeycode(display, keysym);
if(res != 0)
ok = true;
return res;
}
return 0;
}
quint32 QHotkeyPrivateX11::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok)
{
quint32 nMods = 0;
if (modifiers & Qt::ShiftModifier)
nMods |= ShiftMask;
if (modifiers & Qt::ControlModifier)
nMods |= ControlMask;
if (modifiers & Qt::AltModifier)
nMods |= Mod1Mask;
if (modifiers & Qt::MetaModifier)
nMods |= Mod4Mask;
ok = true;
return nMods;
}
bool QHotkeyPrivateX11::registerShortcut(QHotkey::NativeShortcut shortcut)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface<QNativeInterface::QX11Application>();
Display *display = x11Interface->display();
#else
const bool x11Interface = QX11Info::isPlatformX11();
Display *display = QX11Info::display();
#endif
if(!display || !x11Interface)
return false;
HotkeyErrorHandler errorHandler;
for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) {
XGrabKey(display,
shortcut.key,
shortcut.modifier | specialMod,
DefaultRootWindow(display),
True,
GrabModeAsync,
GrabModeAsync);
}
XSync(display, False);
if(errorHandler.hasError) {
error = errorHandler.errorString;
this->unregisterShortcut(shortcut);
return false;
}
return true;
}
bool QHotkeyPrivateX11::unregisterShortcut(QHotkey::NativeShortcut shortcut)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
Display *display = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display();
#else
Display *display = QX11Info::display();
#endif
if(!display)
return false;
HotkeyErrorHandler errorHandler;
for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) {
XUngrabKey(display,
shortcut.key,
shortcut.modifier | specialMod,
XDefaultRootWindow(display));
}
XSync(display, False);
if(HotkeyErrorHandler::hasError) {
error = HotkeyErrorHandler::errorString;
return false;
}
return true;
}
QString QHotkeyPrivateX11::formatX11Error(Display *display, int errorCode)
{
char errStr[256];
XGetErrorText(display, errorCode, errStr, 256);
return QString::fromLatin1(errStr);
}
// ---------- QHotkeyPrivateX11::HotkeyErrorHandler implementation ----------
bool QHotkeyPrivateX11::HotkeyErrorHandler::hasError = false;
QString QHotkeyPrivateX11::HotkeyErrorHandler::errorString;
QHotkeyPrivateX11::HotkeyErrorHandler::HotkeyErrorHandler()
{
prevHandler = XSetErrorHandler(&HotkeyErrorHandler::handleError);
}
QHotkeyPrivateX11::HotkeyErrorHandler::~HotkeyErrorHandler()
{
XSetErrorHandler(prevHandler);
hasError = false;
errorString.clear();
}
int QHotkeyPrivateX11::HotkeyErrorHandler::handleError(Display *display, XErrorEvent *error)
{
switch (error->error_code) {
case BadAccess:
case BadValue:
case BadWindow:
if (error->request_code == 33 || //grab key
error->request_code == 34) {// ungrab key
hasError = true;
errorString = QHotkeyPrivateX11::formatX11Error(display, error->error_code);
return 1;
}
Q_FALLTHROUGH();
// fall through
default:
return 0;
}
}