Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-mpris-adapter"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <valent.h>
9 : :
10 : : #include "valent-mpris-impl.h"
11 : : #include "valent-mpris-player.h"
12 : : #include "valent-mpris-utils.h"
13 : :
14 : : #include "valent-mpris-adapter.h"
15 : :
16 : :
17 : : struct _ValentMPRISAdapter
18 : : {
19 : : ValentMediaAdapter parent_instance;
20 : :
21 : : GDBusConnection *connection;
22 : : unsigned int name_owner_changed_id;
23 : : GHashTable *players;
24 : : GHashTable *exports;
25 : : };
26 : :
27 : : static void g_async_initable_iface_init (GAsyncInitableIface *iface);
28 : :
29 [ + + + - ]: 17 : G_DEFINE_FINAL_TYPE_WITH_CODE (ValentMPRISAdapter, valent_mpris_adapter, VALENT_TYPE_MEDIA_ADAPTER,
30 : : G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, g_async_initable_iface_init))
31 : :
32 : :
33 : : static unsigned int n_exports = 0;
34 : :
35 : :
36 : : static void
37 : 4 : g_async_initable_new_async_cb (GObject *object,
38 : : GAsyncResult *result,
39 : : gpointer user_data)
40 : : {
41 : 4 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (user_data);
42 : 4 : GAsyncInitable *initable = G_ASYNC_INITABLE (object);
43 : 4 : g_autoptr (GObject) player = NULL;
44 [ - - ]: 4 : g_autofree char *name = NULL;
45 : 4 : g_autoptr (GError) error = NULL;
46 : :
47 [ + - ]: 4 : g_assert (VALENT_IS_MPRIS_ADAPTER (self));
48 [ + - + - : 4 : g_assert (G_IS_ASYNC_INITABLE (initable));
+ - - + ]
49 : :
50 [ - + ]: 4 : if ((player = g_async_initable_new_finish (initable, result, &error)) == NULL)
51 : : {
52 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
53 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
54 : :
55 : 0 : return;
56 : : }
57 : :
58 : 4 : g_object_get (player, "bus-name", &name, NULL);
59 : :
60 [ + - ]: 4 : if (g_hash_table_contains (self->players, name))
61 : : return;
62 : :
63 : 4 : g_hash_table_replace (self->players,
64 : : g_steal_pointer (&name),
65 : : g_object_ref (player));
66 : :
67 [ - + ]: 4 : valent_media_adapter_player_added (VALENT_MEDIA_ADAPTER (self),
68 : : VALENT_MEDIA_PLAYER (player));
69 : : }
70 : :
71 : : static void
72 : 10 : on_name_owner_changed (GDBusConnection *connection,
73 : : const char *sender_name,
74 : : const char *object_path,
75 : : const char *interface_name,
76 : : const char *signal_name,
77 : : GVariant *parameters,
78 : : gpointer user_data)
79 : : {
80 : 10 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (user_data);
81 : 10 : const char *name;
82 : 10 : const char *old_owner;
83 : 10 : const char *new_owner;
84 : 10 : gboolean known;
85 : :
86 : 10 : g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
87 : :
88 : : /* This is the D-Bus name we export on */
89 [ - + + + : 10 : if G_UNLIKELY (g_str_has_prefix (name, VALENT_MPRIS_DBUS_NAME))
- + ]
90 : 2 : return;
91 : :
92 : 8 : known = g_hash_table_contains (self->players, name);
93 : :
94 [ + + + - ]: 8 : if (*new_owner != '\0' && !known)
95 : : {
96 : 8 : g_autoptr (GCancellable) destroy = NULL;
97 : :
98 : : /* Cancel initialization if the adapter is destroyed */
99 : 4 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
100 [ + - ]: 4 : g_async_initable_new_async (VALENT_TYPE_MPRIS_PLAYER,
101 : : G_PRIORITY_DEFAULT,
102 : : destroy,
103 : : g_async_initable_new_async_cb,
104 : : self,
105 : : "source", self,
106 : : "bus-name", name,
107 : : NULL);
108 : : }
109 [ + - + - ]: 4 : else if (*old_owner != '\0' && known)
110 : : {
111 : 4 : ValentMediaAdapter *adapter = VALENT_MEDIA_ADAPTER (self);
112 : 4 : gpointer key, value;
113 : :
114 [ + - ]: 4 : if (g_hash_table_steal_extended (self->players, name, &key, &value))
115 : : {
116 : 4 : valent_media_adapter_player_removed (adapter, value);
117 : 4 : g_free (key);
118 : 4 : g_object_unref (value);
119 : : }
120 : : }
121 : : }
122 : :
123 : : /*
124 : : * GAsyncInitable
125 : : */
126 : : static void
127 : 6 : list_names_cb (GDBusConnection *connection,
128 : : GAsyncResult *result,
129 : : gpointer user_data)
130 : : {
131 : 6 : ValentMPRISAdapter *self;
132 : 12 : g_autoptr (GTask) task = G_TASK (user_data);
133 [ - - + - ]: 6 : g_autoptr (GVariant) reply = NULL;
134 : :
135 : 6 : self = g_task_get_source_object (task);
136 : :
137 : : /* If successful, add any currently exported players */
138 : 6 : reply = g_dbus_connection_call_finish (connection, result, NULL);
139 : :
140 [ + - ]: 6 : if (reply != NULL)
141 : : {
142 : 6 : g_autoptr (GCancellable) destroy = NULL;
143 [ + - ]: 6 : g_autoptr (GVariant) names = NULL;
144 : 6 : GVariantIter iter;
145 : 6 : const char *name;
146 : :
147 : 6 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
148 : 6 : names = g_variant_get_child_value (reply, 0);
149 : 6 : g_variant_iter_init (&iter, names);
150 : :
151 [ + + ]: 66 : while (g_variant_iter_next (&iter, "&s", &name))
152 : : {
153 [ - + + + : 60 : if G_LIKELY (!g_str_has_prefix (name, "org.mpris.MediaPlayer2"))
- + ]
154 : 60 : continue;
155 : :
156 : : /* This is the D-Bus name we export on */
157 [ # # # # : 0 : if G_UNLIKELY (g_str_has_prefix (name, VALENT_MPRIS_DBUS_NAME))
# # ]
158 : 0 : continue;
159 : :
160 : 0 : g_async_initable_new_async (VALENT_TYPE_MPRIS_PLAYER,
161 : : G_PRIORITY_DEFAULT,
162 : : destroy,
163 : : g_async_initable_new_async_cb,
164 : : self,
165 : : "bus-name", name,
166 : : NULL);
167 : : }
168 : : }
169 : :
170 [ - + ]: 6 : if (g_task_return_error_if_cancelled (task))
171 [ # # ]: 0 : return;
172 : :
173 : : /* Regardless of the result of `ListNames()`, the connection is valid */
174 : 12 : self->name_owner_changed_id =
175 : 6 : g_dbus_connection_signal_subscribe (connection,
176 : : "org.freedesktop.DBus",
177 : : "org.freedesktop.DBus",
178 : : "NameOwnerChanged",
179 : : "/org/freedesktop/DBus",
180 : : "org.mpris.MediaPlayer2",
181 : : G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
182 : : on_name_owner_changed,
183 : : self, NULL);
184 : :
185 : :
186 : : /* Report the adapter as active */
187 : 6 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
188 : : VALENT_PLUGIN_STATE_ACTIVE,
189 : : NULL);
190 [ + - ]: 6 : g_task_return_boolean (task, TRUE);
191 : : }
192 : :
193 : : static void
194 : 6 : valent_mpris_adapter_init_async (GAsyncInitable *initable,
195 : : int io_priority,
196 : : GCancellable *cancellable,
197 : : GAsyncReadyCallback callback,
198 : : gpointer user_data)
199 : : {
200 : 6 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (initable);
201 : 6 : g_autoptr (GTask) task = NULL;
202 [ - - ]: 6 : g_autoptr (GCancellable) destroy = NULL;
203 [ - - + - ]: 6 : g_autoptr (GError) error = NULL;
204 : :
205 [ + - ]: 6 : g_assert (VALENT_IS_MPRIS_ADAPTER (self));
206 : :
207 : : /* Cancel initialization if the object is destroyed */
208 : 6 : destroy = valent_object_chain_cancellable (VALENT_OBJECT (initable),
209 : : cancellable);
210 : :
211 : 6 : task = g_task_new (initable, destroy, callback, user_data);
212 : 6 : g_task_set_priority (task, io_priority);
213 [ + - ]: 6 : g_task_set_source_tag (task, valent_mpris_adapter_init_async);
214 : :
215 : 6 : self->connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
216 : : destroy,
217 : : &error);
218 : :
219 [ - + ]: 6 : if (self->connection == NULL)
220 : : {
221 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
222 : : VALENT_PLUGIN_STATE_ERROR,
223 : : error);
224 [ # # ]: 0 : return g_task_return_error (task, g_steal_pointer (&error));
225 : : }
226 : :
227 [ - + ]: 6 : g_dbus_connection_call (self->connection,
228 : : "org.freedesktop.DBus",
229 : : "/org/freedesktop/DBus",
230 : : "org.freedesktop.DBus",
231 : : "ListNames",
232 : : NULL,
233 : : NULL,
234 : : G_DBUS_CALL_FLAGS_NONE,
235 : : -1,
236 : : destroy,
237 : : (GAsyncReadyCallback)list_names_cb,
238 : : g_steal_pointer (&task));
239 : : }
240 : :
241 : : static void
242 : 2 : g_async_initable_iface_init (GAsyncInitableIface *iface)
243 : : {
244 : 2 : iface->init_async = valent_mpris_adapter_init_async;
245 : 2 : }
246 : :
247 : : /*
248 : : * ValentMediaAdapter
249 : : */
250 : : static void
251 : 1 : valent_mpris_impl_export_full_cb (ValentMPRISImpl *impl,
252 : : GAsyncResult *result,
253 : : ValentMPRISAdapter *self)
254 : : {
255 : 2 : g_autoptr (GError) error = NULL;
256 : :
257 [ - + - - ]: 1 : if (!valent_mpris_impl_export_finish (impl, result, &error) &&
258 : 0 : !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
259 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
260 : 1 : }
261 : :
262 : : static void
263 : 1 : valent_mpris_adapter_export_player (ValentMediaAdapter *adapter,
264 : : ValentMediaPlayer *player)
265 : : {
266 : 1 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (adapter);
267 : 0 : g_autoptr (ValentMPRISImpl) impl = NULL;
268 [ + - ]: 1 : g_autoptr (GCancellable) destroy = NULL;
269 [ + - ]: 1 : g_autofree char *bus_name = NULL;
270 : :
271 [ - + ]: 1 : if (g_hash_table_contains (self->exports, player))
272 : 0 : return;
273 : :
274 : 1 : impl = valent_mpris_impl_new (player);
275 : 1 : g_hash_table_insert (self->exports, player, g_object_ref (impl));
276 : :
277 : 1 : bus_name = g_strdup_printf ("%s.Player%u",
278 : : VALENT_MPRIS_DBUS_NAME,
279 : : n_exports++);
280 : 1 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (adapter));
281 : 1 : valent_mpris_impl_export_full (impl,
282 : : bus_name,
283 : : destroy,
284 : : (GAsyncReadyCallback)valent_mpris_impl_export_full_cb,
285 : : self);
286 : : }
287 : :
288 : : static void
289 : 1 : valent_mpris_adapter_unexport_player (ValentMediaAdapter *adapter,
290 : : ValentMediaPlayer *player)
291 : : {
292 : 1 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (adapter);
293 : 1 : g_autoptr (ValentMPRISImpl) impl = NULL;
294 : :
295 [ + - ]: 1 : g_assert (VALENT_IS_MPRIS_ADAPTER (self));
296 [ - + ]: 1 : g_assert (VALENT_IS_MEDIA_PLAYER (player));
297 : :
298 [ - + ]: 1 : if (!g_hash_table_steal_extended (self->exports, player, NULL, (void **)&impl))
299 [ # # ]: 0 : return;
300 : :
301 : 1 : g_signal_handlers_disconnect_by_data (impl, self);
302 [ + - ]: 1 : valent_mpris_impl_unexport (impl);
303 : : }
304 : :
305 : : /*
306 : : * ValentObject
307 : : */
308 : : static void
309 : 10 : valent_mpris_adapter_destroy (ValentObject *object)
310 : : {
311 : 10 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (object);
312 : 10 : GHashTableIter iter;
313 : 10 : ValentMPRISImpl *impl;
314 : :
315 [ + + ]: 10 : if (self->name_owner_changed_id > 0)
316 : : {
317 : 5 : g_dbus_connection_signal_unsubscribe (self->connection,
318 : : self->name_owner_changed_id);
319 : 5 : self->name_owner_changed_id = 0;
320 : : }
321 : :
322 : 10 : g_hash_table_iter_init (&iter, self->exports);
323 : :
324 [ - + ]: 10 : while (g_hash_table_iter_next (&iter, NULL, (void **)&impl))
325 : : {
326 : 0 : g_signal_handlers_disconnect_by_data (impl, self);
327 : 0 : valent_mpris_impl_unexport (impl);
328 : 0 : g_hash_table_iter_remove (&iter);
329 : : }
330 : :
331 : 10 : VALENT_OBJECT_CLASS (valent_mpris_adapter_parent_class)->destroy (object);
332 : 10 : }
333 : :
334 : : /*
335 : : * GObject
336 : : */
337 : : static void
338 : 5 : valent_mpris_adapter_finalize (GObject *object)
339 : : {
340 : 5 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (object);
341 : :
342 [ + - ]: 5 : g_clear_object (&self->connection);
343 [ + - ]: 5 : g_clear_pointer (&self->players, g_hash_table_unref);
344 [ + - ]: 5 : g_clear_pointer (&self->exports, g_hash_table_unref);
345 : :
346 : 5 : G_OBJECT_CLASS (valent_mpris_adapter_parent_class)->finalize (object);
347 : 5 : }
348 : :
349 : : static void
350 : 2 : valent_mpris_adapter_class_init (ValentMPRISAdapterClass *klass)
351 : : {
352 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
353 : 2 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
354 : 2 : ValentMediaAdapterClass *adapter_class = VALENT_MEDIA_ADAPTER_CLASS (klass);
355 : :
356 : 2 : object_class->finalize = valent_mpris_adapter_finalize;
357 : :
358 : 2 : vobject_class->destroy = valent_mpris_adapter_destroy;
359 : :
360 : 2 : adapter_class->export_player = valent_mpris_adapter_export_player;
361 : 2 : adapter_class->unexport_player = valent_mpris_adapter_unexport_player;
362 : : }
363 : :
364 : : static void
365 : 6 : valent_mpris_adapter_init (ValentMPRISAdapter *self)
366 : : {
367 : 6 : self->exports = g_hash_table_new_full (NULL, NULL,
368 : : NULL, g_object_unref);
369 : 6 : self->players = g_hash_table_new_full (g_str_hash, g_str_equal,
370 : : g_free, g_object_unref);
371 : 6 : }
372 : :
|