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 : : "bus-name", name,
106 : : NULL);
107 : : }
108 [ + - + - ]: 4 : else if (*old_owner != '\0' && known)
109 : : {
110 : 4 : ValentMediaAdapter *adapter = VALENT_MEDIA_ADAPTER (self);
111 : 4 : gpointer key, value;
112 : :
113 [ + - ]: 4 : if (g_hash_table_steal_extended (self->players, name, &key, &value))
114 : : {
115 : 4 : valent_media_adapter_player_removed (adapter, value);
116 : 4 : g_free (key);
117 : 4 : g_object_unref (value);
118 : : }
119 : : }
120 : : }
121 : :
122 : : /*
123 : : * GAsyncInitable
124 : : */
125 : : static void
126 : 6 : list_names_cb (GDBusConnection *connection,
127 : : GAsyncResult *result,
128 : : gpointer user_data)
129 : : {
130 : 6 : ValentMPRISAdapter *self;
131 : 12 : g_autoptr (GTask) task = G_TASK (user_data);
132 [ - - + - ]: 6 : g_autoptr (GVariant) reply = NULL;
133 : :
134 : 6 : self = g_task_get_source_object (task);
135 : :
136 : : /* If successful, add any currently exported players */
137 : 6 : reply = g_dbus_connection_call_finish (connection, result, NULL);
138 : :
139 [ + - ]: 6 : if (reply != NULL)
140 : : {
141 : 6 : g_autoptr (GCancellable) destroy = NULL;
142 [ + - ]: 6 : g_autoptr (GVariant) names = NULL;
143 : 6 : GVariantIter iter;
144 : 6 : const char *name;
145 : :
146 : 6 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
147 : 6 : names = g_variant_get_child_value (reply, 0);
148 : 6 : g_variant_iter_init (&iter, names);
149 : :
150 [ + + ]: 66 : while (g_variant_iter_next (&iter, "&s", &name))
151 : : {
152 [ - + + + : 60 : if G_LIKELY (!g_str_has_prefix (name, "org.mpris.MediaPlayer2"))
- + ]
153 : 60 : continue;
154 : :
155 : : /* This is the D-Bus name we export on */
156 [ # # # # : 0 : if G_UNLIKELY (g_str_has_prefix (name, VALENT_MPRIS_DBUS_NAME))
# # ]
157 : 0 : continue;
158 : :
159 : 0 : g_async_initable_new_async (VALENT_TYPE_MPRIS_PLAYER,
160 : : G_PRIORITY_DEFAULT,
161 : : destroy,
162 : : g_async_initable_new_async_cb,
163 : : self,
164 : : "bus-name", name,
165 : : NULL);
166 : : }
167 : : }
168 : :
169 [ - + ]: 6 : if (g_task_return_error_if_cancelled (task))
170 [ # # ]: 0 : return;
171 : :
172 : : /* Regardless of the result of `ListNames()`, the connection is valid */
173 : 12 : self->name_owner_changed_id =
174 : 6 : g_dbus_connection_signal_subscribe (connection,
175 : : "org.freedesktop.DBus",
176 : : "org.freedesktop.DBus",
177 : : "NameOwnerChanged",
178 : : "/org/freedesktop/DBus",
179 : : "org.mpris.MediaPlayer2",
180 : : G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
181 : : on_name_owner_changed,
182 : : self, NULL);
183 : :
184 : :
185 : : /* Report the adapter as active */
186 : 6 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
187 : : VALENT_PLUGIN_STATE_ACTIVE,
188 : : NULL);
189 [ + - ]: 6 : g_task_return_boolean (task, TRUE);
190 : : }
191 : :
192 : : static void
193 : 6 : valent_mpris_adapter_init_async (GAsyncInitable *initable,
194 : : int io_priority,
195 : : GCancellable *cancellable,
196 : : GAsyncReadyCallback callback,
197 : : gpointer user_data)
198 : : {
199 : 6 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (initable);
200 : 6 : g_autoptr (GTask) task = NULL;
201 [ - - ]: 6 : g_autoptr (GCancellable) destroy = NULL;
202 [ - - + - ]: 6 : g_autoptr (GError) error = NULL;
203 : :
204 [ + - ]: 6 : g_assert (VALENT_IS_MPRIS_ADAPTER (self));
205 : :
206 : : /* Cede the primary position until complete */
207 : 6 : valent_extension_plugin_state_changed (VALENT_EXTENSION (initable),
208 : : VALENT_PLUGIN_STATE_INACTIVE,
209 : : NULL);
210 : :
211 : : /* Cancel initialization if the object is destroyed */
212 : 6 : destroy = valent_object_chain_cancellable (VALENT_OBJECT (initable),
213 : : cancellable);
214 : :
215 : 6 : task = g_task_new (initable, destroy, callback, user_data);
216 : 6 : g_task_set_priority (task, io_priority);
217 [ + - ]: 6 : g_task_set_source_tag (task, valent_mpris_adapter_init_async);
218 : :
219 : 6 : self->connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
220 : : destroy,
221 : : &error);
222 : :
223 [ - + ]: 6 : if (self->connection == NULL)
224 : : {
225 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
226 : : VALENT_PLUGIN_STATE_ERROR,
227 : : error);
228 [ # # ]: 0 : return g_task_return_error (task, g_steal_pointer (&error));
229 : : }
230 : :
231 [ - + ]: 6 : g_dbus_connection_call (self->connection,
232 : : "org.freedesktop.DBus",
233 : : "/org/freedesktop/DBus",
234 : : "org.freedesktop.DBus",
235 : : "ListNames",
236 : : NULL,
237 : : NULL,
238 : : G_DBUS_CALL_FLAGS_NONE,
239 : : -1,
240 : : destroy,
241 : : (GAsyncReadyCallback)list_names_cb,
242 : : g_steal_pointer (&task));
243 : : }
244 : :
245 : : static void
246 : 2 : g_async_initable_iface_init (GAsyncInitableIface *iface)
247 : : {
248 : 2 : iface->init_async = valent_mpris_adapter_init_async;
249 : 2 : }
250 : :
251 : : /*
252 : : * ValentMediaAdapter
253 : : */
254 : : static void
255 : 1 : valent_mpris_impl_export_full_cb (ValentMPRISImpl *impl,
256 : : GAsyncResult *result,
257 : : ValentMPRISAdapter *self)
258 : : {
259 : 2 : g_autoptr (GError) error = NULL;
260 : :
261 [ - + - - ]: 1 : if (!valent_mpris_impl_export_finish (impl, result, &error) &&
262 : 0 : !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
263 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
264 : 1 : }
265 : :
266 : : static void
267 : 1 : valent_mpris_adapter_export (ValentMediaAdapter *adapter,
268 : : ValentMediaPlayer *player)
269 : : {
270 : 1 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (adapter);
271 : 0 : g_autoptr (ValentMPRISImpl) impl = NULL;
272 [ + - ]: 1 : g_autoptr (GCancellable) destroy = NULL;
273 [ + - ]: 1 : g_autofree char *bus_name = NULL;
274 : :
275 [ - + ]: 1 : if (g_hash_table_contains (self->exports, player))
276 : 0 : return;
277 : :
278 : 1 : impl = valent_mpris_impl_new (player);
279 : 1 : g_hash_table_insert (self->exports, player, g_object_ref (impl));
280 : :
281 : 1 : bus_name = g_strdup_printf ("%s.Player%u",
282 : : VALENT_MPRIS_DBUS_NAME,
283 : : n_exports++);
284 : 1 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (adapter));
285 : 1 : valent_mpris_impl_export_full (impl,
286 : : bus_name,
287 : : destroy,
288 : : (GAsyncReadyCallback)valent_mpris_impl_export_full_cb,
289 : : self);
290 : : }
291 : :
292 : : static void
293 : 1 : valent_mpris_adapter_unexport (ValentMediaAdapter *adapter,
294 : : ValentMediaPlayer *player)
295 : : {
296 : 1 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (adapter);
297 : 1 : g_autoptr (ValentMPRISImpl) impl = NULL;
298 : :
299 [ + - ]: 1 : g_assert (VALENT_IS_MPRIS_ADAPTER (self));
300 [ - + ]: 1 : g_assert (VALENT_IS_MEDIA_PLAYER (player));
301 : :
302 [ - + ]: 1 : if (!g_hash_table_steal_extended (self->exports, player, NULL, (void **)&impl))
303 [ # # ]: 0 : return;
304 : :
305 : 1 : g_signal_handlers_disconnect_by_data (impl, self);
306 [ + - ]: 1 : valent_mpris_impl_unexport (impl);
307 : : }
308 : :
309 : : /*
310 : : * ValentObject
311 : : */
312 : : static void
313 : 5 : valent_mpris_adapter_destroy (ValentObject *object)
314 : : {
315 : 5 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (object);
316 : 5 : GHashTableIter iter;
317 : 5 : ValentMPRISImpl *impl;
318 : :
319 [ + - ]: 5 : if (self->name_owner_changed_id > 0)
320 : : {
321 : 5 : g_dbus_connection_signal_unsubscribe (self->connection,
322 : : self->name_owner_changed_id);
323 : 5 : self->name_owner_changed_id = 0;
324 : : }
325 : :
326 : 5 : g_hash_table_iter_init (&iter, self->exports);
327 : :
328 [ - + ]: 5 : while (g_hash_table_iter_next (&iter, NULL, (void **)&impl))
329 : : {
330 : 0 : g_signal_handlers_disconnect_by_data (impl, self);
331 : 0 : valent_mpris_impl_unexport (impl);
332 : 0 : g_hash_table_iter_remove (&iter);
333 : : }
334 : :
335 : 5 : VALENT_OBJECT_CLASS (valent_mpris_adapter_parent_class)->destroy (object);
336 : 5 : }
337 : :
338 : : /*
339 : : * GObject
340 : : */
341 : : static void
342 : 5 : valent_mpris_adapter_finalize (GObject *object)
343 : : {
344 : 5 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (object);
345 : :
346 [ + - ]: 5 : g_clear_object (&self->connection);
347 [ + - ]: 5 : g_clear_pointer (&self->players, g_hash_table_unref);
348 [ + - ]: 5 : g_clear_pointer (&self->exports, g_hash_table_unref);
349 : :
350 : 5 : G_OBJECT_CLASS (valent_mpris_adapter_parent_class)->finalize (object);
351 : 5 : }
352 : :
353 : : static void
354 : 2 : valent_mpris_adapter_class_init (ValentMPRISAdapterClass *klass)
355 : : {
356 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
357 : 2 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
358 : 2 : ValentMediaAdapterClass *adapter_class = VALENT_MEDIA_ADAPTER_CLASS (klass);
359 : :
360 : 2 : object_class->finalize = valent_mpris_adapter_finalize;
361 : :
362 : 2 : vobject_class->destroy = valent_mpris_adapter_destroy;
363 : :
364 : 2 : adapter_class->export_player = valent_mpris_adapter_export;
365 : 2 : adapter_class->unexport_player = valent_mpris_adapter_unexport;
366 : : }
367 : :
368 : : static void
369 : 6 : valent_mpris_adapter_init (ValentMPRISAdapter *self)
370 : : {
371 : 6 : self->exports = g_hash_table_new_full (NULL, NULL,
372 : : NULL, g_object_unref);
373 : 6 : self->players = g_hash_table_new_full (g_str_hash, g_str_equal,
374 : : g_free, g_object_unref);
375 : 6 : }
376 : :
|