From 62bb6d9411fc6fbd2850e89e5aa46c9f11a62875 Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Fri, 25 Nov 2022 19:48:36 +0100 Subject: [PATCH] FEAT(client): Add support to XDG Desktop Portal GlobalShortcuts This makes it possible to have global shortcuts on systems running the XDG Desktop Portal service. This is especially relevant on Wayland where we are not able to run a system-wide keylogger to get the global shortcuts triggers. Fixes #5257 --- ...org.freedesktop.portal.GlobalShortcuts.xml | 268 ++++++++++++++++++ .../org.freedesktop.portal.Request.xml | 86 ++++++ src/mumble/CMakeLists.txt | 6 + src/mumble/GlobalShortcut.cpp | 21 +- src/mumble/GlobalShortcut.h | 3 + src/mumble/GlobalShortcut.ui | 20 +- src/mumble/GlobalShortcut_unix.cpp | 8 + src/mumble/GlobalShortcut_xdp.cpp | 200 +++++++++++++ src/mumble/GlobalShortcut_xdp.h | 52 ++++ src/mumble/Settings.cpp | 8 - 10 files changed, 643 insertions(+), 29 deletions(-) create mode 100644 auxiliary_files/org.freedesktop.portal.GlobalShortcuts.xml create mode 100644 auxiliary_files/org.freedesktop.portal.Request.xml create mode 100644 src/mumble/GlobalShortcut_xdp.cpp create mode 100644 src/mumble/GlobalShortcut_xdp.h diff --git a/auxiliary_files/org.freedesktop.portal.GlobalShortcuts.xml b/auxiliary_files/org.freedesktop.portal.GlobalShortcuts.xml new file mode 100644 index 00000000000..1fc2b0ac9db --- /dev/null +++ b/auxiliary_files/org.freedesktop.portal.GlobalShortcuts.xml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/auxiliary_files/org.freedesktop.portal.Request.xml b/auxiliary_files/org.freedesktop.portal.Request.xml new file mode 100644 index 00000000000..c1abb4eb7b1 --- /dev/null +++ b/auxiliary_files/org.freedesktop.portal.Request.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/mumble/CMakeLists.txt b/src/mumble/CMakeLists.txt index 56d6c045340..87450a3942f 100644 --- a/src/mumble/CMakeLists.txt +++ b/src/mumble/CMakeLists.txt @@ -820,10 +820,16 @@ if(dbus AND NOT WIN32 AND NOT APPLE) PRIVATE "DBus.cpp" "DBus.h" + "GlobalShortcut_xdp.cpp" ) + qt5_add_dbus_interface(mumble_xdp_SRCS ${CMAKE_SOURCE_DIR}/auxiliary_files/org.freedesktop.portal.GlobalShortcuts.xml globalshortcuts_portal_interface) + qt5_add_dbus_interface(mumble_xdp_SRCS ${CMAKE_SOURCE_DIR}/auxiliary_files/org.freedesktop.portal.Request.xml portalsrequest_interface) + target_sources(mumble_client_object_lib PRIVATE ${mumble_xdp_SRCS}) target_compile_definitions(mumble_client_object_lib PUBLIC "USE_DBUS") target_link_libraries(mumble_client_object_lib PUBLIC Qt5::DBus) + target_include_directories(mumble_client_object_lib PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + endif() if(translations) diff --git a/src/mumble/GlobalShortcut.cpp b/src/mumble/GlobalShortcut.cpp index e6f849f188a..d432fb5c009 100644 --- a/src/mumble/GlobalShortcut.cpp +++ b/src/mumble/GlobalShortcut.cpp @@ -545,6 +545,15 @@ GlobalShortcutConfig::GlobalShortcutConfig(Settings &st) : ConfigWidget(st) { bool canSuppress = GlobalShortcutEngine::engine->canSuppress(); bool canDisable = GlobalShortcutEngine::engine->canDisable(); + bool canConfigure = GlobalShortcutEngine::engine->canConfigure(); + + qpbAdd->setVisible(!canConfigure); + qpbRemove->setVisible(!canConfigure); + qpbConfigure->setVisible(canConfigure); + if (canConfigure) { + connect(qpbConfigure, &QPushButton::clicked, GlobalShortcutEngine::engine, &GlobalShortcutEngine::configure); + connect(GlobalShortcutEngine::engine, &GlobalShortcutEngine::shortcutsChanged, this, &GlobalShortcutConfig::reload); + } qwWarningContainer->setVisible(false); @@ -565,14 +574,6 @@ GlobalShortcutConfig::GlobalShortcutConfig(Settings &st) : ConfigWidget(st) { qcbEnableGlobalShortcuts->setVisible(canDisable); - qlWaylandNote->setVisible(false); -#ifdef Q_OS_LINUX - if (EnvUtils::waylandIsUsed()) { - // Our global shortcut system doesn't work properly with Wayland - qlWaylandNote->setVisible(true); - } -#endif - #ifdef Q_OS_MAC // Help Mac users enable accessibility access for Mumble... # if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) @@ -908,6 +909,10 @@ bool GlobalShortcutEngine::canDisable() { return false; } +bool GlobalShortcutEngine::canConfigure() { + return false; +} + void GlobalShortcutEngine::resetMap() { tReset.restart(); qlActiveButtons.clear(); diff --git a/src/mumble/GlobalShortcut.h b/src/mumble/GlobalShortcut.h index 9ba68971952..055ea328d24 100644 --- a/src/mumble/GlobalShortcut.h +++ b/src/mumble/GlobalShortcut.h @@ -253,13 +253,16 @@ class GlobalShortcutEngine : public QThread { static void remove(GlobalShortcut *); virtual bool canDisable(); + virtual bool canConfigure(); virtual bool canSuppress(); virtual bool enabled(); virtual void setEnabled(bool b); + virtual void configure() {} virtual ButtonInfo buttonInfo(const QVariant &) = 0; signals: void buttonPressed(bool last); + void shortcutsChanged(); }; #endif diff --git a/src/mumble/GlobalShortcut.ui b/src/mumble/GlobalShortcut.ui index 55faa57fa11..8e97c75d9a1 100644 --- a/src/mumble/GlobalShortcut.ui +++ b/src/mumble/GlobalShortcut.ui @@ -95,19 +95,6 @@ - - - - <html><head/><body><p>Mumble's Global Shortcuts system does currently not work properly in combination with the Wayland protocol. For more information, visit <a href="https://github.com/mumble-voip/mumble/issues/5257"><span style=" text-decoration: underline; color:#0057ae;">https://github.com/mumble-voip/mumble/issues/5257</span></a>.</p></body></html> - - - true - - - true - - - @@ -204,6 +191,13 @@ + + + + Configure + + + diff --git a/src/mumble/GlobalShortcut_unix.cpp b/src/mumble/GlobalShortcut_unix.cpp index 5c59f79471e..e66cd7b146a 100644 --- a/src/mumble/GlobalShortcut_unix.cpp +++ b/src/mumble/GlobalShortcut_unix.cpp @@ -4,6 +4,9 @@ // Mumble source tree or at . #include "GlobalShortcut_unix.h" +#ifdef USE_DBUS +# include "GlobalShortcut_xdp.h" +#endif #include "Settings.h" #include "Global.h" @@ -45,8 +48,13 @@ * @see GlobalShortcutX * @see GlobalShortcutMac * @see GlobalShortcutWin + * @see GlobalShortcutXdp */ GlobalShortcutEngine *GlobalShortcutEngine::platformInit() { + if (GlobalShortcutXdp::isAvailable()) { + return new GlobalShortcutXdp; + } + return new GlobalShortcutX(); } diff --git a/src/mumble/GlobalShortcut_xdp.cpp b/src/mumble/GlobalShortcut_xdp.cpp new file mode 100644 index 00000000000..61790dfdb64 --- /dev/null +++ b/src/mumble/GlobalShortcut_xdp.cpp @@ -0,0 +1,200 @@ +// Copyright 2022 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +#include "GlobalShortcut_xdp.h" + +#include "EnvUtils.h" +#include "Global.h" +#include "GlobalShortcutTypes.h" +#include "Settings.h" + +#include + +#include "globalshortcuts_portal_interface.h" +#include "portalsrequest_interface.h" + +Q_GLOBAL_STATIC_WITH_ARGS(OrgFreedesktopPortalGlobalShortcutsInterface, s_shortcutsInterface, ("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + QDBusConnection::sessionBus())); + +bool GlobalShortcutXdp::isAvailable() +{ + return s_shortcutsInterface->isValid(); +} + +GlobalShortcutXdp::GlobalShortcutXdp() { + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + connect(s_shortcutsInterface, &OrgFreedesktopPortalGlobalShortcutsInterface::Activated, this, &GlobalShortcutXdp::shortcutActivated); + connect(s_shortcutsInterface, &OrgFreedesktopPortalGlobalShortcutsInterface::Deactivated, this, &GlobalShortcutXdp::shortcutDeactivated); + connect(s_shortcutsInterface, &OrgFreedesktopPortalGlobalShortcutsInterface::ShortcutsChanged, this, &GlobalShortcutXdp::shortcutsConfigured); + + QTimer::singleShot(0, this, &GlobalShortcutXdp::createSession); +} + +GlobalShortcutXdp::~GlobalShortcutXdp() { + +} + +void GlobalShortcutXdp::createSession() +{ + XdpShortcuts initialShortcuts; + initialShortcuts.reserve(qmShortcuts.count()); + Global::get().s.qlShortcuts.clear(); + + int i = 0; + m_ids.resize(qmShortcuts.count()); + // Populate the shortcuts list to provide the xdg-desktop-portal + for (GlobalShortcut *shortcut : qmShortcuts) { + initialShortcuts.append({shortcut->objectName(), { + { QStringLiteral("description"), shortcut->name } + }}); + + Shortcut ourShortcut = { i, {uint(i)}, shortcut->qvDefault, false}; + m_ids[i] = shortcut->objectName(); + Global::get().s.qlShortcuts << ourShortcut; + ++i; + } + + QDBusArgument arg; + arg << initialShortcuts; + QDBusPendingReply reply = s_shortcutsInterface->CreateSession({ + { QLatin1String("session_handle_token"), "Mumble" }, + { QLatin1String("handle_token"), QLatin1String("mumble") }, + { QLatin1String("shortcuts"), QVariant::fromValue(arg) }, + }); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "Couldn't get reply"; + qWarning() << "Error:" << reply.error().message(); + } else { + QDBusConnection::sessionBus().connect(QString(), + reply.value().path(), + QLatin1String("org.freedesktop.portal.Request"), + QLatin1String("Response"), + this, + SLOT(gotGlobalShortcutsCreateSessionResponse(uint,QVariantMap))); + } +} + +void GlobalShortcutXdp::shortcutActivated(const QDBusObjectPath &session_handle, const QString &shortcut_id, qulonglong timestamp, const QVariantMap &options) +{ + Q_UNUSED(session_handle); + Q_UNUSED(timestamp); + Q_UNUSED(options); + + for (GlobalShortcut *shortcut : qmShortcuts) { + if (shortcut_id == shortcut->objectName()) { + shortcut->triggered(true, shortcut_id); + shortcut->down(shortcut_id); + } + } +} + +void GlobalShortcutXdp::shortcutDeactivated(const QDBusObjectPath &session_handle, const QString &shortcut_id, qulonglong timestamp, const QVariantMap &options) +{ + Q_UNUSED(session_handle); + Q_UNUSED(timestamp); + Q_UNUSED(options); + + for (GlobalShortcut *shortcut : qmShortcuts) { + if (shortcut_id == shortcut->objectName()) { + shortcut->triggered(false, shortcut_id); + } + } +} + +void GlobalShortcutXdp::shortcutsConfigured(const QDBusObjectPath &session_handle, const XdpShortcuts &shortcuts) +{ + Q_UNUSED(session_handle); + if (m_shortcuts != shortcuts) { + m_shortcuts = shortcuts; + emit shortcutsChanged(); + } +} + +void GlobalShortcutXdp::run() { + // 🤘🤪 +} + +bool GlobalShortcutXdp::canDisable() { + return false; +} + +GlobalShortcutXdp::ButtonInfo GlobalShortcutXdp::buttonInfo(const QVariant &v) { + ButtonInfo info; + bool ok; + unsigned int key = v.toUInt(&ok); + if (!ok) { + return info; + } + + info.device = tr("Desktop"); + info.devicePrefix = QString(); + const QString id = m_ids.value(key); + for (const XdpShortcut &x : qAsConst(m_shortcuts)) { + if (x.first == id) { + info.name = x.second["trigger_description"].toString(); + } + } + return info; +} + +static QString parentWindowId() +{ + if (EnvUtils::waylandIsUsed()) { + // TODO + return {}; + } + return QLatin1String("x11:") + QString::number(qApp->focusWindow()->winId()); +} + +void GlobalShortcutXdp::gotGlobalShortcutsCreateSessionResponse(uint code, const QVariantMap &results) +{ + if (code != 0) { + qWarning() << "failed to create a global shortcuts session" << code << results; + return; + } + + m_globalShortcutsSession = QDBusObjectPath(results["session_handle"].toString()); + + QDBusPendingReply reply = s_shortcutsInterface->ListShortcuts(m_globalShortcutsSession, {}); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "failed to call ListShortcuts" << reply.error(); + return; + } + + OrgFreedesktopPortalRequestInterface *req = new OrgFreedesktopPortalRequestInterface(QLatin1String("org.freedesktop.portal.Desktop"), + reply.value().path(), QDBusConnection::sessionBus(), this); + + // BindShortcuts and ListShortcuts answer the same + connect(req, &OrgFreedesktopPortalRequestInterface::Response, this, &GlobalShortcutXdp::gotListShortcutsResponse); + connect(req, &OrgFreedesktopPortalRequestInterface::Response, req, &QObject::deleteLater); +} + +void GlobalShortcutXdp::gotListShortcutsResponse(uint, const QVariantMap &results) +{ + const QDBusArgument arg = results["shortcuts"].value(); + arg >> m_shortcuts; +} + +void GlobalShortcutXdp::configure() +{ + QDBusPendingReply reply = s_shortcutsInterface->BindShortcuts(m_globalShortcutsSession, m_shortcuts, parentWindowId(), {}); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "failed to call ListShortcuts" << reply.error(); + return; + } + + OrgFreedesktopPortalRequestInterface *req = new OrgFreedesktopPortalRequestInterface(QLatin1String("org.freedesktop.portal.Desktop"), + reply.value().path(), QDBusConnection::sessionBus(), this); + + // BindShortcuts and ListShortcuts answer the same + connect(req, &OrgFreedesktopPortalRequestInterface::Response, this, &GlobalShortcutXdp::gotListShortcutsResponse); + connect(req, &OrgFreedesktopPortalRequestInterface::Response, req, &QObject::deleteLater); +} diff --git a/src/mumble/GlobalShortcut_xdp.h b/src/mumble/GlobalShortcut_xdp.h new file mode 100644 index 00000000000..c267c0e92c8 --- /dev/null +++ b/src/mumble/GlobalShortcut_xdp.h @@ -0,0 +1,52 @@ +// Copyright 2022 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +#ifndef MUMBLE_MUMBLE_GLOBALSHORTCUT_XDP_H_ +#define MUMBLE_MUMBLE_GLOBALSHORTCUT_XDP_H_ + +#include "ConfigDialog.h" +#include "Global.h" +#include "GlobalShortcut.h" + +#include + +class OrgFreedesktopPortalGlobalShortcutsInterface; + +using XdpShortcut = QPair; +using XdpShortcuts = QList; + +class GlobalShortcutXdp : public GlobalShortcutEngine { +private: + Q_OBJECT + Q_DISABLE_COPY(GlobalShortcutXdp) +public: + static bool isAvailable(); + + void createSession(); + + GlobalShortcutXdp(); + ~GlobalShortcutXdp() Q_DECL_OVERRIDE; + void run() Q_DECL_OVERRIDE; + ButtonInfo buttonInfo(const QVariant &) Q_DECL_OVERRIDE; + + bool canDisable() override; + bool canConfigure() override { return true; } + void configure() override; + +public slots: + void gotGlobalShortcutsCreateSessionResponse(uint, const QVariantMap &results); + void gotListShortcutsResponse(uint, const QVariantMap &results); + + void shortcutActivated(const QDBusObjectPath &session_handle, const QString &shortcut_id, qulonglong timestamp, const QVariantMap &options); + void shortcutDeactivated(const QDBusObjectPath &session_handle, const QString &shortcut_id, qulonglong timestamp, const QVariantMap &options); + void shortcutsConfigured(const QDBusObjectPath &session_handle, const QList> &shortcuts); + +private: + XdpShortcuts m_shortcuts; + QVector m_ids; + QDBusObjectPath m_globalShortcutsSession; +}; + +#endif diff --git a/src/mumble/Settings.cpp b/src/mumble/Settings.cpp index b9c16cfbe65..e5f4b2c5578 100644 --- a/src/mumble/Settings.cpp +++ b/src/mumble/Settings.cpp @@ -552,14 +552,6 @@ Settings::Settings() { lmLoopMode = Server; #endif - -#ifdef Q_OS_LINUX - if (EnvUtils::waylandIsUsed()) { - // Due to the issues we're currently having on Wayland, we disable shortcuts by default - bShortcutEnable = false; - } -#endif - for (int i = Log::firstMsgType; i <= Log::lastMsgType; ++i) { qmMessages.insert(i, Settings::LogConsole | Settings::LogBalloon | Settings::LogTTS | Settings::LogMessageLimit);