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..bd9cfb9fc --- /dev/null +++ b/data/org.freedesktop.impl.portal.GlobalShortcuts.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/org.freedesktop.portal.GlobalShortcuts.xml b/data/org.freedesktop.portal.GlobalShortcuts.xml new file mode 100644 index 000000000..cf2a66d5e --- /dev/null +++ b/data/org.freedesktop.portal.GlobalShortcuts.xml @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..1cd1f391e --- /dev/null +++ b/src/globalshortcuts.c @@ -0,0 +1,454 @@ +/* + * 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 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_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_bind_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_bind_shortcuts ( + XdpGlobalShortcuts *object, + GDBusMethodInvocation *invocation, + const gchar *arg_session_handle, + const gchar *const *arg_shortcuts, + const gchar *arg_parent_window, + GVariant *arg_options) +{ + 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_shortcuts (impl, + request->id, + arg_session_handle, + arg_shortcuts, + arg_parent_window, + arg_options, + NULL, + bind_shortcuts_done, + g_object_ref (request)); + + xdp_global_shortcuts_complete_bind_shortcuts (object, invocation, request->id); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +list_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, + list_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_shortcuts = handle_bind_shortcuts; + 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, + guint64 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_global_shortcuts_emit_activated(data, session_id, shortcut_id, timestamp); + } +} + +static void +deactivated_cb (XdpImplGlobalShortcuts *impl, + const char *session_id, + const char *shortcut_id, + guint64 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_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_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);