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);