Merge lp:~larsu/indicator-sound/use-bus-watch-namespace into lp:indicator-sound/13.10

Proposed by Lars Karlitski
Status: Merged
Approved by: Charles Kerr
Approved revision: 368
Merged at revision: 371
Proposed branch: lp:~larsu/indicator-sound/use-bus-watch-namespace
Merge into: lp:indicator-sound/13.10
Diff against target: 756 lines (+464/-218)
7 files modified
src/CMakeLists.txt (+3/-8)
src/Makefile.am.THIS (+39/-0)
src/bus-watch-namespace.c (+349/-0)
src/bus-watch-namespace.h (+34/-0)
src/media-player-list.vala (+14/-10)
src/mpris2-watcher.vala (+0/-200)
vapi/bus-watcher.vapi (+25/-0)
To merge this branch: bzr merge lp:~larsu/indicator-sound/use-bus-watch-namespace
Reviewer Review Type Date Requested Status
Charles Kerr (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+178246@code.launchpad.net

Description of the change

Use bus_watch_namespace() for more robust monitoring of mpris players appearing or disappearing on the bus.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

bus-watch-namespace is a nice piece of work. I only have three issues -- a bug, a typo, and a question:

 * watcher->name_space is leaked

 * trivial style: ";;" used after g_cancellable_new()

 * the API contract is a little odd in that user_data can be
   destroyed before bus_unwatch_namespace() is called by the client.
   This isn't an issue in i-sound, which doesn't use the argument,
   but was it intentional in the contract?

The changes to media-player-list.vala are straightforward and fine.

review: Needs Fixing
366. By Lars Karlitski

bus-watch-namespace: free name_space

367. By Lars Karlitski

bus-watch-namespace: remove stray semicolon

Revision history for this message
Lars Karlitski (larsu) wrote :

> bus-watch-namespace is a nice piece of work. I only have three issues -- a
> bug, a typo, and a question:
>
> * watcher->name_space is leaked
>
> * trivial style: ";;" used after g_cancellable_new()

Nice catches, thanks! Fixed in revisions 366 and 367.

> * the API contract is a little odd in that user_data can be
> destroyed before bus_unwatch_namespace() is called by the client.
> This isn't an issue in i-sound, which doesn't use the argument,
> but was it intentional in the contract?

Yes, it is intentional. The contract is basically: as long as we call your callbacks, your user data will be alive. Since in some cases (e.g. no bus connection) there's no way those callbacks ever get called, I just free the user data directly.

I should probably document this better, thanks for raising it.

> The changes to media-player-list.vala are straightforward and fine.

:)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
368. By Lars Karlitski

