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..297502694a9 --- /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, (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, &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);