KWin
Loading...
Searching...
No Matches
buttonrebindsfilter.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2022 David Redondo <kde@david-redono.de>
3 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6*/
7
9#include "buttonrebinds_debug.h"
10
11#include "cursor.h"
12#include "input_event.h"
13#include "keyboard_input.h"
14#include "xkb.h"
15
16#include <KKeyServer>
17
18#include <QMetaEnum>
19
20#include <linux/input-event-codes.h>
21
22#include <array>
23#include <optional>
24#include <utility>
25
26// Tells us that we are already in a binding event
28{
29 static uint s_scopes;
30
31public:
33 {
34 s_scopes++;
35 }
37 {
38 Q_ASSERT(s_scopes > 0);
39 s_scopes--;
40 }
41 Q_DISABLE_COPY_MOVE(RebindScope)
42 static bool isRebinding()
43 {
44 return s_scopes > 0;
45 }
46};
47uint RebindScope::s_scopes = 0;
48
49quint32 qHash(const Trigger &t)
50{
51 return qHash(t.device) * (t.button + 1);
52}
53
54QString InputDevice::name() const
55{
56 return QStringLiteral("Button rebinding device");
57}
58
59QString InputDevice::sysName() const
60{
61 return {};
62}
63
64KWin::LEDs InputDevice::leds() const
65{
66 return {};
67}
68
69void InputDevice::setLeds(KWin::LEDs leds)
70{
71}
72
73void InputDevice::setEnabled(bool enabled)
74{
75}
76
77bool InputDevice::isEnabled() const
78{
79 return true;
80}
81
82bool InputDevice::isKeyboard() const
83{
84 return true;
85}
86
87bool InputDevice::isLidSwitch() const
88{
89 return false;
90}
91
92bool InputDevice::isPointer() const
93{
94 return false;
95}
96
97bool InputDevice::isTabletModeSwitch() const
98{
99 return false;
100}
101
102bool InputDevice::isTabletPad() const
103{
104 return false;
105}
106
107bool InputDevice::isTabletTool() const
108{
109 return false;
110}
111
112bool InputDevice::isTouch() const
113{
114 return false;
115}
116
117bool InputDevice::isTouchpad() const
118{
119 return false;
120}
121
123 : m_configWatcher(KConfigWatcher::create(KSharedConfig::openConfig("kcminputrc")))
124{
125 KWin::input()->addInputDevice(&m_inputDevice);
126 const QLatin1String groupName("ButtonRebinds");
127 connect(m_configWatcher.get(), &KConfigWatcher::configChanged, this, [this, groupName](const KConfigGroup &group) {
128 if (group.parent().name() == groupName) {
129 loadConfig(group.parent());
130 } else if (group.parent().parent().name() == groupName) {
131 loadConfig(group.parent().parent());
132 }
133 });
134 loadConfig(m_configWatcher->config()->group(groupName));
135}
136
137void ButtonRebindsFilter::loadConfig(const KConfigGroup &group)
138{
139 Q_ASSERT(QLatin1String("ButtonRebinds") == group.name());
141 for (auto &action : m_actions) {
142 action.clear();
143 }
144
145 bool foundActions = false;
146 const auto mouseButtonEnum = QMetaEnum::fromType<Qt::MouseButtons>();
147 const auto mouseGroup = group.group(QStringLiteral("Mouse"));
148 static constexpr auto maximumQtExtraButton = 24;
149 for (int i = 1; i <= maximumQtExtraButton; ++i) {
150 const QByteArray buttonName = QByteArray("ExtraButton") + QByteArray::number(i);
151 if (mouseGroup.hasKey(buttonName.constData())) {
152 const auto entry = mouseGroup.readEntry(buttonName.constData(), QStringList());
153 const auto button = static_cast<quint32>(mouseButtonEnum.keyToValue(buttonName));
154 insert(Pointer, {QString(), button}, entry);
155 foundActions = true;
156 }
157 }
158
159 const auto tabletsGroup = group.group(QStringLiteral("Tablet"));
160 const auto tablets = tabletsGroup.groupList();
161 for (const auto &tabletName : tablets) {
162 const auto tabletGroup = tabletsGroup.group(tabletName);
163 const auto tabletButtons = tabletGroup.keyList();
164 for (const auto &buttonName : tabletButtons) {
165 const auto entry = tabletGroup.readEntry(buttonName, QStringList());
166 bool ok = false;
167 const uint button = buttonName.toUInt(&ok);
168 if (ok) {
169 foundActions = true;
170 insert(TabletPad, {tabletName, button}, entry);
171 }
172 }
173 }
174
175 const auto tabletToolsGroup = group.group(QStringLiteral("TabletTool"));
176 const auto tabletTools = tabletToolsGroup.groupList();
177 for (const auto &tabletToolName : tabletTools) {
178 const auto toolGroup = tabletToolsGroup.group(tabletToolName);
179 const auto tabletToolButtons = toolGroup.keyList();
180 for (const auto &buttonName : tabletToolButtons) {
181 const auto entry = toolGroup.readEntry(buttonName, QStringList());
182 bool ok = false;
183 const uint button = buttonName.toUInt(&ok);
184 if (ok) {
185 foundActions = true;
186 insert(TabletToolButtonType, {tabletToolName, button}, entry);
187 }
188 }
189 }
190
191 if (foundActions) {
193 }
194}
195
196bool ButtonRebindsFilter::pointerEvent(KWin::MouseEvent *event, quint32 nativeButton)
197{
198 if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease) {
199 return false;
200 }
202 return false;
203 }
204
205 return send(Pointer, {{}, event->button()}, event->type() == QEvent::MouseButtonPress, event->timestamp());
206}
207
208bool ButtonRebindsFilter::tabletPadButtonEvent(uint button, bool pressed, const KWin::TabletPadId &tabletPadId, std::chrono::microseconds time)
209{
211 return false;
212 }
213 return send(TabletPad, {tabletPadId.name, button}, pressed, time);
214}
215
216bool ButtonRebindsFilter::tabletToolButtonEvent(uint button, bool pressed, const KWin::TabletToolId &tabletToolId, std::chrono::microseconds time)
217{
219 return false;
220 }
221 m_tabletTool = tabletToolId;
222 return send(TabletToolButtonType, {tabletToolId.m_name, button}, pressed, time);
223}
224
225void ButtonRebindsFilter::insert(TriggerType type, const Trigger &trigger, const QStringList &entry)
226{
227 if (entry.size() != 2) {
228 qCWarning(KWIN_BUTTONREBINDS) << "Failed to rebind to" << entry;
229 return;
230 }
231 if (entry.first() == QLatin1String("Key")) {
232 const auto keys = QKeySequence::fromString(entry.at(1), QKeySequence::PortableText);
233 if (!keys.isEmpty()) {
234 m_actions.at(type).insert(trigger, keys);
235 }
236 } else if (entry.first() == QLatin1String("MouseButton")) {
237 bool ok = false;
238 const MouseButton mb{entry.last().toUInt(&ok)};
239 if (ok) {
240 m_actions.at(type).insert(trigger, mb);
241 } else {
242 qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << entry << "into a mouse button";
243 }
244 } else if (entry.first() == QLatin1String("TabletToolButton")) {
245 bool ok = false;
246 const TabletToolButton tb{entry.last().toUInt(&ok)};
247 if (ok) {
248 m_actions.at(type).insert(trigger, tb);
249 } else {
250 qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << entry << "into a mouse button";
251 }
252 }
253}
254
255bool ButtonRebindsFilter::send(TriggerType type, const Trigger &trigger, bool pressed, std::chrono::microseconds timestamp)
256{
257 const auto &typeActions = m_actions.at(type);
258 if (typeActions.isEmpty()) {
259 return false;
260 }
261
262 const auto &action = typeActions[trigger];
263 if (const QKeySequence *seq = std::get_if<QKeySequence>(&action)) {
264 return sendKeySequence(*seq, pressed, timestamp);
265 }
266 if (const auto mb = std::get_if<MouseButton>(&action)) {
267 return sendMouseButton(mb->button, pressed, timestamp);
268 }
269 if (const auto tb = std::get_if<TabletToolButton>(&action)) {
270 return sendTabletToolButton(tb->button, pressed, timestamp);
271 }
272 return false;
273}
274
275static constexpr std::array<std::pair<int, int>, 4> s_modifierKeyTable = {
276 std::pair(Qt::Key_Control, KEY_LEFTCTRL),
277 std::pair(Qt::Key_Alt, KEY_LEFTALT),
278 std::pair(Qt::Key_Shift, KEY_LEFTSHIFT),
279 std::pair(Qt::Key_Meta, KEY_LEFTMETA),
280};
281
282bool ButtonRebindsFilter::sendKeySequence(const QKeySequence &keys, bool pressed, std::chrono::microseconds time)
283{
284 if (keys.isEmpty()) {
285 return false;
286 }
287
288 const auto &key = keys[0];
289 auto sendKey = [this, pressed, time](xkb_keycode_t key) {
291 Q_EMIT m_inputDevice.keyChanged(key, state, time, &m_inputDevice);
292 };
293
294 // handle modifier-only keys
295 for (const auto &[keySymQt, keySymLinux] : s_modifierKeyTable) {
296 if (key == keySymQt) {
297 RebindScope scope;
298 sendKey(keySymLinux);
299 return true;
300 }
301 }
302
303 const QList<int> syms(KKeyServer::keyQtToSymXs(keys[0]));
304 if (syms.empty()) {
305 qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << keys << "to keysym";
306 return false;
307 }
308 // KKeyServer returns upper case syms, lower it to not confuse modifiers handling
309 std::optional<int> keyCode;
310 for (int sym : syms) {
311 auto code = KWin::input()->keyboard()->xkb()->keycodeFromKeysym(sym);
312 if (code) {
313 keyCode = code;
314 break;
315 }
316 }
317 if (!keyCode) {
318 qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << keys << "syms: " << syms << "to keycode";
319 return false;
320 }
321
322 RebindScope scope;
323
324 if (key & Qt::ShiftModifier) {
325 sendKey(KEY_LEFTSHIFT);
326 }
327 if (key & Qt::ControlModifier) {
328 sendKey(KEY_LEFTCTRL);
329 }
330 if (key & Qt::AltModifier) {
331 sendKey(KEY_LEFTALT);
332 }
333 if (key & Qt::MetaModifier) {
334 sendKey(KEY_LEFTMETA);
335 }
336
337 sendKey(keyCode.value());
338 return true;
339}
340
341bool ButtonRebindsFilter::sendMouseButton(quint32 button, bool pressed, std::chrono::microseconds time)
342{
343 RebindScope scope;
344 Q_EMIT m_inputDevice.pointerButtonChanged(button, KWin::InputRedirection::PointerButtonState(pressed), time, &m_inputDevice);
345 return true;
346}
347
348bool ButtonRebindsFilter::sendTabletToolButton(quint32 button, bool pressed, std::chrono::microseconds time)
349{
350 if (!m_tabletTool) {
351 return false;
352 }
353 RebindScope scope;
354 Q_EMIT m_inputDevice.tabletToolButtonEvent(button, pressed, *m_tabletTool, time);
355 return true;
356}
357
358#include "moc_buttonrebindsfilter.cpp"
quint32 qHash(const Trigger &t)
bool tabletToolButtonEvent(uint button, bool pressed, const KWin::TabletToolId &tabletToolId, std::chrono::microseconds time) override
bool pointerEvent(KWin::MouseEvent *event, quint32 nativeButton) override
bool tabletPadButtonEvent(uint button, bool pressed, const KWin::TabletPadId &tabletPadId, std::chrono::microseconds time) override
void pointerButtonChanged(quint32 button, InputRedirection::PointerButtonState state, std::chrono::microseconds time, InputDevice *device)
void tabletToolButtonEvent(uint button, bool isPressed, const TabletToolId &tabletToolId, std::chrono::microseconds time)
void keyChanged(quint32 key, InputRedirection::KeyboardKeyState, std::chrono::microseconds time, InputDevice *device)
KeyboardInputRedirection * keyboard() const
Definition input.h:216
void addInputDevice(InputDevice *device)
Definition input.cpp:2995
void uninstallInputEventFilter(InputEventFilter *filter)
Definition input.cpp:2684
void prependInputEventFilter(InputEventFilter *filter)
Definition input.cpp:2678
std::chrono::microseconds timestamp() const
Definition input_event.h:38
const QString name
std::optional< int > keycodeFromKeysym(xkb_keysym_t keysym)
Definition xkb.cpp:750
static bool isRebinding()
InputRedirection * input()
Definition input.h:549