Skip to content

Commit c3ba979

Browse files
committed
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 mumble-voip#5257
1 parent e2debec commit c3ba979

11 files changed

+545
-30
lines changed

src/EnvUtils.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ bool setenv(QString name, QString value) {
5858

5959
bool waylandIsUsed() {
6060
// If wayland is used, this environment variable is expected to be set
61-
return getenv(QStringLiteral("WAYLAND_DISPLAY")) != "";
61+
// return getenv(QStringLiteral("WAYLAND_DISPLAY")) != "";
62+
// TODO just drop it all
63+
return false;
6264
}
6365

6466
}; // namespace EnvUtils

src/EnvUtils.h

-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ QString getenv(QString name);
2222

2323
bool setenv(QString name, QString value);
2424

25-
bool waylandIsUsed();
26-
2725
}; // namespace EnvUtils
2826

2927
#endif

src/mumble/CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -838,10 +838,17 @@ if(dbus AND NOT WIN32 AND NOT APPLE)
838838
PRIVATE
839839
"DBus.cpp"
840840
"DBus.h"
841+
"GlobalShortcut_xdp.cpp"
841842
)
842843

844+
qt_add_dbus_interface(mumble_xdp_SRCS org.freedesktop.portal.GlobalShortcuts.xml globalshortcuts_portal_interface)
845+
find_file(PORTALSREQUEST_XML share/dbus-1/interfaces/org.freedesktop.portal.Request.xml PATH_SUFFIXES share PATHS /usr ${CMAKE_INSTALL_PREFIX})
846+
qt_add_dbus_interface(mumble_xdp_SRCS ${PORTALSREQUEST_XML} portalsrequest_interface)
847+
target_sources(mumble_client_object_lib PRIVATE ${mumble_xdp_SRCS})
843848
target_compile_definitions(mumble_client_object_lib PUBLIC "USE_DBUS")
844849
target_link_libraries(mumble_client_object_lib PUBLIC Qt5::DBus)
850+
target_include_directories(mumble_client_object_lib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
851+
845852
endif()
846853

847854
if(translations)

src/mumble/GlobalShortcut.cpp

+13-6
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,15 @@ GlobalShortcutConfig::GlobalShortcutConfig(Settings &st) : ConfigWidget(st) {
545545

546546
bool canSuppress = GlobalShortcutEngine::engine->canSuppress();
547547
bool canDisable = GlobalShortcutEngine::engine->canDisable();
548+
bool canConfigure = GlobalShortcutEngine::engine->canConfigure();
549+
550+
qpbAdd->setVisible(!canConfigure);
551+
qpbRemove->setVisible(!canConfigure);
552+
qpbConfigure->setVisible(canConfigure);
553+
if (canConfigure) {
554+
connect(qpbConfigure, &QPushButton::clicked, GlobalShortcutEngine::engine, &GlobalShortcutEngine::configure);
555+
connect(GlobalShortcutEngine::engine, &GlobalShortcutEngine::shortcutsChanged, this, &GlobalShortcutConfig::reload);
556+
}
548557

549558
qwWarningContainer->setVisible(false);
550559

@@ -566,12 +575,6 @@ GlobalShortcutConfig::GlobalShortcutConfig(Settings &st) : ConfigWidget(st) {
566575
qcbEnableGlobalShortcuts->setVisible(canDisable);
567576

568577
qlWaylandNote->setVisible(false);
569-
#ifdef Q_OS_LINUX
570-
if (EnvUtils::waylandIsUsed()) {
571-
// Our global shortcut system doesn't work properly with Wayland
572-
qlWaylandNote->setVisible(true);
573-
}
574-
#endif
575578

576579
#ifdef Q_OS_MAC
577580
// Help Mac users enable accessibility access for Mumble...
@@ -908,6 +911,10 @@ bool GlobalShortcutEngine::canDisable() {
908911
return false;
909912
}
910913

914+
bool GlobalShortcutEngine::canConfigure() {
915+
return false;
916+
}
917+
911918
void GlobalShortcutEngine::resetMap() {
912919
tReset.restart();
913920
qlActiveButtons.clear();

src/mumble/GlobalShortcut.h

+3
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,16 @@ class GlobalShortcutEngine : public QThread {
253253
static void remove(GlobalShortcut *);
254254

255255
virtual bool canDisable();
256+
virtual bool canConfigure();
256257
virtual bool canSuppress();
257258
virtual bool enabled();
258259
virtual void setEnabled(bool b);
260+
virtual void configure() {}
259261

260262
virtual ButtonInfo buttonInfo(const QVariant &) = 0;
261263
signals:
262264
void buttonPressed(bool last);
265+
void shortcutsChanged();
263266
};
264267

265268
#endif

src/mumble/GlobalShortcut.ui

+7-13
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,6 @@
9595
</layout>
9696
</widget>
9797
</item>
98-
<item>
99-
<widget class="QLabel" name="qlWaylandNote">
100-
<property name="text">
101-
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Mumble's Global Shortcuts system does currently not work properly in combination with the Wayland protocol. For more information, visit &lt;a href=&quot;https://github.com/mumble-voip/mumble/issues/5257&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0057ae;&quot;&gt;https://github.com/mumble-voip/mumble/issues/5257&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
102-
</property>
103-
<property name="wordWrap">
104-
<bool>true</bool>
105-
</property>
106-
<property name="openExternalLinks">
107-
<bool>true</bool>
108-
</property>
109-
</widget>
110-
</item>
11198
<item>
11299
<widget class="QGroupBox" name="qgbShortcuts">
113100
<property name="title">
@@ -204,6 +191,13 @@
204191
</property>
205192
</widget>
206193
</item>
194+
<item>
195+
<widget class="QPushButton" name="qpbConfigure">
196+
<property name="text">
197+
<string>Configure</string>
198+
</property>
199+
</widget>
200+
</item>
207201
<item>
208202
<spacer name="horizontalSpacer">
209203
<property name="orientation">

src/mumble/GlobalShortcut_unix.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
55

66
#include "GlobalShortcut_unix.h"
7+
#include "GlobalShortcut_xdp.h"
78

89
#include "Settings.h"
910
#include "Global.h"
@@ -45,8 +46,13 @@
4546
* @see GlobalShortcutX
4647
* @see GlobalShortcutMac
4748
* @see GlobalShortcutWin
49+
* @see GlobalShortcutXdp
4850
*/
4951
GlobalShortcutEngine *GlobalShortcutEngine::platformInit() {
52+
if (GlobalShortcutXdp::isAvailable()) {
53+
return new GlobalShortcutXdp;
54+
}
55+
5056
return new GlobalShortcutX();
5157
}
5258

src/mumble/GlobalShortcut_xdp.cpp

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright 2007-2022 Aleix Pol Gonzalez <[email protected]>.
2+
// All rights reserved.
3+
// Use of this source code is governed by a BSD-style license
4+
// that can be found in the LICENSE file at the root of the
5+
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
6+
7+
#include "GlobalShortcut_xdp.h"
8+
9+
#include "Settings.h"
10+
#include "Global.h"
11+
#include "globalshortcuts_portal_interface.h"
12+
#include "portalsrequest_interface.h"
13+
#include "GlobalShortcutTypes.h"
14+
15+
#include <QtGui/QWindow>
16+
17+
Q_GLOBAL_STATIC_WITH_ARGS(OrgFreedesktopPortalGlobalShortcutsInterface, s_shortcutsInterface, (QLatin1String("org.freedesktop.portal.Desktop"),
18+
QLatin1String("/org/freedesktop/portal/desktop"),
19+
QDBusConnection::sessionBus()));
20+
21+
bool GlobalShortcutXdp::isAvailable()
22+
{
23+
return s_shortcutsInterface->isValid();
24+
}
25+
26+
GlobalShortcutXdp::GlobalShortcutXdp() {
27+
qDBusRegisterMetaType<XdpShortcuts>();
28+
qDBusRegisterMetaType<QPair<QString, QVariantMap>>();
29+
30+
connect(s_shortcutsInterface, &OrgFreedesktopPortalGlobalShortcutsInterface::Activated, this, [this] (const QDBusObjectPath &session_handle, const QString &shortcut_id, qulonglong timestamp, const QVariantMap &options) {
31+
Q_UNUSED(session_handle);
32+
Q_UNUSED(timestamp);
33+
Q_UNUSED(options);
34+
35+
foreach (GlobalShortcut *shortcut, qmShortcuts) {
36+
if (shortcut_id == shortcut->objectName()) {
37+
shortcut->triggered(true, shortcut_id);
38+
shortcut->down(shortcut_id);
39+
}
40+
}
41+
});
42+
connect(s_shortcutsInterface, &OrgFreedesktopPortalGlobalShortcutsInterface::Deactivated, this, [this] (const QDBusObjectPath &session_handle, const QString &shortcut_id, qulonglong timestamp, const QVariantMap &options) {
43+
Q_UNUSED(session_handle);
44+
Q_UNUSED(timestamp);
45+
Q_UNUSED(options);
46+
47+
foreach (GlobalShortcut *shortcut, qmShortcuts) {
48+
if (shortcut_id == shortcut->objectName()) {
49+
shortcut->triggered(false, shortcut_id);
50+
}
51+
}
52+
});
53+
connect(s_shortcutsInterface, &OrgFreedesktopPortalGlobalShortcutsInterface::ShortcutsChanged, this, [this] (const QDBusObjectPath &session_handle, const QList<QPair<QString,QVariantMap>> &shortcuts) {
54+
Q_UNUSED(session_handle);
55+
qDebug() << "ShortcutsChanged" << shortcuts;
56+
if (m_shortcuts != shortcuts) {
57+
m_shortcuts = shortcuts;
58+
Q_EMIT shortcutsChanged();
59+
}
60+
});
61+
62+
QTimer::singleShot(0, this, &GlobalShortcutXdp::createSession);
63+
}
64+
65+
GlobalShortcutXdp::~GlobalShortcutXdp() {
66+
67+
}
68+
69+
void GlobalShortcutXdp::createSession()
70+
{
71+
XdpShortcuts initialShortcuts;
72+
initialShortcuts.reserve(qmShortcuts.count());
73+
Global::get().s.qlShortcuts.clear();
74+
75+
int i = 0;
76+
m_ids.resize(qmShortcuts.count());
77+
foreach (GlobalShortcut *shortcut, qmShortcuts) {
78+
initialShortcuts.append({shortcut->objectName(), {
79+
{ QStringLiteral("description"), shortcut->name }
80+
}});
81+
82+
Shortcut ssss = { i, {uint(i)}, shortcut->qvDefault, false};
83+
m_ids[i] = shortcut->objectName();
84+
Global::get().s.qlShortcuts << ssss;
85+
++i;
86+
}
87+
88+
QDBusArgument arg;
89+
arg << initialShortcuts;
90+
auto reply = s_shortcutsInterface->CreateSession({
91+
{ QLatin1String("session_handle_token"), "Mumble" },
92+
{ QLatin1String("handle_token"), QLatin1String("mumble") },
93+
{ QLatin1String("shortcuts"), QVariant::fromValue(arg) },
94+
});
95+
reply.waitForFinished();
96+
if (reply.isError()) {
97+
qWarning() << "Couldn't get reply";
98+
qWarning() << "Error:" << reply.error().message();
99+
} else {
100+
QDBusConnection::sessionBus().connect(QString(),
101+
reply.value().path(),
102+
QLatin1String("org.freedesktop.portal.Request"),
103+
QLatin1String("Response"),
104+
this,
105+
SLOT(gotGlobalShortcutsCreateSessionResponse(uint,QVariantMap)));
106+
}
107+
}
108+
109+
void GlobalShortcutXdp::run() {
110+
// 🤘🤪
111+
}
112+
113+
bool GlobalShortcutXdp::canDisable() {
114+
return false;
115+
}
116+
117+
GlobalShortcutXdp::ButtonInfo GlobalShortcutXdp::buttonInfo(const QVariant &v) {
118+
ButtonInfo info;
119+
bool ok;
120+
unsigned int key = v.toUInt(&ok);
121+
if (!ok) {
122+
return info;
123+
}
124+
125+
info.device = tr("Desktop");
126+
info.devicePrefix = QString();
127+
const auto id = m_ids[key];
128+
for (const auto &x : m_shortcuts) {
129+
if (x.first == id) {
130+
info.name = x.second["trigger_description"].toString();
131+
}
132+
}
133+
return info;
134+
}
135+
136+
static QString parentWindowId()
137+
{
138+
if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) {
139+
// TODO
140+
return {};
141+
}
142+
return QLatin1String("x11:") + QString::number(qApp->focusWindow()->winId());
143+
}
144+
145+
void GlobalShortcutXdp::gotGlobalShortcutsCreateSessionResponse(uint code, const QVariantMap &results)
146+
{
147+
if (code != 0) {
148+
qWarning() << "failed to create a global shortcuts session" << code << results;
149+
return;
150+
}
151+
152+
m_globalShortcutsSession = QDBusObjectPath(results["session_handle"].toString());
153+
154+
auto reply = s_shortcutsInterface->ListShortcuts(m_globalShortcutsSession, {});
155+
reply.waitForFinished();
156+
if (reply.isError()) {
157+
qWarning() << "failed to call ListShortcuts" << reply.error();
158+
return;
159+
}
160+
161+
auto req = new OrgFreedesktopPortalRequestInterface(QLatin1String("org.freedesktop.portal.Desktop"),
162+
reply.value().path(), QDBusConnection::sessionBus(), this);
163+
164+
// BindShortcuts and ListShortcuts answer the same
165+
connect(req, &OrgFreedesktopPortalRequestInterface::Response, this, &GlobalShortcutXdp::gotListShortcutsResponse);
166+
connect(req, &OrgFreedesktopPortalRequestInterface::Response, req, &QObject::deleteLater);
167+
}
168+
169+
void GlobalShortcutXdp::gotListShortcutsResponse(uint, const QVariantMap &results)
170+
{
171+
const auto arg = results["shortcuts"].value<QDBusArgument>();
172+
arg >> m_shortcuts;
173+
}
174+
175+
void GlobalShortcutXdp::configure()
176+
{
177+
auto reply = s_shortcutsInterface->BindShortcuts(m_globalShortcutsSession, m_shortcuts, parentWindowId(), {});
178+
reply.waitForFinished();
179+
if (reply.isError()) {
180+
qWarning() << "failed to call ListShortcuts" << reply.error();
181+
return;
182+
}
183+
184+
auto req = new OrgFreedesktopPortalRequestInterface(QLatin1String("org.freedesktop.portal.Desktop"),
185+
reply.value().path(), QDBusConnection::sessionBus(), this);
186+
187+
// BindShortcuts and ListShortcuts answer the same
188+
connect(req, &OrgFreedesktopPortalRequestInterface::Response, this, &GlobalShortcutXdp::gotListShortcutsResponse);
189+
connect(req, &OrgFreedesktopPortalRequestInterface::Response, req, &QObject::deleteLater);
190+
}

src/mumble/GlobalShortcut_xdp.h

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2007-2022 Aleix Pol Gonzalez <[email protected]>.
2+
// All rights reserved.
3+
// Use of this source code is governed by a BSD-style license
4+
// that can be found in the LICENSE file at the root of the
5+
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
6+
7+
#ifndef MUMBLE_MUMBLE_GLOBALSHORTCUT_XDP_H_
8+
#define MUMBLE_MUMBLE_GLOBALSHORTCUT_XDP_H_
9+
10+
#include "ConfigDialog.h"
11+
#include "Global.h"
12+
#include "GlobalShortcut.h"
13+
14+
#include <QDBusObjectPath>
15+
16+
class OrgFreedesktopPortalGlobalShortcutsInterface;
17+
18+
using XdpShortcuts = QList<QPair<QString, QVariantMap>>;
19+
20+
class GlobalShortcutXdp : public GlobalShortcutEngine {
21+
private:
22+
Q_OBJECT
23+
Q_DISABLE_COPY(GlobalShortcutXdp)
24+
public:
25+
static bool isAvailable();
26+
27+
void createSession();
28+
29+
GlobalShortcutXdp();
30+
~GlobalShortcutXdp() Q_DECL_OVERRIDE;
31+
void run() Q_DECL_OVERRIDE;
32+
ButtonInfo buttonInfo(const QVariant &) Q_DECL_OVERRIDE;
33+
34+
bool canDisable() override;
35+
bool canConfigure() override { return true; }
36+
void configure() override;
37+
38+
public Q_SLOTS:
39+
void gotGlobalShortcutsCreateSessionResponse(uint, const QVariantMap &results);
40+
void gotListShortcutsResponse(uint, const QVariantMap &results);
41+
42+
private:
43+
XdpShortcuts m_shortcuts;
44+
QVector<QString> m_ids;
45+
QDBusObjectPath m_globalShortcutsSession;
46+
};
47+
48+
#endif

0 commit comments

Comments
 (0)