-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
11 changed files
with
541 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
// Copyright 2007-2022 Aleix Pol Gonzalez <[email protected]>. | ||
// 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); | ||
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Copyright 2007-2022 Aleix Pol Gonzalez <[email protected]>. | ||
// 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 |
Oops, something went wrong.