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 [ + + ]: 18 : while (g_variant_iter_next (&iter, "&s", &name))
152 : : {
153 [ - + - + : 12 : if G_LIKELY (!g_str_has_prefix (name, "org.mpris.MediaPlayer2"))
- - ]
154 : 12 : 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 : : "source", self,
167 : : NULL);
168 : : }
169 : : }
170 : :
171 [ - + ]: 6 : if (g_task_return_error_if_cancelled (task))
172 [ # # ]: 0 : return;
173 : :
174 : : /* Regardless of the result of `ListNames()`, the connection is valid */
175 : 12 : self->name_owner_changed_id =
176 : 6 : g_dbus_connection_signal_subscribe (connection,
177 : : "org.freedesktop.DBus",
178 : : "org.freedesktop.DBus",
179 : : "NameOwnerChanged",
180 : : "/org/freedesktop/DBus",
181 : : "org.mpris.MediaPlayer2",
182 : : G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
183 : : on_name_owner_changed,
184 : : self, NULL);
185 : :
186 : :
187 : : /* Report the adapter as active */
188 : 6 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
189 : : VALENT_PLUGIN_STATE_ACTIVE,
190 : : NULL);
191 [ + - ]: 6 : g_task_return_boolean (task, TRUE);
192 : : }
193 : :
194 : : static void
195 : 6 : valent_mpris_adapter_init_async (GAsyncInitable *initable,
196 : : int io_priority,
197 : : GCancellable *cancellable,
198 : : GAsyncReadyCallback callback,
199 : : gpointer user_data)
200 : : {
201 : 6 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (initable);
202 : 6 : g_autoptr (GTask) task = NULL;
203 [ - - ]: 6 : g_autoptr (GError) error = NULL;
204 : :
205 [ + - ]: 6 : g_assert (VALENT_IS_MPRIS_ADAPTER (self));
206 : :
207 : 6 : task = g_task_new (initable, cancellable, callback, user_data);
208 : 6 : g_task_set_priority (task, io_priority);
209 [ + - ]: 6 : g_task_set_source_tag (task, valent_mpris_adapter_init_async);
210 : :
211 : 6 : self->connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
212 : : cancellable,
213 : : &error);
214 : :
215 [ - + ]: 6 : if (self->connection == NULL)
216 : : {
217 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
218 : : VALENT_PLUGIN_STATE_ERROR,
219 : : error);
220 [ # # ]: 0 : return g_task_return_error (task, g_steal_pointer (&error));
221 : : }
222 : :
223 [ - + ]: 6 : g_dbus_connection_call (self->connection,
224 : : "org.freedesktop.DBus",
225 : : "/org/freedesktop/DBus",
226 : : "org.freedesktop.DBus",
227 : : "ListNames",
228 : : NULL,
229 : : NULL,
230 : : G_DBUS_CALL_FLAGS_NONE,
231 : : -1,
232 : : cancellable,
233 : : (GAsyncReadyCallback)list_names_cb,
234 : : g_steal_pointer (&task));
235 : : }
236 : :
237 : : static void
238 : 2 : g_async_initable_iface_init (GAsyncInitableIface *iface)
239 : : {
240 : 2 : iface->init_async = valent_mpris_adapter_init_async;
241 : 2 : }
242 : :
243 : : /*
244 : : * ValentMediaAdapter
245 : : */
246 : : static void
247 : 1 : valent_mpris_impl_export_full_cb (ValentMPRISImpl *impl,
248 : : GAsyncResult *result,
249 : : ValentMPRISAdapter *self)
250 : : {
251 : 2 : g_autoptr (GError) error = NULL;
252 : :
253 [ - + - - ]: 1 : if (!valent_mpris_impl_export_finish (impl, result, &error) &&
254 : 0 : !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
255 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
256 : 1 : }
257 : :
258 : : static void
259 : 1 : valent_mpris_adapter_export_player (ValentMediaAdapter *adapter,
260 : : ValentMediaPlayer *player)
261 : : {
262 : 1 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (adapter);
263 : 0 : g_autoptr (ValentMPRISImpl) impl = NULL;
264 [ + - ]: 1 : g_autoptr (GCancellable) destroy = NULL;
265 [ + - ]: 1 : g_autofree char *bus_name = NULL;
266 : :
267 [ - + ]: 1 : if (g_hash_table_contains (self->exports, player))
268 : 0 : return;
269 : :
270 : 1 : impl = valent_mpris_impl_new (player);
271 : 1 : g_hash_table_insert (self->exports, player, g_object_ref (impl));
272 : :
273 : 1 : bus_name = g_strdup_printf ("%s.Player%u",
274 : : VALENT_MPRIS_DBUS_NAME,
275 : : n_exports++);
276 : 1 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (adapter));
277 : 1 : valent_mpris_impl_export_full (impl,
278 : : bus_name,
279 : : destroy,
280 : : (GAsyncReadyCallback)valent_mpris_impl_export_full_cb,
281 : : self);
282 : : }
283 : :
284 : : static void
285 : 1 : valent_mpris_adapter_unexport_player (ValentMediaAdapter *adapter,
286 : : ValentMediaPlayer *player)
287 : : {
288 : 1 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (adapter);
289 : 1 : g_autoptr (ValentMPRISImpl) impl = NULL;
290 : :
291 [ + - ]: 1 : g_assert (VALENT_IS_MPRIS_ADAPTER (self));
292 [ - + ]: 1 : g_assert (VALENT_IS_MEDIA_PLAYER (player));
293 : :
294 [ - + ]: 1 : if (!g_hash_table_steal_extended (self->exports, player, NULL, (void **)&impl))
295 [ # # ]: 0 : return;
296 : :
297 : 1 : g_signal_handlers_disconnect_by_data (impl, self);
298 [ + - ]: 1 : valent_mpris_impl_unexport (impl);
299 : : }
300 : :
301 : : /*
302 : : * ValentObject
303 : : */
304 : : static void
305 : 10 : valent_mpris_adapter_destroy (ValentObject *object)
306 : : {
307 : 10 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (object);
308 : 10 : GHashTableIter iter;
309 : 10 : ValentMPRISImpl *impl;
310 : :
311 [ + + ]: 10 : if (self->name_owner_changed_id > 0)
312 : : {
313 : 5 : g_dbus_connection_signal_unsubscribe (self->connection,
314 : : self->name_owner_changed_id);
315 : 5 : self->name_owner_changed_id = 0;
316 : : }
317 : :
318 : 10 : g_hash_table_iter_init (&iter, self->exports);
319 : :
320 [ - + ]: 10 : while (g_hash_table_iter_next (&iter, NULL, (void **)&impl))
321 : : {
322 : 0 : g_signal_handlers_disconnect_by_data (impl, self);
323 : 0 : valent_mpris_impl_unexport (impl);
324 : 0 : g_hash_table_iter_remove (&iter);
325 : : }
326 : :
327 : 10 : VALENT_OBJECT_CLASS (valent_mpris_adapter_parent_class)->destroy (object);
328 : 10 : }
329 : :
330 : : /*
331 : : * GObject
332 : : */
333 : : static void
334 : 5 : valent_mpris_adapter_finalize (GObject *object)
335 : : {
336 : 5 : ValentMPRISAdapter *self = VALENT_MPRIS_ADAPTER (object);
337 : :
338 [ + - ]: 5 : g_clear_object (&self->connection);
339 [ + - ]: 5 : g_clear_pointer (&self->players, g_hash_table_unref);
340 [ + - ]: 5 : g_clear_pointer (&self->exports, g_hash_table_unref);
341 : :
342 : 5 : G_OBJECT_CLASS (valent_mpris_adapter_parent_class)->finalize (object);
343 : 5 : }
344 : :
345 : : static void
346 : 2 : valent_mpris_adapter_class_init (ValentMPRISAdapterClass *klass)
347 : : {
348 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
349 : 2 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
350 : 2 : ValentMediaAdapterClass *adapter_class = VALENT_MEDIA_ADAPTER_CLASS (klass);
351 : :
352 : 2 : object_class->finalize = valent_mpris_adapter_finalize;
353 : :
354 : 2 : vobject_class->destroy = valent_mpris_adapter_destroy;
355 : :
356 : 2 : adapter_class->export_player = valent_mpris_adapter_export_player;
357 : 2 : adapter_class->unexport_player = valent_mpris_adapter_unexport_player;
358 : : }
359 : :
360 : : static void
361 : 6 : valent_mpris_adapter_init (ValentMPRISAdapter *self)
362 : : {
363 : 6 : self->exports = g_hash_table_new_full (NULL, NULL,
364 : : NULL, g_object_unref);
365 : 6 : self->players = g_hash_table_new_full (g_str_hash, g_str_equal,
366 : : g_free, g_object_unref);
367 : 6 : }
368 : :
|