Add kwin full source tree, greeter login, zsh, pcid service, and build system improvements

This commit is contained in:
2026-04-26 22:31:07 +01:00
parent d4a6b356eb
commit 70a84cefee
3416 changed files with 1360518 additions and 10522 deletions
@@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2022 David Redondo <kde@david-redono.de>
# SPDX-License-Identifier: BSD-3-Clause
kcoreaddons_add_plugin(buttonsrebind INSTALL_NAMESPACE "kwin/plugins")
ecm_qt_declare_logging_category(buttonsrebind
HEADER buttonrebinds_debug.h
IDENTIFIER KWIN_BUTTONREBINDS
CATEGORY_NAME kwin_buttonrebinds
DEFAULT_SEVERITY Warning
)
target_sources(buttonsrebind PRIVATE
main.cpp
buttonrebindsfilter.cpp
)
target_link_libraries(buttonsrebind PRIVATE kwin Qt::GuiPrivate XKB::XKB)
@@ -0,0 +1,476 @@
/*
SPDX-FileCopyrightText: 2022 David Redondo <kde@david-redono.de>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
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 "cursor.h"
#include "input_event.h"
#include "keyboard_input.h"
#include "xkb.h"
#include <QMetaEnum>
#include <linux/input-event-codes.h>
#include <array>
#include <optional>
#include <utility>
#include <private/qxkbcommon_p.h>
// Tells us that we are already in a binding event
class RebindScope
{
static uint s_scopes;
public:
RebindScope()
{
s_scopes++;
}
~RebindScope()
{
Q_ASSERT(s_scopes > 0);
s_scopes--;
}
Q_DISABLE_COPY_MOVE(RebindScope)
static bool isRebinding()
{
return s_scopes > 0;
}
};
uint RebindScope::s_scopes = 0;
quint32 qHash(const Trigger &t)
{
return qHash(t.device) * (t.button + 1);
}
QString InputDevice::name() const
{
return QStringLiteral("Button rebinding device");
}
void InputDevice::setEnabled(bool enabled)
{
}
bool InputDevice::isEnabled() const
{
return true;
}
bool InputDevice::isKeyboard() const
{
return true;
}
bool InputDevice::isLidSwitch() const
{
return false;
}
bool InputDevice::isPointer() const
{
return true;
}
bool InputDevice::isTabletModeSwitch() const
{
return false;
}
bool InputDevice::isTabletPad() const
{
return true;
}
bool InputDevice::isTabletTool() const
{
return true;
}
bool InputDevice::isTouch() const
{
return false;
}
bool InputDevice::isTouchpad() const
{
return false;
}
ButtonRebindsFilter::ButtonRebindsFilter()
: KWin::InputEventFilter(KWin::InputFilterOrder::ButtonRebind)
, m_configWatcher(KConfigWatcher::create(KSharedConfig::openConfig("kcminputrc")))
{
const QLatin1String groupName("ButtonRebinds");
connect(m_configWatcher.get(), &KConfigWatcher::configChanged, this, [this, groupName](const KConfigGroup &group) {
if (group.parent().name() == groupName) {
loadConfig(group.parent());
} else if (group.parent().parent().name() == groupName) {
loadConfig(group.parent().parent());
}
});
loadConfig(m_configWatcher->config()->group(groupName));
}
ButtonRebindsFilter::~ButtonRebindsFilter()
{
// on shutdown, input is destroyed before this filter
if (KWin::input() && m_inputDevice) {
KWin::input()->removeInputDevice(m_inputDevice.get());
}
}
void ButtonRebindsFilter::loadConfig(const KConfigGroup &group)
{
Q_ASSERT(QLatin1String("ButtonRebinds") == group.name());
if (m_inputDevice) {
KWin::input()->removeInputDevice(m_inputDevice.get());
m_inputDevice.reset();
}
KWin::input()->uninstallInputEventFilter(this);
for (auto &action : m_actions) {
action.clear();
}
bool foundActions = false;
const auto mouseButtonEnum = QMetaEnum::fromType<Qt::MouseButtons>();
const auto mouseGroup = group.group(QStringLiteral("Mouse"));
const auto mouseGroupKeys = mouseGroup.keyList();
for (const QString &configKey : mouseGroupKeys) {
const int mappedButton = mouseButtonEnum.keyToValue(configKey.toLatin1());
if (mappedButton != -1) {
const auto action = mouseGroup.readEntry(configKey, QStringList());
insert(Pointer, {QString(), static_cast<uint>(mappedButton)}, action);
foundActions = true;
}
}
const auto tabletsGroup = group.group(QStringLiteral("Tablet"));
const auto tablets = tabletsGroup.groupList();
for (const auto &tabletName : tablets) {
const auto tabletGroup = tabletsGroup.group(tabletName);
const auto tabletButtons = tabletGroup.keyList();
for (const auto &buttonName : tabletButtons) {
const auto entry = tabletGroup.readEntry(buttonName, QStringList());
bool ok = false;
const uint button = buttonName.toUInt(&ok);
if (ok) {
foundActions = true;
insert(TabletPad, {tabletName, button}, entry);
}
}
}
const auto tabletToolsGroup = group.group(QStringLiteral("TabletTool"));
const auto tabletTools = tabletToolsGroup.groupList();
for (const auto &tabletToolName : tabletTools) {
const auto toolGroup = tabletToolsGroup.group(tabletToolName);
const auto tabletToolButtons = toolGroup.keyList();
for (const auto &buttonName : tabletToolButtons) {
const auto entry = toolGroup.readEntry(buttonName, QStringList());
bool ok = false;
const uint button = buttonName.toUInt(&ok);
if (ok) {
foundActions = true;
insert(TabletToolButtonType, {tabletToolName, button}, entry);
}
}
}
if (foundActions) {
KWin::input()->installInputEventFilter(this);
m_inputDevice = std::make_unique<InputDevice>();
KWin::input()->addInputDevice(m_inputDevice.get());
}
}
bool ButtonRebindsFilter::pointerButton(KWin::PointerButtonEvent *event)
{
if (RebindScope::isRebinding()) {
return false;
}
return send(Pointer, {{}, event->button}, event->state == KWin::PointerButtonState::Pressed, event->timestamp);
}
bool ButtonRebindsFilter::tabletToolProximityEvent(KWin::TabletEvent *event)
{
if (RebindScope::isRebinding()) {
return false;
}
m_tabletCursorPos = event->position();
return false;
}
bool ButtonRebindsFilter::tabletToolAxisEvent(KWin::TabletEvent *event)
{
if (RebindScope::isRebinding()) {
return false;
}
m_tabletCursorPos = event->position();
return false;
}
bool ButtonRebindsFilter::tabletToolTipEvent(KWin::TabletEvent *event)
{
if (RebindScope::isRebinding()) {
return false;
}
m_tabletCursorPos = event->position();
return false;
}
bool ButtonRebindsFilter::tabletPadButtonEvent(KWin::TabletPadButtonEvent *event)
{
if (RebindScope::isRebinding()) {
return false;
}
return send(TabletPad, {event->device->name(), event->button}, event->pressed, event->time);
}
bool ButtonRebindsFilter::tabletToolButtonEvent(KWin::TabletToolButtonEvent *event)
{
if (RebindScope::isRebinding()) {
return false;
}
m_tabletTool = event->tool;
return send(TabletToolButtonType, {event->device->name(), event->button}, event->pressed, event->time);
}
void ButtonRebindsFilter::insert(TriggerType type, const Trigger &trigger, const QStringList &entry)
{
if (entry.empty()) {
qCWarning(KWIN_BUTTONREBINDS) << "Failed to rebind to" << entry;
return;
}
if (entry.first() == QLatin1String("Key")) {
if (entry.size() != 2) {
qCWarning(KWIN_BUTTONREBINDS) << "Invalid key" << entry;
return;
}
const auto keys = QKeySequence::fromString(entry.at(1), QKeySequence::PortableText);
if (!keys.isEmpty()) {
m_actions.at(type).insert(trigger, keys);
}
} else if (entry.first() == QLatin1String("MouseButton")) {
if (entry.size() < 2) {
qCWarning(KWIN_BUTTONREBINDS) << "Invalid mouse button" << entry;
return;
}
bool ok = false;
MouseButton mb{entry[1].toUInt(&ok), {}};
// Last bit is the keyboard mods
if (entry.size() == 3) {
const auto keyboardModsRaw = entry.last().toInt(&ok);
mb.modifiers = Qt::KeyboardModifiers{keyboardModsRaw};
}
if (ok) {
m_actions.at(type).insert(trigger, mb);
} else {
qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << entry << "into a mouse button";
}
} else if (entry.first() == QLatin1String("TabletToolButton")) {
if (entry.size() != 2) {
qCWarning(KWIN_BUTTONREBINDS)
<< "Invalid tablet tool button" << entry;
return;
}
bool ok = false;
const TabletToolButton tb{entry.last().toUInt(&ok)};
if (ok) {
m_actions.at(type).insert(trigger, tb);
} else {
qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << entry << "into a mouse button";
}
} else if (entry.first() == QLatin1String("Disabled")) {
m_actions.at(type).insert(trigger, DisabledButton{});
}
}
bool ButtonRebindsFilter::send(TriggerType type, const Trigger &trigger, bool pressed, std::chrono::microseconds timestamp)
{
const auto &typeActions = m_actions.at(type);
if (typeActions.isEmpty()) {
return false;
}
const auto &action = typeActions[trigger];
if (const QKeySequence *seq = std::get_if<QKeySequence>(&action)) {
return sendKeySequence(*seq, pressed, timestamp);
}
if (const auto mb = std::get_if<MouseButton>(&action)) {
bool sentMouseEvent = false;
if (pressed && type != Pointer) {
sentMouseEvent |= sendMousePosition(m_tabletCursorPos, timestamp);
}
sendKeyModifiers(mb->modifiers, pressed, timestamp);
sentMouseEvent |= sendMouseButton(mb->button, pressed, timestamp);
if (sentMouseEvent) {
sendMouseFrame();
}
return sentMouseEvent;
}
if (const auto tb = std::get_if<TabletToolButton>(&action)) {
return sendTabletToolButton(tb->button, pressed, timestamp);
}
if (std::get_if<DisabledButton>(&action)) {
// Intentional, we don't want to anything to anybody
return true;
}
return false;
}
static constexpr std::array<std::pair<int, int>, 4> s_modifierKeyTable = {
std::pair(Qt::Key_Control, KEY_LEFTCTRL),
std::pair(Qt::Key_Alt, KEY_LEFTALT),
std::pair(Qt::Key_Shift, KEY_LEFTSHIFT),
std::pair(Qt::Key_Meta, KEY_LEFTMETA),
};
bool ButtonRebindsFilter::sendKeySequence(const QKeySequence &keys, bool pressed, std::chrono::microseconds time)
{
if (keys.isEmpty()) {
return false;
}
const auto &key = keys[0];
auto sendKey = [this, pressed, time](xkb_keycode_t key) {
auto state = pressed ? KWin::KeyboardKeyState::Pressed : KWin::KeyboardKeyState::Released;
Q_EMIT m_inputDevice->keyChanged(key, state, time, m_inputDevice.get());
};
// handle modifier-only keys
for (const auto &[keySymQt, keySymLinux] : s_modifierKeyTable) {
if (key == keySymQt) {
RebindScope scope;
sendKey(keySymLinux);
return true;
}
}
QList<xkb_keysym_t> syms = KWin::Xkb::keysymsFromQtKey(keys[0]);
// Use keysyms from the keypad if and only if KeypadModifier is set
syms.erase(std::remove_if(syms.begin(), syms.end(), [keys](int sym) {
bool onKeyPad = sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_Equal;
if (keys[0] & Qt::KeypadModifier) {
return !onKeyPad;
} else {
return onKeyPad;
}
}),
syms.end());
if (syms.empty()) {
qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << keys << "to keysym";
return false;
}
// KKeyServer returns upper case syms, lower it to not confuse modifiers handling
std::optional<int> keyCode;
std::optional<int> level;
for (int sym : syms) {
auto code = KWin::input()->keyboard()->xkb()->keycodeFromKeysym(sym);
if (code) {
keyCode = code->first;
level = code->second;
break;
}
}
if (!keyCode) {
qCWarning(KWIN_BUTTONREBINDS) << "Could not convert" << keys << "syms: " << syms << "to keycode";
return false;
}
RebindScope scope;
if (key & Qt::ShiftModifier || level == 1) {
sendKey(KEY_LEFTSHIFT);
}
if (key & Qt::ControlModifier) {
sendKey(KEY_LEFTCTRL);
}
if (key & Qt::AltModifier) {
sendKey(KEY_LEFTALT);
}
if (key & Qt::MetaModifier) {
sendKey(KEY_LEFTMETA);
}
sendKey(keyCode.value());
return true;
}
bool ButtonRebindsFilter::sendKeyModifiers(const Qt::KeyboardModifiers &modifiers, bool pressed, std::chrono::microseconds time)
{
if (modifiers == Qt::NoModifier) {
return false;
}
auto sendKey = [this, pressed, time](xkb_keycode_t key) {
auto state = pressed ? KWin::KeyboardKeyState::Pressed : KWin::KeyboardKeyState::Released;
Q_EMIT m_inputDevice->keyChanged(key, state, time, m_inputDevice.get());
};
if (modifiers.testFlag(Qt::ShiftModifier)) {
sendKey(KEY_LEFTSHIFT);
}
if (modifiers.testFlag(Qt::ControlModifier)) {
sendKey(KEY_LEFTCTRL);
}
if (modifiers.testFlag(Qt::AltModifier)) {
sendKey(KEY_LEFTALT);
}
if (modifiers.testFlag(Qt::MetaModifier)) {
sendKey(KEY_LEFTMETA);
}
return true;
}
bool ButtonRebindsFilter::sendMouseButton(quint32 button, bool pressed, std::chrono::microseconds time)
{
RebindScope scope;
Q_EMIT m_inputDevice->pointerButtonChanged(button, KWin::PointerButtonState(pressed), time, m_inputDevice.get());
return true;
}
bool ButtonRebindsFilter::sendMousePosition(QPointF position, std::chrono::microseconds time)
{
RebindScope scope;
Q_EMIT m_inputDevice->pointerMotionAbsolute(position, time, m_inputDevice.get());
return true;
}
bool ButtonRebindsFilter::sendMouseFrame()
{
RebindScope scope;
Q_EMIT m_inputDevice->pointerFrame(m_inputDevice.get());
return true;
}
bool ButtonRebindsFilter::sendTabletToolButton(quint32 button, bool pressed, std::chrono::microseconds time)
{
if (!m_tabletTool) {
return false;
}
RebindScope scope;
Q_EMIT m_inputDevice->tabletToolButtonEvent(button, pressed, m_tabletTool, time, m_inputDevice.get());
return true;
}
#include "moc_buttonrebindsfilter.cpp"
@@ -0,0 +1,97 @@
/*
SPDX-FileCopyrightText: 2022 David Redondo <kde@david-redono.de>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include "plugin.h"
#include <array>
#include <optional>
#include <variant>
#include "core/inputdevice.h"
#include "input.h"
#include "input_event.h"
#include <QKeySequence>
class InputDevice : public KWin::InputDevice
{
QString name() const override;
bool isEnabled() const override;
void setEnabled(bool enabled) override;
bool isKeyboard() 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;
};
struct Trigger
{
QString device;
uint button;
bool operator==(const Trigger &o) const
{
return button == o.button && device == o.device;
}
};
class ButtonRebindsFilter : public KWin::Plugin, public KWin::InputEventFilter
{
Q_OBJECT
public:
enum TriggerType {
Pointer,
TabletPad,
TabletToolButtonType,
LastType
};
Q_ENUM(TriggerType)
struct TabletToolButton
{
quint32 button;
};
struct MouseButton
{
quint32 button;
Qt::KeyboardModifiers modifiers;
};
struct DisabledButton
{
};
explicit ButtonRebindsFilter();
~ButtonRebindsFilter() override;
bool pointerButton(KWin::PointerButtonEvent *event) override;
bool tabletToolProximityEvent(KWin::TabletEvent *event) override;
bool tabletToolAxisEvent(KWin::TabletEvent *event) override;
bool tabletToolTipEvent(KWin::TabletEvent *event) override;
bool tabletPadButtonEvent(KWin::TabletPadButtonEvent *event) override;
bool tabletToolButtonEvent(KWin::TabletToolButtonEvent *event) override;
private:
void loadConfig(const KConfigGroup &group);
void insert(TriggerType type, const Trigger &trigger, const QStringList &action);
bool send(TriggerType type, const Trigger &trigger, bool pressed, std::chrono::microseconds timestamp);
bool sendKeySequence(const QKeySequence &sequence, bool pressed, std::chrono::microseconds time);
bool sendKeyModifiers(const Qt::KeyboardModifiers &modifiers, bool pressed, std::chrono::microseconds time);
bool sendMouseButton(quint32 button, bool pressed, std::chrono::microseconds time);
bool sendMousePosition(QPointF position, std::chrono::microseconds time);
bool sendMouseFrame();
bool sendTabletToolButton(quint32 button, bool pressed, std::chrono::microseconds time);
std::unique_ptr<InputDevice> m_inputDevice;
std::array<QHash<Trigger, std::variant<QKeySequence, MouseButton, TabletToolButton, DisabledButton>>, LastType> m_actions;
KConfigWatcher::Ptr m_configWatcher;
QPointer<KWin::InputDeviceTabletTool> m_tabletTool;
QPointF m_cursorPos, m_tabletCursorPos;
};
@@ -0,0 +1,32 @@
/*
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:
std::unique_ptr<KWin::Plugin> create() const override
{
switch (KWin::kwinApp()->operationMode()) {
case KWin::Application::OperationModeWayland:
return std::make_unique<ButtonRebindsFilter>();
case KWin::Application::OperationModeX11:
[[fallthrough]];
default:
return nullptr;
}
}
};
#include "main.moc"
@@ -0,0 +1,5 @@
{
"KPlugin": {
"EnabledByDefault": true
}
}