-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
globalaccel.cpp
254 lines (226 loc) · 10.3 KB
/
globalaccel.cpp
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <[email protected]>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "globalaccel.h"
#include <KKeyServer>
#include <netwm.h>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingReply>
#include <QKeyEvent>
#include <QRegularExpression>
#include "x11info.h"
#include <X11/keysym.h>
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
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|Turn Off Screen|Sleep|Hibernate"))},
{QStringLiteral("/component/KDE_Keyboard_Layout_Switcher"),
QRegularExpression(QStringLiteral("Switch to Next 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"))},
};
static uint g_keyModMaskXAccel = 0;
static uint g_keyModMaskXOnOrOff = 0;
static void calculateGrabMasks()
{
g_keyModMaskXAccel = KKeyServer::accelModMaskX();
g_keyModMaskXOnOrOff = KKeyServer::modXLock() | KKeyServer::modXNumLock() | KKeyServer::modXScrollLock() | KKeyServer::modXModeSwitch();
}
GlobalAccel::GlobalAccel(QObject *parent)
: QObject(parent)
{
}
void GlobalAccel::prepare()
{
// recursion check
if (m_updatingInformation) {
return;
}
// first ensure that we don't have some left over
release();
if (X11Info::isPlatformX11()) {
m_keySymbols = xcb_key_symbols_alloc(X11Info::connection());
calculateGrabMasks();
}
// 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();
if (m_keySymbols) {
xcb_key_symbols_free(m_keySymbols);
m_keySymbols = nullptr;
}
}
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);
return true;
}
}
}
return false;
}
bool GlobalAccel::checkKeyPress(xcb_key_press_event_t *event)
{
if (!m_keySymbols) {
return false;
}
// based and inspired from code in kglobalaccel_x11.cpp
xcb_keycode_t keyCodeX = event->detail;
uint16_t keyModX = event->state & (g_keyModMaskXAccel | KKeyServer::MODE_SWITCH);
xcb_keysym_t keySymX = xcb_key_press_lookup_keysym(m_keySymbols, event, 0);
// If numlock is active and a keypad key is pressed, XOR the SHIFT state.
// e.g., KP_4 => Shift+KP_Left, and Shift+KP_4 => KP_Left.
if (event->state & KKeyServer::modXNumLock()) {
xcb_keysym_t sym = xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 0);
// If this is a keypad key,
if (sym >= XK_KP_Space && sym <= XK_KP_9) {
switch (sym) {
// Leave the following keys unaltered
// FIXME: The proper solution is to see which keysyms don't change when shifted.
case XK_KP_Multiply:
case XK_KP_Add:
case XK_KP_Subtract:
case XK_KP_Divide:
break;
default:
keyModX ^= KKeyServer::modXShift();
}
}
}
int keyCodeQt;
KKeyServer::symXModXToKeyQt(keySymX, keyModX, &keyCodeQt);
// Split keycode and modifier
int keyModQt = keyCodeQt & Qt::KeyboardModifierMask;
keyCodeQt &= ~Qt::KeyboardModifierMask;
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);
return true;
}
}
}
return false;
}
#include "moc_globalaccel.cpp"