Skip to content

Commit

Permalink
FEAT(client): Add support to XDG Desktop Portal GlobalShortcuts
Browse files Browse the repository at this point in the history
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
  • Loading branch information
aleixpol committed Nov 25, 2022
1 parent e2debec commit e9f84da
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 34 deletions.
5 changes: 0 additions & 5 deletions src/EnvUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 0 additions & 2 deletions src/EnvUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ QString getenv(QString name);

bool setenv(QString name, QString value);

bool waylandIsUsed();

}; // namespace EnvUtils

#endif
7 changes: 7 additions & 0 deletions src/mumble/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 13 additions & 6 deletions src/mumble/GlobalShortcut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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...
Expand Down Expand Up @@ -908,6 +911,10 @@ bool GlobalShortcutEngine::canDisable() {
return false;
}

bool GlobalShortcutEngine::canConfigure() {
return false;
}

void GlobalShortcutEngine::resetMap() {
tReset.restart();
qlActiveButtons.clear();
Expand Down
3 changes: 3 additions & 0 deletions src/mumble/GlobalShortcut.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
20 changes: 7 additions & 13 deletions src/mumble/GlobalShortcut.ui
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,6 @@
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="qlWaylandNote">
<property name="text">
<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>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="qgbShortcuts">
<property name="title">
Expand Down Expand Up @@ -204,6 +191,13 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="qpbConfigure">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
Expand Down
6 changes: 6 additions & 0 deletions src/mumble/GlobalShortcut_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#include "GlobalShortcut_unix.h"
#include "GlobalShortcut_xdp.h"

#include "Settings.h"
#include "Global.h"
Expand Down Expand Up @@ -45,8 +46,13 @@
* @see GlobalShortcutX
* @see GlobalShortcutMac
* @see GlobalShortcutWin
* @see GlobalShortcutXdp
*/
GlobalShortcutEngine *GlobalShortcutEngine::platformInit() {
if (GlobalShortcutXdp::isAvailable()) {
return new GlobalShortcutXdp;
}

return new GlobalShortcutX();
}

Expand Down
190 changes: 190 additions & 0 deletions src/mumble/GlobalShortcut_xdp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Copyright 2007-2022 Aleix Pol Gonzalez <aleixpol@kde.org>.
// 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 <https://www.mumble.info/LICENSE>.

#include "GlobalShortcut_xdp.h"

#include "Settings.h"
#include "Global.h"
#include "globalshortcuts_portal_interface.h"
#include "portalsrequest_interface.h"
#include "GlobalShortcutTypes.h"

#include <QtGui/QWindow>

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<XdpShortcuts>();
qDBusRegisterMetaType<QPair<QString, QVariantMap>>();

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<QPair<QString,QVariantMap>> &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<QDBusArgument>();
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);
}
48 changes: 48 additions & 0 deletions src/mumble/GlobalShortcut_xdp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2007-2022 Aleix Pol Gonzalez <aleixpol@kde.org>.
// 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 <https://www.mumble.info/LICENSE>.

#ifndef MUMBLE_MUMBLE_GLOBALSHORTCUT_XDP_H_
#define MUMBLE_MUMBLE_GLOBALSHORTCUT_XDP_H_

#include "ConfigDialog.h"
#include "Global.h"
#include "GlobalShortcut.h"

#include <QDBusObjectPath>

class OrgFreedesktopPortalGlobalShortcutsInterface;

using XdpShortcuts = QList<QPair<QString, QVariantMap>>;

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<QString> m_ids;
QDBusObjectPath m_globalShortcutsSession;
};

#endif
Loading

0 comments on commit e9f84da

Please sign in to comment.