platform-nix-portal.c 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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. #define PORTAL_PATH "/org/freedesktop/portal/desktop"
  23. #define INHIBIT_PORTAL_IFACE "org.freedesktop.portal.Inhibit"
  24. struct portal_inhibit_info {
  25. GDBusConnection *c;
  26. GCancellable *cancellable;
  27. unsigned int signal_id;
  28. char *sender_name;
  29. char *request_path;
  30. bool active;
  31. };
  32. static void new_request(struct portal_inhibit_info *info, char **out_token, char **out_path)
  33. {
  34. struct dstr token;
  35. struct dstr path;
  36. uint32_t id;
  37. id = rand();
  38. dstr_init(&token);
  39. dstr_printf(&token, "obs_inhibit_portal%u", id);
  40. *out_token = token.array;
  41. dstr_init(&path);
  42. dstr_printf(&path, "/org/freedesktop/portal/desktop/request/%s/%s", info->sender_name, token.array);
  43. *out_path = path.array;
  44. }
  45. static inline void unsubscribe_from_request(struct portal_inhibit_info *info)
  46. {
  47. if (info->signal_id > 0) {
  48. g_dbus_connection_signal_unsubscribe(info->c, info->signal_id);
  49. info->signal_id = 0;
  50. }
  51. }
  52. static inline void remove_inhibit_data(struct portal_inhibit_info *info)
  53. {
  54. g_clear_pointer(&info->request_path, bfree);
  55. info->active = false;
  56. }
  57. static void response_received(GDBusConnection *bus, const char *sender_name, const char *object_path,
  58. const char *interface_name, const char *signal_name, GVariant *parameters, gpointer data)
  59. {
  60. UNUSED_PARAMETER(bus);
  61. UNUSED_PARAMETER(sender_name);
  62. UNUSED_PARAMETER(object_path);
  63. UNUSED_PARAMETER(interface_name);
  64. UNUSED_PARAMETER(signal_name);
  65. struct portal_inhibit_info *info = data;
  66. g_autoptr(GVariant) ret = NULL;
  67. uint32_t response;
  68. g_variant_get(parameters, "(u@a{sv})", &response, &ret);
  69. if (response != 0) {
  70. if (response == 1)
  71. blog(LOG_WARNING, "Inhibit denied by user");
  72. remove_inhibit_data(info);
  73. }
  74. unsubscribe_from_request(info);
  75. }
  76. static void inhibited_cb(GObject *source_object, GAsyncResult *result, gpointer user_data)
  77. {
  78. UNUSED_PARAMETER(source_object);
  79. struct portal_inhibit_info *info = user_data;
  80. g_autoptr(GVariant) reply = NULL;
  81. g_autoptr(GError) error = NULL;
  82. reply = g_dbus_connection_call_finish(info->c, result, &error);
  83. if (error != NULL) {
  84. if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
  85. blog(LOG_ERROR, "Failed to inhibit: %s", error->message);
  86. unsubscribe_from_request(info);
  87. remove_inhibit_data(info);
  88. }
  89. g_clear_object(&info->cancellable);
  90. }
  91. static void do_inhibit(struct portal_inhibit_info *info, const char *reason)
  92. {
  93. GVariantBuilder options;
  94. uint32_t flags = 0xC;
  95. char *token;
  96. info->active = true;
  97. new_request(info, &token, &info->request_path);
  98. info->signal_id = g_dbus_connection_signal_subscribe(info->c, PORTAL_NAME, "org.freedesktop.portal.Request",
  99. "Response", info->request_path, NULL,
  100. G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, response_received, info,
  101. NULL);
  102. g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
  103. g_variant_builder_add(&options, "{sv}", "handle_token", g_variant_new_string(token));
  104. g_variant_builder_add(&options, "{sv}", "reason", g_variant_new_string(reason));
  105. bfree(token);
  106. info->cancellable = g_cancellable_new();
  107. g_dbus_connection_call(info->c, PORTAL_NAME, PORTAL_PATH, INHIBIT_PORTAL_IFACE, "Inhibit",
  108. g_variant_new("(sua{sv})", "", flags, &options), NULL, G_DBUS_CALL_FLAGS_NONE, -1,
  109. info->cancellable, inhibited_cb, info);
  110. }
  111. static void uninhibited_cb(GObject *source_object, GAsyncResult *result, gpointer user_data)
  112. {
  113. UNUSED_PARAMETER(source_object);
  114. struct portal_inhibit_info *info = user_data;
  115. g_autoptr(GVariant) reply = NULL;
  116. g_autoptr(GError) error = NULL;
  117. reply = g_dbus_connection_call_finish(info->c, result, &error);
  118. if (error)
  119. blog(LOG_WARNING, "Error uninhibiting: %s", error->message);
  120. }
  121. static void do_uninhibit(struct portal_inhibit_info *info)
  122. {
  123. if (info->cancellable) {
  124. /* If uninhibit is called before the inhibit call is finished,
  125. * cancel it instead.
  126. */
  127. g_cancellable_cancel(info->cancellable);
  128. g_clear_object(&info->cancellable);
  129. } else {
  130. g_dbus_connection_call(info->c, PORTAL_NAME, info->request_path, "org.freedesktop.portal.Request",
  131. "Close", g_variant_new("()"), G_VARIANT_TYPE_UNIT, G_DBUS_CALL_FLAGS_NONE, -1,
  132. NULL, uninhibited_cb, info);
  133. }
  134. remove_inhibit_data(info);
  135. }
  136. void portal_inhibit_info_destroy(struct portal_inhibit_info *info)
  137. {
  138. if (info) {
  139. g_cancellable_cancel(info->cancellable);
  140. unsubscribe_from_request(info);
  141. remove_inhibit_data(info);
  142. g_clear_pointer(&info->sender_name, bfree);
  143. g_clear_object(&info->cancellable);
  144. g_clear_object(&info->c);
  145. bfree(info);
  146. }
  147. }
  148. struct portal_inhibit_info *portal_inhibit_info_create(void)
  149. {
  150. struct portal_inhibit_info *info = bzalloc(sizeof(*info));
  151. g_autoptr(GVariant) reply = NULL;
  152. g_autoptr(GError) error = NULL;
  153. char *aux;
  154. info->c = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
  155. if (!info->c) {
  156. blog(LOG_ERROR, "Could not create dbus connection: %s", error->message);
  157. bfree(info);
  158. return NULL;
  159. }
  160. info->sender_name = bstrdup(g_dbus_connection_get_unique_name(info->c) + 1);
  161. while ((aux = strstr(info->sender_name, ".")) != NULL)
  162. *aux = '_';
  163. reply = g_dbus_connection_call_sync(info->c, PORTAL_NAME, PORTAL_PATH, "org.freedesktop.DBus.Properties", "Get",
  164. g_variant_new("(ss)", INHIBIT_PORTAL_IFACE, "version"),
  165. G_VARIANT_TYPE("(v)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);
  166. if (reply != NULL) {
  167. blog(LOG_DEBUG, "Found portal inhibitor");
  168. return info;
  169. }
  170. portal_inhibit_info_destroy(info);
  171. return NULL;
  172. }
  173. void portal_inhibit(struct portal_inhibit_info *info, const char *reason, bool active)
  174. {
  175. if (active == info->active)
  176. return;
  177. if (active)
  178. do_inhibit(info, reason);
  179. else
  180. do_uninhibit(info);
  181. }