[plasma/kglobalacceld] /: Implement setInverseShortcutActions D-Bus method and config storage
Jakob Petsovits <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 017dcc7c0c9f23f30c1113e87663d9a808b86a95 by Jakob Petsovits.
Committed on 07/08/2026 at 20:11.
Pushed by jpetso into branch 'master'.
Implement setInverseShortcutActions D-Bus method and config storage
Depends on new API introduced in KGlobalAccel 6.27.
In terms of storage, this persists the action pairs in a sub-group
of the component called "InverseActions", e.g. [kwin][$InverseAction]
for the "kwin" component. We do not currently store which of the
actions is "forward" and which is "backward", if we need this we
can add it at a later time.
M +1 -1 CMakeLists.txt
M +45 -0 src/component.cpp
M +10 -0 src/component.h
M +10 -0 src/globalshortcut.cpp
M +9 -1 src/globalshortcut.h
M +4 -0 src/globalshortcutsregistry.cpp
M +34 -0 src/kglobalacceld.cpp
M +4 -0 src/kglobalacceld.h
M +8 -0 src/kserviceactioncomponent.cpp
https://invent.kde.org/plasma/kglobalacceld/-/commit/017dcc7c0c9f23f30c1113e87663d9a808b86a95
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 22f1d48..c686650 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,7 +5,7 @@ project(kglobalacceld)
set(PROJECT_VERSION "6.7.80")
set(QT_MIN_VERSION "6.10.0")
-set(KF6_MIN_VERSION "6.26.0")
+set(KF6_MIN_VERSION "6.27.0")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
diff --git a/src/component.cpp b/src/component.cpp
index 090295f..f10b83b 100644
--- a/src/component.cpp
+++ b/src/component.cpp
@@ -14,6 +14,8 @@
#include <QKeySequence>
#include <QStringList>
+using namespace Qt::StringLiterals;
+
QSet<QKeySequence> Component::keysFromString(const QString &str)
{
QSet<QKeySequence> ret;
@@ -272,6 +274,30 @@ GlobalShortcut *Component::registerShortcut(const QString &uniqueName,
return shortcut;
}
+void Component::loadInverseAction(const QString &aUniqueName, const QStringList &configEntry)
+{
+ if (configEntry.size() != 1) { // possibility of later expansion with optional list entries
+ qCWarning(KGLOBALACCELD) << "Inverse action invalid format, ignoring:" << aUniqueName << configEntry;
+ return;
+ }
+ const QString &bUniqueName = configEntry.last();
+ GlobalShortcut *a = currentContext()->_actionsMap.value(aUniqueName);
+ GlobalShortcut *b = currentContext()->_actionsMap.value(bUniqueName);
+ if (!a || !b) {
+ qCWarning(KGLOBALACCELD) << "Inverse action configured but one or both shortcuts not found, ignoring:" << //
+ aUniqueName << static_cast<bool>(a) << "|" << bUniqueName << static_cast<bool>(b);
+ return;
+ }
+ if ((!a->inverseActionUniqueName().isEmpty() && a->inverseActionUniqueName() != bUniqueName)
+ || (!b->inverseActionUniqueName().isEmpty() && b->inverseActionUniqueName() != aUniqueName)) {
+ qCWarning(KGLOBALACCELD) << "Inverse action configured for actions with a pre-existing assignment:" << //
+ aUniqueName << a->inverseActionUniqueName() << "|" << bUniqueName << b->inverseActionUniqueName();
+ return;
+ }
+ a->setInverseActionUniqueName(bUniqueName);
+ b->setInverseActionUniqueName(aUniqueName);
+}
+
void Component::loadSettings(const KConfigGroup &configGroup, const KConfigGroup &stateGroup)
{
// GlobalShortcutsRegistry::loadSettings handles contexts.
@@ -286,6 +312,13 @@ void Component::loadSettings(const KConfigGroup &configGroup, const KConfigGroup
registerShortcut(confKey, entry[2], entry[0], entry[1], serial);
}
+
+ const KConfigGroup inverseActionGroup(&configGroup, "$InverseAction"_L1);
+
+ const auto inverseActionKeys = inverseActionGroup.keyList();
+ for (const QString &confKey : inverseActionKeys) {
+ loadInverseAction(confKey, inverseActionGroup.readEntry(confKey, QStringList()));
+ }
}
void Component::setFriendlyName(const QString &name)
@@ -345,6 +378,8 @@ void Component::writeSettings(KConfigGroup &configGroup, KConfigGroup &stateGrou
contextGroup.writeEntry("_k_friendly_name", context->friendlyName());
}
+ KConfigGroup inverseActionGroup(&contextGroup, "$InverseAction"_L1);
+
// qCDebug(KGLOBALACCELD) << "writing group " << _uniqueName << ":" << context->uniqueName();
for (const GlobalShortcut *shortcut : std::as_const(context->_actionsMap)) {
@@ -363,8 +398,18 @@ void Component::writeSettings(KConfigGroup &configGroup, KConfigGroup &stateGrou
contextGroup.writeEntry(shortcut->uniqueName(), entry);
stateGroup.writeEntry(shortcut->uniqueName(), shortcut->serial());
+
+ if (!shortcut->inverseActionUniqueName().isEmpty() && !inverseActionGroup.hasKey(shortcut->inverseActionUniqueName())) {
+ inverseActionGroup.writeEntry(shortcut->uniqueName(), QStringList{shortcut->inverseActionUniqueName()});
+ }
}
}
}
+// static
+bool Component::isReservedConfigGroupName(const QString &name)
+{
+ return name.startsWith("$"_L1); // "$InverseAction", "$Trigger", etc.
+}
+
#include "moc_component.cpp"
diff --git a/src/component.h b/src/component.h
index bcb67b4..498d946 100644
--- a/src/component.h
+++ b/src/component.h
@@ -99,6 +99,9 @@ public:
virtual void writeSettings(KConfigGroup &config, KConfigGroup &state) const;
+ //! Returns whether the given sub-group name is reserved for component info as opposed to specific contexts
+ static bool isReservedConfigGroupName(const QString &name);
+
protected:
friend class ::GlobalShortcutsRegistry;
friend class ::ShortcutsTest;
@@ -122,6 +125,13 @@ protected:
const QString &defaultShortcutString,
uint64_t serial = 0);
+ /**
+ * Assign two already registered actions to each other as inverse actions.
+ * @param aUniqueName internal unique name of a given action
+ * @param configEntry internal unique name of its inverse action, plus optional extra flags
+ */
+ void loadInverseAction(const QString &aUniqueName, const QStringList &configEntry);
+
static QString stringFromKeys(const QSet<QKeySequence> &keys);
static QSet<QKeySequence> keysFromString(const QString &str);
diff --git a/src/globalshortcut.cpp b/src/globalshortcut.cpp
index 0c3fed3..8a5f035 100644
--- a/src/globalshortcut.cpp
+++ b/src/globalshortcut.cpp
@@ -165,6 +165,16 @@ void GlobalShortcut::setDefaultKeys(const QSet<QKeySequence> &newKeys)
_defaultKeys = newKeys;
}
+QString GlobalShortcut::inverseActionUniqueName() const
+{
+ return _inverseActionUniqueName;
+}
+
+void GlobalShortcut::setInverseActionUniqueName(const QString &uniqueName)
+{
+ _inverseActionUniqueName = uniqueName;
+}
+
void GlobalShortcut::setActive()
{
if (!_isPresent || _isRegistered) {
diff --git a/src/globalshortcut.h b/src/globalshortcut.h
index aa8de10..334d5e6 100644
--- a/src/globalshortcut.h
+++ b/src/globalshortcut.h
@@ -40,7 +40,10 @@ public:
//! Return the friendly display name for this shortcut.
QString friendlyName() const;
- //! Check if the shortcut is active. It's keys are grabbed
+ //! Returns the unique name of the associated inverse action, or empty if not paired.
+ QString inverseActionUniqueName() const;
+
+ //! Check if the shortcut is active. Its keys are grabbed
bool isActive() const;
//! Check if the shortcut is fresh/new. Is an internal state
@@ -64,6 +67,9 @@ public:
//! Sets the friendly name for the shortcut. For display.
void setFriendlyName(const QString &);
+ //! Sets the inverse action for this shortcut.
+ void setInverseActionUniqueName(const QString &uniqueName);
+
//! Sets the shortcut inactive. No longer grabs the keys.
void setInactive();
@@ -105,6 +111,8 @@ private:
QSet<QKeySequence> _keys;
QSet<QKeySequence> _defaultKeys;
uint64_t _serial;
+
+ QString _inverseActionUniqueName;
};
#endif /* #ifndef GLOBALSHORTCUT_H */
diff --git a/src/globalshortcutsregistry.cpp b/src/globalshortcutsregistry.cpp
index d507e20..90e186b 100644
--- a/src/globalshortcutsregistry.cpp
+++ b/src/globalshortcutsregistry.cpp
@@ -676,6 +676,10 @@ void GlobalShortcutsRegistry::loadSettings()
if (context == QLatin1String("Friendly Name")) {
continue;
}
+ // Skip any other sub-groups that are used to store extended action info
+ if (Component::isReservedConfigGroupName(context)) {
+ continue;
+ }
const KConfigGroup contextConfigGroup(&configGroup, context);
const KConfigGroup contextStateGroup(&stateGroup, context);
diff --git a/src/kglobalacceld.cpp b/src/kglobalacceld.cpp
index f20c0c3..6257940 100644
--- a/src/kglobalacceld.cpp
+++ b/src/kglobalacceld.cpp
@@ -568,6 +568,40 @@ void KGlobalAccelD::setForeignShortcutKeys(const QStringList &actionId, const QS
Q_EMIT yourShortcutsChanged(actionId, newKeys);
}
+bool KGlobalAccelD::setInverseShortcutActions(const QString &componentUnique,
+ const QString &forwardActionUnique,
+ const QString &backwardActionUnique,
+ uint inverseSetterFlags)
+{
+ if (inverseSetterFlags != 0) { // reserved, may change pair assignment behaviors
+ return false;
+ }
+
+ GlobalShortcut *forwardShortcut = d->findAction(componentUnique, forwardActionUnique);
+ GlobalShortcut *backwardShortcut = d->findAction(componentUnique, backwardActionUnique);
+ if (!forwardShortcut || !backwardShortcut) {
+ qCWarning(KGLOBALACCELD) << "Inverse actions" << componentUnique << forwardActionUnique << backwardActionUnique
+ << "not assigned: one or both shortcuts were not registered when calling setInverseShortcutActions()";
+ return false;
+ }
+ const QString existingInverseOfForward = forwardShortcut->inverseActionUniqueName();
+ const QString existingInverseOfBackward = backwardShortcut->inverseActionUniqueName();
+
+ if (existingInverseOfForward == backwardActionUnique && existingInverseOfBackward == forwardActionUnique) {
+ // no changes, everything is as it was already loaded beforehand
+ return true;
+ } else if (!existingInverseOfForward.isEmpty() || !existingInverseOfBackward.isEmpty()) {
+ qCWarning(KGLOBALACCELD) << "Inverse actions" << componentUnique << forwardActionUnique << backwardActionUnique
+ << "not assigned: existing inverse actions exist and differ from requested action pair";
+ return false;
+ }
+
+ forwardShortcut->setInverseActionUniqueName(backwardActionUnique);
+ backwardShortcut->setInverseActionUniqueName(forwardActionUnique);
+ scheduleWriteSettings();
+ return true;
+}
+
void KGlobalAccelD::scheduleWriteSettings() const
{
if (!d->writeoutTimer.isActive()) {
diff --git a/src/kglobalacceld.h b/src/kglobalacceld.h
index 6d3560c..9ca45b2 100644
--- a/src/kglobalacceld.h
+++ b/src/kglobalacceld.h
@@ -115,6 +115,10 @@ public Q_SLOTS:
#endif
Q_SCRIPTABLE void setForeignShortcutKeys(const QStringList &actionId, const QSet<QKeySequence> &keys);
+ //! \since 6.8
+ Q_SCRIPTABLE bool
+ setInverseShortcutActions(const QString &componentUnique, const QString &forwardActionUnique, const QString &backwardActionUnique, uint couplingFlags);
+
// to be called when a KAction is destroyed. The shortcut stays in the data structures for
// conflict resolution but won't trigger.
Q_SCRIPTABLE void setInactive(const QStringList &actionId);
diff --git a/src/kserviceactioncomponent.cpp b/src/kserviceactioncomponent.cpp
index 1238602..05d3a05 100644
--- a/src/kserviceactioncomponent.cpp
+++ b/src/kserviceactioncomponent.cpp
@@ -140,6 +140,14 @@ void KServiceActionComponent::loadSettings(const KConfigGroup &configGroup, cons
shortcut->setIsPresent(true);
}
+ // Inverse actions - load only after all actions are registered, so they can be properly paired
+ for (const KServiceAction &action : actions) {
+ const QStringList entry = action.property<QStringList>(QStringLiteral("X-KDE-InverseAction"));
+ if (!entry.isEmpty()) {
+ loadInverseAction(action.name(), entry);
+ }
+ }
+
const QString type = m_service->property<QString>(QStringLiteral("X-KDE-GlobalShortcutType"));
// Type can be Application or Service