1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22#include "SDL_dbus.h"
23#include "../../stdlib/SDL_vacopy.h"
24
25#ifdef SDL_USE_LIBDBUS
26// we never link directly to libdbus.
27static const char *dbus_library = "libdbus-1.so.3";
28static SDL_SharedObject *dbus_handle = NULL;
29static char *inhibit_handle = NULL;
30static unsigned int screensaver_cookie = 0;
31static SDL_DBusContext dbus;
32
33static bool LoadDBUSSyms(void)
34{
35#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \
36 dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y)
37
38#define SDL_DBUS_SYM2(TYPE, x, y) \
39 if (!(dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y))) \
40 return false
41
42#define SDL_DBUS_SYM_OPTIONAL(TYPE, x) \
43 SDL_DBUS_SYM2_OPTIONAL(TYPE, x, dbus_##x)
44
45#define SDL_DBUS_SYM(TYPE, x) \
46 SDL_DBUS_SYM2(TYPE, x, dbus_##x)
47
48 SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private);
49 SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register);
50 SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match);
51 SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_remove_match);
52 SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private);
53 SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect);
54 SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected);
55 SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction), connection_add_filter);
56 SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *), connection_remove_filter);
57 SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *, const DBusObjectPathVTable *, void *, DBusError *), connection_try_register_object_path);
58 SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusMessage *, dbus_uint32_t *), connection_send);
59 SDL_DBUS_SYM(DBusMessage *(*)(DBusConnection *, DBusMessage *, int, DBusError *), connection_send_with_reply_and_block);
60 SDL_DBUS_SYM(void (*)(DBusConnection *), connection_close);
61 SDL_DBUS_SYM(void (*)(DBusConnection *), connection_ref);
62 SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref);
63 SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush);
64 SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write);
65 SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write_dispatch);
66 SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch);
67 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal);
68 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path);
69 SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call);
70 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args);
71 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist);
72 SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append);
73 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const char *, DBusMessageIter *), message_iter_open_container);
74 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const void *), message_iter_append_basic);
75 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, DBusMessageIter *), message_iter_close_container);
76 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, ...), message_get_args);
77 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, va_list), message_get_args_valist);
78 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusMessageIter *), message_iter_init);
79 SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *), message_iter_next);
80 SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *), message_iter_get_basic);
81 SDL_DBUS_SYM(int (*)(DBusMessageIter *), message_iter_get_arg_type);
82 SDL_DBUS_SYM(void (*)(DBusMessageIter *, DBusMessageIter *), message_iter_recurse);
83 SDL_DBUS_SYM(void (*)(DBusMessage *), message_unref);
84 SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default);
85 SDL_DBUS_SYM(void (*)(DBusError *), error_init);
86 SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set);
87 SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *, const char *), error_has_name);
88 SDL_DBUS_SYM(void (*)(DBusError *), error_free);
89 SDL_DBUS_SYM(char *(*)(void), get_local_machine_id);
90 SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id);
91 SDL_DBUS_SYM(void (*)(void *), free);
92 SDL_DBUS_SYM(void (*)(char **), free_string_array);
93 SDL_DBUS_SYM(void (*)(void), shutdown);
94
95#undef SDL_DBUS_SYM
96#undef SDL_DBUS_SYM2
97
98 return true;
99}
100
101static void UnloadDBUSLibrary(void)
102{
103 if (dbus_handle) {
104 SDL_UnloadObject(dbus_handle);
105 dbus_handle = NULL;
106 }
107}
108
109static bool LoadDBUSLibrary(void)
110{
111 bool result = true;
112 if (!dbus_handle) {
113 dbus_handle = SDL_LoadObject(dbus_library);
114 if (!dbus_handle) {
115 result = false;
116 // Don't call SDL_SetError(): SDL_LoadObject already did.
117 } else {
118 result = LoadDBUSSyms();
119 if (!result) {
120 UnloadDBUSLibrary();
121 }
122 }
123 }
124 return result;
125}
126
127static SDL_InitState dbus_init;
128
129void SDL_DBus_Init(void)
130{
131 static bool is_dbus_available = true;
132
133 if (!is_dbus_available) {
134 return; // don't keep trying if this fails.
135 }
136
137 if (!SDL_ShouldInit(&dbus_init)) {
138 return;
139 }
140
141 if (!LoadDBUSLibrary()) {
142 goto error;
143 }
144
145 if (!dbus.threads_init_default()) {
146 goto error;
147 }
148
149 DBusError err;
150 dbus.error_init(&err);
151 // session bus is required
152
153 dbus.session_conn = dbus.bus_get_private(DBUS_BUS_SESSION, &err);
154 if (dbus.error_is_set(&err)) {
155 dbus.error_free(&err);
156 goto error;
157 }
158 dbus.connection_set_exit_on_disconnect(dbus.session_conn, 0);
159
160 // system bus is optional
161 dbus.system_conn = dbus.bus_get_private(DBUS_BUS_SYSTEM, &err);
162 if (!dbus.error_is_set(&err)) {
163 dbus.connection_set_exit_on_disconnect(dbus.system_conn, 0);
164 }
165
166 dbus.error_free(&err);
167 SDL_SetInitialized(&dbus_init, true);
168 return;
169
170error:
171 is_dbus_available = false;
172 SDL_SetInitialized(&dbus_init, true);
173 SDL_DBus_Quit();
174}
175
176void SDL_DBus_Quit(void)
177{
178 if (!SDL_ShouldQuit(&dbus_init)) {
179 return;
180 }
181
182 if (dbus.system_conn) {
183 dbus.connection_close(dbus.system_conn);
184 dbus.connection_unref(dbus.system_conn);
185 }
186 if (dbus.session_conn) {
187 dbus.connection_close(dbus.session_conn);
188 dbus.connection_unref(dbus.session_conn);
189 }
190
191 if (SDL_GetHintBoolean(SDL_HINT_SHUTDOWN_DBUS_ON_QUIT, false)) {
192 if (dbus.shutdown) {
193 dbus.shutdown();
194 }
195
196 UnloadDBUSLibrary();
197 } else {
198 /* Leaving libdbus loaded when skipping dbus_shutdown() avoids
199 * spurious leak warnings from LeakSanitizer on internal D-Bus
200 * allocations that would be freed by dbus_shutdown(). */
201 dbus_handle = NULL;
202 }
203
204 SDL_zero(dbus);
205 if (inhibit_handle) {
206 SDL_free(inhibit_handle);
207 inhibit_handle = NULL;
208 }
209
210 SDL_SetInitialized(&dbus_init, false);
211}
212
213SDL_DBusContext *SDL_DBus_GetContext(void)
214{
215 if (!dbus_handle || !dbus.session_conn) {
216 SDL_DBus_Init();
217 }
218
219 return (dbus_handle && dbus.session_conn) ? &dbus : NULL;
220}
221
222static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
223{
224 bool result = false;
225
226 if (conn) {
227 DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
228 if (msg) {
229 int firstarg;
230 va_list ap_reply;
231 va_copy(ap_reply, ap); // copy the arg list so we don't compete with D-Bus for it
232 firstarg = va_arg(ap, int);
233 if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
234 DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
235 if (reply) {
236 // skip any input args, get to output args.
237 while ((firstarg = va_arg(ap_reply, int)) != DBUS_TYPE_INVALID) {
238 // we assume D-Bus already validated all this.
239 {
240 void *dumpptr = va_arg(ap_reply, void *);
241 (void)dumpptr;
242 }
243 if (firstarg == DBUS_TYPE_ARRAY) {
244 {
245 const int dumpint = va_arg(ap_reply, int);
246 (void)dumpint;
247 }
248 }
249 }
250 firstarg = va_arg(ap_reply, int);
251 if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_get_args_valist(reply, NULL, firstarg, ap_reply)) {
252 result = true;
253 }
254 dbus.message_unref(reply);
255 }
256 }
257 va_end(ap_reply);
258 dbus.message_unref(msg);
259 }
260 }
261
262 return result;
263}
264
265bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
266{
267 bool result;
268 va_list ap;
269 va_start(ap, method);
270 result = SDL_DBus_CallMethodInternal(conn, node, path, interface, method, ap);
271 va_end(ap);
272 return result;
273}
274
275bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...)
276{
277 bool result;
278 va_list ap;
279 va_start(ap, method);
280 result = SDL_DBus_CallMethodInternal(dbus.session_conn, node, path, interface, method, ap);
281 va_end(ap);
282 return result;
283}
284
285static bool SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
286{
287 bool result = false;
288
289 if (conn) {
290 DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
291 if (msg) {
292 int firstarg = va_arg(ap, int);
293 if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
294 if (dbus.connection_send(conn, msg, NULL)) {
295 dbus.connection_flush(conn);
296 result = true;
297 }
298 }
299
300 dbus.message_unref(msg);
301 }
302 }
303
304 return result;
305}
306
307static bool SDL_DBus_CallWithBasicReply(DBusConnection *conn, DBusMessage *msg, const int expectedtype, void *result)
308{
309 bool retval = false;
310
311 DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
312 if (reply) {
313 DBusMessageIter iter, actual_iter;
314 dbus.message_iter_init(reply, &iter);
315 if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) {
316 dbus.message_iter_recurse(&iter, &actual_iter);
317 } else {
318 actual_iter = iter;
319 }
320
321 if (dbus.message_iter_get_arg_type(&actual_iter) == expectedtype) {
322 dbus.message_iter_get_basic(&actual_iter, result);
323 retval = true;
324 }
325
326 dbus.message_unref(reply);
327 }
328
329 return retval;
330}
331
332bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
333{
334 bool result;
335 va_list ap;
336 va_start(ap, method);
337 result = SDL_DBus_CallVoidMethodInternal(conn, node, path, interface, method, ap);
338 va_end(ap);
339 return result;
340}
341
342bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...)
343{
344 bool result;
345 va_list ap;
346 va_start(ap, method);
347 result = SDL_DBus_CallVoidMethodInternal(dbus.session_conn, node, path, interface, method, ap);
348 va_end(ap);
349 return result;
350}
351
352bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)
353{
354 bool retval = false;
355
356 if (conn) {
357 DBusMessage *msg = dbus.message_new_method_call(node, path, "org.freedesktop.DBus.Properties", "Get");
358 if (msg) {
359 if (dbus.message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {
360 retval = SDL_DBus_CallWithBasicReply(conn, msg, expectedtype, result);
361 }
362 dbus.message_unref(msg);
363 }
364 }
365
366 return retval;
367}
368
369bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)
370{
371 return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, node, path, interface, property, expectedtype, result);
372}
373
374void SDL_DBus_ScreensaverTickle(void)
375{
376 if (screensaver_cookie == 0 && !inhibit_handle) { // no need to tickle if we're inhibiting.
377 // org.gnome.ScreenSaver is the legacy interface, but it'll either do nothing or just be a second harmless tickle on newer systems, so we leave it for now.
378 SDL_DBus_CallVoidMethod("org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", "org.gnome.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
379 SDL_DBus_CallVoidMethod("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
380 }
381}
382
383static bool SDL_DBus_AppendDictWithKeysAndValues(DBusMessageIter *iterInit, const char **keys, const char **values, int count)
384{
385 DBusMessageIter iterDict;
386
387 if (!dbus.message_iter_open_container(iterInit, DBUS_TYPE_ARRAY, "{sv}", &iterDict)) {
388 goto failed;
389 }
390
391 for (int i = 0; i < count; i++) {
392 DBusMessageIter iterEntry, iterValue;
393 const char *key = keys[i];
394 const char *value = values[i];
395
396 if (!dbus.message_iter_open_container(&iterDict, DBUS_TYPE_DICT_ENTRY, NULL, &iterEntry)) {
397 goto failed;
398 }
399
400 if (!dbus.message_iter_append_basic(&iterEntry, DBUS_TYPE_STRING, &key)) {
401 goto failed;
402 }
403
404 if (!dbus.message_iter_open_container(&iterEntry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &iterValue)) {
405 goto failed;
406 }
407
408 if (!dbus.message_iter_append_basic(&iterValue, DBUS_TYPE_STRING, &value)) {
409 goto failed;
410 }
411
412 if (!dbus.message_iter_close_container(&iterEntry, &iterValue) || !dbus.message_iter_close_container(&iterDict, &iterEntry)) {
413 goto failed;
414 }
415 }
416
417 if (!dbus.message_iter_close_container(iterInit, &iterDict)) {
418 goto failed;
419 }
420
421 return true;
422
423failed:
424 /* message_iter_abandon_container_if_open() and message_iter_abandon_container() might be
425 * missing if libdbus is too old. Instead, we just return without cleaning up any eventual
426 * open container */
427 return false;
428}
429
430static bool SDL_DBus_AppendDictWithKeyValue(DBusMessageIter *iterInit, const char *key, const char *value)
431{
432 const char *keys[1];
433 const char *values[1];
434
435 keys[0] = key;
436 values[0] = value;
437 return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1);
438}
439
440bool SDL_DBus_ScreensaverInhibit(bool inhibit)
441{
442 const char *default_inhibit_reason = "Playing a game";
443
444 if ((inhibit && (screensaver_cookie != 0 || inhibit_handle)) || (!inhibit && (screensaver_cookie == 0 && !inhibit_handle))) {
445 return true;
446 }
447
448 if (!dbus.session_conn) {
449 /* We either lost connection to the session bus or were not able to
450 * load the D-Bus library at all. */
451 return false;
452 }
453
454 if (SDL_GetSandbox() != SDL_SANDBOX_NONE) {
455 const char *bus_name = "org.freedesktop.portal.Desktop";
456 const char *path = "/org/freedesktop/portal/desktop";
457 const char *interface = "org.freedesktop.portal.Inhibit";
458 const char *window = ""; // As a future improvement we could gather the X11 XID or Wayland surface identifier
459 static const unsigned int INHIBIT_IDLE = 8; // Taken from the portal API reference
460 DBusMessageIter iterInit;
461
462 if (inhibit) {
463 DBusMessage *msg;
464 bool result = false;
465 const char *key = "reason";
466 const char *reply = NULL;
467 const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
468 if (!reason || !reason[0]) {
469 reason = default_inhibit_reason;
470 }
471
472 msg = dbus.message_new_method_call(bus_name, path, interface, "Inhibit");
473 if (!msg) {
474 return false;
475 }
476
477 if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &window, DBUS_TYPE_UINT32, &INHIBIT_IDLE, DBUS_TYPE_INVALID)) {
478 dbus.message_unref(msg);
479 return false;
480 }
481
482 dbus.message_iter_init_append(msg, &iterInit);
483
484 // a{sv}
485 if (!SDL_DBus_AppendDictWithKeyValue(&iterInit, key, reason)) {
486 dbus.message_unref(msg);
487 return false;
488 }
489
490 if (SDL_DBus_CallWithBasicReply(dbus.session_conn, msg, DBUS_TYPE_OBJECT_PATH, &reply)) {
491 inhibit_handle = SDL_strdup(reply);
492 result = true;
493 }
494
495 dbus.message_unref(msg);
496 return result;
497 } else {
498 if (!SDL_DBus_CallVoidMethod(bus_name, inhibit_handle, "org.freedesktop.portal.Request", "Close", DBUS_TYPE_INVALID)) {
499 return false;
500 }
501 SDL_free(inhibit_handle);
502 inhibit_handle = NULL;
503 }
504 } else {
505 const char *bus_name = "org.freedesktop.ScreenSaver";
506 const char *path = "/org/freedesktop/ScreenSaver";
507 const char *interface = "org.freedesktop.ScreenSaver";
508
509 if (inhibit) {
510 const char *app = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
511 const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
512 if (!reason || !reason[0]) {
513 reason = default_inhibit_reason;
514 }
515
516 if (!SDL_DBus_CallMethod(bus_name, path, interface, "Inhibit",
517 DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID,
518 DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
519 return false;
520 }
521 return (screensaver_cookie != 0);
522 } else {
523 if (!SDL_DBus_CallVoidMethod(bus_name, path, interface, "UnInhibit", DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
524 return false;
525 }
526 screensaver_cookie = 0;
527 }
528 }
529
530 return true;
531}
532
533void SDL_DBus_PumpEvents(void)
534{
535 if (dbus.session_conn) {
536 dbus.connection_read_write(dbus.session_conn, 0);
537
538 while (dbus.connection_dispatch(dbus.session_conn) == DBUS_DISPATCH_DATA_REMAINS) {
539 // Do nothing, actual work happens in DBus_MessageFilter
540 SDL_DelayNS(SDL_US_TO_NS(10));
541 }
542 }
543}
544
545/*
546 * Get the machine ID if possible. Result must be freed with dbus->free().
547 */
548char *SDL_DBus_GetLocalMachineId(void)
549{
550 DBusError err;
551 char *result;
552
553 dbus.error_init(&err);
554
555 if (dbus.try_get_local_machine_id) {
556 // Available since dbus 1.12.0, has proper error-handling
557 result = dbus.try_get_local_machine_id(&err);
558 } else {
559 /* Available since time immemorial, but has no error-handling:
560 * if the machine ID can't be read, many versions of libdbus will
561 * treat that as a fatal mis-installation and abort() */
562 result = dbus.get_local_machine_id();
563 }
564
565 if (result) {
566 return result;
567 }
568
569 if (dbus.error_is_set(&err)) {
570 SDL_SetError("%s: %s", err.name, err.message);
571 dbus.error_free(&err);
572 } else {
573 SDL_SetError("Error getting D-Bus machine ID");
574 }
575
576 return NULL;
577}
578
579/*
580 * Convert file drops with mime type "application/vnd.portal.filetransfer" to file paths
581 * Result must be freed with dbus->free_string_array().
582 * https://flatpak.github.io/xdg-desktop-portal/#gdbus-method-org-freedesktop-portal-FileTransfer.RetrieveFiles
583 */
584char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count)
585{
586 DBusError err;
587 DBusMessageIter iter, iterDict;
588 char **paths = NULL;
589 DBusMessage *reply = NULL;
590 DBusMessage *msg = dbus.message_new_method_call("org.freedesktop.portal.Documents", // Node
591 "/org/freedesktop/portal/documents", // Path
592 "org.freedesktop.portal.FileTransfer", // Interface
593 "RetrieveFiles"); // Method
594
595 // Make sure we have a connection to the dbus session bus
596 if (!SDL_DBus_GetContext() || !dbus.session_conn) {
597 /* We either cannot connect to the session bus or were unable to
598 * load the D-Bus library at all. */
599 return NULL;
600 }
601
602 dbus.error_init(&err);
603
604 // First argument is a "application/vnd.portal.filetransfer" key from a DnD or clipboard event
605 if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
606 SDL_OutOfMemory();
607 dbus.message_unref(msg);
608 goto failed;
609 }
610
611 /* Second argument is a variant dictionary for options.
612 * The spec doesn't define any entries yet so it's empty. */
613 dbus.message_iter_init_append(msg, &iter);
614 if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
615 !dbus.message_iter_close_container(&iter, &iterDict)) {
616 SDL_OutOfMemory();
617 dbus.message_unref(msg);
618 goto failed;
619 }
620
621 reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
622 dbus.message_unref(msg);
623
624 if (reply) {
625 dbus.message_get_args(reply, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &paths, path_count, DBUS_TYPE_INVALID);
626 dbus.message_unref(reply);
627 }
628
629 if (paths) {
630 return paths;
631 }
632
633failed:
634 if (dbus.error_is_set(&err)) {
635 SDL_SetError("%s: %s", err.name, err.message);
636 dbus.error_free(&err);
637 } else {
638 SDL_SetError("Error retrieving paths for documents portal \"%s\"", key);
639 }
640
641 return NULL;
642}
643
644typedef struct SDL_DBus_CameraPortalMessageHandlerData
645{
646 uint32_t response;
647 char *path;
648 DBusError *err;
649 bool done;
650} SDL_DBus_CameraPortalMessageHandlerData;
651
652static DBusHandlerResult SDL_DBus_CameraPortalMessageHandler(DBusConnection *conn, DBusMessage *msg, void *v)
653{
654 SDL_DBus_CameraPortalMessageHandlerData *data = v;
655 const char *name, *old, *new;
656
657 if (dbus.message_is_signal(msg, "org.freedesktop.DBus", "NameOwnerChanged")) {
658 if (!dbus.message_get_args(msg, data->err,
659 DBUS_TYPE_STRING, &name,
660 DBUS_TYPE_STRING, &old,
661 DBUS_TYPE_STRING, &new,
662 DBUS_TYPE_INVALID)) {
663 data->done = true;
664 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
665 }
666 if (SDL_strcmp(name, "org.freedesktop.portal.Desktop") != 0 ||
667 SDL_strcmp(new, "") != 0) {
668 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
669 }
670 data->done = true;
671 data->response = -1;
672 return DBUS_HANDLER_RESULT_HANDLED;
673 }
674 if (!dbus.message_has_path(msg, data->path) || !dbus.message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
675 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
676 }
677 dbus.message_get_args(msg, data->err, DBUS_TYPE_UINT32, &data->response, DBUS_TYPE_INVALID);
678 data->done = true;
679 return DBUS_HANDLER_RESULT_HANDLED;
680}
681
682#define SIGNAL_NAMEOWNERCHANGED "type='signal',\
683 sender='org.freedesktop.DBus',\
684 interface='org.freedesktop.DBus',\
685 member='NameOwnerChanged',\
686 arg0='org.freedesktop.portal.Desktop',\
687 arg2=''"
688
689/*
690 * Requests access for the camera. Returns -1 on error, -2 on denied access or
691 * missing portal, otherwise returns a file descriptor to be used by the Pipewire driver.
692 * https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html
693 */
694int SDL_DBus_CameraPortalRequestAccess(void)
695{
696 SDL_DBus_CameraPortalMessageHandlerData data;
697 DBusError err;
698 DBusMessageIter iter, iterDict;
699 DBusMessage *reply, *msg;
700 int fd;
701
702 if (SDL_GetSandbox() == SDL_SANDBOX_NONE) {
703 return -2;
704 }
705
706 if (!SDL_DBus_GetContext()) {
707 return -2;
708 }
709
710 dbus.error_init(&err);
711
712 msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop",
713 "/org/freedesktop/portal/desktop",
714 "org.freedesktop.portal.Camera",
715 "AccessCamera");
716
717 dbus.message_iter_init_append(msg, &iter);
718 if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
719 !dbus.message_iter_close_container(&iter, &iterDict)) {
720 SDL_OutOfMemory();
721 dbus.message_unref(msg);
722 goto failed;
723 }
724
725 reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
726 dbus.message_unref(msg);
727
728 if (reply) {
729 dbus.message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &data.path, DBUS_TYPE_INVALID);
730 if (dbus.error_is_set(&err)) {
731 dbus.message_unref(reply);
732 goto failed;
733 }
734 if ((data.path = SDL_strdup(data.path)) == NULL) {
735 dbus.message_unref(reply);
736 SDL_OutOfMemory();
737 goto failed;
738 }
739 dbus.message_unref(reply);
740 } else {
741 if (dbus.error_has_name(&err, DBUS_ERROR_NAME_HAS_NO_OWNER)) {
742 return -2;
743 }
744 goto failed;
745 }
746
747 dbus.bus_add_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err);
748 if (dbus.error_is_set(&err)) {
749 SDL_free(data.path);
750 goto failed;
751 }
752 data.err = &err;
753 data.done = false;
754 if (!dbus.connection_add_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data, NULL)) {
755 SDL_free(data.path);
756 SDL_OutOfMemory();
757 goto failed;
758 }
759 while (!data.done && dbus.connection_read_write_dispatch(dbus.session_conn, -1)) {
760 ;
761 }
762
763 dbus.bus_remove_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err);
764 if (dbus.error_is_set(&err)) {
765 SDL_free(data.path);
766 goto failed;
767 }
768 dbus.connection_remove_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data);
769 SDL_free(data.path);
770 if (!data.done) {
771 goto failed;
772 }
773 if (dbus.error_is_set(&err)) { // from the message handler
774 goto failed;
775 }
776 if (data.response == 1 || data.response == 2) {
777 return -2;
778 } else if (data.response != 0) {
779 goto failed;
780 }
781
782 msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop",
783 "/org/freedesktop/portal/desktop",
784 "org.freedesktop.portal.Camera",
785 "OpenPipeWireRemote");
786
787 dbus.message_iter_init_append(msg, &iter);
788 if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
789 !dbus.message_iter_close_container(&iter, &iterDict)) {
790 SDL_OutOfMemory();
791 dbus.message_unref(msg);
792 goto failed;
793 }
794
795 reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
796 dbus.message_unref(msg);
797
798 if (reply) {
799 dbus.message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID);
800 dbus.message_unref(reply);
801 if (dbus.error_is_set(&err)) {
802 goto failed;
803 }
804 } else {
805 goto failed;
806 }
807
808 return fd;
809
810failed:
811 if (dbus.error_is_set(&err)) {
812 if (dbus.error_has_name(&err, DBUS_ERROR_NO_MEMORY)) {
813 SDL_OutOfMemory();
814 }
815 SDL_SetError("%s: %s", err.name, err.message);
816 dbus.error_free(&err);
817 } else {
818 SDL_SetError("Error requesting access for the camera");
819 }
820
821 return -1;
822}
823
824#endif
825