Branch data Line data Source code
1 : : /* GDBus - GLib D-Bus Library
2 : : *
3 : : * Copyright (C) 2008-2010 Red Hat, Inc.
4 : : *
5 : : * SPDX-License-Identifier: LGPL-2.1-or-later
6 : : *
7 : : * This library is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU Lesser General Public
9 : : * License as published by the Free Software Foundation; either
10 : : * version 2.1 of the License, or (at your option) any later version.
11 : : *
12 : : * This library is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : : * Lesser General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU Lesser General
18 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 : : *
20 : : * Author: David Zeuthen <davidz@redhat.com>
21 : : */
22 : :
23 : : #include "config.h"
24 : :
25 : : #include <stdlib.h>
26 : : #include <string.h>
27 : :
28 : : #include "gdbusutils.h"
29 : : #include "gdbusnamewatching.h"
30 : : #include "gdbuserror.h"
31 : : #include "gdbusprivate.h"
32 : : #include "gdbusconnection.h"
33 : :
34 : : #include "glibintl.h"
35 : :
36 : : G_LOCK_DEFINE_STATIC (lock);
37 : :
38 : : /* ---------------------------------------------------------------------------------------------------- */
39 : :
40 : : typedef enum
41 : : {
42 : : PREVIOUS_CALL_NONE = 0,
43 : : PREVIOUS_CALL_APPEARED,
44 : : PREVIOUS_CALL_VANISHED,
45 : : } PreviousCall;
46 : :
47 : : typedef struct
48 : : {
49 : : gint ref_count; /* (atomic) */
50 : : guint id;
51 : : gchar *name;
52 : : GBusNameWatcherFlags flags;
53 : : gchar *name_owner;
54 : : GBusNameAppearedCallback name_appeared_handler;
55 : : GBusNameVanishedCallback name_vanished_handler;
56 : : gpointer user_data;
57 : : GDestroyNotify user_data_free_func;
58 : : GMainContext *main_context;
59 : :
60 : : GDBusConnection *connection;
61 : : gulong disconnected_signal_handler_id;
62 : : guint name_owner_changed_subscription_id;
63 : :
64 : : PreviousCall previous_call;
65 : :
66 : : gboolean cancelled;
67 : : gboolean initialized;
68 : : } Client;
69 : :
70 : : /* Must be accessed atomically. */
71 : : static guint next_global_id = 1; /* (atomic) */
72 : :
73 : : /* Must be accessed with @lock held. */
74 : : static GHashTable *map_id_to_client = NULL;
75 : :
76 : : static Client *
77 : 60 : client_ref (Client *client)
78 : : {
79 : 60 : g_atomic_int_inc (&client->ref_count);
80 : 60 : return client;
81 : : }
82 : :
83 : : static gboolean
84 : 17 : free_user_data_cb (gpointer user_data)
85 : : {
86 : : /* The user data is actually freed by the GDestroyNotify for the idle source */
87 : 17 : return G_SOURCE_REMOVE;
88 : : }
89 : :
90 : : static void
91 : 84 : client_unref (Client *client)
92 : : {
93 [ + + ]: 84 : if (g_atomic_int_dec_and_test (&client->ref_count))
94 : : {
95 [ + + ]: 26 : if (client->connection != NULL)
96 : : {
97 [ + - ]: 15 : if (client->name_owner_changed_subscription_id > 0)
98 : 15 : g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
99 [ + - ]: 15 : if (client->disconnected_signal_handler_id > 0)
100 : 15 : g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
101 : 15 : g_object_unref (client->connection);
102 : : }
103 : 26 : g_free (client->name);
104 : 26 : g_free (client->name_owner);
105 : :
106 [ + + ]: 26 : if (client->user_data_free_func != NULL)
107 : : {
108 : : /* Ensure client->user_data_free_func() is called from the right thread */
109 [ + + ]: 19 : if (client->main_context != g_main_context_get_thread_default ())
110 : : {
111 : 17 : GSource *idle_source = g_idle_source_new ();
112 : 17 : g_source_set_callback (idle_source, free_user_data_cb,
113 : : client->user_data,
114 : : client->user_data_free_func);
115 : 17 : g_source_set_name (idle_source, "[gio, gdbusnamewatching.c] free_user_data_cb");
116 : 17 : g_source_attach (idle_source, client->main_context);
117 : 17 : g_source_unref (idle_source);
118 : : }
119 : : else
120 : 2 : client->user_data_free_func (client->user_data);
121 : : }
122 : :
123 : 26 : g_main_context_unref (client->main_context);
124 : :
125 : 26 : g_free (client);
126 : : }
127 : 84 : }
128 : :
129 : : /* ---------------------------------------------------------------------------------------------------- */
130 : :
131 : : typedef enum
132 : : {
133 : : CALL_TYPE_NAME_APPEARED,
134 : : CALL_TYPE_NAME_VANISHED
135 : : } CallType;
136 : :
137 : : typedef struct
138 : : {
139 : : Client *client;
140 : :
141 : : /* keep this separate because client->connection may
142 : : * be set to NULL after scheduling the call
143 : : */
144 : : GDBusConnection *connection;
145 : :
146 : : /* ditto */
147 : : gchar *name_owner;
148 : :
149 : : CallType call_type;
150 : : } CallHandlerData;
151 : :
152 : : static void
153 : 2 : call_handler_data_free (CallHandlerData *data)
154 : : {
155 [ - + ]: 2 : if (data->connection != NULL)
156 : 0 : g_object_unref (data->connection);
157 : 2 : g_free (data->name_owner);
158 : 2 : client_unref (data->client);
159 : 2 : g_free (data);
160 : 2 : }
161 : :
162 : : static void
163 : 35 : actually_do_call (Client *client, GDBusConnection *connection, const gchar *name_owner, CallType call_type)
164 : : {
165 : : /* The client might have been cancelled (g_bus_unwatch_name()) while we were
166 : : * sitting in the #GMainContext dispatch queue. */
167 [ + + ]: 35 : if (client->cancelled)
168 : 1 : return;
169 : :
170 [ + + - ]: 34 : switch (call_type)
171 : : {
172 : 15 : case CALL_TYPE_NAME_APPEARED:
173 [ + - ]: 15 : if (client->name_appeared_handler != NULL)
174 : : {
175 : 15 : client->name_appeared_handler (connection,
176 : 15 : client->name,
177 : : name_owner,
178 : : client->user_data);
179 : : }
180 : 15 : break;
181 : :
182 : 19 : case CALL_TYPE_NAME_VANISHED:
183 [ + - ]: 19 : if (client->name_vanished_handler != NULL)
184 : : {
185 : 19 : client->name_vanished_handler (connection,
186 : 19 : client->name,
187 : : client->user_data);
188 : : }
189 : 19 : break;
190 : :
191 : 0 : default:
192 : : g_assert_not_reached ();
193 : : break;
194 : : }
195 : : }
196 : :
197 : : static gboolean
198 : 2 : call_in_idle_cb (gpointer _data)
199 : : {
200 : 2 : CallHandlerData *data = _data;
201 : 2 : actually_do_call (data->client, data->connection, data->name_owner, data->call_type);
202 : 2 : return FALSE;
203 : : }
204 : :
205 : : static void
206 : 2 : schedule_call_in_idle (Client *client, CallType call_type)
207 : : {
208 : : CallHandlerData *data;
209 : : GSource *idle_source;
210 : :
211 : 2 : data = g_new0 (CallHandlerData, 1);
212 : 2 : data->client = client_ref (client);
213 [ - + ]: 2 : data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL;
214 : 2 : data->name_owner = g_strdup (client->name_owner);
215 : 2 : data->call_type = call_type;
216 : :
217 : 2 : idle_source = g_idle_source_new ();
218 : 2 : g_source_set_priority (idle_source, G_PRIORITY_HIGH);
219 : 2 : g_source_set_callback (idle_source,
220 : : call_in_idle_cb,
221 : : data,
222 : : (GDestroyNotify) call_handler_data_free);
223 : 2 : g_source_set_static_name (idle_source, "[gio, gdbusnamewatching.c] call_in_idle_cb");
224 : 2 : g_source_attach (idle_source, client->main_context);
225 : 2 : g_source_unref (idle_source);
226 : 2 : }
227 : :
228 : : static void
229 : 35 : do_call (Client *client, CallType call_type)
230 : : {
231 : : GMainContext *current_context;
232 : :
233 : : /* only schedule in idle if we're not in the right thread */
234 : 35 : current_context = g_main_context_ref_thread_default ();
235 [ + + ]: 35 : if (current_context != client->main_context)
236 : 2 : schedule_call_in_idle (client, call_type);
237 : : else
238 : 33 : actually_do_call (client, client->connection, client->name_owner, call_type);
239 : 35 : g_main_context_unref (current_context);
240 : 35 : }
241 : :
242 : : static void
243 : 21 : call_appeared_handler (Client *client)
244 : : {
245 [ + - ]: 21 : if (client->previous_call != PREVIOUS_CALL_APPEARED)
246 : : {
247 : 21 : client->previous_call = PREVIOUS_CALL_APPEARED;
248 [ + - + + ]: 21 : if (!client->cancelled && client->name_appeared_handler != NULL)
249 : : {
250 : 15 : do_call (client, CALL_TYPE_NAME_APPEARED);
251 : : }
252 : : }
253 : 21 : }
254 : :
255 : : static void
256 : 22 : call_vanished_handler (Client *client)
257 : : {
258 [ + - ]: 22 : if (client->previous_call != PREVIOUS_CALL_VANISHED)
259 : : {
260 : 22 : client->previous_call = PREVIOUS_CALL_VANISHED;
261 [ + - + + ]: 22 : if (!client->cancelled && client->name_vanished_handler != NULL)
262 : : {
263 : 20 : do_call (client, CALL_TYPE_NAME_VANISHED);
264 : : }
265 : : }
266 : 22 : }
267 : :
268 : : /* ---------------------------------------------------------------------------------------------------- */
269 : :
270 : : /* Return a reference to the #Client for @watcher_id, or %NULL if it’s been
271 : : * unwatched. This is safe to call from any thread. */
272 : : static Client *
273 : 18 : dup_client (guint watcher_id)
274 : : {
275 : : Client *client;
276 : :
277 : 18 : G_LOCK (lock);
278 : :
279 : 18 : g_assert (watcher_id != 0);
280 : 18 : g_assert (map_id_to_client != NULL);
281 : :
282 : 18 : client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id));
283 : :
284 [ + - ]: 18 : if (client != NULL)
285 : 18 : client_ref (client);
286 : :
287 : 18 : G_UNLOCK (lock);
288 : :
289 : 18 : return client;
290 : : }
291 : :
292 : : /* Could be called from any thread, so it could be called after client_unref()
293 : : * has started finalising the #Client. Avoid that by looking up the #Client
294 : : * atomically. */
295 : : static void
296 : 6 : on_connection_disconnected (GDBusConnection *connection,
297 : : gboolean remote_peer_vanished,
298 : : GError *error,
299 : : gpointer user_data)
300 : : {
301 : 6 : guint watcher_id = GPOINTER_TO_UINT (user_data);
302 : 6 : Client *client = NULL;
303 : :
304 : 6 : client = dup_client (watcher_id);
305 [ - + ]: 6 : if (client == NULL)
306 : 0 : return;
307 : :
308 [ + - ]: 6 : if (client->name_owner_changed_subscription_id > 0)
309 : 6 : g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
310 [ + - ]: 6 : if (client->disconnected_signal_handler_id > 0)
311 : 6 : g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
312 : 6 : g_object_unref (client->connection);
313 : 6 : client->disconnected_signal_handler_id = 0;
314 : 6 : client->name_owner_changed_subscription_id = 0;
315 : 6 : client->connection = NULL;
316 : :
317 : 6 : call_vanished_handler (client);
318 : :
319 : 6 : client_unref (client);
320 : : }
321 : :
322 : : /* ---------------------------------------------------------------------------------------------------- */
323 : :
324 : : /* Will always be called from the thread which acquired client->main_context. */
325 : : static void
326 : 12 : on_name_owner_changed (GDBusConnection *connection,
327 : : const gchar *sender_name,
328 : : const gchar *object_path,
329 : : const gchar *interface_name,
330 : : const gchar *signal_name,
331 : : GVariant *parameters,
332 : : gpointer user_data)
333 : : {
334 : 12 : guint watcher_id = GPOINTER_TO_UINT (user_data);
335 : 12 : Client *client = NULL;
336 : : const gchar *name;
337 : : const gchar *old_owner;
338 : : const gchar *new_owner;
339 : :
340 : 12 : client = dup_client (watcher_id);
341 [ - + ]: 12 : if (client == NULL)
342 : 0 : return;
343 : :
344 [ + + ]: 12 : if (!client->initialized)
345 : 1 : goto out;
346 : :
347 [ + - + - ]: 22 : if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
348 [ - + ]: 22 : g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
349 : 11 : g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
350 : 0 : goto out;
351 : :
352 : 11 : g_variant_get (parameters,
353 : : "(&s&s&s)",
354 : : &name,
355 : : &old_owner,
356 : : &new_owner);
357 : :
358 : : /* we only care about a specific name */
359 [ - + ]: 11 : if (g_strcmp0 (name, client->name) != 0)
360 : 0 : goto out;
361 : :
362 [ + - + + : 11 : if ((old_owner != NULL && strlen (old_owner) > 0) && client->name_owner != NULL)
+ - ]
363 : : {
364 : 4 : g_free (client->name_owner);
365 : 4 : client->name_owner = NULL;
366 : 4 : call_vanished_handler (client);
367 : : }
368 : :
369 [ - + + + ]: 11 : if (new_owner != NULL && strlen (new_owner) > 0)
370 : : {
371 [ - + ]: 7 : g_warn_if_fail (client->name_owner == NULL);
372 : 7 : g_free (client->name_owner);
373 : 7 : client->name_owner = g_strdup (new_owner);
374 : 7 : call_appeared_handler (client);
375 : : }
376 : :
377 : 4 : out:
378 : 12 : client_unref (client);
379 : : }
380 : :
381 : : /* ---------------------------------------------------------------------------------------------------- */
382 : :
383 : : static void
384 : 21 : get_name_owner_cb (GObject *source_object,
385 : : GAsyncResult *res,
386 : : gpointer user_data)
387 : : {
388 : 21 : Client *client = user_data;
389 : : GVariant *result;
390 : : const char *name_owner;
391 : :
392 : 21 : name_owner = NULL;
393 : 21 : result = NULL;
394 : :
395 : 21 : result = g_dbus_connection_call_finish (client->connection,
396 : : res,
397 : : NULL);
398 [ + + ]: 21 : if (result != NULL)
399 : : {
400 : 14 : g_variant_get (result, "(&s)", &name_owner);
401 : : }
402 : :
403 [ + + ]: 21 : if (name_owner != NULL)
404 : : {
405 [ - + ]: 14 : g_warn_if_fail (client->name_owner == NULL);
406 : 14 : client->name_owner = g_strdup (name_owner);
407 : 14 : call_appeared_handler (client);
408 : : }
409 : : else
410 : : {
411 : 7 : call_vanished_handler (client);
412 : : }
413 : :
414 : 21 : client->initialized = TRUE;
415 : :
416 [ + + ]: 21 : if (result != NULL)
417 : 14 : g_variant_unref (result);
418 : 21 : client_unref (client);
419 : 21 : }
420 : :
421 : : /* ---------------------------------------------------------------------------------------------------- */
422 : :
423 : : static void
424 : 21 : invoke_get_name_owner (Client *client)
425 : : {
426 : 21 : g_dbus_connection_call (client->connection,
427 : : "org.freedesktop.DBus", /* bus name */
428 : : "/org/freedesktop/DBus", /* object path */
429 : : "org.freedesktop.DBus", /* interface name */
430 : : "GetNameOwner", /* method name */
431 : : g_variant_new ("(s)", client->name),
432 : : G_VARIANT_TYPE ("(s)"),
433 : : G_DBUS_CALL_FLAGS_NONE,
434 : : -1,
435 : : NULL,
436 : : (GAsyncReadyCallback) get_name_owner_cb,
437 : 21 : client_ref (client));
438 : 21 : }
439 : :
440 : : /* ---------------------------------------------------------------------------------------------------- */
441 : :
442 : : static void
443 : 6 : start_service_by_name_cb (GObject *source_object,
444 : : GAsyncResult *res,
445 : : gpointer user_data)
446 : : {
447 : 6 : Client *client = user_data;
448 : : GVariant *result;
449 : :
450 : 6 : result = NULL;
451 : :
452 : 6 : result = g_dbus_connection_call_finish (client->connection,
453 : : res,
454 : : NULL);
455 [ + + ]: 6 : if (result != NULL)
456 : : {
457 : : guint32 start_service_result;
458 : 2 : g_variant_get (result, "(u)", &start_service_result);
459 : :
460 [ + + ]: 2 : if (start_service_result == 1) /* DBUS_START_REPLY_SUCCESS */
461 : : {
462 : 1 : invoke_get_name_owner (client);
463 : : }
464 [ + - ]: 1 : else if (start_service_result == 2) /* DBUS_START_REPLY_ALREADY_RUNNING */
465 : : {
466 : 1 : invoke_get_name_owner (client);
467 : : }
468 : : else
469 : : {
470 : 0 : g_warning ("Unexpected reply %d from StartServiceByName() method", start_service_result);
471 : 0 : call_vanished_handler (client);
472 : 0 : client->initialized = TRUE;
473 : : }
474 : : }
475 : : else
476 : : {
477 : : /* Errors are not unexpected; the bus will reply e.g.
478 : : *
479 : : * org.freedesktop.DBus.Error.ServiceUnknown: The name org.gnome.Epiphany2
480 : : * was not provided by any .service files
481 : : *
482 : : * This doesn't mean that the name doesn't have an owner, just
483 : : * that it's not provided by a .service file. So proceed to
484 : : * invoke GetNameOwner().
485 : : */
486 : 4 : invoke_get_name_owner (client);
487 : : }
488 : :
489 [ + + ]: 6 : if (result != NULL)
490 : 2 : g_variant_unref (result);
491 : 6 : client_unref (client);
492 : 6 : }
493 : :
494 : : /* ---------------------------------------------------------------------------------------------------- */
495 : :
496 : : static void
497 : 21 : has_connection (Client *client)
498 : : {
499 : : /* listen for disconnection */
500 : 21 : client->disconnected_signal_handler_id = g_signal_connect (client->connection,
501 : : "closed",
502 : : G_CALLBACK (on_connection_disconnected),
503 : : GUINT_TO_POINTER (client->id));
504 : :
505 : : /* start listening to NameOwnerChanged messages immediately */
506 : 42 : client->name_owner_changed_subscription_id = g_dbus_connection_signal_subscribe (client->connection,
507 : : "org.freedesktop.DBus", /* name */
508 : : "org.freedesktop.DBus", /* if */
509 : : "NameOwnerChanged", /* signal */
510 : : "/org/freedesktop/DBus", /* path */
511 : 21 : client->name,
512 : : G_DBUS_SIGNAL_FLAGS_NONE,
513 : : on_name_owner_changed,
514 : 21 : GUINT_TO_POINTER (client->id),
515 : : NULL);
516 : :
517 [ + + ]: 21 : if (client->flags & G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
518 : : {
519 : 6 : g_dbus_connection_call (client->connection,
520 : : "org.freedesktop.DBus", /* bus name */
521 : : "/org/freedesktop/DBus", /* object path */
522 : : "org.freedesktop.DBus", /* interface name */
523 : : "StartServiceByName", /* method name */
524 : : g_variant_new ("(su)", client->name, 0),
525 : : G_VARIANT_TYPE ("(u)"),
526 : : G_DBUS_CALL_FLAGS_NONE,
527 : : -1,
528 : : NULL,
529 : : (GAsyncReadyCallback) start_service_by_name_cb,
530 : 6 : client_ref (client));
531 : : }
532 : : else
533 : : {
534 : : /* check owner */
535 : 15 : invoke_get_name_owner (client);
536 : : }
537 : 21 : }
538 : :
539 : :
540 : : static void
541 : 11 : connection_get_cb (GObject *source_object,
542 : : GAsyncResult *res,
543 : : gpointer user_data)
544 : : {
545 : 11 : Client *client = user_data;
546 : :
547 : 11 : client->connection = g_bus_get_finish (res, NULL);
548 [ + + ]: 11 : if (client->connection == NULL)
549 : : {
550 : 5 : call_vanished_handler (client);
551 : 5 : goto out;
552 : : }
553 : :
554 : 6 : has_connection (client);
555 : :
556 : 11 : out:
557 : 11 : client_unref (client);
558 : 11 : }
559 : :
560 : : /* ---------------------------------------------------------------------------------------------------- */
561 : :
562 : : /**
563 : : * g_bus_watch_name:
564 : : * @bus_type: The type of bus to watch a name on.
565 : : * @name: The name (well-known or unique) to watch.
566 : : * @flags: Flags from the #GBusNameWatcherFlags enumeration.
567 : : * @name_appeared_handler: (nullable) (scope notified): Handler to invoke when
568 : : * @name is known to exist or %NULL.
569 : : * @name_vanished_handler: (nullable) (scope notified): Handler to invoke when
570 : : * @name is known to not exist or %NULL.
571 : : * @user_data: User data to pass to handlers.
572 : : * @user_data_free_func: (nullable): Function for freeing @user_data or %NULL.
573 : : *
574 : : * Starts watching @name on the bus specified by @bus_type and calls
575 : : * @name_appeared_handler and @name_vanished_handler when the name is
576 : : * known to have an owner respectively known to lose its
577 : : * owner. Callbacks will be invoked in the
578 : : * [thread-default main context][g-main-context-push-thread-default]
579 : : * of the thread you are calling this function from.
580 : : *
581 : : * You are guaranteed that one of the handlers will be invoked after
582 : : * calling this function. When you are done watching the name, just
583 : : * call g_bus_unwatch_name() with the watcher id this function
584 : : * returns.
585 : : *
586 : : * If the name vanishes or appears (for example the application owning
587 : : * the name could restart), the handlers are also invoked. If the
588 : : * #GDBusConnection that is used for watching the name disconnects, then
589 : : * @name_vanished_handler is invoked since it is no longer
590 : : * possible to access the name.
591 : : *
592 : : * Another guarantee is that invocations of @name_appeared_handler
593 : : * and @name_vanished_handler are guaranteed to alternate; that
594 : : * is, if @name_appeared_handler is invoked then you are
595 : : * guaranteed that the next time one of the handlers is invoked, it
596 : : * will be @name_vanished_handler. The reverse is also true.
597 : : *
598 : : * This behavior makes it very simple to write applications that want
599 : : * to take action when a certain [name exists][gdbus-watching-names].
600 : : * Basically, the application should create object proxies in
601 : : * @name_appeared_handler and destroy them again (if any) in
602 : : * @name_vanished_handler.
603 : : *
604 : : * Returns: An identifier (never 0) that can be used with
605 : : * g_bus_unwatch_name() to stop watching the name.
606 : : *
607 : : * Since: 2.26
608 : : */
609 : : guint
610 : 13 : g_bus_watch_name (GBusType bus_type,
611 : : const gchar *name,
612 : : GBusNameWatcherFlags flags,
613 : : GBusNameAppearedCallback name_appeared_handler,
614 : : GBusNameVanishedCallback name_vanished_handler,
615 : : gpointer user_data,
616 : : GDestroyNotify user_data_free_func)
617 : : {
618 : : Client *client;
619 : :
620 : 13 : g_return_val_if_fail (g_dbus_is_name (name), 0);
621 : :
622 : 13 : G_LOCK (lock);
623 : :
624 : 13 : client = g_new0 (Client, 1);
625 : 13 : client->ref_count = 1;
626 : 13 : client->id = (guint) g_atomic_int_add (&next_global_id, 1); /* TODO: uh oh, handle overflow */
627 : 13 : client->name = g_strdup (name);
628 : 13 : client->flags = flags;
629 : 13 : client->name_appeared_handler = name_appeared_handler;
630 : 13 : client->name_vanished_handler = name_vanished_handler;
631 : 13 : client->user_data = user_data;
632 : 13 : client->user_data_free_func = user_data_free_func;
633 : 13 : client->main_context = g_main_context_ref_thread_default ();
634 : :
635 [ + + ]: 13 : if (map_id_to_client == NULL)
636 : : {
637 : 4 : map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
638 : : }
639 : 13 : g_hash_table_insert (map_id_to_client,
640 : 13 : GUINT_TO_POINTER (client->id),
641 : : client);
642 : :
643 : 13 : g_bus_get (bus_type,
644 : : NULL,
645 : : connection_get_cb,
646 : 13 : client_ref (client));
647 : :
648 : 13 : G_UNLOCK (lock);
649 : :
650 : 13 : return client->id;
651 : : }
652 : :
653 : : /**
654 : : * g_bus_watch_name_on_connection:
655 : : * @connection: A #GDBusConnection.
656 : : * @name: The name (well-known or unique) to watch.
657 : : * @flags: Flags from the #GBusNameWatcherFlags enumeration.
658 : : * @name_appeared_handler: (nullable) (scope notified): Handler to invoke when
659 : : * @name is known to exist or %NULL.
660 : : * @name_vanished_handler: (nullable) (scope notified): Handler to invoke when
661 : : * @name is known to not exist or %NULL.
662 : : * @user_data: User data to pass to handlers.
663 : : * @user_data_free_func: (nullable): Function for freeing @user_data or %NULL.
664 : : *
665 : : * Like g_bus_watch_name() but takes a #GDBusConnection instead of a
666 : : * #GBusType.
667 : : *
668 : : * Returns: An identifier (never 0) that can be used with
669 : : * g_bus_unwatch_name() to stop watching the name.
670 : : *
671 : : * Since: 2.26
672 : : */
673 : 15 : guint g_bus_watch_name_on_connection (GDBusConnection *connection,
674 : : const gchar *name,
675 : : GBusNameWatcherFlags flags,
676 : : GBusNameAppearedCallback name_appeared_handler,
677 : : GBusNameVanishedCallback name_vanished_handler,
678 : : gpointer user_data,
679 : : GDestroyNotify user_data_free_func)
680 : : {
681 : : Client *client;
682 : :
683 : 15 : g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
684 : 15 : g_return_val_if_fail (g_dbus_is_name (name), 0);
685 : :
686 : 15 : G_LOCK (lock);
687 : :
688 : 15 : client = g_new0 (Client, 1);
689 : 15 : client->ref_count = 1;
690 : 15 : client->id = (guint) g_atomic_int_add (&next_global_id, 1); /* TODO: uh oh, handle overflow */
691 : 15 : client->name = g_strdup (name);
692 : 15 : client->flags = flags;
693 : 15 : client->name_appeared_handler = name_appeared_handler;
694 : 15 : client->name_vanished_handler = name_vanished_handler;
695 : 15 : client->user_data = user_data;
696 : 15 : client->user_data_free_func = user_data_free_func;
697 : 15 : client->main_context = g_main_context_ref_thread_default ();
698 : :
699 [ + + ]: 15 : if (map_id_to_client == NULL)
700 : 5 : map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
701 : :
702 : 15 : g_hash_table_insert (map_id_to_client,
703 : 15 : GUINT_TO_POINTER (client->id),
704 : : client);
705 : :
706 : 15 : client->connection = g_object_ref (connection);
707 : 15 : G_UNLOCK (lock);
708 : :
709 : 15 : has_connection (client);
710 : :
711 : 15 : return client->id;
712 : : }
713 : :
714 : : typedef struct {
715 : : GClosure *name_appeared_closure;
716 : : GClosure *name_vanished_closure;
717 : : } WatchNameData;
718 : :
719 : : static WatchNameData *
720 : 4 : watch_name_data_new (GClosure *name_appeared_closure,
721 : : GClosure *name_vanished_closure)
722 : : {
723 : : WatchNameData *data;
724 : :
725 : 4 : data = g_new0 (WatchNameData, 1);
726 : :
727 [ + - ]: 4 : if (name_appeared_closure != NULL)
728 : : {
729 : 4 : data->name_appeared_closure = g_closure_ref (name_appeared_closure);
730 : 4 : g_closure_sink (name_appeared_closure);
731 [ + - ]: 4 : if (G_CLOSURE_NEEDS_MARSHAL (name_appeared_closure))
732 : 4 : g_closure_set_marshal (name_appeared_closure, g_cclosure_marshal_generic);
733 : : }
734 : :
735 [ + - ]: 4 : if (name_vanished_closure != NULL)
736 : : {
737 : 4 : data->name_vanished_closure = g_closure_ref (name_vanished_closure);
738 : 4 : g_closure_sink (name_vanished_closure);
739 [ + - ]: 4 : if (G_CLOSURE_NEEDS_MARSHAL (name_vanished_closure))
740 : 4 : g_closure_set_marshal (name_vanished_closure, g_cclosure_marshal_generic);
741 : : }
742 : :
743 : 4 : return data;
744 : : }
745 : :
746 : : static void
747 : 4 : watch_with_closures_on_name_appeared (GDBusConnection *connection,
748 : : const gchar *name,
749 : : const gchar *name_owner,
750 : : gpointer user_data)
751 : : {
752 : 4 : WatchNameData *data = user_data;
753 : 4 : GValue params[3] = { G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT };
754 : :
755 : 4 : g_value_init (¶ms[0], G_TYPE_DBUS_CONNECTION);
756 : 4 : g_value_set_object (¶ms[0], connection);
757 : :
758 : 4 : g_value_init (¶ms[1], G_TYPE_STRING);
759 : 4 : g_value_set_string (¶ms[1], name);
760 : :
761 : 4 : g_value_init (¶ms[2], G_TYPE_STRING);
762 : 4 : g_value_set_string (¶ms[2], name_owner);
763 : :
764 : 4 : g_closure_invoke (data->name_appeared_closure, NULL, 3, params, NULL);
765 : :
766 : 4 : g_value_unset (params + 0);
767 : 4 : g_value_unset (params + 1);
768 : 4 : g_value_unset (params + 2);
769 : 4 : }
770 : :
771 : : static void
772 : 4 : watch_with_closures_on_name_vanished (GDBusConnection *connection,
773 : : const gchar *name,
774 : : gpointer user_data)
775 : : {
776 : 4 : WatchNameData *data = user_data;
777 : 4 : GValue params[2] = { G_VALUE_INIT, G_VALUE_INIT };
778 : :
779 : 4 : g_value_init (¶ms[0], G_TYPE_DBUS_CONNECTION);
780 : 4 : g_value_set_object (¶ms[0], connection);
781 : :
782 : 4 : g_value_init (¶ms[1], G_TYPE_STRING);
783 : 4 : g_value_set_string (¶ms[1], name);
784 : :
785 : 4 : g_closure_invoke (data->name_vanished_closure, NULL, 2, params, NULL);
786 : :
787 : 4 : g_value_unset (params + 0);
788 : 4 : g_value_unset (params + 1);
789 : 4 : }
790 : :
791 : : static void
792 : 4 : bus_watch_name_free_func (gpointer user_data)
793 : : {
794 : 4 : WatchNameData *data = user_data;
795 : :
796 [ + - ]: 4 : if (data->name_appeared_closure != NULL)
797 : 4 : g_closure_unref (data->name_appeared_closure);
798 : :
799 [ + - ]: 4 : if (data->name_vanished_closure != NULL)
800 : 4 : g_closure_unref (data->name_vanished_closure);
801 : :
802 : 4 : g_free (data);
803 : 4 : }
804 : :
805 : : /**
806 : : * g_bus_watch_name_with_closures: (rename-to g_bus_watch_name)
807 : : * @bus_type: The type of bus to watch a name on.
808 : : * @name: The name (well-known or unique) to watch.
809 : : * @flags: Flags from the #GBusNameWatcherFlags enumeration.
810 : : * @name_appeared_closure: (nullable): #GClosure to invoke when @name is known
811 : : * to exist or %NULL.
812 : : * @name_vanished_closure: (nullable): #GClosure to invoke when @name is known
813 : : * to not exist or %NULL.
814 : : *
815 : : * Version of g_bus_watch_name() using closures instead of callbacks for
816 : : * easier binding in other languages.
817 : : *
818 : : * Returns: An identifier (never 0) that can be used with
819 : : * g_bus_unwatch_name() to stop watching the name.
820 : : *
821 : : * Since: 2.26
822 : : */
823 : : guint
824 : 2 : g_bus_watch_name_with_closures (GBusType bus_type,
825 : : const gchar *name,
826 : : GBusNameWatcherFlags flags,
827 : : GClosure *name_appeared_closure,
828 : : GClosure *name_vanished_closure)
829 : : {
830 [ + - + - ]: 2 : return g_bus_watch_name (bus_type,
831 : : name,
832 : : flags,
833 : : name_appeared_closure != NULL ? watch_with_closures_on_name_appeared : NULL,
834 : : name_vanished_closure != NULL ? watch_with_closures_on_name_vanished : NULL,
835 : 2 : watch_name_data_new (name_appeared_closure, name_vanished_closure),
836 : : bus_watch_name_free_func);
837 : : }
838 : :
839 : : /**
840 : : * g_bus_watch_name_on_connection_with_closures: (rename-to g_bus_watch_name_on_connection)
841 : : * @connection: A #GDBusConnection.
842 : : * @name: The name (well-known or unique) to watch.
843 : : * @flags: Flags from the #GBusNameWatcherFlags enumeration.
844 : : * @name_appeared_closure: (nullable): #GClosure to invoke when @name is known
845 : : * to exist or %NULL.
846 : : * @name_vanished_closure: (nullable): #GClosure to invoke when @name is known
847 : : * to not exist or %NULL.
848 : : *
849 : : * Version of g_bus_watch_name_on_connection() using closures instead of callbacks for
850 : : * easier binding in other languages.
851 : : *
852 : : * Returns: An identifier (never 0) that can be used with
853 : : * g_bus_unwatch_name() to stop watching the name.
854 : : *
855 : : * Since: 2.26
856 : : */
857 : 2 : guint g_bus_watch_name_on_connection_with_closures (
858 : : GDBusConnection *connection,
859 : : const gchar *name,
860 : : GBusNameWatcherFlags flags,
861 : : GClosure *name_appeared_closure,
862 : : GClosure *name_vanished_closure)
863 : : {
864 [ + - + - ]: 2 : return g_bus_watch_name_on_connection (connection,
865 : : name,
866 : : flags,
867 : : name_appeared_closure != NULL ? watch_with_closures_on_name_appeared : NULL,
868 : : name_vanished_closure != NULL ? watch_with_closures_on_name_vanished : NULL,
869 : 2 : watch_name_data_new (name_appeared_closure, name_vanished_closure),
870 : : bus_watch_name_free_func);
871 : : }
872 : :
873 : : /**
874 : : * g_bus_unwatch_name:
875 : : * @watcher_id: An identifier obtained from g_bus_watch_name()
876 : : *
877 : : * Stops watching a name.
878 : : *
879 : : * Note that there may still be D-Bus traffic to process (relating to watching
880 : : * and unwatching the name) in the current thread-default #GMainContext after
881 : : * this function has returned. You should continue to iterate the #GMainContext
882 : : * until the #GDestroyNotify function passed to g_bus_watch_name() is called, in
883 : : * order to avoid memory leaks through callbacks queued on the #GMainContext
884 : : * after it’s stopped being iterated.
885 : : *
886 : : * Since: 2.26
887 : : */
888 : : void
889 : 26 : g_bus_unwatch_name (guint watcher_id)
890 : : {
891 : : Client *client;
892 : :
893 : 26 : g_return_if_fail (watcher_id > 0);
894 : :
895 : 26 : client = NULL;
896 : :
897 : 26 : G_LOCK (lock);
898 [ + - ]: 26 : if (watcher_id == 0 ||
899 [ + - - + ]: 52 : map_id_to_client == NULL ||
900 : 26 : (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL)
901 : : {
902 : 0 : g_warning ("Invalid id %d passed to g_bus_unwatch_name()", watcher_id);
903 : 0 : goto out;
904 : : }
905 : :
906 : 26 : client->cancelled = TRUE;
907 [ - + ]: 26 : g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id)));
908 : :
909 : 26 : out:
910 : 26 : G_UNLOCK (lock);
911 : :
912 : : /* do callback without holding lock */
913 [ + - ]: 26 : if (client != NULL)
914 : : {
915 : 26 : client_unref (client);
916 : : }
917 : : }
|