kcms/gamecontroller: Add switch for KWin "gamecontroller" plugin

Add the ability to enable/disable the new KWin "gamecontroller"
plugin from KWin commit 3110eaef. The new UI element will be
added as part of the existing gamecontroller KCM.

This plugin provides some integration for game controllers.
While no other process is using a given controller device, it
translates controller inputs to keyboard and mouse events, to
make the device useful for input on the desktop.
It also prevents the system from going idle (turning the screen
off or going to standby) while any gamepad is used.

BUG: 328987
This commit is contained in:
Yelsin Sepulveda 2025-10-01 12:36:48 -04:00 committed by Jakob Petsovits
parent 863d22887a
commit 7493110869
5 changed files with 92 additions and 4 deletions

1
.gitignore vendored
View file

@ -27,3 +27,4 @@ cmake-build-debug*
/compile_commands.json /compile_commands.json
.clangd .clangd
.cache .cache
*.qmlls.ini

View file

@ -44,5 +44,6 @@ target_link_libraries(kcm_gamecontroller PRIVATE
Qt6::Quick Qt6::Quick
Qt6::QuickWidgets Qt6::QuickWidgets
SDL2::SDL2 SDL2::SDL2
Qt::DBus
UDev::UDev UDev::UDev
) )

View file

@ -2,13 +2,20 @@
SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com> SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
SPDX-FileCopyrightText: 2023 Jeremy Whiting <jpwhiting@kde.org> SPDX-FileCopyrightText: 2023 Jeremy Whiting <jpwhiting@kde.org>
SPDX-FileCopyrightText: 2023 Niccolò Venerandi <niccolo@venerandi.com> SPDX-FileCopyrightText: 2023 Niccolò Venerandi <niccolo@venerandi.com>
SPDX-FileCopyrightText: 2025 Yelsin Sepulveda <yelsin.sepulveda@kdemail.org>
SPDX-FileCopyrightText: 2025 Jakob Petsovits <jpetso@petsovits.com>
SPDX-License-Identifier: GPL-2.0-or-later SPDX-License-Identifier: GPL-2.0-or-later
*/ */
#include "kcm.h" #include "kcm.h"
#include <KConfigGroup>
#include <KPluginFactory> #include <KPluginFactory>
#include <KSharedConfig>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include "axesmodel.h" #include "axesmodel.h"
#include "axesproxymodel.h" #include "axesproxymodel.h"
@ -18,13 +25,16 @@
K_PLUGIN_CLASS_WITH_JSON(KCMGameController, "kcm_gamecontroller.json") K_PLUGIN_CLASS_WITH_JSON(KCMGameController, "kcm_gamecontroller.json")
static const QString s_kwinPluginId = QStringLiteral("gamecontroller");
KCMGameController::KCMGameController(QObject *parent, const KPluginMetaData &metaData) KCMGameController::KCMGameController(QObject *parent, const KPluginMetaData &metaData)
: KQuickManagedConfigModule(parent, metaData) : KQuickConfigModule(parent, metaData)
{ {
setButtons(Help); setButtons(Help);
constexpr const char *uri{"org.kde.plasma.gamecontroller.kcm"}; constexpr const char *uri{"org.kde.plasma.gamecontroller.kcm"};
qmlRegisterUncreatableType<KCMGameController>(uri, 1, 0, "KCMGameController", QString());
qmlRegisterType<DeviceModel>(uri, 1, 0, "DeviceModel"); qmlRegisterType<DeviceModel>(uri, 1, 0, "DeviceModel");
qmlRegisterType<AxesModel>(uri, 1, 0, "AxesModel"); qmlRegisterType<AxesModel>(uri, 1, 0, "AxesModel");
qmlRegisterType<AxesProxyModel>(uri, 1, 0, "AxesProxyModel"); qmlRegisterType<AxesProxyModel>(uri, 1, 0, "AxesProxyModel");
@ -36,6 +46,36 @@ KCMGameController::~KCMGameController()
{ {
} }
bool KCMGameController::isPluginEnabled() const
{
KConfigGroup plugins(KSharedConfig::openConfig("kwinrc"), QStringLiteral("Plugins"));
return plugins.readEntry(s_kwinPluginId + QStringLiteral("Enabled"), true);
}
void KCMGameController::setPluginEnabled(bool enabled)
{
KConfigGroup plugins(KSharedConfig::openConfig("kwinrc"), QStringLiteral("Plugins"));
plugins.writeEntry(s_kwinPluginId + QStringLiteral("Enabled"), enabled, KConfig::Notify);
plugins.sync();
QDBusMessage message;
if (enabled) {
message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
QStringLiteral("/Plugins"),
QStringLiteral("org.kde.KWin.Plugins"),
QStringLiteral("LoadPlugin"));
message << s_kwinPluginId;
} else {
message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
QStringLiteral("/Plugins"),
QStringLiteral("org.kde.KWin.Plugins"),
QStringLiteral("UnloadPlugin"));
message << s_kwinPluginId;
}
QDBusConnection::sessionBus().asyncCall(message);
Q_EMIT pluginEnabledChanged();
}
#include "kcm.moc" #include "kcm.moc"
#include "moc_kcm.cpp" #include "moc_kcm.cpp"

