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. |
27 | static const char *dbus_library = "libdbus-1.so.3" ; |
28 | static SDL_SharedObject *dbus_handle = NULL; |
29 | static char *inhibit_handle = NULL; |
30 | static unsigned int screensaver_cookie = 0; |
31 | static SDL_DBusContext dbus; |
32 | |
33 | static 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 | |
101 | static void UnloadDBUSLibrary(void) |
102 | { |
103 | if (dbus_handle) { |
104 | SDL_UnloadObject(dbus_handle); |
105 | dbus_handle = NULL; |
106 | } |
107 | } |
108 | |
109 | static 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 | |
127 | static SDL_InitState dbus_init; |
128 | |
129 | void 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 | |
170 | error: |
171 | is_dbus_available = false; |
172 | SDL_SetInitialized(&dbus_init, true); |
173 | SDL_DBus_Quit(); |
174 | } |
175 | |
176 | void 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 | |
213 | SDL_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 | |
222 | static 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 | |
265 | bool 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 | |
275 | bool 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 | |
285 | static 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 | |
307 | static 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 | |
332 | bool 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 | |
342 | bool 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 | |
352 | bool 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 | |
369 | bool 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 | |
374 | void 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 | |
383 | static 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 | |
423 | failed: |
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 | |
430 | static 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 | |
440 | bool 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 | |
533 | void 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 | */ |
548 | char *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 | */ |
584 | char **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 | |
633 | failed: |
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 | |
644 | typedef struct SDL_DBus_CameraPortalMessageHandlerData |
645 | { |
646 | uint32_t response; |
647 | char *path; |
648 | DBusError *err; |
649 | bool done; |
650 | } SDL_DBus_CameraPortalMessageHandlerData; |
651 | |
652 | static 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 | */ |
694 | int 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 | |
810 | failed: |
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 | |