From e9f84da50ba21aae27f284997ff7377dacb3e83e 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 --- src/EnvUtils.cpp | 5 - src/EnvUtils.h | 2 - src/mumble/CMakeLists.txt | 7 + src/mumble/GlobalShortcut.cpp | 19 +- src/mumble/GlobalShortcut.h | 3 + src/mumble/GlobalShortcut.ui | 20 +- src/mumble/GlobalShortcut_unix.cpp | 6 + src/mumble/GlobalShortcut_xdp.cpp | 190 +++++++++++++ src/mumble/GlobalShortcut_xdp.h | 48 ++++ src/mumble/Settings.cpp | 8 - ...org.freedesktop.portal.GlobalShortcuts.xml | 268 ++++++++++++++++++ 11 files changed, 542 insertions(+), 34 deletions(-) create mode 100644 src/mumble/GlobalShortcut_xdp.cpp create mode 100644 src/mumble/GlobalShortcut_xdp.h create mode 100644 src/mumble/org.freedesktop.portal.GlobalShortcuts.xml diff --git a/src/EnvUtils.cpp b/src/EnvUtils.cpp index 1c47cf15ffc..c2a1dad5057 100644 --- a/src/EnvUtils.cpp +++ b/src/EnvUtils.cpp @@ -56,9 +56,4 @@ bool setenv(QString name, QString value) { #endif } -bool waylandIsUsed() { - // If wayland is used, this environment variable is expected to be set - return getenv(QStringLiteral("WAYLAND_DISPLAY")) != ""; -} - }; // namespace EnvUtils diff --git a/src/EnvUtils.h b/src/EnvUtils.h index 7b1a73cb170..42658243284 100644 --- a/src/EnvUtils.h +++ b/src/EnvUtils.h @@ -22,8 +22,6 @@ QString getenv(QString name); bool setenv(QString name, QString value); -bool waylandIsUsed(); - }; // namespace EnvUtils #endif diff --git a/src/mumble/CMakeLists.txt b/src/mumble/CMakeLists.txt index 984cdbc5d33..ff9269df87b 100644 --- a/src/mumble/CMakeLists.txt +++ b/src/mumble/CMakeLists.txt @@ -838,10 +838,17 @@ if(dbus AND NOT WIN32 AND NOT APPLE) PRIVATE "DBus.cpp" "DBus.h" + "GlobalShortcut_xdp.cpp" ) + qt_add_dbus_interface(mumble_xdp_SRCS org.freedesktop.portal.GlobalShortcuts.xml globalshortcuts_portal_interface) + find_file(PORTALSREQUEST_XML share/dbus-1/interfaces/org.freedesktop.portal.Request.xml PATH_SUFFIXES share PATHS /usr ${CMAKE_INSTALL_PREFIX}) + qt_add_dbus_interface(mumble_xdp_SRCS ${PORTALSREQUEST_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..2f32eb04ded 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); @@ -566,12 +575,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... @@ -908,6 +911,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..aa2437132ed 100644 --- a/src/mumble/GlobalShortcut_unix.cpp +++ b/src/mumble/GlobalShortcut_unix.cpp @@ -4,6 +4,7 @@ // Mumble source tree or at . #include "GlobalShortcut_unix.h" +#include "GlobalShortcut_xdp.h" #include "Settings.h" #include "Global.h" @@ -45,8 +46,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..add2c34427a --- /dev/null +++ b/src/mumble/GlobalShortcut_xdp.cpp @@ -0,0 +1,190 @@ +// Copyright 2007-2022 Aleix Pol Gonzalez . +// 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 "Settings.h" +#include "Global.h" +#include "globalshortcuts_portal_interface.h" +#include "portalsrequest_interface.h" +#include "GlobalShortcutTypes.h" + +#include + +Q_GLOBAL_STATIC_WITH_ARGS(OrgFreedesktopPortalGlobalShortcutsInterface, s_shortcutsInterface, (QLatin1String("org.freedesktop.portal.Desktop"), + QLatin1String("/org/freedesktop/portal/desktop"), + QDBusConnection::sessionBus())); + +bool GlobalShortcutXdp::isAvailable() +{ + return s_shortcutsInterface->isValid(); +} + +GlobalShortcutXdp::GlobalShortcutXdp() { + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + + connect(s_shortcutsInterface, &OrgFreedesktopPortalGlobalShortcutsInterface::Activated, this, [this] (const QDBusObjectPath &session_handle, const QString &shortcut_id, qulonglong timestamp, const QVariantMap &options) { + Q_UNUSED(session_handle); + Q_UNUSED(timestamp); + Q_UNUSED(options); + + foreach (GlobalShortcut *shortcut, qmShortcuts) { + if (shortcut_id == shortcut->objectName()) { + shortcut->triggered(true, shortcut_id); + shortcut->down(shortcut_id); + } + } + }); + connect(s_shortcutsInterface, &OrgFreedesktopPortalGlobalShortcutsInterface::Deactivated, this, [this] (const QDBusObjectPath &session_handle, const QString &shortcut_id, qulonglong timestamp, const QVariantMap &options) { + Q_UNUSED(session_handle); + Q_UNUSED(timestamp); + Q_UNUSED(options); + + foreach (GlobalShortcut *shortcut, qmShortcuts) { + if (shortcut_id == shortcut->objectName()) { + shortcut->triggered(false, shortcut_id); + } + } + }); + connect(s_shortcutsInterface, &OrgFreedesktopPortalGlobalShortcutsInterface::ShortcutsChanged, this, [this] (const QDBusObjectPath &session_handle, const QList> &shortcuts) { + Q_UNUSED(session_handle); + qDebug() << "ShortcutsChanged" << shortcuts; + if (m_shortcuts != shortcuts) { + m_shortcuts = shortcuts; + Q_EMIT shortcutsChanged(); + } + }); + + 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()); + foreach (GlobalShortcut *shortcut, qmShortcuts) { + initialShortcuts.append({shortcut->objectName(), { + { QStringLiteral("description"), shortcut->name } + }}); + + Shortcut ssss = { i, {uint(i)}, shortcut->qvDefault, false}; + m_ids[i] = shortcut->objectName(); + Global::get().s.qlShortcuts << ssss; + ++i; + } + + QDBusArgument arg; + arg << initialShortcuts; + auto 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::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 auto id = m_ids[key]; + for (const auto &x : m_shortcuts) { + if (x.first == id) { + info.name = x.second["trigger_description"].toString(); + } + } + return info; +} + +static QString parentWindowId() +{ + if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) { + // 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()); + + auto reply = s_shortcutsInterface->ListShortcuts(m_globalShortcutsSession, {}); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "failed to call ListShortcuts" << reply.error(); + return; + } + + auto 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 auto arg = results["shortcuts"].value(); + arg >> m_shortcuts; +} + +void GlobalShortcutXdp::configure() +{ + auto reply = s_shortcutsInterface->BindShortcuts(m_globalShortcutsSession, m_shortcuts, parentWindowId(), {}); + reply.waitForFinished(); + if (reply.isError()) { + qWarning() << "failed to call ListShortcuts" << reply.error(); + return; + } + + auto 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..352b446fb3d --- /dev/null +++ b/src/mumble/GlobalShortcut_xdp.h @@ -0,0 +1,48 @@ +// Copyright 2007-2022 Aleix Pol Gonzalez . +// 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 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 Q_SLOTS: + void gotGlobalShortcutsCreateSessionResponse(uint, const QVariantMap &results); + void gotListShortcutsResponse(uint, const QVariantMap &results); + +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); diff --git a/src/mumble/org.freedesktop.portal.GlobalShortcuts.xml b/src/mumble/org.freedesktop.portal.GlobalShortcuts.xml new file mode 100644 index 00000000000..1fc2b0ac9db --- /dev/null +++ b/src/mumble/org.freedesktop.portal.GlobalShortcuts.xml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +