diff --git a/Makefile.am.inc b/Makefile.am.inc
index 7be4c4f89..32b3d3656 100644
--- a/Makefile.am.inc
+++ b/Makefile.am.inc
@@ -9,6 +9,7 @@ PORTAL_IFACE_FILES =\
$(top_srcdir)/data/org.freedesktop.portal.FileChooser.xml \
$(top_srcdir)/data/org.freedesktop.portal.FileTransfer.xml \
$(top_srcdir)/data/org.freedesktop.portal.GameMode.xml \
+ $(top_srcdir)/data/org.freedesktop.portal.GlobalShortcuts.xml \
$(top_srcdir)/data/org.freedesktop.portal.Inhibit.xml \
$(top_srcdir)/data/org.freedesktop.portal.Location.xml \
$(top_srcdir)/data/org.freedesktop.portal.MemoryMonitor.xml \
@@ -38,6 +39,7 @@ PORTAL_IMPL_IFACE_FILES =\
$(top_srcdir)/data/org.freedesktop.impl.portal.DynamicLauncher.xml \
$(top_srcdir)/data/org.freedesktop.impl.portal.Email.xml \
$(top_srcdir)/data/org.freedesktop.impl.portal.FileChooser.xml \
+ $(top_srcdir)/data/org.freedesktop.impl.portal.GlobalShortcuts.xml \
$(top_srcdir)/data/org.freedesktop.impl.portal.Inhibit.xml \
$(top_srcdir)/data/org.freedesktop.impl.portal.Lockdown.xml \
$(top_srcdir)/data/org.freedesktop.impl.portal.Notification.xml \
diff --git a/data/org.freedesktop.impl.portal.GlobalShortcuts.xml b/data/org.freedesktop.impl.portal.GlobalShortcuts.xml
new file mode 100644
index 000000000..81137314a
--- /dev/null
+++ b/data/org.freedesktop.impl.portal.GlobalShortcuts.xml
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/org.freedesktop.portal.GlobalShortcuts.xml b/data/org.freedesktop.portal.GlobalShortcuts.xml
new file mode 100644
index 000000000..38d516e65
--- /dev/null
+++ b/data/org.freedesktop.portal.GlobalShortcuts.xml
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Makefile.am.inc b/src/Makefile.am.inc
index 0d15b040c..56c8ca51a 100644
--- a/src/Makefile.am.inc
+++ b/src/Makefile.am.inc
@@ -128,6 +128,8 @@ xdg_desktop_portal_SOURCES = \
src/flatpak-instance.h \
src/portal-impl.h \
src/portal-impl.c \
+ src/globalshortcuts.h \
+ src/globalshortcuts.c \
$(NULL)
if HAVE_LIBSYSTEMD
diff --git a/src/globalshortcuts.c b/src/globalshortcuts.c
new file mode 100644
index 000000000..bd7f2fc03
--- /dev/null
+++ b/src/globalshortcuts.c
@@ -0,0 +1,455 @@
+/*
+ * Copyright © 2022 Aleix Pol Gonzalez
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors:
+ * Aleix Pol Gonzalez
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "globalshortcuts.h"
+#include "request.h"
+#include "session.h"
+#include "permissions.h"
+#include "xdp-dbus.h"
+#include "xdp-impl-dbus.h"
+#include "xdp-utils.h"
+
+typedef struct _GlobalShortcuts GlobalShortcuts;
+typedef struct _GlobalShortcutsClass GlobalShortcutsClass;
+
+static GQuark quark_request_session;
+
+struct _GlobalShortcuts
+{
+ XdpGlobalShortcutsSkeleton parent_instance;
+};
+
+struct _GlobalShortcutsClass
+{
+ XdpGlobalShortcutsSkeletonClass parent_class;
+};
+
+static XdpImplGlobalShortcuts *impl;
+static GlobalShortcuts *global_shortcuts;
+
+GType global_shortcuts_get_type (void) G_GNUC_CONST;
+static void global_shortcuts_iface_init (XdpGlobalShortcutsIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GlobalShortcuts, global_shortcuts, XDP_TYPE_GLOBAL_SHORTCUTS_SKELETON,
+ G_IMPLEMENT_INTERFACE (XDP_TYPE_GLOBAL_SHORTCUTS, global_shortcuts_iface_init));
+
+typedef struct _GlobalShortcutsSession
+{
+ Session parent;
+ gboolean registering;
+
+ gboolean closed;
+} GlobalShortcutsSession;
+
+typedef struct _GlobalShortcutsSessionClass
+{
+ SessionClass parent_class;
+} GlobalShortcutsSessionClass;
+
+GType global_shortcuts_session_get_type (void);
+
+G_DEFINE_TYPE (GlobalShortcutsSession, global_shortcuts_session, session_get_type ())
+
+static void
+global_shortcuts_session_close (Session *session)
+{
+ GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session;
+
+ global_shortcuts_session->closed = TRUE;
+}
+
+static void
+global_shortcuts_session_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (global_shortcuts_session_parent_class)->finalize (object);
+}
+
+static void
+global_shortcuts_session_init (GlobalShortcutsSession *global_shortcuts_session)
+{
+}
+
+static void
+global_shortcuts_session_class_init (GlobalShortcutsSessionClass *klass)
+{
+ GObjectClass *object_class;
+ SessionClass *session_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = global_shortcuts_session_finalize;
+
+ session_class = (SessionClass *)klass;
+ session_class->close = global_shortcuts_session_close;
+}
+
+static GlobalShortcutsSession *
+global_shortcuts_session_new (GVariant *options,
+ GDBusMethodInvocation *invocation,
+ GError **error)
+{
+ Session *session;
+ const char *session_token;
+ GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation);
+ const gchar *sender = g_dbus_method_invocation_get_sender (invocation);
+ XdpAppInfo *app_info = xdp_invocation_lookup_app_info_sync (invocation, NULL, NULL);
+ const char *impl_dbus_name = g_dbus_proxy_get_name (G_DBUS_PROXY (impl));
+
+ session_token = lookup_session_token (options);
+ session = g_initable_new (global_shortcuts_session_get_type (), NULL, error,
+ "sender", sender,
+ "app-id", xdp_app_info_get_id (app_info),
+ "token", session_token,
+ "connection", connection,
+ "impl-dbus-name", impl_dbus_name,
+ NULL);
+
+ if (session)
+ g_debug ("global_shortcuts session owned by '%s' created", session->sender);
+
+ return (GlobalShortcutsSession*)session;
+}
+
+static gboolean
+handle_create_session (
+ XdpGlobalShortcuts *object,
+ GDBusMethodInvocation *invocation,
+ GVariant *arg_options)
+{
+ g_autoptr(GError) error = NULL;
+ GlobalShortcutsSession *session;
+
+ session = global_shortcuts_session_new (arg_options, invocation, &error);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (!session_export ((Session *)session, &error))
+ {
+ g_warning ("Failed to export session: %s", error->message);
+ session_close ((Session *)session, FALSE);
+ }
+ else
+ {
+ g_debug ("CreateSession new session '%s'", ((Session *)session)->id);
+ session_register ((Session *)session);
+ }
+
+ xdp_global_shortcuts_complete_create_session (object, invocation, ((Session *)session)->id);
+
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+void
+bind_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ g_autoptr(Request) request = data;
+ Session *session;
+ guint response = 2;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) results = NULL;
+
+ REQUEST_AUTOLOCK (request);
+
+ session = g_object_get_qdata (G_OBJECT (request), quark_request_session);
+ SESSION_AUTOLOCK_UNREF (g_object_ref (session));
+ g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL);
+
+ if (!xdp_impl_global_shortcuts_call_bind_finish (impl, &results, res, &error))
+ {
+ g_dbus_error_strip_remote_error (error);
+ g_warning ("A backend call failed: %s", error->message);
+ }
+
+ if (request->exported)
+ {
+ if (!results)
+ {
+ GVariantBuilder results_builder;
+
+ g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT);
+ results = g_variant_ref_sink (g_variant_builder_end (&results_builder));
+ }
+
+ xdp_request_emit_response (XDP_REQUEST (request), response, results);
+ request_unexport (request);
+ }
+
+ GlobalShortcutsSession *gs_session = (GlobalShortcutsSession *)session;
+ g_assert (gs_session->registering);
+ gs_session->registering = FALSE;
+}
+
+static gboolean
+handle_bind (
+ XdpGlobalShortcuts *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *arg_session_handle,
+ const gchar *arg_name)
+{
+ Request *request = request_from_invocation (invocation);
+ Session *session;
+ g_autoptr(XdpImplRequest) impl_request = NULL;
+ g_autoptr(GError) error = NULL;
+
+ REQUEST_AUTOLOCK (request);
+
+ session = acquire_session (arg_session_handle, request);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ SESSION_AUTOLOCK_UNREF (session);
+
+ impl_request =
+ xdp_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)),
+ G_DBUS_PROXY_FLAGS_NONE,
+ g_dbus_proxy_get_name (G_DBUS_PROXY (impl)),
+ request->id,
+ NULL, &error);
+ if (!impl_request)
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ request_set_impl_request (request, impl_request);
+ request_export (request, g_dbus_method_invocation_get_connection (invocation));
+
+ g_object_set_qdata_full (G_OBJECT (request),
+ quark_request_session,
+ g_object_ref (session),
+ g_object_unref);
+
+ xdp_impl_global_shortcuts_call_bind (impl,
+ request->id,
+ arg_session_handle,
+ arg_name,
+ NULL,
+ bind_done,
+ g_object_ref (request));
+
+ xdp_global_shortcuts_complete_bind (object, invocation, request->id);
+
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+shortcuts_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ g_autoptr(Request) request = data;
+ Session *session;
+ guint response = 2;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) results = NULL;
+
+ REQUEST_AUTOLOCK (request);
+
+ session = g_object_get_qdata (G_OBJECT (request), quark_request_session);
+ SESSION_AUTOLOCK_UNREF (g_object_ref (session));
+ g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL);
+
+ if (!xdp_impl_global_shortcuts_call_list_shortcuts_finish (impl, &results, res, &error))
+ {
+ g_dbus_error_strip_remote_error (error);
+ g_warning ("A backend call failed: %s", error->message);
+ }
+
+ if (request->exported)
+ {
+ if (!results)
+ {
+ GVariantBuilder results_builder;
+
+ g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT);
+ results = g_variant_ref_sink (g_variant_builder_end (&results_builder));
+ }
+
+ xdp_request_emit_response (XDP_REQUEST (request), response, results);
+ request_unexport (request);
+ }
+}
+
+static gboolean
+handle_list_shortcuts (
+ XdpGlobalShortcuts *object,
+ GDBusMethodInvocation *invocation,
+ const gchar *arg_session_handle)
+{
+ Request *request = request_from_invocation (invocation);
+ Session *session;
+ g_autoptr(XdpImplRequest) impl_request = NULL;
+ g_autoptr(GError) error = NULL;
+
+ REQUEST_AUTOLOCK (request);
+
+ session = acquire_session (arg_session_handle, request);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ impl_request =
+ xdp_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)),
+ G_DBUS_PROXY_FLAGS_NONE,
+ g_dbus_proxy_get_name (G_DBUS_PROXY (impl)),
+ request->id,
+ NULL, &error);
+ if (!impl_request)
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ request_set_impl_request (request, impl_request);
+ request_export (request, g_dbus_method_invocation_get_connection (invocation));
+
+ g_object_set_qdata_full (G_OBJECT (request),
+ quark_request_session,
+ g_object_ref (session),
+ g_object_unref);
+
+ xdp_impl_global_shortcuts_call_list_shortcuts (impl,
+ request->id,
+ arg_session_handle,
+ NULL,
+ shortcuts_done,
+ g_object_ref (request));
+
+ xdp_global_shortcuts_complete_list_shortcuts (object, invocation, request->id);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+global_shortcuts_iface_init (XdpGlobalShortcutsIface *iface)
+{
+ xdp_global_shortcuts_set_version (XDP_GLOBAL_SHORTCUTS (iface), 1);
+
+ iface->handle_create_session = handle_create_session;
+ iface->handle_bind = handle_bind;
+ iface->handle_list_shortcuts = handle_list_shortcuts;
+}
+
+static void
+global_shortcuts_init (GlobalShortcuts *global_shortcuts)
+{
+ xdp_global_shortcuts_set_version(XDP_GLOBAL_SHORTCUTS (global_shortcuts), 1);
+}
+
+static void
+global_shortcuts_class_init (GlobalShortcutsClass *klass)
+{
+}
+
+static void
+activated_cb (XdpImplGlobalShortcuts *impl,
+ const char *session_id,
+ const char *shortcut_id,
+ gint64 timestamp,
+ gpointer data)
+{
+ g_autoptr(Session) session = lookup_session (session_id);
+ GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session;
+
+ g_debug ("Received activated %s for %s", session_id, shortcut_id);
+
+ if (global_shortcuts_session && !global_shortcuts_session->closed) {
+ xdp_impl_global_shortcuts_emit_activated(data, session_id, shortcut_id, timestamp);
+ }
+}
+
+static void
+deactivated_cb (XdpImplGlobalShortcuts *impl,
+ const char *session_id,
+ const char *shortcut_id,
+ gint64 timestamp,
+ gpointer data)
+{
+ g_autoptr(Session) session = lookup_session (session_id);
+ GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session;
+
+ g_debug ("Received deactivated %s for %s", session_id, shortcut_id);
+
+ if (global_shortcuts_session && !global_shortcuts_session->closed) {
+ xdp_impl_global_shortcuts_emit_deactivated(data, session_id, shortcut_id, timestamp);
+ }
+}
+
+static void
+shortcutschanged_cb (XdpImplGlobalShortcuts *impl,
+ const char *session_id,
+ gpointer data)
+{
+ g_autoptr(Session) session = lookup_session (session_id);
+ GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session;
+
+ g_debug ("Received ShortcutsChanged %s", session_id);
+
+ if (global_shortcuts_session && !global_shortcuts_session->closed) {
+ xdp_impl_global_shortcuts_emit_shortcuts_changed(data, session_id);
+ }
+}
+
+GDBusInterfaceSkeleton *
+global_shortcuts_create (GDBusConnection *connection,
+ const char *dbus_name)
+{
+ g_autoptr(GError) error = NULL;
+
+ impl = xdp_impl_global_shortcuts_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ dbus_name,
+ "/org/freedesktop/portal/desktop",
+ NULL, &error);
+ if (impl == NULL)
+ {
+ g_warning ("Failed to create global_shortcuts proxy: %s", error->message);
+ return NULL;
+ }
+
+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (impl), G_MAXINT);
+
+ global_shortcuts = g_object_new (global_shortcuts_get_type (), NULL);
+
+ g_signal_connect (impl, "Activated", G_CALLBACK (activated_cb), global_shortcuts);
+ g_signal_connect (impl, "Deactivated", G_CALLBACK (deactivated_cb), global_shortcuts);
+ g_signal_connect (impl, "ShortcutsChanged", G_CALLBACK (shortcutschanged_cb), global_shortcuts);
+
+ return G_DBUS_INTERFACE_SKELETON (global_shortcuts);
+}
diff --git a/src/globalshortcuts.h b/src/globalshortcuts.h
new file mode 100644
index 000000000..4f884d60b
--- /dev/null
+++ b/src/globalshortcuts.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2022 Aleix Pol Gonzalez
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors:
+ * Aleix Pol Gonzalez
+ */
+
+#pragma once
+
+#include
+
+GDBusInterfaceSkeleton * globalshortcuts_create (GDBusConnection *connection,
+ const char *dbus_name);