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