Merge trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 2013-08-16 03:58:55 +0000
+++ src/CMakeLists.txt 2013-08-26 15:12:41 +0000
@@ -20,6 +20,7 @@
20 --vapidir=${CMAKE_SOURCE_DIR}/vapi/20 --vapidir=${CMAKE_SOURCE_DIR}/vapi/
21 --vapidir=.21 --vapidir=.
22 --target-glib=2.3622 --target-glib=2.36
23 --pkg=bus-watcher
23)24)
2425
25vala_add(indicator-sound-service26vala_add(indicator-sound-service
@@ -47,19 +48,12 @@
47 media-player-list.vala48 media-player-list.vala
48 DEPENDS49 DEPENDS
49 media-player50 media-player
50 mpris2-watcher51 mpris2-interfaces
51)52)
52vala_add(indicator-sound-service53vala_add(indicator-sound-service
53 mpris2-interfaces.vala54 mpris2-interfaces.vala
54)55)
55vala_add(indicator-sound-service56vala_add(indicator-sound-service
56 mpris2-watcher.vala
57 DEPENDS
58 media-player
59 mpris2-interfaces
60 freedesktop-interfaces
61)
62vala_add(indicator-sound-service
63 freedesktop-interfaces.vala57 freedesktop-interfaces.vala
64)58)
65vala_add(indicator-sound-service59vala_add(indicator-sound-service
@@ -89,6 +83,7 @@
89 INDICATOR_SOUND_SOURCES83 INDICATOR_SOUND_SOURCES
90 ${project_VALA_SOURCES}84 ${project_VALA_SOURCES}
91 ${project_VALA_C}85 ${project_VALA_C}
86 bus-watch-namespace.c
92 ${SYMBOLS_PATH}87 ${SYMBOLS_PATH}
93)88)
9489
9590
=== added file 'src/Makefile.am.THIS'
--- src/Makefile.am.THIS 1970-01-01 00:00:00 +0000
+++ src/Makefile.am.THIS 2013-08-26 15:12:41 +0000
@@ -0,0 +1,39 @@
1pkglibexec_PROGRAMS = indicator-sound-service
2
3indicator_sound_service_SOURCES = \
4 service.vala \
5 main.vala \
6 volume-control.vala \
7 media-player.vala \
8 media-player-list.vala \
9 mpris2-interfaces.vala \
10 freedesktop-interfaces.vala \
11 sound-menu.vala \
12 bus-watch-namespace.c \
13 bus-watch-namespace.h
14
15indicator_sound_service_VALAFLAGS = \
16 --ccode \
17 --vapidir=$(top_srcdir)/vapi/ \
18 --vapidir=./ \
19 --thread \
20 --pkg config \
21 --pkg gio-2.0 \
22 --pkg gio-unix-2.0 \
23 --pkg libxml-2.0 \
24 --pkg libpulse \
25 --pkg libpulse-mainloop-glib \
26 --pkg bus-watcher \
27 --target-glib=2.36
28
29# -w to disable warnings for vala-generated code
30indicator_sound_service_CFLAGS = $(PULSEAUDIO_CFLAGS) \
31 $(SOUNDSERVICE_CFLAGS) \
32 $(GCONF_CFLAGS) \
33 $(COVERAGE_CFLAGS) \
34 -DLIBEXECDIR=\"$(libexecdir)\" \
35 -w \
36 -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\"
37
38indicator_sound_service_LDADD = $(PULSEAUDIO_LIBS) $(SOUNDSERVICE_LIBS) $(GCONF_LIBS)
39indicator_sound_service_LDFLAGS = $(COVERAGE_LDFLAGS)
040
=== added file 'src/bus-watch-namespace.c'
--- src/bus-watch-namespace.c 1970-01-01 00:00:00 +0000
+++ src/bus-watch-namespace.c 2013-08-26 15:12:41 +0000
@@ -0,0 +1,349 @@
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
18 */
19
20#include <gio/gio.h>
21#include <string.h>
22#include "bus-watch-namespace.h"
23
24typedef struct
25{
26 guint id;
27 gchar *name_space;
28 GBusNameAppearedCallback appeared_handler;
29 GBusNameVanishedCallback vanished_handler;
30 gpointer user_data;
31 GDestroyNotify user_data_destroy;
32
33 GDBusConnection *connection;
34 GCancellable *cancellable;
35 GHashTable *names;
36 guint subscription_id;
37} NamespaceWatcher;
38
39typedef struct
40{
41 NamespaceWatcher *watcher;
42 gchar *name;
43} GetNameOwnerData;
44
45static guint namespace_watcher_next_id;
46static GHashTable *namespace_watcher_watchers;
47
48static void
49namespace_watcher_stop (gpointer data)
50{
51 NamespaceWatcher *watcher = data;
52
53 g_cancellable_cancel (watcher->cancellable);
54 g_object_unref (watcher->cancellable);
55
56 if (watcher->subscription_id)
57 g_dbus_connection_signal_unsubscribe (watcher->connection, watcher->subscription_id);
58
59 if (watcher->vanished_handler)
60 {
61 GHashTableIter it;
62 const gchar *name;
63
64 g_hash_table_iter_init (&it, watcher->names);
65 while (g_hash_table_iter_next (&it, (gpointer *) &name, NULL))
66 watcher->vanished_handler (watcher->connection, name, watcher->user_data);
67 }
68
69 if (watcher->user_data_destroy)
70 watcher->user_data_destroy (watcher->user_data);
71
72 if (watcher->connection)
73 {
74 g_signal_handlers_disconnect_by_func (watcher->connection, namespace_watcher_stop, watcher);
75 g_object_unref (watcher->connection);
76 }
77
78 g_hash_table_unref (watcher->names);
79
80 g_hash_table_remove (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id));
81 if (g_hash_table_size (namespace_watcher_watchers) == 0)
82 g_clear_pointer (&namespace_watcher_watchers, g_hash_table_destroy);
83
84 g_free (watcher->name_space);
85
86 g_free (watcher);
87}
88
89static void
90namespace_watcher_name_appeared (NamespaceWatcher *watcher,
91 const gchar *name,
92 const gchar *owner)
93{
94 /* There's a race between NameOwnerChanged signals arriving and the
95 * ListNames/GetNameOwner sequence returning, so this function might
96 * be called more than once for the same name. To ensure that
97 * appeared_handler is only called once for each name, it is only
98 * called when inserting the name into watcher->names (each name is
99 * only inserted once there).
100 */
101 if (g_hash_table_contains (watcher->names, name))
102 return;
103
104 g_hash_table_add (watcher->names, g_strdup (name));
105
106 if (watcher->appeared_handler)
107 watcher->appeared_handler (watcher->connection, name, owner, watcher->user_data);
108}
109
110static void
111namespace_watcher_name_vanished (NamespaceWatcher *watcher,
112 const gchar *name)
113{
114 if (g_hash_table_remove (watcher->names, name) && watcher->vanished_handler)
115 watcher->vanished_handler (watcher->connection, name, watcher->user_data);
116}
117
118static gboolean
119dbus_name_has_namespace (const gchar *name,
120 const gchar *name_space)
121{
122 gint len_name;
123 gint len_namespace;
124
125 len_name = strlen (name);
126 len_namespace = strlen (name_space);
127
128 if (len_name < len_namespace)
129 return FALSE;
130
131 if (memcmp (name_space, name, len_namespace) != 0)
132 return FALSE;
133
134 return len_namespace == len_name || name[len_namespace] == '.';
135}
136
137static void
138name_owner_changed (GDBusConnection *connection,
139 const gchar *sender_name,
140 const gchar *object_path,
141 const gchar *interface_name,
142 const gchar *signal_name,
143 GVariant *parameters,
144 gpointer user_data)
145{
146 NamespaceWatcher *watcher = user_data;
147 const gchar *name;
148 const gchar *old_owner;
149 const gchar *new_owner;
150
151 g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
152
153 if (old_owner[0] != '\0')
154 namespace_watcher_name_vanished (watcher, name);
155
156 if (new_owner[0] != '\0')
157 namespace_watcher_name_appeared (watcher, name, new_owner);
158}
159
160static void
161got_name_owner (GObject *object,
162 GAsyncResult *result,
163 gpointer user_data)
164{
165 GetNameOwnerData *data = user_data;
166 GError *error = NULL;
167 GVariant *reply;
168 const gchar *owner;
169
170 reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
171
172 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
173 {
174 g_error_free (error);
175 goto out;
176 }
177
178 if (reply == NULL)
179 {
180 if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
181 g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.GetNameOwner: %s", error->message);
182 g_error_free (error);
183 goto out;
184 }
185
186 g_variant_get (reply, "(&s)", &owner);
187 namespace_watcher_name_appeared (data->watcher, data->name, owner);
188
189 g_variant_unref (reply);
190
191out:
192 g_free (data->name);
193 g_slice_free (GetNameOwnerData, data);
194}
195
196static void
197names_listed (GObject *object,
198 GAsyncResult *result,
199 gpointer user_data)
200{
201 NamespaceWatcher *watcher;
202 GError *error = NULL;
203 GVariant *reply;
204 GVariantIter *iter;
205 const gchar *name;
206
207 reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
208
209 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
210 {
211 g_error_free (error);
212 return;
213 }
214
215 watcher = user_data;
216
217 if (reply == NULL)
218 {
219 g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.ListNames: %s", error->message);
220 g_error_free (error);
221 return;
222 }
223
224 g_variant_get (reply, "(as)", &iter);
225 while (g_variant_iter_next (iter, "&s", &name))
226 {
227 if (dbus_name_has_namespace (name, watcher->name_space))
228 {
229 GetNameOwnerData *data = g_slice_new (GetNameOwnerData);
230 data->watcher = watcher;
231 data->name = g_strdup (name);
232 g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/",
233 "org.freedesktop.DBus", "GetNameOwner",
234 g_variant_new ("(s)", name), G_VARIANT_TYPE ("(s)"),
235 G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable,
236 got_name_owner, data);
237 }
238 }
239
240 g_variant_iter_free (iter);
241 g_variant_unref (reply);
242}
243
244static void
245connection_closed (GDBusConnection *connection,
246 gboolean remote_peer_vanished,
247 GError *error,
248 gpointer user_data)
249{
250 NamespaceWatcher *watcher = user_data;
251
252 namespace_watcher_stop (watcher);
253}
254
255static void
256got_bus (GObject *object,
257 GAsyncResult *result,
258 gpointer user_data)
259{
260 GDBusConnection *connection;
261 NamespaceWatcher *watcher;
262 GError *error = NULL;
263
264 connection = g_bus_get_finish (result, &error);
265
266 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
267 {
268 g_error_free (error);
269 return;
270 }
271
272 watcher = user_data;
273
274 if (connection == NULL)
275 {
276 namespace_watcher_stop (watcher);
277 return;
278 }
279
280 watcher->connection = connection;
281 g_signal_connect (watcher->connection, "closed", G_CALLBACK (connection_closed), watcher);
282
283 watcher->subscription_id =
284 g_dbus_connection_signal_subscribe (watcher->connection, "org.freedesktop.DBus",
285 "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus",
286 watcher->name_space, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
287 name_owner_changed, watcher, NULL);
288
289 g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/",
290 "org.freedesktop.DBus", "ListNames", NULL, G_VARIANT_TYPE ("(as)"),
291 G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable,
292 names_listed, watcher);
293}
294
295guint
296bus_watch_namespace (GBusType bus_type,
297 const gchar *name_space,
298 GBusNameAppearedCallback appeared_handler,
299 GBusNameVanishedCallback vanished_handler,
300 gpointer user_data,
301 GDestroyNotify user_data_destroy)
302{
303 NamespaceWatcher *watcher;
304
305 /* same rules for interfaces and well-known names */
306 g_return_val_if_fail (name_space != NULL && g_dbus_is_interface_name (name_space), 0);
307 g_return_val_if_fail (appeared_handler || vanished_handler, 0);
308
309 watcher = g_new0 (NamespaceWatcher, 1);
310 watcher->id = namespace_watcher_next_id++;
311 watcher->name_space = g_strdup (name_space);
312 watcher->appeared_handler = appeared_handler;
313 watcher->vanished_handler = vanished_handler;
314 watcher->user_data = user_data;
315 watcher->user_data_destroy = user_data_destroy;
316 watcher->cancellable = g_cancellable_new ();
317 watcher->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
318
319 if (namespace_watcher_watchers == NULL)
320 namespace_watcher_watchers = g_hash_table_new (g_direct_hash, g_direct_equal);
321 g_hash_table_insert (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id), watcher);
322
323 g_bus_get (bus_type, watcher->cancellable, got_bus, watcher);
324
325 return watcher->id;
326}
327
328void
329bus_unwatch_namespace (guint id)
330{
331 /* namespace_watcher_stop() might have already removed the watcher
332 * with @id in the case of a connection error. Thus, this function
333 * doesn't warn when @id is absent from the hash table.
334 */
335
336 if (namespace_watcher_watchers)
337 {
338 NamespaceWatcher *watcher;
339
340 watcher = g_hash_table_lookup (namespace_watcher_watchers, GUINT_TO_POINTER (id));
341 if (watcher)
342 {
343 /* make sure vanished() is not called as a result of this function */
344 g_hash_table_remove_all (watcher->names);
345
346 namespace_watcher_stop (watcher);
347 }
348 }
349}
0350
=== added file 'src/bus-watch-namespace.h'
--- src/bus-watch-namespace.h 1970-01-01 00:00:00 +0000
+++ src/bus-watch-namespace.h 2013-08-26 15:12:41 +0000
@@ -0,0 +1,34 @@
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
18 */
19
20#ifndef __BUS_WATCH_NAMESPACE_H__
21#define __BUS_WATCH_NAMESPACE_H__
22
23#include <gio/gio.h>
24
25guint bus_watch_namespace (GBusType bus_type,
26 const gchar *name_space,
27 GBusNameAppearedCallback appeared_handler,
28 GBusNameVanishedCallback vanished_handler,
29 gpointer user_data,
30 GDestroyNotify user_data_destroy);
31
32void bus_unwatch_namespace (guint id);
33
34#endif
035
=== modified file 'src/media-player-list.vala'
--- src/media-player-list.vala 2013-06-28 19:58:32 +0000
+++ src/media-player-list.vala 2013-08-26 15:12:41 +0000
@@ -26,9 +26,7 @@
26 public MediaPlayerList () {26 public MediaPlayerList () {
27 this._players = new HashTable<string, MediaPlayer> (str_hash, str_equal);27 this._players = new HashTable<string, MediaPlayer> (str_hash, str_equal);
2828
29 this.mpris_watcher = new Mpris2Watcher ();29 BusWatcher.watch_namespace (BusType.SESSION, "org.mpris.MediaPlayer2", this.player_appeared, this.player_disappeared);
30 this.mpris_watcher.client_appeared.connect (this.player_appeared);
31 this.mpris_watcher.client_disappeared.connect (this.player_disappeared);
32 }30 }
3331
34 /* only valid while the list is not changed */32 /* only valid while the list is not changed */
@@ -113,15 +111,21 @@
113 public signal void player_removed (MediaPlayer player);111 public signal void player_removed (MediaPlayer player);
114112
115 HashTable<string, MediaPlayer> _players;113 HashTable<string, MediaPlayer> _players;
116 Mpris2Watcher mpris_watcher;114
117115 void player_appeared (DBusConnection connection, string name, string owner) {
118 void player_appeared (string desktop_id, string dbus_name, bool use_playlists) {116 try {
119 var player = this.insert (desktop_id);117 MprisRoot mpris2_root = Bus.get_proxy_sync (BusType.SESSION, name, MPRIS_MEDIA_PLAYER_PATH);
120 if (player != null)118
121 player.attach (dbus_name);119 var player = this.insert (mpris2_root.DesktopEntry);
120 if (player != null)
121 player.attach (name);
122 }
123 catch (Error e) {
124 warning ("unable to create mpris proxy for '%s': %s", name, e.message);
125 }
122 }126 }
123127
124 void player_disappeared (string dbus_name) {128 void player_disappeared (DBusConnection connection, string dbus_name) {
125 MediaPlayer? player = this._players.find ( (name, player) => {129 MediaPlayer? player = this._players.find ( (name, player) => {
126 return player.dbus_name == dbus_name;130 return player.dbus_name == dbus_name;
127 });131 });
128132
=== removed file 'src/mpris2-watcher.vala'
--- src/mpris2-watcher.vala 2013-06-28 19:58:32 +0000
+++ src/mpris2-watcher.vala 1970-01-01 00:00:00 +0000
@@ -1,200 +0,0 @@
1/*
2Copyright 2010 Canonical Ltd.
3
4Authors:
5 Conor Curran <conor.curran@canonical.com>
6
7This program is free software: you can redistribute it and/or modify it
8under the terms of the GNU General Public License version 3, as published
9by the Free Software Foundation.
10
11This program is distributed in the hope that it will be useful, but
12WITHOUT ANY WARRANTY; without even the implied warranties of
13MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14PURPOSE. See the GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License along
17with this program. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20using Xml;
21
22public class Mpris2Watcher : GLib.Object
23{
24 DBusConnection session_bus;
25
26 public signal void client_appeared ( string desktop_file_name,
27 string dbus_name,
28 bool use_playlists );
29 public signal void client_disappeared ( string dbus_name );
30
31 public Mpris2Watcher ()
32 {
33 }
34
35 construct
36 {
37 const string match_rule = "type='signal'," +
38 "sender='org.freedesktop.DBus'," +
39 "interface='org.freedesktop.DBus'," +
40 "member='NameOwnerChanged'," +
41 "path='/org/freedesktop/DBus'," +
42 "arg0namespace='org.mpris.MediaPlayer2'";
43 try {
44 this.session_bus = Bus.get_sync (BusType.SESSION);
45
46 this.session_bus.call_sync ("org.freedesktop.DBus",
47 "/",
48 "org.freedesktop.DBus",
49 "AddMatch",
50 new Variant ("(s)", match_rule),
51 VariantType.TUPLE,
52 DBusCallFlags.NONE,
53 -1);
54
55 this.session_bus.signal_subscribe ("org.freedesktop.DBus",
56 "org.freedesktop.DBus",
57 "NameOwnerChanged",
58 "/org/freedesktop/DBus",
59 null,
60 DBusSignalFlags.NO_MATCH_RULE,
61 this.name_owner_changed);
62
63 this.check_for_active_clients.begin();
64 }
65 catch (GLib.Error e) {
66 warning ("unable to set up name watch for mrpis clients: %s", e.message);
67 }
68 }
69
70 // At startup check to see if there are clients up that we are interested in
71 // More relevant for development and daemon's like mpd.
72 async void check_for_active_clients()
73 {
74 Variant interfaces;
75
76 try {
77 interfaces = yield this.session_bus.call ("org.freedesktop.DBus",
78 "/",
79 "org.freedesktop.DBus",
80 "ListNames",
81 null,
82 new VariantType ("(as)"),
83 DBusCallFlags.NONE,
84 -1);
85 }
86 catch (GLib.Error e) {
87 warning ("unable to search for existing mpris clients: %s ", e.message);
88 return;
89 }
90
91 foreach (var val in interfaces.get_child_value (0)) {
92 var address = (string) val;
93 if (address.has_prefix (MPRIS_PREFIX)){
94 MprisRoot? mpris2_root = this.create_mpris_root(address);
95 if (mpris2_root == null) return;
96 bool use_playlists = this.supports_playlists ( address );
97 client_appeared (mpris2_root.DesktopEntry + ".desktop", address, use_playlists);
98 }
99 }
100 }
101
102 void name_owner_changed (DBusConnection con, string sender, string object_path,
103 string interface_name, string signal_name, Variant parameters)
104 {
105 string name, previous_owner, current_owner;
106
107 parameters.get ("(sss)", out name, out previous_owner, out current_owner);
108
109 MprisRoot? mpris2_root = this.create_mpris_root (name);
110 if (mpris2_root == null) return;
111
112 if (previous_owner != "" && current_owner == "") {
113 debug ("Client '%s' gone down", name);
114 client_disappeared (name);
115 }
116 else if (previous_owner == "" && current_owner != "") {
117 debug ("Client '%s' has appeared", name);
118 bool use_playlists = this.supports_playlists ( name );
119 client_appeared (mpris2_root.DesktopEntry + ".desktop", name, use_playlists);
120 }
121 }
122
123 private MprisRoot? create_mpris_root ( string name ){
124 MprisRoot mpris2_root = null;
125 if ( name.has_prefix (MPRIS_PREFIX) ){
126 try {
127 mpris2_root = Bus.get_proxy_sync ( BusType.SESSION,
128 name,
129 MPRIS_MEDIA_PLAYER_PATH );
130 }
131 catch (IOError e){
132 warning( "Mpris2watcher could not create a root interface: %s",
133 e.message );
134 }
135 }
136 return mpris2_root;
137 }
138
139 private bool supports_playlists ( string name )
140 {
141 FreeDesktopIntrospectable introspectable;
142
143 try {
144 /* The dbusproxy flag parameter is needed to ensure Banshee does not
145 blow up. I suspect the issue is that if you
146 try to instantiate a dbus object which does not have any properties
147 associated with it, gdbus will attempt to fetch the properties (this is
148 in the documentation) but the banshee mpris dbus object more than likely
149 causes a crash because it doesn't check for the presence of properties
150 before attempting to access them.
151 */
152 introspectable = Bus.get_proxy_sync ( BusType.SESSION,
153 name,
154 MPRIS_MEDIA_PLAYER_PATH,
155 GLib.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES);
156 var results = introspectable.Introspect();
157 return this.parse_interfaces (results);
158 }
159 catch (IOError e){
160 warning( "Could not create an introspectable object: %s",
161 e.message );
162 }
163 return false;
164 }
165
166 private bool parse_interfaces( string interface_info )
167 {
168 //parse the document from path
169 bool result = false;
170 Xml.Doc* xml_doc = Parser.parse_doc (interface_info);
171 if (xml_doc == null) {
172 warning ("Mpris2Watcher - parse-interfaces - failed to instantiate xml doc");
173 return false;
174 }
175 //get the root node. notice the dereferencing operator -> instead of .
176 Xml.Node* root_node = xml_doc->get_root_element ();
177 if (root_node == null) {
178 //free the document manually before throwing because the garbage collector can't work on pointers
179 delete xml_doc;
180 warning ("Mpris2Watcher - the interface info xml is empty");
181 return false;
182 }
183
184 //let's parse those nodes
185 for (Xml.Node* iter = root_node->children; iter != null; iter = iter->next) {
186 //spaces btw. tags are also nodes, discard them
187 if (iter->type != ElementType.ELEMENT_NODE){
188 continue;
189 }
190 Xml.Attr* attributes = iter->properties; //get the node's name
191 string interface_name = attributes->children->content;
192 debug ( "this dbus object has interface %s ", interface_name );
193 if ( interface_name == MPRIS_PREFIX.concat("Playlists")){
194 result = true;
195 }
196 }
197 delete xml_doc;
198 return result;
199 }
200}
2010
=== added file 'vapi/bus-watcher.vapi'
--- vapi/bus-watcher.vapi 1970-01-01 00:00:00 +0000
+++ vapi/bus-watcher.vapi 2013-08-26 15:12:41 +0000
@@ -0,0 +1,25 @@
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Lars Uebernickel <lars.uebernickel@canonical.com>
18 */
19
20namespace BusWatcher {
21 [CCode (cheader_filename = "bus-watch-namespace.h", cname = "bus_watch_namespace")]
22 public static uint watch_namespace (GLib.BusType bus_type, string name_space,
23 [CCode (delegate_target_pos = 4.9)] owned GLib.BusNameAppearedCallback? name_appeared,
24 [CCode (delegate_target_pos = 4.9)] owned GLib.BusNameVanishedCallback? name_vanished);
25}

Subscribers

People subscribed via source and target branches