Add kglobalacceld recipe with D-Bus service wiring and config entry

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-25 14:49:39 +01:00
parent 9c8de2d919
commit da53c400b4
48 changed files with 5797 additions and 2 deletions
@@ -0,0 +1,391 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "component.h"
#include "globalshortcutcontext.h"
#include "globalshortcutsregistry.h"
#include "kglobalaccel_interface.h"
#include "logging_p.h"
#include <config-kglobalaccel.h>
#include <QKeySequence>
#include <QStringList>
#if HAVE_X11
#include <private/qtx11extras_p.h>
#endif
QList<QKeySequence> Component::keysFromString(const QString &str)
{
QList<QKeySequence> ret;
if (str == QLatin1String("none")) {
return ret;
}
const QStringList strList = str.split(QLatin1Char('\t'));
for (const QString &s : strList) {
QKeySequence key = QKeySequence::fromString(s, QKeySequence::PortableText);
if (!key.isEmpty()) { // sanity check just in case
ret.append(key);
}
}
return ret;
}
QString Component::stringFromKeys(const QList<QKeySequence> &keys)
{
if (keys.isEmpty()) {
return QStringLiteral("none");
}
QString ret;
for (const QKeySequence &key : keys) {
ret.append(key.toString(QKeySequence::PortableText));
ret.append(QLatin1Char('\t'));
}
ret.chop(1);
return ret;
}
Component::Component(const QString &uniqueName, const QString &friendlyName)
: _uniqueName(uniqueName)
, _friendlyName(friendlyName)
, _registry(GlobalShortcutsRegistry::self())
{
// Make sure we do no get uniquenames still containing the context
Q_ASSERT(uniqueName.indexOf(QLatin1Char('|')) == -1);
const QString DEFAULT(QStringLiteral("default"));
createGlobalShortcutContext(DEFAULT, QStringLiteral("Default Context"));
_current = _contexts.value(DEFAULT);
}
Component::~Component()
{
// We delete all shortcuts from all contexts
qDeleteAll(_contexts);
}
bool Component::activateGlobalShortcutContext(const QString &uniqueName)
{
if (!_contexts.value(uniqueName)) {
createGlobalShortcutContext(uniqueName, QStringLiteral("TODO4"));
return false;
}
// Deactivate the current contexts shortcuts
deactivateShortcuts();
// Switch the context
_current = _contexts.value(uniqueName);
return true;
}
void Component::activateShortcuts()
{
for (GlobalShortcut *shortcut : std::as_const(_current->_actionsMap)) {
shortcut->setActive();
}
}
QList<GlobalShortcut *> Component::allShortcuts(const QString &contextName) const
{
GlobalShortcutContext *context = _contexts.value(contextName);
return context ? context->_actionsMap.values() : QList<GlobalShortcut *>{};
}
QList<KGlobalShortcutInfo> Component::allShortcutInfos(const QString &contextName) const
{
GlobalShortcutContext *context = _contexts.value(contextName);
return context ? context->allShortcutInfos() : QList<KGlobalShortcutInfo>{};
}
bool Component::cleanUp()
{
bool changed = false;
const auto actions = _current->_actionsMap;
for (GlobalShortcut *shortcut : actions) {
qCDebug(KGLOBALACCELD) << _current->_actionsMap.size();
if (!shortcut->isPresent()) {
changed = true;
shortcut->unRegister();
}
}
if (changed) {
_registry->writeSettings();
// We could be destroyed after this call!
}
return changed;
}
bool Component::createGlobalShortcutContext(const QString &uniqueName, const QString &friendlyName)
{
if (_contexts.value(uniqueName)) {
qCDebug(KGLOBALACCELD) << "Shortcut Context " << uniqueName << "already exists for component " << _uniqueName;
return false;
}
_contexts.insert(uniqueName, new GlobalShortcutContext(uniqueName, friendlyName, this));
return true;
}
GlobalShortcutContext *Component::currentContext()
{
return _current;
}
QDBusObjectPath Component::dbusPath() const
{
auto isNonAscii = [](QChar ch) {
const char c = ch.unicode();
const bool isAscii = c == '_' //
|| (c >= 'A' && c <= 'Z') //
|| (c >= 'a' && c <= 'z') //
|| (c >= '0' && c <= '9');
return !isAscii;
};
QString dbusPath = _uniqueName;
// DBus path can only contain ASCII characters, any non-alphanumeric char should
// be turned into '_'
std::replace_if(dbusPath.begin(), dbusPath.end(), isNonAscii, QLatin1Char('_'));
// QDBusObjectPath could be a little bit easier to handle :-)
return QDBusObjectPath(_registry->dbusPath().path() + QLatin1String("component/") + dbusPath);
}
void Component::deactivateShortcuts(bool temporarily)
{
for (GlobalShortcut *shortcut : std::as_const(_current->_actionsMap)) {
if (temporarily //
&& _uniqueName == QLatin1String("kwin") //
&& shortcut->uniqueName() == QLatin1String("Block Global Shortcuts")) {
continue;
}
shortcut->setInactive();
}
}
void Component::emitGlobalShortcutPressed(const GlobalShortcut &shortcut)
{
#if HAVE_X11
// pass X11 timestamp
const long timestamp = QX11Info::appTime();
#else
const long timestamp = 0;
#endif
if (shortcut.context()->component() != this) {
return;
}
Q_EMIT globalShortcutPressed(shortcut.context()->component()->uniqueName(), shortcut.uniqueName(), timestamp);
}
void Component::emitGlobalShortcutReleased(const GlobalShortcut &shortcut)
{
#if HAVE_X11
// pass X11 timestamp
const long timestamp = QX11Info::appTime();
#else
const long timestamp = 0;
#endif
if (shortcut.context()->component() != this) {
return;
}
Q_EMIT globalShortcutReleased(shortcut.context()->component()->uniqueName(), shortcut.uniqueName(), timestamp);
}
void Component::invokeShortcut(const QString &shortcutName, const QString &context)
{
GlobalShortcut *shortcut = getShortcutByName(shortcutName, context);
if (shortcut) {
emitGlobalShortcutPressed(*shortcut);
}
}
QString Component::friendlyName() const
{
return !_friendlyName.isEmpty() ? _friendlyName : _uniqueName;
}
GlobalShortcut *Component::getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
{
return _current->getShortcutByKey(key, type);
}
QList<GlobalShortcut *> Component::getShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
{
QList<GlobalShortcut *> rc;
for (GlobalShortcutContext *context : std::as_const(_contexts)) {
GlobalShortcut *sc = context->getShortcutByKey(key, type);
if (sc) {
rc.append(sc);
}
}
return rc;
}
GlobalShortcut *Component::getShortcutByName(const QString &uniqueName, const QString &context) const
{
const GlobalShortcutContext *shortcutContext = _contexts.value(context);
return shortcutContext ? shortcutContext->_actionsMap.value(uniqueName) : nullptr;
}
QStringList Component::getShortcutContexts() const
{
return _contexts.keys();
}
bool Component::isActive() const
{
// The component is active if at least one of it's global shortcuts is
// present.
for (GlobalShortcut *shortcut : std::as_const(_current->_actionsMap)) {
if (shortcut->isPresent()) {
return true;
}
}
return false;
}
bool Component::isShortcutAvailable(const QKeySequence &key, const QString &component, const QString &context) const
{
qCDebug(KGLOBALACCELD) << key.toString() << component;
// if this component asks for the key. only check the keys in the same
// context
if (component == uniqueName()) {
return shortcutContext(context)->isShortcutAvailable(key);
} else {
for (auto it = _contexts.cbegin(), endIt = _contexts.cend(); it != endIt; ++it) {
const GlobalShortcutContext *ctx = it.value();
if (!ctx->isShortcutAvailable(key)) {
return false;
}
}
}
return true;
}
GlobalShortcut *
Component::registerShortcut(const QString &uniqueName, const QString &friendlyName, const QString &shortcutString, const QString &defaultShortcutString)
{
// The shortcut will register itself with us
GlobalShortcut *shortcut = new GlobalShortcut(uniqueName, friendlyName, currentContext());
const QList<QKeySequence> keys = keysFromString(shortcutString);
shortcut->setDefaultKeys(keysFromString(defaultShortcutString));
shortcut->setIsFresh(false);
QList<QKeySequence> newKeys = keys;
for (const QKeySequence &key : keys) {
if (!key.isEmpty()) {
if (_registry->getShortcutByKey(key)) {
// The shortcut is already used. The config file is
// broken. Ignore the request.
newKeys.removeAll(key);
qCWarning(KGLOBALACCELD) << "Shortcut found twice in kglobalshortcutsrc." << key;
}
}
}
shortcut->setKeys(keys);
return shortcut;
}
void Component::loadSettings(KConfigGroup &configGroup)
{
// GlobalShortcutsRegistry::loadSettings handles contexts.
const auto listKeys = configGroup.keyList();
for (const QString &confKey : listKeys) {
const QStringList entry = configGroup.readEntry(confKey, QStringList());
if (entry.size() != 3) {
continue;
}
registerShortcut(confKey, entry[2], entry[0], entry[1]);
}
}
void Component::setFriendlyName(const QString &name)
{
_friendlyName = name;
}
GlobalShortcutContext *Component::shortcutContext(const QString &contextName)
{
return _contexts.value(contextName);
}
GlobalShortcutContext const *Component::shortcutContext(const QString &contextName) const
{
return _contexts.value(contextName);
}
QStringList Component::shortcutNames(const QString &contextName) const
{
const GlobalShortcutContext *context = _contexts.value(contextName);
return context ? context->_actionsMap.keys() : QStringList{};
}
QString Component::uniqueName() const
{
return _uniqueName;
}
void Component::unregisterShortcut(const QString &uniqueName)
{
// Now wrote all contexts
for (GlobalShortcutContext *context : std::as_const(_contexts)) {
if (context->_actionsMap.value(uniqueName)) {
delete context->takeShortcut(context->_actionsMap.value(uniqueName));
}
}
}
void Component::writeSettings(KConfigGroup &configGroup) const
{
// If we don't delete the current content global shortcut
// registrations will never not deleted after forgetGlobalShortcut()
configGroup.deleteGroup();
// Now write all contexts
for (GlobalShortcutContext *context : std::as_const(_contexts)) {
KConfigGroup contextGroup;
if (context->uniqueName() == QLatin1String("default")) {
contextGroup = configGroup;
// Write the friendly name
contextGroup.writeEntry("_k_friendly_name", friendlyName());
} else {
contextGroup = KConfigGroup(&configGroup, context->uniqueName());
// Write the friendly name
contextGroup.writeEntry("_k_friendly_name", context->friendlyName());
}
// qCDebug(KGLOBALACCELD) << "writing group " << _uniqueName << ":" << context->uniqueName();
for (const GlobalShortcut *shortcut : std::as_const(context->_actionsMap)) {
// qCDebug(KGLOBALACCELD) << "writing" << shortcut->uniqueName();
// We do not write fresh shortcuts.
// We do not write session shortcuts
if (shortcut->isFresh() || shortcut->isSessionShortcut()) {
continue;
}
// qCDebug(KGLOBALACCELD) << "really writing" << shortcut->uniqueName();
QStringList entry(stringFromKeys(shortcut->keys()));
entry.append(stringFromKeys(shortcut->defaultKeys()));
entry.append(shortcut->friendlyName());
contextGroup.writeEntry(shortcut->uniqueName(), entry);
}
}
}