1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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_hints.h"
23 #include "SDL_dbus.h"
24 #include "SDL_atomic.h"
25
26 #if SDL_USE_LIBDBUS
27 /* we never link directly to libdbus. */
28 #include "SDL_loadso.h"
29 static const char *dbus_library = "libdbus-1.so.3";
30 static void *dbus_handle = NULL;
31 static unsigned int screensaver_cookie = 0;
32 static SDL_DBusContext dbus;
33
34 static int
LoadDBUSSyms(void)35 LoadDBUSSyms(void)
36 {
37 #define SDL_DBUS_SYM2(x, y) \
38 if (!(dbus.x = SDL_LoadFunction(dbus_handle, #y))) return -1
39
40 #define SDL_DBUS_SYM(x) \
41 SDL_DBUS_SYM2(x, dbus_##x)
42
43 SDL_DBUS_SYM(bus_get_private);
44 SDL_DBUS_SYM(bus_register);
45 SDL_DBUS_SYM(bus_add_match);
46 SDL_DBUS_SYM(connection_open_private);
47 SDL_DBUS_SYM(connection_set_exit_on_disconnect);
48 SDL_DBUS_SYM(connection_get_is_connected);
49 SDL_DBUS_SYM(connection_add_filter);
50 SDL_DBUS_SYM(connection_try_register_object_path);
51 SDL_DBUS_SYM(connection_send);
52 SDL_DBUS_SYM(connection_send_with_reply_and_block);
53 SDL_DBUS_SYM(connection_close);
54 SDL_DBUS_SYM(connection_unref);
55 SDL_DBUS_SYM(connection_flush);
56 SDL_DBUS_SYM(connection_read_write);
57 SDL_DBUS_SYM(connection_dispatch);
58 SDL_DBUS_SYM(message_is_signal);
59 SDL_DBUS_SYM(message_new_method_call);
60 SDL_DBUS_SYM(message_append_args);
61 SDL_DBUS_SYM(message_append_args_valist);
62 SDL_DBUS_SYM(message_iter_init_append);
63 SDL_DBUS_SYM(message_iter_open_container);
64 SDL_DBUS_SYM(message_iter_append_basic);
65 SDL_DBUS_SYM(message_iter_close_container);
66 SDL_DBUS_SYM(message_get_args);
67 SDL_DBUS_SYM(message_get_args_valist);
68 SDL_DBUS_SYM(message_iter_init);
69 SDL_DBUS_SYM(message_iter_next);
70 SDL_DBUS_SYM(message_iter_get_basic);
71 SDL_DBUS_SYM(message_iter_get_arg_type);
72 SDL_DBUS_SYM(message_iter_recurse);
73 SDL_DBUS_SYM(message_unref);
74 SDL_DBUS_SYM(threads_init_default);
75 SDL_DBUS_SYM(error_init);
76 SDL_DBUS_SYM(error_is_set);
77 SDL_DBUS_SYM(error_free);
78 SDL_DBUS_SYM(get_local_machine_id);
79 SDL_DBUS_SYM(free);
80 SDL_DBUS_SYM(free_string_array);
81 SDL_DBUS_SYM(shutdown);
82
83 #undef SDL_DBUS_SYM
84 #undef SDL_DBUS_SYM2
85
86 return 0;
87 }
88
89 static void
UnloadDBUSLibrary(void)90 UnloadDBUSLibrary(void)
91 {
92 if (dbus_handle != NULL) {
93 SDL_UnloadObject(dbus_handle);
94 dbus_handle = NULL;
95 }
96 }
97
98 static int
LoadDBUSLibrary(void)99 LoadDBUSLibrary(void)
100 {
101 int retval = 0;
102 if (dbus_handle == NULL) {
103 dbus_handle = SDL_LoadObject(dbus_library);
104 if (dbus_handle == NULL) {
105 retval = -1;
106 /* Don't call SDL_SetError(): SDL_LoadObject already did. */
107 } else {
108 retval = LoadDBUSSyms();
109 if (retval < 0) {
110 UnloadDBUSLibrary();
111 }
112 }
113 }
114
115 return retval;
116 }
117
118
119 static SDL_SpinLock spinlock_dbus_init = 0;
120
121 /* you must hold spinlock_dbus_init before calling this! */
122 static void
SDL_DBus_Init_Spinlocked(void)123 SDL_DBus_Init_Spinlocked(void)
124 {
125 static SDL_bool is_dbus_available = SDL_TRUE;
126 if (!is_dbus_available) {
127 return; /* don't keep trying if this fails. */
128 }
129
130 if (!dbus.session_conn) {
131 DBusError err;
132
133 if (LoadDBUSLibrary() == -1) {
134 is_dbus_available = SDL_FALSE; /* can't load at all? Don't keep trying. */
135 return; /* oh well */
136 }
137
138 if (!dbus.threads_init_default()) {
139 is_dbus_available = SDL_FALSE;
140 return;
141 }
142
143 dbus.error_init(&err);
144 /* session bus is required */
145
146 dbus.session_conn = dbus.bus_get_private(DBUS_BUS_SESSION, &err);
147 if (dbus.error_is_set(&err)) {
148 dbus.error_free(&err);
149 SDL_DBus_Quit();
150 is_dbus_available = SDL_FALSE;
151 return; /* oh well */
152 }
153 dbus.connection_set_exit_on_disconnect(dbus.session_conn, 0);
154
155 /* system bus is optional */
156 dbus.system_conn = dbus.bus_get_private(DBUS_BUS_SYSTEM, &err);
157 if (!dbus.error_is_set(&err)) {
158 dbus.connection_set_exit_on_disconnect(dbus.system_conn, 0);
159 }
160
161 dbus.error_free(&err);
162 }
163 }
164
165 void
SDL_DBus_Init(void)166 SDL_DBus_Init(void)
167 {
168 SDL_AtomicLock(&spinlock_dbus_init); /* make sure two threads can't init at same time, since this can happen before SDL_Init. */
169 SDL_DBus_Init_Spinlocked();
170 SDL_AtomicUnlock(&spinlock_dbus_init);
171 }
172
173 void
SDL_DBus_Quit(void)174 SDL_DBus_Quit(void)
175 {
176 if (dbus.system_conn) {
177 dbus.connection_close(dbus.system_conn);
178 dbus.connection_unref(dbus.system_conn);
179 }
180 if (dbus.session_conn) {
181 dbus.connection_close(dbus.session_conn);
182 dbus.connection_unref(dbus.session_conn);
183 }
184 /* Don't do this - bug 3950
185 dbus_shutdown() is a debug feature which closes all global resources in the dbus library. Calling this should be done by the app, not a library, because if there are multiple users of dbus in the process then SDL could shut it down even though another part is using it.
186 */
187 #if 0
188 if (dbus.shutdown) {
189 dbus.shutdown();
190 }
191 #endif
192 SDL_zero(dbus);
193 UnloadDBUSLibrary();
194 }
195
196 SDL_DBusContext *
SDL_DBus_GetContext(void)197 SDL_DBus_GetContext(void)
198 {
199 if (!dbus_handle || !dbus.session_conn) {
200 SDL_DBus_Init();
201 }
202
203 return (dbus_handle && dbus.session_conn) ? &dbus : NULL;
204 }
205
206 static SDL_bool
SDL_DBus_CallMethodInternal(DBusConnection * conn,const char * node,const char * path,const char * interface,const char * method,va_list ap)207 SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
208 {
209 SDL_bool retval = SDL_FALSE;
210
211 if (conn) {
212 DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
213 if (msg) {
214 int firstarg;
215 va_list ap_reply;
216 va_copy(ap_reply, ap); /* copy the arg list so we don't compete with D-Bus for it */
217 firstarg = va_arg(ap, int);
218 if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
219 DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
220 if (reply) {
221 /* skip any input args, get to output args. */
222 while ((firstarg = va_arg(ap_reply, int)) != DBUS_TYPE_INVALID) {
223 /* we assume D-Bus already validated all this. */
224 { void *dumpptr = va_arg(ap_reply, void*); (void) dumpptr; }
225 if (firstarg == DBUS_TYPE_ARRAY) {
226 { const int dumpint = va_arg(ap_reply, int); (void) dumpint; }
227 }
228 }
229 firstarg = va_arg(ap_reply, int);
230 if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_get_args_valist(reply, NULL, firstarg, ap_reply)) {
231 retval = SDL_TRUE;
232 }
233 dbus.message_unref(reply);
234 }
235 }
236 va_end(ap_reply);
237 dbus.message_unref(msg);
238 }
239 }
240
241 return retval;
242 }
243
244 SDL_bool
SDL_DBus_CallMethodOnConnection(DBusConnection * conn,const char * node,const char * path,const char * interface,const char * method,...)245 SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
246 {
247 SDL_bool retval;
248 va_list ap;
249 va_start(ap, method);
250 retval = SDL_DBus_CallMethodInternal(conn, node, path, interface, method, ap);
251 va_end(ap);
252 return retval;
253 }
254
255 SDL_bool
SDL_DBus_CallMethod(const char * node,const char * path,const char * interface,const char * method,...)256 SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...)
257 {
258 SDL_bool retval;
259 va_list ap;
260 va_start(ap, method);
261 retval = SDL_DBus_CallMethodInternal(dbus.session_conn, node, path, interface, method, ap);
262 va_end(ap);
263 return retval;
264 }
265
266 static SDL_bool
SDL_DBus_CallVoidMethodInternal(DBusConnection * conn,const char * node,const char * path,const char * interface,const char * method,va_list ap)267 SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
268 {
269 SDL_bool retval = SDL_FALSE;
270
271 if (conn) {
272 DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
273 if (msg) {
274 int firstarg = va_arg(ap, int);
275 if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
276 if (dbus.connection_send(conn, msg, NULL)) {
277 dbus.connection_flush(conn);
278 retval = SDL_TRUE;
279 }
280 }
281
282 dbus.message_unref(msg);
283 }
284 }
285
286 return retval;
287 }
288
289 SDL_bool
SDL_DBus_CallVoidMethodOnConnection(DBusConnection * conn,const char * node,const char * path,const char * interface,const char * method,...)290 SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
291 {
292 SDL_bool retval;
293 va_list ap;
294 va_start(ap, method);
295 retval = SDL_DBus_CallVoidMethodInternal(conn, node, path, interface, method, ap);
296 va_end(ap);
297 return retval;
298 }
299
300 SDL_bool
SDL_DBus_CallVoidMethod(const char * node,const char * path,const char * interface,const char * method,...)301 SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...)
302 {
303 SDL_bool retval;
304 va_list ap;
305 va_start(ap, method);
306 retval = SDL_DBus_CallVoidMethodInternal(dbus.session_conn, node, path, interface, method, ap);
307 va_end(ap);
308 return retval;
309 }
310
311 SDL_bool
SDL_DBus_QueryPropertyOnConnection(DBusConnection * conn,const char * node,const char * path,const char * interface,const char * property,const int expectedtype,void * result)312 SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, const int expectedtype, void *result)
313 {
314 SDL_bool retval = SDL_FALSE;
315
316 if (conn) {
317 DBusMessage *msg = dbus.message_new_method_call(node, path, "org.freedesktop.DBus.Properties", "Get");
318 if (msg) {
319 if (dbus.message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {
320 DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
321 if (reply) {
322 DBusMessageIter iter, sub;
323 dbus.message_iter_init(reply, &iter);
324 if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) {
325 dbus.message_iter_recurse(&iter, &sub);
326 if (dbus.message_iter_get_arg_type(&sub) == expectedtype) {
327 dbus.message_iter_get_basic(&sub, result);
328 retval = SDL_TRUE;
329 }
330 }
331 dbus.message_unref(reply);
332 }
333 }
334 dbus.message_unref(msg);
335 }
336 }
337
338 return retval;
339 }
340
341 SDL_bool
SDL_DBus_QueryProperty(const char * node,const char * path,const char * interface,const char * property,const int expectedtype,void * result)342 SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, const int expectedtype, void *result)
343 {
344 return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, node, path, interface, property, expectedtype, result);
345 }
346
347
348 void
SDL_DBus_ScreensaverTickle(void)349 SDL_DBus_ScreensaverTickle(void)
350 {
351 if (screensaver_cookie == 0) { /* no need to tickle if we're inhibiting. */
352 /* 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. */
353 SDL_DBus_CallVoidMethod("org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", "org.gnome.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
354 SDL_DBus_CallVoidMethod("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
355 }
356 }
357
358 SDL_bool
SDL_DBus_ScreensaverInhibit(SDL_bool inhibit)359 SDL_DBus_ScreensaverInhibit(SDL_bool inhibit)
360 {
361 if ( (inhibit && (screensaver_cookie != 0)) || (!inhibit && (screensaver_cookie == 0)) ) {
362 return SDL_TRUE;
363 } else {
364 const char *node = "org.freedesktop.ScreenSaver";
365 const char *path = "/org/freedesktop/ScreenSaver";
366 const char *interface = "org.freedesktop.ScreenSaver";
367
368 if (inhibit) {
369 const char *app = SDL_GetHint(SDL_HINT_APP_NAME);
370 const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
371 if (!app || !app[0]) {
372 app = "My SDL application";
373 }
374 if (!reason || !reason[0]) {
375 reason = "Playing a game";
376 }
377
378 if (!SDL_DBus_CallMethod(node, path, interface, "Inhibit",
379 DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID,
380 DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
381 return SDL_FALSE;
382 }
383 return (screensaver_cookie != 0) ? SDL_TRUE : SDL_FALSE;
384 } else {
385 if (!SDL_DBus_CallVoidMethod(node, path, interface, "UnInhibit", DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
386 return SDL_FALSE;
387 }
388 screensaver_cookie = 0;
389 }
390 }
391
392 return SDL_TRUE;
393 }
394 #endif
395
396 /* vi: set ts=4 sw=4 expandtab: */
397