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:
@@ -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
|
||||
Reference in New Issue
Block a user