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