Allow rebinding of extra mouse buttons
Some mice have more than the three standard buttons. While some applications can use a subset of those (mostly the backwards and forwards buttons) in many cases pressing them will do nothing. This makes it possible to assign key combinations to buttons that will cause synthetic key event when pressed.
This commit is contained in:
parent
80d28499e1
commit
bc792a2bc8
6 changed files with 313 additions and 0 deletions
|
@ -5,6 +5,7 @@ add_subdirectory(windowsystem)
|
|||
add_subdirectory(kpackage)
|
||||
add_subdirectory(nightcolor)
|
||||
add_subdirectory(colord-integration)
|
||||
add_subdirectory(buttonrebinds)
|
||||
if (KWIN_BUILD_DECORATIONS)
|
||||
add_subdirectory(kdecorations)
|
||||
endif()
|
||||
|
|
18
src/plugins/buttonrebinds/CMakeLists.txt
Normal file
18
src/plugins/buttonrebinds/CMakeLists.txt
Normal file
|
@ -0,0 +1,18 @@
|
|||
# SPDX-FileCopyrightText: 2022 David Redondo <kde@david-redono.de>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
kcoreaddons_add_plugin(MouseButtonToKeyPlugin INSTALL_NAMESPACE "kwin/plugins")
|
||||
|
||||
ecm_qt_declare_logging_category(MouseButtonToKeyPlugin
|
||||
HEADER buttonrebinds_debug.h
|
||||
IDENTIFIER KWIN_BUTTONREBINDS
|
||||
CATEGORY_NAME kwin_buttonrebinds
|
||||
DEFAULT_SEVERITY Warning
|
||||
)
|
||||
|
||||
target_sources(MouseButtonToKeyPlugin PRIVATE
|
||||
main.cpp
|
||||
buttonrebindsfilter.cpp
|
||||
)
|
||||
target_link_libraries(MouseButtonToKeyPlugin PRIVATE kwin KF5::WindowSystem)
|
||||
|
200
src/plugins/buttonrebinds/buttonrebindsfilter.cpp
Normal file
200
src/plugins/buttonrebinds/buttonrebindsfilter.cpp
Normal file
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2022 David Redondo <kde@david-redono.de>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
#include "buttonrebindsfilter.h"
|
||||
#include "buttonrebinds_debug.h"
|
||||
|
||||
#include "input_event.h"
|
||||
#include "keyboard_input.h"
|
||||
|
||||
#include <KKeyServer>
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
#include <linux/input-event-codes.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
QString InputDevice::name() const
|
||||
{
|
||||
return QStringLiteral("Button rebinding device");
|
||||
}
|
||||
|
||||
QString InputDevice::sysName() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
KWin::LEDs InputDevice::leds() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void InputDevice::setLeds(KWin::LEDs leds)
|
||||
{
|
||||
Q_UNUSED(leds)
|
||||
}
|
||||
|
||||
void InputDevice::setEnabled(bool enabled)
|
||||
{
|
||||
Q_UNUSED(enabled)
|
||||
}
|
||||
|
||||
bool InputDevice::isEnabled() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputDevice::isAlphaNumericKeyboard() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputDevice::isKeyboard() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputDevice::isLidSwitch() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputDevice::isPointer() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputDevice::isTabletModeSwitch() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputDevice::isTabletPad() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputDevice::isTabletTool() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputDevice::isTouch() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputDevice::isTouchpad() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::optional<int> keycodeFromKeysym(xkb_keysym_t keysym)
|
||||
{
|
||||
auto xkb = KWin::input()->keyboard()->xkb();
|
||||
auto layout = xkb_state_serialize_layout(xkb->state(), XKB_STATE_LAYOUT_EFFECTIVE);
|
||||
const xkb_keycode_t max = xkb_keymap_max_keycode(xkb->keymap());
|
||||
for (xkb_keycode_t keycode = xkb_keymap_min_keycode(xkb->keymap()); keycode < max; keycode++) {
|
||||
uint levelCount = xkb_keymap_num_levels_for_key(xkb->keymap(), keycode, layout);
|
||||
for (uint currentLevel = 0; currentLevel < levelCount; currentLevel++) {
|
||||
const xkb_keysym_t *syms;
|
||||
uint num_syms = xkb_keymap_key_get_syms_by_level(xkb->keymap(), keycode, layout, currentLevel, &syms);
|
||||
for (uint sym = 0; sym < num_syms; sym++) {
|
||||
if (syms[sym] == keysym) {
|
||||
return {keycode - 8};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ButtonRebindsFilter::ButtonRebindsFilter()
|
||||
: KWin::Plugin()
|
||||
, KWin::InputEventFilter()
|
||||
, m_configWatcher(KConfigWatcher::create(KSharedConfig::openConfig("kcminputrc")))
|
||||
{
|
||||
KWin::input()->addInputDevice(&m_inputDevice);
|
||||
const QLatin1String groupName("ButtonRebinds");
|
||||
connect(m_configWatcher.get(), &KConfigWatcher::configChanged, this, [this, groupName](const KConfigGroup &group) {
|
||||
if (group.parent().name() != groupName) {
|
||||
return;
|
||||
}
|
||||
loadConfig(group.parent());
|
||||
});
|
||||
loadConfig(m_configWatcher->config()->group(groupName));
|
||||
}
|
||||
|
||||
void ButtonRebindsFilter::loadConfig(const KConfigGroup &group)
|
||||
{
|
||||
KWin::input()->uninstallInputEventFilter(this);
|
||||
m_mouseMapping.clear();
|
||||
auto mouseButtonEnum = QMetaEnum::fromType<Qt::MouseButtons>();
|
||||
auto mouseGroup = group.group("Mouse");
|
||||
for (int i = 1; i <= 24; ++i) {
|
||||
const QByteArray buttonName = QByteArray("ExtraButton") + QByteArray::number(i);
|
||||
auto entry = mouseGroup.readEntry(buttonName.constData(), QStringList());
|
||||
if (entry.size() == 2 && entry.first() == QLatin1String("Key")) {
|
||||
auto keys = QKeySequence::fromString(entry.at(1), QKeySequence::PortableText);
|
||||
if (!keys.isEmpty()) {
|
||||
m_mouseMapping.insert(static_cast<Qt::MouseButton>(mouseButtonEnum.keyToValue(buttonName)), keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_mouseMapping.size() != 0) {
|
||||
KWin::input()->prependInputEventFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool ButtonRebindsFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton)
|
||||
{
|
||||
Q_UNUSED(nativeButton);
|
||||
|
||||
if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QKeySequence keys = m_mouseMapping.value(event->button());
|
||||
if (keys.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const auto &key = keys[0];
|
||||
|
||||
int sym;
|
||||
if (!KKeyServer::keyQtToSymX(keys[0], &sym)) {
|
||||
qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << keys << "to keysym";
|
||||
return false;
|
||||
}
|
||||
// KKeyServer returns upper case syms, lower it to not confuse modifiers handling
|
||||
auto keyCode = keycodeFromKeysym(sym);
|
||||
if (!keyCode) {
|
||||
qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << keys << "sym: " << sym << "to keycode";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sendKey = [this, event](xkb_keycode_t key) {
|
||||
auto state = event->type() == QEvent::MouseButtonPress ? KWin::InputRedirection::KeyboardKeyPressed : KWin::InputRedirection::KeyboardKeyReleased;
|
||||
Q_EMIT m_inputDevice.keyChanged(key, state, event->timestamp(), &m_inputDevice);
|
||||
};
|
||||
|
||||
if (key & Qt::ShiftModifier) {
|
||||
sendKey(KEY_LEFTSHIFT);
|
||||
}
|
||||
if (key & Qt::ControlModifier) {
|
||||
sendKey(KEY_LEFTCTRL);
|
||||
}
|
||||
if (key & Qt::AltModifier) {
|
||||
sendKey(KEY_LEFTALT);
|
||||
}
|
||||
if (key & Qt::MetaModifier) {
|
||||
sendKey(XKB_KEY_Super_L);
|
||||
}
|
||||
|
||||
sendKey(keyCode.value());
|
||||
|
||||
return true;
|
||||
}
|
49
src/plugins/buttonrebinds/buttonrebindsfilter.h
Normal file
49
src/plugins/buttonrebinds/buttonrebindsfilter.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2022 David Redondo <kde@david-redono.de>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "plugin.h"
|
||||
|
||||
#include "input.h"
|
||||
#include "inputdevice.h"
|
||||
|
||||
class InputDevice : public KWin::InputDevice
|
||||
{
|
||||
QString sysName() const override;
|
||||
QString name() const override;
|
||||
|
||||
bool isEnabled() const override;
|
||||
void setEnabled(bool enabled) override;
|
||||
|
||||
void setLeds(KWin::LEDs leds) override;
|
||||
KWin::LEDs leds() const override;
|
||||
|
||||
bool isKeyboard() const override;
|
||||
bool isAlphaNumericKeyboard() const override;
|
||||
bool isPointer() const override;
|
||||
bool isTouchpad() const override;
|
||||
bool isTouch() const override;
|
||||
bool isTabletTool() const override;
|
||||
bool isTabletPad() const override;
|
||||
bool isTabletModeSwitch() const override;
|
||||
bool isLidSwitch() const override;
|
||||
};
|
||||
|
||||
class ButtonRebindsFilter : public KWin::Plugin, public KWin::InputEventFilter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ButtonRebindsFilter();
|
||||
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override;
|
||||
|
||||
private:
|
||||
void loadConfig(const KConfigGroup &group);
|
||||
|
||||
InputDevice m_inputDevice;
|
||||
QMap<Qt::MouseButton, QKeySequence> m_mouseMapping;
|
||||
KConfigWatcher::Ptr m_configWatcher;
|
||||
};
|
39
src/plugins/buttonrebinds/main.cpp
Normal file
39
src/plugins/buttonrebinds/main.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2022 David Redondo <kde@david-redono.de>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
#include <main.h>
|
||||
#include <plugin.h>
|
||||
|
||||
#include "buttonrebindsfilter.h"
|
||||
|
||||
class KWIN_EXPORT ButtonRebindsFactory : public KWin::PluginFactory
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID PluginFactory_iid FILE "metadata.json")
|
||||
Q_INTERFACES(KWin::PluginFactory)
|
||||
|
||||
public:
|
||||
explicit ButtonRebindsFactory()
|
||||
: PluginFactory()
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<KWin::Plugin> create() const override
|
||||
{
|
||||
switch (KWin::kwinApp()->operationMode()) {
|
||||
case KWin::Application::OperationModeXwayland:
|
||||
[[fallthrough]];
|
||||
case KWin::Application::OperationModeWaylandOnly:
|
||||
return std::make_unique<ButtonRebindsFilter>();
|
||||
case KWin::Application::OperationModeX11:
|
||||
[[fallthrough]];
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include "main.moc"
|
6
src/plugins/buttonrebinds/metadata.json
Normal file
6
src/plugins/buttonrebinds/metadata.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"KPlugin": {
|
||||
"EnabledByDefault": true,
|
||||
"Id": "kwin5_plugin_buttonrebinds"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue