-
-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathglobalaccel.cpp
More file actions
171 lines (155 loc) · 7.43 KB
/
Copy pathglobalaccel.cpp
File metadata and controls
171 lines (155 loc) · 7.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "globalaccel.h"
#include <KKeyServer>
#include <netwm.h>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusMetaType>
#include <QDBusPendingReply>
#include <QKeyEvent>
#include <QRegularExpression>
static const QString s_kglobalAccelService = QStringLiteral("org.kde.kglobalaccel");
static const QString s_componentInterface = QStringLiteral("org.kde.kglobalaccel.Component");
/**
* Whitelist of the components which are allowed to get global shortcuts.
* The DBus path of the component is the key for the whitelist.
* The value for each key contains a regular expression matching unique shortcut names which are allowed.
* This allows to not only restrict on component, but also restrict on the shortcuts.
* E.g. plasmashell might accept media shortcuts, but not shortcuts for switching the activity.
**/
static const QMap<QString, QRegularExpression> s_shortcutWhitelist{
{QStringLiteral("/component/mediacontrol"), QRegularExpression(QStringLiteral("stopmedia|nextmedia|previousmedia|playpausemedia"))},
{QStringLiteral("/component/kmix"), QRegularExpression(QStringLiteral("mute|decrease_volume|increase_volume"))},
{QStringLiteral("/component/org_kde_powerdevil"),
QRegularExpression(QStringLiteral("Increase Screen Brightness|Decrease Screen Brightness|Increase Keyboard Brightness|Decrease Keyboard Brightness|Toggle "
"Keyboard Backlight|Turn Off Screen|Sleep|Hibernate"))},
{QStringLiteral("/component/KDE_Keyboard_Layout_Switcher"),
QRegularExpression(QStringLiteral("Switch to Next Keyboard Layout|Switch to Last-Used Keyboard Layout|Switch keyboard layout to .*"))},
{QStringLiteral("/component/kcm_touchpad"), QRegularExpression(QStringLiteral("Toggle Touchpad|Enable Touchpad|Disable Touchpad"))},
{QStringLiteral("/component/kwin"), QRegularExpression(QStringLiteral("view_zoom_in|view_zoom_out|view_actual_size"))},
};
GlobalAccel::GlobalAccel(QObject *parent)
: QObject(parent)
{
qDBusRegisterMetaType<KGlobalShortcutInfo>();
qDBusRegisterMetaType<QList<KGlobalShortcutInfo>>();
}
void GlobalAccel::prepare()
{
// recursion check
if (m_updatingInformation) {
return;
}
// first ensure that we don't have some left over
release();
// fetch all components from KGlobalAccel
m_updatingInformation++;
auto message = QDBusMessage::createMethodCall(s_kglobalAccelService,
QStringLiteral("/kglobalaccel"),
QStringLiteral("org.kde.KGlobalAccel"),
QStringLiteral("allComponents"));
QDBusPendingReply<QList<QDBusObjectPath>> async = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, &GlobalAccel::components);
}
void GlobalAccel::components(QDBusPendingCallWatcher *self)
{
QDBusPendingReply<QList<QDBusObjectPath>> reply = *self;
self->deleteLater();
if (!reply.isValid()) {
m_updatingInformation--;
return;
}
// go through all components, check whether they are in our whitelist
// if they are whitelisted we check whether they are active
for (const auto &path : reply.value()) {
const QString objectPath = path.path();
bool whitelisted = false;
for (const auto &[key, value] : s_shortcutWhitelist.asKeyValueRange()) {
if (objectPath == key) {
whitelisted = true;
break;
}
}
if (!whitelisted) {
continue;
}
auto message = QDBusMessage::createMethodCall(s_kglobalAccelService, objectPath, s_componentInterface, QStringLiteral("isActive"));
QDBusPendingReply<bool> async = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
m_updatingInformation++;
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this, objectPath](QDBusPendingCallWatcher *self) {
QDBusPendingReply<bool> reply = *self;
self->deleteLater();
// filter out inactive components
if (!reply.isValid() || !reply.value()) {
m_updatingInformation--;
return;
}
// active, whitelisted component: get all shortcuts
auto message = QDBusMessage::createMethodCall(s_kglobalAccelService, objectPath, s_componentInterface, QStringLiteral("allShortcutInfos"));
QDBusPendingReply<QList<KGlobalShortcutInfo>> async = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this, objectPath](QDBusPendingCallWatcher *self) {
m_updatingInformation--;
QDBusPendingReply<QList<KGlobalShortcutInfo>> reply = *self;
self->deleteLater();
if (!reply.isValid()) {
return;
}
// restrict to whitelist
QList<KGlobalShortcutInfo> infos;
auto whitelist = s_shortcutWhitelist.constFind(objectPath);
if (whitelist == s_shortcutWhitelist.constEnd()) {
// this should not happen, just for safety
return;
}
const auto shortcuts = reply.value();
for (const auto &shortcut : shortcuts) {
auto matches = whitelist.value().match(shortcut.uniqueName());
if (matches.hasMatch()) {
infos.append(shortcut);
}
}
m_shortcuts.insert(objectPath, infos);
});
});
}
m_updatingInformation--;
}
void GlobalAccel::release()
{
m_shortcuts.clear();
}
bool GlobalAccel::keyEvent(QKeyEvent *event)
{
const int keyCodeQt = event->key();
Qt::KeyboardModifiers keyModQt = event->modifiers();
if (keyModQt & Qt::SHIFT && !KKeyServer::isShiftAsModifierAllowed(keyCodeQt)) {
keyModQt &= ~Qt::SHIFT;
}
if ((keyModQt == 0 || keyModQt == Qt::SHIFT) && (keyCodeQt >= Qt::Key_Space && keyCodeQt <= Qt::Key_AsciiTilde)) {
// security check: we don't allow shortcuts without modifier for "normal" keys
// this is to prevent a malicious application to grab shortcuts for all keys
// and by that being able to read out the keyboard
return false;
}
const QKeySequence seq(keyCodeQt | keyModQt);
// let's check whether we have a mapping shortcut
for (const auto &[key, value] : std::as_const(m_shortcuts).asKeyValueRange()) {
for (const auto &info : value) {
if (info.keys().contains(seq)) {
auto signal = QDBusMessage::createMethodCall(s_kglobalAccelService, key, s_componentInterface, QStringLiteral("invokeShortcut"));
signal.setArguments(QList<QVariant>{QVariant(info.uniqueName())});
QDBusConnection::sessionBus().asyncCall(signal);
event->setAccepted(true);
return true;
}
}
}
return false;
}
#include "moc_globalaccel.cpp"