Clementine-audio-player-Mac.../src/core/kglobalaccelglobalshortcutb...

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