#include "kglobalaccelglobalshortcutbackend.h" #include #include #include "core/logging.h" #ifdef HAVE_DBUS #include #include #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 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 KGlobalAccelShortcutBackend::ToIntList( const QList& seq) { QList 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 active_shortcut; active_shortcut << shortcut.action->shortcut(); QStringList action_id = GetId(shortcut.id, shortcut.action); const QList result = iface_->setShortcut( action_id, ToIntList(active_shortcut), SetShortcutFlag::SetPresent); const QList 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 KGlobalAccelShortcutBackend::ToKeySequenceList( const QList& seq) { QList 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 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