platform-nix-portal.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. * Copyright (c) 2023 Lain Bailey <[email protected]>
  3. * 2021 Georges Basile Stavracas Neto <[email protected]>
  4. *
  5. * Permission to use, copy, modify, and distribute this software for any
  6. * purpose with or without fee is hereby granted, provided that the above
  7. * copyright notice and this permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  12. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  15. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. */
  17. #include <assert.h>
  18. #include <gio/gio.h>
  19. #include "bmem.h"
  20. #include "dstr.h"
  21. #define PORTAL_NAME "org.freedesktop.portal.Desktop"
  22. struct portal_inhibit_info {
  23. GDBusConnection *c;
  24. GCancellable *cancellable;
  25. unsigned int signal_id;
  26. char *sender_name;
  27. char *request_path;
  28. bool active;
  29. };
  30. static void new_request(struct portal_inhibit_info *info, char **out_token, char **out_path)
  31. {
  32. struct dstr token;
  33. struct dstr path;
  34. uint32_t id;
  35. id = rand();
  36. dstr_init(&token);
  37. dstr_printf(&token, "obs_inhibit_portal%u", id);
  38. *out_token = token.array;
  39. dstr_init(&path);
  40. dstr_printf(&path, "/org/freedesktop/portal/desktop/request/%s/%s", info->sender_name, token.array);
  41. *out_path = path.array;
  42. }
  43. static inline void unsubscribe_from_request(struct portal_inhibit_info *info)
  44. {
  45. if (info->signal_id > 0) {
  46. g_dbus_connection_signal_unsubscribe(info->c, info->signal_id);
  47. info->signal_id = 0;
  48. }
  49. }
  50. static inline void remove_inhibit_data(struct portal_inhibit_info *info)
  51. {
  52. g_clear_pointer(&info->request_path, bfree);
  53. info->active = false;
  54. }
  55. static void response_received(GDBusConnection *bus, const char *sender_name, const char *object_path,
  56. const char *interface_name, const char *signal_name, GVariant *parameters, gpointer data)
  57. {
  58. UNUSED_PARAMETER(bus);
  59. UNUSED_PARAMETER(sender_name);
  60. UNUSED_PARAMETER(object_path);
  61. UNUSED_PARAMETER(interface_name);
  62. UNUSED_PARAMETER(signal_name);
  63. struct portal_inhibit_info *info = data;
  64. g_autoptr(GVariant) ret = NULL;
  65. uint32_t response;
  66. g_variant_get(parameters, "(u@a{sv})", &response, &ret);
  67. if (response != 0) {
  68. if (response == 1)
  69. blog(LOG_WARNING, "Inhibit denied by user");
  70. remove_inhibit_data(info);
  71. }
  72. unsubscribe_from_request(info);
  73. }
  74. static void inhibited_cb(GObject *source_object, GAsyncResult *result, gpointer user_data)
  75. {
  76. UNUSED_PARAMETER(source_object);
  77. struct portal_inhibit_info *info = user_data;
  78. g_autoptr(GVariant) reply = NULL;
  79. g_autoptr(GError) error = NULL;
  80. reply = g_dbus_connection_call_finish(info->c, result, &error);
  81. if (error != NULL) {
  82. if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
  83. blog(LOG_ERROR, "Failed to inhibit: %s", error->message);
  84. unsubscribe_from_request(info);
  85. remove_inhibit_data(info);
  86. }
  87. g_clear_object(&info->cancellable);
  88. }
  89. static void do_inhibit(struct portal_inhibit_info *info, const char *reason)
  90. {
  91. GVariantBuilder options;
  92. uint32_t flags = 0xC;
  93. char *token;
  94. info->active = true;
  95. new_request(info, &token, &info->request_path);
  96. info->signal_id = g_dbus_connection_signal_subscribe(info->c, PORTAL_NAME, "org.freedesktop.portal.Request",
  97. "Response", info->request_path, NULL,
  98. G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, response_received, info,
  99. NULL);
  100. g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
  101. g_variant_builder_add(&options, "{sv}", "handle_token", g_variant_new_string(token));
  102. g_variant_builder_add(&options, "{sv}", "reason", g_variant_new_string(reason));
  103. bfree(token);
  104. info->cancellable = g_cancellable_new();
  105. g_dbus_connection_call(info->c, PORTAL_NAME, "/org/freedesktop/portal/desktop",
  106. "org.freedesktop.portal.Inhibit", "Inhibit",
  107. g_variant_new("(sua{sv})", "", flags, &options), NULL, G_DBUS_CALL_FLAGS_NONE, -1,
  108. info->cancellable, inhibited_cb, info);
  109. }
  110. static void uninhibited_cb(GObject *source_object, GAsyncResult *result, gpointer user_data)
  111. {
  112. UNUSED_PARAMETER(source_object);
  113. struct portal_inhibit_info *info = user_data;
  114. g_autoptr(GVariant) reply = NULL;
  115. g_autoptr(GError) error = NULL;
  116. reply = g_dbus_connection_call_finish(info->c, result, &error);
  117. if (error)
  118. blog(LOG_WARNING, "Error uninhibiting: %s", error->message);
  119. }
  120. static void do_uninhibit(struct portal_inhibit_info *info)
  121. {
  122. if (info->cancellable) {
  123. /* If uninhibit is called before the inhibit call is finished,
  124. * cancel it instead.
  125. */
  126. g_cancellable_cancel(info->cancellable);
  127. g_clear_object(&info->cancellable);
  128. } else {
  129. g_dbus_connection_call(info->c, PORTAL_NAME, info->request_path, "org.freedesktop.portal.Request",
  130. "Close", g_variant_new("()"), G_VARIANT_TYPE_UNIT, G_DBUS_CALL_FLAGS_NONE, -1,
  131. NULL, uninhibited_cb, info);
  132. }
  133. remove_inhibit_data(info);
  134. }
  135. void portal_inhibit_info_destroy(struct portal_inhibit_info *info)
  136. {
  137. if (info) {
  138. g_cancellable_cancel(info->cancellable);
  139. unsubscribe_from_request(info);
  140. remove_inhibit_data(info);
  141. g_clear_pointer(&info->sender_name, bfree);
  142. g_clear_object(&info->cancellable);
  143. g_clear_object(&info->c);
  144. bfree(info);
  145. }
  146. }
  147. struct portal_inhibit_info *portal_inhibit_info_create(void)
  148. {
  149. struct portal_inhibit_info *info = bzalloc(sizeof(*info));
  150. g_autoptr(GVariant) reply = NULL;
  151. g_autoptr(GError) error = NULL;
  152. char *aux;
  153. info->c = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
  154. if (!info->c) {
  155. blog(LOG_ERROR, "Could not create dbus connection: %s", error->message);
  156. bfree(info);
  157. return NULL;
  158. }
  159. info->sender_name = bstrdup(g_dbus_connection_get_unique_name(info->c) + 1);
  160. while ((aux = strstr(info->sender_name, ".")) != NULL)
  161. *aux = '_';
  162. reply = g_dbus_connection_call_sync(info->c, "org.freedesktop.DBus", "/org/freedesktop/DBus",
  163. "org.freedesktop.DBus", "GetNameOwner", g_variant_new("(s)", PORTAL_NAME),
  164. NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, NULL);
  165. if (reply != NULL) {
  166. blog(LOG_DEBUG, "Found portal inhibitor");
  167. return info;
  168. }
  169. portal_inhibit_info_destroy(info);
  170. return NULL;
  171. }
  172. void portal_inhibit(struct portal_inhibit_info *info, const char *reason, bool active)
  173. {
  174. if (active == info->active)
  175. return;
  176. if (active)
  177. do_inhibit(info, reason);
  178. else
  179. do_uninhibit(info);
  180. }