/* * Copyright © 2016 Red Hat, Inc * * 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: * Matthias Clasen */ #include "config.h" #include #include #include "inhibit.h" #include "request.h" #include "session.h" #include "permissions.h" #include "xdp-dbus.h" #include "xdp-impl-dbus.h" #include "xdp-utils.h" #define PERMISSION_TABLE "inhibit" #define PERMISSION_ID "inhibit" enum { INHIBIT_LOGOUT = 1, INHIBIT_USER_SWITCH = 2, INHIBIT_SUSPEND = 4, INHIBIT_IDLE = 8 }; #define INHIBIT_ALL (INHIBIT_LOGOUT|INHIBIT_USER_SWITCH|INHIBIT_SUSPEND|INHIBIT_IDLE) typedef struct _Inhibit Inhibit; typedef struct _InhibitClass InhibitClass; struct _Inhibit { XdpDbusInhibitSkeleton parent_instance; }; struct _InhibitClass { XdpDbusInhibitSkeletonClass parent_class; }; static XdpDbusImplInhibit *impl; static Inhibit *inhibit; GType inhibit_get_type (void) G_GNUC_CONST; static void inhibit_iface_init (XdpDbusInhibitIface *iface); G_DEFINE_TYPE_WITH_CODE (Inhibit, inhibit, XDP_DBUS_TYPE_INHIBIT_SKELETON, G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_INHIBIT, inhibit_iface_init)); static void inhibit_done (GObject *source, GAsyncResult *result, gpointer data) { g_autoptr(GError) error = NULL; Request *request = data; int response = 0; REQUEST_AUTOLOCK (request); if (!xdp_dbus_impl_inhibit_call_inhibit_finish (impl, result, &error)) { g_dbus_error_strip_remote_error (error); g_warning ("A backend call failed: %s", error->message); response = 2; } if (request->exported) { GVariantBuilder new_results; g_variant_builder_init (&new_results, G_VARIANT_TYPE_VARDICT); xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&new_results)); } } static guint32 get_allowed_inhibit (const char *app_id) { g_auto(GStrv) perms = NULL; guint32 ret = 0; perms = get_permissions_sync (app_id, PERMISSION_TABLE, PERMISSION_ID); if (perms != NULL) { int i; for (i = 0; perms[i]; i++) { if (strcmp (perms[i], "logout") == 0) ret |= INHIBIT_LOGOUT; else if (strcmp (perms[i], "switch") == 0) ret |= INHIBIT_USER_SWITCH; else if (strcmp (perms[i], "suspend") == 0) ret |= INHIBIT_SUSPEND; else if (strcmp (perms[i], "idle") == 0) ret |= INHIBIT_IDLE; else g_warning ("Unknown inhibit flag in permission store: %s", perms[i]); } } else ret = INHIBIT_ALL; /* all allowed */ g_debug ("Inhibit permissions for %s: %d", app_id, ret); return ret; } static void handle_inhibit_in_thread_func (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { Request *request = (Request *)task_data; const char *window; guint32 flags; GVariant *options; const char *app_id; REQUEST_AUTOLOCK (request); window = (const char *)g_object_get_data (G_OBJECT (request), "window"); flags = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (request), "flags")); options = (GVariant *)g_object_get_data (G_OBJECT (request), "options"); app_id = xdp_app_info_get_id (request->app_info); flags = flags & get_allowed_inhibit (app_id); if (flags == 0) return; g_debug ("Calling inhibit backend for %s: %d", app_id, flags); xdp_dbus_impl_inhibit_call_inhibit (impl, request->id, app_id, window, flags, options, NULL, inhibit_done, g_object_ref (request)); } static gboolean validate_reason (const char *key, GVariant *value, GVariant *options, GError **error) { const char *string = g_variant_get_string (value, NULL); if (g_utf8_strlen (string, -1) > 256) { g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, "Not accepting overly long reasons"); return FALSE; } return TRUE; } static XdpOptionKey inhibit_options[] = { { "reason", G_VARIANT_TYPE_STRING, validate_reason } }; static gboolean handle_inhibit (XdpDbusInhibit *object, GDBusMethodInvocation *invocation, const char *arg_window, guint32 arg_flags, GVariant *arg_options) { Request *request = request_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; g_autoptr(GTask) task = NULL; GVariantBuilder opt_builder; g_autoptr(GVariant) options = NULL; REQUEST_AUTOLOCK (request); if ((arg_flags & ~INHIBIT_ALL) != 0) { g_dbus_method_invocation_return_error (invocation, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, "Invalid flags"); return G_DBUS_METHOD_INVOCATION_HANDLED; } g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); xdp_filter_options (arg_options, &opt_builder, inhibit_options, G_N_ELEMENTS (inhibit_options), NULL); options = g_variant_ref_sink (g_variant_builder_end (&opt_builder)); g_object_set_data_full (G_OBJECT (request), "window", g_strdup (arg_window), g_free); g_object_set_data (G_OBJECT (request), "flags", GUINT_TO_POINTER (arg_flags)); g_object_set_data_full (G_OBJECT (request), "options", g_variant_ref (options), (GDestroyNotify)g_variant_unref); impl_request = xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)), G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, 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)); task = g_task_new (object, NULL, NULL, NULL); g_task_set_task_data (task, g_object_ref (request), g_object_unref); g_task_run_in_thread (task, handle_inhibit_in_thread_func); xdp_dbus_inhibit_complete_inhibit (object, invocation, request->id); return G_DBUS_METHOD_INVOCATION_HANDLED; } typedef struct _InhibitSession { Session parent; gboolean closed; } InhibitSession; typedef struct _InhibitSessionClass { SessionClass parent_class; } InhibitSessionClass; GType inhibit_session_get_type (void); G_DEFINE_TYPE (InhibitSession, inhibit_session, session_get_type ()) static void inhibit_session_close (Session *session) { InhibitSession *inhibit_session = (InhibitSession *)session; inhibit_session->closed = TRUE; g_debug ("inhibit session owned by '%s' closed", session->sender); } static void inhibit_session_finalize (GObject *object) { G_OBJECT_CLASS (inhibit_session_parent_class)->finalize (object); } static void inhibit_session_init (InhibitSession *inhibit_session) { } static void inhibit_session_class_init (InhibitSessionClass *klass) { GObjectClass *object_class; SessionClass *session_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = inhibit_session_finalize; session_class = (SessionClass *)klass; session_class->close = inhibit_session_close; } static InhibitSession * inhibit_session_new (GVariant *options, Request *request, GError **error) { Session *session; const char *session_token; GDBusInterfaceSkeleton *interface_skeleton = G_DBUS_INTERFACE_SKELETON (request); GDBusConnection *connection = g_dbus_interface_skeleton_get_connection (interface_skeleton); GDBusConnection *impl_connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); const char *impl_dbus_name = g_dbus_proxy_get_name (G_DBUS_PROXY (impl)); session_token = lookup_session_token (options); session = g_initable_new (inhibit_session_get_type (), NULL, error, "sender", request->sender, "app-id", xdp_app_info_get_id (request->app_info), "token", session_token, "connection", connection, "impl-connection", impl_connection, "impl-dbus-name", impl_dbus_name, NULL); if (session) g_debug ("inhibit session owned by '%s' created", session->sender); return (InhibitSession*)session; } static void create_monitor_done (GObject *source_object, GAsyncResult *res, gpointer data) { g_autoptr(Request) request = data; Session *session; guint response = 2; gboolean should_close_session; GVariantBuilder results_builder; g_autoptr(GError) error = NULL; REQUEST_AUTOLOCK (request); session = g_object_get_data (G_OBJECT (request), "session"); SESSION_AUTOLOCK_UNREF (g_object_ref (session)); g_object_set_data (G_OBJECT (request), "session", NULL); g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); if (!xdp_dbus_impl_inhibit_call_create_monitor_finish (impl, &response, res, &error)) { g_dbus_error_strip_remote_error (error); g_warning ("A backend call failed: %s", error->message); should_close_session = TRUE; goto out; } if (request->exported && response == 0) { if (!session_export (session, &error)) { g_warning ("Failed to export session: %s", error->message); response = 2; should_close_session = TRUE; goto out; } should_close_session = FALSE; session_register (session); } else { should_close_session = TRUE; } g_variant_builder_add (&results_builder, "{sv}", "session_handle", g_variant_new ("s", session->id)); out: if (request->exported) { xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results_builder)); request_unexport (request); } else { g_variant_builder_clear (&results_builder); } if (should_close_session) session_close (session, FALSE); } static gboolean handle_create_monitor (XdpDbusInhibit *object, GDBusMethodInvocation *invocation, const char *arg_window, GVariant *arg_options) { Request *request = request_from_invocation (invocation); g_autoptr(GError) error = NULL; g_autoptr(XdpDbusImplRequest) impl_request = NULL; Session *session; REQUEST_AUTOLOCK (request); impl_request = xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)), G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, 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)); session = (Session *)inhibit_session_new (arg_options, request, &error); if (!session) { g_dbus_method_invocation_return_gerror (invocation, error); return G_DBUS_METHOD_INVOCATION_HANDLED; } g_object_set_data_full (G_OBJECT (request), "session", g_object_ref (session), g_object_unref); xdp_dbus_impl_inhibit_call_create_monitor (impl, request->id, session->id, xdp_app_info_get_id (request->app_info), arg_window, NULL, create_monitor_done, g_object_ref (request)); xdp_dbus_inhibit_complete_create_monitor (object, invocation, request->id); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean handle_query_end_response (XdpDbusInhibit *object, GDBusMethodInvocation *invocation, const char *session_id) { g_autoptr(Session) session = lookup_session (session_id); 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; } xdp_dbus_impl_inhibit_call_query_end_response (impl, session->id, NULL, NULL, NULL); xdp_dbus_inhibit_complete_query_end_response (object, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static void inhibit_iface_init (XdpDbusInhibitIface *iface) { iface->handle_inhibit = handle_inhibit; iface->handle_create_monitor = handle_create_monitor; iface->handle_query_end_response = handle_query_end_response; } static void inhibit_init (Inhibit *inhibit) { xdp_dbus_inhibit_set_version (XDP_DBUS_INHIBIT (inhibit), 3); } static void inhibit_class_init (InhibitClass *klass) { } static void state_changed_cb (XdpDbusImplInhibit *impl, const char *session_id, GVariant *state, gpointer data) { GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); g_autoptr(Session) session = lookup_session (session_id); InhibitSession *inhibit_session = (InhibitSession *)session; gboolean active = FALSE; guint32 session_state = 0; g_variant_lookup (state, "screensaver-active", "b", &active); g_variant_lookup (state, "session-state", "u", &session_state); g_debug ("Received state-changed %s: screensaver-active: %d, session-state: %u", session_id, active, session_state); if (inhibit_session && !inhibit_session->closed) g_dbus_connection_emit_signal (connection, session->sender, "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Inhibit", "StateChanged", g_variant_new ("(o@a{sv})", session_id, state), NULL); } GDBusInterfaceSkeleton * inhibit_create (GDBusConnection *connection, const char *dbus_name) { g_autoptr(GError) error = NULL; impl = xdp_dbus_impl_inhibit_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 inhibit proxy: %s", error->message); return NULL; } g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (impl), G_MAXINT); inhibit = g_object_new (inhibit_get_type (), NULL); g_signal_connect (impl, "state-changed", G_CALLBACK (state_changed_cb), inhibit); return G_DBUS_INTERFACE_SKELETON (inhibit); }