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_dbus.h"
23
24#if SDL_USE_LIBDBUS
25/* we never link directly to libdbus. */
26#include "SDL_loadso.h"
27static const char *dbus_library = "libdbus-1.so.3";
28static void *dbus_handle = NULL;
29static unsigned int screensaver_cookie = 0;
30static SDL_DBusContext dbus;
31
32static int
33LoadDBUSSyms(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
87static void
88UnloadDBUSLibrary(void)
89{
90 if (dbus_handle != NULL) {
91 SDL_UnloadObject(dbus_handle);
92 dbus_handle = NULL;
93 }
94}
95
96static int
97LoadDBUSLibrary(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
116void
117SDL_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
159void
160SDL_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
182SDL_DBusContext *
183SDL_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
192static SDL_bool
193SDL_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
230SDL_bool
231SDL_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
241SDL_bool
242SDL_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
252static SDL_bool
253SDL_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
275SDL_bool
276SDL_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
286SDL_bool
287SDL_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
297SDL_bool
298SDL_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
327SDL_bool
328SDL_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
334void
335SDL_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
344SDL_bool
345SDL_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