Merge lp:~larsu/indicator-sound/use-bus-watch-namespace into lp:indicator-sound/13.10
- use-bus-watch-namespace
- Merge into trunk.13.10
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 |
Related bugs: |
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 |
Commit message
Description of the change
Use bus_watch_
PS Jenkins bot (ps-jenkins) wrote : | # |
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_
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-
- 366. By Lars Karlitski
-
bus-watch-
namespace: free name_space - 367. By Lars Karlitski
-
bus-watch-
namespace: remove stray semicolon
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_
> 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-
:)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:367
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 368. By Lars Karlitski
-
Merge trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:368
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Charles Kerr (charlesk) : | # |
Preview Diff
1 | === modified file 'src/CMakeLists.txt' | |||
2 | --- src/CMakeLists.txt 2013-08-16 03:58:55 +0000 | |||
3 | +++ src/CMakeLists.txt 2013-08-26 15:12:41 +0000 | |||
4 | @@ -20,6 +20,7 @@ | |||
5 | 20 | --vapidir=${CMAKE_SOURCE_DIR}/vapi/ | 20 | --vapidir=${CMAKE_SOURCE_DIR}/vapi/ |
6 | 21 | --vapidir=. | 21 | --vapidir=. |
7 | 22 | --target-glib=2.36 | 22 | --target-glib=2.36 |
8 | 23 | --pkg=bus-watcher | ||
9 | 23 | ) | 24 | ) |
10 | 24 | 25 | ||
11 | 25 | vala_add(indicator-sound-service | 26 | vala_add(indicator-sound-service |
12 | @@ -47,19 +48,12 @@ | |||
13 | 47 | media-player-list.vala | 48 | media-player-list.vala |
14 | 48 | DEPENDS | 49 | DEPENDS |
15 | 49 | media-player | 50 | media-player |
17 | 50 | mpris2-watcher | 51 | mpris2-interfaces |
18 | 51 | ) | 52 | ) |
19 | 52 | vala_add(indicator-sound-service | 53 | vala_add(indicator-sound-service |
20 | 53 | mpris2-interfaces.vala | 54 | mpris2-interfaces.vala |
21 | 54 | ) | 55 | ) |
22 | 55 | vala_add(indicator-sound-service | 56 | vala_add(indicator-sound-service |
23 | 56 | mpris2-watcher.vala | ||
24 | 57 | DEPENDS | ||
25 | 58 | media-player | ||
26 | 59 | mpris2-interfaces | ||
27 | 60 | freedesktop-interfaces | ||
28 | 61 | ) | ||
29 | 62 | vala_add(indicator-sound-service | ||
30 | 63 | freedesktop-interfaces.vala | 57 | freedesktop-interfaces.vala |
31 | 64 | ) | 58 | ) |
32 | 65 | vala_add(indicator-sound-service | 59 | vala_add(indicator-sound-service |
33 | @@ -89,6 +83,7 @@ | |||
34 | 89 | INDICATOR_SOUND_SOURCES | 83 | INDICATOR_SOUND_SOURCES |
35 | 90 | ${project_VALA_SOURCES} | 84 | ${project_VALA_SOURCES} |
36 | 91 | ${project_VALA_C} | 85 | ${project_VALA_C} |
37 | 86 | bus-watch-namespace.c | ||
38 | 92 | ${SYMBOLS_PATH} | 87 | ${SYMBOLS_PATH} |
39 | 93 | ) | 88 | ) |
40 | 94 | 89 | ||
41 | 95 | 90 | ||
42 | === added file 'src/Makefile.am.THIS' | |||
43 | --- src/Makefile.am.THIS 1970-01-01 00:00:00 +0000 | |||
44 | +++ src/Makefile.am.THIS 2013-08-26 15:12:41 +0000 | |||
45 | @@ -0,0 +1,39 @@ | |||
46 | 1 | pkglibexec_PROGRAMS = indicator-sound-service | ||
47 | 2 | |||
48 | 3 | indicator_sound_service_SOURCES = \ | ||
49 | 4 | service.vala \ | ||
50 | 5 | main.vala \ | ||
51 | 6 | volume-control.vala \ | ||
52 | 7 | media-player.vala \ | ||
53 | 8 | media-player-list.vala \ | ||
54 | 9 | mpris2-interfaces.vala \ | ||
55 | 10 | freedesktop-interfaces.vala \ | ||
56 | 11 | sound-menu.vala \ | ||
57 | 12 | bus-watch-namespace.c \ | ||
58 | 13 | bus-watch-namespace.h | ||
59 | 14 | |||
60 | 15 | indicator_sound_service_VALAFLAGS = \ | ||
61 | 16 | --ccode \ | ||
62 | 17 | --vapidir=$(top_srcdir)/vapi/ \ | ||
63 | 18 | --vapidir=./ \ | ||
64 | 19 | --thread \ | ||
65 | 20 | --pkg config \ | ||
66 | 21 | --pkg gio-2.0 \ | ||
67 | 22 | --pkg gio-unix-2.0 \ | ||
68 | 23 | --pkg libxml-2.0 \ | ||
69 | 24 | --pkg libpulse \ | ||
70 | 25 | --pkg libpulse-mainloop-glib \ | ||
71 | 26 | --pkg bus-watcher \ | ||
72 | 27 | --target-glib=2.36 | ||
73 | 28 | |||
74 | 29 | # -w to disable warnings for vala-generated code | ||
75 | 30 | indicator_sound_service_CFLAGS = $(PULSEAUDIO_CFLAGS) \ | ||
76 | 31 | $(SOUNDSERVICE_CFLAGS) \ | ||
77 | 32 | $(GCONF_CFLAGS) \ | ||
78 | 33 | $(COVERAGE_CFLAGS) \ | ||
79 | 34 | -DLIBEXECDIR=\"$(libexecdir)\" \ | ||
80 | 35 | -w \ | ||
81 | 36 | -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" | ||
82 | 37 | |||
83 | 38 | indicator_sound_service_LDADD = $(PULSEAUDIO_LIBS) $(SOUNDSERVICE_LIBS) $(GCONF_LIBS) | ||
84 | 39 | indicator_sound_service_LDFLAGS = $(COVERAGE_LDFLAGS) | ||
85 | 0 | 40 | ||
86 | === added file 'src/bus-watch-namespace.c' | |||
87 | --- src/bus-watch-namespace.c 1970-01-01 00:00:00 +0000 | |||
88 | +++ src/bus-watch-namespace.c 2013-08-26 15:12:41 +0000 | |||
89 | @@ -0,0 +1,349 @@ | |||
90 | 1 | /* | ||
91 | 2 | * Copyright 2013 Canonical Ltd. | ||
92 | 3 | * | ||
93 | 4 | * This program is free software; you can redistribute it and/or modify | ||
94 | 5 | * it under the terms of the GNU Lesser General Public License as | ||
95 | 6 | * published by the Free Software Foundation; either version 2 of the | ||
96 | 7 | * License, or (at your option) any later version. | ||
97 | 8 | * | ||
98 | 9 | * This program is distributed in the hope that it will be useful, but | ||
99 | 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
100 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
101 | 12 | * Lesser General Public License for more details. | ||
102 | 13 | * | ||
103 | 14 | * You should have received a copy of the GNU Lesser General Public License | ||
104 | 15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
105 | 16 | * | ||
106 | 17 | * Author: Lars Uebernickel <lars.uebernickel@canonical.com> | ||
107 | 18 | */ | ||
108 | 19 | |||
109 | 20 | #include <gio/gio.h> | ||
110 | 21 | #include <string.h> | ||
111 | 22 | #include "bus-watch-namespace.h" | ||
112 | 23 | |||
113 | 24 | typedef struct | ||
114 | 25 | { | ||
115 | 26 | guint id; | ||
116 | 27 | gchar *name_space; | ||
117 | 28 | GBusNameAppearedCallback appeared_handler; | ||
118 | 29 | GBusNameVanishedCallback vanished_handler; | ||
119 | 30 | gpointer user_data; | ||
120 | 31 | GDestroyNotify user_data_destroy; | ||
121 | 32 | |||
122 | 33 | GDBusConnection *connection; | ||
123 | 34 | GCancellable *cancellable; | ||
124 | 35 | GHashTable *names; | ||
125 | 36 | guint subscription_id; | ||
126 | 37 | } NamespaceWatcher; | ||
127 | 38 | |||
128 | 39 | typedef struct | ||
129 | 40 | { | ||
130 | 41 | NamespaceWatcher *watcher; | ||
131 | 42 | gchar *name; | ||
132 | 43 | } GetNameOwnerData; | ||
133 | 44 | |||
134 | 45 | static guint namespace_watcher_next_id; | ||
135 | 46 | static GHashTable *namespace_watcher_watchers; | ||
136 | 47 | |||
137 | 48 | static void | ||
138 | 49 | namespace_watcher_stop (gpointer data) | ||
139 | 50 | { | ||
140 | 51 | NamespaceWatcher *watcher = data; | ||
141 | 52 | |||
142 | 53 | g_cancellable_cancel (watcher->cancellable); | ||
143 | 54 | g_object_unref (watcher->cancellable); | ||
144 | 55 | |||
145 | 56 | if (watcher->subscription_id) | ||
146 | 57 | g_dbus_connection_signal_unsubscribe (watcher->connection, watcher->subscription_id); | ||
147 | 58 | |||
148 | 59 | if (watcher->vanished_handler) | ||
149 | 60 | { | ||
150 | 61 | GHashTableIter it; | ||
151 | 62 | const gchar *name; | ||
152 | 63 | |||
153 | 64 | g_hash_table_iter_init (&it, watcher->names); | ||
154 | 65 | while (g_hash_table_iter_next (&it, (gpointer *) &name, NULL)) | ||
155 | 66 | watcher->vanished_handler (watcher->connection, name, watcher->user_data); | ||
156 | 67 | } | ||
157 | 68 | |||
158 | 69 | if (watcher->user_data_destroy) | ||
159 | 70 | watcher->user_data_destroy (watcher->user_data); | ||
160 | 71 | |||
161 | 72 | if (watcher->connection) | ||
162 | 73 | { | ||
163 | 74 | g_signal_handlers_disconnect_by_func (watcher->connection, namespace_watcher_stop, watcher); | ||
164 | 75 | g_object_unref (watcher->connection); | ||
165 | 76 | } | ||
166 | 77 | |||
167 | 78 | g_hash_table_unref (watcher->names); | ||
168 | 79 | |||
169 | 80 | g_hash_table_remove (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id)); | ||
170 | 81 | if (g_hash_table_size (namespace_watcher_watchers) == 0) | ||
171 | 82 | g_clear_pointer (&namespace_watcher_watchers, g_hash_table_destroy); | ||
172 | 83 | |||
173 | 84 | g_free (watcher->name_space); | ||
174 | 85 | |||
175 | 86 | g_free (watcher); | ||
176 | 87 | } | ||
177 | 88 | |||
178 | 89 | static void | ||
179 | 90 | namespace_watcher_name_appeared (NamespaceWatcher *watcher, | ||
180 | 91 | const gchar *name, | ||
181 | 92 | const gchar *owner) | ||
182 | 93 | { | ||
183 | 94 | /* There's a race between NameOwnerChanged signals arriving and the | ||
184 | 95 | * ListNames/GetNameOwner sequence returning, so this function might | ||
185 | 96 | * be called more than once for the same name. To ensure that | ||
186 | 97 | * appeared_handler is only called once for each name, it is only | ||
187 | 98 | * called when inserting the name into watcher->names (each name is | ||
188 | 99 | * only inserted once there). | ||
189 | 100 | */ | ||
190 | 101 | if (g_hash_table_contains (watcher->names, name)) | ||
191 | 102 | return; | ||
192 | 103 | |||
193 | 104 | g_hash_table_add (watcher->names, g_strdup (name)); | ||
194 | 105 | |||
195 | 106 | if (watcher->appeared_handler) | ||
196 | 107 | watcher->appeared_handler (watcher->connection, name, owner, watcher->user_data); | ||
197 | 108 | } | ||
198 | 109 | |||
199 | 110 | static void | ||
200 | 111 | namespace_watcher_name_vanished (NamespaceWatcher *watcher, | ||
201 | 112 | const gchar *name) | ||
202 | 113 | { | ||
203 | 114 | if (g_hash_table_remove (watcher->names, name) && watcher->vanished_handler) | ||
204 | 115 | watcher->vanished_handler (watcher->connection, name, watcher->user_data); | ||
205 | 116 | } | ||
206 | 117 | |||
207 | 118 | static gboolean | ||
208 | 119 | dbus_name_has_namespace (const gchar *name, | ||
209 | 120 | const gchar *name_space) | ||
210 | 121 | { | ||
211 | 122 | gint len_name; | ||
212 | 123 | gint len_namespace; | ||
213 | 124 | |||
214 | 125 | len_name = strlen (name); | ||
215 | 126 | len_namespace = strlen (name_space); | ||
216 | 127 | |||
217 | 128 | if (len_name < len_namespace) | ||
218 | 129 | return FALSE; | ||
219 | 130 | |||
220 | 131 | if (memcmp (name_space, name, len_namespace) != 0) | ||
221 | 132 | return FALSE; | ||
222 | 133 | |||
223 | 134 | return len_namespace == len_name || name[len_namespace] == '.'; | ||
224 | 135 | } | ||
225 | 136 | |||
226 | 137 | static void | ||
227 | 138 | name_owner_changed (GDBusConnection *connection, | ||
228 | 139 | const gchar *sender_name, | ||
229 | 140 | const gchar *object_path, | ||
230 | 141 | const gchar *interface_name, | ||
231 | 142 | const gchar *signal_name, | ||
232 | 143 | GVariant *parameters, | ||
233 | 144 | gpointer user_data) | ||
234 | 145 | { | ||
235 | 146 | NamespaceWatcher *watcher = user_data; | ||
236 | 147 | const gchar *name; | ||
237 | 148 | const gchar *old_owner; | ||
238 | 149 | const gchar *new_owner; | ||
239 | 150 | |||
240 | 151 | g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner); | ||
241 | 152 | |||
242 | 153 | if (old_owner[0] != '\0') | ||
243 | 154 | namespace_watcher_name_vanished (watcher, name); | ||
244 | 155 | |||
245 | 156 | if (new_owner[0] != '\0') | ||
246 | 157 | namespace_watcher_name_appeared (watcher, name, new_owner); | ||
247 | 158 | } | ||
248 | 159 | |||
249 | 160 | static void | ||
250 | 161 | got_name_owner (GObject *object, | ||
251 | 162 | GAsyncResult *result, | ||
252 | 163 | gpointer user_data) | ||
253 | 164 | { | ||
254 | 165 | GetNameOwnerData *data = user_data; | ||
255 | 166 | GError *error = NULL; | ||
256 | 167 | GVariant *reply; | ||
257 | 168 | const gchar *owner; | ||
258 | 169 | |||
259 | 170 | reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); | ||
260 | 171 | |||
261 | 172 | if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) | ||
262 | 173 | { | ||
263 | 174 | g_error_free (error); | ||
264 | 175 | goto out; | ||
265 | 176 | } | ||
266 | 177 | |||
267 | 178 | if (reply == NULL) | ||
268 | 179 | { | ||
269 | 180 | if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER)) | ||
270 | 181 | g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.GetNameOwner: %s", error->message); | ||
271 | 182 | g_error_free (error); | ||
272 | 183 | goto out; | ||
273 | 184 | } | ||
274 | 185 | |||
275 | 186 | g_variant_get (reply, "(&s)", &owner); | ||
276 | 187 | namespace_watcher_name_appeared (data->watcher, data->name, owner); | ||
277 | 188 | |||
278 | 189 | g_variant_unref (reply); | ||
279 | 190 | |||
280 | 191 | out: | ||
281 | 192 | g_free (data->name); | ||
282 | 193 | g_slice_free (GetNameOwnerData, data); | ||
283 | 194 | } | ||
284 | 195 | |||
285 | 196 | static void | ||
286 | 197 | names_listed (GObject *object, | ||
287 | 198 | GAsyncResult *result, | ||
288 | 199 | gpointer user_data) | ||
289 | 200 | { | ||
290 | 201 | NamespaceWatcher *watcher; | ||
291 | 202 | GError *error = NULL; | ||
292 | 203 | GVariant *reply; | ||
293 | 204 | GVariantIter *iter; | ||
294 | 205 | const gchar *name; | ||
295 | 206 | |||
296 | 207 | reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); | ||
297 | 208 | |||
298 | 209 | if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) | ||
299 | 210 | { | ||
300 | 211 | g_error_free (error); | ||
301 | 212 | return; | ||
302 | 213 | } | ||
303 | 214 | |||
304 | 215 | watcher = user_data; | ||
305 | 216 | |||
306 | 217 | if (reply == NULL) | ||
307 | 218 | { | ||
308 | 219 | g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.ListNames: %s", error->message); | ||
309 | 220 | g_error_free (error); | ||
310 | 221 | return; | ||
311 | 222 | } | ||
312 | 223 | |||
313 | 224 | g_variant_get (reply, "(as)", &iter); | ||
314 | 225 | while (g_variant_iter_next (iter, "&s", &name)) | ||
315 | 226 | { | ||
316 | 227 | if (dbus_name_has_namespace (name, watcher->name_space)) | ||
317 | 228 | { | ||
318 | 229 | GetNameOwnerData *data = g_slice_new (GetNameOwnerData); | ||
319 | 230 | data->watcher = watcher; | ||
320 | 231 | data->name = g_strdup (name); | ||
321 | 232 | g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/", | ||
322 | 233 | "org.freedesktop.DBus", "GetNameOwner", | ||
323 | 234 | g_variant_new ("(s)", name), G_VARIANT_TYPE ("(s)"), | ||
324 | 235 | G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable, | ||
325 | 236 | got_name_owner, data); | ||
326 | 237 | } | ||
327 | 238 | } | ||
328 | 239 | |||
329 | 240 | g_variant_iter_free (iter); | ||
330 | 241 | g_variant_unref (reply); | ||
331 | 242 | } | ||
332 | 243 | |||
333 | 244 | static void | ||
334 | 245 | connection_closed (GDBusConnection *connection, | ||
335 | 246 | gboolean remote_peer_vanished, | ||
336 | 247 | GError *error, | ||
337 | 248 | gpointer user_data) | ||
338 | 249 | { | ||
339 | 250 | NamespaceWatcher *watcher = user_data; | ||
340 | 251 | |||
341 | 252 | namespace_watcher_stop (watcher); | ||
342 | 253 | } | ||
343 | 254 | |||
344 | 255 | static void | ||
345 | 256 | got_bus (GObject *object, | ||
346 | 257 | GAsyncResult *result, | ||
347 | 258 | gpointer user_data) | ||
348 | 259 | { | ||
349 | 260 | GDBusConnection *connection; | ||
350 | 261 | NamespaceWatcher *watcher; | ||
351 | 262 | GError *error = NULL; | ||
352 | 263 | |||
353 | 264 | connection = g_bus_get_finish (result, &error); | ||
354 | 265 | |||
355 | 266 | if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) | ||
356 | 267 | { | ||
357 | 268 | g_error_free (error); | ||
358 | 269 | return; | ||
359 | 270 | } | ||
360 | 271 | |||
361 | 272 | watcher = user_data; | ||
362 | 273 | |||
363 | 274 | if (connection == NULL) | ||
364 | 275 | { | ||
365 | 276 | namespace_watcher_stop (watcher); | ||
366 | 277 | return; | ||
367 | 278 | } | ||
368 | 279 | |||
369 | 280 | watcher->connection = connection; | ||
370 | 281 | g_signal_connect (watcher->connection, "closed", G_CALLBACK (connection_closed), watcher); | ||
371 | 282 | |||
372 | 283 | watcher->subscription_id = | ||
373 | 284 | g_dbus_connection_signal_subscribe (watcher->connection, "org.freedesktop.DBus", | ||
374 | 285 | "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus", | ||
375 | 286 | watcher->name_space, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, | ||
376 | 287 | name_owner_changed, watcher, NULL); | ||
377 | 288 | |||
378 | 289 | g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/", | ||
379 | 290 | "org.freedesktop.DBus", "ListNames", NULL, G_VARIANT_TYPE ("(as)"), | ||
380 | 291 | G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable, | ||
381 | 292 | names_listed, watcher); | ||
382 | 293 | } | ||
383 | 294 | |||
384 | 295 | guint | ||
385 | 296 | bus_watch_namespace (GBusType bus_type, | ||
386 | 297 | const gchar *name_space, | ||
387 | 298 | GBusNameAppearedCallback appeared_handler, | ||
388 | 299 | GBusNameVanishedCallback vanished_handler, | ||
389 | 300 | gpointer user_data, | ||
390 | 301 | GDestroyNotify user_data_destroy) | ||
391 | 302 | { | ||
392 | 303 | NamespaceWatcher *watcher; | ||
393 | 304 | |||
394 | 305 | /* same rules for interfaces and well-known names */ | ||
395 | 306 | g_return_val_if_fail (name_space != NULL && g_dbus_is_interface_name (name_space), 0); | ||
396 | 307 | g_return_val_if_fail (appeared_handler || vanished_handler, 0); | ||
397 | 308 | |||
398 | 309 | watcher = g_new0 (NamespaceWatcher, 1); | ||
399 | 310 | watcher->id = namespace_watcher_next_id++; | ||
400 | 311 | watcher->name_space = g_strdup (name_space); | ||
401 | 312 | watcher->appeared_handler = appeared_handler; | ||
402 | 313 | watcher->vanished_handler = vanished_handler; | ||
403 | 314 | watcher->user_data = user_data; | ||
404 | 315 | watcher->user_data_destroy = user_data_destroy; | ||
405 | 316 | watcher->cancellable = g_cancellable_new (); | ||
406 | 317 | watcher->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); | ||
407 | 318 | |||
408 | 319 | if (namespace_watcher_watchers == NULL) | ||
409 | 320 | namespace_watcher_watchers = g_hash_table_new (g_direct_hash, g_direct_equal); | ||
410 | 321 | g_hash_table_insert (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id), watcher); | ||
411 | 322 | |||
412 | 323 | g_bus_get (bus_type, watcher->cancellable, got_bus, watcher); | ||
413 | 324 | |||
414 | 325 | return watcher->id; | ||
415 | 326 | } | ||
416 | 327 | |||
417 | 328 | void | ||
418 | 329 | bus_unwatch_namespace (guint id) | ||
419 | 330 | { | ||
420 | 331 | /* namespace_watcher_stop() might have already removed the watcher | ||
421 | 332 | * with @id in the case of a connection error. Thus, this function | ||
422 | 333 | * doesn't warn when @id is absent from the hash table. | ||
423 | 334 | */ | ||
424 | 335 | |||
425 | 336 | if (namespace_watcher_watchers) | ||
426 | 337 | { | ||
427 | 338 | NamespaceWatcher *watcher; | ||
428 | 339 | |||
429 | 340 | watcher = g_hash_table_lookup (namespace_watcher_watchers, GUINT_TO_POINTER (id)); | ||
430 | 341 | if (watcher) | ||
431 | 342 | { | ||
432 | 343 | /* make sure vanished() is not called as a result of this function */ | ||
433 | 344 | g_hash_table_remove_all (watcher->names); | ||
434 | 345 | |||
435 | 346 | namespace_watcher_stop (watcher); | ||
436 | 347 | } | ||
437 | 348 | } | ||
438 | 349 | } | ||
439 | 0 | 350 | ||
440 | === added file 'src/bus-watch-namespace.h' | |||
441 | --- src/bus-watch-namespace.h 1970-01-01 00:00:00 +0000 | |||
442 | +++ src/bus-watch-namespace.h 2013-08-26 15:12:41 +0000 | |||
443 | @@ -0,0 +1,34 @@ | |||
444 | 1 | /* | ||
445 | 2 | * Copyright 2013 Canonical Ltd. | ||
446 | 3 | * | ||
447 | 4 | * This program is free software; you can redistribute it and/or modify | ||
448 | 5 | * it under the terms of the GNU Lesser General Public License as | ||
449 | 6 | * published by the Free Software Foundation; either version 2 of the | ||
450 | 7 | * License, or (at your option) any later version. | ||
451 | 8 | * | ||
452 | 9 | * This program is distributed in the hope that it will be useful, but | ||
453 | 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
454 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
455 | 12 | * Lesser General Public License for more details. | ||
456 | 13 | * | ||
457 | 14 | * You should have received a copy of the GNU Lesser General Public License | ||
458 | 15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
459 | 16 | * | ||
460 | 17 | * Author: Lars Uebernickel <lars.uebernickel@canonical.com> | ||
461 | 18 | */ | ||
462 | 19 | |||
463 | 20 | #ifndef __BUS_WATCH_NAMESPACE_H__ | ||
464 | 21 | #define __BUS_WATCH_NAMESPACE_H__ | ||
465 | 22 | |||
466 | 23 | #include <gio/gio.h> | ||
467 | 24 | |||
468 | 25 | guint bus_watch_namespace (GBusType bus_type, | ||
469 | 26 | const gchar *name_space, | ||
470 | 27 | GBusNameAppearedCallback appeared_handler, | ||
471 | 28 | GBusNameVanishedCallback vanished_handler, | ||
472 | 29 | gpointer user_data, | ||
473 | 30 | GDestroyNotify user_data_destroy); | ||
474 | 31 | |||
475 | 32 | void bus_unwatch_namespace (guint id); | ||
476 | 33 | |||
477 | 34 | #endif | ||
478 | 0 | 35 | ||
479 | === modified file 'src/media-player-list.vala' | |||
480 | --- src/media-player-list.vala 2013-06-28 19:58:32 +0000 | |||
481 | +++ src/media-player-list.vala 2013-08-26 15:12:41 +0000 | |||
482 | @@ -26,9 +26,7 @@ | |||
483 | 26 | public MediaPlayerList () { | 26 | public MediaPlayerList () { |
484 | 27 | this._players = new HashTable<string, MediaPlayer> (str_hash, str_equal); | 27 | this._players = new HashTable<string, MediaPlayer> (str_hash, str_equal); |
485 | 28 | 28 | ||
489 | 29 | this.mpris_watcher = new Mpris2Watcher (); | 29 | BusWatcher.watch_namespace (BusType.SESSION, "org.mpris.MediaPlayer2", this.player_appeared, this.player_disappeared); |
487 | 30 | this.mpris_watcher.client_appeared.connect (this.player_appeared); | ||
488 | 31 | this.mpris_watcher.client_disappeared.connect (this.player_disappeared); | ||
490 | 32 | } | 30 | } |
491 | 33 | 31 | ||
492 | 34 | /* only valid while the list is not changed */ | 32 | /* only valid while the list is not changed */ |
493 | @@ -113,15 +111,21 @@ | |||
494 | 113 | public signal void player_removed (MediaPlayer player); | 111 | public signal void player_removed (MediaPlayer player); |
495 | 114 | 112 | ||
496 | 115 | HashTable<string, MediaPlayer> _players; | 113 | HashTable<string, MediaPlayer> _players; |
503 | 116 | Mpris2Watcher mpris_watcher; | 114 | |
504 | 117 | 115 | void player_appeared (DBusConnection connection, string name, string owner) { | |
505 | 118 | void player_appeared (string desktop_id, string dbus_name, bool use_playlists) { | 116 | try { |
506 | 119 | var player = this.insert (desktop_id); | 117 | MprisRoot mpris2_root = Bus.get_proxy_sync (BusType.SESSION, name, MPRIS_MEDIA_PLAYER_PATH); |
507 | 120 | if (player != null) | 118 | |
508 | 121 | player.attach (dbus_name); | 119 | var player = this.insert (mpris2_root.DesktopEntry); |
509 | 120 | if (player != null) | ||
510 | 121 | player.attach (name); | ||
511 | 122 | } | ||
512 | 123 | catch (Error e) { | ||
513 | 124 | warning ("unable to create mpris proxy for '%s': %s", name, e.message); | ||
514 | 125 | } | ||
515 | 122 | } | 126 | } |
516 | 123 | 127 | ||
518 | 124 | void player_disappeared (string dbus_name) { | 128 | void player_disappeared (DBusConnection connection, string dbus_name) { |
519 | 125 | MediaPlayer? player = this._players.find ( (name, player) => { | 129 | MediaPlayer? player = this._players.find ( (name, player) => { |
520 | 126 | return player.dbus_name == dbus_name; | 130 | return player.dbus_name == dbus_name; |
521 | 127 | }); | 131 | }); |
522 | 128 | 132 | ||
523 | === removed file 'src/mpris2-watcher.vala' | |||
524 | --- src/mpris2-watcher.vala 2013-06-28 19:58:32 +0000 | |||
525 | +++ src/mpris2-watcher.vala 1970-01-01 00:00:00 +0000 | |||
526 | @@ -1,200 +0,0 @@ | |||
527 | 1 | /* | ||
528 | 2 | Copyright 2010 Canonical Ltd. | ||
529 | 3 | |||
530 | 4 | Authors: | ||
531 | 5 | Conor Curran <conor.curran@canonical.com> | ||
532 | 6 | |||
533 | 7 | This program is free software: you can redistribute it and/or modify it | ||
534 | 8 | under the terms of the GNU General Public License version 3, as published | ||
535 | 9 | by the Free Software Foundation. | ||
536 | 10 | |||
537 | 11 | This program is distributed in the hope that it will be useful, but | ||
538 | 12 | WITHOUT ANY WARRANTY; without even the implied warranties of | ||
539 | 13 | MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
540 | 14 | PURPOSE. See the GNU General Public License for more details. | ||
541 | 15 | |||
542 | 16 | You should have received a copy of the GNU General Public License along | ||
543 | 17 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
544 | 18 | */ | ||
545 | 19 | |||
546 | 20 | using Xml; | ||
547 | 21 | |||
548 | 22 | public class Mpris2Watcher : GLib.Object | ||
549 | 23 | { | ||
550 | 24 | DBusConnection session_bus; | ||
551 | 25 | |||
552 | 26 | public signal void client_appeared ( string desktop_file_name, | ||
553 | 27 | string dbus_name, | ||
554 | 28 | bool use_playlists ); | ||
555 | 29 | public signal void client_disappeared ( string dbus_name ); | ||
556 | 30 | |||
557 | 31 | public Mpris2Watcher () | ||
558 | 32 | { | ||
559 | 33 | } | ||
560 | 34 | |||
561 | 35 | construct | ||
562 | 36 | { | ||
563 | 37 | const string match_rule = "type='signal'," + | ||
564 | 38 | "sender='org.freedesktop.DBus'," + | ||
565 | 39 | "interface='org.freedesktop.DBus'," + | ||
566 | 40 | "member='NameOwnerChanged'," + | ||
567 | 41 | "path='/org/freedesktop/DBus'," + | ||
568 | 42 | "arg0namespace='org.mpris.MediaPlayer2'"; | ||
569 | 43 | try { | ||
570 | 44 | this.session_bus = Bus.get_sync (BusType.SESSION); | ||
571 | 45 | |||
572 | 46 | this.session_bus.call_sync ("org.freedesktop.DBus", | ||
573 | 47 | "/", | ||
574 | 48 | "org.freedesktop.DBus", | ||
575 | 49 | "AddMatch", | ||
576 | 50 | new Variant ("(s)", match_rule), | ||
577 | 51 | VariantType.TUPLE, | ||
578 | 52 | DBusCallFlags.NONE, | ||
579 | 53 | -1); | ||
580 | 54 | |||
581 | 55 | this.session_bus.signal_subscribe ("org.freedesktop.DBus", | ||
582 | 56 | "org.freedesktop.DBus", | ||
583 | 57 | "NameOwnerChanged", | ||
584 | 58 | "/org/freedesktop/DBus", | ||
585 | 59 | null, | ||
586 | 60 | DBusSignalFlags.NO_MATCH_RULE, | ||
587 | 61 | this.name_owner_changed); | ||
588 | 62 | |||
589 | 63 | this.check_for_active_clients.begin(); | ||
590 | 64 | } | ||
591 | 65 | catch (GLib.Error e) { | ||
592 | 66 | warning ("unable to set up name watch for mrpis clients: %s", e.message); | ||
593 | 67 | } | ||
594 | 68 | } | ||
595 | 69 | |||
596 | 70 | // At startup check to see if there are clients up that we are interested in | ||
597 | 71 | // More relevant for development and daemon's like mpd. | ||
598 | 72 | async void check_for_active_clients() | ||
599 | 73 | { | ||
600 | 74 | Variant interfaces; | ||
601 | 75 | |||
602 | 76 | try { | ||
603 | 77 | interfaces = yield this.session_bus.call ("org.freedesktop.DBus", | ||
604 | 78 | "/", | ||
605 | 79 | "org.freedesktop.DBus", | ||
606 | 80 | "ListNames", | ||
607 | 81 | null, | ||
608 | 82 | new VariantType ("(as)"), | ||
609 | 83 | DBusCallFlags.NONE, | ||
610 | 84 | -1); | ||
611 | 85 | } | ||
612 | 86 | catch (GLib.Error e) { | ||
613 | 87 | warning ("unable to search for existing mpris clients: %s ", e.message); | ||
614 | 88 | return; | ||
615 | 89 | } | ||
616 | 90 | |||
617 | 91 | foreach (var val in interfaces.get_child_value (0)) { | ||
618 | 92 | var address = (string) val; | ||
619 | 93 | if (address.has_prefix (MPRIS_PREFIX)){ | ||
620 | 94 | MprisRoot? mpris2_root = this.create_mpris_root(address); | ||
621 | 95 | if (mpris2_root == null) return; | ||
622 | 96 | bool use_playlists = this.supports_playlists ( address ); | ||
623 | 97 | client_appeared (mpris2_root.DesktopEntry + ".desktop", address, use_playlists); | ||
624 | 98 | } | ||
625 | 99 | } | ||
626 | 100 | } | ||
627 | 101 | |||
628 | 102 | void name_owner_changed (DBusConnection con, string sender, string object_path, | ||
629 | 103 | string interface_name, string signal_name, Variant parameters) | ||
630 | 104 | { | ||
631 | 105 | string name, previous_owner, current_owner; | ||
632 | 106 | |||
633 | 107 | parameters.get ("(sss)", out name, out previous_owner, out current_owner); | ||
634 | 108 | |||
635 | 109 | MprisRoot? mpris2_root = this.create_mpris_root (name); | ||
636 | 110 | if (mpris2_root == null) return; | ||
637 | 111 | |||
638 | 112 | if (previous_owner != "" && current_owner == "") { | ||
639 | 113 | debug ("Client '%s' gone down", name); | ||
640 | 114 | client_disappeared (name); | ||
641 | 115 | } | ||
642 | 116 | else if (previous_owner == "" && current_owner != "") { | ||
643 | 117 | debug ("Client '%s' has appeared", name); | ||
644 | 118 | bool use_playlists = this.supports_playlists ( name ); | ||
645 | 119 | client_appeared (mpris2_root.DesktopEntry + ".desktop", name, use_playlists); | ||
646 | 120 | } | ||
647 | 121 | } | ||
648 | 122 | |||
649 | 123 | private MprisRoot? create_mpris_root ( string name ){ | ||
650 | 124 | MprisRoot mpris2_root = null; | ||
651 | 125 | if ( name.has_prefix (MPRIS_PREFIX) ){ | ||
652 | 126 | try { | ||
653 | 127 | mpris2_root = Bus.get_proxy_sync ( BusType.SESSION, | ||
654 | 128 | name, | ||
655 | 129 | MPRIS_MEDIA_PLAYER_PATH ); | ||
656 | 130 | } | ||
657 | 131 | catch (IOError e){ | ||
658 | 132 | warning( "Mpris2watcher could not create a root interface: %s", | ||
659 | 133 | e.message ); | ||
660 | 134 | } | ||
661 | 135 | } | ||
662 | 136 | return mpris2_root; | ||
663 | 137 | } | ||
664 | 138 | |||
665 | 139 | private bool supports_playlists ( string name ) | ||
666 | 140 | { | ||
667 | 141 | FreeDesktopIntrospectable introspectable; | ||
668 | 142 | |||
669 | 143 | try { | ||
670 | 144 | /* The dbusproxy flag parameter is needed to ensure Banshee does not | ||
671 | 145 | blow up. I suspect the issue is that if you | ||
672 | 146 | try to instantiate a dbus object which does not have any properties | ||
673 | 147 | associated with it, gdbus will attempt to fetch the properties (this is | ||
674 | 148 | in the documentation) but the banshee mpris dbus object more than likely | ||
675 | 149 | causes a crash because it doesn't check for the presence of properties | ||
676 | 150 | before attempting to access them. | ||
677 | 151 | */ | ||
678 | 152 | introspectable = Bus.get_proxy_sync ( BusType.SESSION, | ||
679 | 153 | name, | ||
680 | 154 | MPRIS_MEDIA_PLAYER_PATH, | ||
681 | 155 | GLib.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES); | ||
682 | 156 | var results = introspectable.Introspect(); | ||
683 | 157 | return this.parse_interfaces (results); | ||
684 | 158 | } | ||
685 | 159 | catch (IOError e){ | ||
686 | 160 | warning( "Could not create an introspectable object: %s", | ||
687 | 161 | e.message ); | ||
688 | 162 | } | ||
689 | 163 | return false; | ||
690 | 164 | } | ||
691 | 165 | |||
692 | 166 | private bool parse_interfaces( string interface_info ) | ||
693 | 167 | { | ||
694 | 168 | //parse the document from path | ||
695 | 169 | bool result = false; | ||
696 | 170 | Xml.Doc* xml_doc = Parser.parse_doc (interface_info); | ||
697 | 171 | if (xml_doc == null) { | ||
698 | 172 | warning ("Mpris2Watcher - parse-interfaces - failed to instantiate xml doc"); | ||
699 | 173 | return false; | ||
700 | 174 | } | ||
701 | 175 | //get the root node. notice the dereferencing operator -> instead of . | ||
702 | 176 | Xml.Node* root_node = xml_doc->get_root_element (); | ||
703 | 177 | if (root_node == null) { | ||
704 | 178 | //free the document manually before throwing because the garbage collector can't work on pointers | ||
705 | 179 | delete xml_doc; | ||
706 | 180 | warning ("Mpris2Watcher - the interface info xml is empty"); | ||
707 | 181 | return false; | ||
708 | 182 | } | ||
709 | 183 | |||
710 | 184 | //let's parse those nodes | ||
711 | 185 | for (Xml.Node* iter = root_node->children; iter != null; iter = iter->next) { | ||
712 | 186 | //spaces btw. tags are also nodes, discard them | ||
713 | 187 | if (iter->type != ElementType.ELEMENT_NODE){ | ||
714 | 188 | continue; | ||
715 | 189 | } | ||
716 | 190 | Xml.Attr* attributes = iter->properties; //get the node's name | ||
717 | 191 | string interface_name = attributes->children->content; | ||
718 | 192 | debug ( "this dbus object has interface %s ", interface_name ); | ||
719 | 193 | if ( interface_name == MPRIS_PREFIX.concat("Playlists")){ | ||
720 | 194 | result = true; | ||
721 | 195 | } | ||
722 | 196 | } | ||
723 | 197 | delete xml_doc; | ||
724 | 198 | return result; | ||
725 | 199 | } | ||
726 | 200 | } | ||
727 | 201 | 0 | ||
728 | === added file 'vapi/bus-watcher.vapi' | |||
729 | --- vapi/bus-watcher.vapi 1970-01-01 00:00:00 +0000 | |||
730 | +++ vapi/bus-watcher.vapi 2013-08-26 15:12:41 +0000 | |||
731 | @@ -0,0 +1,25 @@ | |||
732 | 1 | /* | ||
733 | 2 | * Copyright 2013 Canonical Ltd. | ||
734 | 3 | * | ||
735 | 4 | * This program is free software; you can redistribute it and/or modify | ||
736 | 5 | * it under the terms of the GNU Lesser General Public License as | ||
737 | 6 | * published by the Free Software Foundation; version 3. | ||
738 | 7 | * | ||
739 | 8 | * This program is distributed in the hope that it will be useful, | ||
740 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
741 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
742 | 11 | * GNU Lesser General Public License for more details. | ||
743 | 12 | * | ||
744 | 13 | * You should have received a copy of the GNU Lesser General Public License | ||
745 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
746 | 15 | * | ||
747 | 16 | * Authors: | ||
748 | 17 | * Lars Uebernickel <lars.uebernickel@canonical.com> | ||
749 | 18 | */ | ||
750 | 19 | |||
751 | 20 | namespace BusWatcher { | ||
752 | 21 | [CCode (cheader_filename = "bus-watch-namespace.h", cname = "bus_watch_namespace")] | ||
753 | 22 | public static uint watch_namespace (GLib.BusType bus_type, string name_space, | ||
754 | 23 | [CCode (delegate_target_pos = 4.9)] owned GLib.BusNameAppearedCallback? name_appeared, | ||
755 | 24 | [CCode (delegate_target_pos = 4.9)] owned GLib.BusNameVanishedCallback? name_vanished); | ||
756 | 25 | } |
PASSED: Continuous integration, rev:365 jenkins. qa.ubuntu. com/job/ indicator- sound-ci/ 35/ jenkins. qa.ubuntu. com/job/ indicator- sound-saucy- amd64-ci/ 16 jenkins. qa.ubuntu. com/job/ indicator- sound-saucy- armhf-ci/ 16
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ indicator- sound-ci/ 35/rebuild
http://