View file

@ -2,19 +2,27 @@
SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com> SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
SPDX-FileCopyrightText: 2023 Jeremy Whiting <jpwhiting@kde.org> SPDX-FileCopyrightText: 2023 Jeremy Whiting <jpwhiting@kde.org>
SPDX-FileCopyrightText: 2023 Niccolò Venerandi <niccolo@venerandi.com> SPDX-FileCopyrightText: 2023 Niccolò Venerandi <niccolo@venerandi.com>
SPDX-FileCopyrightText: 2025 Yelsin Sepulveda <yelsin.sepulveda@kdemail.net>
SPDX-License-Identifier: GPL-2.0-or-later SPDX-License-Identifier: GPL-2.0-or-later
*/ */
#pragma once #pragma once
#include <KQuickManagedConfigModule> #include <KQuickConfigModule>
class KCMGameController : public KQuickManagedConfigModule class KCMGameController : public KQuickConfigModule
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool pluginEnabled READ isPluginEnabled WRITE setPluginEnabled NOTIFY pluginEnabledChanged FINAL)
public: public:
KCMGameController(QObject *parent, const KPluginMetaData &metaData); KCMGameController(QObject *parent, const KPluginMetaData &metaData);
~KCMGameController() override; ~KCMGameController() override;
bool isPluginEnabled() const;
void setPluginEnabled(bool enabled);
Q_SIGNALS:
void pluginEnabledChanged();
}; };

View file

@ -1,5 +1,7 @@
/* /*
SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com> SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
SPDX-FileCopyrightText: 2025 Yelsin Sepulveda <yelsin.sepulveda@kdemail.org>
SPDX-FileCopyrightText: 2025 Jakob Petsovits <jpetso@petsovits.com>
SPDX-License-Identifier: GPL-2.0-or-later SPDX-License-Identifier: GPL-2.0-or-later
*/ */
@ -20,6 +22,30 @@ KCM.SimpleKCM {
readonly property var deviceType: device?.type ?? "" readonly property var deviceType: device?.type ?? ""
readonly property var deviceControllerType: device?.controllerTypeName ?? "" readonly property var deviceControllerType: device?.controllerTypeName ?? ""
readonly property var deviceConnectionType: device?.connectionType ?? "" readonly property var deviceConnectionType: device?.connectionType ?? ""
readonly property bool kwinPluginEnabled: KCM.ConfigModule.pluginEnabled
actions: [
Kirigami.Action {
displayComponent: RowLayout {
spacing: Kirigami.Units.smallSpacing
Layout.fillWidth: true
QQC2.Switch {
id: plasmaIntegrationSwitch
text: i18nc("@option:check Allow using game controllers as a pointer and keyboard", "Allow using as pointer and keyboard")
checked: kwinPluginEnabled
onToggled: {
KCM.ConfigModule.pluginEnabled = checked
}
}
Kirigami.ContextualHelpButton {
toolTipText: xi18nc("@info:tooltip", "Allow game controllers to act like pointer and keyboard devices in Plasma and non-game applications.<nl/><nl/>Games can always use the controller.")
}
}
}
]
Kirigami.PlaceholderMessage { Kirigami.PlaceholderMessage {
icon.name: "input-gamepad" icon.name: "input-gamepad"
@ -47,6 +73,16 @@ KCM.SimpleKCM {
} }
} }
headerPaddingEnabled: false // Let the InlineMessages touch the edges
header: Kirigami.InlineMessage {
Layout.fillWidth: true
visible: kwinPluginEnabled && deviceCombo.count !== 0
type: Kirigami.MessageType.Warning
position: Kirigami.InlineMessage.Position.Header
showCloseButton: true
text: i18nc("@info:usagetip", "The system may automatically go to sleep or lock the screen while the controller is in use. Consider manually blocking them while using the controller, or enable automatic blocking by allowing the use as pointer and keyboard.")
}
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
@ -90,7 +126,7 @@ KCM.SimpleKCM {
text: deviceType text: deviceType
} }
} }
RowLayout { RowLayout {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
@ -122,6 +158,7 @@ KCM.SimpleKCM {
text: deviceConnectionType text: deviceConnectionType
} }
} }
RowLayout { RowLayout {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
@ -201,5 +238,6 @@ KCM.SimpleKCM {
} }
} }
} }
Item { Layout.fillHeight: true }
} }
} }