Add KDE global shortcuts

Fixes #572
This commit is contained in:
Jonas Kvinge
2020-10-31 02:08:19 +01:00
parent e8492940a5
commit c258e5a3af
10 changed files with 480 additions and 67 deletions

View File

@@ -0,0 +1,200 @@
/*
* Strawberry Music Player
* Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <dbus/kglobalaccel.h>
#include <dbus/kglobalaccelcomponent.h>
#include <QCoreApplication>
#include <QList>
#include <QString>
#include <QStringList>
#include <QAction>
#include <QDBusConnection>
#include <QDBusReply>
#include <QDBusObjectPath>
#include <QDBusPendingCallWatcher>
#include <QKeySequence>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
# include <QKeyCombination>
#endif
#include "core/logging.h"
#include "core/closure.h"
#include "globalshortcutbackend-kde.h"
const char *GlobalShortcutBackendKDE::kKdeService = "org.kde.kglobalaccel";
const char *GlobalShortcutBackendKDE::kKdePath = "/kglobalaccel";
GlobalShortcutBackendKDE::GlobalShortcutBackendKDE(GlobalShortcuts *parent) : GlobalShortcutBackend(parent), interface_(nullptr), component_(nullptr) {}
bool GlobalShortcutBackendKDE::DoRegister() {
qLog(Debug) << "Registering";
if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(kKdeService)) {
qLog(Warning) << "KGlobalAccel is not registered";
return false;
}
if (!interface_) {
interface_ = new OrgKdeKGlobalAccelInterface(kKdeService, kKdePath, QDBusConnection::sessionBus(), this);
}
for (const GlobalShortcuts::Shortcut &shortcut : manager_->shortcuts().values()) {
RegisterShortcut(shortcut);
}
QDBusPendingReply<QDBusObjectPath> reply = interface_->getComponent(QCoreApplication::applicationName());
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
NewClosure(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(RegisterFinished(QDBusPendingCallWatcher*)), watcher);
return true;
}
void GlobalShortcutBackendKDE::RegisterFinished(QDBusPendingCallWatcher *watcher) {
QDBusReply<QDBusObjectPath> reply = watcher->reply();
watcher->deleteLater();
if (!reply.isValid()) {
qLog(Error) << "Failed to register:" << reply.error().name() << reply.error().message();
return;
}
if (!component_) {
component_ = new org::kde::kglobalaccel::Component(kKdeService, reply.value().path(), QDBusConnection::sessionBus(), interface_);
}
if (!component_->isValid()) {
qLog(Error) << "Component is invalid:" << QDBusConnection::sessionBus().lastError();
return;
}
connect(component_, SIGNAL(globalShortcutPressed(QString, QString, qlonglong)), this, SLOT(GlobalShortcutPressed(QString, QString, qlonglong)), Qt::UniqueConnection);
qLog(Debug) << "Registered";
}
void GlobalShortcutBackendKDE::DoUnregister() {
if (!interface_ || !interface_->isValid()) return;
qLog(Debug) << "Unregistering";
for (const GlobalShortcuts::Shortcut &shortcut : manager_->shortcuts()) {
if (actions_.contains(shortcut.id)) {
interface_->unRegister(GetActionId(shortcut.id, shortcut.action));
actions_.remove(shortcut.id, shortcut.action);
qLog(Info) << "Unregistered shortcut" << shortcut.id << shortcut.action->shortcut();
}
}
disconnect(component_, nullptr, this, nullptr);
qLog(Debug) << "Unregistered";
}
bool GlobalShortcutBackendKDE::RegisterShortcut(const GlobalShortcuts::Shortcut &shortcut) {
if (!interface_ || !interface_->isValid() || shortcut.id.isEmpty() || !shortcut.action || shortcut.action->shortcut().isEmpty()) return false;
if (shortcut.action->shortcut() == QKeySequence(Qt::Key_MediaPlay) ||
shortcut.action->shortcut() == QKeySequence(Qt::Key_MediaStop) ||
shortcut.action->shortcut() == QKeySequence(Qt::Key_MediaNext) ||
shortcut.action->shortcut() == QKeySequence(Qt::Key_MediaPrevious)) {
qLog(Info) << "Media shortcut" << shortcut.id << shortcut.action->shortcut();
return true;
}
QStringList action_id = GetActionId(shortcut.id, shortcut.action);
actions_.insert(shortcut.id, shortcut.action);
interface_->doRegister(action_id);
QList<QKeySequence> active_shortcut = QList<QKeySequence>() << shortcut.action->shortcut();
const QList<int> result = interface_->setShortcut(action_id, ToIntList(active_shortcut), 0x2);
const QList<QKeySequence> result_sequence = ToKeySequenceList(result);
if (result_sequence != active_shortcut) {
if (result_sequence.isEmpty()) {
shortcut.action->setShortcut(QKeySequence());
}
else {
shortcut.action->setShortcut(result_sequence[0]);
}
}
qLog(Info) << "Registered shortcut" << shortcut.id << shortcut.action->shortcut();
return true;
}
QStringList GlobalShortcutBackendKDE::GetActionId(const QString &id, const QAction *action) {
QStringList ret;
ret << QCoreApplication::applicationName();
ret << id;
ret << QCoreApplication::applicationName();
ret << action->text().remove('&');
if (ret.back().isEmpty()) ret.back() = id;
return ret;
}
QList<int> GlobalShortcutBackendKDE::ToIntList(const QList<QKeySequence> &sequence_list) {
QList<int> ret;
for (const QKeySequence &sequence : sequence_list) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
ret.append(sequence[0].toCombined());
#else
ret.append(sequence[0]);
#endif
}
return ret;
}
QList<QKeySequence> GlobalShortcutBackendKDE::ToKeySequenceList(const QList<int> &sequence_list) {
QList<QKeySequence> ret;
for (int sequence : sequence_list) {
ret.append(sequence);
}
return ret;
}
void GlobalShortcutBackendKDE::GlobalShortcutPressed(const QString &component_unique, const QString &shortcut_unique, qlonglong) {
if (QCoreApplication::applicationName() == component_unique && actions_.contains(shortcut_unique)) {
for (QAction *action : actions_.values(shortcut_unique)) {
qLog(Debug) << "Key" << action->shortcut() << "pressed.";
if (action->isEnabled()) action->trigger();
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Strawberry Music Player
* Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLOBALSHORTCUTBACKEND_KDE_H
#define GLOBALSHORTCUTBACKEND_KDE_H
#include "config.h"
#include <QList>
#include <QMultiHash>
#include <QString>
#include <QStringList>
#include <QKeySequence>
#include "globalshortcutbackend.h"
#include "globalshortcuts.h"
class QDBusPendingCallWatcher;
class QAction;
class OrgKdeKGlobalAccelInterface;
class OrgKdeKglobalaccelComponentInterface;
class GlobalShortcutBackendKDE : public GlobalShortcutBackend {
Q_OBJECT
public:
explicit GlobalShortcutBackendKDE(GlobalShortcuts *parent);
static const char *kKdeService;
protected:
bool DoRegister() override;
void DoUnregister() override;
private:
bool RegisterShortcut(const GlobalShortcuts::Shortcut &shortcut);
static QStringList GetActionId(const QString &id, const QAction *action);
static QList<int> ToIntList(const QList<QKeySequence> &sequence_list);
static QList<QKeySequence> ToKeySequenceList(const QList<int> &sequence_list);
private slots:
void RegisterFinished(QDBusPendingCallWatcher *watcher);
void GlobalShortcutPressed(const QString &component_unique, const QString &shortcut_unique, qlonglong);
private:
static const char *kKdePath;
OrgKdeKGlobalAccelInterface *interface_;
OrgKdeKglobalaccelComponentInterface *component_;
QMultiHash<QString, QAction*> actions_;
};
#endif // GLOBALSHORTCUTBACKEND_KDE_H

View File

@@ -39,6 +39,7 @@
#ifdef HAVE_DBUS
# include "globalshortcutbackend-gsd.h"
# include "globalshortcutbackend-kde.h"
#endif
#if defined(HAVE_X11) || defined(Q_OS_WIN)
# include "globalshortcutbackend-system.h"
@@ -51,9 +52,11 @@
GlobalShortcuts::GlobalShortcuts(QWidget *parent)
: QWidget(parent),
dbus_backend_(nullptr),
gsd_backend_(nullptr),
kde_backend_(nullptr),
system_backend_(nullptr),
use_gsd_(true),
use_kde_(true),
use_x11_(false)
{
@@ -82,7 +85,8 @@ GlobalShortcuts::GlobalShortcuts(QWidget *parent)
// Create backends - these do the actual shortcut registration
#ifdef HAVE_DBUS
dbus_backend_ = new GlobalShortcutBackendGSD(this);
gsd_backend_ = new GlobalShortcutBackendGSD(this);
kde_backend_ = new GlobalShortcutBackendKDE(this);
#endif
#ifdef Q_OS_MACOS
@@ -106,6 +110,7 @@ void GlobalShortcuts::ReloadSettings() {
// The actual shortcuts have been set in our actions for us by the config dialog already - we just need to reread the gnome settings.
use_gsd_ = settings_.value("use_gsd", true).toBool();
use_kde_ = settings_.value("use_kde", true).toBool();
use_x11_ = settings_.value("use_x11", false).toBool();
Unregister();
@@ -149,6 +154,16 @@ bool GlobalShortcuts::IsGsdAvailable() const {
}
bool GlobalShortcuts::IsKdeAvailable() const {
#ifdef HAVE_DBUS
return QDBusConnection::sessionBus().interface()->isServiceRegistered(GlobalShortcutBackendKDE::kKdeService);
#else
return false;
#endif
}
bool GlobalShortcuts::IsX11Available() const {
#ifdef HAVE_X11
@@ -160,7 +175,9 @@ bool GlobalShortcuts::IsX11Available() const {
}
void GlobalShortcuts::Register() {
if (use_gsd_ && dbus_backend_ && dbus_backend_->Register()) return;
if (use_gsd_ && gsd_backend_ && gsd_backend_->Register()) return;
if (use_kde_ && kde_backend_ && kde_backend_->Register()) return;
#ifdef HAVE_X11 // If this system has X11, only use the system backend if X11 is enabled in the global shortcut settings
if (use_x11_) {
#endif
@@ -169,11 +186,15 @@ void GlobalShortcuts::Register() {
#ifdef HAVE_X11
}
#endif
}
void GlobalShortcuts::Unregister() {
if (dbus_backend_ && dbus_backend_->is_active()) dbus_backend_->Unregister();
if (gsd_backend_ && gsd_backend_->is_active()) gsd_backend_->Unregister();
if (kde_backend_ && kde_backend_->is_active()) kde_backend_->Unregister();
if (system_backend_ && system_backend_->is_active()) system_backend_->Unregister();
}
bool GlobalShortcuts::IsMacAccessibilityEnabled() const {

View File

@@ -50,6 +50,7 @@ class GlobalShortcuts : public QWidget {
QMap<QString, Shortcut> shortcuts() const { return shortcuts_; }
bool IsGsdAvailable() const;
bool IsKdeAvailable() const;
bool IsX11Available() const;
bool IsMacAccessibilityEnabled() const;
@@ -87,13 +88,15 @@ class GlobalShortcuts : public QWidget {
Shortcut AddShortcut(const QString &id, const QString &name, const QKeySequence &default_key);
private:
GlobalShortcutBackend *dbus_backend_;
GlobalShortcutBackend *gsd_backend_;
GlobalShortcutBackend *kde_backend_;
GlobalShortcutBackend *system_backend_;
QMap<QString, Shortcut> shortcuts_;
QSettings settings_;
bool use_gsd_;
bool use_kde_;
bool use_x11_;
};