270 lines
7.9 KiB
C++
270 lines
7.9 KiB
C++
#include "kglobalaccelglobalshortcutbackend.h"
|
|
|
|
#include <QAction>
|
|
#include <QGuiApplication>
|
|
|
|
#include "core/logging.h"
|
|
|
|
#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
|