Merge pull request #6470 from lacc97/kglobalaccel

Add KGlobalAccel global shortcuts backend
This commit is contained in:
John Maguire 2019-12-10 10:14:32 +00:00 committed by GitHub
commit 998f12699b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 458 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,9 @@ 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 +94,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 +162,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,268 @@
#include "core/logging.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 {
#ifdef HAVE_DBUS
QString ComponentDisplayName() {
return QGuiApplication::applicationDisplayName().isEmpty()
? QCoreApplication::applicationName()
: QGuiApplication::applicationDisplayName();
}
QString ComponentUniqueName() { return QCoreApplication::applicationName(); }
const QString& IdActionUniqueName(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;
}
}
#endif // HAVE_DBUS
} // namespace
#ifdef HAVE_DBUS
KGlobalAccelShortcutBackend::KGlobalAccelShortcutBackend(
GlobalShortcuts* parent)
: GlobalShortcutBackend(parent), iface_(nullptr), component_(nullptr) {}
#else // HAVE_DBUS
KGlobalAccelShortcutBackend::KGlobalAccelShortcutBackend(
GlobalShortcuts* parent)
: GlobalShortcutBackend(parent) {}
#endif // HAVE_DBUS
bool KGlobalAccelShortcutBackend::IsKGlobalAccelAvailable() {
#ifdef HAVE_DBUS
return QDBusConnection::sessionBus().interface()->isServiceRegistered(
kService);
#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,
Qt::UniqueConnection);
return complete;
#else // HAVE_DBUS
qLog(Warning) << "dbus not available";
return false;
#endif // HAVE_DBUS
}
void KGlobalAccelShortcutBackend::DoUnregister() {
#ifdef HAVE_DBUS
if (!AcquireInterface()) return;
if (!AcquireComponent()) return;
for (const GlobalShortcuts::Shortcut& shortcut : manager_->shortcuts())
UnregisterAction(shortcut.id, shortcut.action);
QObject::disconnect(
component_, &OrgKdeKglobalaccelComponentInterface::globalShortcutPressed,
this, &KGlobalAccelShortcutBackend::OnShortcutPressed);
#endif // HAVE_DBUS
}
#ifdef HAVE_DBUS
const char* KGlobalAccelShortcutBackend::kService = "org.kde.kglobalaccel";
const char* KGlobalAccelShortcutBackend::kPath = "/kglobalaccel";
bool KGlobalAccelShortcutBackend::AcquireComponent() {
Q_ASSERT(iface_ && iface_->isValid());
if (component_) return true;
QDBusReply<QDBusObjectPath> reply =
iface_->getComponent(ComponentUniqueName());
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(
kService, reply.value().path(), QDBusConnection::sessionBus(), iface_);
if (!component_->isValid()) {
qLog(Warning) << "Failed to get KGlobalAccel component:"
<< QDBusConnection::sessionBus().lastError();
component_->deleteLater();
component_ = nullptr;
return false;
}
return true;
}
bool KGlobalAccelShortcutBackend::AcquireInterface() {
if (iface_ && iface_->isValid()) return true;
if (IsKGlobalAccelAvailable()) {
iface_ = new OrgKdeKGlobalAccelInterface(
kService, kPath, 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::GetId(const QString& name,
const QAction* action) {
Q_ASSERT(action);
QStringList ret;
ret << ComponentUniqueName();
ret << name;
ret << ComponentDisplayName();
ret << action->text().replace(QLatin1Char('&'), QStringLiteral(""));
if (ret.back().isEmpty()) ret.back() = name;
return ret;
}
QList<int> KGlobalAccelShortcutBackend::ToIntList(
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) {
Q_ASSERT(action);
if (name.isEmpty() &&
(action->objectName().isEmpty() ||
action->objectName().startsWith(QLatin1String("unnamed-")))) {
qLog(Warning) << "Cannot register shortcut for unnamed action";
return false;
}
QStringList action_id = GetId(name, action);
name_to_action_.insertMulti(IdActionUniqueName(action_id), action);
iface_->doRegister(action_id);
return true;
}
bool KGlobalAccelShortcutBackend::RegisterShortcut(
const GlobalShortcuts::Shortcut& shortcut) {
if (!RegisterAction(shortcut.id, shortcut.action)) return false;
QList<QKeySequence> active_shortcut;
active_shortcut << shortcut.action->shortcut();
QStringList action_id = GetId(shortcut.id, shortcut.action);
const QList<int> result = iface_->setShortcut(
action_id, ToIntList(active_shortcut), SetShortcutFlag::SetPresent);
const QList<QKeySequence> result_sequence = ToKeySequenceList(result);
if (result_sequence != active_shortcut) {
qLog(Warning) << "Tried setting global shortcut" << active_shortcut
<< "but KGlobalAccel returned" << result_sequence;
if (!result_sequence.isEmpty()) {
if (!IsCorrectMediaKeyShortcut(shortcut)) {
// there is some conflict with our preferred shortcut so we use
// the new shortcut that kglobalaccel suggests
shortcut.action->setShortcut(result_sequence[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::ToKeySequenceList(
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 action_id = GetId(name, action);
name_to_action_.remove(IdActionUniqueName(action_id), action);
iface_->unRegister(action_id);
}
void KGlobalAccelShortcutBackend::OnShortcutPressed(
const QString& component_unique, const QString& action_unique,
qlonglong timestamp) const {
QAction* action = nullptr;
const QList<QAction*> candidates = name_to_action_.values(action_unique);
for (QAction* a : candidates) {
if (ComponentUniqueName() == component_unique) {
action = a;
}
}
if (action && action->isEnabled()) action->trigger();
}
#endif // HAVE_DBUS

View File

@ -0,0 +1,66 @@
#ifndef CORE_KGLOBALACCELGLOBALSHORTCUTBACKEND_H_
#define CORE_KGLOBALACCELGLOBALSHORTCUTBACKEND_H_
#include "config.h"
#include "globalshortcutbackend.h"
#include "globalshortcuts.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 GetId(const QString& name, const QAction* action);
static QList<int> ToIntList(const QList<QKeySequence>& seq);
bool RegisterAction(const QString& name, QAction* action);
bool RegisterShortcut(const GlobalShortcuts::Shortcut& shortcut);
static QList<QKeySequence> ToKeySequenceList(const QList<int>& seq);
void UnregisterAction(const QString& name, QAction* action);
private slots:
void OnShortcutPressed(const QString& component_unique,
const QString& action_unique,
qlonglong timestamp) const;
private:
static const char* kService;
static const char* kPath;
OrgKdeKGlobalAccelInterface* iface_;
OrgKdeKglobalaccelComponentInterface* component_;
QMultiHash<QString, QAction*> name_to_action_;
#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>