Merge lp:~ted/indicator-sound/sound-stream-cleanup into lp:indicator-sound/15.04
- sound-stream-cleanup
- Merge into trunk.15.04
Status: | Superseded |
---|---|
Proposed branch: | lp:~ted/indicator-sound/sound-stream-cleanup |
Merge into: | lp:indicator-sound/15.04 |
Prerequisite: | lp:~ted/indicator-sound/notification-cleanup |
Diff against target: |
2215 lines (+1174/-512) 19 files modified
CMakeLists.txt (+1/-1) debian/control (+2/-2) src/CMakeLists.txt (+13/-0) src/focus-tracker-stack.vala (+88/-0) src/focus-tracker.vala (+22/-0) src/main.c (+22/-4) src/media-player-list.vala (+1/-1) src/service.vala (+23/-4) src/volume-control.vala (+315/-486) tests/CMakeLists.txt (+32/-0) tests/accounts-service-mock.h (+21/-0) tests/accounts-service-user.cc (+1/-2) tests/focus-tracker-mock.vala (+27/-0) tests/indicator-fixture.h (+444/-0) tests/indicator-test.cc (+110/-0) tests/media-player-user.cc (+5/-11) tests/pa-mock.cpp (+22/-0) tests/volume-control-test.cc (+3/-1) vapi/libpulse-ext-stream-restore.vapi (+22/-0) |
To merge this branch: | bzr merge lp:~ted/indicator-sound/sound-stream-cleanup |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Indicator Applet Developers | Pending | ||
Review via email: mp+245053@code.launchpad.net |
This proposal supersedes a proposal from 2014-12-17.
This proposal has been superseded by a proposal from 2015-01-30.
Commit message
Description of the change
- 498. By Ted Gould
-
Merging the indicator-test branch
- 499. By Ted Gould
-
Put an abstraction in for the focus tracker so we can mock it
- 500. By Ted Gould
-
Add a focus tracker mock
- 501. By Ted Gould
-
Add a mock symbol for stream restore checking
- 502. By Ted Gould
-
Adding another missing symbol
- 503. By Ted Gould
-
Merging in the notifications mock branch
Unmerged revisions
- 503. By Ted Gould
-
Merging in the notifications mock branch
- 502. By Ted Gould
-
Adding another missing symbol
- 501. By Ted Gould
-
Add a mock symbol for stream restore checking
- 500. By Ted Gould
-
Add a focus tracker mock
- 499. By Ted Gould
-
Put an abstraction in for the focus tracker so we can mock it
- 498. By Ted Gould
-
Merging the indicator-test branch
- 497. By Ted Gould
-
Set it up so the end we're detecting that the stream changes, and then looking up values based on that.
- 496. By Ted Gould
-
Warn when we stop because of not being able to get a name
- 495. By Ted Gould
-
Making sure all the media player lists can use standard object primitivs
- 494. By Ted Gould
-
Bind the property into a value in volume control
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2014-02-12 02:00:36 +0000 |
3 | +++ CMakeLists.txt 2015-01-30 23:01:09 +0000 |
4 | @@ -49,7 +49,7 @@ |
5 | |
6 | pkg_check_modules( |
7 | TEST REQUIRED |
8 | - dbustest-1 |
9 | + dbustest-1>=15.04.0 |
10 | ) |
11 | include_directories(${TEST_INCLUDE_DIRS}) |
12 | |
13 | |
14 | === modified file 'debian/control' |
15 | --- debian/control 2014-10-03 20:52:10 +0000 |
16 | +++ debian/control 2015-01-30 23:01:09 +0000 |
17 | @@ -6,14 +6,14 @@ |
18 | Build-Depends: debhelper (>= 9.0), |
19 | cmake, |
20 | dbus, |
21 | - dbus-test-runner (>= 14.04.0+14.04.20140226), |
22 | + dbus-test-runner (>> 14.04.0+14.04.20150120.1), |
23 | dh-translations, |
24 | gir1.2-accountsservice-1.0, |
25 | gnome-common, |
26 | autotools-dev, |
27 | valac (>= 0.20), |
28 | libaccountsservice-dev, |
29 | - libdbustest1-dev (>= 14.04.1), |
30 | + libdbustest1-dev (>= 15.04.0), |
31 | libgirepository1.0-dev, |
32 | libglib2.0-dev (>= 2.22.3), |
33 | libgtest-dev, |
34 | |
35 | === modified file 'src/CMakeLists.txt' |
36 | --- src/CMakeLists.txt 2015-01-27 17:42:26 +0000 |
37 | +++ src/CMakeLists.txt 2015-01-30 23:01:09 +0000 |
38 | @@ -36,6 +36,7 @@ |
39 | --vapidir=. |
40 | --pkg=url-dispatcher |
41 | --pkg=bus-watcher |
42 | + --pkg=libpulse-ext-stream-restore |
43 | ) |
44 | |
45 | vala_add(indicator-sound-service |
46 | @@ -47,9 +48,12 @@ |
47 | media-player-list |
48 | mpris2-interfaces |
49 | accounts-service-user |
50 | + focus-tracker |
51 | ) |
52 | vala_add(indicator-sound-service |
53 | volume-control.vala |
54 | + DEPENDS |
55 | + focus-tracker |
56 | ) |
57 | vala_add(indicator-sound-service |
58 | media-player.vala |
59 | @@ -120,6 +124,14 @@ |
60 | vala_add(indicator-sound-service |
61 | greeter-broadcast.vala |
62 | ) |
63 | +vala_add(indicator-sound-service |
64 | + focus-tracker.vala |
65 | +) |
66 | +vala_add(indicator-sound-service |
67 | + focus-tracker-stack.vala |
68 | + DEPENDS |
69 | + focus-tracker |
70 | +) |
71 | |
72 | vala_finish(indicator-sound-service |
73 | SOURCES |
74 | @@ -154,6 +166,7 @@ |
75 | |
76 | add_definitions( |
77 | -w |
78 | + -DG_LOG_DOMAIN="indicator-sound" |
79 | ) |
80 | |
81 | add_library( |
82 | |
83 | === added file 'src/focus-tracker-stack.vala' |
84 | --- src/focus-tracker-stack.vala 1970-01-01 00:00:00 +0000 |
85 | +++ src/focus-tracker-stack.vala 2015-01-30 23:01:09 +0000 |
86 | @@ -0,0 +1,88 @@ |
87 | +/* |
88 | + * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- |
89 | + * Copyright © 2014 Canonical Ltd. |
90 | + * |
91 | + * This program is free software; you can redistribute it and/or modify |
92 | + * it under the terms of the GNU General Public License as published by |
93 | + * the Free Software Foundation; version 3. |
94 | + * |
95 | + * This program is distributed in the hope that it will be useful, |
96 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
97 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
98 | + * GNU General Public License for more details. |
99 | + * |
100 | + * You should have received a copy of the GNU General Public License |
101 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
102 | + * |
103 | + * Authors: |
104 | + * Ted Gould <ted@canonical.com> |
105 | + */ |
106 | + |
107 | +private struct WindowInfo { |
108 | + public uint window_id; |
109 | + public string app_id; |
110 | + public bool focused; |
111 | + public uint stage; |
112 | +} |
113 | + |
114 | +/* |
115 | +private struct DesktopInfo { |
116 | + public string app_id; |
117 | + public string desktop_file; |
118 | +} |
119 | +*/ |
120 | + |
121 | +[DBus (name = "com.canonical.Unity.WindowStack")] |
122 | +private interface WindowStack : Object { |
123 | + /* public abstract async DesktopInfo GetAppIdFromPid (uint pid) throws IOError; */ |
124 | + public abstract async WindowInfo[] GetWindowStack () throws IOError; |
125 | + /* public abstract async string[] GetWindowProperties (uint window_id, string app_id, string[] names) throws IOError; */ |
126 | + |
127 | + public signal void FocusedWindowChanged (uint window_id, string app_id, uint stage); |
128 | + /* public signal void WindowCreated (uint window_id, string app_id); */ |
129 | + /* public signal void WindowDestroyed (uint window_id, string app_id); */ |
130 | +} |
131 | + |
132 | +public class FocusTrackerStack : FocusTracker { |
133 | + public override string focused_appid { get; private set; default = "unknown"; } |
134 | + private WindowStack proxy; |
135 | + |
136 | + public FocusTrackerStack ( ) { |
137 | + build_proxies.begin(); |
138 | + } |
139 | + |
140 | + private async void build_proxies() { |
141 | + try { |
142 | + proxy = yield Bus.get_proxy<WindowStack>(BusType.SESSION, |
143 | + "com.canonical.Unity.WindowStack", |
144 | + "/com/canonical/Unity/WindowStack", |
145 | + DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, |
146 | + null); |
147 | + |
148 | + proxy.FocusedWindowChanged.connect((window, appid, stage) => { |
149 | + if (stage == 0) { |
150 | + debug("Focus changed to: %s", appid); |
151 | + this.focused_appid = appid; |
152 | + } |
153 | + }); |
154 | + |
155 | + update_focused_appid.begin(); |
156 | + } catch (Error e) { |
157 | + warning("Unable to create window stack proxy: %s", e.message); |
158 | + } |
159 | + } |
160 | + |
161 | + private async void update_focused_appid () { |
162 | + try { |
163 | + var windows = yield proxy.GetWindowStack(); |
164 | + foreach (var window in windows) { |
165 | + if (window.focused && window.stage == 0) { |
166 | + focused_appid = window.app_id; |
167 | + break; |
168 | + } |
169 | + } |
170 | + } catch (Error e) { |
171 | + warning("Unable to get window stack list: %s", e.message); |
172 | + } |
173 | + } |
174 | +} |
175 | |
176 | === added file 'src/focus-tracker.vala' |
177 | --- src/focus-tracker.vala 1970-01-01 00:00:00 +0000 |
178 | +++ src/focus-tracker.vala 2015-01-30 23:01:09 +0000 |
179 | @@ -0,0 +1,22 @@ |
180 | +/* |
181 | + * Copyright © 2015 Canonical Ltd. |
182 | + * |
183 | + * This program is free software; you can redistribute it and/or modify |
184 | + * it under the terms of the GNU General Public License as published by |
185 | + * the Free Software Foundation; version 3. |
186 | + * |
187 | + * This program is distributed in the hope that it will be useful, |
188 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
189 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
190 | + * GNU General Public License for more details. |
191 | + * |
192 | + * You should have received a copy of the GNU General Public License |
193 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
194 | + * |
195 | + * Authors: |
196 | + * Ted Gould <ted@canonical.com> |
197 | + */ |
198 | + |
199 | +public abstract class FocusTracker : Object { |
200 | + public virtual string focused_appid { get; protected set; } |
201 | +} |
202 | |
203 | === modified file 'src/main.c' |
204 | --- src/main.c 2014-02-25 22:47:45 +0000 |
205 | +++ src/main.c 2015-01-30 23:01:09 +0000 |
206 | @@ -1,6 +1,21 @@ |
207 | -/* main.c generated by valac 0.22.1, the Vala compiler |
208 | - * generated from main.vala, do not modify */ |
209 | - |
210 | +/* |
211 | + * Copyright © 2015 Canonical Ltd. |
212 | + * |
213 | + * This program is free software; you can redistribute it and/or modify |
214 | + * it under the terms of the GNU General Public License as published by |
215 | + * the Free Software Foundation; version 3. |
216 | + * |
217 | + * This program is distributed in the hope that it will be useful, |
218 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
219 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
220 | + * GNU General Public License for more details. |
221 | + * |
222 | + * You should have received a copy of the GNU General Public License |
223 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
224 | + * |
225 | + * Authors: |
226 | + * Ted Gould <ted@canonical.com> |
227 | + */ |
228 | |
229 | #include <glib.h> |
230 | #include <locale.h> |
231 | @@ -29,10 +44,13 @@ |
232 | playerlist = MEDIA_PLAYER_LIST(media_player_list_mpris_new()); |
233 | } |
234 | |
235 | - service = indicator_sound_service_new (playerlist); |
236 | + FocusTracker * tracker = FOCUS_TRACKER(focus_tracker_stack_new()); |
237 | + |
238 | + service = indicator_sound_service_new (playerlist, tracker); |
239 | result = indicator_sound_service_run (service); |
240 | |
241 | g_object_unref(playerlist); |
242 | + g_object_unref(tracker); |
243 | g_object_unref(service); |
244 | |
245 | return result; |
246 | |
247 | === modified file 'src/media-player-list.vala' |
248 | --- src/media-player-list.vala 2014-02-24 22:47:50 +0000 |
249 | +++ src/media-player-list.vala 2015-01-30 23:01:09 +0000 |
250 | @@ -17,7 +17,7 @@ |
251 | * Ted Gould <ted@canonical.com> |
252 | */ |
253 | |
254 | -public class MediaPlayerList { |
255 | +public class MediaPlayerList : Object { |
256 | public class Iterator { |
257 | public virtual MediaPlayer? next_value() { |
258 | return null; |
259 | |
260 | === modified file 'src/service.vala' |
261 | --- src/service.vala 2015-01-29 17:31:03 +0000 |
262 | +++ src/service.vala 2015-01-30 23:01:09 +0000 |
263 | @@ -18,7 +18,7 @@ |
264 | */ |
265 | |
266 | public class IndicatorSound.Service: Object { |
267 | - public Service (MediaPlayerList playerlist) { |
268 | + public Service (MediaPlayerList playerlist, FocusTracker tracker) { |
269 | sync_notification = new Notify.Notification(_("Volume"), "", "audio-volume-muted"); |
270 | this.notification_server_watch = GLib.Bus.watch_name(GLib.BusType.SESSION, |
271 | "org.freedesktop.Notifications", |
272 | @@ -32,7 +32,13 @@ |
273 | this.settings.bind ("visible", this, "visible", SettingsBindFlags.GET); |
274 | this.notify["visible"].connect ( () => this.update_root_icon () ); |
275 | |
276 | - this.volume_control = new VolumeControl (); |
277 | + this.focus_tracker = tracker; |
278 | + |
279 | + this.volume_control = new VolumeControl (this.focus_tracker); |
280 | + this.volume_control.notify["stream"].connect(() => { |
281 | + this.update_sync_notification(); |
282 | + }); |
283 | + this.volume_control.bind_property ("media-playing", this, "player-playing", BindingFlags.SYNC_CREATE); |
284 | |
285 | /* If we're on the greeter, don't export */ |
286 | if (GLib.Environment.get_user_name() != "lightdm") { |
287 | @@ -177,7 +183,9 @@ |
288 | Settings settings; |
289 | Settings sharedsettings; |
290 | VolumeControl volume_control; |
291 | + FocusTracker focus_tracker; |
292 | MediaPlayerList players; |
293 | + public bool player_playing { get; set; default = false; } |
294 | uint player_action_update_id; |
295 | bool mute_blocks_sound; |
296 | uint sound_was_blocked_timeout_id; |
297 | @@ -288,7 +296,7 @@ |
298 | return; |
299 | |
300 | /* Determine Label */ |
301 | - string volume_label = ""; |
302 | + string volume_label = volume_control.stream; /* TODO: Undo this */ |
303 | if (volume_control.high_volume) |
304 | volume_label = _("High volume"); |
305 | |
306 | @@ -362,7 +370,7 @@ |
307 | }); |
308 | |
309 | mute_action.change_state.connect ( (action, val) => { |
310 | - volume_control.set_mute (val.get_boolean ()); |
311 | + volume_control.mute = val.get_boolean (); |
312 | }); |
313 | |
314 | this.volume_control.notify["mute"].connect ( () => { |
315 | @@ -475,6 +483,7 @@ |
316 | } |
317 | |
318 | void name_lost (DBusConnection connection, string name) { |
319 | + warning("Unable to get DBus Name: %s", name); |
320 | this.loop.quit (); |
321 | } |
322 | |
323 | @@ -493,6 +502,7 @@ |
324 | |
325 | bool update_player_actions () { |
326 | bool clear_accounts_player = true; |
327 | + bool player_playing = false; |
328 | |
329 | foreach (var player in this.players) { |
330 | SimpleAction? action = this.actions.lookup_action (player.id) as SimpleAction; |
331 | @@ -512,11 +522,20 @@ |
332 | accounts_service.player = player; |
333 | clear_accounts_player = false; |
334 | } |
335 | + |
336 | + if (player.state == "Playing") { |
337 | + player_playing = true; |
338 | + } |
339 | } |
340 | |
341 | if (clear_accounts_player) |
342 | clear_acts_player(); |
343 | |
344 | + if (this.player_playing != player_playing) { |
345 | + this.player_playing = player_playing; |
346 | + update_root_icon(); |
347 | + } |
348 | + |
349 | this.player_action_update_id = 0; |
350 | return false; |
351 | } |
352 | |
353 | === modified file 'src/volume-control.vala' |
354 | --- src/volume-control.vala 2015-01-29 17:31:55 +0000 |
355 | +++ src/volume-control.vala 2015-01-30 23:01:09 +0000 |
356 | @@ -19,6 +19,7 @@ |
357 | */ |
358 | |
359 | using PulseAudio; |
360 | +using PulseAudio.Extension.StreamRestore; |
361 | using Notify; |
362 | using Gee; |
363 | |
364 | @@ -37,29 +38,47 @@ |
365 | /* this is static to ensure it being freed after @context (loop does not have ref counting) */ |
366 | private static PulseAudio.GLibMainLoop loop; |
367 | |
368 | + private FocusTracker focus_tracker; |
369 | + public bool media_playing { get; set; default = false; } |
370 | + |
371 | private uint _reconnect_timer = 0; |
372 | |
373 | private PulseAudio.Context context; |
374 | private bool _mute = true; |
375 | - private bool _is_playing = false; |
376 | + public bool is_playing { get; private set; default = false; } |
377 | private double _volume = 0.0; |
378 | private double _mic_volume = 0.0; |
379 | |
380 | /* Used by the pulseaudio stream restore extension */ |
381 | - private DBusConnection _pconn; |
382 | - /* Need both the list and hash so we can retrieve the last known sink-input after |
383 | - * releasing the current active one (restoring back to the previous known role) */ |
384 | - private Gee.ArrayList<uint32> _sink_input_list = new Gee.ArrayList<uint32> (); |
385 | - private HashMap<uint32, string> _sink_input_hash = new HashMap<uint32, string> (); |
386 | private bool _pulse_use_stream_restore = false; |
387 | - private uint32 _active_sink_input = -1; |
388 | - private string[] _valid_roles = {"multimedia", "alert", "alarm", "phone"}; |
389 | - private string? _objp_role_multimedia = null; |
390 | - private string? _objp_role_alert = null; |
391 | - private string? _objp_role_alarm = null; |
392 | - private string? _objp_role_phone = null; |
393 | - private uint _pa_volume_sig_count = 0; |
394 | - |
395 | + private enum InputStreamType { |
396 | + ALERT = 0, |
397 | + ALARM = 1, |
398 | + MULTIMEDIA = 2, |
399 | + PHONE = 3 |
400 | + } |
401 | + private InputStreamType currentstream = InputStreamType.ALERT; |
402 | + private InputStreamType tempstream = InputStreamType.ALERT; |
403 | + /** The name of the stream that we're currently playing */ |
404 | + public string stream { |
405 | + get { |
406 | + switch (this.currentstream) { |
407 | + case InputStreamType.ALERT: |
408 | + return "alert"; |
409 | + case InputStreamType.ALARM: |
410 | + return "alarm"; |
411 | + case InputStreamType.MULTIMEDIA: |
412 | + return "multimedia"; |
413 | + case InputStreamType.PHONE: |
414 | + return "phone"; |
415 | + } |
416 | + |
417 | + return "alert"; |
418 | + } |
419 | + } |
420 | + |
421 | + |
422 | + /* Accounts Service Variables */ |
423 | private DBusProxy _user_proxy; |
424 | private GreeterListInterface _greeter_proxy; |
425 | private Cancellable _mute_cancellable; |
426 | @@ -68,7 +87,14 @@ |
427 | private uint _accountservice_volume_timer = 0; |
428 | private bool _send_next_local_volume = false; |
429 | private double _account_service_volume = 0.0; |
430 | + |
431 | + |
432 | private bool _active_port_headphone = false; |
433 | + /* There is not easy way to check if the port is a headset/headphone besides |
434 | + * checking for the port name. On touch (with the pulseaudio droid element) |
435 | + * the headset/headphone port is called 'output-headset' and 'output-headphone'. |
436 | + * On the desktop this is usually called 'analog-output-headphones' */ |
437 | + private string[] headphone_outputs = {"output-wired_headset", "output-wired_headphone", "analog-output-headphones"}; |
438 | |
439 | /** true when connected to the pulse server */ |
440 | public bool ready { get; set; } |
441 | @@ -79,12 +105,20 @@ |
442 | /** true when high volume warnings should be shown */ |
443 | public bool high_volume { |
444 | get { |
445 | - return this._volume > 0.75 && _active_port_headphone; |
446 | + return this._volume > 0.75 && _active_port_headphone && stream == "multimedia"; |
447 | } |
448 | } |
449 | |
450 | - public VolumeControl () |
451 | + public VolumeControl (FocusTracker tracker) |
452 | { |
453 | + this.focus_tracker = tracker; |
454 | + this.focus_tracker.notify["focused-appid"].connect(() => { |
455 | + if (!this.ready) |
456 | + return; |
457 | + |
458 | + this.update_sink_input(); |
459 | + }); |
460 | + |
461 | if (loop == null) |
462 | loop = new PulseAudio.GLibMainLoop (); |
463 | |
464 | @@ -94,6 +128,13 @@ |
465 | setup_accountsservice.begin (); |
466 | |
467 | this.reconnect_to_pulse (); |
468 | + |
469 | + this.notify["media-playing"].connect(() => { |
470 | + update_sink_input(); |
471 | + }); |
472 | + this.notify["stream"].connect(() => { |
473 | + update_stream(); |
474 | + }); |
475 | } |
476 | |
477 | ~VolumeControl () |
478 | @@ -106,7 +147,10 @@ |
479 | stop_account_service_volume_timer(); |
480 | } |
481 | |
482 | - /* PulseAudio logic*/ |
483 | + /************************/ |
484 | + /* PulseAudio logic */ |
485 | + /************************/ |
486 | + |
487 | private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index) |
488 | { |
489 | switch (t & Context.SubscriptionEventType.FACILITY_MASK) |
490 | @@ -116,23 +160,7 @@ |
491 | break; |
492 | |
493 | case Context.SubscriptionEventType.SINK_INPUT: |
494 | - switch (t & Context.SubscriptionEventType.TYPE_MASK) |
495 | - { |
496 | - case Context.SubscriptionEventType.NEW: |
497 | - c.get_sink_input_info (index, handle_new_sink_input_cb); |
498 | - break; |
499 | - |
500 | - case Context.SubscriptionEventType.CHANGE: |
501 | - c.get_sink_input_info (index, handle_changed_sink_input_cb); |
502 | - break; |
503 | - |
504 | - case Context.SubscriptionEventType.REMOVE: |
505 | - remove_sink_input_from_list (index); |
506 | - break; |
507 | - default: |
508 | - debug ("Sink input event not known."); |
509 | - break; |
510 | - } |
511 | + update_sink_input (); |
512 | break; |
513 | |
514 | case Context.SubscriptionEventType.SOURCE: |
515 | @@ -143,7 +171,14 @@ |
516 | switch (t & Context.SubscriptionEventType.TYPE_MASK) |
517 | { |
518 | case Context.SubscriptionEventType.NEW: |
519 | - c.get_source_output_info (index, source_output_info_cb); |
520 | + c.get_source_output_info (index, (context, info, eol) => { |
521 | + if (info == null) |
522 | + return; |
523 | + |
524 | + var role = info.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); |
525 | + if (role == "phone" || role == "production") |
526 | + this.active_mic = true; |
527 | + }); |
528 | break; |
529 | |
530 | case Context.SubscriptionEventType.REMOVE: |
531 | @@ -154,291 +189,205 @@ |
532 | } |
533 | } |
534 | |
535 | - private void sink_info_cb_for_props (Context c, SinkInfo? i, int eol) |
536 | - { |
537 | - bool old_high_volume = this.high_volume; |
538 | - |
539 | - if (i == null) |
540 | - return; |
541 | - |
542 | - if (_mute != (bool)i.mute) |
543 | - { |
544 | - _mute = (bool)i.mute; |
545 | - this.notify_property ("mute"); |
546 | - } |
547 | - |
548 | - var playing = (i.state == PulseAudio.SinkState.RUNNING); |
549 | - if (_is_playing != playing) |
550 | - { |
551 | - _is_playing = playing; |
552 | - this.notify_property ("is-playing"); |
553 | - } |
554 | - |
555 | - /* Check if the current active port is headset/headphone */ |
556 | - /* There is not easy way to check if the port is a headset/headphone besides |
557 | - * checking for the port name. On touch (with the pulseaudio droid element) |
558 | - * the headset/headphone port is called 'output-headset' and 'output-headphone'. |
559 | - * On the desktop this is usually called 'analog-output-headphones' */ |
560 | - if (i.active_port != null && |
561 | - (i.active_port.name == "output-wired_headset" || |
562 | - i.active_port.name == "output-wired_headphone" || |
563 | - i.active_port.name == "analog-output-headphones")) { |
564 | - _active_port_headphone = true; |
565 | - } else { |
566 | - _active_port_headphone = false; |
567 | - } |
568 | - |
569 | - if (_pulse_use_stream_restore == false && |
570 | - _volume != volume_to_double (i.volume.max ())) |
571 | - { |
572 | - _volume = volume_to_double (i.volume.max ()); |
573 | - this.notify_property("volume"); |
574 | - start_local_volume_timer(); |
575 | - } |
576 | - |
577 | - if (this.high_volume != old_high_volume) { |
578 | - this.notify_property("high-volume"); |
579 | - } |
580 | - } |
581 | - |
582 | - private void source_info_cb (Context c, SourceInfo? i, int eol) |
583 | - { |
584 | - if (i == null) |
585 | - return; |
586 | - |
587 | - if (_mic_volume != volume_to_double (i.volume.values[0])) |
588 | - { |
589 | - _mic_volume = volume_to_double (i.volume.values[0]); |
590 | - this.notify_property ("mic-volume"); |
591 | - } |
592 | - } |
593 | - |
594 | - private void server_info_cb_for_props (Context c, ServerInfo? i) |
595 | - { |
596 | - if (i == null) |
597 | - return; |
598 | - context.get_sink_info_by_name (i.default_sink_name, sink_info_cb_for_props); |
599 | - } |
600 | - |
601 | private void update_sink () |
602 | { |
603 | - context.get_server_info (server_info_cb_for_props); |
604 | - } |
605 | - |
606 | - private void update_source_get_server_info_cb (PulseAudio.Context c, PulseAudio.ServerInfo? i) { |
607 | - if (i != null) |
608 | - context.get_source_info_by_name (i.default_source_name, source_info_cb); |
609 | + if (!this.ready) |
610 | + return; |
611 | + |
612 | + context.get_server_info ((context, info) => { |
613 | + if (info == null) |
614 | + return; |
615 | + |
616 | + context.get_sink_info_by_name (info.default_sink_name, (context, info, eol) => { |
617 | + bool old_high_volume = this.high_volume; |
618 | + |
619 | + if (info == null) |
620 | + return; |
621 | + |
622 | + if (_mute != (bool)info.mute) |
623 | + { |
624 | + _mute = (bool)info.mute; |
625 | + this.notify_property ("mute"); |
626 | + } |
627 | + |
628 | + this.is_playing = (info.state == PulseAudio.SinkState.RUNNING); |
629 | + |
630 | + /* Check if the current active port is headset/headphone */ |
631 | + if (info.active_port.name in headphone_outputs) { |
632 | + _active_port_headphone = true; |
633 | + } else { |
634 | + _active_port_headphone = false; |
635 | + } |
636 | + |
637 | + if (_pulse_use_stream_restore == false && |
638 | + _volume != volume_to_double (info.volume.max ())) |
639 | + { |
640 | + _volume = volume_to_double (info.volume.max ()); |
641 | + this.notify_property("volume"); |
642 | + start_local_volume_timer(); |
643 | + } |
644 | + |
645 | + if (this.high_volume != old_high_volume) { |
646 | + this.notify_property("high-volume"); |
647 | + } |
648 | + }); |
649 | + }); |
650 | + } |
651 | + |
652 | + private void update_sink_input () |
653 | + { |
654 | + if (!this.ready) |
655 | + return; |
656 | + |
657 | + if (!this._pulse_use_stream_restore) |
658 | + return; |
659 | + |
660 | + this.tempstream = InputStreamType.ALERT; |
661 | + if (this.media_playing) |
662 | + this.tempstream = InputStreamType.MULTIMEDIA; |
663 | + |
664 | + context.get_sink_input_info_list((context, info, eol) => { |
665 | + var inputstream = InputStreamType.ALARM; |
666 | + |
667 | + if (info != null) { |
668 | + var role = info.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); |
669 | + var corked = (info.corked != 0); |
670 | + var appid = info.proplist.gets (PulseAudio.Proplist.PROP_APPLICATION_ID); |
671 | + |
672 | + /* Override the corked value for the phone as it's always corked */ |
673 | + if (role != null && role == "phone") |
674 | + corked = false; |
675 | + |
676 | + if (!corked && role != null && appid == this.focus_tracker.focused_appid) { |
677 | + switch (role) { |
678 | + case "phone": |
679 | + inputstream = InputStreamType.PHONE; |
680 | + break; |
681 | + case "alarm": |
682 | + inputstream = InputStreamType.ALARM; |
683 | + break; |
684 | + case "alert": |
685 | + inputstream = InputStreamType.ALERT; |
686 | + break; |
687 | + case "multimedia": |
688 | + default: |
689 | + /* We treat unknown roles (i.e. 'music') as multimedia */ |
690 | + inputstream = InputStreamType.MULTIMEDIA; |
691 | + break; |
692 | + } |
693 | + } |
694 | + } |
695 | + |
696 | + if (inputstream > this.tempstream) { |
697 | + this.tempstream = inputstream; |
698 | + } |
699 | + |
700 | + if (eol != 0) { |
701 | + if (this.currentstream != this.tempstream) { |
702 | + this.currentstream = this.tempstream; |
703 | + debug("Current stream: %s", this.stream); |
704 | + this.notify_property("stream"); |
705 | + } |
706 | + } |
707 | + }); |
708 | + } |
709 | + |
710 | + /* Look at a given profile or role and figure out what our volume and |
711 | + mute settings should be based on that. */ |
712 | + private void update_stream () |
713 | + { |
714 | + |
715 | + |
716 | } |
717 | |
718 | private void update_source () |
719 | { |
720 | - context.get_server_info (update_source_get_server_info_cb); |
721 | - } |
722 | - |
723 | - private DBusMessage pulse_dbus_filter (DBusConnection connection, owned DBusMessage message, bool incoming) |
724 | - { |
725 | - if (message.get_message_type () == DBusMessageType.SIGNAL) { |
726 | - string active_role_objp = _objp_role_alert; |
727 | - if (_active_sink_input != -1) |
728 | - active_role_objp = _sink_input_hash.get (_active_sink_input); |
729 | - |
730 | - if (message.get_path () == active_role_objp && message.get_member () == "VolumeUpdated") { |
731 | - uint sig_count = 0; |
732 | - lock (_pa_volume_sig_count) { |
733 | - sig_count = _pa_volume_sig_count; |
734 | - if (_pa_volume_sig_count > 0) |
735 | - _pa_volume_sig_count--; |
736 | - } |
737 | - |
738 | - /* We only care about signals if our internal count is zero */ |
739 | - if (sig_count == 0) { |
740 | - /* Extract volume and make sure it's not a side effect of us setting it */ |
741 | - Variant body = message.get_body (); |
742 | - Variant varray = body.get_child_value (0); |
743 | - |
744 | - uint32 type = 0, volume = 0; |
745 | - VariantIter iter = varray.iterator (); |
746 | - iter.next ("(uu)", &type, &volume); |
747 | - /* Here we need to compare integer values to avoid rounding issues, so just |
748 | - * using the volume values used by pulseaudio */ |
749 | - PulseAudio.Volume cvolume = double_to_volume (_volume); |
750 | - if (volume != cvolume) { |
751 | - /* Someone else changed the volume for this role, reflect on the indicator */ |
752 | - _volume = volume_to_double (volume); |
753 | - this.notify_property("volume"); |
754 | - start_local_volume_timer(); |
755 | - } |
756 | - } |
757 | - } |
758 | - } |
759 | - |
760 | - return message; |
761 | - } |
762 | - |
763 | - private async void update_active_sink_input (uint32 index) |
764 | - { |
765 | - if ((index == -1) || (index != _active_sink_input && index in _sink_input_list)) { |
766 | - string sink_input_objp = _objp_role_alert; |
767 | - if (index != -1) |
768 | - sink_input_objp = _sink_input_hash.get (index); |
769 | - _active_sink_input = index; |
770 | - |
771 | - /* Listen for role volume changes from pulse itself (external clients) */ |
772 | - try { |
773 | - var builder = new VariantBuilder (new VariantType ("ao")); |
774 | - builder.add ("o", sink_input_objp); |
775 | - |
776 | - yield _pconn.call ("org.PulseAudio.Core1", "/org/pulseaudio/core1", |
777 | - "org.PulseAudio.Core1", "ListenForSignal", |
778 | - new Variant ("(sao)", "org.PulseAudio.Ext.StreamRestore1.RestoreEntry.VolumeUpdated", builder), |
779 | - null, DBusCallFlags.NONE, -1); |
780 | - } catch (GLib.Error e) { |
781 | - warning ("unable to listen for pulseaudio dbus signals (%s)", e.message); |
782 | - } |
783 | - |
784 | - try { |
785 | - var props_variant = yield _pconn.call ("org.PulseAudio.Ext.StreamRestore1.RestoreEntry", |
786 | - sink_input_objp, "org.freedesktop.DBus.Properties", "Get", |
787 | - new Variant ("(ss)", "org.PulseAudio.Ext.StreamRestore1.RestoreEntry", "Volume"), |
788 | - null, DBusCallFlags.NONE, -1); |
789 | - Variant tmp; |
790 | - props_variant.get ("(v)", out tmp); |
791 | - uint32 type = 0, volume = 0; |
792 | - VariantIter iter = tmp.iterator (); |
793 | - iter.next ("(uu)", &type, &volume); |
794 | - |
795 | - _volume = volume_to_double (volume); |
796 | - this.notify_property("volume"); |
797 | - start_local_volume_timer(); |
798 | - } catch (GLib.Error e) { |
799 | - warning ("unable to get volume for active role %s (%s)", sink_input_objp, e.message); |
800 | - } |
801 | - } |
802 | - } |
803 | - |
804 | - private void add_sink_input_into_list (SinkInputInfo sink_input) |
805 | - { |
806 | - /* We're only adding ones that are not corked and with a valid role */ |
807 | - var role = sink_input.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); |
808 | - |
809 | - if (role != null && role in _valid_roles) { |
810 | - if (sink_input.corked == 0 || role == "phone") { |
811 | - _sink_input_list.insert (0, sink_input.index); |
812 | - switch (role) |
813 | + if (!this.ready) |
814 | + return; |
815 | + |
816 | + context.get_server_info ((context, info) => { |
817 | + if (info == null) |
818 | + return; |
819 | + |
820 | + context.get_source_info_by_name (info.default_source_name, (context, info, eol) => { |
821 | + if (info == null) |
822 | + return; |
823 | + |
824 | + if (_mic_volume != volume_to_double (info.volume.values[0])) |
825 | { |
826 | - case "multimedia": |
827 | - _sink_input_hash.set (sink_input.index, _objp_role_multimedia); |
828 | - break; |
829 | - case "alert": |
830 | - _sink_input_hash.set (sink_input.index, _objp_role_alert); |
831 | - break; |
832 | - case "alarm": |
833 | - _sink_input_hash.set (sink_input.index, _objp_role_alarm); |
834 | - break; |
835 | - case "phone": |
836 | - _sink_input_hash.set (sink_input.index, _objp_role_phone); |
837 | - break; |
838 | + _mic_volume = volume_to_double (info.volume.values[0]); |
839 | + this.notify_property ("mic-volume"); |
840 | } |
841 | - /* Only switch the active sink input in case a phone one is not active */ |
842 | - if (_active_sink_input == -1 || |
843 | - _sink_input_hash.get (_active_sink_input) != _objp_role_phone) |
844 | - update_active_sink_input.begin (sink_input.index); |
845 | - } |
846 | - } |
847 | - } |
848 | - |
849 | - private void remove_sink_input_from_list (uint32 index) |
850 | - { |
851 | - if (index in _sink_input_list) { |
852 | - _sink_input_list.remove (index); |
853 | - _sink_input_hash.unset (index); |
854 | - if (index == _active_sink_input) { |
855 | - if (_sink_input_list.size != 0) |
856 | - update_active_sink_input.begin (_sink_input_list.get (0)); |
857 | - else |
858 | - update_active_sink_input.begin (-1); |
859 | - } |
860 | - } |
861 | - } |
862 | - |
863 | - private void handle_new_sink_input_cb (Context c, SinkInputInfo? i, int eol) |
864 | - { |
865 | - if (i == null) |
866 | - return; |
867 | - |
868 | - add_sink_input_into_list (i); |
869 | - } |
870 | - |
871 | - private void handle_changed_sink_input_cb (Context c, SinkInputInfo? i, int eol) |
872 | - { |
873 | - if (i == null) |
874 | - return; |
875 | - |
876 | - if (i.index in _sink_input_list) { |
877 | - /* Phone stream is always corked, so handle it differently */ |
878 | - if (i.corked == 1 && _sink_input_hash.get (i.index) != _objp_role_phone) |
879 | - remove_sink_input_from_list (i.index); |
880 | - } else { |
881 | - if (i.corked == 0) |
882 | - add_sink_input_into_list (i); |
883 | - } |
884 | - } |
885 | - |
886 | - private void source_output_info_cb (Context c, SourceOutputInfo? i, int eol) |
887 | - { |
888 | - if (i == null) |
889 | - return; |
890 | - |
891 | - var role = i.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); |
892 | - if (role == "phone" || role == "production") |
893 | - this.active_mic = true; |
894 | + }); |
895 | + }); |
896 | } |
897 | |
898 | private void context_state_callback (Context c) |
899 | { |
900 | switch (c.get_state ()) { |
901 | case Context.State.READY: |
902 | - if (_pulse_use_stream_restore) { |
903 | - c.subscribe (PulseAudio.Context.SubscriptionMask.SINK | |
904 | - PulseAudio.Context.SubscriptionMask.SINK_INPUT | |
905 | - PulseAudio.Context.SubscriptionMask.SOURCE | |
906 | - PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT); |
907 | - } else { |
908 | - c.subscribe (PulseAudio.Context.SubscriptionMask.SINK | |
909 | - PulseAudio.Context.SubscriptionMask.SOURCE | |
910 | - PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT); |
911 | - } |
912 | - c.set_subscribe_callback (context_events_cb); |
913 | - update_sink (); |
914 | - update_source (); |
915 | - this.ready = true; |
916 | + PulseAudio.Extension.StreamRestore.test(this.context, (context, version) => { |
917 | + if (version != 0) { |
918 | + debug("Got the stream restore extension"); |
919 | + _pulse_use_stream_restore = true; |
920 | + } else { |
921 | + debug("No stream restore extension found"); |
922 | + } |
923 | + |
924 | + if (_pulse_use_stream_restore) { |
925 | + this.context.subscribe (PulseAudio.Context.SubscriptionMask.SINK | |
926 | + PulseAudio.Context.SubscriptionMask.SINK_INPUT | |
927 | + PulseAudio.Context.SubscriptionMask.SOURCE | |
928 | + PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT); |
929 | + } else { |
930 | + this.context.subscribe (PulseAudio.Context.SubscriptionMask.SINK | |
931 | + PulseAudio.Context.SubscriptionMask.SOURCE | |
932 | + PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT); |
933 | + } |
934 | + this.context.set_subscribe_callback (context_events_cb); |
935 | + this.ready = true; |
936 | + |
937 | + debug("Pulse state ready"); |
938 | + |
939 | + update_sink (); |
940 | + update_sink_input (); |
941 | + update_source (); |
942 | + update_stream (); |
943 | + }); |
944 | + |
945 | break; |
946 | |
947 | case Context.State.FAILED: |
948 | case Context.State.TERMINATED: |
949 | + warning("Pulse state disconnected, retrying."); |
950 | + this.ready = false; |
951 | if (_reconnect_timer == 0) |
952 | - _reconnect_timer = Timeout.add_seconds (2, reconnect_timeout); |
953 | + _reconnect_timer = Timeout.add_seconds (2, () => { |
954 | + _reconnect_timer = 0; |
955 | + reconnect_to_pulse (); |
956 | + return false; // G_SOURCE_REMOVE |
957 | + }); |
958 | + break; |
959 | + |
960 | + case Context.State.CONNECTING: |
961 | + case Context.State.AUTHORIZING: |
962 | + case Context.State.SETTING_NAME: |
963 | + debug(@"Pulse state initializing step $((int)c.get_state())"); |
964 | break; |
965 | |
966 | default: |
967 | - this.ready = false; |
968 | + warning("Unknown state returned by Pulse Audio context. Not reconnecting."); |
969 | break; |
970 | } |
971 | } |
972 | |
973 | - bool reconnect_timeout () |
974 | - { |
975 | - _reconnect_timer = 0; |
976 | - reconnect_to_pulse (); |
977 | - return false; // G_SOURCE_REMOVE |
978 | - } |
979 | - |
980 | void reconnect_to_pulse () |
981 | { |
982 | if (this.ready) { |
983 | this.context.disconnect (); |
984 | this.context = null; |
985 | this.ready = false; |
986 | + this._pulse_use_stream_restore = false; |
987 | } |
988 | |
989 | var props = new Proplist (); |
990 | @@ -447,8 +396,6 @@ |
991 | props.sets (Proplist.PROP_APPLICATION_ICON_NAME, "multimedia-volume-control"); |
992 | props.sets (Proplist.PROP_APPLICATION_VERSION, "0.1"); |
993 | |
994 | - reconnect_pulse_dbus (); |
995 | - |
996 | this.context = new PulseAudio.Context (loop.get_api(), null, props); |
997 | this.context.set_state_callback (context_state_callback); |
998 | |
999 | @@ -456,41 +403,35 @@ |
1000 | warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno())); |
1001 | } |
1002 | |
1003 | - void sink_info_list_callback_set_mute (PulseAudio.Context context, PulseAudio.SinkInfo? sink, int eol) { |
1004 | - if (sink != null) |
1005 | - context.set_sink_mute_by_index (sink.index, true, null); |
1006 | - } |
1007 | - |
1008 | - void sink_info_list_callback_unset_mute (PulseAudio.Context context, PulseAudio.SinkInfo? sink, int eol) { |
1009 | - if (sink != null) |
1010 | - context.set_sink_mute_by_index (sink.index, false, null); |
1011 | - } |
1012 | - |
1013 | - /* Mute operations */ |
1014 | + /******************************/ |
1015 | + /* Mute operations */ |
1016 | + /******************************/ |
1017 | + |
1018 | bool set_mute_internal (bool mute) |
1019 | { |
1020 | - return_val_if_fail (context.get_state () == Context.State.READY, false); |
1021 | + if (!this.ready) |
1022 | + return false; |
1023 | |
1024 | if (_mute != mute) { |
1025 | if (mute) |
1026 | - context.get_sink_info_list (sink_info_list_callback_set_mute); |
1027 | + context.get_sink_info_list ((context, sink, eol) => { |
1028 | + if (sink != null) |
1029 | + context.set_sink_mute_by_index (sink.index, true, null); |
1030 | + }); |
1031 | else |
1032 | - context.get_sink_info_list (sink_info_list_callback_unset_mute); |
1033 | + context.get_sink_info_list ((context, sink, eol) => { |
1034 | + if (sink != null) |
1035 | + context.set_sink_mute_by_index (sink.index, false, null); |
1036 | + }); |
1037 | return true; |
1038 | } else { |
1039 | return false; |
1040 | } |
1041 | } |
1042 | |
1043 | - public void set_mute (bool mute) |
1044 | - { |
1045 | - if (set_mute_internal (mute)) |
1046 | - sync_mute_to_accountsservice.begin (mute); |
1047 | - } |
1048 | - |
1049 | public void toggle_mute () |
1050 | { |
1051 | - this.set_mute (!this._mute); |
1052 | + this.mute = !this._mute; |
1053 | } |
1054 | |
1055 | public bool mute |
1056 | @@ -499,17 +440,17 @@ |
1057 | { |
1058 | return this._mute; |
1059 | } |
1060 | - } |
1061 | - |
1062 | - public bool is_playing |
1063 | - { |
1064 | - get |
1065 | + set |
1066 | { |
1067 | - return this._is_playing; |
1068 | + if (set_mute_internal (value)) |
1069 | + sync_mute_to_accountsservice.begin (value); |
1070 | } |
1071 | } |
1072 | |
1073 | - /* Volume operations */ |
1074 | + /******************************/ |
1075 | + /* Volume operations */ |
1076 | + /******************************/ |
1077 | + |
1078 | private static PulseAudio.Volume double_to_volume (double vol) |
1079 | { |
1080 | double tmp = (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED) * vol; |
1081 | @@ -522,79 +463,41 @@ |
1082 | return tmp / (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED); |
1083 | } |
1084 | |
1085 | - private void set_volume_success_cb (Context c, int success) |
1086 | - { |
1087 | - if ((bool)success) |
1088 | - this.notify_property("volume"); |
1089 | - } |
1090 | - |
1091 | - private void sink_info_set_volume_cb (Context c, SinkInfo? i, int eol) |
1092 | - { |
1093 | - if (i == null) |
1094 | - return; |
1095 | - |
1096 | - unowned CVolume cvol = i.volume; |
1097 | - cvol.scale (double_to_volume (_volume)); |
1098 | - c.set_sink_volume_by_index (i.index, cvol, set_volume_success_cb); |
1099 | - } |
1100 | - |
1101 | - private void server_info_cb_for_set_volume (Context c, ServerInfo? i) |
1102 | - { |
1103 | - if (i == null) |
1104 | - { |
1105 | - warning ("Could not get PulseAudio server info"); |
1106 | - return; |
1107 | - } |
1108 | - |
1109 | - context.get_sink_info_by_name (i.default_sink_name, sink_info_set_volume_cb); |
1110 | - } |
1111 | - |
1112 | - private async void set_volume_active_role () |
1113 | - { |
1114 | - string active_role_objp = _objp_role_alert; |
1115 | - |
1116 | - if (_active_sink_input != -1 && _active_sink_input in _sink_input_list) |
1117 | - active_role_objp = _sink_input_hash.get (_active_sink_input); |
1118 | - |
1119 | - try { |
1120 | - var builder = new VariantBuilder (new VariantType ("a(uu)")); |
1121 | - builder.add ("(uu)", 0, double_to_volume (_volume)); |
1122 | - Variant volume = builder.end (); |
1123 | - |
1124 | - /* Increase the signal counter so we can handle the callback */ |
1125 | - lock (_pa_volume_sig_count) { |
1126 | - _pa_volume_sig_count++; |
1127 | - } |
1128 | - |
1129 | - yield _pconn.call ("org.PulseAudio.Ext.StreamRestore1.RestoreEntry", |
1130 | - active_role_objp, "org.freedesktop.DBus.Properties", "Set", |
1131 | - new Variant ("(ssv)", "org.PulseAudio.Ext.StreamRestore1.RestoreEntry", "Volume", volume), |
1132 | - null, DBusCallFlags.NONE, -1); |
1133 | - |
1134 | - this.notify_property("volume"); |
1135 | - } catch (GLib.Error e) { |
1136 | - lock (_pa_volume_sig_count) { |
1137 | - _pa_volume_sig_count--; |
1138 | - } |
1139 | - warning ("unable to set volume for stream obj path %s (%s)", active_role_objp, e.message); |
1140 | - } |
1141 | - } |
1142 | - |
1143 | - bool set_volume_internal (double volume) |
1144 | - { |
1145 | - if (context.get_state () != Context.State.READY) |
1146 | + bool set_volume_internal (double newvolume) |
1147 | + { |
1148 | + if (!this.ready) |
1149 | return false; |
1150 | |
1151 | - if (_volume != volume) { |
1152 | + if (_volume != newvolume) { |
1153 | var old_high_volume = this.high_volume; |
1154 | - |
1155 | - _volume = volume; |
1156 | - if (_pulse_use_stream_restore) |
1157 | - set_volume_active_role.begin (); |
1158 | - else |
1159 | - context.get_server_info (server_info_cb_for_set_volume); |
1160 | - |
1161 | - this.notify_property("volume"); |
1162 | + /* Ideally, we'd be able to set this when we give the value to Pulse, but |
1163 | + we kinda have a Vala bug that's messing up capturing the variables. So |
1164 | + we're setting it here so that the VolumeControl object can track it all |
1165 | + the way through */ |
1166 | + /* https://bugzilla.gnome.org/show_bug.cgi?id=741485 */ |
1167 | + _volume = newvolume; |
1168 | + |
1169 | + context.get_server_info ((context, i) => { |
1170 | + if (i == null) |
1171 | + { |
1172 | + warning ("Could not get PulseAudio server info"); |
1173 | + return; |
1174 | + } |
1175 | + |
1176 | + context.get_sink_info_by_name (i.default_sink_name, (context, info, eol) => { |
1177 | + if (info == null) |
1178 | + return; |
1179 | + |
1180 | + unowned CVolume cvol = info.volume; |
1181 | + cvol.scale (double_to_volume (this._volume)); |
1182 | + context.set_sink_volume_by_index (info.index, cvol, (context, success) => { |
1183 | + if (!(bool)success) |
1184 | + return; |
1185 | + |
1186 | + this.notify_property("volume"); |
1187 | + }); |
1188 | + }); |
1189 | + }); |
1190 | |
1191 | if (this.high_volume != old_high_volume) |
1192 | this.notify_property("high-volume"); |
1193 | @@ -605,20 +508,6 @@ |
1194 | } |
1195 | } |
1196 | |
1197 | - void set_mic_volume_success_cb (Context c, int success) |
1198 | - { |
1199 | - if ((bool)success) |
1200 | - this.notify_property ("mic-volume"); |
1201 | - } |
1202 | - |
1203 | - void set_mic_volume_get_server_info_cb (PulseAudio.Context c, PulseAudio.ServerInfo? i) { |
1204 | - if (i != null) { |
1205 | - unowned CVolume cvol = CVolume (); |
1206 | - cvol = vol_set (cvol, 1, double_to_volume (_mic_volume)); |
1207 | - c.set_source_volume_by_name (i.default_source_name, cvol, set_mic_volume_success_cb); |
1208 | - } |
1209 | - } |
1210 | - |
1211 | public double volume { |
1212 | get { |
1213 | return _volume; |
1214 | @@ -635,94 +524,34 @@ |
1215 | return _mic_volume; |
1216 | } |
1217 | set { |
1218 | - return_if_fail (context.get_state () == Context.State.READY); |
1219 | + if (!this.ready) |
1220 | + return; |
1221 | |
1222 | + /* Ideally, we'd be able to set this when we give the value to Pulse, but |
1223 | + we kinda have a Vala bug that's messing up capturing the variables. So |
1224 | + we're setting it here so that the VolumeControl object can track it all |
1225 | + the way through */ |
1226 | + /* https://bugzilla.gnome.org/show_bug.cgi?id=741485 */ |
1227 | _mic_volume = value; |
1228 | |
1229 | - context.get_server_info (set_mic_volume_get_server_info_cb); |
1230 | - } |
1231 | - } |
1232 | - |
1233 | - /* PulseAudio Dbus (Stream Restore) logic */ |
1234 | - private void reconnect_pulse_dbus () |
1235 | - { |
1236 | - unowned string pulse_dbus_server_env = Environment.get_variable ("PULSE_DBUS_SERVER"); |
1237 | - string address; |
1238 | - |
1239 | - /* In case of a reconnect */ |
1240 | - _pulse_use_stream_restore = false; |
1241 | - _pa_volume_sig_count = 0; |
1242 | - |
1243 | - if (pulse_dbus_server_env != null) { |
1244 | - address = pulse_dbus_server_env; |
1245 | - } else { |
1246 | - DBusConnection conn; |
1247 | - Variant props; |
1248 | - |
1249 | - try { |
1250 | - conn = Bus.get_sync (BusType.SESSION); |
1251 | - } catch (GLib.IOError e) { |
1252 | - warning ("unable to get the dbus session bus: %s", e.message); |
1253 | - return; |
1254 | - } |
1255 | - |
1256 | - try { |
1257 | - var props_variant = conn.call_sync ("org.PulseAudio1", |
1258 | - "/org/pulseaudio/server_lookup1", "org.freedesktop.DBus.Properties", |
1259 | - "Get", new Variant ("(ss)", "org.PulseAudio.ServerLookup1", "Address"), |
1260 | - null, DBusCallFlags.NONE, -1); |
1261 | - props_variant.get ("(v)", out props); |
1262 | - address = props.get_string (); |
1263 | - } catch (GLib.Error e) { |
1264 | - warning ("unable to get pulse unix socket: %s", e.message); |
1265 | - return; |
1266 | - } |
1267 | - } |
1268 | - |
1269 | - stdout.printf ("PulseAudio dbus unix socket: %s\n", address); |
1270 | - try { |
1271 | - _pconn = new DBusConnection.for_address_sync (address, DBusConnectionFlags.AUTHENTICATION_CLIENT); |
1272 | - } catch (GLib.Error e) { |
1273 | - /* If it fails, it means the dbus pulse extension is not available */ |
1274 | - return; |
1275 | - } |
1276 | - |
1277 | - /* For pulse dbus related events */ |
1278 | - _pconn.add_filter (pulse_dbus_filter); |
1279 | - |
1280 | - /* Check if the 4 currently supported media roles are already available in StreamRestore |
1281 | - * Roles: multimedia, alert, alarm and phone */ |
1282 | - _objp_role_multimedia = stream_restore_get_object_path ("sink-input-by-media-role:multimedia"); |
1283 | - _objp_role_alert = stream_restore_get_object_path ("sink-input-by-media-role:alert"); |
1284 | - _objp_role_alarm = stream_restore_get_object_path ("sink-input-by-media-role:alarm"); |
1285 | - _objp_role_phone = stream_restore_get_object_path ("sink-input-by-media-role:phone"); |
1286 | - |
1287 | - /* Only use stream restore if every used role is available */ |
1288 | - if (_objp_role_multimedia != null && _objp_role_alert != null && _objp_role_alarm != null && _objp_role_phone != null) { |
1289 | - stdout.printf ("Using PulseAudio DBUS Stream Restore module\n"); |
1290 | - /* Restore volume and update default entry */ |
1291 | - update_active_sink_input.begin (-1); |
1292 | - _pulse_use_stream_restore = true; |
1293 | - } |
1294 | - } |
1295 | - |
1296 | - private string? stream_restore_get_object_path (string name) { |
1297 | - string? objp = null; |
1298 | - try { |
1299 | - Variant props_variant = _pconn.call_sync ("org.PulseAudio.Ext.StreamRestore1", |
1300 | - "/org/pulseaudio/stream_restore1", "org.PulseAudio.Ext.StreamRestore1", |
1301 | - "GetEntryByName", new Variant ("(s)", name), null, DBusCallFlags.NONE, -1); |
1302 | - /* Workaround for older versions of vala that don't provide get_objv */ |
1303 | - VariantIter iter = props_variant.iterator (); |
1304 | - iter.next ("o", &objp); |
1305 | - stdout.printf ("Found obj path %s for restore data named %s\n", objp, name); |
1306 | - } catch (GLib.Error e) { |
1307 | - warning ("unable to find stream restore data for: %s", name); |
1308 | - } |
1309 | - return objp; |
1310 | - } |
1311 | - |
1312 | + context.get_server_info ((context, info) => { |
1313 | + if (info == null) |
1314 | + return; |
1315 | + |
1316 | + unowned CVolume cvol = CVolume (); |
1317 | + cvol = vol_set (cvol, 1, double_to_volume (_mic_volume)); |
1318 | + context.set_source_volume_by_name (info.default_source_name, cvol, (context, success) => { |
1319 | + if ((bool)success) |
1320 | + this.notify_property ("mic-volume"); |
1321 | + }); |
1322 | + }); |
1323 | + } |
1324 | + } |
1325 | + |
1326 | + /******************************/ |
1327 | /* AccountsService operations */ |
1328 | + /******************************/ |
1329 | + |
1330 | private void accountsservice_props_changed_cb (DBusProxy proxy, Variant changed_properties, string[]? invalidated_properties) |
1331 | { |
1332 | Variant volume_variant = changed_properties.lookup_value ("Volume", new VariantType ("d")); |
1333 | |
1334 | === modified file 'tests/CMakeLists.txt' |
1335 | --- tests/CMakeLists.txt 2015-01-28 15:41:22 +0000 |
1336 | +++ tests/CMakeLists.txt 2015-01-30 23:01:09 +0000 |
1337 | @@ -41,6 +41,9 @@ |
1338 | vala_add(vala-mocks |
1339 | media-player-mock.vala |
1340 | ) |
1341 | +vala_add(vala-mocks |
1342 | + focus-tracker-mock.vala |
1343 | +) |
1344 | |
1345 | vala_finish(vala-mocks |
1346 | SOURCES |
1347 | @@ -92,6 +95,8 @@ |
1348 | pa-mock.cpp |
1349 | ) |
1350 | |
1351 | +target_link_libraries (pulse-mock ${PULSEAUDIO_LIBRARIES}) |
1352 | + |
1353 | ########################### |
1354 | # Name Watch Test |
1355 | ########################### |
1356 | @@ -135,6 +140,7 @@ |
1357 | volume-control-test |
1358 | indicator-sound-service-lib |
1359 | pulse-mock |
1360 | + vala-mocks-lib |
1361 | gtest |
1362 | ${TEST_LIBRARIES} |
1363 | ) |
1364 | @@ -207,3 +213,29 @@ |
1365 | greeter-list-test --gtest_filter=GreeterListTest.BasicIterator |
1366 | ) |
1367 | |
1368 | +########################### |
1369 | +# Indicator Test |
1370 | +########################### |
1371 | + |
1372 | +add_definitions( |
1373 | + -DINDICATOR_SOUND_SERVICE_BINARY="${CMAKE_BINARY_DIR}/src/indicator-sound-service" |
1374 | + -DPA_MOCK_LIB="${CMAKE_CURRENT_BINARY_DIR}/libpulse-mock.so" |
1375 | +) |
1376 | +add_executable (indicator-test indicator-test.cc) |
1377 | +target_link_libraries ( |
1378 | + indicator-test |
1379 | + gtest |
1380 | + ${SOUNDSERVICE_LIBRARIES} |
1381 | + ${TEST_LIBRARIES} |
1382 | +) |
1383 | + |
1384 | +# Split tests to work around libaccountservice sucking |
1385 | +add_test(indcator-test-phone-menu |
1386 | + indicator-test --gtest_filter=IndicatorTest.PhoneMenu |
1387 | +) |
1388 | +add_test(indcator-test-desktop-menu |
1389 | + indicator-test --gtest_filter=IndicatorTest.DesktopMenu |
1390 | +) |
1391 | +add_test(indcator-test-silent-actions |
1392 | + indicator-test --gtest_filter=IndicatorTest.SilentActions |
1393 | +) |
1394 | |
1395 | === modified file 'tests/accounts-service-mock.h' |
1396 | --- tests/accounts-service-mock.h 2014-03-04 21:24:10 +0000 |
1397 | +++ tests/accounts-service-mock.h 2015-01-30 23:01:09 +0000 |
1398 | @@ -17,6 +17,7 @@ |
1399 | * Ted Gould <ted@canonical.com> |
1400 | */ |
1401 | |
1402 | +#include <memory> |
1403 | #include <libdbustest/dbus-test.h> |
1404 | |
1405 | class AccountsServiceMock |
1406 | @@ -24,11 +25,14 @@ |
1407 | DbusTestDbusMock * mock = nullptr; |
1408 | DbusTestDbusMockObject * soundobj = nullptr; |
1409 | DbusTestDbusMockObject * userobj = nullptr; |
1410 | + DbusTestDbusMockObject * syssoundobj = nullptr; |
1411 | |
1412 | public: |
1413 | AccountsServiceMock () { |
1414 | mock = dbus_test_dbus_mock_new("org.freedesktop.Accounts"); |
1415 | |
1416 | + dbus_test_task_set_bus(DBUS_TEST_TASK(mock), DBUS_TEST_SERVICE_BUS_SYSTEM); |
1417 | + |
1418 | DbusTestDbusMockObject * baseobj = dbus_test_dbus_mock_get_object(mock, "/org/freedesktop/Accounts", "org.freedesktop.Accounts", NULL); |
1419 | |
1420 | dbus_test_dbus_mock_object_add_method(mock, baseobj, |
1421 | @@ -80,6 +84,11 @@ |
1422 | dbus_test_dbus_mock_object_add_property(mock, soundobj, |
1423 | "ArtUrl", G_VARIANT_TYPE_STRING, |
1424 | g_variant_new_string(""), NULL); |
1425 | + |
1426 | + syssoundobj = dbus_test_dbus_mock_get_object(mock, "/user", "com.ubuntu.touch.AccountsService.Sound", NULL); |
1427 | + dbus_test_dbus_mock_object_add_property(mock, syssoundobj, |
1428 | + "SilentMode", G_VARIANT_TYPE_BOOLEAN, |
1429 | + g_variant_new_boolean(FALSE), NULL); |
1430 | } |
1431 | |
1432 | ~AccountsServiceMock () { |
1433 | @@ -87,6 +96,18 @@ |
1434 | g_clear_object(&mock); |
1435 | } |
1436 | |
1437 | + void setSilentMode (bool modeValue) { |
1438 | + dbus_test_dbus_mock_object_update_property(mock, syssoundobj, |
1439 | + "SilentMode", g_variant_new_boolean(modeValue ? TRUE : FALSE), |
1440 | + NULL); |
1441 | + } |
1442 | + |
1443 | + operator std::shared_ptr<DbusTestTask> () { |
1444 | + return std::shared_ptr<DbusTestTask>( |
1445 | + DBUS_TEST_TASK(g_object_ref(mock)), |
1446 | + [](DbusTestTask * task) { g_clear_object(&task); }); |
1447 | + } |
1448 | + |
1449 | operator DbusTestTask* () { |
1450 | return DBUS_TEST_TASK(mock); |
1451 | } |
1452 | |
1453 | === modified file 'tests/accounts-service-user.cc' |
1454 | --- tests/accounts-service-user.cc 2014-03-03 20:50:52 +0000 |
1455 | +++ tests/accounts-service-user.cc 2015-01-30 23:01:09 +0000 |
1456 | @@ -42,14 +42,13 @@ |
1457 | |
1458 | virtual void SetUp() { |
1459 | service = dbus_test_service_new(NULL); |
1460 | + dbus_test_service_set_bus(service, DBUS_TEST_SERVICE_BUS_BOTH); |
1461 | |
1462 | AccountsServiceMock service_mock; |
1463 | |
1464 | dbus_test_service_add_task(service, (DbusTestTask*)service_mock); |
1465 | dbus_test_service_start_tasks(service); |
1466 | |
1467 | - g_setenv("DBUS_SYSTEM_BUS_ADDRESS", g_getenv("DBUS_SESSION_BUS_ADDRESS"), TRUE); |
1468 | - |
1469 | session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); |
1470 | ASSERT_NE(nullptr, session); |
1471 | g_dbus_connection_set_exit_on_close(session, FALSE); |
1472 | |
1473 | === added file 'tests/focus-tracker-mock.vala' |
1474 | --- tests/focus-tracker-mock.vala 1970-01-01 00:00:00 +0000 |
1475 | +++ tests/focus-tracker-mock.vala 2015-01-30 23:01:09 +0000 |
1476 | @@ -0,0 +1,27 @@ |
1477 | +/* |
1478 | + * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- |
1479 | + * Copyright © 2015 Canonical Ltd. |
1480 | + * |
1481 | + * This program is free software; you can redistribute it and/or modify |
1482 | + * it under the terms of the GNU General Public License as published by |
1483 | + * the Free Software Foundation; version 3. |
1484 | + * |
1485 | + * This program is distributed in the hope that it will be useful, |
1486 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1487 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1488 | + * GNU General Public License for more details. |
1489 | + * |
1490 | + * You should have received a copy of the GNU General Public License |
1491 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1492 | + * |
1493 | + * Authors: |
1494 | + * Ted Gould <ted@canonical.com> |
1495 | + */ |
1496 | + |
1497 | +public class FocusTrackerMock : FocusTracker { |
1498 | + public override string focused_appid { get; private set; default = "unknown"; } |
1499 | + |
1500 | + public void mock_set_appid (string newappid) { |
1501 | + this.focused_appid = newappid; |
1502 | + } |
1503 | +} |
1504 | |
1505 | === added file 'tests/indicator-fixture.h' |
1506 | --- tests/indicator-fixture.h 1970-01-01 00:00:00 +0000 |
1507 | +++ tests/indicator-fixture.h 2015-01-30 23:01:09 +0000 |
1508 | @@ -0,0 +1,444 @@ |
1509 | +/* |
1510 | + * Copyright © 2014 Canonical Ltd. |
1511 | + * |
1512 | + * This program is free software; you can redistribute it and/or modify |
1513 | + * it under the terms of the GNU General Public License as published by |
1514 | + * the Free Software Foundation; version 3. |
1515 | + * |
1516 | + * This program is distributed in the hope that it will be useful, |
1517 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1518 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1519 | + * GNU General Public License for more details. |
1520 | + * |
1521 | + * You should have received a copy of the GNU General Public License |
1522 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1523 | + * |
1524 | + * Authors: |
1525 | + * Ted Gould <ted@canonical.com> |
1526 | + */ |
1527 | + |
1528 | +#include <memory> |
1529 | +#include <algorithm> |
1530 | +#include <string> |
1531 | + |
1532 | +#include <gtest/gtest.h> |
1533 | +#include <gio/gio.h> |
1534 | +#include <libdbustest/dbus-test.h> |
1535 | + |
1536 | +class IndicatorFixture : public ::testing::Test |
1537 | +{ |
1538 | + private: |
1539 | + std::string _indicatorPath; |
1540 | + std::string _indicatorAddress; |
1541 | + std::vector<std::shared_ptr<DbusTestTask>> _mocks; |
1542 | + |
1543 | + class PerRunData { |
1544 | + public: |
1545 | + /* We're private in the fixture but other than that we don't care, |
1546 | + we don't leak out. This object's purpose isn't to hide data it is |
1547 | + to make the lifecycle of the items more clear. */ |
1548 | + std::shared_ptr<GMenuModel> _menu; |
1549 | + std::shared_ptr<GActionGroup> _actions; |
1550 | + DbusTestService * _session_service; |
1551 | + DbusTestService * _system_service; |
1552 | + DbusTestTask * _test_indicator; |
1553 | + DbusTestTask * _test_dummy; |
1554 | + GDBusConnection * _session; |
1555 | + GDBusConnection * _system; |
1556 | + |
1557 | + PerRunData (const std::string& indicatorPath, const std::string& indicatorAddress, std::vector<std::shared_ptr<DbusTestTask>>& mocks) |
1558 | + : _menu(nullptr) |
1559 | + , _session(nullptr) |
1560 | + { |
1561 | + _session_service = dbus_test_service_new(nullptr); |
1562 | + dbus_test_service_set_bus(_session_service, DBUS_TEST_SERVICE_BUS_SESSION); |
1563 | + |
1564 | + _system_service = dbus_test_service_new(nullptr); |
1565 | + dbus_test_service_set_bus(_system_service, DBUS_TEST_SERVICE_BUS_SYSTEM); |
1566 | + |
1567 | + _test_indicator = DBUS_TEST_TASK(dbus_test_process_new(indicatorPath.c_str())); |
1568 | + dbus_test_task_set_name(_test_indicator, "Indicator"); |
1569 | + dbus_test_service_add_task(_session_service, _test_indicator); |
1570 | + |
1571 | + _test_dummy = dbus_test_task_new(); |
1572 | + dbus_test_task_set_wait_for(_test_dummy, indicatorAddress.c_str()); |
1573 | + dbus_test_task_set_name(_test_dummy, "Dummy"); |
1574 | + dbus_test_service_add_task(_session_service, _test_dummy); |
1575 | + |
1576 | + std::for_each(mocks.begin(), mocks.end(), [this](std::shared_ptr<DbusTestTask> task) { |
1577 | + if (dbus_test_task_get_bus(task.get()) == DBUS_TEST_SERVICE_BUS_SYSTEM) { |
1578 | + dbus_test_service_add_task(_system_service, task.get()); |
1579 | + } else { |
1580 | + dbus_test_service_add_task(_session_service, task.get()); |
1581 | + } |
1582 | + }); |
1583 | + |
1584 | + g_debug("Starting System Bus"); |
1585 | + dbus_test_service_start_tasks(_system_service); |
1586 | + _system = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr); |
1587 | + g_dbus_connection_set_exit_on_close(_system, FALSE); |
1588 | + |
1589 | + g_debug("Starting Session Bus"); |
1590 | + dbus_test_service_start_tasks(_session_service); |
1591 | + _session = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); |
1592 | + g_dbus_connection_set_exit_on_close(_session, FALSE); |
1593 | + } |
1594 | + |
1595 | + virtual ~PerRunData (void) { |
1596 | + _menu.reset(); |
1597 | + _actions.reset(); |
1598 | + |
1599 | + /* D-Bus Test Stuff */ |
1600 | + g_clear_object(&_test_dummy); |
1601 | + g_clear_object(&_test_indicator); |
1602 | + g_clear_object(&_session_service); |
1603 | + g_clear_object(&_system_service); |
1604 | + |
1605 | + /* Wait for D-Bus session bus to go */ |
1606 | + if (!g_dbus_connection_is_closed(_session)) { |
1607 | + g_dbus_connection_close_sync(_session, nullptr, nullptr); |
1608 | + } |
1609 | + g_clear_object(&_session); |
1610 | + |
1611 | + if (!g_dbus_connection_is_closed(_system)) { |
1612 | + g_dbus_connection_close_sync(_system, nullptr, nullptr); |
1613 | + } |
1614 | + g_clear_object(&_system); |
1615 | + } |
1616 | + }; |
1617 | + |
1618 | + std::shared_ptr<PerRunData> run; |
1619 | + |
1620 | + public: |
1621 | + virtual ~IndicatorFixture() = default; |
1622 | + |
1623 | + IndicatorFixture (const std::string& path, |
1624 | + const std::string& addr) |
1625 | + : _indicatorPath(path) |
1626 | + , _indicatorAddress(addr) |
1627 | + { |
1628 | + }; |
1629 | + |
1630 | + |
1631 | + protected: |
1632 | + virtual void SetUp() override |
1633 | + { |
1634 | + run = std::make_shared<PerRunData>(_indicatorPath, _indicatorAddress, _mocks); |
1635 | + |
1636 | + _mocks.clear(); |
1637 | + } |
1638 | + |
1639 | + virtual void TearDown() override |
1640 | + { |
1641 | + run.reset(); |
1642 | + } |
1643 | + |
1644 | + void addMock (std::shared_ptr<DbusTestTask> mock) |
1645 | + { |
1646 | + _mocks.push_back(mock); |
1647 | + } |
1648 | + |
1649 | + std::shared_ptr<DbusTestTask> buildBustleMock (const std::string& filename, DbusTestServiceBus bus = DBUS_TEST_SERVICE_BUS_BOTH) |
1650 | + { |
1651 | + return std::shared_ptr<DbusTestTask>([filename, bus]() { |
1652 | + DbusTestTask * bustle = DBUS_TEST_TASK(dbus_test_bustle_new(filename.c_str())); |
1653 | + dbus_test_task_set_name(bustle, "Bustle"); |
1654 | + dbus_test_task_set_bus(bustle, bus); |
1655 | + return bustle; |
1656 | + }(), [](DbusTestTask * bustle) { |
1657 | + g_clear_object(&bustle); |
1658 | + }); |
1659 | + } |
1660 | + |
1661 | + private: |
1662 | + void waitForCore (GObject * obj, const gchar * signalname) { |
1663 | + auto loop = g_main_loop_new(nullptr, FALSE); |
1664 | + |
1665 | + /* Our two exit criteria */ |
1666 | + gulong signal = g_signal_connect_swapped(obj, signalname, G_CALLBACK(g_main_loop_quit), loop); |
1667 | + guint timer = g_timeout_add_seconds(5, [](gpointer user_data) -> gboolean { |
1668 | + g_warning("Menu Timeout"); |
1669 | + g_main_loop_quit((GMainLoop *)user_data); |
1670 | + return G_SOURCE_CONTINUE; |
1671 | + }, loop); |
1672 | + |
1673 | + /* Wait for sync */ |
1674 | + g_main_loop_run(loop); |
1675 | + |
1676 | + /* Clean up */ |
1677 | + g_source_remove(timer); |
1678 | + g_signal_handler_disconnect(obj, signal); |
1679 | + |
1680 | + g_main_loop_unref(loop); |
1681 | + } |
1682 | + |
1683 | + void menuWaitForItems (const std::shared_ptr<GMenuModel>& menu) { |
1684 | + auto count = g_menu_model_get_n_items(menu.get()); |
1685 | + |
1686 | + if (count != 0) |
1687 | + return; |
1688 | + |
1689 | + waitForCore(G_OBJECT(menu.get()), "items-changed"); |
1690 | + } |
1691 | + |
1692 | + void agWaitForActions (const std::shared_ptr<GActionGroup>& group) { |
1693 | + auto list = std::shared_ptr<gchar *>( |
1694 | + g_action_group_list_actions(group.get()), |
1695 | + [](gchar ** list) { |
1696 | + g_strfreev(list); |
1697 | + }); |
1698 | + |
1699 | + if (g_strv_length(list.get()) != 0) { |
1700 | + return; |
1701 | + } |
1702 | + |
1703 | + waitForCore(G_OBJECT(group.get()), "action-added"); |
1704 | + } |
1705 | + |
1706 | + protected: |
1707 | + void setMenu (const std::string& path) { |
1708 | + run->_menu.reset(); |
1709 | + |
1710 | + g_debug("Getting Menu: %s:%s", _indicatorAddress.c_str(), path.c_str()); |
1711 | + run->_menu = std::shared_ptr<GMenuModel>(G_MENU_MODEL(g_dbus_menu_model_get(run->_session, _indicatorAddress.c_str(), path.c_str())), [](GMenuModel * modelptr) { |
1712 | + g_clear_object(&modelptr); |
1713 | + }); |
1714 | + |
1715 | + menuWaitForItems(run->_menu); |
1716 | + } |
1717 | + |
1718 | + void setActions (const std::string& path) { |
1719 | + run->_actions.reset(); |
1720 | + |
1721 | + run->_actions = std::shared_ptr<GActionGroup>(G_ACTION_GROUP(g_dbus_action_group_get(run->_session, _indicatorAddress.c_str(), path.c_str())), [](GActionGroup * groupptr) { |
1722 | + g_clear_object(&groupptr); |
1723 | + }); |
1724 | + |
1725 | + agWaitForActions(run->_actions); |
1726 | + } |
1727 | + |
1728 | + testing::AssertionResult expectActionExists (const gchar * nameStr, const std::string& name) { |
1729 | + bool hasit = g_action_group_has_action(run->_actions.get(), name.c_str()); |
1730 | + |
1731 | + if (!hasit) { |
1732 | + auto result = testing::AssertionFailure(); |
1733 | + result << |
1734 | + " Action: " << nameStr << std::endl << |
1735 | + " Expected: " << "Exists" << std::endl << |
1736 | + " Actual: " << "No action found" << std::endl; |
1737 | + |
1738 | + return result; |
1739 | + } |
1740 | + |
1741 | + auto result = testing::AssertionSuccess(); |
1742 | + return result; |
1743 | + } |
1744 | + |
1745 | + testing::AssertionResult expectActionStateType (const char * nameStr, const char * typeStr, const std::string& name, const GVariantType * type) { |
1746 | + auto atype = g_action_group_get_action_state_type(run->_actions.get(), name.c_str()); |
1747 | + bool same = false; |
1748 | + |
1749 | + if (atype != nullptr) { |
1750 | + same = g_variant_type_equal(atype, type); |
1751 | + } |
1752 | + |
1753 | + if (!same) { |
1754 | + auto result = testing::AssertionFailure(); |
1755 | + result << |
1756 | + " Action: " << nameStr << std::endl << |
1757 | + " Expected: " << typeStr << std::endl << |
1758 | + " Actual: " << g_variant_type_peek_string(atype) << std::endl; |
1759 | + |
1760 | + return result; |
1761 | + } |
1762 | + |
1763 | + auto result = testing::AssertionSuccess(); |
1764 | + return result; |
1765 | + } |
1766 | + |
1767 | + testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, GVariant * value) { |
1768 | + auto varref = std::shared_ptr<GVariant>(g_variant_ref_sink(value), [](GVariant * varptr) { |
1769 | + if (varptr != nullptr) |
1770 | + g_variant_unref(varptr); |
1771 | + }); |
1772 | + auto aval = std::shared_ptr<GVariant>(g_action_group_get_action_state(run->_actions.get(), name.c_str()), [] (GVariant * varptr) { |
1773 | + if (varptr != nullptr) |
1774 | + g_variant_unref(varptr); |
1775 | + }); |
1776 | + bool match = false; |
1777 | + |
1778 | + if (aval != nullptr) { |
1779 | + match = g_variant_equal(aval.get(), varref.get()); |
1780 | + } |
1781 | + |
1782 | + if (!match) { |
1783 | + gchar * attstr = nullptr; |
1784 | + |
1785 | + if (aval != nullptr) { |
1786 | + attstr = g_variant_print(aval.get(), TRUE); |
1787 | + } else { |
1788 | + attstr = g_strdup("nullptr"); |
1789 | + } |
1790 | + |
1791 | + auto result = testing::AssertionFailure(); |
1792 | + result << |
1793 | + " Action: " << nameStr << std::endl << |
1794 | + " Expected: " << valueStr << std::endl << |
1795 | + " Actual: " << attstr << std::endl; |
1796 | + |
1797 | + g_free(attstr); |
1798 | + |
1799 | + return result; |
1800 | + } else { |
1801 | + auto result = testing::AssertionSuccess(); |
1802 | + return result; |
1803 | + } |
1804 | + } |
1805 | + |
1806 | + testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, bool value) { |
1807 | + GVariant * var = g_variant_new_boolean(value); |
1808 | + return expectActionStateIs(nameStr, valueStr, name, var); |
1809 | + } |
1810 | + |
1811 | + testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, std::string value) { |
1812 | + GVariant * var = g_variant_new_string(value.c_str()); |
1813 | + return expectActionStateIs(nameStr, valueStr, name, var); |
1814 | + } |
1815 | + |
1816 | + testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, const char * value) { |
1817 | + GVariant * var = g_variant_new_string(value); |
1818 | + return expectActionStateIs(nameStr, valueStr, name, var); |
1819 | + } |
1820 | + |
1821 | + testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, double value) { |
1822 | + GVariant * var = g_variant_new_double(value); |
1823 | + return expectActionStateIs(nameStr, valueStr, name, var); |
1824 | + } |
1825 | + |
1826 | + testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, float value) { |
1827 | + GVariant * var = g_variant_new_double(value); |
1828 | + return expectActionStateIs(nameStr, valueStr, name, var); |
1829 | + } |
1830 | + |
1831 | + |
1832 | + private: |
1833 | + std::shared_ptr<GVariant> getMenuAttributeVal (int location, std::shared_ptr<GMenuModel>& menu, const std::string& attribute, std::shared_ptr<GVariant>& value) { |
1834 | + if (!(location < g_menu_model_get_n_items(menu.get()))) { |
1835 | + return nullptr; |
1836 | + } |
1837 | + |
1838 | + if (location >= g_menu_model_get_n_items(menu.get())) |
1839 | + return nullptr; |
1840 | + |
1841 | + auto menuval = std::shared_ptr<GVariant>(g_menu_model_get_item_attribute_value(menu.get(), location, attribute.c_str(), g_variant_get_type(value.get())), [](GVariant * varptr) { |
1842 | + if (varptr != nullptr) |
1843 | + g_variant_unref(varptr); |
1844 | + }); |
1845 | + |
1846 | + return menuval; |
1847 | + } |
1848 | + |
1849 | + std::shared_ptr<GVariant> getMenuAttributeRecurse (std::vector<int>::const_iterator menuLocation, std::vector<int>::const_iterator menuEnd, const std::string& attribute, std::shared_ptr<GVariant>& value, std::shared_ptr<GMenuModel>& menu) { |
1850 | + if (menuLocation == menuEnd) |
1851 | + return nullptr; |
1852 | + |
1853 | + if (menuLocation + 1 == menuEnd) |
1854 | + return getMenuAttributeVal(*menuLocation, menu, attribute, value); |
1855 | + |
1856 | + auto clearfunc = [](GMenuModel * modelptr) { |
1857 | + g_clear_object(&modelptr); |
1858 | + }; |
1859 | + |
1860 | + auto submenu = std::shared_ptr<GMenuModel>(g_menu_model_get_item_link(menu.get(), *menuLocation, G_MENU_LINK_SUBMENU), clearfunc); |
1861 | + |
1862 | + if (submenu == nullptr) |
1863 | + submenu = std::shared_ptr<GMenuModel>(g_menu_model_get_item_link(menu.get(), *menuLocation, G_MENU_LINK_SECTION), clearfunc); |
1864 | + |
1865 | + if (submenu == nullptr) |
1866 | + return nullptr; |
1867 | + |
1868 | + menuWaitForItems(submenu); |
1869 | + |
1870 | + return getMenuAttributeRecurse(menuLocation + 1, menuEnd, attribute, value, submenu); |
1871 | + } |
1872 | + |
1873 | + protected: |
1874 | + testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const gchar * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, GVariant * value) { |
1875 | + auto varref = std::shared_ptr<GVariant>(g_variant_ref_sink(value), [](GVariant * varptr) { |
1876 | + if (varptr != nullptr) |
1877 | + g_variant_unref(varptr); |
1878 | + }); |
1879 | + |
1880 | + auto attrib = getMenuAttributeRecurse(menuLocation.cbegin(), menuLocation.cend(), attribute, varref, run->_menu); |
1881 | + bool same = false; |
1882 | + |
1883 | + if (attrib != nullptr && varref != nullptr) { |
1884 | + same = g_variant_equal(attrib.get(), varref.get()); |
1885 | + } |
1886 | + |
1887 | + if (!same) { |
1888 | + gchar * attstr = nullptr; |
1889 | + |
1890 | + if (attrib != nullptr) { |
1891 | + attstr = g_variant_print(attrib.get(), TRUE); |
1892 | + } else { |
1893 | + attstr = g_strdup("nullptr"); |
1894 | + } |
1895 | + |
1896 | + auto result = testing::AssertionFailure(); |
1897 | + result << |
1898 | + " Menu: " << menuLocationStr << std::endl << |
1899 | + " Attribute: " << attributeStr << std::endl << |
1900 | + " Expected: " << valueStr << std::endl << |
1901 | + " Actual: " << attstr << std::endl; |
1902 | + |
1903 | + g_free(attstr); |
1904 | + |
1905 | + return result; |
1906 | + } else { |
1907 | + auto result = testing::AssertionSuccess(); |
1908 | + return result; |
1909 | + } |
1910 | + } |
1911 | + |
1912 | + testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const gchar * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, bool value) { |
1913 | + GVariant * var = g_variant_new_boolean(value); |
1914 | + return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var); |
1915 | + } |
1916 | + |
1917 | + testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const gchar * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, std::string value) { |
1918 | + GVariant * var = g_variant_new_string(value.c_str()); |
1919 | + return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var); |
1920 | + } |
1921 | + |
1922 | + testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const gchar * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, const char * value) { |
1923 | + GVariant * var = g_variant_new_string(value); |
1924 | + return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var); |
1925 | + } |
1926 | + |
1927 | +}; |
1928 | + |
1929 | +#define EXPECT_MENU_ATTRIB(menu, attrib, value) \ |
1930 | + EXPECT_PRED_FORMAT3(IndicatorFixture::expectMenuAttribute, menu, attrib, value) |
1931 | + |
1932 | +#define ASSERT_MENU_ATTRIB(menu, attrib, value) \ |
1933 | + ASSERT_PRED_FORMAT3(IndicatorFixture::expectMenuAttribute, menu, attrib, value) |
1934 | + |
1935 | +#define ASSERT_ACTION_EXISTS(action) \ |
1936 | + ASSERT_PRED_FORMAT1(IndicatorFixture::expectActionExists, action) |
1937 | + |
1938 | +#define EXPECT_ACTION_EXISTS(action) \ |
1939 | + EXPECT_PRED_FORMAT1(IndicatorFixture::expectActionExists, action) |
1940 | + |
1941 | +#define EXPECT_ACTION_STATE(action, value) \ |
1942 | + EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionStateIs, action, value) |
1943 | + |
1944 | +#define ASSERT_ACTION_STATE(action, value) \ |
1945 | + ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionStateIs, action, value) |
1946 | + |
1947 | +#define EXPECT_ACTION_STATE_TYPE(action, type) \ |
1948 | + EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionStateType, action, type) |
1949 | + |
1950 | +#define ASSERT_ACTION_STATE_TYPE(action, type) \ |
1951 | + ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionStateType, action, type) |
1952 | + |
1953 | |
1954 | === added file 'tests/indicator-test.cc' |
1955 | --- tests/indicator-test.cc 1970-01-01 00:00:00 +0000 |
1956 | +++ tests/indicator-test.cc 2015-01-30 23:01:09 +0000 |
1957 | @@ -0,0 +1,110 @@ |
1958 | +/* |
1959 | + * Copyright © 2014 Canonical Ltd. |
1960 | + * |
1961 | + * This program is free software; you can redistribute it and/or modify |
1962 | + * it under the terms of the GNU General Public License as published by |
1963 | + * the Free Software Foundation; version 3. |
1964 | + * |
1965 | + * This program is distributed in the hope that it will be useful, |
1966 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1967 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1968 | + * GNU General Public License for more details. |
1969 | + * |
1970 | + * You should have received a copy of the GNU General Public License |
1971 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1972 | + * |
1973 | + * Authors: |
1974 | + * Ted Gould <ted@canonical.com> |
1975 | + */ |
1976 | + |
1977 | +#include <gtest/gtest.h> |
1978 | +#include <gio/gio.h> |
1979 | + |
1980 | +#include "indicator-fixture.h" |
1981 | +#include "accounts-service-mock.h" |
1982 | + |
1983 | +class IndicatorTest : public IndicatorFixture |
1984 | +{ |
1985 | +protected: |
1986 | + IndicatorTest (void) : |
1987 | + IndicatorFixture(INDICATOR_SOUND_SERVICE_BINARY, "com.canonical.indicator.sound") |
1988 | + { |
1989 | + } |
1990 | + |
1991 | + std::shared_ptr<AccountsServiceMock> as; |
1992 | + |
1993 | + virtual void SetUp() override |
1994 | + { |
1995 | + //addMock(buildBustleMock("indicator-test-session.bustle", DBUS_TEST_SERVICE_BUS_SESSION)); |
1996 | + //addMock(buildBustleMock("indicator-test-system.bustle", DBUS_TEST_SERVICE_BUS_SYSTEM)); |
1997 | + g_setenv("LD_PRELOAD", PA_MOCK_LIB, TRUE); |
1998 | + |
1999 | + as = std::make_shared<AccountsServiceMock>(); |
2000 | + addMock(*as); |
2001 | + |
2002 | + IndicatorFixture::SetUp(); |
2003 | + } |
2004 | + |
2005 | + virtual void TearDown() override |
2006 | + { |
2007 | + as.reset(); |
2008 | + |
2009 | + IndicatorFixture::TearDown(); |
2010 | + } |
2011 | + |
2012 | +}; |
2013 | + |
2014 | + |
2015 | +TEST_F(IndicatorTest, PhoneMenu) { |
2016 | + setMenu("/com/canonical/indicator/sound/phone"); |
2017 | + |
2018 | + EXPECT_MENU_ATTRIB({0}, "action", "indicator.root"); |
2019 | + EXPECT_MENU_ATTRIB({0}, "x-canonical-type", "com.canonical.indicator.root"); |
2020 | + EXPECT_MENU_ATTRIB({0}, "x-canonical-scroll-action", "indicator.scroll"); |
2021 | + EXPECT_MENU_ATTRIB({0}, "x-canonical-secondary-action", "indicator.mute"); |
2022 | + |
2023 | + EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "action", "indicator.silent-mode"); |
2024 | + EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "label", "Silent Mode"); |
2025 | + |
2026 | + EXPECT_MENU_ATTRIB(std::vector<int>({0, 1}), "action", "indicator.phone-settings"); |
2027 | + EXPECT_MENU_ATTRIB(std::vector<int>({0, 1}), "label", "Sound Settings…"); |
2028 | +} |
2029 | + |
2030 | +TEST_F(IndicatorTest, DesktopMenu) { |
2031 | + setMenu("/com/canonical/indicator/sound/desktop"); |
2032 | + |
2033 | + EXPECT_MENU_ATTRIB({0}, "action", "indicator.root"); |
2034 | + EXPECT_MENU_ATTRIB({0}, "x-canonical-type", "com.canonical.indicator.root"); |
2035 | + EXPECT_MENU_ATTRIB({0}, "x-canonical-scroll-action", "indicator.scroll"); |
2036 | + EXPECT_MENU_ATTRIB({0}, "x-canonical-secondary-action", "indicator.mute"); |
2037 | + |
2038 | + EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "action", "indicator.mute"); |
2039 | + EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "label", "Mute"); |
2040 | + |
2041 | + EXPECT_MENU_ATTRIB(std::vector<int>({0, 2}), "action", "indicator.desktop-settings"); |
2042 | + EXPECT_MENU_ATTRIB(std::vector<int>({0, 2}), "label", "Sound Settings…"); |
2043 | +} |
2044 | + |
2045 | +TEST_F(IndicatorTest, BaseActions) { |
2046 | + setActions("/com/canonical/indicator/sound"); |
2047 | + |
2048 | + ASSERT_ACTION_EXISTS("root"); |
2049 | + ASSERT_ACTION_STATE_TYPE("root", G_VARIANT_TYPE("a{sv}")); |
2050 | + |
2051 | + ASSERT_ACTION_EXISTS("scroll"); |
2052 | + |
2053 | + ASSERT_ACTION_EXISTS("silent-mode"); |
2054 | + ASSERT_ACTION_STATE_TYPE("silent-mode", G_VARIANT_TYPE_BOOLEAN); |
2055 | + EXPECT_ACTION_STATE("silent-mode", false); |
2056 | + |
2057 | + ASSERT_ACTION_EXISTS("mute"); |
2058 | + ASSERT_ACTION_STATE_TYPE("mute", G_VARIANT_TYPE_BOOLEAN); |
2059 | + EXPECT_ACTION_STATE("mute", false); |
2060 | + |
2061 | + ASSERT_ACTION_EXISTS("mic-volume"); |
2062 | + ASSERT_ACTION_STATE_TYPE("mic-volume", G_VARIANT_TYPE_DOUBLE); |
2063 | + |
2064 | + ASSERT_ACTION_EXISTS("volume"); |
2065 | + ASSERT_ACTION_STATE_TYPE("volume", G_VARIANT_TYPE_DOUBLE); |
2066 | +} |
2067 | + |
2068 | |
2069 | === modified file 'tests/media-player-user.cc' |
2070 | --- tests/media-player-user.cc 2014-03-25 15:54:57 +0000 |
2071 | +++ tests/media-player-user.cc 2015-01-30 23:01:09 +0000 |
2072 | @@ -34,30 +34,22 @@ |
2073 | DbusTestService * service = NULL; |
2074 | AccountsServiceMock service_mock; |
2075 | |
2076 | - GDBusConnection * session = NULL; |
2077 | GDBusConnection * system = NULL; |
2078 | GDBusProxy * proxy = NULL; |
2079 | |
2080 | virtual void SetUp() { |
2081 | service = dbus_test_service_new(NULL); |
2082 | - |
2083 | + dbus_test_service_set_bus(service, DBUS_TEST_SERVICE_BUS_SYSTEM); |
2084 | |
2085 | dbus_test_service_add_task(service, (DbusTestTask*)service_mock); |
2086 | dbus_test_service_start_tasks(service); |
2087 | |
2088 | - g_setenv("DBUS_SYSTEM_BUS_ADDRESS", g_getenv("DBUS_SESSION_BUS_ADDRESS"), TRUE); |
2089 | - |
2090 | - session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); |
2091 | - ASSERT_NE(nullptr, session); |
2092 | - g_dbus_connection_set_exit_on_close(session, FALSE); |
2093 | - g_object_add_weak_pointer(G_OBJECT(session), (gpointer *)&session); |
2094 | - |
2095 | system = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); |
2096 | ASSERT_NE(nullptr, system); |
2097 | g_dbus_connection_set_exit_on_close(system, FALSE); |
2098 | g_object_add_weak_pointer(G_OBJECT(system), (gpointer *)&system); |
2099 | |
2100 | - proxy = g_dbus_proxy_new_sync(session, |
2101 | + proxy = g_dbus_proxy_new_sync(system, |
2102 | G_DBUS_PROXY_FLAGS_NONE, |
2103 | NULL, |
2104 | "org.freedesktop.Accounts", |
2105 | @@ -71,7 +63,6 @@ |
2106 | g_clear_object(&proxy); |
2107 | g_clear_object(&service); |
2108 | |
2109 | - g_object_unref(session); |
2110 | g_object_unref(system); |
2111 | |
2112 | #if 0 |
2113 | @@ -189,6 +180,9 @@ |
2114 | set_property("Album", g_variant_new_string("Vinyl is dead")); |
2115 | set_property("ArtUrl", g_variant_new_string("http://art.url")); |
2116 | |
2117 | + /* Ensure the properties get set before we pull them */ |
2118 | + loop(100); |
2119 | + |
2120 | /* Build our media player */ |
2121 | MediaPlayerUser * player = media_player_user_new("user"); |
2122 | ASSERT_NE(nullptr, player); |
2123 | |
2124 | === modified file 'tests/pa-mock.cpp' |
2125 | --- tests/pa-mock.cpp 2015-01-29 14:34:50 +0000 |
2126 | +++ tests/pa-mock.cpp 2015-01-30 23:01:09 +0000 |
2127 | @@ -23,6 +23,7 @@ |
2128 | |
2129 | #include <pulse/pulseaudio.h> |
2130 | #include <pulse/glib-mainloop.h> |
2131 | +#include <pulse/ext-stream-restore.h> |
2132 | #include <gio/gio.h> |
2133 | #include <math.h> |
2134 | |
2135 | @@ -280,6 +281,12 @@ |
2136 | return dummy_operation(); |
2137 | } |
2138 | |
2139 | +pa_operation * |
2140 | +pa_context_get_sink_input_info_list (pa_context *c, pa_sink_input_info_cb_t cb, void * userdata) |
2141 | +{ |
2142 | + return pa_context_get_sink_input_info (c, 0, cb, userdata); |
2143 | +} |
2144 | + |
2145 | pa_operation* |
2146 | pa_context_get_source_info_by_name (pa_context *c, const char * name, pa_source_info_cb_t cb, void *userdata) |
2147 | { |
2148 | @@ -569,3 +576,18 @@ |
2149 | return cvol; |
2150 | } |
2151 | |
2152 | +/* ******************************* |
2153 | + * ext-stream-restore.h |
2154 | + * *******************************/ |
2155 | + |
2156 | +pa_operation * |
2157 | +pa_ext_stream_restore_test (pa_context * c, pa_ext_stream_restore_test_cb_t callback, void * userdata) |
2158 | +{ |
2159 | + reinterpret_cast<PAMockContext*>(c)->idleOnce( |
2160 | + [c, callback, userdata]() { |
2161 | + if (callback != nullptr) |
2162 | + callback(c, 1, userdata); |
2163 | + }); |
2164 | + |
2165 | + return dummy_operation(); |
2166 | +} |
2167 | |
2168 | === modified file 'tests/volume-control-test.cc' |
2169 | --- tests/volume-control-test.cc 2015-01-27 17:42:26 +0000 |
2170 | +++ tests/volume-control-test.cc 2015-01-30 23:01:09 +0000 |
2171 | @@ -23,6 +23,7 @@ |
2172 | |
2173 | extern "C" { |
2174 | #include "indicator-sound-service.h" |
2175 | +#include "vala-mocks.h" |
2176 | } |
2177 | |
2178 | class VolumeControlTest : public ::testing::Test |
2179 | @@ -71,7 +72,8 @@ |
2180 | }; |
2181 | |
2182 | TEST_F(VolumeControlTest, BasicObject) { |
2183 | - VolumeControl * control = volume_control_new(); |
2184 | + FocusTrackerMock * focustracker = focus_tracker_mock_new(); |
2185 | + VolumeControl * control = volume_control_new(FOCUS_TRACKER(focustracker)); |
2186 | |
2187 | /* Setup the PA backend */ |
2188 | loop(100); |
2189 | |
2190 | === added file 'vapi/libpulse-ext-stream-restore.vapi' |
2191 | --- vapi/libpulse-ext-stream-restore.vapi 1970-01-01 00:00:00 +0000 |
2192 | +++ vapi/libpulse-ext-stream-restore.vapi 2015-01-30 23:01:09 +0000 |
2193 | @@ -0,0 +1,22 @@ |
2194 | + |
2195 | +using GLib; |
2196 | +using PulseAudio; |
2197 | + |
2198 | +[CCode (cheader_filename="pulse/ext-stream-restore.h")] |
2199 | +namespace PulseAudio.Extension.StreamRestore { |
2200 | + public struct Info { |
2201 | + string name; |
2202 | + PulseAudio.ChannelMap channel_map; |
2203 | + PulseAudio.CVolume volume; |
2204 | + string device; |
2205 | + int mute; |
2206 | + } |
2207 | + |
2208 | + public delegate void TestCb (PulseAudio.Context context, uint32 version); |
2209 | + [CCode (cname="pa_ext_stream_restore_test")] |
2210 | + PulseAudio.Operation? test (PulseAudio.Context context, TestCb? callback = null); |
2211 | + |
2212 | + public delegate void ReadCb (PulseAudio.Context context, PulseAudio.Extension.StreamRestore.Info[] streams, int eol); |
2213 | + [CCode (cname="pa_ext_stream_restore_read")] |
2214 | + PulseAudio.Operation? read (PulseAudio.Context context, ReadCb? callback = null); |
2215 | +} |