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