File indexing completed on 2024-11-10 04:56:57
0001 /* 0002 SPDX-FileCopyrightText: 2022 David Redondo <kde@david-redono.de> 0003 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "buttonrebindsfilter.h" 0009 #include "buttonrebinds_debug.h" 0010 0011 #include "cursor.h" 0012 #include "input_event.h" 0013 #include "keyboard_input.h" 0014 #include "xkb.h" 0015 0016 #include <KKeyServer> 0017 0018 #include <QMetaEnum> 0019 0020 #include <linux/input-event-codes.h> 0021 0022 #include <array> 0023 #include <optional> 0024 #include <utility> 0025 0026 // Tells us that we are already in a binding event 0027 class RebindScope 0028 { 0029 static uint s_scopes; 0030 0031 public: 0032 RebindScope() 0033 { 0034 s_scopes++; 0035 } 0036 ~RebindScope() 0037 { 0038 Q_ASSERT(s_scopes > 0); 0039 s_scopes--; 0040 } 0041 Q_DISABLE_COPY_MOVE(RebindScope) 0042 static bool isRebinding() 0043 { 0044 return s_scopes > 0; 0045 } 0046 }; 0047 uint RebindScope::s_scopes = 0; 0048 0049 quint32 qHash(const Trigger &t) 0050 { 0051 return qHash(t.device) * (t.button + 1); 0052 } 0053 0054 QString InputDevice::name() const 0055 { 0056 return QStringLiteral("Button rebinding device"); 0057 } 0058 0059 QString InputDevice::sysName() const 0060 { 0061 return {}; 0062 } 0063 0064 KWin::LEDs InputDevice::leds() const 0065 { 0066 return {}; 0067 } 0068 0069 void InputDevice::setLeds(KWin::LEDs leds) 0070 { 0071 } 0072 0073 void InputDevice::setEnabled(bool enabled) 0074 { 0075 } 0076 0077 bool InputDevice::isEnabled() const 0078 { 0079 return true; 0080 } 0081 0082 bool InputDevice::isAlphaNumericKeyboard() const 0083 { 0084 return true; 0085 } 0086 0087 bool InputDevice::isKeyboard() const 0088 { 0089 return true; 0090 } 0091 0092 bool InputDevice::isLidSwitch() const 0093 { 0094 return false; 0095 } 0096 0097 bool InputDevice::isPointer() const 0098 { 0099 return false; 0100 } 0101 0102 bool InputDevice::isTabletModeSwitch() const 0103 { 0104 return false; 0105 } 0106 0107 bool InputDevice::isTabletPad() const 0108 { 0109 return false; 0110 } 0111 0112 bool InputDevice::isTabletTool() const 0113 { 0114 return false; 0115 } 0116 0117 bool InputDevice::isTouch() const 0118 { 0119 return false; 0120 } 0121 0122 bool InputDevice::isTouchpad() const 0123 { 0124 return false; 0125 } 0126 0127 ButtonRebindsFilter::ButtonRebindsFilter() 0128 : m_configWatcher(KConfigWatcher::create(KSharedConfig::openConfig("kcminputrc"))) 0129 { 0130 KWin::input()->addInputDevice(&m_inputDevice); 0131 const QLatin1String groupName("ButtonRebinds"); 0132 connect(m_configWatcher.get(), &KConfigWatcher::configChanged, this, [this, groupName](const KConfigGroup &group) { 0133 if (group.parent().name() == groupName) { 0134 loadConfig(group.parent()); 0135 } else if (group.parent().parent().name() == groupName) { 0136 loadConfig(group.parent().parent()); 0137 } 0138 }); 0139 loadConfig(m_configWatcher->config()->group(groupName)); 0140 } 0141 0142 void ButtonRebindsFilter::loadConfig(const KConfigGroup &group) 0143 { 0144 Q_ASSERT(QLatin1String("ButtonRebinds") == group.name()); 0145 KWin::input()->uninstallInputEventFilter(this); 0146 for (auto &action : m_actions) { 0147 action.clear(); 0148 } 0149 0150 bool foundActions = false; 0151 const auto mouseButtonEnum = QMetaEnum::fromType<Qt::MouseButtons>(); 0152 const auto mouseGroup = group.group(QStringLiteral("Mouse")); 0153 static constexpr auto maximumQtExtraButton = 24; 0154 for (int i = 1; i <= maximumQtExtraButton; ++i) { 0155 const QByteArray buttonName = QByteArray("ExtraButton") + QByteArray::number(i); 0156 if (mouseGroup.hasKey(buttonName.constData())) { 0157 const auto entry = mouseGroup.readEntry(buttonName.constData(), QStringList()); 0158 const auto button = static_cast<quint32>(mouseButtonEnum.keyToValue(buttonName)); 0159 insert(Pointer, {QString(), button}, entry); 0160 foundActions = true; 0161 } 0162 } 0163 0164 const auto tabletsGroup = group.group(QStringLiteral("Tablet")); 0165 const auto tablets = tabletsGroup.groupList(); 0166 for (const auto &tabletName : tablets) { 0167 const auto tabletGroup = tabletsGroup.group(tabletName); 0168 const auto tabletButtons = tabletGroup.keyList(); 0169 for (const auto &buttonName : tabletButtons) { 0170 const auto entry = tabletGroup.readEntry(buttonName, QStringList()); 0171 bool ok = false; 0172 const uint button = buttonName.toUInt(&ok); 0173 if (ok) { 0174 foundActions = true; 0175 insert(TabletPad, {tabletName, button}, entry); 0176 } 0177 } 0178 } 0179 0180 const auto tabletToolsGroup = group.group(QStringLiteral("TabletTool")); 0181 const auto tabletTools = tabletToolsGroup.groupList(); 0182 for (const auto &tabletToolName : tabletTools) { 0183 const auto toolGroup = tabletToolsGroup.group(tabletToolName); 0184 const auto tabletToolButtons = toolGroup.keyList(); 0185 for (const auto &buttonName : tabletToolButtons) { 0186 const auto entry = toolGroup.readEntry(buttonName, QStringList()); 0187 bool ok = false; 0188 const uint button = buttonName.toUInt(&ok); 0189 if (ok) { 0190 foundActions = true; 0191 insert(TabletToolButtonType, {tabletToolName, button}, entry); 0192 } 0193 } 0194 } 0195 0196 if (foundActions) { 0197 KWin::input()->prependInputEventFilter(this); 0198 } 0199 } 0200 0201 bool ButtonRebindsFilter::pointerEvent(KWin::MouseEvent *event, quint32 nativeButton) 0202 { 0203 if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease) { 0204 return false; 0205 } 0206 if (RebindScope::isRebinding()) { 0207 return false; 0208 } 0209 0210 return send(Pointer, {{}, event->button()}, event->type() == QEvent::MouseButtonPress, event->timestamp()); 0211 } 0212 0213 bool ButtonRebindsFilter::tabletPadButtonEvent(uint button, bool pressed, const KWin::TabletPadId &tabletPadId, std::chrono::microseconds time) 0214 { 0215 if (RebindScope::isRebinding()) { 0216 return false; 0217 } 0218 return send(TabletPad, {tabletPadId.name, button}, pressed, time); 0219 } 0220 0221 bool ButtonRebindsFilter::tabletToolButtonEvent(uint button, bool pressed, const KWin::TabletToolId &tabletToolId, std::chrono::microseconds time) 0222 { 0223 if (RebindScope::isRebinding()) { 0224 return false; 0225 } 0226 m_tabletTool = tabletToolId; 0227 return send(TabletToolButtonType, {tabletToolId.m_name, button}, pressed, time); 0228 } 0229 0230 void ButtonRebindsFilter::insert(TriggerType type, const Trigger &trigger, const QStringList &entry) 0231 { 0232 if (entry.size() != 2) { 0233 qCWarning(KWIN_BUTTONREBINDS) << "Failed to rebind to" << entry; 0234 return; 0235 } 0236 if (entry.first() == QLatin1String("Key")) { 0237 const auto keys = QKeySequence::fromString(entry.at(1), QKeySequence::PortableText); 0238 if (!keys.isEmpty()) { 0239 m_actions.at(type).insert(trigger, keys); 0240 } 0241 } else if (entry.first() == QLatin1String("MouseButton")) { 0242 bool ok = false; 0243 const MouseButton mb{entry.last().toUInt(&ok)}; 0244 if (ok) { 0245 m_actions.at(type).insert(trigger, mb); 0246 } else { 0247 qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << entry << "into a mouse button"; 0248 } 0249 } else if (entry.first() == QLatin1String("TabletToolButton")) { 0250 bool ok = false; 0251 const TabletToolButton tb{entry.last().toUInt(&ok)}; 0252 if (ok) { 0253 m_actions.at(type).insert(trigger, tb); 0254 } else { 0255 qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << entry << "into a mouse button"; 0256 } 0257 } 0258 } 0259 0260 bool ButtonRebindsFilter::send(TriggerType type, const Trigger &trigger, bool pressed, std::chrono::microseconds timestamp) 0261 { 0262 const auto &typeActions = m_actions.at(type); 0263 if (typeActions.isEmpty()) { 0264 return false; 0265 } 0266 0267 const auto &action = typeActions[trigger]; 0268 if (const QKeySequence *seq = std::get_if<QKeySequence>(&action)) { 0269 return sendKeySequence(*seq, pressed, timestamp); 0270 } 0271 if (const auto mb = std::get_if<MouseButton>(&action)) { 0272 return sendMouseButton(mb->button, pressed, timestamp); 0273 } 0274 if (const auto tb = std::get_if<TabletToolButton>(&action)) { 0275 return sendTabletToolButton(tb->button, pressed, timestamp); 0276 } 0277 return false; 0278 } 0279 0280 static constexpr std::array<std::pair<int, int>, 4> s_modifierKeyTable = { 0281 std::pair(Qt::Key_Control, KEY_LEFTCTRL), 0282 std::pair(Qt::Key_Alt, KEY_LEFTALT), 0283 std::pair(Qt::Key_Shift, KEY_LEFTSHIFT), 0284 std::pair(Qt::Key_Meta, KEY_LEFTMETA), 0285 }; 0286 0287 bool ButtonRebindsFilter::sendKeySequence(const QKeySequence &keys, bool pressed, std::chrono::microseconds time) 0288 { 0289 if (keys.isEmpty()) { 0290 return false; 0291 } 0292 0293 const auto &key = keys[0]; 0294 auto sendKey = [this, pressed, time](xkb_keycode_t key) { 0295 auto state = pressed ? KWin::InputRedirection::KeyboardKeyPressed : KWin::InputRedirection::KeyboardKeyReleased; 0296 Q_EMIT m_inputDevice.keyChanged(key, state, time, &m_inputDevice); 0297 }; 0298 0299 // handle modifier-only keys 0300 for (const auto &[keySymQt, keySymLinux] : s_modifierKeyTable) { 0301 if (key == keySymQt) { 0302 RebindScope scope; 0303 sendKey(keySymLinux); 0304 return true; 0305 } 0306 } 0307 0308 const QList<int> syms(KKeyServer::keyQtToSymXs(keys[0])); 0309 if (syms.empty()) { 0310 qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << keys << "to keysym"; 0311 return false; 0312 } 0313 // KKeyServer returns upper case syms, lower it to not confuse modifiers handling 0314 std::optional<int> keyCode; 0315 for (int sym : syms) { 0316 auto code = KWin::input()->keyboard()->xkb()->keycodeFromKeysym(sym); 0317 if (code) { 0318 keyCode = code; 0319 break; 0320 } 0321 } 0322 if (!keyCode) { 0323 qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << keys << "syms: " << syms << "to keycode"; 0324 return false; 0325 } 0326 0327 RebindScope scope; 0328 0329 if (key & Qt::ShiftModifier) { 0330 sendKey(KEY_LEFTSHIFT); 0331 } 0332 if (key & Qt::ControlModifier) { 0333 sendKey(KEY_LEFTCTRL); 0334 } 0335 if (key & Qt::AltModifier) { 0336 sendKey(KEY_LEFTALT); 0337 } 0338 if (key & Qt::MetaModifier) { 0339 sendKey(KEY_LEFTMETA); 0340 } 0341 0342 sendKey(keyCode.value()); 0343 return true; 0344 } 0345 0346 bool ButtonRebindsFilter::sendMouseButton(quint32 button, bool pressed, std::chrono::microseconds time) 0347 { 0348 RebindScope scope; 0349 Q_EMIT m_inputDevice.pointerButtonChanged(button, KWin::InputRedirection::PointerButtonState(pressed), time, &m_inputDevice); 0350 return true; 0351 } 0352 0353 bool ButtonRebindsFilter::sendTabletToolButton(quint32 button, bool pressed, std::chrono::microseconds time) 0354 { 0355 if (!m_tabletTool) { 0356 return false; 0357 } 0358 RebindScope scope; 0359 Q_EMIT m_inputDevice.tabletToolButtonEvent(button, pressed, *m_tabletTool, time); 0360 return true; 0361 } 0362 0363 #include "moc_buttonrebindsfilter.cpp"