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,87 @@
set(kglobalaccelprivate_SRCS
kglobalacceld.cpp
kglobalaccel_interface.cpp
kserviceactioncomponent.cpp
component.cpp
logging.cpp
globalshortcut.cpp
globalshortcutsregistry.cpp
globalshortcutcontext.cpp
sequencehelpers_p.cpp
)
configure_file(config-kglobalaccel.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kglobalaccel.h )
add_library(KGlobalAccelD ${kglobalaccelprivate_SRCS})
add_library(K::KGlobalAccelD ALIAS KGlobalAccelD)
ecm_generate_export_header(KGlobalAccelD
EXPORT_FILE_NAME kglobalacceld_export.h
BASE_NAME KGlobalAccelD
VERSION ${PROJECT_VERSION}
USE_VERSION_HEADER
VERSION_BASE_NAME KGlobalAccel
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 4.3 5.90
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KGlobalAccelD INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/KGlobalAccelD>")
target_link_libraries(KGlobalAccelD
PUBLIC
KF6::GlobalAccel
PRIVATE
Qt6::DBus
KF6::WindowSystem # KKeyServer
KF6::CoreAddons # KAboutData
KF6::ConfigCore
KF6::Service
KF6::KIOCore
KF6::JobWidgets
)
set_target_properties(KGlobalAccelD PROPERTIES VERSION ${KGLOBALACCELD_VERSION}
SOVERSION ${KGLOBALACCELD_SOVERSION}
EXPORT_NAME "KGlobalAccelD"
)
target_compile_definitions(KGlobalAccelD PRIVATE -DQDBUS=${qdbus_EXECUTABLE})
if(XCB_XCB_FOUND)
target_link_libraries(KGlobalAccelD PUBLIC Qt6::GuiPrivate) # qtx11extras_p.h
endif()
add_executable(kglobalacceld main.cpp logging.cpp)
target_include_directories(kglobalacceld PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(kglobalacceld
KGlobalAccelD
KF6::CoreAddons
KF6::DBusAddons # KDBusService
KF6::Crash
)
add_subdirectory(plugins)
install(TARGETS KGlobalAccelD EXPORT KGlobalAccelDTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP)
install(TARGETS kglobalacceld DESTINATION ${KDE_INSTALL_LIBEXECDIR})
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kglobalacceld_export.h
kglobalacceld.h
kglobalaccel_interface.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KGlobalAccelD/ COMPONENT Devel
)
include (ECMConfiguredInstall)
ecm_install_configured_files(
INPUT plasma-kglobalaccel.service.in
DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}
)
ecm_install_configured_files(
INPUT kglobalacceld.desktop.in
DESTINATION ${KDE_INSTALL_AUTOSTARTDIR} @ONLY
)
@@ -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);
}
}
}
@@ -0,0 +1,182 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef COMPONENT_H
#define COMPONENT_H
#include "globalshortcut.h"
#include "kglobalshortcutinfo.h"
#include "kconfiggroup.h"
#include <KGlobalAccel>
#include <QHash>
#include <QObject>
class GlobalShortcut;
class GlobalShortcutContext;
class GlobalShortcutsRegistry;
/**
* @author Michael Jansen <kde@michael-jansen.biz>
*/
class Component : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kglobalaccel.Component")
/* clang-format off */
Q_SCRIPTABLE Q_PROPERTY(QString friendlyName READ friendlyName)
Q_SCRIPTABLE Q_PROPERTY(QString uniqueName READ uniqueName)
public:
~Component() override;
/* clang-format on */
bool activateGlobalShortcutContext(const QString &uniqueName);
void activateShortcuts();
//! Returns all shortcuts in context @context
QList<GlobalShortcut *> allShortcuts(const QString &context = QStringLiteral("default")) const;
//! Creates the new global shortcut context @p context
bool createGlobalShortcutContext(const QString &context, const QString &friendlyName = QString());
//! Return the current context
GlobalShortcutContext *currentContext();
//! Return uniqueName converted to a valid dbus path
QDBusObjectPath dbusPath() const;
//! Deactivate all currently active shortcuts
void deactivateShortcuts(bool temporarily = false);
//! Returns the friendly name
QString friendlyName() const;
//! Returns the currently active shortcut for key
GlobalShortcut *getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const;
//! Returns the shortcut context @p name or nullptr
GlobalShortcutContext *shortcutContext(const QString &name);
GlobalShortcutContext const *shortcutContext(const QString &name) const;
/**
* Returns the list of shortcuts (different context) registered with @p
* key.
*/
QList<GlobalShortcut *> getShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const;
//! Returns the shortcut by unique name. Only the active context is
//! searched.
GlobalShortcut *getShortcutByName(const QString &uniqueName, const QString &context = QStringLiteral("default")) const;
/**
* Check if @a key is available for component @p component
*/
bool isShortcutAvailable(const QKeySequence &key, const QString &component, const QString &context) const;
//! Load the settings from config group @p config
virtual void loadSettings(KConfigGroup &config);
//! Sets the human readable name for this component.
void setFriendlyName(const QString &);
QString uniqueName() const;
//! Unregister @a shortcut. This will remove its siblings from all contexts
void unregisterShortcut(const QString &uniqueName);
virtual void writeSettings(KConfigGroup &config) const;
protected:
friend class ::GlobalShortcutsRegistry;
//! Constructs a component. This is a private constructor, to create a component
//! use GlobalShortcutsRegistry::self()->createComponent().
Component(const QString &uniqueName, const QString &friendlyName);
/**
* Create a new globalShortcut by its name
* @param uniqueName internal unique name to identify the shortcut
* @param friendlyName name for the shortcut to be presented to the user
* @param shortcutString string representation of the shortcut, such as "CTRL+S"
* @param defaultShortcutString string representation of the default shortcut,
* such as "CTRL+S", when the user choses to reset to default
* the keyboard shortcut will return to this one.
*/
GlobalShortcut *
registerShortcut(const QString &uniqueName, const QString &friendlyName, const QString &shortcutString, const QString &defaultShortcutString);
static QString stringFromKeys(const QList<QKeySequence> &keys);
static QList<QKeySequence> keysFromString(const QString &str);
public Q_SLOTS:
// For dbus Q_SCRIPTABLE has to be on slots. Scriptable methods are not
// exported.
/**
* Remove all currently not used global shortcuts registrations for this
* component and if nothing is left the component too.
*
* If the method returns true consider all information previously acquired
* from this component as void.
*
* The method will cleanup in all contexts.
*
* @return @c true if a change was made, @c false if not.
*/
Q_SCRIPTABLE virtual bool cleanUp();
/**
* Check if the component is currently active.
*
* A component is active if at least one of it's global shortcuts is
* currently present.
*/
Q_SCRIPTABLE bool isActive() const;
//! Get all shortcutnames living in @a context
Q_SCRIPTABLE QStringList shortcutNames(const QString &context = QStringLiteral("default")) const;
//! Returns all shortcut in @a context
Q_SCRIPTABLE QList<KGlobalShortcutInfo> allShortcutInfos(const QString &context = QStringLiteral("default")) const;
//! Returns the shortcut contexts available for the component.
Q_SCRIPTABLE QStringList getShortcutContexts() const;
virtual void emitGlobalShortcutPressed(const GlobalShortcut &shortcut);
virtual void emitGlobalShortcutReleased(const GlobalShortcut &shortcut);
Q_SCRIPTABLE void invokeShortcut(const QString &shortcutName, const QString &context = QStringLiteral("default"));
Q_SIGNALS:
//! Signals that a action for this component was triggered
Q_SCRIPTABLE void globalShortcutPressed(const QString &componentUnique, const QString &shortcutUnique, qlonglong timestamp);
//! Signals that a action for this component is not triggered anymore
Q_SCRIPTABLE void globalShortcutReleased(const QString &componentUnique, const QString &shortcutUnique, qlonglong timestamp);
protected:
QString _uniqueName;
// the name as it would be found in a magazine article about the application,
// possibly localized if a localized name exists.
QString _friendlyName;
GlobalShortcutsRegistry *_registry;
GlobalShortcutContext *_current;
QHash<QString, GlobalShortcutContext *> _contexts;
};
#endif /* #ifndef COMPONENT_H */
@@ -0,0 +1 @@
#cmakedefine01 HAVE_X11
@@ -0,0 +1,205 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "globalshortcut.h"
#include "kglobalshortcutinfo_p.h"
#include "component.h"
#include "globalshortcutcontext.h"
#include "globalshortcutsregistry.h"
#include "logging_p.h"
#include <QKeySequence>
GlobalShortcut::GlobalShortcut()
: GlobalShortcut(QString{}, QString{}, nullptr)
{
}
GlobalShortcut::GlobalShortcut(const QString &uniqueName, const QString &friendlyName, GlobalShortcutContext *context)
: _isPresent(false)
, _isRegistered(false)
, _isFresh(true)
, _registry(GlobalShortcutsRegistry::self())
, _context(context)
, _uniqueName(uniqueName)
, _friendlyName(friendlyName)
{
if (_context) {
_context->addShortcut(this);
}
}
GlobalShortcut::~GlobalShortcut()
{
setInactive();
}
GlobalShortcut::operator KGlobalShortcutInfo() const
{
KGlobalShortcutInfo info;
info.d->uniqueName = _uniqueName;
info.d->friendlyName = _friendlyName;
info.d->contextUniqueName = context()->uniqueName();
info.d->contextFriendlyName = context()->friendlyName();
info.d->componentUniqueName = context()->component()->uniqueName();
info.d->componentFriendlyName = context()->component()->friendlyName();
for (const QKeySequence &key : std::as_const(_keys)) {
info.d->keys.append(key);
}
for (const QKeySequence &key : std::as_const(_defaultKeys)) {
info.d->defaultKeys.append(key);
}
return info;
}
bool GlobalShortcut::isActive() const
{
return _isRegistered;
}
bool GlobalShortcut::isFresh() const
{
return _isFresh;
}
bool GlobalShortcut::isPresent() const
{
return _isPresent;
}
bool GlobalShortcut::isSessionShortcut() const
{
return uniqueName().startsWith(QLatin1String("_k_session:"));
}
void GlobalShortcut::setIsFresh(bool value)
{
_isFresh = value;
}
void GlobalShortcut::setIsPresent(bool value)
{
// (de)activate depending on old/new value
_isPresent = value;
if (_isPresent) {
setActive();
} else {
setInactive();
}
}
GlobalShortcutContext *GlobalShortcut::context()
{
return _context;
}
GlobalShortcutContext const *GlobalShortcut::context() const
{
return _context;
}
QString GlobalShortcut::uniqueName() const
{
return _uniqueName;
}
void GlobalShortcut::unRegister()
{
return _context->component()->unregisterShortcut(uniqueName());
}
QString GlobalShortcut::friendlyName() const
{
return _friendlyName;
}
void GlobalShortcut::setFriendlyName(const QString &name)
{
_friendlyName = name;
}
QList<QKeySequence> GlobalShortcut::keys() const
{
return _keys;
}
void GlobalShortcut::setKeys(const QList<QKeySequence> &newKeys)
{
bool active = _isRegistered;
if (active) {
setInactive();
}
_keys.clear();
auto getKey = [this](const QKeySequence &key) {
if (key.isEmpty()) {
qCDebug(KGLOBALACCELD) << _uniqueName << "skipping because key is empty";
return QKeySequence{};
}
if (_registry->getShortcutByKey(key) //
|| _registry->getShortcutByKey(key, KGlobalAccel::MatchType::Shadowed) //
|| _registry->getShortcutByKey(key, KGlobalAccel::MatchType::Shadows) //
) {
qCDebug(KGLOBALACCELD) << _uniqueName << "skipping because key" << QKeySequence(key).toString() << "is already taken";
return QKeySequence{};
}
return key;
};
std::transform(newKeys.cbegin(), newKeys.cend(), std::back_inserter(_keys), getKey);
if (active) {
setActive();
}
}
QList<QKeySequence> GlobalShortcut::defaultKeys() const
{
return _defaultKeys;
}
void GlobalShortcut::setDefaultKeys(const QList<QKeySequence> &newKeys)
{
_defaultKeys = newKeys;
}
void GlobalShortcut::setActive()
{
if (!_isPresent || _isRegistered) {
// The corresponding application is not present or the keys are
// already grabbed
return;
}
for (const QKeySequence &key : std::as_const(_keys)) {
if (!key.isEmpty() && !_registry->registerKey(key, this)) {
qCDebug(KGLOBALACCELD) << uniqueName() << ": Failed to register " << QKeySequence(key).toString();
}
}
_isRegistered = true;
}
void GlobalShortcut::setInactive()
{
if (!_isRegistered) {
// The keys are not grabbed currently
return;
}
for (const QKeySequence &key : std::as_const(_keys)) {
if (!key.isEmpty() && !_registry->unregisterKey(key, this)) {
qCDebug(KGLOBALACCELD) << uniqueName() << ": Failed to unregister " << QKeySequence(key).toString();
}
}
_isRegistered = false;
}
@@ -0,0 +1,107 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef GLOBALSHORTCUT_H
#define GLOBALSHORTCUT_H
#include <KGlobalShortcutInfo>
class GlobalShortcutContext;
class GlobalShortcutsRegistry;
/**
* Represents a global shortcut.
*
* @internal
*
* \note This class can handle multiple keys (default and active). This
* feature isn't used currently. kde4 only allows setting one key per global
* shortcut.
*
* @author Michael Jansen <kde@michael-jansen.biz>
*/
class GlobalShortcut
{
public:
GlobalShortcut(const QString &uniqueName, const QString &friendlyName, GlobalShortcutContext *context);
GlobalShortcut();
~GlobalShortcut();
//! Returns the context the shortcuts belongs to
GlobalShortcutContext *context();
GlobalShortcutContext const *context() const;
//! Returns the default keys for this shortcut.
QList<QKeySequence> defaultKeys() const;
//! Return the friendly display name for this shortcut.
QString friendlyName() const;
//! Check if the shortcut is active. It's keys are grabbed
bool isActive() const;
//! Check if the shortcut is fresh/new. Is an internal state
bool isFresh() const;
//! Check if the shortcut is present. It application is running.
bool isPresent() const;
//! Returns true if the shortcut is a session shortcut
bool isSessionShortcut() const;
//! Returns a list of keys associated with this shortcut.
QList<QKeySequence> keys() const;
//! Activates the shortcut. The keys are grabbed.
void setActive();
//! Sets the default keys for this shortcut.
void setDefaultKeys(const QList<QKeySequence> &);
//! Sets the friendly name for the shortcut. For display.
void setFriendlyName(const QString &);
//! Sets the shortcut inactive. No longer grabs the keys.
void setInactive();
void setIsPresent(bool);
void setIsFresh(bool);
//! Sets the keys activated with this shortcut. The old keys are freed.
void setKeys(const QList<QKeySequence> &);
//! Returns the unique name aka id for the shortcuts.
QString uniqueName() const;
operator KGlobalShortcutInfo() const;
//! Remove this shortcut and it's siblings
void unRegister();
private:
//! means the associated application is present.
bool _isPresent : 1;
//! means the shortcut is registered with GlobalShortcutsRegistry
bool _isRegistered : 1;
//! means the shortcut is new
bool _isFresh : 1;
GlobalShortcutsRegistry *_registry = nullptr;
//! The context the shortcut belongs too
GlobalShortcutContext *_context = nullptr;
QString _uniqueName;
QString _friendlyName; // usually localized
QList<QKeySequence> _keys;
QList<QKeySequence> _defaultKeys;
};
#endif /* #ifndef GLOBALSHORTCUT_H */
@@ -0,0 +1,110 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "globalshortcutcontext.h"
#include "globalshortcut.h"
#include "kglobalaccel.h"
#include "sequencehelpers_p.h"
GlobalShortcutContext::GlobalShortcutContext(const QString &uniqueName, const QString &friendlyName, Component *component)
: _uniqueName(uniqueName)
, _friendlyName(friendlyName)
, _component(component)
{
}
GlobalShortcutContext::~GlobalShortcutContext()
{
qDeleteAll(_actionsMap);
_actionsMap.clear();
}
void GlobalShortcutContext::addShortcut(GlobalShortcut *shortcut)
{
_actionsMap.insert(shortcut->uniqueName(), shortcut);
}
QList<KGlobalShortcutInfo> GlobalShortcutContext::allShortcutInfos() const
{
QList<KGlobalShortcutInfo> rc;
for (GlobalShortcut *shortcut : std::as_const(_actionsMap)) {
rc.append(static_cast<KGlobalShortcutInfo>(*shortcut));
}
return rc;
}
Component const *GlobalShortcutContext::component() const
{
return _component;
}
Component *GlobalShortcutContext::component()
{
return _component;
}
QString GlobalShortcutContext::friendlyName() const
{
return _friendlyName;
}
GlobalShortcut *GlobalShortcutContext::getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
{
if (key.isEmpty()) {
return nullptr;
}
QKeySequence keyMangled = Utils::mangleKey(key);
for (GlobalShortcut *sc : std::as_const(_actionsMap)) {
const auto keys = sc->keys();
for (const QKeySequence &other : keys) {
QKeySequence otherMangled = Utils::mangleKey(other);
switch (type) {
case KGlobalAccel::MatchType::Equal:
if (otherMangled == keyMangled) {
return sc;
}
break;
case KGlobalAccel::MatchType::Shadows:
if (!other.isEmpty() && Utils::contains(keyMangled, otherMangled)) {
return sc;
}
break;
case KGlobalAccel::MatchType::Shadowed:
if (!other.isEmpty() && Utils::contains(otherMangled, keyMangled)) {
return sc;
}
break;
}
}
}
return nullptr;
}
GlobalShortcut *GlobalShortcutContext::takeShortcut(GlobalShortcut *shortcut)
{
// Try to take the shortcut. Result could be nullptr if the shortcut doesn't
// belong to this component.
return _actionsMap.take(shortcut->uniqueName());
}
QString GlobalShortcutContext::uniqueName() const
{
return _uniqueName;
}
bool GlobalShortcutContext::isShortcutAvailable(const QKeySequence &key) const
{
for (auto it = _actionsMap.cbegin(), endIt = _actionsMap.cend(); it != endIt; ++it) {
const GlobalShortcut *sc = it.value();
if (Utils::matchSequences(key, sc->keys())) {
return false;
}
}
return true;
}
@@ -0,0 +1,78 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef GLOBALSHORTCUTCONTEXT_H
#define GLOBALSHORTCUTCONTEXT_H
#include "kglobalshortcutinfo.h"
#include <QHash>
#include <QString>
#include <kglobalaccel.h>
class Component;
class GlobalShortcut;
/**
* @author Michael Jansen <kde@michael-jansen.biz>
*/
class GlobalShortcutContext
{
public:
/**
* Default constructor
*/
GlobalShortcutContext(const QString &uniqueName, const QString &friendlyName, Component *component);
/**
* Destructor
*/
virtual ~GlobalShortcutContext();
//! Adds @p shortcut to the context
void addShortcut(GlobalShortcut *shortcut);
//! Return KGlobalShortcutInfos for all shortcuts
QList<KGlobalShortcutInfo> allShortcutInfos() const;
/**
* Get the name for the context
*/
QString uniqueName() const;
QString friendlyName() const;
Component *component();
Component const *component() const;
//! Get shortcut for @p key or nullptr
GlobalShortcut *getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const;
//! Remove @p shortcut from the context. The shortcut is not deleted.
GlobalShortcut *takeShortcut(GlobalShortcut *shortcut);
// Returns true if key is not used by any global shortcuts in this context,
// otherwise returns false
bool isShortcutAvailable(const QKeySequence &key) const;
private:
friend class Component;
friend class KServiceActionComponent;
//! The unique name for this context
QString _uniqueName;
//! The unique name for this context
QString _friendlyName;
//! The component the context belongs to
Component *_component = nullptr;
//! The actions associated with this context
QHash<QString, GlobalShortcut *> _actionsMap;
};
#endif /* #ifndef GLOBALSHORTCUTCONTEXT_H */
@@ -0,0 +1,816 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-FileCopyrightText: 2022 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "globalshortcutsregistry.h"
#include "component.h"
#include "globalshortcut.h"
#include "globalshortcutcontext.h"
#include "kglobalaccel_interface.h"
#include "kglobalshortcutinfo_p.h"
#include "kserviceactioncomponent.h"
#include "logging_p.h"
#include <config-kglobalaccel.h>
#include <KDesktopFile>
#include <KFileUtils>
#include <KPluginMetaData>
#include <KApplicationTrader>
#include <QDBusConnection>
#include <QDir>
#include <QGuiApplication>
#include <QJsonArray>
#include <QPluginLoader>
#include <QStandardPaths>
static bool checkPlatform(const QJsonObject &metadata, const QString &platformName)
{
const QJsonArray platforms = metadata.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("platforms")).toArray();
return std::any_of(platforms.begin(), platforms.end(), [&platformName](const QJsonValue &value) {
return QString::compare(platformName, value.toString(), Qt::CaseInsensitive) == 0;
});
}
static KGlobalAccelInterface *loadPlugin(GlobalShortcutsRegistry *parent)
{
QString platformName = QString::fromLocal8Bit(qgetenv("KGLOBALACCELD_PLATFORM"));
if (platformName.isEmpty()) {
platformName = QGuiApplication::platformName();
}
const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins();
for (const QStaticPlugin &staticPlugin : staticPlugins) {
const QJsonObject metadata = staticPlugin.metaData();
if (metadata.value(QLatin1String("IID")) != QLatin1String(KGlobalAccelInterface_iid)) {
continue;
}
if (checkPlatform(metadata, platformName)) {
KGlobalAccelInterface *interface = qobject_cast<KGlobalAccelInterface *>(staticPlugin.instance());
if (interface) {
qCDebug(KGLOBALACCELD) << "Loaded a static plugin for platform" << platformName;
interface->setRegistry(parent);
return interface;
}
}
}
const QList<KPluginMetaData> candidates = KPluginMetaData::findPlugins(QStringLiteral("org.kde.kglobalacceld.platforms"));
for (const KPluginMetaData &candidate : candidates) {
QPluginLoader loader(candidate.fileName());
if (checkPlatform(loader.metaData(), platformName)) {
KGlobalAccelInterface *interface = qobject_cast<KGlobalAccelInterface *>(loader.instance());
if (interface) {
qCDebug(KGLOBALACCELD) << "Loaded plugin" << candidate.fileName() << "for platform" << platformName;
interface->setRegistry(parent);
return interface;
}
}
}
qCWarning(KGLOBALACCELD) << "Could not find any platform plugin";
return nullptr;
}
static QString getConfigFile()
{
return qEnvironmentVariableIsSet("KGLOBALACCEL_TEST_MODE") ? QString() : QStringLiteral("kglobalshortcutsrc");
}
void GlobalShortcutsRegistry::migrateKHotkeys()
{
KConfig hotkeys(QStringLiteral("khotkeysrc"));
int dataCount = hotkeys.group(QStringLiteral("Data")).readEntry("DataCount", 0);
for (int i = 1; i <= dataCount; ++i) {
const QString groupName = QStringLiteral("Data_") + QString::number(i);
KConfigGroup dataGroup(&hotkeys, groupName);
if (dataGroup.readEntry("Type") != QLatin1String("SIMPLE_ACTION_DATA")) {
continue;
}
const QString name = dataGroup.readEntry("Name");
QString exec;
QString uuid;
int actionsCount = KConfigGroup(&hotkeys, groupName + QLatin1String("Actions")).readEntry("ActionsCount", 0);
for (int i = 0; i < actionsCount; ++i) {
KConfigGroup actionGroup = KConfigGroup(&hotkeys, groupName + QLatin1String("Actions") + QString::number(i));
const QString type = actionGroup.readEntry("Type");
if (type == QLatin1String("COMMAND_URL")) {
exec = actionGroup.readEntry("CommandURL");
} else if (type == QLatin1String("DBUS")) {
exec = QStringLiteral(QT_STRINGIFY(QDBUS) " %1 %2 %3")
.arg(actionGroup.readEntry("RemoteApp"), actionGroup.readEntry("RemoteObj"), actionGroup.readEntry("Call"));
const QString args = actionGroup.readEntry("Arguments");
if (!args.isEmpty()) {
exec += QLatin1Char(' ') + args;
}
}
}
if (exec.isEmpty()) {
continue;
}
int triggerCount = KConfigGroup(&hotkeys, groupName + QLatin1String("Triggers")).readEntry("TriggersCount", 0);
for (int i = 0; i < triggerCount; ++i) {
KConfigGroup triggerGroup = KConfigGroup(&hotkeys, groupName + QLatin1String("Triggers") + QString::number(i));
if (triggerGroup.readEntry("Type") != QLatin1String("SHORTCUT")) {
continue;
}
uuid = triggerGroup.readEntry("Uuid");
}
const QString kglobalaccelEntry = _config.group(QStringLiteral("khotkeys")).readEntry(uuid);
if (kglobalaccelEntry.isEmpty()) {
continue;
}
const QString key = kglobalaccelEntry.split(QLatin1Char(',')).first();
KDesktopFile file(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kglobalaccel/") + uuid
+ QLatin1String(".desktop"));
file.desktopGroup().writeEntry("Type", "Application");
file.desktopGroup().writeEntry("Name", name);
file.desktopGroup().writeEntry("Exec", exec);
file.desktopGroup().writeEntry("X-KDE-GlobalAccel-CommandShortcut", true);
file.desktopGroup().writeEntry("StartupNotify", false);
_config.group(QStringLiteral("services")).group(uuid + QLatin1String(".desktop")).writeEntry("_launch", kglobalaccelEntry);
_config.group(QStringLiteral("khotkeys")).revertToDefault(uuid);
}
}
/*
* Migrate the config for service actions to a new format that only stores the actual shortcut if not default.
* All other information is read from the desktop file.
* Keep the old data for compatibility with KF5-based kglobalaccel.
* Once Plasma 6 settles down consider dropping this data
*/
void GlobalShortcutsRegistry::migrateConfig()
{
const QStringList groups = _config.groupList();
KConfigGroup services = _config.group(QStringLiteral("services"));
for (const QString &componentName : groups) {
if (!componentName.endsWith(QLatin1String(".desktop"))) {
continue;
}
KConfigGroup component = _config.group(componentName);
KConfigGroup newGroup = services.group(componentName);
for (auto [key, value] : component.entryMap().asKeyValueRange()) {
if (key == QLatin1String("_k_friendly_name")) {
continue;
}
const QString shortcut = value.split(QLatin1Char(','))[0];
const QString defaultShortcut = value.split(QLatin1Char(','))[1];
if (shortcut != defaultShortcut) {
newGroup.writeEntry(key, shortcut);
}
}
component.deleteGroup();
}
// Migrate dynamic shortcuts to service-based shortcuts
const QStringList desktopPaths =
QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kglobalaccel"), QStandardPaths::LocateDirectory);
const QStringList desktopFiles = KFileUtils::findAllUniqueFiles(desktopPaths, {QStringLiteral("*.desktop")});
for (const QString &fileName : desktopFiles) {
KDesktopFile file(fileName);
const QString componentName = QFileInfo(fileName).fileName();
auto migrateTo = [this, componentName](KConfigGroup &group, const QString &actionName, const QString displayName) {
QString migrateFrom = group.readEntry<QString>(QStringLiteral("X-KDE-Migrate-Shortcut"), QString());
if (migrateFrom.isEmpty()) {
return;
}
const QStringList migrateFromParts = migrateFrom.split(QLatin1Char(','));
if (!_config.group(migrateFromParts[0]).hasKey(migrateFromParts[1])) {
// Probably already migrated
return;
}
const QStringList shortcutTriple = _config.group(migrateFromParts[0]).readEntry<QStringList>(migrateFromParts[1], QStringList());
const QString oldShortcut = shortcutTriple[0];
const QString oldDefaultShortcut = shortcutTriple[1];
const QString newDefaultShortcut = group.readEntry<QString>("X-KDE-Shortcuts", QString());
// Only write value if it is not the old or new default
if (oldShortcut != oldDefaultShortcut && oldShortcut != newDefaultShortcut) {
_config.group(QStringLiteral("services")).group(componentName).writeEntry(actionName, oldShortcut);
}
_config.group(migrateFromParts[0]).deleteEntry(migrateFromParts[1]);
if (_config.group(migrateFromParts[0]).entryMap().size() == 1) {
// only _k_friendly_name left, remove the group
_config.deleteGroup(migrateFromParts[0]);
}
};
KConfigGroup desktopGroup = file.desktopGroup();
migrateTo(desktopGroup, QStringLiteral("_launch"), file.readName());
const QStringList actions = file.readActions();
for (const QString &action : actions) {
KConfigGroup actionGroup = file.actionGroup(action);
migrateTo(actionGroup, action, file.actionGroup(action).readEntry<QString>("Name", QString()));
}
}
_config.sync();
}
GlobalShortcutsRegistry::GlobalShortcutsRegistry()
: QObject()
, _manager(loadPlugin(this))
, _config(getConfigFile(), KConfig::SimpleConfig)
{
migrateKHotkeys();
migrateConfig();
if (_manager) {
_manager->setEnabled(true);
}
}
GlobalShortcutsRegistry::~GlobalShortcutsRegistry()
{
m_components.clear();
if (_manager) {
_manager->setEnabled(false);
// Ungrab all keys. We don't go over GlobalShortcuts because
// GlobalShortcutsRegistry::self() doesn't work anymore.
const auto listKeys = _active_keys.keys();
for (const QKeySequence &key : listKeys) {
for (int i = 0; i < key.count(); i++) {
_manager->grabKey(key[i].toCombined(), false);
}
}
}
_active_keys.clear();
_keys_count.clear();
}
Component *GlobalShortcutsRegistry::registerComponent(ComponentPtr component)
{
m_components.push_back(std::move(component));
auto *comp = m_components.back().get();
Q_ASSERT(!comp->dbusPath().path().isEmpty());
QDBusConnection conn(QDBusConnection::sessionBus());
conn.registerObject(comp->dbusPath().path(), comp, QDBusConnection::ExportScriptableContents);
return comp;
}
void GlobalShortcutsRegistry::activateShortcuts()
{
for (auto &component : m_components) {
component->activateShortcuts();
}
}
QList<QDBusObjectPath> GlobalShortcutsRegistry::componentsDbusPaths() const
{
QList<QDBusObjectPath> dbusPaths;
dbusPaths.reserve(m_components.size());
std::transform(m_components.cbegin(), m_components.cend(), std::back_inserter(dbusPaths), [](const auto &comp) {
return comp->dbusPath();
});
return dbusPaths;
}
QList<QStringList> GlobalShortcutsRegistry::allComponentNames() const
{
QList<QStringList> ret;
ret.reserve(m_components.size());
std::transform(m_components.cbegin(), m_components.cend(), std::back_inserter(ret), [](const auto &component) {
// A string for each enumerator in KGlobalAccel::actionIdFields
return QStringList{component->uniqueName(), component->friendlyName(), {}, {}};
});
return ret;
}
void GlobalShortcutsRegistry::clear()
{
m_components.clear();
// The shortcuts should have deregistered themselves
Q_ASSERT(_active_keys.isEmpty());
}
QDBusObjectPath GlobalShortcutsRegistry::dbusPath() const
{
return _dbusPath;
}
void GlobalShortcutsRegistry::deactivateShortcuts(bool temporarily)
{
for (ComponentPtr &component : m_components) {
component->deactivateShortcuts(temporarily);
}
}
Component *GlobalShortcutsRegistry::getComponent(const QString &uniqueName)
{
auto it = findByName(uniqueName);
return it != m_components.cend() ? (*it).get() : nullptr;
}
GlobalShortcut *GlobalShortcutsRegistry::getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
{
for (const ComponentPtr &component : m_components) {
GlobalShortcut *rc = component->getShortcutByKey(key, type);
if (rc) {
return rc;
}
}
return nullptr;
}
QList<GlobalShortcut *> GlobalShortcutsRegistry::getShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
{
QList<GlobalShortcut *> rc;
for (const ComponentPtr &component : m_components) {
rc = component->getShortcutsByKey(key, type);
if (!rc.isEmpty()) {
return rc;
}
}
return {};
}
bool GlobalShortcutsRegistry::isShortcutAvailable(const QKeySequence &shortcut, const QString &componentName, const QString &contextName) const
{
return std::all_of(m_components.cbegin(), m_components.cend(), [&shortcut, &componentName, &contextName](const ComponentPtr &component) {
return component->isShortcutAvailable(shortcut, componentName, contextName);
});
}
Q_GLOBAL_STATIC(GlobalShortcutsRegistry, _self)
GlobalShortcutsRegistry *GlobalShortcutsRegistry::self()
{
return _self;
}
static void correctKeyEvent(int &keyQt)
{
// When we are provided just a Shift key press, interpret it as "Shift" not as "Shift+Shift"
switch (keyQt) {
case (Qt::ShiftModifier | Qt::Key_Shift).toCombined():
keyQt = Qt::Key_Shift;
break;
case (Qt::ControlModifier | Qt::Key_Control).toCombined():
keyQt = Qt::Key_Control;
break;
case (Qt::AltModifier | Qt::Key_Alt).toCombined():
keyQt = Qt::Key_Alt;
break;
case (Qt::MetaModifier | Qt::Key_Meta).toCombined():
keyQt = Qt::Key_Meta;
break;
}
// Known limitation:
// When shortcut is Mod(s)+Alt+Print, only works when Alt is released before Mod(s),
// Does not work with multikey shortcuts.
// When the user presses Mod(s)+Alt+Print, the SysReq event is fired only
// when the Alt key is released. Before we get the Mod(s)+SysReq event, we
// first get a Mod(s)+Alt event, breaking multikey shortcuts.
if ((keyQt & ~Qt::KeyboardModifierMask) == Qt::Key_SysReq) {
keyQt = Qt::Key_Print | (keyQt & Qt::KeyboardModifierMask) | Qt::AltModifier;
}
}
bool GlobalShortcutsRegistry::keyPressed(int keyQt)
{
correctKeyEvent(keyQt);
int keys[maxSequenceLength] = {0, 0, 0, 0};
int count = _active_sequence.count();
if (count == maxSequenceLength) {
// buffer is full, rotate it
for (int i = 1; i < count; i++) {
keys[i - 1] = _active_sequence[i].toCombined();
}
keys[maxSequenceLength - 1] = keyQt;
} else {
// just append the new key
for (int i = 0; i < count; i++) {
keys[i] = _active_sequence[i].toCombined();
}
keys[count] = keyQt;
}
_active_sequence = QKeySequence(keys[0], keys[1], keys[2], keys[3]);
GlobalShortcut *shortcut = nullptr;
QKeySequence tempSequence;
for (int length = 1; length <= _active_sequence.count(); length++) {
// We have to check all possible matches from the end since we're rotating active sequence
// instead of cleaning it when it's full
int sequenceToCheck[maxSequenceLength] = {0, 0, 0, 0};
for (int i = 0; i < length; i++) {
sequenceToCheck[i] = _active_sequence[_active_sequence.count() - length + i].toCombined();
}
tempSequence = QKeySequence(sequenceToCheck[0], sequenceToCheck[1], sequenceToCheck[2], sequenceToCheck[3]);
shortcut = getShortcutByKey(tempSequence);
if (shortcut) {
break;
}
}
qCDebug(KGLOBALACCELD) << "Pressed key" << QKeySequence(keyQt).toString() << ", current sequence" << _active_sequence.toString() << "="
<< (shortcut ? shortcut->uniqueName() : QStringLiteral("(no shortcut found)"));
if (!shortcut) {
// This can happen for example with the ALT-Print shortcut of kwin.
// ALT+PRINT is SYSREQ on my keyboard. So we grab something we think
// is ALT+PRINT but symXToKeyQt and modXToQt make ALT+SYSREQ of it
// when pressed (correctly). We can't match that.
qCDebug(KGLOBALACCELD) << "Got unknown key" << QKeySequence(keyQt).toString();
// In production mode just do nothing.
return false;
} else if (!shortcut->isActive()) {
qCDebug(KGLOBALACCELD) << "Got inactive key" << QKeySequence(keyQt).toString();
// In production mode just do nothing.
return false;
}
qCDebug(KGLOBALACCELD) << QKeySequence(keyQt).toString() << "=" << shortcut->uniqueName();
// shortcut is found, reset active sequence
_active_sequence = QKeySequence();
QStringList data(shortcut->context()->component()->uniqueName());
data.append(shortcut->uniqueName());
data.append(shortcut->context()->component()->friendlyName());
data.append(shortcut->friendlyName());
if (m_lastShortcut && m_lastShortcut != shortcut) {
m_lastShortcut->context()->component()->emitGlobalShortcutReleased(*m_lastShortcut);
}
// Invoke the action
shortcut->context()->component()->emitGlobalShortcutPressed(*shortcut);
m_lastShortcut = shortcut;
return true;
}
bool GlobalShortcutsRegistry::keyReleased(int keyQt)
{
Q_UNUSED(keyQt)
if (m_lastShortcut) {
m_lastShortcut->context()->component()->emitGlobalShortcutReleased(*m_lastShortcut);
m_lastShortcut = nullptr;
}
return false;
}
Component *GlobalShortcutsRegistry::createComponent(const QString &uniqueName, const QString &friendlyName)
{
auto it = findByName(uniqueName);
if (it != m_components.cend()) {
Q_ASSERT_X(false, //
"GlobalShortcutsRegistry::createComponent",
QLatin1String("A Component with the name: %1, already exists").arg(uniqueName).toUtf8().constData());
return (*it).get();
}
auto *c = registerComponent(ComponentPtr(new Component(uniqueName, friendlyName), &unregisterComponent));
return c;
}
void GlobalShortcutsRegistry::unregisterComponent(Component *component)
{
QDBusConnection::sessionBus().unregisterObject(component->dbusPath().path());
delete component;
}
KServiceActionComponent *GlobalShortcutsRegistry::createServiceActionComponent(KService::Ptr service)
{
auto it = findByName(service->storageId());
if (it != m_components.cend()) {
Q_ASSERT_X(false, //
"GlobalShortcutsRegistry::createServiceActionComponent",
QLatin1String("A KServiceActionComponent with the name: %1, already exists").arg(service->storageId()).toUtf8().constData());
return static_cast<KServiceActionComponent *>((*it).get());
}
auto *c = registerComponent(ComponentPtr(new KServiceActionComponent(service), &unregisterComponent));
return static_cast<KServiceActionComponent *>(c);
}
KServiceActionComponent *GlobalShortcutsRegistry::createServiceActionComponent(const QString &uniqueName)
{
auto it = findByName(uniqueName);
if (it != m_components.cend()) {
Q_ASSERT_X(false, //
"GlobalShortcutsRegistry::createServiceActionComponent",
QLatin1String("A KServiceActionComponent with the name: %1, already exists").arg(uniqueName).toUtf8().constData());
return static_cast<KServiceActionComponent *>((*it).get());
}
KService::Ptr service = KService::serviceByStorageId(uniqueName);
if (!service) {
const QString filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kglobalaccel/") + uniqueName);
if (filePath.isEmpty()) {
return nullptr;
}
service = new KService(filePath);
}
auto *c = registerComponent(ComponentPtr(new KServiceActionComponent(service), &unregisterComponent));
return static_cast<KServiceActionComponent *>(c);
}
void GlobalShortcutsRegistry::loadSettings()
{
if (!m_components.empty()) {
qCDebug(KGLOBALACCELD) << "Registry settings already loaded. Skipped loading again.";
return;
}
auto groupList = _config.groupList();
for (const QString &groupName : groupList) {
if (groupName == QLatin1String("services")) {
continue;
}
if (groupName.endsWith(QLatin1String(".desktop"))) {
continue;
}
qCDebug(KGLOBALACCELD) << "Loading group " << groupName;
Q_ASSERT(groupName.indexOf(QLatin1Char('\x1d')) == -1);
// loadSettings isn't designed to be called in between. Only at the
// beginning.
Q_ASSERT(!getComponent(groupName));
KConfigGroup configGroup(&_config, groupName);
const QString friendlyName = configGroup.readEntry("_k_friendly_name");
Component *component = createComponent(groupName, friendlyName);
// Now load the contexts
const auto groupList = configGroup.groupList();
for (const QString &context : groupList) {
// Skip the friendly name group, this was previously used instead of _k_friendly_name
if (context == QLatin1String("Friendly Name")) {
continue;
}
KConfigGroup contextGroup(&configGroup, context);
QString contextFriendlyName = contextGroup.readEntry("_k_friendly_name");
component->createGlobalShortcutContext(context, contextFriendlyName);
component->activateGlobalShortcutContext(context);
component->loadSettings(contextGroup);
}
// Load the default context
component->activateGlobalShortcutContext(QStringLiteral("default"));
component->loadSettings(configGroup);
}
groupList = _config.group(QStringLiteral("services")).groupList();
for (const QString &groupName : groupList) {
qCDebug(KGLOBALACCELD) << "Loading group " << groupName;
Q_ASSERT(groupName.indexOf(QLatin1Char('\x1d')) == -1);
// loadSettings isn't designed to be called in between. Only at the
// beginning.
Q_ASSERT(!getComponent(groupName));
KConfigGroup configGroup = _config.group(QStringLiteral("services")).group(groupName);
Component *component = createServiceActionComponent(groupName);
if (!component) {
qDebug() << "could not create a component for " << groupName;
continue;
}
Q_ASSERT(!component->uniqueName().isEmpty());
// Now load the contexts
const auto groupList = configGroup.groupList();
for (const QString &context : groupList) {
// Skip the friendly name group, this was previously used instead of _k_friendly_name
if (context == QLatin1String("Friendly Name")) {
continue;
}
KConfigGroup contextGroup(&configGroup, context);
QString contextFriendlyName = contextGroup.readEntry("_k_friendly_name");
component->createGlobalShortcutContext(context, contextFriendlyName);
component->activateGlobalShortcutContext(context);
component->loadSettings(contextGroup);
}
// Load the default context
component->activateGlobalShortcutContext(QStringLiteral("default"));
component->loadSettings(configGroup);
}
// Load the configured KServiceActions
const QStringList desktopPaths =
QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kglobalaccel"), QStandardPaths::LocateDirectory);
const QStringList desktopFiles = KFileUtils::findAllUniqueFiles(desktopPaths, {QStringLiteral("*.desktop")});
for (const QString &file : desktopFiles) {
const QString fileName = QFileInfo(file).fileName();
auto it = findByName(fileName);
if (it != m_components.cend()) {
continue;
}
KService::Ptr service(new KService(file));
if (service->noDisplay()) {
continue;
}
auto *actionComp = createServiceActionComponent(service);
actionComp->activateGlobalShortcutContext(QStringLiteral("default"));
actionComp->loadFromService();
}
auto appsWithShortcuts = KApplicationTrader::query([](const KService::Ptr &service) {
return !service->property<QString>(QStringLiteral("X-KDE-Shortcuts")).isEmpty();
});
for (auto service : appsWithShortcuts) {
auto it = findByName(service->storageId());
if (it != m_components.cend()) {
continue;
}
auto *actionComp = createServiceActionComponent(service);
actionComp->activateGlobalShortcutContext(QStringLiteral("default"));
actionComp->loadFromService();
}
}
void GlobalShortcutsRegistry::grabKeys()
{
activateShortcuts();
}
bool GlobalShortcutsRegistry::registerKey(const QKeySequence &key, GlobalShortcut *shortcut)
{
if (!_manager) {
return false;
}
if (key.isEmpty()) {
qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Key '" << QKeySequence(key).toString() << "' already taken by "
<< _active_keys.value(key)->uniqueName() << ".";
return false;
} else if (_active_keys.value(key)) {
qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Attempt to register key 0.";
return false;
}
qCDebug(KGLOBALACCELD) << "Registering key" << QKeySequence(key).toString() << "for" << shortcut->context()->component()->uniqueName() << ":"
<< shortcut->uniqueName();
bool error = false;
int i;
for (i = 0; i < key.count(); i++) {
const int combined = key[i].toCombined();
if (!_manager->grabKey(combined, true)) {
error = true;
break;
}
++_keys_count[combined];
}
if (error) {
// Last key was not registered, rewind index by 1
for (--i; i >= 0; i--) {
const int combined = key[i].toCombined();
auto it = _keys_count.find(combined);
if (it == _keys_count.end()) {
continue;
}
if (it.value() == 1) {
_keys_count.erase(it);
_manager->grabKey(combined, false);
} else {
--(it.value());
}
}
return false;
}
_active_keys.insert(key, shortcut);
return true;
}
void GlobalShortcutsRegistry::setDBusPath(const QDBusObjectPath &path)
{
_dbusPath = path;
}
void GlobalShortcutsRegistry::ungrabKeys()
{
deactivateShortcuts();
}
bool GlobalShortcutsRegistry::unregisterKey(const QKeySequence &key, GlobalShortcut *shortcut)
{
if (!_manager) {
return false;
}
if (_active_keys.value(key) != shortcut) {
// The shortcut doesn't own the key or the key isn't grabbed
return false;
}
for (int i = 0; i < key.count(); i++) {
auto iter = _keys_count.find(key[i].toCombined());
if ((iter == _keys_count.end()) || (iter.value() <= 0)) {
continue;
}
// Unregister if there's only one ref to given key
// We should fail earlier when key is not registered
if (iter.value() == 1) {
qCDebug(KGLOBALACCELD) << "Unregistering key" << QKeySequence(key[i]).toString() << "for" << shortcut->context()->component()->uniqueName() << ":"
<< shortcut->uniqueName();
_manager->grabKey(key[i].toCombined(), false);
_keys_count.erase(iter);
} else {
qCDebug(KGLOBALACCELD) << "Refused to unregister key" << QKeySequence(key[i]).toString() << ": used by another global shortcut";
--(iter.value());
}
}
if (shortcut && shortcut == m_lastShortcut) {
m_lastShortcut->context()->component()->emitGlobalShortcutReleased(*m_lastShortcut);
m_lastShortcut = nullptr;
}
_active_keys.remove(key);
return true;
}
void GlobalShortcutsRegistry::writeSettings()
{
auto it = std::remove_if(m_components.begin(), m_components.end(), [this](const ComponentPtr &component) {
bool isService = component->uniqueName().endsWith(QLatin1String(".desktop"));
KConfigGroup configGroup =
isService ? _config.group(QStringLiteral("services")).group(component->uniqueName()) : _config.group(component->uniqueName());
if (component->allShortcuts().isEmpty()) {
configGroup.deleteGroup();
return true;
} else {
component->writeSettings(configGroup);
return false;
}
});
m_components.erase(it, m_components.end());
_config.sync();
}
#include "moc_globalshortcutsregistry.cpp"
@@ -0,0 +1,180 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef GLOBALSHORTCUTSREGISTRY_H
#define GLOBALSHORTCUTSREGISTRY_H
#include "kglobalaccel.h"
#include "component.h"
#include "kserviceactioncomponent.h"
#include <KSharedConfig>
#include <QDBusObjectPath>
#include <QHash>
#include <QKeySequence>
#include <QObject>
#include "kglobalaccel_export.h"
class Component;
class GlobalShortcut;
class KGlobalAccelInterface;
/**
* Global Shortcut Registry.
*
* Shortcuts are registered by component. A component is for example kmail or
* amarok.
*
* A component can have contexts. Currently on plasma is planned to support
* that feature. A context enables plasma to keep track of global shortcut
* settings when switching containments.
*
* A shortcut (WIN+x) can be registered by one component only. The component
* is allowed to register it more than once in different contexts.
*
* @author Michael Jansen <kde@michael-jansen.biz>
*/
class KGLOBALACCEL_EXPORT GlobalShortcutsRegistry : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KdedGlobalAccel.GlobalShortcutsRegistry")
public:
/**
* Use GlobalShortcutsRegistry::self()
*
* @internal
*/
GlobalShortcutsRegistry();
~GlobalShortcutsRegistry() override;
/**
* Activate all shortcuts having their application present.
*/
void activateShortcuts();
/**
* Returns a list of D-Bus paths of registered Components.
*
* The returned paths are absolute (i.e. no need to prepend anything).
*/
QList<QDBusObjectPath> componentsDbusPaths() const;
/**
* Returns a list of QStringLists (one string list per registered component,
* with each string list containing four strings, one for each enumerator in
* KGlobalAccel::actionIdFields).
*/
QList<QStringList> allComponentNames() const;
/**
* Return the root dbus path for the registry.
*/
QDBusObjectPath dbusPath() const;
/**
* Deactivate all currently active shortcuts.
*/
void deactivateShortcuts(bool temporarily = false);
/**
*/
Component *getComponent(const QString &uniqueName);
/**
* Get the shortcut corresponding to key. Active and inactive shortcuts
* are considered. But if the matching application uses contexts only one
* shortcut is returned.
*
* @see getShortcutsByKey(int key)
*/
GlobalShortcut *getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type = KGlobalAccel::MatchType::Equal) const;
/**
* Get the shortcuts corresponding to key. Active and inactive shortcuts
* are considered.
*
* @see getShortcutsByKey(int key)
*/
QList<GlobalShortcut *> getShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const;
/**
* Checks if @p shortcut is available for @p component.
*
* It is available if not used by another component in any context or used
* by @p component only in not active contexts.
*/
bool isShortcutAvailable(const QKeySequence &shortcut, const QString &component, const QString &context) const;
static GlobalShortcutsRegistry *self();
bool registerKey(const QKeySequence &key, GlobalShortcut *shortcut);
void setDBusPath(const QDBusObjectPath &path);
bool unregisterKey(const QKeySequence &key, GlobalShortcut *shortcut);
public Q_SLOTS:
void clear();
void loadSettings();
void writeSettings();
// Grab the keys
void grabKeys();
// Ungrab the keys
void ungrabKeys();
private:
friend struct KGlobalAccelDPrivate;
friend class Component;
friend class KGlobalAccelInterface;
Component *createComponent(const QString &uniqueName, const QString &friendlyName);
KServiceActionComponent *createServiceActionComponent(const QString &uniqueName);
KServiceActionComponent *createServiceActionComponent(KService::Ptr service);
void migrateConfig();
void migrateKHotkeys();
static void unregisterComponent(Component *component);
using ComponentPtr = std::unique_ptr<Component, decltype(&unregisterComponent)>;
Component *registerComponent(ComponentPtr component);
// called by the implementation to inform us about key presses
// returns true if the key was handled
bool keyPressed(int keyQt);
bool keyReleased(int keyQt);
QHash<QKeySequence, GlobalShortcut *> _active_keys;
QKeySequence _active_sequence;
QHash<int, int> _keys_count;
using ComponentVec = std::vector<ComponentPtr>;
ComponentVec m_components;
ComponentVec::const_iterator findByName(const QString &name) const
{
return std::find_if(m_components.cbegin(), m_components.cend(), [&name](const ComponentPtr &comp) {
return comp->uniqueName() == name;
});
}
KGlobalAccelInterface *_manager = nullptr;
mutable KConfig _config;
QDBusObjectPath _dbusPath;
GlobalShortcut *m_lastShortcut = nullptr;
};
#endif /* #ifndef GLOBALSHORTCUTSREGISTRY_H */
@@ -0,0 +1,53 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kglobalaccel_interface.h"
#include "globalshortcutsregistry.h"
class KGlobalAccelInterface::Private
{
public:
Private(GlobalShortcutsRegistry *owner)
: owner(owner)
{
}
GlobalShortcutsRegistry *owner;
};
KGlobalAccelInterface::KGlobalAccelInterface(QObject *owner)
: QObject(owner)
, d(new Private(qobject_cast<GlobalShortcutsRegistry *>(owner)))
{
}
KGlobalAccelInterface::~KGlobalAccelInterface() = default;
void KGlobalAccelInterface::setRegistry(GlobalShortcutsRegistry *registry)
{
setParent(registry);
d->owner = registry;
}
bool KGlobalAccelInterface::keyPressed(int keyQt)
{
return d->owner->keyPressed(keyQt);
}
void KGlobalAccelInterface::grabKeys()
{
d->owner->grabKeys();
}
void KGlobalAccelInterface::ungrabKeys()
{
d->owner->ungrabKeys();
}
bool KGlobalAccelInterface::keyReleased(int keyQt)
{
return d->owner->keyReleased(keyQt);
}
@@ -0,0 +1,76 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001, 2002 Ellis Whitehead <ellis@kde.org>
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KGLOBALACCEL_INTERFACE_H
#define KGLOBALACCEL_INTERFACE_H
#include <QObject>
#include "kglobalacceld_export.h"
class GlobalShortcutsRegistry;
#define KGlobalAccelInterface_iid "org.kde.kglobalaccel5.KGlobalAccelInterface"
/**
* Abstract interface for plugins to implement
*/
class KGLOBALACCELD_EXPORT KGlobalAccelInterface : public QObject
{
Q_OBJECT
public:
explicit KGlobalAccelInterface(QObject *parent);
~KGlobalAccelInterface() override;
public:
/**
* This function registers or unregisters a certain key for global capture,
* depending on \b grab.
*
* Before destruction, every grabbed key will be released, so this
* object does not need to do any tracking.
*
* \param key the Qt keycode to grab or release.
* \param grab true to grab they key, false to release the key.
*
* \return true if successful, otherwise false.
*/
virtual bool grabKey(int key, bool grab) = 0;
/*
* Enable/disable all shortcuts. There will not be any grabbed shortcuts at this point.
*/
virtual void setEnabled(bool) = 0;
void setRegistry(GlobalShortcutsRegistry *registry);
protected:
/**
* called by the implementation to inform us about key presses
* @returns @c true if the key was handled
**/
bool keyPressed(int keyQt);
void grabKeys();
void ungrabKeys();
/**
* Called by the implementation to inform us about key releases
*
* @param keyQt the key that was just released
*
* @returns @c true if the key was handled
**/
bool keyReleased(int keyQt);
class Private;
QScopedPointer<Private> d;
};
Q_DECLARE_INTERFACE(KGlobalAccelInterface, KGlobalAccelInterface_iid)
#endif
@@ -0,0 +1,551 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-FileCopyrightText: 2007 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kglobalacceld.h"
#include "component.h"
#include "globalshortcut.h"
#include "globalshortcutcontext.h"
#include "globalshortcutsregistry.h"
#include "kglobalaccel.h"
#include "kserviceactioncomponent.h"
#include "logging_p.h"
#include <QDBusConnection>
#include <QDBusMetaType>
#include <QMetaMethod>
#include <QTimer>
struct KGlobalAccelDPrivate {
KGlobalAccelDPrivate(KGlobalAccelD *qq)
: q(qq)
{
}
GlobalShortcut *findAction(const QStringList &actionId) const;
/**
* Find the action @a shortcutUnique in @a componentUnique.
*
* @return the action or @c nullptr if doesn't exist
*/
GlobalShortcut *findAction(const QString &componentUnique, const QString &shortcutUnique) const;
GlobalShortcut *addAction(const QStringList &actionId);
Component *component(const QStringList &actionId) const;
void splitComponent(QString &component, QString &context) const
{
context = QStringLiteral("default");
const int index = component.indexOf(QLatin1Char('|'));
if (index != -1) {
Q_ASSERT(component.indexOf(QLatin1Char('|'), index + 1) == -1); // Only one '|' character
context = component.mid(index + 1);
component.truncate(index);
}
}
//! Timer for delayed writing to kglobalshortcutsrc
QTimer writeoutTimer;
//! Our holder
KGlobalAccelD *q;
GlobalShortcutsRegistry *m_registry = nullptr;
};
GlobalShortcut *KGlobalAccelDPrivate::findAction(const QStringList &actionId) const
{
// Check if actionId is valid
if (actionId.size() != 4) {
qCDebug(KGLOBALACCELD) << "Invalid! '" << actionId << "'";
return nullptr;
}
return findAction(actionId.at(KGlobalAccel::ComponentUnique), actionId.at(KGlobalAccel::ActionUnique));
}
GlobalShortcut *KGlobalAccelDPrivate::findAction(const QString &_componentUnique, const QString &shortcutUnique) const
{
QString componentUnique = _componentUnique;
Component *component;
QString contextUnique;
if (componentUnique.indexOf(QLatin1Char('|')) == -1) {
component = m_registry->getComponent(componentUnique);
if (component) {
contextUnique = component->currentContext()->uniqueName();
}
} else {
splitComponent(componentUnique, contextUnique);
component = m_registry->getComponent(componentUnique);
}
if (!component) {
qCDebug(KGLOBALACCELD) << componentUnique << "not found";
return nullptr;
}
GlobalShortcut *shortcut = component->getShortcutByName(shortcutUnique, contextUnique);
if (shortcut) {
qCDebug(KGLOBALACCELD) << componentUnique << contextUnique << shortcut->uniqueName();
} else {
qCDebug(KGLOBALACCELD) << "No match for" << shortcutUnique;
}
return shortcut;
}
Component *KGlobalAccelDPrivate::component(const QStringList &actionId) const
{
const QString uniqueName = actionId.at(KGlobalAccel::ComponentUnique);
// If a component for action already exists, use that...
if (Component *c = m_registry->getComponent(uniqueName)) {
return c;
}
// ... otherwise, create a new one
const QString friendlyName = actionId.at(KGlobalAccel::ComponentFriendly);
if (uniqueName.endsWith(QLatin1String(".desktop"))) {
auto *actionComp = m_registry->createServiceActionComponent(uniqueName);
if (!actionComp) {
return nullptr;
}
actionComp->activateGlobalShortcutContext(QStringLiteral("default"));
actionComp->loadFromService();
return actionComp;
} else {
return m_registry->createComponent(uniqueName, friendlyName);
}
}
GlobalShortcut *KGlobalAccelDPrivate::addAction(const QStringList &actionId)
{
Q_ASSERT(actionId.size() >= 4);
QString componentUnique = actionId.at(KGlobalAccel::ComponentUnique);
QString contextUnique;
splitComponent(componentUnique, contextUnique);
QStringList actionIdTmp = actionId;
actionIdTmp.replace(KGlobalAccel::ComponentUnique, componentUnique);
// Create the component if necessary
Component *component = this->component(actionIdTmp);
if (!component) {
return nullptr;
}
// Create the context if necessary
if (component->getShortcutContexts().count(contextUnique) == 0) {
component->createGlobalShortcutContext(contextUnique);
}
Q_ASSERT(!component->getShortcutByName(componentUnique, contextUnique));
return new GlobalShortcut(actionId.at(KGlobalAccel::ActionUnique), actionId.at(KGlobalAccel::ActionFriendly), component->shortcutContext(contextUnique));
}
Q_DECLARE_METATYPE(QStringList)
KGlobalAccelD::KGlobalAccelD(QObject *parent)
: QObject(parent)
, d(new KGlobalAccelDPrivate(this))
{
}
bool KGlobalAccelD::init()
{
qDBusRegisterMetaType<QKeySequence>();
qDBusRegisterMetaType<QList<QKeySequence>>();
qDBusRegisterMetaType<QList<QDBusObjectPath>>();
qDBusRegisterMetaType<QList<QStringList>>();
qDBusRegisterMetaType<QStringList>();
qDBusRegisterMetaType<KGlobalShortcutInfo>();
qDBusRegisterMetaType<QList<KGlobalShortcutInfo>>();
qDBusRegisterMetaType<KGlobalAccel::MatchType>();
d->m_registry = GlobalShortcutsRegistry::self();
Q_ASSERT(d->m_registry);
d->writeoutTimer.setSingleShot(true);
connect(&d->writeoutTimer, &QTimer::timeout, d->m_registry, &GlobalShortcutsRegistry::writeSettings);
if (!QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.kglobalaccel"))) {
qCWarning(KGLOBALACCELD) << "Failed to register service org.kde.kglobalaccel";
return false;
}
if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/kglobalaccel"), this, QDBusConnection::ExportScriptableContents)) {
qCWarning(KGLOBALACCELD) << "Failed to register object kglobalaccel in org.kde.kglobalaccel";
return false;
}
d->m_registry->setDBusPath(QDBusObjectPath("/"));
d->m_registry->loadSettings();
return true;
}
KGlobalAccelD::~KGlobalAccelD()
{
if (d->writeoutTimer.isActive()) {
d->writeoutTimer.stop();
d->m_registry->writeSettings();
}
d->m_registry->deactivateShortcuts();
delete d;
}
QList<QStringList> KGlobalAccelD::allMainComponents() const
{
return d->m_registry->allComponentNames();
}
QList<QStringList> KGlobalAccelD::allActionsForComponent(const QStringList &actionId) const
{
// ### Would it be advantageous to sort the actions by unique name?
QList<QStringList> ret;
Component *const component = d->m_registry->getComponent(actionId[KGlobalAccel::ComponentUnique]);
if (!component) {
return ret;
}
QStringList partialId(actionId[KGlobalAccel::ComponentUnique]); // ComponentUnique
partialId.append(QString()); // ActionUnique
// Use our internal friendlyName, not the one passed in. We should have the latest data.
partialId.append(component->friendlyName()); // ComponentFriendly
partialId.append(QString()); // ActionFriendly
const auto listShortcuts = component->allShortcuts();
for (const GlobalShortcut *const shortcut : listShortcuts) {
if (shortcut->isFresh()) {
// isFresh is only an intermediate state, not to be reported outside.
continue;
}
QStringList actionId(partialId);
actionId[KGlobalAccel::ActionUnique] = shortcut->uniqueName();
actionId[KGlobalAccel::ActionFriendly] = shortcut->friendlyName();
ret.append(actionId);
}
return ret;
}
#if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
QStringList KGlobalAccelD::action(int key) const
{
return actionList(key);
}
#endif
QStringList KGlobalAccelD::actionList(const QKeySequence &key) const
{
GlobalShortcut *shortcut = d->m_registry->getShortcutByKey(key);
QStringList ret;
if (shortcut) {
ret.append(shortcut->context()->component()->uniqueName());
ret.append(shortcut->uniqueName());
ret.append(shortcut->context()->component()->friendlyName());
ret.append(shortcut->friendlyName());
}
return ret;
}
void KGlobalAccelD::activateGlobalShortcutContext(const QString &component, const QString &uniqueName)
{
Component *const comp = d->m_registry->getComponent(component);
if (comp) {
comp->activateGlobalShortcutContext(uniqueName);
}
}
QList<QDBusObjectPath> KGlobalAccelD::allComponents() const
{
return d->m_registry->componentsDbusPaths();
}
void KGlobalAccelD::blockGlobalShortcuts(bool block)
{
qCDebug(KGLOBALACCELD) << "Block global shortcuts?" << block;
if (block) {
d->m_registry->deactivateShortcuts(true);
} else {
d->m_registry->activateShortcuts();
}
}
#if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
QList<int> KGlobalAccelD::shortcut(const QStringList &action) const
{
GlobalShortcut *shortcut = d->findAction(action);
if (shortcut) {
QList<int> ret;
for (auto i : shortcut->keys()) {
ret << i[0].toCombined();
}
return ret;
}
return QList<int>();
}
#endif
QList<QKeySequence> KGlobalAccelD::shortcutKeys(const QStringList &action) const
{
GlobalShortcut *shortcut = d->findAction(action);
if (shortcut) {
return shortcut->keys();
}
return QList<QKeySequence>();
}
#if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
QList<int> KGlobalAccelD::defaultShortcut(const QStringList &action) const
{
GlobalShortcut *shortcut = d->findAction(action);
if (shortcut) {
QList<int> ret;
for (auto i : shortcut->keys()) {
ret << i[0].toCombined();
}
return ret;
}
return QList<int>();
}
#endif
QList<QKeySequence> KGlobalAccelD::defaultShortcutKeys(const QStringList &action) const
{
GlobalShortcut *shortcut = d->findAction(action);
if (shortcut) {
return shortcut->defaultKeys();
}
return QList<QKeySequence>();
}
// This method just registers the action. Nothing else. Shortcut has to be set
// later.
void KGlobalAccelD::doRegister(const QStringList &actionId)
{
qCDebug(KGLOBALACCELD) << actionId;
// Check because we would not want to add a action for an invalid
// actionId. findAction returns nullptr in that case.
if (actionId.size() < 4) {
return;
}
GlobalShortcut *shortcut = d->findAction(actionId);
if (!shortcut) {
shortcut = d->addAction(actionId);
} else {
// a switch of locales is one common reason for a changing friendlyName
if ((!actionId[KGlobalAccel::ActionFriendly].isEmpty()) && shortcut->friendlyName() != actionId[KGlobalAccel::ActionFriendly]) {
shortcut->setFriendlyName(actionId[KGlobalAccel::ActionFriendly]);
scheduleWriteSettings();
}
if ((!actionId[KGlobalAccel::ComponentFriendly].isEmpty())
&& shortcut->context()->component()->friendlyName() != actionId[KGlobalAccel::ComponentFriendly]) {
shortcut->context()->component()->setFriendlyName(actionId[KGlobalAccel::ComponentFriendly]);
scheduleWriteSettings();
}
}
}
QDBusObjectPath KGlobalAccelD::getComponent(const QString &componentUnique) const
{
qCDebug(KGLOBALACCELD) << componentUnique;
Component *component = d->m_registry->getComponent(componentUnique);
if (component) {
return component->dbusPath();
} else {
sendErrorReply(QStringLiteral("org.kde.kglobalaccel.NoSuchComponent"), QStringLiteral("The component '%1' doesn't exist.").arg(componentUnique));
return QDBusObjectPath("/");
}
}
#if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
QList<KGlobalShortcutInfo> KGlobalAccelD::getGlobalShortcutsByKey(int key) const
{
return globalShortcutsByKey(key, KGlobalAccel::MatchType::Equal);
}
#endif
QList<KGlobalShortcutInfo> KGlobalAccelD::globalShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
{
qCDebug(KGLOBALACCELD) << key;
const QList<GlobalShortcut *> shortcuts = d->m_registry->getShortcutsByKey(key, type);
QList<KGlobalShortcutInfo> rc;
rc.reserve(shortcuts.size());
for (const GlobalShortcut *sc : shortcuts) {
qCDebug(KGLOBALACCELD) << sc->context()->uniqueName() << sc->uniqueName();
rc.append(static_cast<KGlobalShortcutInfo>(*sc));
}
return rc;
}
#if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
bool KGlobalAccelD::isGlobalShortcutAvailable(int shortcut, const QString &component) const
{
return globalShortcutAvailable(shortcut, component);
}
#endif
bool KGlobalAccelD::globalShortcutAvailable(const QKeySequence &shortcut, const QString &component) const
{
QString realComponent = component;
QString context;
d->splitComponent(realComponent, context);
return d->m_registry->isShortcutAvailable(shortcut, realComponent, context);
}
void KGlobalAccelD::setInactive(const QStringList &actionId)
{
qCDebug(KGLOBALACCELD) << actionId;
GlobalShortcut *shortcut = d->findAction(actionId);
if (shortcut) {
shortcut->setIsPresent(false);
}
}
bool KGlobalAccelD::unregister(const QString &componentUnique, const QString &shortcutUnique)
{
qCDebug(KGLOBALACCELD) << componentUnique << shortcutUnique;
// Stop grabbing the key
GlobalShortcut *shortcut = d->findAction(componentUnique, shortcutUnique);
if (shortcut) {
shortcut->unRegister();
scheduleWriteSettings();
}
return shortcut;
}
#if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(4, 3)
void KGlobalAccelD::unRegister(const QStringList &actionId)
{
qCDebug(KGLOBALACCELD) << actionId;
// Stop grabbing the key
GlobalShortcut *shortcut = d->findAction(actionId);
if (shortcut) {
shortcut->unRegister();
scheduleWriteSettings();
}
}
#endif
#if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
QList<int> KGlobalAccelD::setShortcut(const QStringList &actionId, const QList<int> &keys, uint flags)
{
QList<QKeySequence> input;
input.reserve(keys.size());
for (auto i : keys) {
input << i;
}
const QList<QKeySequence> list = setShortcutKeys(actionId, input, flags);
QList<int> ret;
ret.reserve(list.size());
for (auto i : list) {
ret << i[0].toCombined();
}
return ret;
}
#endif
QList<QKeySequence> KGlobalAccelD::setShortcutKeys(const QStringList &actionId, const QList<QKeySequence> &keys, uint flags)
{
// spare the DBus framework some work
const bool setPresent = (flags & SetPresent);
const bool isAutoloading = !(flags & NoAutoloading);
const bool isDefault = (flags & IsDefault);
GlobalShortcut *shortcut = d->findAction(actionId);
if (!shortcut) {
return QList<QKeySequence>();
}
// default shortcuts cannot clash because they don't do anything
if (isDefault) {
if (shortcut->defaultKeys() != keys) {
shortcut->setDefaultKeys(keys);
scheduleWriteSettings();
}
return keys; // doesn't matter
}
if (isAutoloading && !shortcut->isFresh()) {
// the trivial and common case - synchronize the action from our data
// and exit.
if (!shortcut->isPresent() && setPresent) {
shortcut->setIsPresent(true);
}
// We are finished here. Return the list of current active keys.
return shortcut->keys();
}
// now we are actually changing the shortcut of the action
shortcut->setKeys(keys);
if (setPresent) {
shortcut->setIsPresent(true);
}
// maybe isFresh should really only be set if setPresent, but only two things should use !setPresent:
//- the global shortcuts KCM: very unlikely to catch KWin/etc.'s actions in isFresh state
//- KGlobalAccel::stealGlobalShortcutSystemwide(): only applies to actions with shortcuts
// which can never be fresh if created the usual way
shortcut->setIsFresh(false);
scheduleWriteSettings();
return shortcut->keys();
}
#if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
void KGlobalAccelD::setForeignShortcut(const QStringList &actionId, const QList<int> &keys)
{
QList<QKeySequence> input;
for (auto i : keys) {
input << i;
}
return setForeignShortcutKeys(actionId, input);
}
#endif
void KGlobalAccelD::setForeignShortcutKeys(const QStringList &actionId, const QList<QKeySequence> &keys)
{
qCDebug(KGLOBALACCELD) << actionId;
GlobalShortcut *shortcut = d->findAction(actionId);
if (!shortcut) {
return;
}
QList<QKeySequence> newKeys = setShortcutKeys(actionId, keys, NoAutoloading);
Q_EMIT yourShortcutsChanged(actionId, newKeys);
}
void KGlobalAccelD::scheduleWriteSettings() const
{
if (!d->writeoutTimer.isActive()) {
d->writeoutTimer.start(500);
}
}
#include "moc_kglobalacceld.cpp"
@@ -0,0 +1,41 @@
[Desktop Entry]
Type=Application
Name=Global Shortcuts
Name[ar]=اختصارات عامة
Name[ast]=Atayos globales
Name[bg]=Глобални клавишни комбинации
Name[ca]=Dreceres globals
Name[ca@valencia]=Dreceres globals
Name[cs]=Globální zkratky
Name[de]=Globale Kurzbefehle
Name[en_GB]=Global Shortcuts
Name[eo]=Ĉieaj Ŝparvojoj
Name[es]=Atajo de teclado globales
Name[eu]=Lasterbide globalak
Name[fa]=میان‌بر های چهانی
Name[fi]=Työpöydänlaajuiset pikanäppäimet
Name[fr]=Raccourcis globaux
Name[gl]=Atallos globais
Name[hu]=Globális gyorsbillentyűk
Name[ia]=Vias Breve Global
Name[it]=Scorciatoie globali
Name[ka]=გლობალური მალსახმობები
Name[ko]=전역 단축키
Name[nl]=Globale sneltoetsen
Name[nn]=Globale snarvegar
Name[pl]=Skróty globalne
Name[pt_BR]=Atalhos globais
Name[ru]=Глобальные комбинации клавиш
Name[sa]=वैश्विक संक्षिप्तमार्गाः
Name[sk]=Globálne skratky
Name[sl]=Globalne bližnjice
Name[ta]=முழுதளாவிய சுருக்குவழிகள்
Name[tr]=Global Kısayollar
Name[uk]=Загальні скорочення
Name[x-test]=xxGlobal Shortcutsxx
Name[zh_CN]=全局快捷键
Name[zh_TW]=全域快捷鍵
Exec=@KDE_INSTALL_FULL_LIBEXECDIR@/kglobalacceld
NoDisplay=true
OnlyShowIn=KDE;
X-systemd-skip=true
@@ -0,0 +1,195 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KGLOBALACCELD_H
#define KGLOBALACCELD_H
#include "kglobalacceld_export.h"
#include <kglobalshortcutinfo.h>
#include <KGlobalAccel>
#include <QDBusContext>
#include <QDBusObjectPath>
#include <QList>
#include <QStringList>
struct KGlobalAccelDPrivate;
/**
* @note: Even though this is private API, KWin creates an object
* of this type, check in KWin to see which methods are used before
* removing them from here.
*
* @todo get rid of all of those QStringList parameters.
*/
class KGLOBALACCELD_EXPORT KGlobalAccelD : public QObject, protected QDBusContext
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KGlobalAccel")
public:
enum SetShortcutFlag {
SetPresent = 2,
NoAutoloading = 4,
IsDefault = 8,
};
Q_ENUM(SetShortcutFlag)
Q_DECLARE_FLAGS(SetShortcutFlags, SetShortcutFlag)
Q_FLAG(SetShortcutFlags)
explicit KGlobalAccelD(QObject *parent = nullptr);
~KGlobalAccelD() override;
bool init();
public Q_SLOTS:
/**
* Get the dbus path for all known components.
*
* The returned path is absolute. No need to prepend anything.
*/
Q_SCRIPTABLE QList<QDBusObjectPath> allComponents() const;
/**
* Returns a list of QStringLists (one string list per known component,
* with each string list containing four strings, one for each enumerator
* in KGlobalAccel::actionIdFields).
*/
Q_SCRIPTABLE QList<QStringList> allMainComponents() const;
Q_SCRIPTABLE QList<QStringList> allActionsForComponent(const QStringList &actionId) const;
#if KGLOBALACCELD_ENABLE_DEPRECATED_SINCE(5, 90)
KGLOBALACCELD_DEPRECATED_VERSION(5, 90, "Use actionList(const QKeySequence&, int) instead.")
Q_SCRIPTABLE QStringList action(int key) const;
#endif
Q_SCRIPTABLE QStringList actionList(const QKeySequence &key) const;
// to be called by main components not owning the action
#if KGLOBALACCELD_ENABLE_DEPRECATED_SINCE(5, 90)
KGLOBALACCELD_DEPRECATED_VERSION(5, 90, "Use shortcutKeys(const QStringList &) instead.")
Q_SCRIPTABLE QList<int> shortcut(const QStringList &actionId) const;
#endif
Q_SCRIPTABLE QList<QKeySequence> shortcutKeys(const QStringList &actionId) const;
// to be called by main components not owning the action
#if KGLOBALACCELD_ENABLE_DEPRECATED_SINCE(5, 90)
KGLOBALACCELD_DEPRECATED_VERSION(5, 90, "Use defaultShortcutKeys(const QStringList &) instead.")
Q_SCRIPTABLE QList<int> defaultShortcut(const QStringList &actionId) const;
#endif
Q_SCRIPTABLE QList<QKeySequence> defaultShortcutKeys(const QStringList &actionId) const;
/**
* Get the dbus path for @ componentUnique
*
* @param componentUnique the components unique identifier
*
* @return the absolute dbus path
*/
Q_SCRIPTABLE QDBusObjectPath getComponent(const QString &componentUnique) const;
// to be called by main components owning the action
#if KGLOBALACCELD_ENABLE_DEPRECATED_SINCE(5, 90)
KGLOBALACCELD_DEPRECATED_VERSION(5, 90, "Use setShortcutKeys(const QStringList &, const QList<QKeySequence> &, uint) instead.")
Q_SCRIPTABLE QList<int> setShortcut(const QStringList &actionId, const QList<int> &keys, uint flags);
#endif
Q_SCRIPTABLE QList<QKeySequence> setShortcutKeys(const QStringList &actionId, const QList<QKeySequence> &keys, uint flags);
// this is used if application A wants to change shortcuts of application B
#if KGLOBALACCELD_ENABLE_DEPRECATED_SINCE(5, 90)
KGLOBALACCELD_DEPRECATED_VERSION(5, 90, "Use setForeignShortcutKeys(const QStringList &, const QList<QKeySequence> &) instead.")
Q_SCRIPTABLE void setForeignShortcut(const QStringList &actionId, const QList<int> &keys);
#endif
Q_SCRIPTABLE void setForeignShortcutKeys(const QStringList &actionId, const QList<QKeySequence> &keys);
// 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);
Q_SCRIPTABLE void doRegister(const QStringList &actionId);
#if KGLOBALACCELD_ENABLE_DEPRECATED_SINCE(4, 3)
//! @deprecated Since 4.3, use KGlobalAccelD::unregister
KGLOBALACCELD_DEPRECATED_VERSION(4, 3, "Use KGlobalAccelD::unregister(const QString&, const QString&")
Q_SCRIPTABLE void unRegister(const QStringList &actionId);
#endif
Q_SCRIPTABLE void activateGlobalShortcutContext(const QString &component, const QString &context);
#if KGLOBALACCELD_ENABLE_DEPRECATED_SINCE(5, 90)
/**
* Returns the shortcuts registered for @p key.
*
* If there is more than one shortcut they are guaranteed to be from the
* same component but different contexts. All shortcuts are searched.
*
* @deprecated Since 5.90, use globalShortcutsByKey(const QKeySequence &, int) instead.
*/
KGLOBALACCELD_DEPRECATED_VERSION(5, 90, "Use globalShortcutsByKey(const QKeySequence &, int) instead.")
Q_SCRIPTABLE QList<KGlobalShortcutInfo> getGlobalShortcutsByKey(int key) const;
#endif
/**
* Returns the shortcuts registered for @p key.
*
* If there is more than one shortcut they are guaranteed to be from the
* same component but different contexts. All shortcuts are searched.
*
* @since 5.90
*/
Q_SCRIPTABLE QList<KGlobalShortcutInfo> globalShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const;
#if KGLOBALACCELD_ENABLE_DEPRECATED_SINCE(5, 90)
/**
* Returns true if the @p shortcut is available for @p component.
*
* @deprecated Since 5.90, use globalShortcutAvailable(const QKeySequence &, const QString &) instead.
*/
KGLOBALACCELD_DEPRECATED_VERSION(5, 90, "Use globalShortcutAvailable(const QKeySequence &, const QString &) instead.")
Q_SCRIPTABLE bool isGlobalShortcutAvailable(int key, const QString &component) const;
#endif
/**
* Returns true if the @p shortcut is available for @p component.
*
* @since 5.90
*/
Q_SCRIPTABLE bool globalShortcutAvailable(const QKeySequence &key, const QString &component) const;
/**
* Delete the shortcut with @a component and @name.
*
* The shortcut is removed from the registry even if it is currently
* present. It is removed from all contexts.
*
* @param componentUnique the components unique identifier
* @param shortcutUnique the shortcut id
*
* @return @c true if the shortcuts was deleted, @c false if it didn't * exist.
*/
Q_SCRIPTABLE bool unregister(const QString &componentUnique, const QString &shortcutUnique);
Q_SCRIPTABLE void blockGlobalShortcuts(bool);
Q_SIGNALS:
#if KGLOBALACCELD_ENABLE_DEPRECATED_SINCE(5, 90)
KGLOBALACCELD_DEPRECATED_VERSION(5, 90, "Use the yourShortcutsChanged(const QStringList &, const QList<QKeySequence> &) signal instead.")
Q_SCRIPTABLE void yourShortcutGotChanged(const QStringList &actionId, const QList<int> &newKeys);
#endif
Q_SCRIPTABLE void yourShortcutsChanged(const QStringList &actionId, const QList<QKeySequence> &newKeys);
private:
void scheduleWriteSettings() const;
KGlobalAccelDPrivate *const d;
};
#endif // KGLOBALACCELD_H
@@ -0,0 +1,32 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KGLOBALSHORTCUTINFO_P_H
#define KGLOBALSHORTCUTINFO_P_H
/**
* @internal
*/
static const int maxSequenceLength = 4;
#include "kglobalaccel.h"
#include "kglobalshortcutinfo.h"
class KGlobalShortcutInfoPrivate
{
public:
QString contextUniqueName;
QString contextFriendlyName;
QString componentUniqueName;
QString componentFriendlyName;
QString uniqueName;
QString friendlyName;
QList<QKeySequence> keys;
QList<QKeySequence> defaultKeys;
};
#endif /* #ifndef KGLOBALSHORTCUTINFO_P_H */
@@ -0,0 +1,156 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kserviceactioncomponent.h"
#include "globalshortcutcontext.h"
#include "logging_p.h"
#include <QFileInfo>
#include <QProcess>
#include <KService>
#include "config-kglobalaccel.h"
#if HAVE_X11
#include <KStartupInfo>
#include <private/qtx11extras_p.h>
#endif
QString makeUniqueName(const KService::Ptr &service)
{
if (service->storageId().startsWith(QLatin1Char('/'))) {
return QFileInfo(service->storageId()).fileName();
}
return service->storageId();
}
KServiceActionComponent::KServiceActionComponent(KService::Ptr service)
: Component(makeUniqueName(service), service->name())
, m_service(service)
{
}
KServiceActionComponent::~KServiceActionComponent() = default;
void KServiceActionComponent::emitGlobalShortcutPressed(const GlobalShortcut &shortcut)
{
QString exec;
if (shortcut.uniqueName() == QLatin1String("_launch")) {
exec = m_service->exec();
} else {
const auto actions = m_service->actions();
const auto it = std::find_if(actions.cbegin(), actions.cend(), [&shortcut](const KServiceAction &action) {
return action.name() == shortcut.uniqueName();
});
if (it == actions.cend()) {
qCCritical(KGLOBALACCELD, "failed to find an action matching the '%s' name", qPrintable(shortcut.uniqueName()));
return;
}
exec = it->exec();
}
if (exec.isEmpty()) {
qCWarning(KGLOBALACCELD) << "No exec line for service" << m_service->desktopEntryName();
return;
}
QProcess::startDetached(QStringLiteral("/bin/sh"), {QStringLiteral("-c"), exec});
}
void KServiceActionComponent::loadFromService()
{
const QString type = m_service->property<QString>(QStringLiteral("X-KDE-GlobalShortcutType"));
// Type can be Application or Service
// For applications add a lauch shortcut
// If no type is set assume Application
if (type.isEmpty() || type == QLatin1String("Application")) {
const QString shortcutString = m_service->property<QStringList>(QStringLiteral("X-KDE-Shortcuts")).join(QLatin1Char('\t'));
GlobalShortcut *shortcut = registerShortcut(QStringLiteral("_launch"), m_service->name(), shortcutString, shortcutString);
shortcut->setIsPresent(true);
}
const auto lstActions = m_service->actions();
for (const KServiceAction &action : lstActions) {
const QString shortcutString = action.property<QStringList>(QStringLiteral("X-KDE-Shortcuts")).join(QLatin1Char('\t'));
GlobalShortcut *shortcut = registerShortcut(action.name(), action.text(), shortcutString, shortcutString);
shortcut->setIsPresent(true);
}
}
bool KServiceActionComponent::cleanUp()
{
qCDebug(KGLOBALACCELD) << "Disabling desktop file";
const auto shortcuts = allShortcuts();
for (GlobalShortcut *shortcut : shortcuts) {
shortcut->setIsPresent(false);
}
return Component::cleanUp();
}
void KServiceActionComponent::writeSettings(KConfigGroup &config) const
{
// Clear the config so we remove entries after forgetGlobalShortcut
config.deleteGroup();
// Now write all contexts
for (GlobalShortcutContext *context : std::as_const(_contexts)) {
KConfigGroup contextGroup;
if (context->uniqueName() == QLatin1String("default")) {
contextGroup = config;
} else {
contextGroup = KConfigGroup(&config, context->uniqueName());
}
for (const GlobalShortcut *shortcut : std::as_const(context->_actionsMap)) {
// We do not write fresh shortcuts.
// We do not write session shortcuts
if (shortcut->isFresh() || shortcut->isSessionShortcut()) {
continue;
}
if (shortcut->keys() != shortcut->defaultKeys()) {
contextGroup.writeEntry(shortcut->uniqueName(), stringFromKeys(shortcut->keys()));
} else {
contextGroup.revertToDefault(shortcut->uniqueName());
}
}
}
}
void KServiceActionComponent::loadSettings(KConfigGroup &configGroup)
{
// Action shortcuts
const auto actions = m_service->actions();
for (const KServiceAction &action : actions) {
const QString defaultShortcutString = action.property<QString>(QStringLiteral("X-KDE-Shortcuts")).replace(QLatin1Char(','), QLatin1Char('\t'));
const QString shortcutString = configGroup.readEntry(action.name(), defaultShortcutString);
GlobalShortcut *shortcut = registerShortcut(action.name(), action.text(), shortcutString, defaultShortcutString);
shortcut->setIsPresent(true);
}
const QString type = m_service->property<QString>(QStringLiteral("X-KDE-GlobalShortcutType"));
// Type can be Application or Service
// For applications add a lauch shortcut
// If no type is set assume Application
if (type.isEmpty() || type == QLatin1String("Application")) {
const QString defaultShortcutString = m_service->property<QString>(QStringLiteral("X-KDE-Shortcuts")).replace(QLatin1Char(','), QLatin1Char('\t'));
const QString shortcutString = configGroup.readEntry("_launch", defaultShortcutString);
GlobalShortcut *shortcut = registerShortcut(QStringLiteral("_launch"), m_service->name(), shortcutString, defaultShortcutString);
shortcut->setIsPresent(true);
}
}
#include "moc_kserviceactioncomponent.cpp"
@@ -0,0 +1,42 @@
/*
SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSERVICEACTIONCOMPONENT_H
#define KSERVICEACTIONCOMPONENT_H
#include "component.h"
#include <KService>
#include <memory>
/**
* @author Michael Jansen <kde@michael-jansen.biz>
*/
class KServiceActionComponent : public Component
{
Q_OBJECT
public:
~KServiceActionComponent() override;
void loadFromService();
void emitGlobalShortcutPressed(const GlobalShortcut &shortcut) override;
void writeSettings(KConfigGroup &config) const override;
void loadSettings(KConfigGroup &config) override;
bool cleanUp() override;
private:
friend class ::GlobalShortcutsRegistry;
//! Constructs a KServiceActionComponent. This is a private constuctor, to create
//! a KServiceActionComponent, use GlobalShortcutsRegistry::self()->createServiceActionComponent().
KServiceActionComponent(KService::Ptr service);
KService::Ptr m_service;
};
#endif /* #ifndef KSERVICEACTIONCOMPONENT_H */
@@ -0,0 +1,10 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "logging_p.h"
// logging category for this framework, default: log stuff >= warning
Q_LOGGING_CATEGORY(KGLOBALACCELD, "kf.globalaccel.kglobalacceld", QtWarningMsg)
@@ -0,0 +1,12 @@
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KGLOBALACCELD_LOGGING_P_H
#define KGLOBALACCELD_LOGGING_P_H
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(KGLOBALACCELD)
#endif
@@ -0,0 +1,75 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-FileCopyrightText: 2007 Michael Jansen <kde@michael-jansen.biz>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kglobalaccel_version.h"
#include "kglobalacceld.h"
#include "logging_p.h"
#include <KAboutData>
#include <KCrash>
#include <KDBusService>
#include <QCommandLineParser>
#include <QGuiApplication>
int main(int argc, char **argv)
{
// On Wayland the shortcuts are ran as part of kwin_wayland
// no-op when started on Wayland
if (qEnvironmentVariable("XDG_SESSION_TYPE") == QLatin1String("wayland")) {
return 0;
}
auto sessionManager = qgetenv("SESSION_MANAGER");
// Disable Session Management the right way (C)
//
// ksmserver has global shortcuts. disableSessionManagement() does not prevent Qt from
// registering the app with the session manager. We remove the address to make sure we do not
// get a hang on kglobalaccel restart (kglobalaccel tries to register with ksmserver,
// ksmserver tries to register with kglobalaccel).
qunsetenv("SESSION_MANAGER");
QGuiApplication::setDesktopSettingsAware(false);
QGuiApplication::setQuitLockEnabled(false);
QGuiApplication app(argc, argv);
KAboutData aboutdata(QStringLiteral("kglobalaccel"),
QObject::tr("KDE Global Shortcuts Service"),
QStringLiteral(KGLOBALACCEL_VERSION_STRING),
QObject::tr("KDE Global Shortcuts Service"),
KAboutLicense::LGPL,
QStringLiteral("(C) 2007-2009 Andreas Hartmetz, Michael Jansen"));
aboutdata.addAuthor(QStringLiteral("Andreas Hartmetz"), QObject::tr("Maintainer"), QStringLiteral("ahartmetz@gmail.com"));
aboutdata.addAuthor(QStringLiteral("Michael Jansen"), QObject::tr("Maintainer"), QStringLiteral("kde@michael-jansen.biz"));
KAboutData::setApplicationData(aboutdata);
{
QCommandLineParser parser;
aboutdata.setupCommandLine(&parser);
parser.process(app);
aboutdata.processCommandLine(&parser);
}
KDBusService service(KDBusService::Unique);
app.setQuitOnLastWindowClosed(false);
if (!sessionManager.isEmpty()) {
qputenv("SESSION_MANAGER", sessionManager);
}
// Restart on a crash
KCrash::setFlags(KCrash::AutoRestart);
KGlobalAccelD globalaccel;
if (!globalaccel.init()) {
return -1;
}
return app.exec();
}
@@ -0,0 +1,12 @@
[Unit]
Description=KDE Global Shortcuts Server
PartOf=graphical-session.target
[Service]
ExecStart=@KDE_INSTALL_FULL_LIBEXECDIR@/kglobalacceld
BusName=org.kde.kglobalaccel
Slice=background.slice
TimeoutSec=5sec
Restart=on-failure
# Exit status 1 is used when the X11 connection drops
RestartPreventExitStatus=1
@@ -0,0 +1,3 @@
if (XCB_XCB_FOUND AND XCB_KEYSYMS_FOUND AND XCB_XKB_FOUND)
add_subdirectory(xcb)
endif()
@@ -0,0 +1,22 @@
set(xcb_plugin_SRCS
kglobalaccel_x11.cpp
kglobalaccel_x11.h
../../logging.cpp
)
add_library(KGlobalAccelDXcb MODULE ${xcb_plugin_SRCS})
target_link_libraries(KGlobalAccelDXcb
KGlobalAccelD
KF6::WindowSystem
XCB::XCB
XCB::KEYSYMS
XCB::XKB
XCB::RECORD
)
install(
TARGETS
KGlobalAccelDXcb
DESTINATION
${KDE_INSTALL_PLUGINDIR}/org.kde.kglobalacceld.platforms/
)
@@ -0,0 +1,380 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001, 2002 Ellis Whitehead <ellis@kde.org>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kglobalaccel_x11.h"
#include "logging_p.h"
#include <KKeyServer>
#include <netwm.h>
#include <QDebug>
#include <QSocketNotifier>
#include <QApplication>
#include <QWidget>
#include <private/qtx11extras_p.h>
#include <X11/keysym.h>
// xcb
// It uses "explicit" as a variable name, which is not allowed in C++
#define explicit xcb_explicit
#include <xcb/record.h>
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xcbext.h>
#include <xcb/xkb.h>
#undef explicit
// g_keyModMaskXAccel
// mask of modifiers which can be used in shortcuts
// (meta, alt, ctrl, shift)
// g_keyModMaskXOnOrOff
// mask of modifiers where we don't care whether they are on or off
// (caps lock, num lock, scroll lock)
static uint g_keyModMaskXAccel = 0;
static uint g_keyModMaskXOnOrOff = 0;
static void calculateGrabMasks()
{
g_keyModMaskXAccel = KKeyServer::accelModMaskX();
g_keyModMaskXOnOrOff = KKeyServer::modXLock() | KKeyServer::modXNumLock() | KKeyServer::modXScrollLock() | KKeyServer::modXModeSwitch();
// qCDebug(KGLOBALACCELD) << "g_keyModMaskXAccel = " << g_keyModMaskXAccel
// << "g_keyModMaskXOnOrOff = " << g_keyModMaskXOnOrOff << endl;
}
//----------------------------------------------------
KGlobalAccelImpl::KGlobalAccelImpl(QObject *parent)
: KGlobalAccelInterface(parent)
, m_keySymbols(nullptr)
, m_xkb_first_event(0)
{
Q_ASSERT(QX11Info::connection());
int events = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE;
xcb_change_window_attributes(QX11Info::connection(), QX11Info::appRootWindow(), XCB_CW_EVENT_MASK, &events);
const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_xkb_id);
if (reply && reply->present) {
m_xkb_first_event = reply->first_event;
}
// We use XRecord to get the released keys because we need a way to get notified about
// them without needing to hold a grab
// Holding a grab would be a problem for the cases when a process (looking at you KWin's
// toolbox) replies to a global shortcut trigger with another grab
m_display = XOpenDisplay(nullptr);
auto connection = xcb_connect(XDisplayString((Display *)m_display), nullptr);
auto context = xcb_generate_id(connection);
xcb_record_range_t range;
memset(&range, 0, sizeof(range));
range.device_events.first = XCB_KEY_RELEASE;
range.device_events.last = XCB_KEY_RELEASE;
xcb_record_client_spec_t cs = XCB_RECORD_CS_ALL_CLIENTS;
xcb_record_create_context(connection, context, 0, 1, 1, &cs, &range);
auto cookie = xcb_record_enable_context(connection, context);
xcb_flush(connection);
m_xrecordCookieSequence = cookie.sequence;
auto m_notifier = new QSocketNotifier(xcb_get_file_descriptor(connection), QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this, [this, connection] {
xcb_generic_event_t *event;
while ((event = xcb_poll_for_event(connection))) {
std::free(event);
}
xcb_record_enable_context_reply_t *reply = nullptr;
xcb_generic_error_t *error = nullptr;
while (m_xrecordCookieSequence && xcb_poll_for_reply(connection, m_xrecordCookieSequence, (void **)&reply, &error)) {
// xcb_poll_for_reply may set both reply and error to null if connection has error.
// break if xcb_connection has error, no point to continue anyway.
if (xcb_connection_has_error(connection)) {
break;
}
if (error) {
std::free(error);
break;
}
if (!reply) {
continue;
}
QScopedPointer<xcb_record_enable_context_reply_t, QScopedPointerPodDeleter> data(reinterpret_cast<xcb_record_enable_context_reply_t *>(reply));
xcb_key_press_event_t *events = reinterpret_cast<xcb_key_press_event_t *>(xcb_record_enable_context_data(reply));
int nEvents = xcb_record_enable_context_data_length(reply) / sizeof(xcb_key_press_event_t);
for (xcb_key_press_event_t *e = events; e < events + nEvents; e++) {
Q_ASSERT(e->response_type == XCB_KEY_RELEASE);
qCDebug(KGLOBALACCELD) << "Got XKeyRelease event";
x11KeyRelease(e);
}
}
});
m_notifier->setEnabled(true);
calculateGrabMasks();
}
KGlobalAccelImpl::~KGlobalAccelImpl()
{
XCloseDisplay((Display *)m_display);
if (m_keySymbols) {
xcb_key_symbols_free(m_keySymbols);
}
}
bool KGlobalAccelImpl::grabKey(int keyQt, bool grab)
{
// grabKey is called during shutdown
// shutdown might be due to the X server being killed
// if so, fail immediately before trying to make other xcb calls
if (!QX11Info::connection() || xcb_connection_has_error(QX11Info::connection())) {
return false;
}
if (!m_keySymbols) {
m_keySymbols = xcb_key_symbols_alloc(QX11Info::connection());
if (!m_keySymbols) {
return false;
}
}
if (!keyQt) {
qCDebug(KGLOBALACCELD) << "Tried to grab key with null code.";
return false;
}
uint keyModX;
// Resolve the modifier
if (!KKeyServer::keyQtToModX(keyQt, &keyModX)) {
qCDebug(KGLOBALACCELD) << "keyQt (0x" << Qt::hex << keyQt << ") failed to resolve to x11 modifier";
return false;
}
// Resolve the X symbol
const QList<int> keySymXs(KKeyServer::keyQtToSymXs(keyQt));
if (keySymXs.empty()) {
qCDebug(KGLOBALACCELD) << "keyQt (0x" << Qt::hex << keyQt << ") failed to resolve to x11 keycode";
return false;
}
xcb_keycode_t *keyCodes = nullptr;
xcb_keysym_t keySymX;
for (xcb_keysym_t sym : keySymXs) {
keyCodes = xcb_key_symbols_get_keycode(m_keySymbols, sym);
if (keyCodes) {
keySymX = sym;
break;
}
}
if (!keyCodes) {
return false;
}
int i = 0;
bool success = !grab;
while (keyCodes[i] != XCB_NO_SYMBOL) {
xcb_keycode_t keyCodeX = keyCodes[i++];
// Check if shift needs to be added to the grab since KKeySequenceWidget
// can remove shift for some keys. (all the %&* and such)
/* clang-format off */
if (!(keyQt & Qt::SHIFT)
&& !KKeyServer::isShiftAsModifierAllowed(keyQt)
&& !(keyQt & Qt::KeypadModifier)
&& keySymX != xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 0)
&& keySymX == xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 1)) { /* clang-format on */
qCDebug(KGLOBALACCELD) << "adding shift to the grab";
keyModX |= KKeyServer::modXShift();
}
keyModX &= g_keyModMaskXAccel; // Get rid of any non-relevant bits in mod
if (!keyCodeX) {
qCDebug(KGLOBALACCELD) << "keyQt (0x" << Qt::hex << keyQt << ") was resolved to x11 keycode 0";
continue;
}
// We'll have to grab 8 key modifier combinations in order to cover all
// combinations of CapsLock, NumLock, ScrollLock.
// Does anyone with more X-savvy know how to set a mask on QX11Info::appRootWindow so that
// the irrelevant bits are always ignored and we can just make one XGrabKey
// call per accelerator? -- ellis
#ifndef NDEBUG
QString sDebug = QStringLiteral("\tcode: 0x%1 state: 0x%2 | ").arg(keyCodeX, 0, 16).arg(keyModX, 0, 16);
#endif
uint keyModMaskX = ~g_keyModMaskXOnOrOff;
QList<xcb_void_cookie_t> cookies;
for (uint irrelevantBitsMask = 0; irrelevantBitsMask <= 0xff; irrelevantBitsMask++) {
if ((irrelevantBitsMask & keyModMaskX) == 0) {
#ifndef NDEBUG
sDebug += QStringLiteral("0x%3, ").arg(irrelevantBitsMask, 0, 16);
#endif
if (grab) {
cookies << xcb_grab_key_checked(QX11Info::connection(),
true,
QX11Info::appRootWindow(),
keyModX | irrelevantBitsMask,
keyCodeX,
XCB_GRAB_MODE_ASYNC,
XCB_GRAB_MODE_SYNC);
} else {
/* clang-format off */
cookies << xcb_ungrab_key_checked(QX11Info::connection(),
keyCodeX, QX11Info::appRootWindow(),
keyModX | irrelevantBitsMask);
/* clang-format on */
}
}
}
bool failed = false;
if (grab) {
for (int i = 0; i < cookies.size(); ++i) {
QScopedPointer<xcb_generic_error_t, QScopedPointerPodDeleter> error(xcb_request_check(QX11Info::connection(), cookies.at(i)));
if (!error.isNull()) {
failed = true;
}
}
if (failed) {
qCDebug(KGLOBALACCELD) << "grab failed!\n";
for (uint m = 0; m <= 0xff; m++) {
if ((m & keyModMaskX) == 0) {
xcb_ungrab_key(QX11Info::connection(), keyCodeX, QX11Info::appRootWindow(), keyModX | m);
}
}
} else {
success = true;
}
}
}
free(keyCodes);
return success;
}
bool KGlobalAccelImpl::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
{
if (eventType != "xcb_generic_event_t") {
return false;
}
xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message);
const uint8_t responseType = event->response_type & ~0x80;
if (responseType == XCB_MAPPING_NOTIFY) {
x11MappingNotify();
// Make sure to let Qt handle it as well
return false;
} else if (responseType == XCB_KEY_PRESS) {
qCDebug(KGLOBALACCELD) << "Got XKeyPress event";
return x11KeyPress(reinterpret_cast<xcb_key_press_event_t *>(event));
} else if (m_xkb_first_event && responseType == m_xkb_first_event) {
const uint8_t xkbEvent = event->pad0;
switch (xkbEvent) {
case XCB_XKB_MAP_NOTIFY:
x11MappingNotify();
break;
case XCB_XKB_NEW_KEYBOARD_NOTIFY: {
const xcb_xkb_new_keyboard_notify_event_t *ev = reinterpret_cast<xcb_xkb_new_keyboard_notify_event_t *>(event);
if (ev->changed & XCB_XKB_NKN_DETAIL_KEYCODES) {
x11MappingNotify();
}
break;
}
default:
break;
}
// Make sure to let Qt handle it as well
return false;
} else {
// We get all XEvents. Just ignore them.
return false;
}
}
void KGlobalAccelImpl::x11MappingNotify()
{
qCDebug(KGLOBALACCELD) << "Got XMappingNotify event";
// Maybe the X modifier map has been changed.
// uint oldKeyModMaskXAccel = g_keyModMaskXAccel;
// uint oldKeyModMaskXOnOrOff = g_keyModMaskXOnOrOff;
// First ungrab all currently grabbed keys. This is needed because we
// store the keys as qt keycodes and use KKeyServer to map them to x11 key
// codes. After calling KKeyServer::initializeMods() they could map to
// different keycodes.
ungrabKeys();
if (m_keySymbols) {
// Force reloading of the keySym mapping
xcb_key_symbols_free(m_keySymbols);
m_keySymbols = nullptr;
}
KKeyServer::initializeMods();
calculateGrabMasks();
grabKeys();
}
bool KGlobalAccelImpl::x11KeyPress(xcb_key_press_event_t *pEvent)
{
if (QWidget::keyboardGrabber() || QApplication::activePopupWidget()) {
qCWarning(KGLOBALACCELD) << "kglobalacceld should be popup and keyboard grabbing free!";
}
// Keyboard needs to be ungrabed after XGrabKey() activates the grab,
// otherwise it becomes frozen.
xcb_connection_t *c = QX11Info::connection();
xcb_void_cookie_t cookie = xcb_ungrab_keyboard_checked(c, XCB_TIME_CURRENT_TIME);
xcb_flush(c);
// xcb_flush() only makes sure that the ungrab keyboard request has been
// sent, but is not enough to make sure that request has been fulfilled. Use
// xcb_request_check() to make sure that the request has been processed.
xcb_request_check(c, cookie);
int keyQt;
if (!KKeyServer::xcbKeyPressEventToQt(pEvent, &keyQt)) {
qCWarning(KGLOBALACCELD) << "KKeyServer::xcbKeyPressEventToQt failed";
return false;
}
// qDebug() << "keyQt=" << QString::number(keyQt, 16);
// All that work for this hey... argh...
if (NET::timestampCompare(pEvent->time, QX11Info::appTime()) > 0) {
QX11Info::setAppTime(pEvent->time);
}
return keyPressed(keyQt);
}
bool KGlobalAccelImpl::x11KeyRelease(xcb_key_press_event_t *pEvent)
{
if (QWidget::keyboardGrabber() || QApplication::activePopupWidget()) {
qCWarning(KGLOBALACCELD) << "kglobalacceld should be popup and keyboard grabbing free!";
}
int keyQt;
if (!KKeyServer::xcbKeyPressEventToQt(pEvent, &keyQt)) {
return false;
}
return keyReleased(keyQt);
}
void KGlobalAccelImpl::setEnabled(bool enable)
{
if (enable && qApp->platformName() == QLatin1String("xcb")) {
qApp->installNativeEventFilter(this);
} else {
qApp->removeNativeEventFilter(this);
}
}
@@ -0,0 +1,73 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001, 2002 Ellis Whitehead <ellis@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef _KGLOBALACCEL_X11_H
#define _KGLOBALACCEL_X11_H
#include "../../kglobalaccel_interface.h"
#include <QAbstractNativeEventFilter>
#include <QObject>
struct xcb_key_press_event_t;
typedef xcb_key_press_event_t xcb_key_release_event_t;
typedef struct _XCBKeySymbols xcb_key_symbols_t;
/**
* @internal
*
* The KGlobalAccel private class handles grabbing of global keys,
* and notification of when these keys are pressed.
*/
class KGlobalAccelImpl : public KGlobalAccelInterface, public QAbstractNativeEventFilter
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kde.kglobalaccel5.KGlobalAccelInterface" FILE "xcb.json")
Q_INTERFACES(KGlobalAccelInterface)
public:
KGlobalAccelImpl(QObject *parent = nullptr);
~KGlobalAccelImpl() override;
public:
/**
* This function registers or unregisters a certain key for global capture,
* depending on \b grab.
*
* Before destruction, every grabbed key will be released, so this
* object does not need to do any tracking.
*
* \param key the Qt keycode to grab or release.
* \param grab true to grab they key, false to release the key.
*
* \return true if successful, otherwise false.
*/
bool grabKey(int key, bool grab) override;
/// Enable/disable all shortcuts. There will not be any grabbed shortcuts at this point.
void setEnabled(bool) override;
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override;
private:
/**
* Filters X11 events ev for key bindings in the accelerator dictionary.
* If a match is found the activated activated is emitted and the function
* returns true. Return false if the event is not processed.
*
* This is public for compatibility only. You do not need to call it.
*/
void x11MappingNotify();
bool x11KeyPress(xcb_key_press_event_t *event);
bool x11KeyRelease(xcb_key_press_event_t *event);
xcb_key_symbols_t *m_keySymbols;
uint8_t m_xkb_first_event;
void *m_display;
unsigned int m_xrecordCookieSequence;
};
#endif // _KGLOBALACCEL_X11_H
@@ -0,0 +1,3 @@
{
"platforms": ["xcb"]
}
@@ -0,0 +1,105 @@
#include <QKeySequence>
#include "kglobalshortcutinfo_p.h"
#include "sequencehelpers_p.h"
#include <QList>
namespace Utils
{
QKeySequence reverseKey(const QKeySequence &key)
{
int k[maxSequenceLength] = {0, 0, 0, 0};
int count = key.count();
for (int i = 0; i < count; i++) {
k[count - i - 1] = key[i].toCombined();
}
return QKeySequence(k[0], k[1], k[2], k[3]);
}
QKeySequence cropKey(const QKeySequence &key, int count)
{
if (count < 1) {
return key;
}
// Key is shorter than count we want to cut off
if (key.count() < count) {
return QKeySequence();
}
int k[maxSequenceLength] = {0, 0, 0, 0};
// cut from beginning
for (int i = count; i < key.count(); i++) {
k[i - count] = key[i].toCombined();
}
return QKeySequence(k[0], k[1], k[2], k[3]);
}
bool contains(const QKeySequence &key, const QKeySequence &other)
{
int minLength = std::min(key.count(), other.count());
// There's an empty key, assume it matches nothing
if (!minLength) {
return false;
}
bool ret = false;
for (int i = 0; i <= other.count() - minLength; i++) {
QKeySequence otherCropped = cropKey(other, i);
if (key.matches(otherCropped) == QKeySequence::PartialMatch || reverseKey(key).matches(reverseKey(otherCropped)) == QKeySequence::PartialMatch) {
ret = true;
break;
}
}
return ret;
}
bool matchSequences(const QKeySequence &key, const QList<QKeySequence> &keys)
{
// Since we're testing sequences, we need to check for all possible matches
// between existing and new sequences.
// Let's assume we have (Alt+B, Alt+F, Alt+G) assigned. Examples of bad shortcuts are:
// 1) Exact matching: (Alt+B, Alt+F, Alt+G)
// 2) Sequence shadowing: (Alt+B, Alt+F)
// 3) Sequence being shadowed: (Alt+B, Alt+F, Alt+G, <any key>)
// 4) Shadowing at the end: (Alt+F, Alt+G)
// 5) Being shadowed from the end: (<any key>, Alt+B, Alt+F, Alt+G)
for (const QKeySequence &otherKey : keys) {
if (otherKey.isEmpty()) {
continue;
}
if (key.matches(otherKey) == QKeySequence::ExactMatch || contains(key, otherKey) || contains(otherKey, key)) {
return true;
}
}
return false;
}
QKeySequence mangleKey(const QKeySequence &key)
{
// Qt triggers both shortcuts that include Shift+Backtab and Shift+Tab
// when user presses Shift+Tab. Make no difference here.
int k[maxSequenceLength] = {0, 0, 0, 0};
for (int i = 0; i < key.count(); i++) {
// Qt triggers both shortcuts that include Shift+Backtab and Shift+Tab
// when user presses Shift+Tab. Make no difference here.
int keySym = key[i].toCombined() & ~Qt::KeyboardModifierMask;
int keyMod = key[i].toCombined() & Qt::KeyboardModifierMask;
if ((keyMod & Qt::SHIFT) && (keySym == Qt::Key_Backtab || keySym == Qt::Key_Tab)) {
k[i] = keyMod | Qt::Key_Tab;
} else {
k[i] = key[i].toCombined();
}
}
return QKeySequence(k[0], k[1], k[2], k[3]);
}
} // namespace Utils
@@ -0,0 +1,23 @@
#ifndef SEQUENCEHELPERS_H
#define SEQUENCEHELPERS_H
#include <kglobalaccel_export.h>
#include <QKeySequence>
// Some methods are exported for the unittest
namespace Utils
{
KGLOBALACCEL_EXPORT QKeySequence reverseKey(const QKeySequence &key);
KGLOBALACCEL_EXPORT QKeySequence cropKey(const QKeySequence &key, int count);
bool contains(const QKeySequence &key, const QKeySequence &other);
KGLOBALACCEL_EXPORT bool matchSequences(const QKeySequence &key, const QList<QKeySequence> &keys);
KGLOBALACCEL_EXPORT QKeySequence mangleKey(const QKeySequence &key);
}
#endif // SEQUENCEHELPERS_H