Initial KGlobalAccel support

This commit is contained in:
Luis Caceres 2019-12-06 04:25:28 +00:00
parent 440ac6dda2
commit 24a571769a
7 changed files with 478 additions and 1 deletions

View File

@ -82,6 +82,7 @@ set(SOURCES
core/globalshortcutbackend.cpp
core/globalshortcuts.cpp
core/gnomeglobalshortcutbackend.cpp
core/kglobalaccelglobalshortcutbackend.cpp
core/mergedproxymodel.cpp
core/metatypes.cpp
core/multisortfilterproxy.cpp
@ -413,6 +414,7 @@ set(HEADERS
core/globalshortcuts.h
core/globalshortcutbackend.h
core/gnomeglobalshortcutbackend.h
core/kglobalaccelglobalshortcutbackend.h
core/mergedproxymodel.h
core/mimedata.h
core/network.h
@ -937,6 +939,15 @@ if(UNIX AND HAVE_DBUS)
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
dbus/gnomesettingsdaemon)
# org.kde.KGlobalAccel interfaces
# these are taken from the KGlobalAccel sources (LGPL 2.1)
qt5_add_dbus_interface(SOURCES
dbus/org.kde.KGlobalAccel.xml
dbus/kglobalaccel)
qt5_add_dbus_interface(SOURCES
dbus/org.kde.kglobalaccel.Component.xml
dbus/kglobalaccelcomponent)
# org.freedesktop.Avahi.Server interface
add_custom_command(
OUTPUT

View File

@ -22,6 +22,7 @@
#include "config.h"
#include "globalshortcuts.h"
#include "gnomeglobalshortcutbackend.h"
#include "kglobalaccelglobalshortcutbackend.h"
#include "macglobalshortcutbackend.h"
#include "qxtglobalshortcutbackend.h"
@ -41,7 +42,8 @@ GlobalShortcuts::GlobalShortcuts(QWidget* parent)
: QWidget(parent),
gnome_backend_(nullptr),
system_backend_(nullptr),
use_gnome_(false) {
use_gnome_(false),
have_kglobalaccel_(KGlobalAccelShortcutBackend::isKGlobalAccelAvailable()) {
settings_.beginGroup(kSettingsGroup);
// Create actions
@ -91,6 +93,7 @@ GlobalShortcuts::GlobalShortcuts(QWidget* parent)
// Create backends - these do the actual shortcut registration
gnome_backend_ = new GnomeGlobalShortcutBackend(this);
kglobalaccel_backend_ = new KGlobalAccelShortcutBackend(this);
#ifndef Q_OS_DARWIN
system_backend_ = new QxtGlobalShortcutBackend(this);
@ -158,10 +161,12 @@ void GlobalShortcuts::ReloadSettings() {
void GlobalShortcuts::Unregister() {
if (gnome_backend_->is_active()) gnome_backend_->Unregister();
if (kglobalaccel_backend_->is_active()) kglobalaccel_backend_->Unregister();
if (system_backend_->is_active()) system_backend_->Unregister();
}
void GlobalShortcuts::Register() {
if(have_kglobalaccel_ && kglobalaccel_backend_->Register()) return;
if (use_gnome_ && gnome_backend_->Register()) return;
system_backend_->Register();
}

View File

@ -93,12 +93,14 @@ signals:
private:
GlobalShortcutBackend* gnome_backend_;
GlobalShortcutBackend* kglobalaccel_backend_;
GlobalShortcutBackend* system_backend_;
QMap<QString, Shortcut> shortcuts_;
QSettings settings_;
bool use_gnome_;
bool have_kglobalaccel_;
};
#endif // CORE_GLOBALSHORTCUTS_H_

View File

@ -0,0 +1,284 @@
#include "config.h"
#include "core/closure.h"
#include "core/logging.h"
#include "globalshortcuts.h"
#include "kglobalaccelglobalshortcutbackend.h"
#include <QAction>
#include <QGuiApplication>
#ifdef HAVE_DBUS
#include <dbus/kglobalaccel.h>
#include <dbus/kglobalaccelcomponent.h>
#endif
// Most of this file is based on the KGlobalAccel sources
// (https://phabricator.kde.org/source/kglobalaccel)
namespace {
QString compDisplayName() {
if (!QGuiApplication::applicationDisplayName().isEmpty()) {
return QGuiApplication::applicationDisplayName();
}
return QCoreApplication::applicationName();
}
QString compUniqueName() {
return QCoreApplication::applicationName();
}
const QString &id_ActionUnique(const QStringList &id) {
return id.at(1);
}
bool isCorrectMediaKeyShortcut(const GlobalShortcuts::Shortcut& shortcut) {
if(shortcut.id == QStringLiteral("play_pause")) {
return shortcut.action->shortcut() == QKeySequence(Qt::Key_MediaPlay);
} else if(shortcut.id == QStringLiteral("stop")) {
return shortcut.action->shortcut() == QKeySequence(Qt::Key_MediaStop);
} else if(shortcut.id == QStringLiteral("next_track")) {
return shortcut.action->shortcut() == QKeySequence(Qt::Key_MediaNext);
} else if(shortcut.id == QStringLiteral("prev_track")) {
return shortcut.action->shortcut() == QKeySequence(Qt::Key_MediaPrevious);
} else {
return false;
}
}
}
#ifdef HAVE_DBUS
KGlobalAccelShortcutBackend::KGlobalAccelShortcutBackend(
GlobalShortcuts *parent)
: GlobalShortcutBackend(parent),
iface_(nullptr),
component_(nullptr),
nameToAction_() {}
#else // HAVE_DBUS
KGlobalAccelShortcutBackend::KGlobalAccelShortcutBackend(GlobalShortcuts *parent)
: GlobalShortcutBackend(parent) {}
#endif // HAVE_DBUS
bool KGlobalAccelShortcutBackend::isKGlobalAccelAvailable() {
#ifdef HAVE_DBUS
return QDBusConnection::sessionBus().interface()->isServiceRegistered(
Service);
#else // HAVE_DBUS
return false;
#endif // HAVE_DBUS
}
bool KGlobalAccelShortcutBackend::DoRegister() {
#ifdef HAVE_DBUS
qLog(Debug) << "Registering shortcuts";
if (!acquireInterface())
return false;
bool complete = true;
for (const GlobalShortcuts::Shortcut &shortcut : manager_->shortcuts().values()) {
if(shortcut.action->shortcut().isEmpty())
continue;
if (!registerShortcut(shortcut))
complete = false;
}
if (!acquireComponent())
return false;
QObject::connect(component_,
&OrgKdeKglobalaccelComponentInterface::globalShortcutPressed,
this,
&KGlobalAccelShortcutBackend::onShortcutPressed);
return complete;
#else // HAVE_DBUS
qLog(Warning) << "dbus not available";
return false;
#endif // HAVE_DBUS
}
void KGlobalAccelShortcutBackend::DoUnregister() {
if (!acquireInterface())
return;
if (!acquireComponent())
return;
for (const GlobalShortcuts::Shortcut &shortcut : manager_->shortcuts())
unregisterAction(shortcut.id, shortcut.action);
}
#ifdef HAVE_DBUS
const char *KGlobalAccelShortcutBackend::Service = "org.kde.kglobalaccel";
const char *KGlobalAccelShortcutBackend::Path = "/kglobalaccel";
bool KGlobalAccelShortcutBackend::acquireComponent() {
Q_ASSERT(iface_ && iface_->isValid());
QString componentName = compUniqueName();
QDBusReply<QDBusObjectPath> reply = iface_->getComponent(compUniqueName());
if (!reply.isValid()) {
if (reply.error().name() ==
QLatin1String("org.kde.kglobalaccel.NoSuchComponent"))
return false;
qLog(Warning) << "Failed to get DBus path for KGlobalAccel component";
return false;
}
component_ = new org::kde::kglobalaccel::Component(Service,
reply.value().path(),
QDBusConnection::sessionBus(),
iface_);
if (!component_->isValid()) {
qLog(Warning) << "Failed to get KGlobalAccel component:"
<< QDBusConnection::sessionBus().lastError();
delete component_;
component_ = nullptr;
return false;
}
return true;
}
bool KGlobalAccelShortcutBackend::acquireInterface() {
if (iface_ && iface_->isValid())
return true;
if (isKGlobalAccelAvailable()) {
iface_ = new OrgKdeKGlobalAccelInterface(Service, Path,
QDBusConnection::sessionBus(),
this);
}
if (iface_ && iface_->isValid())
return true;
if (!iface_)
qLog(Warning) << "KGlobalAccel daemon not registered";
else if (!iface_->isValid())
qLog(Warning) << "KGlobalAccel daemon is not valid";
return false;
}
QStringList KGlobalAccelShortcutBackend::id(const QString &name,
const QAction *action) {
Q_ASSERT(action);
QStringList ret;
ret << compUniqueName();
ret << name;
ret << compDisplayName();
ret << action->text().replace(QLatin1Char('&'), QStringLiteral(""));
if (ret.back().isEmpty())
ret.back() = name;
return ret;
}
QList<int> KGlobalAccelShortcutBackend::intList(
const QList<QKeySequence> &seq) {
QList<int> ret;
for (const QKeySequence &sequence : seq) {
ret.append(sequence[0]);
}
while (!ret.isEmpty() && ret.last() == 0) {
ret.removeLast();
}
return ret;
}
bool KGlobalAccelShortcutBackend::registerAction(const QString &name,
QAction *action,
QStringList &actionId) {
Q_ASSERT(action);
if (name.isEmpty() && (action->objectName().isEmpty() ||
action->objectName().startsWith(
QLatin1String("unnamed-")))) {
qLog(Warning) << "Cannot register shortcut for unnamed action";
return false;
}
actionId = id(name, action);
nameToAction_.insertMulti(id_ActionUnique(actionId), action);
iface_->doRegister(actionId);
return true;
}
bool KGlobalAccelShortcutBackend::registerShortcut(const GlobalShortcuts::Shortcut &shortcut) {
QStringList actionId;
if (!registerAction(shortcut.id, shortcut.action, actionId))
return false;
QList<QKeySequence> activeShortcut;
activeShortcut << shortcut.action->shortcut();
const QList<int> result = iface_->setShortcut(actionId,
intList(activeShortcut),
SetShortcutFlag::SetPresent);
const QList<QKeySequence> resultSequence = shortcutList(result);
if (resultSequence != activeShortcut) {
qLog(Warning) << "Tried setting global shortcut" << activeShortcut
<< "but KGlobalAccel returned" << resultSequence;
if (!resultSequence.isEmpty()) {
if(!isCorrectMediaKeyShortcut(shortcut)) {
// there is some conflict with our preferred shortcut so we use
// the new shortcut that kglobalaccel suggests
shortcut.action->setShortcut(resultSequence[0]);
} else {
// media keys are properly handled by plasma through the
// media player plasmoid so we don't do anything in those cases
qLog(Debug) << "Leaving media key shortcuts unchanged";
}
}
}
return true;
}
QList<QKeySequence> KGlobalAccelShortcutBackend::shortcutList(
const QList<int> &seq) {
QList<QKeySequence> ret;
for (int i : seq) {
ret.append(i);
}
return ret;
}
void KGlobalAccelShortcutBackend::unregisterAction(const QString &name,
QAction *action) {
Q_ASSERT(action);
QStringList actionId = id(name, action);
nameToAction_.remove(id_ActionUnique(actionId), action);
iface_->unRegister(actionId);
}
void KGlobalAccelShortcutBackend::onShortcutPressed(
const QString &componentUnique,
const QString &actionUnique,
qlonglong timestamp) const {
QAction *action = nullptr;
const QList<QAction *> candidates = nameToAction_.values(actionUnique);
for (QAction *a : candidates) {
if (compUniqueName() == componentUnique) {
action = a;
}
}
if (action && action->isEnabled())
action->trigger();
}
#endif // HAVE_DBUS

View File

@ -0,0 +1,71 @@
#ifndef CORE_KGLOBALACCELGLOBALSHORTCUTBACKEND_H_
#define CORE_KGLOBALACCELGLOBALSHORTCUTBACKEND_H_
#include "globalshortcutbackend.h"
#include <QSet>
#include <QStringList>
class QAction;
class OrgKdeKGlobalAccelInterface;
class OrgKdeKglobalaccelComponentInterface;
class KGlobalAccelShortcutBackend : public GlobalShortcutBackend {
Q_OBJECT
public:
explicit KGlobalAccelShortcutBackend(GlobalShortcuts *parent);
static bool isKGlobalAccelAvailable();
protected:
bool DoRegister() override;
void DoUnregister() override;
private:
#ifdef HAVE_DBUS
enum SetShortcutFlag {
SetPresent = 2,
NoAutoloading = 4,
IsDefault = 8
};
bool acquireComponent();
bool acquireInterface();
static QStringList id(const QString &name, const QAction *action);
static QList<int> intList(const QList<QKeySequence> &seq);
bool registerAction(const QString &name, QAction *action,
QStringList &actionId);
bool registerShortcut(const GlobalShortcuts::Shortcut &shortcut);
static QList<QKeySequence> shortcutList(const QList<int> &seq);
void unregisterAction(const QString &name, QAction *action);
private slots:
void onShortcutPressed(const QString &componentUnique,
const QString &actionUnique,
qlonglong timestamp) const;
private:
static const char *Service;
static const char *Path;
OrgKdeKGlobalAccelInterface *iface_;
OrgKdeKglobalaccelComponentInterface *component_;
QMultiHash<QString, QAction *> nameToAction_;
#endif // HAVE_DBUS
};
#endif //CORE_KGLOBALACCELGLOBALSHORTCUTBACKEND_H_

View File

@ -0,0 +1,72 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.KGlobalAccel">
<signal name="yourShortcutGotChanged">
<arg name="actionId" type="as" direction="out"/>
<arg name="newKeys" type="ai" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QList&lt;int&gt;"/>
</signal>
<method name="allComponents">
<arg type="ao" direction="out"/>
</method>
<method name="allMainComponents">
<arg type="aas" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList&lt;QStringList&gt;"/>
</method>
<method name="allActionsForComponent">
<arg type="aas" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList&lt;QStringList&gt;"/>
<arg name="actionId" type="as" direction="in"/>
</method>
<method name="action">
<arg type="as" direction="out"/>
<arg name="key" type="i" direction="in"/>
</method>
<method name="getComponent">
<arg type="o" direction="out"/>
<arg name="componentUnique" type="s" direction="in"/>
</method>
<method name="shortcut">
<arg type="ai" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList&lt;int&gt;"/>
<arg name="actionId" type="as" direction="in"/>
</method>
<method name="defaultShortcut">
<arg type="ai" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList&lt;int&gt;"/>
<arg name="actionId" type="as" direction="in"/>
</method>
<method name="setShortcut">
<arg type="ai" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList&lt;int&gt;"/>
<arg name="actionId" type="as" direction="in"/>
<arg name="keys" type="ai" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QList&lt;int&gt;"/>
<arg name="flags" type="u" direction="in"/>
</method>
<method name="setForeignShortcut">
<arg name="actionId" type="as" direction="in"/>
<arg name="keys" type="ai" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QList&lt;int&gt;"/>
</method>
<method name="setInactive">
<arg name="actionId" type="as" direction="in"/>
</method>
<method name="doRegister">
<arg name="actionId" type="as" direction="in"/>
</method>
<method name="unRegister">
<arg name="actionId" type="as" direction="in"/>
</method>
<method name="activateGlobalShortcutContext">
<arg name="component" type="s" direction="in"/>
<arg name="context" type="s" direction="in"/>
</method>
<method name="isGlobalShortcutAvailable">
<arg type="b" direction="out"/>
<arg name="key" type="i" direction="in"/>
<arg name="component" type="s" direction="in"/>
</method>
</interface>
</node>

View File

@ -0,0 +1,32 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.kglobalaccel.Component">
<property name="friendlyName" type="s" access="read"/>
<property name="uniqueName" type="s" access="read"/>
<signal name="globalShortcutPressed">
<arg name="componentUnique" type="s" direction="out"/>
<arg name="actionUnique" type="s" direction="out"/>
<arg name="timestamp" type="x" direction="out"/>
</signal>
<method name="cleanUp">
<arg type="b" direction="out"/>
</method>
<method name="isActive">
<arg type="b" direction="out"/>
</method>
<method name="shortcutNames">
<arg type="as" direction="out"/>
<arg name="context" type="s" direction="in"/>
</method>
<method name="shortcutNames">
<arg type="as" direction="out"/>
</method>
<method name="getShortcutContexts">
<arg type="as" direction="out"/>
</method>
<method name="invokeShortcut">
<arg name="actionName" type="s" direction="in"/>
</method>
</interface>
</node>