Merge lp:~ted/indicator-sound/sound-stream-cleanup into lp:indicator-sound/15.04
- sound-stream-cleanup
- Merge into trunk.15.04
Proposed by
Ted Gould
Status: | Work in progress |
---|---|
Proposed branch: | lp:~ted/indicator-sound/sound-stream-cleanup |
Merge into: | lp:indicator-sound/15.04 |
Prerequisite: | lp:~ted/indicator-sound/indicator-test |
Diff against target: |
2656 lines (+1450/-591) 20 files modified
src/CMakeLists.txt (+18/-0) src/focus-tracker-stack.vala (+88/-0) src/focus-tracker.vala (+22/-0) src/main.c (+73/-12) src/media-player-list.vala (+1/-1) src/service.vala (+95/-77) src/sound-menu.vala (+12/-1) src/volume-control-pulse.vala (+318/-498) src/volume-control.vala (+31/-0) tests/CMakeLists.txt (+30/-0) tests/focus-tracker-mock.vala (+27/-0) tests/gtest-gvariant.h (+110/-0) tests/indicator-test.cc (+6/-0) tests/media-player-list-mock.vala (+25/-0) tests/notifications-mock.h (+155/-0) tests/notifications-test.cc (+348/-0) tests/pa-mock.cpp (+22/-0) tests/volume-control-mock.vala (+43/-0) tests/volume-control-test.cc (+4/-2) 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+248180@code.launchpad.net |
This proposal supersedes a proposal from 2014-12-17.
Commit message
Description of the change
To post a comment you must log in.
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
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/CMakeLists.txt' | |||
2 | --- src/CMakeLists.txt 2015-01-27 17:42:26 +0000 | |||
3 | +++ src/CMakeLists.txt 2015-02-14 02:38:32 +0000 | |||
4 | @@ -36,6 +36,7 @@ | |||
5 | 36 | --vapidir=. | 36 | --vapidir=. |
6 | 37 | --pkg=url-dispatcher | 37 | --pkg=url-dispatcher |
7 | 38 | --pkg=bus-watcher | 38 | --pkg=bus-watcher |
8 | 39 | --pkg=libpulse-ext-stream-restore | ||
9 | 39 | ) | 40 | ) |
10 | 40 | 41 | ||
11 | 41 | vala_add(indicator-sound-service | 42 | vala_add(indicator-sound-service |
12 | @@ -43,15 +44,23 @@ | |||
13 | 43 | DEPENDS | 44 | DEPENDS |
14 | 44 | sound-menu | 45 | sound-menu |
15 | 45 | volume-control | 46 | volume-control |
16 | 47 | volume-control-pulse | ||
17 | 46 | media-player | 48 | media-player |
18 | 47 | media-player-list | 49 | media-player-list |
19 | 48 | mpris2-interfaces | 50 | mpris2-interfaces |
20 | 49 | accounts-service-user | 51 | accounts-service-user |
21 | 52 | focus-tracker | ||
22 | 50 | ) | 53 | ) |
23 | 51 | vala_add(indicator-sound-service | 54 | vala_add(indicator-sound-service |
24 | 52 | volume-control.vala | 55 | volume-control.vala |
25 | 53 | ) | 56 | ) |
26 | 54 | vala_add(indicator-sound-service | 57 | vala_add(indicator-sound-service |
27 | 58 | volume-control-pulse.vala | ||
28 | 59 | DEPENDS | ||
29 | 60 | volume-control | ||
30 | 61 | focus-tracker | ||
31 | 62 | ) | ||
32 | 63 | vala_add(indicator-sound-service | ||
33 | 55 | media-player.vala | 64 | media-player.vala |
34 | 56 | ) | 65 | ) |
35 | 57 | vala_add(indicator-sound-service | 66 | vala_add(indicator-sound-service |
36 | @@ -120,6 +129,14 @@ | |||
37 | 120 | vala_add(indicator-sound-service | 129 | vala_add(indicator-sound-service |
38 | 121 | greeter-broadcast.vala | 130 | greeter-broadcast.vala |
39 | 122 | ) | 131 | ) |
40 | 132 | vala_add(indicator-sound-service | ||
41 | 133 | focus-tracker.vala | ||
42 | 134 | ) | ||
43 | 135 | vala_add(indicator-sound-service | ||
44 | 136 | focus-tracker-stack.vala | ||
45 | 137 | DEPENDS | ||
46 | 138 | focus-tracker | ||
47 | 139 | ) | ||
48 | 123 | 140 | ||
49 | 124 | vala_finish(indicator-sound-service | 141 | vala_finish(indicator-sound-service |
50 | 125 | SOURCES | 142 | SOURCES |
51 | @@ -154,6 +171,7 @@ | |||
52 | 154 | 171 | ||
53 | 155 | add_definitions( | 172 | add_definitions( |
54 | 156 | -w | 173 | -w |
55 | 174 | -DG_LOG_DOMAIN="indicator-sound" | ||
56 | 157 | ) | 175 | ) |
57 | 158 | 176 | ||
58 | 159 | add_library( | 177 | add_library( |
59 | 160 | 178 | ||
60 | === added file 'src/focus-tracker-stack.vala' | |||
61 | --- src/focus-tracker-stack.vala 1970-01-01 00:00:00 +0000 | |||
62 | +++ src/focus-tracker-stack.vala 2015-02-14 02:38:32 +0000 | |||
63 | @@ -0,0 +1,88 @@ | |||
64 | 1 | /* | ||
65 | 2 | * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- | ||
66 | 3 | * Copyright © 2014 Canonical Ltd. | ||
67 | 4 | * | ||
68 | 5 | * This program is free software; you can redistribute it and/or modify | ||
69 | 6 | * it under the terms of the GNU General Public License as published by | ||
70 | 7 | * the Free Software Foundation; version 3. | ||
71 | 8 | * | ||
72 | 9 | * This program is distributed in the hope that it will be useful, | ||
73 | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
74 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
75 | 12 | * GNU General Public License for more details. | ||
76 | 13 | * | ||
77 | 14 | * You should have received a copy of the GNU General Public License | ||
78 | 15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
79 | 16 | * | ||
80 | 17 | * Authors: | ||
81 | 18 | * Ted Gould <ted@canonical.com> | ||
82 | 19 | */ | ||
83 | 20 | |||
84 | 21 | private struct WindowInfo { | ||
85 | 22 | public uint window_id; | ||
86 | 23 | public string app_id; | ||
87 | 24 | public bool focused; | ||
88 | 25 | public uint stage; | ||
89 | 26 | } | ||
90 | 27 | |||
91 | 28 | /* | ||
92 | 29 | private struct DesktopInfo { | ||
93 | 30 | public string app_id; | ||
94 | 31 | public string desktop_file; | ||
95 | 32 | } | ||
96 | 33 | */ | ||
97 | 34 | |||
98 | 35 | [DBus (name = "com.canonical.Unity.WindowStack")] | ||
99 | 36 | private interface WindowStack : Object { | ||
100 | 37 | /* public abstract async DesktopInfo GetAppIdFromPid (uint pid) throws IOError; */ | ||
101 | 38 | public abstract async WindowInfo[] GetWindowStack () throws IOError; | ||
102 | 39 | /* public abstract async string[] GetWindowProperties (uint window_id, string app_id, string[] names) throws IOError; */ | ||
103 | 40 | |||
104 | 41 | public signal void FocusedWindowChanged (uint window_id, string app_id, uint stage); | ||
105 | 42 | /* public signal void WindowCreated (uint window_id, string app_id); */ | ||
106 | 43 | /* public signal void WindowDestroyed (uint window_id, string app_id); */ | ||
107 | 44 | } | ||
108 | 45 | |||
109 | 46 | public class FocusTrackerStack : FocusTracker { | ||
110 | 47 | public override string focused_appid { get; private set; default = "unknown"; } | ||
111 | 48 | private WindowStack proxy; | ||
112 | 49 | |||
113 | 50 | public FocusTrackerStack ( ) { | ||
114 | 51 | build_proxies.begin(); | ||
115 | 52 | } | ||
116 | 53 | |||
117 | 54 | private async void build_proxies() { | ||
118 | 55 | try { | ||
119 | 56 | proxy = yield Bus.get_proxy<WindowStack>(BusType.SESSION, | ||
120 | 57 | "com.canonical.Unity.WindowStack", | ||
121 | 58 | "/com/canonical/Unity/WindowStack", | ||
122 | 59 | DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, | ||
123 | 60 | null); | ||
124 | 61 | |||
125 | 62 | proxy.FocusedWindowChanged.connect((window, appid, stage) => { | ||
126 | 63 | if (stage == 0) { | ||
127 | 64 | debug("Focus changed to: %s", appid); | ||
128 | 65 | this.focused_appid = appid; | ||
129 | 66 | } | ||
130 | 67 | }); | ||
131 | 68 | |||
132 | 69 | update_focused_appid.begin(); | ||
133 | 70 | } catch (Error e) { | ||
134 | 71 | warning("Unable to create window stack proxy: %s", e.message); | ||
135 | 72 | } | ||
136 | 73 | } | ||
137 | 74 | |||
138 | 75 | private async void update_focused_appid () { | ||
139 | 76 | try { | ||
140 | 77 | var windows = yield proxy.GetWindowStack(); | ||
141 | 78 | foreach (var window in windows) { | ||
142 | 79 | if (window.focused && window.stage == 0) { | ||
143 | 80 | focused_appid = window.app_id; | ||
144 | 81 | break; | ||
145 | 82 | } | ||
146 | 83 | } | ||
147 | 84 | } catch (Error e) { | ||
148 | 85 | warning("Unable to get window stack list: %s", e.message); | ||
149 | 86 | } | ||
150 | 87 | } | ||
151 | 88 | } | ||
152 | 0 | 89 | ||
153 | === added file 'src/focus-tracker.vala' | |||
154 | --- src/focus-tracker.vala 1970-01-01 00:00:00 +0000 | |||
155 | +++ src/focus-tracker.vala 2015-02-14 02:38:32 +0000 | |||
156 | @@ -0,0 +1,22 @@ | |||
157 | 1 | /* | ||
158 | 2 | * Copyright © 2015 Canonical Ltd. | ||
159 | 3 | * | ||
160 | 4 | * This program is free software; you can redistribute it and/or modify | ||
161 | 5 | * it under the terms of the GNU General Public License as published by | ||
162 | 6 | * the Free Software Foundation; version 3. | ||
163 | 7 | * | ||
164 | 8 | * This program is distributed in the hope that it will be useful, | ||
165 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
166 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
167 | 11 | * GNU General Public License for more details. | ||
168 | 12 | * | ||
169 | 13 | * You should have received a copy of the GNU General Public License | ||
170 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
171 | 15 | * | ||
172 | 16 | * Authors: | ||
173 | 17 | * Ted Gould <ted@canonical.com> | ||
174 | 18 | */ | ||
175 | 19 | |||
176 | 20 | public abstract class FocusTracker : Object { | ||
177 | 21 | public virtual string focused_appid { get; protected set; } | ||
178 | 22 | } | ||
179 | 0 | 23 | ||
180 | === modified file 'src/main.c' | |||
181 | --- src/main.c 2014-02-25 22:47:45 +0000 | |||
182 | +++ src/main.c 2015-02-14 02:38:32 +0000 | |||
183 | @@ -1,6 +1,21 @@ | |||
187 | 1 | /* main.c generated by valac 0.22.1, the Vala compiler | 1 | /* |
188 | 2 | * generated from main.vala, do not modify */ | 2 | * Copyright © 2015 Canonical Ltd. |
189 | 3 | 3 | * | |
190 | 4 | * This program is free software; you can redistribute it and/or modify | ||
191 | 5 | * it under the terms of the GNU General Public License as published by | ||
192 | 6 | * the Free Software Foundation; version 3. | ||
193 | 7 | * | ||
194 | 8 | * This program is distributed in the hope that it will be useful, | ||
195 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
196 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
197 | 11 | * GNU General Public License for more details. | ||
198 | 12 | * | ||
199 | 13 | * You should have received a copy of the GNU General Public License | ||
200 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
201 | 15 | * | ||
202 | 16 | * Authors: | ||
203 | 17 | * Ted Gould <ted@canonical.com> | ||
204 | 18 | */ | ||
205 | 4 | 19 | ||
206 | 5 | #include <glib.h> | 20 | #include <glib.h> |
207 | 6 | #include <locale.h> | 21 | #include <locale.h> |
208 | @@ -9,33 +24,79 @@ | |||
209 | 9 | #include "indicator-sound-service.h" | 24 | #include "indicator-sound-service.h" |
210 | 10 | #include "config.h" | 25 | #include "config.h" |
211 | 11 | 26 | ||
212 | 27 | static gboolean | ||
213 | 28 | sigterm_handler (gpointer data) | ||
214 | 29 | { | ||
215 | 30 | g_debug("Got SIGTERM"); | ||
216 | 31 | g_main_loop_quit((GMainLoop *)data); | ||
217 | 32 | return G_SOURCE_REMOVE; | ||
218 | 33 | } | ||
219 | 34 | |||
220 | 35 | static void | ||
221 | 36 | name_lost (GDBusConnection * connection, const gchar * name, gpointer user_data) | ||
222 | 37 | { | ||
223 | 38 | g_debug("Name lost"); | ||
224 | 39 | g_main_loop_quit((GMainLoop *)user_data); | ||
225 | 40 | } | ||
226 | 41 | |||
227 | 12 | int | 42 | int |
228 | 13 | main (int argc, char ** argv) { | 43 | main (int argc, char ** argv) { |
230 | 14 | gint result = 0; | 44 | GMainLoop * loop = NULL; |
231 | 15 | IndicatorSoundService* service = NULL; | 45 | IndicatorSoundService* service = NULL; |
232 | 46 | GDBusConnection * bus = NULL; | ||
233 | 16 | 47 | ||
234 | 17 | bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); | 48 | bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); |
235 | 18 | setlocale (LC_ALL, ""); | 49 | setlocale (LC_ALL, ""); |
236 | 19 | bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); | 50 | bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); |
237 | 20 | 51 | ||
238 | 52 | /* Grab DBus */ | ||
239 | 53 | GError * error = NULL; | ||
240 | 54 | bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); | ||
241 | 55 | if (error != NULL) { | ||
242 | 56 | g_error("Unable to get session bus: %s", error->message); | ||
243 | 57 | g_error_free(error); | ||
244 | 58 | return -1; | ||
245 | 59 | } | ||
246 | 60 | |||
247 | 61 | /* Build Mainloop */ | ||
248 | 62 | loop = g_main_loop_new(NULL, FALSE); | ||
249 | 63 | |||
250 | 64 | g_unix_signal_add(SIGTERM, sigterm_handler, loop); | ||
251 | 65 | |||
252 | 21 | /* Initialize libnotify */ | 66 | /* Initialize libnotify */ |
253 | 22 | notify_init ("indicator-sound"); | 67 | notify_init ("indicator-sound"); |
254 | 23 | 68 | ||
255 | 24 | MediaPlayerList * playerlist = NULL; | 69 | MediaPlayerList * playerlist = NULL; |
256 | 70 | AccountsServiceUser * accounts = NULL; | ||
257 | 25 | 71 | ||
258 | 26 | if (g_strcmp0("lightdm", g_get_user_name()) == 0) { | 72 | if (g_strcmp0("lightdm", g_get_user_name()) == 0) { |
259 | 27 | playerlist = MEDIA_PLAYER_LIST(media_player_list_greeter_new()); | 73 | playerlist = MEDIA_PLAYER_LIST(media_player_list_greeter_new()); |
260 | 28 | } else { | 74 | } else { |
261 | 29 | playerlist = MEDIA_PLAYER_LIST(media_player_list_mpris_new()); | 75 | playerlist = MEDIA_PLAYER_LIST(media_player_list_mpris_new()); |
262 | 76 | accounts = accounts_service_user_new(); | ||
263 | 30 | } | 77 | } |
264 | 31 | 78 | ||
272 | 32 | service = indicator_sound_service_new (playerlist); | 79 | FocusTracker * tracker = FOCUS_TRACKER(focus_tracker_stack_new()); |
273 | 33 | result = indicator_sound_service_run (service); | 80 | VolumeControlPulse * volume = volume_control_pulse_new(tracker); |
274 | 34 | 81 | ||
275 | 35 | g_object_unref(playerlist); | 82 | service = indicator_sound_service_new (playerlist, volume, accounts); |
276 | 36 | g_object_unref(service); | 83 | |
277 | 37 | 84 | g_bus_own_name_on_connection(bus, | |
278 | 38 | return result; | 85 | "com.canonical.indicator.sound", |
279 | 86 | G_BUS_NAME_OWNER_FLAGS_NONE, | ||
280 | 87 | NULL, /* acquired */ | ||
281 | 88 | name_lost, | ||
282 | 89 | loop, | ||
283 | 90 | NULL); | ||
284 | 91 | |||
285 | 92 | g_main_loop_run(loop); | ||
286 | 93 | |||
287 | 94 | g_clear_object(&playerlist); | ||
288 | 95 | g_clear_object(&tracker); | ||
289 | 96 | g_clear_object(&accounts); | ||
290 | 97 | g_clear_object(&service); | ||
291 | 98 | g_clear_object(&bus); | ||
292 | 99 | |||
293 | 100 | return 0; | ||
294 | 39 | } | 101 | } |
295 | 40 | 102 | ||
296 | 41 | |||
297 | 42 | 103 | ||
298 | === modified file 'src/media-player-list.vala' | |||
299 | --- src/media-player-list.vala 2014-02-24 22:47:50 +0000 | |||
300 | +++ src/media-player-list.vala 2015-02-14 02:38:32 +0000 | |||
301 | @@ -17,7 +17,7 @@ | |||
302 | 17 | * Ted Gould <ted@canonical.com> | 17 | * Ted Gould <ted@canonical.com> |
303 | 18 | */ | 18 | */ |
304 | 19 | 19 | ||
306 | 20 | public class MediaPlayerList { | 20 | public abstract class MediaPlayerList : Object { |
307 | 21 | public class Iterator { | 21 | public class Iterator { |
308 | 22 | public virtual MediaPlayer? next_value() { | 22 | public virtual MediaPlayer? next_value() { |
309 | 23 | return null; | 23 | return null; |
310 | 24 | 24 | ||
311 | === modified file 'src/service.vala' | |||
312 | --- src/service.vala 2015-02-05 14:52:58 +0000 | |||
313 | +++ src/service.vala 2015-02-14 02:38:32 +0000 | |||
314 | @@ -18,13 +18,29 @@ | |||
315 | 18 | */ | 18 | */ |
316 | 19 | 19 | ||
317 | 20 | public class IndicatorSound.Service: Object { | 20 | public class IndicatorSound.Service: Object { |
319 | 21 | public Service (MediaPlayerList playerlist) { | 21 | DBusConnection bus; |
320 | 22 | DBusProxy notification_proxy; | ||
321 | 23 | |||
322 | 24 | public Service (MediaPlayerList playerlist, VolumeControl volume, AccountsServiceUser? accounts) { | ||
323 | 25 | try { | ||
324 | 26 | bus = Bus.get_sync(GLib.BusType.SESSION); | ||
325 | 27 | } catch (GLib.Error e) { | ||
326 | 28 | error("Unable to get DBus session bus: %s", e.message); | ||
327 | 29 | } | ||
328 | 30 | |||
329 | 22 | sync_notification = new Notify.Notification(_("Volume"), "", "audio-volume-muted"); | 31 | sync_notification = new Notify.Notification(_("Volume"), "", "audio-volume-muted"); |
335 | 23 | this.notification_server_watch = GLib.Bus.watch_name(GLib.BusType.SESSION, | 32 | try { |
336 | 24 | "org.freedesktop.Notifications", | 33 | this.notification_proxy = new DBusProxy.for_bus_sync(GLib.BusType.SESSION, |
337 | 25 | GLib.BusNameWatcherFlags.NONE, | 34 | DBusProxyFlags.DO_NOT_LOAD_PROPERTIES | DBusProxyFlags.DO_NOT_CONNECT_SIGNALS | DBusProxyFlags.DO_NOT_AUTO_START, |
338 | 26 | () => { check_sync_notification = false; }, | 35 | null, /* interface info */ |
339 | 27 | () => { check_sync_notification = false; }); | 36 | "org.freedesktop.Notifications", |
340 | 37 | "/org/freedesktop/Notifications", | ||
341 | 38 | "org.freedesktop.Notifications", | ||
342 | 39 | null); | ||
343 | 40 | this.notification_proxy.notify["g-name-owner"].connect ( () => { debug("Notifications name owner changed"); check_sync_notification = false; } ); | ||
344 | 41 | } catch (GLib.Error e) { | ||
345 | 42 | error("Unable to build notification proxy: %s", e.message); | ||
346 | 43 | } | ||
347 | 28 | 44 | ||
348 | 29 | this.settings = new Settings ("com.canonical.indicator.sound"); | 45 | this.settings = new Settings ("com.canonical.indicator.sound"); |
349 | 30 | this.sharedsettings = new Settings ("com.ubuntu.sound"); | 46 | this.sharedsettings = new Settings ("com.ubuntu.sound"); |
350 | @@ -32,12 +48,12 @@ | |||
351 | 32 | this.settings.bind ("visible", this, "visible", SettingsBindFlags.GET); | 48 | this.settings.bind ("visible", this, "visible", SettingsBindFlags.GET); |
352 | 33 | this.notify["visible"].connect ( () => this.update_root_icon () ); | 49 | this.notify["visible"].connect ( () => this.update_root_icon () ); |
353 | 34 | 50 | ||
355 | 35 | this.volume_control = new VolumeControl (); | 51 | this.volume_control = volume; |
356 | 52 | this.volume_control.bind_property ("media-playing", this, "player-playing", BindingFlags.SYNC_CREATE); | ||
357 | 36 | 53 | ||
358 | 54 | this.accounts_service = accounts; | ||
359 | 37 | /* If we're on the greeter, don't export */ | 55 | /* If we're on the greeter, don't export */ |
363 | 38 | if (GLib.Environment.get_user_name() != "lightdm") { | 56 | if (this.accounts_service != null) { |
361 | 39 | this.accounts_service = new AccountsServiceUser(); | ||
362 | 40 | |||
364 | 41 | this.accounts_service.notify["showDataOnGreeter"].connect(() => { | 57 | this.accounts_service.notify["showDataOnGreeter"].connect(() => { |
365 | 42 | this.export_to_accounts_service = this.accounts_service.showDataOnGreeter; | 58 | this.export_to_accounts_service = this.accounts_service.showDataOnGreeter; |
366 | 43 | eventually_update_player_actions(); | 59 | eventually_update_player_actions(); |
367 | @@ -90,9 +106,27 @@ | |||
368 | 90 | } | 106 | } |
369 | 91 | } | 107 | } |
370 | 92 | }); | 108 | }); |
371 | 109 | |||
372 | 110 | /* Everything is built, let's put it on the bus */ | ||
373 | 111 | try { | ||
374 | 112 | export_actions = bus.export_action_group ("/com/canonical/indicator/sound", this.actions); | ||
375 | 113 | } catch (Error e) { | ||
376 | 114 | critical ("%s", e.message); | ||
377 | 115 | } | ||
378 | 116 | |||
379 | 117 | this.menus.@foreach ( (profile, menu) => menu.export (bus, @"/com/canonical/indicator/sound/$profile")); | ||
380 | 93 | } | 118 | } |
381 | 94 | 119 | ||
382 | 95 | ~Service() { | 120 | ~Service() { |
383 | 121 | debug("Destroying Service Object"); | ||
384 | 122 | |||
385 | 123 | clear_acts_player(); | ||
386 | 124 | |||
387 | 125 | if (this.player_action_update_id > 0) { | ||
388 | 126 | Source.remove (this.player_action_update_id); | ||
389 | 127 | this.player_action_update_id = 0; | ||
390 | 128 | } | ||
391 | 129 | |||
392 | 96 | if (this.sound_was_blocked_timeout_id > 0) { | 130 | if (this.sound_was_blocked_timeout_id > 0) { |
393 | 97 | Source.remove (this.sound_was_blocked_timeout_id); | 131 | Source.remove (this.sound_was_blocked_timeout_id); |
394 | 98 | this.sound_was_blocked_timeout_id = 0; | 132 | this.sound_was_blocked_timeout_id = 0; |
395 | @@ -102,6 +136,11 @@ | |||
396 | 102 | GLib.Bus.unwatch_name(this.notification_server_watch); | 136 | GLib.Bus.unwatch_name(this.notification_server_watch); |
397 | 103 | this.notification_server_watch = 0; | 137 | this.notification_server_watch = 0; |
398 | 104 | } | 138 | } |
399 | 139 | |||
400 | 140 | if (this.export_actions != 0) { | ||
401 | 141 | bus.unexport_action_group(this.export_actions); | ||
402 | 142 | this.export_actions = 0; | ||
403 | 143 | } | ||
404 | 105 | } | 144 | } |
405 | 106 | 145 | ||
406 | 107 | bool greeter_show_track () { | 146 | bool greeter_show_track () { |
407 | @@ -115,30 +154,6 @@ | |||
408 | 115 | this.accounts_service.player = null; | 154 | this.accounts_service.player = null; |
409 | 116 | } | 155 | } |
410 | 117 | 156 | ||
411 | 118 | public int run () { | ||
412 | 119 | if (this.loop != null) { | ||
413 | 120 | warning ("service is already running"); | ||
414 | 121 | return 1; | ||
415 | 122 | } | ||
416 | 123 | |||
417 | 124 | Bus.own_name (BusType.SESSION, "com.canonical.indicator.sound", BusNameOwnerFlags.NONE, | ||
418 | 125 | this.bus_acquired, null, this.name_lost); | ||
419 | 126 | |||
420 | 127 | this.loop = new MainLoop (null, false); | ||
421 | 128 | |||
422 | 129 | GLib.Unix.signal_add(GLib.ProcessSignal.TERM, () => { | ||
423 | 130 | debug("SIGTERM recieved, stopping our mainloop"); | ||
424 | 131 | this.loop.quit(); | ||
425 | 132 | return false; | ||
426 | 133 | }); | ||
427 | 134 | |||
428 | 135 | this.loop.run (); | ||
429 | 136 | |||
430 | 137 | clear_acts_player(); | ||
431 | 138 | |||
432 | 139 | return 0; | ||
433 | 140 | } | ||
434 | 141 | |||
435 | 142 | public bool visible { get; set; } | 157 | public bool visible { get; set; } |
436 | 143 | 158 | ||
437 | 144 | public bool allow_amplified_volume { | 159 | public bool allow_amplified_volume { |
438 | @@ -171,13 +186,13 @@ | |||
439 | 171 | { "indicator-shown", null, null, "@b false", null }, | 186 | { "indicator-shown", null, null, "@b false", null }, |
440 | 172 | }; | 187 | }; |
441 | 173 | 188 | ||
442 | 174 | MainLoop loop; | ||
443 | 175 | SimpleActionGroup actions; | 189 | SimpleActionGroup actions; |
444 | 176 | HashTable<string, SoundMenu> menus; | 190 | HashTable<string, SoundMenu> menus; |
445 | 177 | Settings settings; | 191 | Settings settings; |
446 | 178 | Settings sharedsettings; | 192 | Settings sharedsettings; |
447 | 179 | VolumeControl volume_control; | 193 | VolumeControl volume_control; |
448 | 180 | MediaPlayerList players; | 194 | MediaPlayerList players; |
449 | 195 | public bool player_playing { get; set; default = false; } | ||
450 | 181 | uint player_action_update_id; | 196 | uint player_action_update_id; |
451 | 182 | bool mute_blocks_sound; | 197 | bool mute_blocks_sound; |
452 | 183 | uint sound_was_blocked_timeout_id; | 198 | uint sound_was_blocked_timeout_id; |
453 | @@ -275,6 +290,7 @@ | |||
454 | 275 | 290 | ||
455 | 276 | void update_sync_notification () { | 291 | void update_sync_notification () { |
456 | 277 | if (!check_sync_notification) { | 292 | if (!check_sync_notification) { |
457 | 293 | support_sync_notification = false; | ||
458 | 278 | List<string> caps = Notify.get_server_caps (); | 294 | List<string> caps = Notify.get_server_caps (); |
459 | 279 | if (caps.find_custom ("x-canonical-private-synchronous", strcmp) != null) { | 295 | if (caps.find_custom ("x-canonical-private-synchronous", strcmp) != null) { |
460 | 280 | support_sync_notification = true; | 296 | support_sync_notification = true; |
461 | @@ -285,27 +301,12 @@ | |||
462 | 285 | if (!support_sync_notification) | 301 | if (!support_sync_notification) |
463 | 286 | return; | 302 | return; |
464 | 287 | 303 | ||
465 | 288 | /* Update our volume and output */ | ||
466 | 289 | var oldoutput = this.last_output_notification; | ||
467 | 290 | this.last_output_notification = this.volume_control.stream; | ||
468 | 291 | |||
469 | 292 | var oldvolume = this.last_volume_notification; | ||
470 | 293 | this.last_volume_notification = volume_control.volume; | ||
471 | 294 | |||
472 | 295 | /* Suppress notifications of volume changes if it is because the | ||
473 | 296 | output stream changed. */ | ||
474 | 297 | if (oldoutput != this.last_output_notification) | ||
475 | 298 | return; | ||
476 | 299 | /* Supress updates that don't change the value */ | ||
477 | 300 | if (GLib.Math.fabs(oldvolume - this.last_volume_notification) < 0.01) | ||
478 | 301 | return; | ||
479 | 302 | |||
480 | 303 | var shown_action = actions.lookup_action ("indicator-shown") as SimpleAction; | 304 | var shown_action = actions.lookup_action ("indicator-shown") as SimpleAction; |
481 | 304 | if (shown_action != null && shown_action.get_state().get_boolean()) | 305 | if (shown_action != null && shown_action.get_state().get_boolean()) |
482 | 305 | return; | 306 | return; |
483 | 306 | 307 | ||
484 | 307 | /* Determine Label */ | 308 | /* Determine Label */ |
486 | 308 | string volume_label = ""; | 309 | string volume_label = volume_control.stream; /* TODO: Undo this */ |
487 | 309 | if (volume_control.high_volume) | 310 | if (volume_control.high_volume) |
488 | 310 | volume_label = _("High volume"); | 311 | volume_label = _("High volume"); |
489 | 311 | 312 | ||
490 | @@ -341,13 +342,14 @@ | |||
491 | 341 | } | 342 | } |
492 | 342 | } | 343 | } |
493 | 343 | 344 | ||
494 | 345 | SimpleAction silent_action; | ||
495 | 344 | Action create_silent_mode_action () { | 346 | Action create_silent_mode_action () { |
496 | 345 | bool silentNow = false; | 347 | bool silentNow = false; |
497 | 346 | if (this.accounts_service != null) { | 348 | if (this.accounts_service != null) { |
498 | 347 | silentNow = this.accounts_service.silentMode; | 349 | silentNow = this.accounts_service.silentMode; |
499 | 348 | } | 350 | } |
500 | 349 | 351 | ||
502 | 350 | var silent_action = new SimpleAction.stateful ("silent-mode", null, new Variant.boolean (silentNow)); | 352 | silent_action = new SimpleAction.stateful ("silent-mode", null, new Variant.boolean (silentNow)); |
503 | 351 | 353 | ||
504 | 352 | /* If we're not dealing with accounts service, we'll just always be out | 354 | /* If we're not dealing with accounts service, we'll just always be out |
505 | 353 | of silent mode and that's cool. */ | 355 | of silent mode and that's cool. */ |
506 | @@ -371,15 +373,16 @@ | |||
507 | 371 | return silent_action; | 373 | return silent_action; |
508 | 372 | } | 374 | } |
509 | 373 | 375 | ||
510 | 376 | SimpleAction mute_action; | ||
511 | 374 | Action create_mute_action () { | 377 | Action create_mute_action () { |
513 | 375 | var mute_action = new SimpleAction.stateful ("mute", null, new Variant.boolean (this.volume_control.mute)); | 378 | mute_action = new SimpleAction.stateful ("mute", null, new Variant.boolean (this.volume_control.mute)); |
514 | 376 | 379 | ||
515 | 377 | mute_action.activate.connect ( (action, param) => { | 380 | mute_action.activate.connect ( (action, param) => { |
516 | 378 | action.change_state (new Variant.boolean (!action.get_state ().get_boolean ())); | 381 | action.change_state (new Variant.boolean (!action.get_state ().get_boolean ())); |
517 | 379 | }); | 382 | }); |
518 | 380 | 383 | ||
519 | 381 | mute_action.change_state.connect ( (action, val) => { | 384 | mute_action.change_state.connect ( (action, val) => { |
521 | 382 | volume_control.set_mute (val.get_boolean ()); | 385 | volume_control.mute = val.get_boolean (); |
522 | 383 | }); | 386 | }); |
523 | 384 | 387 | ||
524 | 385 | this.volume_control.notify["mute"].connect ( () => { | 388 | this.volume_control.notify["mute"].connect ( () => { |
525 | @@ -415,6 +418,7 @@ | |||
526 | 415 | return mute_action; | 418 | return mute_action; |
527 | 416 | } | 419 | } |
528 | 417 | 420 | ||
529 | 421 | SimpleAction volume_action; | ||
530 | 418 | Action create_volume_action () { | 422 | Action create_volume_action () { |
531 | 419 | /* The action's state is between be in [0.0, 1.0] instead of [0.0, | 423 | /* The action's state is between be in [0.0, 1.0] instead of [0.0, |
532 | 420 | * max_volume], so that we don't need to update the slider menu item | 424 | * max_volume], so that we don't need to update the slider menu item |
533 | @@ -425,7 +429,7 @@ | |||
534 | 425 | 429 | ||
535 | 426 | double volume = this.volume_control.volume / this.max_volume; | 430 | double volume = this.volume_control.volume / this.max_volume; |
536 | 427 | 431 | ||
538 | 428 | var volume_action = new SimpleAction.stateful ("volume", VariantType.INT32, new Variant.double (volume)); | 432 | volume_action = new SimpleAction.stateful ("volume", VariantType.INT32, new Variant.double (volume)); |
539 | 429 | 433 | ||
540 | 430 | volume_action.change_state.connect ( (action, val) => { | 434 | volume_action.change_state.connect ( (action, val) => { |
541 | 431 | double v = val.get_double () * this.max_volume; | 435 | double v = val.get_double () * this.max_volume; |
542 | @@ -440,12 +444,26 @@ | |||
543 | 440 | }); | 444 | }); |
544 | 441 | 445 | ||
545 | 442 | this.volume_control.notify["volume"].connect (() => { | 446 | this.volume_control.notify["volume"].connect (() => { |
546 | 443 | var vol_action = this.actions.lookup_action ("volume") as SimpleAction; | ||
547 | 444 | |||
548 | 445 | /* Normalize volume, because the volume action's state is [0.0, 1.0], see create_volume_action() */ | 447 | /* Normalize volume, because the volume action's state is [0.0, 1.0], see create_volume_action() */ |
550 | 446 | vol_action.set_state (new Variant.double (this.volume_control.volume / this.max_volume)); | 448 | volume_action.set_state (new Variant.double (this.volume_control.volume / this.max_volume)); |
551 | 447 | 449 | ||
552 | 448 | this.update_root_icon (); | 450 | this.update_root_icon (); |
553 | 451 | |||
554 | 452 | /* Update our volume and output */ | ||
555 | 453 | var oldoutput = this.last_output_notification; | ||
556 | 454 | this.last_output_notification = this.volume_control.stream; | ||
557 | 455 | |||
558 | 456 | var oldvolume = this.last_volume_notification; | ||
559 | 457 | this.last_volume_notification = volume_control.volume; | ||
560 | 458 | |||
561 | 459 | /* Suppress notifications of volume changes if it is because the | ||
562 | 460 | output stream changed. */ | ||
563 | 461 | if (oldoutput != this.last_output_notification) | ||
564 | 462 | return; | ||
565 | 463 | /* Supress updates that don't change the value */ | ||
566 | 464 | if (GLib.Math.fabs(oldvolume - this.last_volume_notification) < 0.01) | ||
567 | 465 | return; | ||
568 | 466 | |||
569 | 449 | this.update_sync_notification (); | 467 | this.update_sync_notification (); |
570 | 450 | }); | 468 | }); |
571 | 451 | 469 | ||
572 | @@ -454,24 +472,26 @@ | |||
573 | 454 | return volume_action; | 472 | return volume_action; |
574 | 455 | } | 473 | } |
575 | 456 | 474 | ||
576 | 475 | SimpleAction mic_volume_action; | ||
577 | 457 | Action create_mic_volume_action () { | 476 | Action create_mic_volume_action () { |
579 | 458 | var volume_action = new SimpleAction.stateful ("mic-volume", null, new Variant.double (this.volume_control.mic_volume)); | 477 | mic_volume_action = new SimpleAction.stateful ("mic-volume", null, new Variant.double (this.volume_control.mic_volume)); |
580 | 459 | 478 | ||
582 | 460 | volume_action.change_state.connect ( (action, val) => { | 479 | mic_volume_action.change_state.connect ( (action, val) => { |
583 | 461 | volume_control.mic_volume = val.get_double (); | 480 | volume_control.mic_volume = val.get_double (); |
584 | 462 | }); | 481 | }); |
585 | 463 | 482 | ||
586 | 464 | this.volume_control.notify["mic-volume"].connect ( () => { | 483 | this.volume_control.notify["mic-volume"].connect ( () => { |
588 | 465 | volume_action.set_state (new Variant.double (this.volume_control.mic_volume)); | 484 | mic_volume_action.set_state (new Variant.double (this.volume_control.mic_volume)); |
589 | 466 | }); | 485 | }); |
590 | 467 | 486 | ||
592 | 468 | this.volume_control.bind_property ("ready", volume_action, "enabled", BindingFlags.SYNC_CREATE); | 487 | this.volume_control.bind_property ("ready", mic_volume_action, "enabled", BindingFlags.SYNC_CREATE); |
593 | 469 | 488 | ||
595 | 470 | return volume_action; | 489 | return mic_volume_action; |
596 | 471 | } | 490 | } |
597 | 472 | 491 | ||
598 | 492 | SimpleAction high_volume_action; | ||
599 | 473 | Action create_high_volume_action () { | 493 | Action create_high_volume_action () { |
601 | 474 | var high_volume_action = new SimpleAction.stateful("high-volume", null, new Variant.boolean (this.volume_control.high_volume)); | 494 | high_volume_action = new SimpleAction.stateful("high-volume", null, new Variant.boolean (this.volume_control.high_volume)); |
602 | 475 | 495 | ||
603 | 476 | this.volume_control.notify["high-volume"].connect( () => { | 496 | this.volume_control.notify["high-volume"].connect( () => { |
604 | 477 | high_volume_action.set_state(new Variant.boolean (this.volume_control.high_volume)); | 497 | high_volume_action.set_state(new Variant.boolean (this.volume_control.high_volume)); |
605 | @@ -481,19 +501,7 @@ | |||
606 | 481 | return high_volume_action; | 501 | return high_volume_action; |
607 | 482 | } | 502 | } |
608 | 483 | 503 | ||
622 | 484 | void bus_acquired (DBusConnection connection, string name) { | 504 | uint export_actions = 0; |
610 | 485 | try { | ||
611 | 486 | connection.export_action_group ("/com/canonical/indicator/sound", this.actions); | ||
612 | 487 | } catch (Error e) { | ||
613 | 488 | critical ("%s", e.message); | ||
614 | 489 | } | ||
615 | 490 | |||
616 | 491 | this.menus.@foreach ( (profile, menu) => menu.export (connection, @"/com/canonical/indicator/sound/$profile")); | ||
617 | 492 | } | ||
618 | 493 | |||
619 | 494 | void name_lost (DBusConnection connection, string name) { | ||
620 | 495 | this.loop.quit (); | ||
621 | 496 | } | ||
623 | 497 | 505 | ||
624 | 498 | Variant action_state_for_player (MediaPlayer player, bool show_track = true) { | 506 | Variant action_state_for_player (MediaPlayer player, bool show_track = true) { |
625 | 499 | var builder = new VariantBuilder (new VariantType ("a{sv}")); | 507 | var builder = new VariantBuilder (new VariantType ("a{sv}")); |
626 | @@ -510,6 +518,7 @@ | |||
627 | 510 | 518 | ||
628 | 511 | bool update_player_actions () { | 519 | bool update_player_actions () { |
629 | 512 | bool clear_accounts_player = true; | 520 | bool clear_accounts_player = true; |
630 | 521 | bool player_playing = false; | ||
631 | 513 | 522 | ||
632 | 514 | foreach (var player in this.players) { | 523 | foreach (var player in this.players) { |
633 | 515 | SimpleAction? action = this.actions.lookup_action (player.id) as SimpleAction; | 524 | SimpleAction? action = this.actions.lookup_action (player.id) as SimpleAction; |
634 | @@ -529,11 +538,20 @@ | |||
635 | 529 | accounts_service.player = player; | 538 | accounts_service.player = player; |
636 | 530 | clear_accounts_player = false; | 539 | clear_accounts_player = false; |
637 | 531 | } | 540 | } |
638 | 541 | |||
639 | 542 | if (player.state == "Playing") { | ||
640 | 543 | player_playing = true; | ||
641 | 544 | } | ||
642 | 532 | } | 545 | } |
643 | 533 | 546 | ||
644 | 534 | if (clear_accounts_player) | 547 | if (clear_accounts_player) |
645 | 535 | clear_acts_player(); | 548 | clear_acts_player(); |
646 | 536 | 549 | ||
647 | 550 | if (this.player_playing != player_playing) { | ||
648 | 551 | this.player_playing = player_playing; | ||
649 | 552 | update_root_icon(); | ||
650 | 553 | } | ||
651 | 554 | |||
652 | 537 | this.player_action_update_id = 0; | 555 | this.player_action_update_id = 0; |
653 | 538 | return false; | 556 | return false; |
654 | 539 | } | 557 | } |
655 | 540 | 558 | ||
656 | === modified file 'src/sound-menu.vala' | |||
657 | --- src/sound-menu.vala 2015-01-29 17:31:03 +0000 | |||
658 | +++ src/sound-menu.vala 2015-02-14 02:38:32 +0000 | |||
659 | @@ -73,9 +73,20 @@ | |||
660 | 73 | this.greeter_players = (flags & DisplayFlags.GREETER_PLAYERS) != 0; | 73 | this.greeter_players = (flags & DisplayFlags.GREETER_PLAYERS) != 0; |
661 | 74 | } | 74 | } |
662 | 75 | 75 | ||
663 | 76 | ~SoundMenu () { | ||
664 | 77 | if (export_id != 0) { | ||
665 | 78 | bus.unexport_menu_model(export_id); | ||
666 | 79 | export_id = 0; | ||
667 | 80 | } | ||
668 | 81 | } | ||
669 | 82 | |||
670 | 83 | DBusConnection? bus = null; | ||
671 | 84 | uint export_id = 0; | ||
672 | 85 | |||
673 | 76 | public void export (DBusConnection connection, string object_path) { | 86 | public void export (DBusConnection connection, string object_path) { |
674 | 87 | bus = connection; | ||
675 | 77 | try { | 88 | try { |
677 | 78 | connection.export_menu_model (object_path, this.root); | 89 | export_id = bus.export_menu_model (object_path, this.root); |
678 | 79 | } catch (Error e) { | 90 | } catch (Error e) { |
679 | 80 | critical ("%s", e.message); | 91 | critical ("%s", e.message); |
680 | 81 | } | 92 | } |
681 | 82 | 93 | ||
682 | === renamed file 'src/volume-control.vala' => 'src/volume-control-pulse.vala' | |||
683 | --- src/volume-control.vala 2015-01-30 14:57:03 +0000 | |||
684 | +++ src/volume-control-pulse.vala 2015-02-14 02:38:32 +0000 | |||
685 | @@ -19,6 +19,7 @@ | |||
686 | 19 | */ | 19 | */ |
687 | 20 | 20 | ||
688 | 21 | using PulseAudio; | 21 | using PulseAudio; |
689 | 22 | using PulseAudio.Extension.StreamRestore; | ||
690 | 22 | using Notify; | 23 | using Notify; |
691 | 23 | using Gee; | 24 | using Gee; |
692 | 24 | 25 | ||
693 | @@ -32,42 +33,51 @@ | |||
694 | 32 | public signal void entry_selected (string entry_name); | 33 | public signal void entry_selected (string entry_name); |
695 | 33 | } | 34 | } |
696 | 34 | 35 | ||
698 | 35 | public class VolumeControl : Object | 36 | public class VolumeControlPulse : VolumeControl |
699 | 36 | { | 37 | { |
700 | 37 | /* this is static to ensure it being freed after @context (loop does not have ref counting) */ | 38 | /* this is static to ensure it being freed after @context (loop does not have ref counting) */ |
701 | 38 | private static PulseAudio.GLibMainLoop loop; | 39 | private static PulseAudio.GLibMainLoop loop; |
702 | 39 | 40 | ||
703 | 41 | private FocusTracker focus_tracker; | ||
704 | 42 | public bool media_playing { get; set; default = false; } | ||
705 | 43 | |||
706 | 40 | private uint _reconnect_timer = 0; | 44 | private uint _reconnect_timer = 0; |
707 | 41 | 45 | ||
708 | 42 | private PulseAudio.Context context; | 46 | private PulseAudio.Context context; |
709 | 43 | private bool _mute = true; | 47 | private bool _mute = true; |
711 | 44 | private bool _is_playing = false; | 48 | public override bool is_playing { get; private set; default = false; } |
712 | 45 | private double _volume = 0.0; | 49 | private double _volume = 0.0; |
713 | 46 | private double _mic_volume = 0.0; | 50 | private double _mic_volume = 0.0; |
714 | 47 | 51 | ||
715 | 48 | /* Used by the pulseaudio stream restore extension */ | 52 | /* Used by the pulseaudio stream restore extension */ |
716 | 49 | private DBusConnection _pconn; | ||
717 | 50 | /* Need both the list and hash so we can retrieve the last known sink-input after | ||
718 | 51 | * releasing the current active one (restoring back to the previous known role) */ | ||
719 | 52 | private Gee.ArrayList<uint32> _sink_input_list = new Gee.ArrayList<uint32> (); | ||
720 | 53 | private HashMap<uint32, string> _sink_input_hash = new HashMap<uint32, string> (); | ||
721 | 54 | private bool _pulse_use_stream_restore = false; | 53 | private bool _pulse_use_stream_restore = false; |
725 | 55 | private uint32 _active_sink_input = -1; | 54 | private enum InputStreamType { |
726 | 56 | private string[] _valid_roles = {"multimedia", "alert", "alarm", "phone"}; | 55 | ALERT = 0, |
727 | 57 | public string stream { | 56 | ALARM = 1, |
728 | 57 | MULTIMEDIA = 2, | ||
729 | 58 | PHONE = 3 | ||
730 | 59 | } | ||
731 | 60 | private InputStreamType currentstream = InputStreamType.ALERT; | ||
732 | 61 | private InputStreamType tempstream = InputStreamType.ALERT; | ||
733 | 62 | /** The name of the stream that we're currently playing */ | ||
734 | 63 | public override string stream { | ||
735 | 58 | get { | 64 | get { |
740 | 59 | if (_active_sink_input < 0 || _active_sink_input >= _valid_roles.length) | 65 | switch (this.currentstream) { |
741 | 60 | return "multimedia"; | 66 | case InputStreamType.ALERT: |
742 | 61 | else | 67 | return "alert"; |
743 | 62 | return _valid_roles[_active_sink_input]; | 68 | case InputStreamType.ALARM: |
744 | 69 | return "alarm"; | ||
745 | 70 | case InputStreamType.MULTIMEDIA: | ||
746 | 71 | return "multimedia"; | ||
747 | 72 | case InputStreamType.PHONE: | ||
748 | 73 | return "phone"; | ||
749 | 74 | } | ||
750 | 75 | |||
751 | 76 | return "alert"; | ||
752 | 63 | } | 77 | } |
753 | 64 | } | 78 | } |
754 | 65 | private string? _objp_role_multimedia = null; | ||
755 | 66 | private string? _objp_role_alert = null; | ||
756 | 67 | private string? _objp_role_alarm = null; | ||
757 | 68 | private string? _objp_role_phone = null; | ||
758 | 69 | private uint _pa_volume_sig_count = 0; | ||
759 | 70 | 79 | ||
760 | 80 | /* Accounts Service Variables */ | ||
761 | 71 | private DBusProxy _user_proxy; | 81 | private DBusProxy _user_proxy; |
762 | 72 | private GreeterListInterface _greeter_proxy; | 82 | private GreeterListInterface _greeter_proxy; |
763 | 73 | private Cancellable _mute_cancellable; | 83 | private Cancellable _mute_cancellable; |
764 | @@ -76,23 +86,38 @@ | |||
765 | 76 | private uint _accountservice_volume_timer = 0; | 86 | private uint _accountservice_volume_timer = 0; |
766 | 77 | private bool _send_next_local_volume = false; | 87 | private bool _send_next_local_volume = false; |
767 | 78 | private double _account_service_volume = 0.0; | 88 | private double _account_service_volume = 0.0; |
768 | 89 | |||
769 | 90 | |||
770 | 79 | private bool _active_port_headphone = false; | 91 | private bool _active_port_headphone = false; |
771 | 92 | /* There is not easy way to check if the port is a headset/headphone besides | ||
772 | 93 | * checking for the port name. On touch (with the pulseaudio droid element) | ||
773 | 94 | * the headset/headphone port is called 'output-headset' and 'output-headphone'. | ||
774 | 95 | * On the desktop this is usually called 'analog-output-headphones' */ | ||
775 | 96 | private string[] headphone_outputs = {"output-wired_headset", "output-wired_headphone", "analog-output-headphones"}; | ||
776 | 80 | 97 | ||
777 | 81 | /** true when connected to the pulse server */ | 98 | /** true when connected to the pulse server */ |
779 | 82 | public bool ready { get; set; } | 99 | public override bool ready { get; private set; } |
780 | 83 | 100 | ||
781 | 84 | /** true when a microphone is active **/ | 101 | /** true when a microphone is active **/ |
783 | 85 | public bool active_mic { get; private set; default = false; } | 102 | public override bool active_mic { get; private set; default = false; } |
784 | 86 | 103 | ||
785 | 87 | /** true when high volume warnings should be shown */ | 104 | /** true when high volume warnings should be shown */ |
787 | 88 | public bool high_volume { | 105 | public override bool high_volume { |
788 | 89 | get { | 106 | get { |
790 | 90 | return this._volume > 0.75 && _active_port_headphone; | 107 | return this._volume > 0.75 && _active_port_headphone && stream == "multimedia"; |
791 | 91 | } | 108 | } |
792 | 92 | } | 109 | } |
793 | 93 | 110 | ||
795 | 94 | public VolumeControl () | 111 | public VolumeControlPulse (FocusTracker tracker) |
796 | 95 | { | 112 | { |
797 | 113 | this.focus_tracker = tracker; | ||
798 | 114 | this.focus_tracker.notify["focused-appid"].connect(() => { | ||
799 | 115 | if (!this.ready) | ||
800 | 116 | return; | ||
801 | 117 | |||
802 | 118 | this.update_sink_input(); | ||
803 | 119 | }); | ||
804 | 120 | |||
805 | 96 | if (loop == null) | 121 | if (loop == null) |
806 | 97 | loop = new PulseAudio.GLibMainLoop (); | 122 | loop = new PulseAudio.GLibMainLoop (); |
807 | 98 | 123 | ||
808 | @@ -102,9 +127,16 @@ | |||
809 | 102 | setup_accountsservice.begin (); | 127 | setup_accountsservice.begin (); |
810 | 103 | 128 | ||
811 | 104 | this.reconnect_to_pulse (); | 129 | this.reconnect_to_pulse (); |
812 | 130 | |||
813 | 131 | this.notify["media-playing"].connect(() => { | ||
814 | 132 | update_sink_input(); | ||
815 | 133 | }); | ||
816 | 134 | this.notify["stream"].connect(() => { | ||
817 | 135 | update_stream(); | ||
818 | 136 | }); | ||
819 | 105 | } | 137 | } |
820 | 106 | 138 | ||
822 | 107 | ~VolumeControl () | 139 | ~VolumeControlPulse () |
823 | 108 | { | 140 | { |
824 | 109 | if (_reconnect_timer != 0) { | 141 | if (_reconnect_timer != 0) { |
825 | 110 | Source.remove (_reconnect_timer); | 142 | Source.remove (_reconnect_timer); |
826 | @@ -114,7 +146,10 @@ | |||
827 | 114 | stop_account_service_volume_timer(); | 146 | stop_account_service_volume_timer(); |
828 | 115 | } | 147 | } |
829 | 116 | 148 | ||
831 | 117 | /* PulseAudio logic*/ | 149 | /************************/ |
832 | 150 | /* PulseAudio logic */ | ||
833 | 151 | /************************/ | ||
834 | 152 | |||
835 | 118 | private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index) | 153 | private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index) |
836 | 119 | { | 154 | { |
837 | 120 | switch (t & Context.SubscriptionEventType.FACILITY_MASK) | 155 | switch (t & Context.SubscriptionEventType.FACILITY_MASK) |
838 | @@ -124,23 +159,7 @@ | |||
839 | 124 | break; | 159 | break; |
840 | 125 | 160 | ||
841 | 126 | case Context.SubscriptionEventType.SINK_INPUT: | 161 | case Context.SubscriptionEventType.SINK_INPUT: |
859 | 127 | switch (t & Context.SubscriptionEventType.TYPE_MASK) | 162 | update_sink_input (); |
843 | 128 | { | ||
844 | 129 | case Context.SubscriptionEventType.NEW: | ||
845 | 130 | c.get_sink_input_info (index, handle_new_sink_input_cb); | ||
846 | 131 | break; | ||
847 | 132 | |||
848 | 133 | case Context.SubscriptionEventType.CHANGE: | ||
849 | 134 | c.get_sink_input_info (index, handle_changed_sink_input_cb); | ||
850 | 135 | break; | ||
851 | 136 | |||
852 | 137 | case Context.SubscriptionEventType.REMOVE: | ||
853 | 138 | remove_sink_input_from_list (index); | ||
854 | 139 | break; | ||
855 | 140 | default: | ||
856 | 141 | debug ("Sink input event not known."); | ||
857 | 142 | break; | ||
858 | 143 | } | ||
860 | 144 | break; | 163 | break; |
861 | 145 | 164 | ||
862 | 146 | case Context.SubscriptionEventType.SOURCE: | 165 | case Context.SubscriptionEventType.SOURCE: |
863 | @@ -151,7 +170,14 @@ | |||
864 | 151 | switch (t & Context.SubscriptionEventType.TYPE_MASK) | 170 | switch (t & Context.SubscriptionEventType.TYPE_MASK) |
865 | 152 | { | 171 | { |
866 | 153 | case Context.SubscriptionEventType.NEW: | 172 | case Context.SubscriptionEventType.NEW: |
868 | 154 | c.get_source_output_info (index, source_output_info_cb); | 173 | c.get_source_output_info (index, (context, info, eol) => { |
869 | 174 | if (info == null) | ||
870 | 175 | return; | ||
871 | 176 | |||
872 | 177 | var role = info.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); | ||
873 | 178 | if (role == "phone" || role == "production") | ||
874 | 179 | this.active_mic = true; | ||
875 | 180 | }); | ||
876 | 155 | break; | 181 | break; |
877 | 156 | 182 | ||
878 | 157 | case Context.SubscriptionEventType.REMOVE: | 183 | case Context.SubscriptionEventType.REMOVE: |
879 | @@ -162,291 +188,205 @@ | |||
880 | 162 | } | 188 | } |
881 | 163 | } | 189 | } |
882 | 164 | 190 | ||
883 | 165 | private void sink_info_cb_for_props (Context c, SinkInfo? i, int eol) | ||
884 | 166 | { | ||
885 | 167 | bool old_high_volume = this.high_volume; | ||
886 | 168 | |||
887 | 169 | if (i == null) | ||
888 | 170 | return; | ||
889 | 171 | |||
890 | 172 | if (_mute != (bool)i.mute) | ||
891 | 173 | { | ||
892 | 174 | _mute = (bool)i.mute; | ||
893 | 175 | this.notify_property ("mute"); | ||
894 | 176 | } | ||
895 | 177 | |||
896 | 178 | var playing = (i.state == PulseAudio.SinkState.RUNNING); | ||
897 | 179 | if (_is_playing != playing) | ||
898 | 180 | { | ||
899 | 181 | _is_playing = playing; | ||
900 | 182 | this.notify_property ("is-playing"); | ||
901 | 183 | } | ||
902 | 184 | |||
903 | 185 | /* Check if the current active port is headset/headphone */ | ||
904 | 186 | /* There is not easy way to check if the port is a headset/headphone besides | ||
905 | 187 | * checking for the port name. On touch (with the pulseaudio droid element) | ||
906 | 188 | * the headset/headphone port is called 'output-headset' and 'output-headphone'. | ||
907 | 189 | * On the desktop this is usually called 'analog-output-headphones' */ | ||
908 | 190 | if (i.active_port != null && | ||
909 | 191 | (i.active_port.name == "output-wired_headset" || | ||
910 | 192 | i.active_port.name == "output-wired_headphone" || | ||
911 | 193 | i.active_port.name == "analog-output-headphones")) { | ||
912 | 194 | _active_port_headphone = true; | ||
913 | 195 | } else { | ||
914 | 196 | _active_port_headphone = false; | ||
915 | 197 | } | ||
916 | 198 | |||
917 | 199 | if (_pulse_use_stream_restore == false && | ||
918 | 200 | _volume != volume_to_double (i.volume.max ())) | ||
919 | 201 | { | ||
920 | 202 | _volume = volume_to_double (i.volume.max ()); | ||
921 | 203 | this.notify_property("volume"); | ||
922 | 204 | start_local_volume_timer(); | ||
923 | 205 | } | ||
924 | 206 | |||
925 | 207 | if (this.high_volume != old_high_volume) { | ||
926 | 208 | this.notify_property("high-volume"); | ||
927 | 209 | } | ||
928 | 210 | } | ||
929 | 211 | |||
930 | 212 | private void source_info_cb (Context c, SourceInfo? i, int eol) | ||
931 | 213 | { | ||
932 | 214 | if (i == null) | ||
933 | 215 | return; | ||
934 | 216 | |||
935 | 217 | if (_mic_volume != volume_to_double (i.volume.values[0])) | ||
936 | 218 | { | ||
937 | 219 | _mic_volume = volume_to_double (i.volume.values[0]); | ||
938 | 220 | this.notify_property ("mic-volume"); | ||
939 | 221 | } | ||
940 | 222 | } | ||
941 | 223 | |||
942 | 224 | private void server_info_cb_for_props (Context c, ServerInfo? i) | ||
943 | 225 | { | ||
944 | 226 | if (i == null) | ||
945 | 227 | return; | ||
946 | 228 | context.get_sink_info_by_name (i.default_sink_name, sink_info_cb_for_props); | ||
947 | 229 | } | ||
948 | 230 | |||
949 | 231 | private void update_sink () | 191 | private void update_sink () |
950 | 232 | { | 192 | { |
957 | 233 | context.get_server_info (server_info_cb_for_props); | 193 | if (!this.ready) |
958 | 234 | } | 194 | return; |
959 | 235 | 195 | ||
960 | 236 | private void update_source_get_server_info_cb (PulseAudio.Context c, PulseAudio.ServerInfo? i) { | 196 | context.get_server_info ((context, info) => { |
961 | 237 | if (i != null) | 197 | if (info == null) |
962 | 238 | context.get_source_info_by_name (i.default_source_name, source_info_cb); | 198 | return; |
963 | 199 | |||
964 | 200 | context.get_sink_info_by_name (info.default_sink_name, (context, info, eol) => { | ||
965 | 201 | bool old_high_volume = this.high_volume; | ||
966 | 202 | |||
967 | 203 | if (info == null) | ||
968 | 204 | return; | ||
969 | 205 | |||
970 | 206 | if (_mute != (bool)info.mute) | ||
971 | 207 | { | ||
972 | 208 | _mute = (bool)info.mute; | ||
973 | 209 | this.notify_property ("mute"); | ||
974 | 210 | } | ||
975 | 211 | |||
976 | 212 | this.is_playing = (info.state == PulseAudio.SinkState.RUNNING); | ||
977 | 213 | |||
978 | 214 | /* Check if the current active port is headset/headphone */ | ||
979 | 215 | if (info.active_port.name in headphone_outputs) { | ||
980 | 216 | _active_port_headphone = true; | ||
981 | 217 | } else { | ||
982 | 218 | _active_port_headphone = false; | ||
983 | 219 | } | ||
984 | 220 | |||
985 | 221 | if (_pulse_use_stream_restore == false && | ||
986 | 222 | _volume != volume_to_double (info.volume.max ())) | ||
987 | 223 | { | ||
988 | 224 | _volume = volume_to_double (info.volume.max ()); | ||
989 | 225 | this.notify_property("volume"); | ||
990 | 226 | start_local_volume_timer(); | ||
991 | 227 | } | ||
992 | 228 | |||
993 | 229 | if (this.high_volume != old_high_volume) { | ||
994 | 230 | this.notify_property("high-volume"); | ||
995 | 231 | } | ||
996 | 232 | }); | ||
997 | 233 | }); | ||
998 | 234 | } | ||
999 | 235 | |||
1000 | 236 | private void update_sink_input () | ||
1001 | 237 | { | ||
1002 | 238 | if (!this.ready) | ||
1003 | 239 | return; | ||
1004 | 240 | |||
1005 | 241 | if (!this._pulse_use_stream_restore) | ||
1006 | 242 | return; | ||
1007 | 243 | |||
1008 | 244 | this.tempstream = InputStreamType.ALERT; | ||
1009 | 245 | if (this.media_playing) | ||
1010 | 246 | this.tempstream = InputStreamType.MULTIMEDIA; | ||
1011 | 247 | |||
1012 | 248 | context.get_sink_input_info_list((context, info, eol) => { | ||
1013 | 249 | var inputstream = InputStreamType.ALARM; | ||
1014 | 250 | |||
1015 | 251 | if (info != null) { | ||
1016 | 252 | var role = info.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); | ||
1017 | 253 | var corked = (info.corked != 0); | ||
1018 | 254 | var appid = info.proplist.gets (PulseAudio.Proplist.PROP_APPLICATION_ID); | ||
1019 | 255 | |||
1020 | 256 | /* Override the corked value for the phone as it's always corked */ | ||
1021 | 257 | if (role != null && role == "phone") | ||
1022 | 258 | corked = false; | ||
1023 | 259 | |||
1024 | 260 | if (!corked && role != null && appid == this.focus_tracker.focused_appid) { | ||
1025 | 261 | switch (role) { | ||
1026 | 262 | case "phone": | ||
1027 | 263 | inputstream = InputStreamType.PHONE; | ||
1028 | 264 | break; | ||
1029 | 265 | case "alarm": | ||
1030 | 266 | inputstream = InputStreamType.ALARM; | ||
1031 | 267 | break; | ||
1032 | 268 | case "alert": | ||
1033 | 269 | inputstream = InputStreamType.ALERT; | ||
1034 | 270 | break; | ||
1035 | 271 | case "multimedia": | ||
1036 | 272 | default: | ||
1037 | 273 | /* We treat unknown roles (i.e. 'music') as multimedia */ | ||
1038 | 274 | inputstream = InputStreamType.MULTIMEDIA; | ||
1039 | 275 | break; | ||
1040 | 276 | } | ||
1041 | 277 | } | ||
1042 | 278 | } | ||
1043 | 279 | |||
1044 | 280 | if (inputstream > this.tempstream) { | ||
1045 | 281 | this.tempstream = inputstream; | ||
1046 | 282 | } | ||
1047 | 283 | |||
1048 | 284 | if (eol != 0) { | ||
1049 | 285 | if (this.currentstream != this.tempstream) { | ||
1050 | 286 | this.currentstream = this.tempstream; | ||
1051 | 287 | debug("Current stream: %s", this.stream); | ||
1052 | 288 | this.notify_property("stream"); | ||
1053 | 289 | } | ||
1054 | 290 | } | ||
1055 | 291 | }); | ||
1056 | 292 | } | ||
1057 | 293 | |||
1058 | 294 | /* Look at a given profile or role and figure out what our volume and | ||
1059 | 295 | mute settings should be based on that. */ | ||
1060 | 296 | private void update_stream () | ||
1061 | 297 | { | ||
1062 | 298 | |||
1063 | 299 | |||
1064 | 239 | } | 300 | } |
1065 | 240 | 301 | ||
1066 | 241 | private void update_source () | 302 | private void update_source () |
1067 | 242 | { | 303 | { |
1161 | 243 | context.get_server_info (update_source_get_server_info_cb); | 304 | if (!this.ready) |
1162 | 244 | } | 305 | return; |
1163 | 245 | 306 | ||
1164 | 246 | private DBusMessage pulse_dbus_filter (DBusConnection connection, owned DBusMessage message, bool incoming) | 307 | context.get_server_info ((context, info) => { |
1165 | 247 | { | 308 | if (info == null) |
1166 | 248 | if (message.get_message_type () == DBusMessageType.SIGNAL) { | 309 | return; |
1167 | 249 | string active_role_objp = _objp_role_alert; | 310 | |
1168 | 250 | if (_active_sink_input != -1) | 311 | context.get_source_info_by_name (info.default_source_name, (context, info, eol) => { |
1169 | 251 | active_role_objp = _sink_input_hash.get (_active_sink_input); | 312 | if (info == null) |
1170 | 252 | 313 | return; | |
1171 | 253 | if (message.get_path () == active_role_objp && message.get_member () == "VolumeUpdated") { | 314 | |
1172 | 254 | uint sig_count = 0; | 315 | if (_mic_volume != volume_to_double (info.volume.values[0])) |
1080 | 255 | lock (_pa_volume_sig_count) { | ||
1081 | 256 | sig_count = _pa_volume_sig_count; | ||
1082 | 257 | if (_pa_volume_sig_count > 0) | ||
1083 | 258 | _pa_volume_sig_count--; | ||
1084 | 259 | } | ||
1085 | 260 | |||
1086 | 261 | /* We only care about signals if our internal count is zero */ | ||
1087 | 262 | if (sig_count == 0) { | ||
1088 | 263 | /* Extract volume and make sure it's not a side effect of us setting it */ | ||
1089 | 264 | Variant body = message.get_body (); | ||
1090 | 265 | Variant varray = body.get_child_value (0); | ||
1091 | 266 | |||
1092 | 267 | uint32 type = 0, volume = 0; | ||
1093 | 268 | VariantIter iter = varray.iterator (); | ||
1094 | 269 | iter.next ("(uu)", &type, &volume); | ||
1095 | 270 | /* Here we need to compare integer values to avoid rounding issues, so just | ||
1096 | 271 | * using the volume values used by pulseaudio */ | ||
1097 | 272 | PulseAudio.Volume cvolume = double_to_volume (_volume); | ||
1098 | 273 | if (volume != cvolume) { | ||
1099 | 274 | /* Someone else changed the volume for this role, reflect on the indicator */ | ||
1100 | 275 | _volume = volume_to_double (volume); | ||
1101 | 276 | this.notify_property("volume"); | ||
1102 | 277 | start_local_volume_timer(); | ||
1103 | 278 | } | ||
1104 | 279 | } | ||
1105 | 280 | } | ||
1106 | 281 | } | ||
1107 | 282 | |||
1108 | 283 | return message; | ||
1109 | 284 | } | ||
1110 | 285 | |||
1111 | 286 | private async void update_active_sink_input (uint32 index) | ||
1112 | 287 | { | ||
1113 | 288 | if ((index == -1) || (index != _active_sink_input && index in _sink_input_list)) { | ||
1114 | 289 | string sink_input_objp = _objp_role_alert; | ||
1115 | 290 | if (index != -1) | ||
1116 | 291 | sink_input_objp = _sink_input_hash.get (index); | ||
1117 | 292 | _active_sink_input = index; | ||
1118 | 293 | |||
1119 | 294 | /* Listen for role volume changes from pulse itself (external clients) */ | ||
1120 | 295 | try { | ||
1121 | 296 | var builder = new VariantBuilder (new VariantType ("ao")); | ||
1122 | 297 | builder.add ("o", sink_input_objp); | ||
1123 | 298 | |||
1124 | 299 | yield _pconn.call ("org.PulseAudio.Core1", "/org/pulseaudio/core1", | ||
1125 | 300 | "org.PulseAudio.Core1", "ListenForSignal", | ||
1126 | 301 | new Variant ("(sao)", "org.PulseAudio.Ext.StreamRestore1.RestoreEntry.VolumeUpdated", builder), | ||
1127 | 302 | null, DBusCallFlags.NONE, -1); | ||
1128 | 303 | } catch (GLib.Error e) { | ||
1129 | 304 | warning ("unable to listen for pulseaudio dbus signals (%s)", e.message); | ||
1130 | 305 | } | ||
1131 | 306 | |||
1132 | 307 | try { | ||
1133 | 308 | var props_variant = yield _pconn.call ("org.PulseAudio.Ext.StreamRestore1.RestoreEntry", | ||
1134 | 309 | sink_input_objp, "org.freedesktop.DBus.Properties", "Get", | ||
1135 | 310 | new Variant ("(ss)", "org.PulseAudio.Ext.StreamRestore1.RestoreEntry", "Volume"), | ||
1136 | 311 | null, DBusCallFlags.NONE, -1); | ||
1137 | 312 | Variant tmp; | ||
1138 | 313 | props_variant.get ("(v)", out tmp); | ||
1139 | 314 | uint32 type = 0, volume = 0; | ||
1140 | 315 | VariantIter iter = tmp.iterator (); | ||
1141 | 316 | iter.next ("(uu)", &type, &volume); | ||
1142 | 317 | |||
1143 | 318 | _volume = volume_to_double (volume); | ||
1144 | 319 | this.notify_property("volume"); | ||
1145 | 320 | start_local_volume_timer(); | ||
1146 | 321 | } catch (GLib.Error e) { | ||
1147 | 322 | warning ("unable to get volume for active role %s (%s)", sink_input_objp, e.message); | ||
1148 | 323 | } | ||
1149 | 324 | } | ||
1150 | 325 | } | ||
1151 | 326 | |||
1152 | 327 | private void add_sink_input_into_list (SinkInputInfo sink_input) | ||
1153 | 328 | { | ||
1154 | 329 | /* We're only adding ones that are not corked and with a valid role */ | ||
1155 | 330 | var role = sink_input.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); | ||
1156 | 331 | |||
1157 | 332 | if (role != null && role in _valid_roles) { | ||
1158 | 333 | if (sink_input.corked == 0 || role == "phone") { | ||
1159 | 334 | _sink_input_list.insert (0, sink_input.index); | ||
1160 | 335 | switch (role) | ||
1173 | 336 | { | 316 | { |
1186 | 337 | case "multimedia": | 317 | _mic_volume = volume_to_double (info.volume.values[0]); |
1187 | 338 | _sink_input_hash.set (sink_input.index, _objp_role_multimedia); | 318 | this.notify_property ("mic-volume"); |
1176 | 339 | break; | ||
1177 | 340 | case "alert": | ||
1178 | 341 | _sink_input_hash.set (sink_input.index, _objp_role_alert); | ||
1179 | 342 | break; | ||
1180 | 343 | case "alarm": | ||
1181 | 344 | _sink_input_hash.set (sink_input.index, _objp_role_alarm); | ||
1182 | 345 | break; | ||
1183 | 346 | case "phone": | ||
1184 | 347 | _sink_input_hash.set (sink_input.index, _objp_role_phone); | ||
1185 | 348 | break; | ||
1188 | 349 | } | 319 | } |
1242 | 350 | /* Only switch the active sink input in case a phone one is not active */ | 320 | }); |
1243 | 351 | if (_active_sink_input == -1 || | 321 | }); |
1191 | 352 | _sink_input_hash.get (_active_sink_input) != _objp_role_phone) | ||
1192 | 353 | update_active_sink_input.begin (sink_input.index); | ||
1193 | 354 | } | ||
1194 | 355 | } | ||
1195 | 356 | } | ||
1196 | 357 | |||
1197 | 358 | private void remove_sink_input_from_list (uint32 index) | ||
1198 | 359 | { | ||
1199 | 360 | if (index in _sink_input_list) { | ||
1200 | 361 | _sink_input_list.remove (index); | ||
1201 | 362 | _sink_input_hash.unset (index); | ||
1202 | 363 | if (index == _active_sink_input) { | ||
1203 | 364 | if (_sink_input_list.size != 0) | ||
1204 | 365 | update_active_sink_input.begin (_sink_input_list.get (0)); | ||
1205 | 366 | else | ||
1206 | 367 | update_active_sink_input.begin (-1); | ||
1207 | 368 | } | ||
1208 | 369 | } | ||
1209 | 370 | } | ||
1210 | 371 | |||
1211 | 372 | private void handle_new_sink_input_cb (Context c, SinkInputInfo? i, int eol) | ||
1212 | 373 | { | ||
1213 | 374 | if (i == null) | ||
1214 | 375 | return; | ||
1215 | 376 | |||
1216 | 377 | add_sink_input_into_list (i); | ||
1217 | 378 | } | ||
1218 | 379 | |||
1219 | 380 | private void handle_changed_sink_input_cb (Context c, SinkInputInfo? i, int eol) | ||
1220 | 381 | { | ||
1221 | 382 | if (i == null) | ||
1222 | 383 | return; | ||
1223 | 384 | |||
1224 | 385 | if (i.index in _sink_input_list) { | ||
1225 | 386 | /* Phone stream is always corked, so handle it differently */ | ||
1226 | 387 | if (i.corked == 1 && _sink_input_hash.get (i.index) != _objp_role_phone) | ||
1227 | 388 | remove_sink_input_from_list (i.index); | ||
1228 | 389 | } else { | ||
1229 | 390 | if (i.corked == 0) | ||
1230 | 391 | add_sink_input_into_list (i); | ||
1231 | 392 | } | ||
1232 | 393 | } | ||
1233 | 394 | |||
1234 | 395 | private void source_output_info_cb (Context c, SourceOutputInfo? i, int eol) | ||
1235 | 396 | { | ||
1236 | 397 | if (i == null) | ||
1237 | 398 | return; | ||
1238 | 399 | |||
1239 | 400 | var role = i.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); | ||
1240 | 401 | if (role == "phone" || role == "production") | ||
1241 | 402 | this.active_mic = true; | ||
1244 | 403 | } | 322 | } |
1245 | 404 | 323 | ||
1246 | 405 | private void context_state_callback (Context c) | 324 | private void context_state_callback (Context c) |
1247 | 406 | { | 325 | { |
1248 | 407 | switch (c.get_state ()) { | 326 | switch (c.get_state ()) { |
1249 | 408 | case Context.State.READY: | 327 | case Context.State.READY: |
1264 | 409 | if (_pulse_use_stream_restore) { | 328 | PulseAudio.Extension.StreamRestore.test(this.context, (context, version) => { |
1265 | 410 | c.subscribe (PulseAudio.Context.SubscriptionMask.SINK | | 329 | if (version != 0) { |
1266 | 411 | PulseAudio.Context.SubscriptionMask.SINK_INPUT | | 330 | debug("Got the stream restore extension"); |
1267 | 412 | PulseAudio.Context.SubscriptionMask.SOURCE | | 331 | _pulse_use_stream_restore = true; |
1268 | 413 | PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT); | 332 | } else { |
1269 | 414 | } else { | 333 | debug("No stream restore extension found"); |
1270 | 415 | c.subscribe (PulseAudio.Context.SubscriptionMask.SINK | | 334 | } |
1271 | 416 | PulseAudio.Context.SubscriptionMask.SOURCE | | 335 | |
1272 | 417 | PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT); | 336 | if (_pulse_use_stream_restore) { |
1273 | 418 | } | 337 | this.context.subscribe (PulseAudio.Context.SubscriptionMask.SINK | |
1274 | 419 | c.set_subscribe_callback (context_events_cb); | 338 | PulseAudio.Context.SubscriptionMask.SINK_INPUT | |
1275 | 420 | update_sink (); | 339 | PulseAudio.Context.SubscriptionMask.SOURCE | |
1276 | 421 | update_source (); | 340 | PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT); |
1277 | 422 | this.ready = true; | 341 | } else { |
1278 | 342 | this.context.subscribe (PulseAudio.Context.SubscriptionMask.SINK | | ||
1279 | 343 | PulseAudio.Context.SubscriptionMask.SOURCE | | ||
1280 | 344 | PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT); | ||
1281 | 345 | } | ||
1282 | 346 | this.context.set_subscribe_callback (context_events_cb); | ||
1283 | 347 | this.ready = true; | ||
1284 | 348 | |||
1285 | 349 | debug("Pulse state ready"); | ||
1286 | 350 | |||
1287 | 351 | update_sink (); | ||
1288 | 352 | update_sink_input (); | ||
1289 | 353 | update_source (); | ||
1290 | 354 | update_stream (); | ||
1291 | 355 | }); | ||
1292 | 356 | |||
1293 | 423 | break; | 357 | break; |
1294 | 424 | 358 | ||
1295 | 425 | case Context.State.FAILED: | 359 | case Context.State.FAILED: |
1296 | 426 | case Context.State.TERMINATED: | 360 | case Context.State.TERMINATED: |
1297 | 361 | warning("Pulse state disconnected, retrying."); | ||
1298 | 362 | this.ready = false; | ||
1299 | 427 | if (_reconnect_timer == 0) | 363 | if (_reconnect_timer == 0) |
1301 | 428 | _reconnect_timer = Timeout.add_seconds (2, reconnect_timeout); | 364 | _reconnect_timer = Timeout.add_seconds (2, () => { |
1302 | 365 | _reconnect_timer = 0; | ||
1303 | 366 | reconnect_to_pulse (); | ||
1304 | 367 | return false; // G_SOURCE_REMOVE | ||
1305 | 368 | }); | ||
1306 | 369 | break; | ||
1307 | 370 | |||
1308 | 371 | case Context.State.CONNECTING: | ||
1309 | 372 | case Context.State.AUTHORIZING: | ||
1310 | 373 | case Context.State.SETTING_NAME: | ||
1311 | 374 | debug(@"Pulse state initializing step $((int)c.get_state())"); | ||
1312 | 429 | break; | 375 | break; |
1313 | 430 | 376 | ||
1314 | 431 | default: | 377 | default: |
1316 | 432 | this.ready = false; | 378 | warning("Unknown state returned by Pulse Audio context. Not reconnecting."); |
1317 | 433 | break; | 379 | break; |
1318 | 434 | } | 380 | } |
1319 | 435 | } | 381 | } |
1320 | 436 | 382 | ||
1321 | 437 | bool reconnect_timeout () | ||
1322 | 438 | { | ||
1323 | 439 | _reconnect_timer = 0; | ||
1324 | 440 | reconnect_to_pulse (); | ||
1325 | 441 | return false; // G_SOURCE_REMOVE | ||
1326 | 442 | } | ||
1327 | 443 | |||
1328 | 444 | void reconnect_to_pulse () | 383 | void reconnect_to_pulse () |
1329 | 445 | { | 384 | { |
1330 | 446 | if (this.ready) { | 385 | if (this.ready) { |
1331 | 447 | this.context.disconnect (); | 386 | this.context.disconnect (); |
1332 | 448 | this.context = null; | 387 | this.context = null; |
1333 | 449 | this.ready = false; | 388 | this.ready = false; |
1334 | 389 | this._pulse_use_stream_restore = false; | ||
1335 | 450 | } | 390 | } |
1336 | 451 | 391 | ||
1337 | 452 | var props = new Proplist (); | 392 | var props = new Proplist (); |
1338 | @@ -455,8 +395,6 @@ | |||
1339 | 455 | props.sets (Proplist.PROP_APPLICATION_ICON_NAME, "multimedia-volume-control"); | 395 | props.sets (Proplist.PROP_APPLICATION_ICON_NAME, "multimedia-volume-control"); |
1340 | 456 | props.sets (Proplist.PROP_APPLICATION_VERSION, "0.1"); | 396 | props.sets (Proplist.PROP_APPLICATION_VERSION, "0.1"); |
1341 | 457 | 397 | ||
1342 | 458 | reconnect_pulse_dbus (); | ||
1343 | 459 | |||
1344 | 460 | this.context = new PulseAudio.Context (loop.get_api(), null, props); | 398 | this.context = new PulseAudio.Context (loop.get_api(), null, props); |
1345 | 461 | this.context.set_state_callback (context_state_callback); | 399 | this.context.set_state_callback (context_state_callback); |
1346 | 462 | 400 | ||
1347 | @@ -464,60 +402,54 @@ | |||
1348 | 464 | warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno())); | 402 | warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno())); |
1349 | 465 | } | 403 | } |
1350 | 466 | 404 | ||
1362 | 467 | void sink_info_list_callback_set_mute (PulseAudio.Context context, PulseAudio.SinkInfo? sink, int eol) { | 405 | /******************************/ |
1363 | 468 | if (sink != null) | 406 | /* Mute operations */ |
1364 | 469 | context.set_sink_mute_by_index (sink.index, true, null); | 407 | /******************************/ |
1365 | 470 | } | 408 | |
1355 | 471 | |||
1356 | 472 | void sink_info_list_callback_unset_mute (PulseAudio.Context context, PulseAudio.SinkInfo? sink, int eol) { | ||
1357 | 473 | if (sink != null) | ||
1358 | 474 | context.set_sink_mute_by_index (sink.index, false, null); | ||
1359 | 475 | } | ||
1360 | 476 | |||
1361 | 477 | /* Mute operations */ | ||
1366 | 478 | bool set_mute_internal (bool mute) | 409 | bool set_mute_internal (bool mute) |
1367 | 479 | { | 410 | { |
1369 | 480 | return_val_if_fail (context.get_state () == Context.State.READY, false); | 411 | if (!this.ready) |
1370 | 412 | return false; | ||
1371 | 481 | 413 | ||
1372 | 482 | if (_mute != mute) { | 414 | if (_mute != mute) { |
1373 | 483 | if (mute) | 415 | if (mute) |
1375 | 484 | context.get_sink_info_list (sink_info_list_callback_set_mute); | 416 | context.get_sink_info_list ((context, sink, eol) => { |
1376 | 417 | if (sink != null) | ||
1377 | 418 | context.set_sink_mute_by_index (sink.index, true, null); | ||
1378 | 419 | }); | ||
1379 | 485 | else | 420 | else |
1381 | 486 | context.get_sink_info_list (sink_info_list_callback_unset_mute); | 421 | context.get_sink_info_list ((context, sink, eol) => { |
1382 | 422 | if (sink != null) | ||
1383 | 423 | context.set_sink_mute_by_index (sink.index, false, null); | ||
1384 | 424 | }); | ||
1385 | 487 | return true; | 425 | return true; |
1386 | 488 | } else { | 426 | } else { |
1387 | 489 | return false; | 427 | return false; |
1388 | 490 | } | 428 | } |
1389 | 491 | } | 429 | } |
1390 | 492 | 430 | ||
1391 | 493 | public void set_mute (bool mute) | ||
1392 | 494 | { | ||
1393 | 495 | if (set_mute_internal (mute)) | ||
1394 | 496 | sync_mute_to_accountsservice.begin (mute); | ||
1395 | 497 | } | ||
1396 | 498 | |||
1397 | 499 | public void toggle_mute () | 431 | public void toggle_mute () |
1398 | 500 | { | 432 | { |
1400 | 501 | this.set_mute (!this._mute); | 433 | this.mute = !this._mute; |
1401 | 502 | } | 434 | } |
1402 | 503 | 435 | ||
1404 | 504 | public bool mute | 436 | public override bool mute |
1405 | 505 | { | 437 | { |
1406 | 506 | get | 438 | get |
1407 | 507 | { | 439 | { |
1408 | 508 | return this._mute; | 440 | return this._mute; |
1409 | 509 | } | 441 | } |
1415 | 510 | } | 442 | set |
1411 | 511 | |||
1412 | 512 | public bool is_playing | ||
1413 | 513 | { | ||
1414 | 514 | get | ||
1416 | 515 | { | 443 | { |
1418 | 516 | return this._is_playing; | 444 | if (set_mute_internal (value)) |
1419 | 445 | sync_mute_to_accountsservice.begin (value); | ||
1420 | 517 | } | 446 | } |
1421 | 518 | } | 447 | } |
1422 | 519 | 448 | ||
1424 | 520 | /* Volume operations */ | 449 | /******************************/ |
1425 | 450 | /* Volume operations */ | ||
1426 | 451 | /******************************/ | ||
1427 | 452 | |||
1428 | 521 | private static PulseAudio.Volume double_to_volume (double vol) | 453 | private static PulseAudio.Volume double_to_volume (double vol) |
1429 | 522 | { | 454 | { |
1430 | 523 | double tmp = (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED) * vol; | 455 | double tmp = (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED) * vol; |
1431 | @@ -530,79 +462,41 @@ | |||
1432 | 530 | return tmp / (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED); | 462 | return tmp / (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED); |
1433 | 531 | } | 463 | } |
1434 | 532 | 464 | ||
1496 | 533 | private void set_volume_success_cb (Context c, int success) | 465 | bool set_volume_internal (double newvolume) |
1497 | 534 | { | 466 | { |
1498 | 535 | if ((bool)success) | 467 | if (!this.ready) |
1438 | 536 | this.notify_property("volume"); | ||
1439 | 537 | } | ||
1440 | 538 | |||
1441 | 539 | private void sink_info_set_volume_cb (Context c, SinkInfo? i, int eol) | ||
1442 | 540 | { | ||
1443 | 541 | if (i == null) | ||
1444 | 542 | return; | ||
1445 | 543 | |||
1446 | 544 | unowned CVolume cvol = i.volume; | ||
1447 | 545 | cvol.scale (double_to_volume (_volume)); | ||
1448 | 546 | c.set_sink_volume_by_index (i.index, cvol, set_volume_success_cb); | ||
1449 | 547 | } | ||
1450 | 548 | |||
1451 | 549 | private void server_info_cb_for_set_volume (Context c, ServerInfo? i) | ||
1452 | 550 | { | ||
1453 | 551 | if (i == null) | ||
1454 | 552 | { | ||
1455 | 553 | warning ("Could not get PulseAudio server info"); | ||
1456 | 554 | return; | ||
1457 | 555 | } | ||
1458 | 556 | |||
1459 | 557 | context.get_sink_info_by_name (i.default_sink_name, sink_info_set_volume_cb); | ||
1460 | 558 | } | ||
1461 | 559 | |||
1462 | 560 | private async void set_volume_active_role () | ||
1463 | 561 | { | ||
1464 | 562 | string active_role_objp = _objp_role_alert; | ||
1465 | 563 | |||
1466 | 564 | if (_active_sink_input != -1 && _active_sink_input in _sink_input_list) | ||
1467 | 565 | active_role_objp = _sink_input_hash.get (_active_sink_input); | ||
1468 | 566 | |||
1469 | 567 | try { | ||
1470 | 568 | var builder = new VariantBuilder (new VariantType ("a(uu)")); | ||
1471 | 569 | builder.add ("(uu)", 0, double_to_volume (_volume)); | ||
1472 | 570 | Variant volume = builder.end (); | ||
1473 | 571 | |||
1474 | 572 | /* Increase the signal counter so we can handle the callback */ | ||
1475 | 573 | lock (_pa_volume_sig_count) { | ||
1476 | 574 | _pa_volume_sig_count++; | ||
1477 | 575 | } | ||
1478 | 576 | |||
1479 | 577 | yield _pconn.call ("org.PulseAudio.Ext.StreamRestore1.RestoreEntry", | ||
1480 | 578 | active_role_objp, "org.freedesktop.DBus.Properties", "Set", | ||
1481 | 579 | new Variant ("(ssv)", "org.PulseAudio.Ext.StreamRestore1.RestoreEntry", "Volume", volume), | ||
1482 | 580 | null, DBusCallFlags.NONE, -1); | ||
1483 | 581 | |||
1484 | 582 | this.notify_property("volume"); | ||
1485 | 583 | } catch (GLib.Error e) { | ||
1486 | 584 | lock (_pa_volume_sig_count) { | ||
1487 | 585 | _pa_volume_sig_count--; | ||
1488 | 586 | } | ||
1489 | 587 | warning ("unable to set volume for stream obj path %s (%s)", active_role_objp, e.message); | ||
1490 | 588 | } | ||
1491 | 589 | } | ||
1492 | 590 | |||
1493 | 591 | bool set_volume_internal (double volume) | ||
1494 | 592 | { | ||
1495 | 593 | if (context.get_state () != Context.State.READY) | ||
1499 | 594 | return false; | 468 | return false; |
1500 | 595 | 469 | ||
1502 | 596 | if (_volume != volume) { | 470 | if (_volume != newvolume) { |
1503 | 597 | var old_high_volume = this.high_volume; | 471 | var old_high_volume = this.high_volume; |
1512 | 598 | 472 | /* Ideally, we'd be able to set this when we give the value to Pulse, but | |
1513 | 599 | _volume = volume; | 473 | we kinda have a Vala bug that's messing up capturing the variables. So |
1514 | 600 | if (_pulse_use_stream_restore) | 474 | we're setting it here so that the VolumeControl object can track it all |
1515 | 601 | set_volume_active_role.begin (); | 475 | the way through */ |
1516 | 602 | else | 476 | /* https://bugzilla.gnome.org/show_bug.cgi?id=741485 */ |
1517 | 603 | context.get_server_info (server_info_cb_for_set_volume); | 477 | _volume = newvolume; |
1518 | 604 | 478 | ||
1519 | 605 | this.notify_property("volume"); | 479 | context.get_server_info ((context, i) => { |
1520 | 480 | if (i == null) | ||
1521 | 481 | { | ||
1522 | 482 | warning ("Could not get PulseAudio server info"); | ||
1523 | 483 | return; | ||
1524 | 484 | } | ||
1525 | 485 | |||
1526 | 486 | context.get_sink_info_by_name (i.default_sink_name, (context, info, eol) => { | ||
1527 | 487 | if (info == null) | ||
1528 | 488 | return; | ||
1529 | 489 | |||
1530 | 490 | unowned CVolume cvol = info.volume; | ||
1531 | 491 | cvol.scale (double_to_volume (this._volume)); | ||
1532 | 492 | context.set_sink_volume_by_index (info.index, cvol, (context, success) => { | ||
1533 | 493 | if (!(bool)success) | ||
1534 | 494 | return; | ||
1535 | 495 | |||
1536 | 496 | this.notify_property("volume"); | ||
1537 | 497 | }); | ||
1538 | 498 | }); | ||
1539 | 499 | }); | ||
1540 | 606 | 500 | ||
1541 | 607 | if (this.high_volume != old_high_volume) | 501 | if (this.high_volume != old_high_volume) |
1542 | 608 | this.notify_property("high-volume"); | 502 | this.notify_property("high-volume"); |
1543 | @@ -613,21 +507,7 @@ | |||
1544 | 613 | } | 507 | } |
1545 | 614 | } | 508 | } |
1546 | 615 | 509 | ||
1562 | 616 | void set_mic_volume_success_cb (Context c, int success) | 510 | public override double volume { |
1548 | 617 | { | ||
1549 | 618 | if ((bool)success) | ||
1550 | 619 | this.notify_property ("mic-volume"); | ||
1551 | 620 | } | ||
1552 | 621 | |||
1553 | 622 | void set_mic_volume_get_server_info_cb (PulseAudio.Context c, PulseAudio.ServerInfo? i) { | ||
1554 | 623 | if (i != null) { | ||
1555 | 624 | unowned CVolume cvol = CVolume (); | ||
1556 | 625 | cvol = vol_set (cvol, 1, double_to_volume (_mic_volume)); | ||
1557 | 626 | c.set_source_volume_by_name (i.default_source_name, cvol, set_mic_volume_success_cb); | ||
1558 | 627 | } | ||
1559 | 628 | } | ||
1560 | 629 | |||
1561 | 630 | public double volume { | ||
1563 | 631 | get { | 511 | get { |
1564 | 632 | return _volume; | 512 | return _volume; |
1565 | 633 | } | 513 | } |
1566 | @@ -638,99 +518,39 @@ | |||
1567 | 638 | } | 518 | } |
1568 | 639 | } | 519 | } |
1569 | 640 | 520 | ||
1571 | 641 | public double mic_volume { | 521 | public override double mic_volume { |
1572 | 642 | get { | 522 | get { |
1573 | 643 | return _mic_volume; | 523 | return _mic_volume; |
1574 | 644 | } | 524 | } |
1575 | 645 | set { | 525 | set { |
1577 | 646 | return_if_fail (context.get_state () == Context.State.READY); | 526 | if (!this.ready) |
1578 | 527 | return; | ||
1579 | 647 | 528 | ||
1580 | 529 | /* Ideally, we'd be able to set this when we give the value to Pulse, but | ||
1581 | 530 | we kinda have a Vala bug that's messing up capturing the variables. So | ||
1582 | 531 | we're setting it here so that the VolumeControl object can track it all | ||
1583 | 532 | the way through */ | ||
1584 | 533 | /* https://bugzilla.gnome.org/show_bug.cgi?id=741485 */ | ||
1585 | 648 | _mic_volume = value; | 534 | _mic_volume = value; |
1586 | 649 | 535 | ||
1670 | 650 | context.get_server_info (set_mic_volume_get_server_info_cb); | 536 | context.get_server_info ((context, info) => { |
1671 | 651 | } | 537 | if (info == null) |
1672 | 652 | } | 538 | return; |
1673 | 653 | 539 | ||
1674 | 654 | /* PulseAudio Dbus (Stream Restore) logic */ | 540 | unowned CVolume cvol = CVolume (); |
1675 | 655 | private void reconnect_pulse_dbus () | 541 | cvol = vol_set (cvol, 1, double_to_volume (_mic_volume)); |
1676 | 656 | { | 542 | context.set_source_volume_by_name (info.default_source_name, cvol, (context, success) => { |
1677 | 657 | unowned string pulse_dbus_server_env = Environment.get_variable ("PULSE_DBUS_SERVER"); | 543 | if ((bool)success) |
1678 | 658 | string address; | 544 | this.notify_property ("mic-volume"); |
1679 | 659 | 545 | }); | |
1680 | 660 | /* In case of a reconnect */ | 546 | }); |
1681 | 661 | _pulse_use_stream_restore = false; | 547 | } |
1682 | 662 | _pa_volume_sig_count = 0; | 548 | } |
1683 | 663 | 549 | ||
1684 | 664 | if (pulse_dbus_server_env != null) { | 550 | /******************************/ |
1602 | 665 | address = pulse_dbus_server_env; | ||
1603 | 666 | } else { | ||
1604 | 667 | DBusConnection conn; | ||
1605 | 668 | Variant props; | ||
1606 | 669 | |||
1607 | 670 | try { | ||
1608 | 671 | conn = Bus.get_sync (BusType.SESSION); | ||
1609 | 672 | } catch (GLib.IOError e) { | ||
1610 | 673 | warning ("unable to get the dbus session bus: %s", e.message); | ||
1611 | 674 | return; | ||
1612 | 675 | } | ||
1613 | 676 | |||
1614 | 677 | try { | ||
1615 | 678 | var props_variant = conn.call_sync ("org.PulseAudio1", | ||
1616 | 679 | "/org/pulseaudio/server_lookup1", "org.freedesktop.DBus.Properties", | ||
1617 | 680 | "Get", new Variant ("(ss)", "org.PulseAudio.ServerLookup1", "Address"), | ||
1618 | 681 | null, DBusCallFlags.NONE, -1); | ||
1619 | 682 | props_variant.get ("(v)", out props); | ||
1620 | 683 | address = props.get_string (); | ||
1621 | 684 | } catch (GLib.Error e) { | ||
1622 | 685 | warning ("unable to get pulse unix socket: %s", e.message); | ||
1623 | 686 | return; | ||
1624 | 687 | } | ||
1625 | 688 | } | ||
1626 | 689 | |||
1627 | 690 | stdout.printf ("PulseAudio dbus unix socket: %s\n", address); | ||
1628 | 691 | try { | ||
1629 | 692 | _pconn = new DBusConnection.for_address_sync (address, DBusConnectionFlags.AUTHENTICATION_CLIENT); | ||
1630 | 693 | } catch (GLib.Error e) { | ||
1631 | 694 | /* If it fails, it means the dbus pulse extension is not available */ | ||
1632 | 695 | return; | ||
1633 | 696 | } | ||
1634 | 697 | |||
1635 | 698 | /* For pulse dbus related events */ | ||
1636 | 699 | _pconn.add_filter (pulse_dbus_filter); | ||
1637 | 700 | |||
1638 | 701 | /* Check if the 4 currently supported media roles are already available in StreamRestore | ||
1639 | 702 | * Roles: multimedia, alert, alarm and phone */ | ||
1640 | 703 | _objp_role_multimedia = stream_restore_get_object_path ("sink-input-by-media-role:multimedia"); | ||
1641 | 704 | _objp_role_alert = stream_restore_get_object_path ("sink-input-by-media-role:alert"); | ||
1642 | 705 | _objp_role_alarm = stream_restore_get_object_path ("sink-input-by-media-role:alarm"); | ||
1643 | 706 | _objp_role_phone = stream_restore_get_object_path ("sink-input-by-media-role:phone"); | ||
1644 | 707 | |||
1645 | 708 | /* Only use stream restore if every used role is available */ | ||
1646 | 709 | if (_objp_role_multimedia != null && _objp_role_alert != null && _objp_role_alarm != null && _objp_role_phone != null) { | ||
1647 | 710 | stdout.printf ("Using PulseAudio DBUS Stream Restore module\n"); | ||
1648 | 711 | /* Restore volume and update default entry */ | ||
1649 | 712 | update_active_sink_input.begin (-1); | ||
1650 | 713 | _pulse_use_stream_restore = true; | ||
1651 | 714 | } | ||
1652 | 715 | } | ||
1653 | 716 | |||
1654 | 717 | private string? stream_restore_get_object_path (string name) { | ||
1655 | 718 | string? objp = null; | ||
1656 | 719 | try { | ||
1657 | 720 | Variant props_variant = _pconn.call_sync ("org.PulseAudio.Ext.StreamRestore1", | ||
1658 | 721 | "/org/pulseaudio/stream_restore1", "org.PulseAudio.Ext.StreamRestore1", | ||
1659 | 722 | "GetEntryByName", new Variant ("(s)", name), null, DBusCallFlags.NONE, -1); | ||
1660 | 723 | /* Workaround for older versions of vala that don't provide get_objv */ | ||
1661 | 724 | VariantIter iter = props_variant.iterator (); | ||
1662 | 725 | iter.next ("o", &objp); | ||
1663 | 726 | stdout.printf ("Found obj path %s for restore data named %s\n", objp, name); | ||
1664 | 727 | } catch (GLib.Error e) { | ||
1665 | 728 | warning ("unable to find stream restore data for: %s", name); | ||
1666 | 729 | } | ||
1667 | 730 | return objp; | ||
1668 | 731 | } | ||
1669 | 732 | |||
1685 | 733 | /* AccountsService operations */ | 551 | /* AccountsService operations */ |
1686 | 552 | /******************************/ | ||
1687 | 553 | |||
1688 | 734 | private void accountsservice_props_changed_cb (DBusProxy proxy, Variant changed_properties, string[]? invalidated_properties) | 554 | private void accountsservice_props_changed_cb (DBusProxy proxy, Variant changed_properties, string[]? invalidated_properties) |
1689 | 735 | { | 555 | { |
1690 | 736 | Variant volume_variant = changed_properties.lookup_value ("Volume", new VariantType ("d")); | 556 | Variant volume_variant = changed_properties.lookup_value ("Volume", new VariantType ("d")); |
1691 | 737 | 557 | ||
1692 | === added file 'src/volume-control.vala' | |||
1693 | --- src/volume-control.vala 1970-01-01 00:00:00 +0000 | |||
1694 | +++ src/volume-control.vala 2015-02-14 02:38:32 +0000 | |||
1695 | @@ -0,0 +1,31 @@ | |||
1696 | 1 | /* | ||
1697 | 2 | * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- | ||
1698 | 3 | * Copyright © 2015 Canonical Ltd. | ||
1699 | 4 | * | ||
1700 | 5 | * This program is free software; you can redistribute it and/or modify | ||
1701 | 6 | * it under the terms of the GNU General Public License as published by | ||
1702 | 7 | * the Free Software Foundation; version 3. | ||
1703 | 8 | * | ||
1704 | 9 | * This program is distributed in the hope that it will be useful, | ||
1705 | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1706 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1707 | 12 | * GNU General Public License for more details. | ||
1708 | 13 | * | ||
1709 | 14 | * You should have received a copy of the GNU General Public License | ||
1710 | 15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1711 | 16 | * | ||
1712 | 17 | * Authors: | ||
1713 | 18 | * Ted Gould <ted@canonical.com> | ||
1714 | 19 | */ | ||
1715 | 20 | |||
1716 | 21 | public abstract class VolumeControl : Object | ||
1717 | 22 | { | ||
1718 | 23 | public virtual string stream { get { return ""; } } | ||
1719 | 24 | public virtual bool ready { get { return false; } set { } } | ||
1720 | 25 | public virtual bool active_mic { get { return false; } set { } } | ||
1721 | 26 | public virtual bool high_volume { get { return false; } } | ||
1722 | 27 | public virtual bool mute { get { return false; } set { } } | ||
1723 | 28 | public virtual bool is_playing { get { return false; } protected set { } } | ||
1724 | 29 | public virtual double volume { get { return 0.0; } set { } } | ||
1725 | 30 | public virtual double mic_volume { get { return 0.0; } set { } } | ||
1726 | 31 | } | ||
1727 | 0 | 32 | ||
1728 | === modified file 'tests/CMakeLists.txt' | |||
1729 | --- tests/CMakeLists.txt 2015-02-04 19:36:53 +0000 | |||
1730 | +++ tests/CMakeLists.txt 2015-02-14 02:38:32 +0000 | |||
1731 | @@ -64,6 +64,17 @@ | |||
1732 | 64 | vala_add(vala-mocks | 64 | vala_add(vala-mocks |
1733 | 65 | media-player-mock.vala | 65 | media-player-mock.vala |
1734 | 66 | ) | 66 | ) |
1735 | 67 | vala_add(vala-mocks | ||
1736 | 68 | focus-tracker-mock.vala | ||
1737 | 69 | ) | ||
1738 | 70 | |||
1739 | 71 | vala_add(vala-mocks | ||
1740 | 72 | media-player-list-mock.vala | ||
1741 | 73 | ) | ||
1742 | 74 | |||
1743 | 75 | vala_add(vala-mocks | ||
1744 | 76 | volume-control-mock.vala | ||
1745 | 77 | ) | ||
1746 | 67 | 78 | ||
1747 | 68 | vala_finish(vala-mocks | 79 | vala_finish(vala-mocks |
1748 | 69 | SOURCES | 80 | SOURCES |
1749 | @@ -160,6 +171,7 @@ | |||
1750 | 160 | volume-control-test | 171 | volume-control-test |
1751 | 161 | indicator-sound-service-lib | 172 | indicator-sound-service-lib |
1752 | 162 | pulse-mock | 173 | pulse-mock |
1753 | 174 | vala-mocks-lib | ||
1754 | 163 | gtest | 175 | gtest |
1755 | 164 | ${TEST_LIBRARIES} | 176 | ${TEST_LIBRARIES} |
1756 | 165 | ) | 177 | ) |
1757 | @@ -184,6 +196,24 @@ | |||
1758 | 184 | add_test(sound-menu-test sound-menu-test) | 196 | add_test(sound-menu-test sound-menu-test) |
1759 | 185 | 197 | ||
1760 | 186 | ########################### | 198 | ########################### |
1761 | 199 | # Notification Test | ||
1762 | 200 | ########################### | ||
1763 | 201 | |||
1764 | 202 | include_directories(${CMAKE_SOURCE_DIR}/src) | ||
1765 | 203 | add_executable (notifications-test notifications-test.cc) | ||
1766 | 204 | target_link_libraries ( | ||
1767 | 205 | notifications-test | ||
1768 | 206 | indicator-sound-service-lib | ||
1769 | 207 | vala-mocks-lib | ||
1770 | 208 | pulse-mock | ||
1771 | 209 | gtest | ||
1772 | 210 | ${SOUNDSERVICE_LIBRARIES} | ||
1773 | 211 | ${TEST_LIBRARIES} | ||
1774 | 212 | ) | ||
1775 | 213 | |||
1776 | 214 | add_test(notifications-test notifications-test) | ||
1777 | 215 | |||
1778 | 216 | ########################### | ||
1779 | 187 | # Accounts Service User | 217 | # Accounts Service User |
1780 | 188 | ########################### | 218 | ########################### |
1781 | 189 | 219 | ||
1782 | 190 | 220 | ||
1783 | === added file 'tests/focus-tracker-mock.vala' | |||
1784 | --- tests/focus-tracker-mock.vala 1970-01-01 00:00:00 +0000 | |||
1785 | +++ tests/focus-tracker-mock.vala 2015-02-14 02:38:32 +0000 | |||
1786 | @@ -0,0 +1,27 @@ | |||
1787 | 1 | /* | ||
1788 | 2 | * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- | ||
1789 | 3 | * Copyright © 2015 Canonical Ltd. | ||
1790 | 4 | * | ||
1791 | 5 | * This program is free software; you can redistribute it and/or modify | ||
1792 | 6 | * it under the terms of the GNU General Public License as published by | ||
1793 | 7 | * the Free Software Foundation; version 3. | ||
1794 | 8 | * | ||
1795 | 9 | * This program is distributed in the hope that it will be useful, | ||
1796 | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1797 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1798 | 12 | * GNU General Public License for more details. | ||
1799 | 13 | * | ||
1800 | 14 | * You should have received a copy of the GNU General Public License | ||
1801 | 15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1802 | 16 | * | ||
1803 | 17 | * Authors: | ||
1804 | 18 | * Ted Gould <ted@canonical.com> | ||
1805 | 19 | */ | ||
1806 | 20 | |||
1807 | 21 | public class FocusTrackerMock : FocusTracker { | ||
1808 | 22 | public override string focused_appid { get; private set; default = "unknown"; } | ||
1809 | 23 | |||
1810 | 24 | public void mock_set_appid (string newappid) { | ||
1811 | 25 | this.focused_appid = newappid; | ||
1812 | 26 | } | ||
1813 | 27 | } | ||
1814 | 0 | 28 | ||
1815 | === added file 'tests/gtest-gvariant.h' | |||
1816 | --- tests/gtest-gvariant.h 1970-01-01 00:00:00 +0000 | |||
1817 | +++ tests/gtest-gvariant.h 2015-02-14 02:38:32 +0000 | |||
1818 | @@ -0,0 +1,110 @@ | |||
1819 | 1 | /* | ||
1820 | 2 | * Copyright © 2015 Canonical Ltd. | ||
1821 | 3 | * | ||
1822 | 4 | * This program is free software; you can redistribute it and/or modify | ||
1823 | 5 | * it under the terms of the GNU General Public License as published by | ||
1824 | 6 | * the Free Software Foundation; version 3. | ||
1825 | 7 | * | ||
1826 | 8 | * This program is distributed in the hope that it will be useful, | ||
1827 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1828 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1829 | 11 | * GNU General Public License for more details. | ||
1830 | 12 | * | ||
1831 | 13 | * You should have received a copy of the GNU General Public License | ||
1832 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1833 | 15 | * | ||
1834 | 16 | * Authors: | ||
1835 | 17 | * Ted Gould <ted@canonical.com> | ||
1836 | 18 | */ | ||
1837 | 19 | |||
1838 | 20 | #include <gtest/gtest.h> | ||
1839 | 21 | #include <gio/gio.h> | ||
1840 | 22 | |||
1841 | 23 | namespace GTestGVariant { | ||
1842 | 24 | |||
1843 | 25 | testing::AssertionResult expectVariantEqual (const gchar * expectStr, const gchar * haveStr, GVariant * expect, GVariant * have) | ||
1844 | 26 | { | ||
1845 | 27 | if (expect == nullptr && have == nullptr) { | ||
1846 | 28 | auto result = testing::AssertionSuccess(); | ||
1847 | 29 | return result; | ||
1848 | 30 | } | ||
1849 | 31 | |||
1850 | 32 | if (expect == nullptr || have == nullptr) { | ||
1851 | 33 | gchar * havePrint; | ||
1852 | 34 | if (have == nullptr) { | ||
1853 | 35 | havePrint = g_strdup("(nullptr)"); | ||
1854 | 36 | } else { | ||
1855 | 37 | havePrint = g_variant_print(have, TRUE); | ||
1856 | 38 | } | ||
1857 | 39 | |||
1858 | 40 | auto result = testing::AssertionFailure(); | ||
1859 | 41 | result << | ||
1860 | 42 | " Result: " << haveStr << std::endl << | ||
1861 | 43 | " Value: " << havePrint << std::endl << | ||
1862 | 44 | " Expected: " << expectStr << std::endl; | ||
1863 | 45 | |||
1864 | 46 | g_free(havePrint); | ||
1865 | 47 | return result; | ||
1866 | 48 | } | ||
1867 | 49 | |||
1868 | 50 | if (g_variant_equal(expect, have)) { | ||
1869 | 51 | auto result = testing::AssertionSuccess(); | ||
1870 | 52 | return result; | ||
1871 | 53 | } else { | ||
1872 | 54 | gchar * havePrint = g_variant_print(have, TRUE); | ||
1873 | 55 | gchar * expectPrint = g_variant_print(expect, TRUE); | ||
1874 | 56 | |||
1875 | 57 | auto result = testing::AssertionFailure(); | ||
1876 | 58 | result << | ||
1877 | 59 | " Result: " << haveStr << std::endl << | ||
1878 | 60 | " Value: " << havePrint << std::endl << | ||
1879 | 61 | " Expected: " << expectStr << std::endl << | ||
1880 | 62 | " Expected: " << expectPrint << std::endl; | ||
1881 | 63 | |||
1882 | 64 | g_free(havePrint); | ||
1883 | 65 | g_free(expectPrint); | ||
1884 | 66 | |||
1885 | 67 | return result; | ||
1886 | 68 | } | ||
1887 | 69 | } | ||
1888 | 70 | |||
1889 | 71 | testing::AssertionResult expectVariantEqual (const gchar * expectStr, const gchar * haveStr, std::shared_ptr<GVariant> expect, std::shared_ptr<GVariant> have) | ||
1890 | 72 | { | ||
1891 | 73 | return expectVariantEqual(expectStr, haveStr, expect.get(), have.get()); | ||
1892 | 74 | } | ||
1893 | 75 | |||
1894 | 76 | testing::AssertionResult expectVariantEqual (const gchar * expectStr, const gchar * haveStr, const char * expect, std::shared_ptr<GVariant> have) | ||
1895 | 77 | { | ||
1896 | 78 | auto expectv = std::shared_ptr<GVariant>([expect] { | ||
1897 | 79 | auto variant = g_variant_parse(nullptr, expect, nullptr, nullptr, nullptr); | ||
1898 | 80 | if (variant != nullptr) | ||
1899 | 81 | g_variant_ref_sink(variant); | ||
1900 | 82 | return variant; | ||
1901 | 83 | }(), | ||
1902 | 84 | [](GVariant * variant) { | ||
1903 | 85 | if (variant != nullptr) | ||
1904 | 86 | g_variant_unref(variant); | ||
1905 | 87 | }); | ||
1906 | 88 | |||
1907 | 89 | return expectVariantEqual(expectStr, haveStr, expectv, have); | ||
1908 | 90 | } | ||
1909 | 91 | |||
1910 | 92 | testing::AssertionResult expectVariantEqual (const gchar * expectStr, const gchar * haveStr, const char * expect, GVariant * have) | ||
1911 | 93 | { | ||
1912 | 94 | auto havep = std::shared_ptr<GVariant>([have] { | ||
1913 | 95 | if (have != nullptr) | ||
1914 | 96 | g_variant_ref_sink(have); | ||
1915 | 97 | return have; | ||
1916 | 98 | }(), | ||
1917 | 99 | [](GVariant * variant) { | ||
1918 | 100 | if (variant != nullptr) | ||
1919 | 101 | g_variant_unref(variant); | ||
1920 | 102 | }); | ||
1921 | 103 | |||
1922 | 104 | return expectVariantEqual(expectStr, haveStr, expect, havep); | ||
1923 | 105 | } | ||
1924 | 106 | |||
1925 | 107 | }; // ns GTestGVariant | ||
1926 | 108 | |||
1927 | 109 | #define EXPECT_GVARIANT_EQ(expect, have) \ | ||
1928 | 110 | EXPECT_PRED_FORMAT2(GTestGVariant::expectVariantEqual, expect, have) | ||
1929 | 0 | 111 | ||
1930 | === modified file 'tests/indicator-test.cc' | |||
1931 | --- tests/indicator-test.cc 2015-02-04 17:43:08 +0000 | |||
1932 | +++ tests/indicator-test.cc 2015-02-14 02:38:32 +0000 | |||
1933 | @@ -22,6 +22,7 @@ | |||
1934 | 22 | 22 | ||
1935 | 23 | #include "indicator-fixture.h" | 23 | #include "indicator-fixture.h" |
1936 | 24 | #include "accounts-service-mock.h" | 24 | #include "accounts-service-mock.h" |
1937 | 25 | #include "notifications-mock.h" | ||
1938 | 25 | 26 | ||
1939 | 26 | class IndicatorTest : public IndicatorFixture | 27 | class IndicatorTest : public IndicatorFixture |
1940 | 27 | { | 28 | { |
1941 | @@ -32,6 +33,7 @@ | |||
1942 | 32 | } | 33 | } |
1943 | 33 | 34 | ||
1944 | 34 | std::shared_ptr<AccountsServiceMock> as; | 35 | std::shared_ptr<AccountsServiceMock> as; |
1945 | 36 | std::shared_ptr<NotificationsMock> notification; | ||
1946 | 35 | 37 | ||
1947 | 36 | virtual void SetUp() override | 38 | virtual void SetUp() override |
1948 | 37 | { | 39 | { |
1949 | @@ -45,12 +47,16 @@ | |||
1950 | 45 | as = std::make_shared<AccountsServiceMock>(); | 47 | as = std::make_shared<AccountsServiceMock>(); |
1951 | 46 | addMock(*as); | 48 | addMock(*as); |
1952 | 47 | 49 | ||
1953 | 50 | notification = std::make_shared<NotificationsMock>(); | ||
1954 | 51 | addMock(*notification); | ||
1955 | 52 | |||
1956 | 48 | IndicatorFixture::SetUp(); | 53 | IndicatorFixture::SetUp(); |
1957 | 49 | } | 54 | } |
1958 | 50 | 55 | ||
1959 | 51 | virtual void TearDown() override | 56 | virtual void TearDown() override |
1960 | 52 | { | 57 | { |
1961 | 53 | as.reset(); | 58 | as.reset(); |
1962 | 59 | notification.reset(); | ||
1963 | 54 | 60 | ||
1964 | 55 | IndicatorFixture::TearDown(); | 61 | IndicatorFixture::TearDown(); |
1965 | 56 | } | 62 | } |
1966 | 57 | 63 | ||
1967 | === added file 'tests/media-player-list-mock.vala' | |||
1968 | --- tests/media-player-list-mock.vala 1970-01-01 00:00:00 +0000 | |||
1969 | +++ tests/media-player-list-mock.vala 2015-02-14 02:38:32 +0000 | |||
1970 | @@ -0,0 +1,25 @@ | |||
1971 | 1 | /* | ||
1972 | 2 | * Copyright © 2014 Canonical Ltd. | ||
1973 | 3 | * | ||
1974 | 4 | * This program is free software; you can redistribute it and/or modify | ||
1975 | 5 | * it under the terms of the GNU General Public License as published by | ||
1976 | 6 | * the Free Software Foundation; version 3. | ||
1977 | 7 | * | ||
1978 | 8 | * This program is distributed in the hope that it will be useful, | ||
1979 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1980 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1981 | 11 | * GNU General Public License for more details. | ||
1982 | 12 | * | ||
1983 | 13 | * You should have received a copy of the GNU General Public License | ||
1984 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1985 | 15 | * | ||
1986 | 16 | * Authors: | ||
1987 | 17 | * Ted Gould <ted@canonical.com> | ||
1988 | 18 | */ | ||
1989 | 19 | |||
1990 | 20 | public class MediaPlayerListMock : MediaPlayerList { | ||
1991 | 21 | public override MediaPlayerList.Iterator iterator () { return new MediaPlayerList.Iterator(); } | ||
1992 | 22 | |||
1993 | 23 | public override void sync (string[] ids) { return; } | ||
1994 | 24 | } | ||
1995 | 25 | |||
1996 | 0 | 26 | ||
1997 | === added file 'tests/notifications-mock.h' | |||
1998 | --- tests/notifications-mock.h 1970-01-01 00:00:00 +0000 | |||
1999 | +++ tests/notifications-mock.h 2015-02-14 02:38:32 +0000 | |||
2000 | @@ -0,0 +1,155 @@ | |||
2001 | 1 | /* | ||
2002 | 2 | * Copyright © 2015 Canonical Ltd. | ||
2003 | 3 | * | ||
2004 | 4 | * This program is free software; you can redistribute it and/or modify | ||
2005 | 5 | * it under the terms of the GNU General Public License as published by | ||
2006 | 6 | * the Free Software Foundation; version 3. | ||
2007 | 7 | * | ||
2008 | 8 | * This program is distributed in the hope that it will be useful, | ||
2009 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2010 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2011 | 11 | * GNU General Public License for more details. | ||
2012 | 12 | * | ||
2013 | 13 | * You should have received a copy of the GNU General Public License | ||
2014 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2015 | 15 | * | ||
2016 | 16 | * Authors: | ||
2017 | 17 | * Ted Gould <ted@canonical.com> | ||
2018 | 18 | */ | ||
2019 | 19 | |||
2020 | 20 | #include <algorithm> | ||
2021 | 21 | #include <map> | ||
2022 | 22 | #include <memory> | ||
2023 | 23 | #include <type_traits> | ||
2024 | 24 | |||
2025 | 25 | #include <libdbustest/dbus-test.h> | ||
2026 | 26 | |||
2027 | 27 | class NotificationsMock | ||
2028 | 28 | { | ||
2029 | 29 | DbusTestDbusMock * mock = nullptr; | ||
2030 | 30 | DbusTestDbusMockObject * baseobj = nullptr; | ||
2031 | 31 | |||
2032 | 32 | public: | ||
2033 | 33 | NotificationsMock (std::vector<std::string> capabilities = {"body", "body-markup", "icon-static", "image/svg+xml", "x-canonical-private-synchronous", "x-canonical-append", "x-canonical-private-icon-only", "x-canonical-truncation", "private-synchronous", "append", "private-icon-only", "truncation"}) { | ||
2034 | 34 | mock = dbus_test_dbus_mock_new("org.freedesktop.Notifications"); | ||
2035 | 35 | dbus_test_task_set_bus(DBUS_TEST_TASK(mock), DBUS_TEST_SERVICE_BUS_SESSION); | ||
2036 | 36 | dbus_test_task_set_name(DBUS_TEST_TASK(mock), "Notify"); | ||
2037 | 37 | |||
2038 | 38 | baseobj =dbus_test_dbus_mock_get_object(mock, "/org/freedesktop/Notifications", "org.freedesktop.Notifications", nullptr); | ||
2039 | 39 | |||
2040 | 40 | std::string capspython("ret = "); | ||
2041 | 41 | capspython += vector2py(capabilities); | ||
2042 | 42 | dbus_test_dbus_mock_object_add_method(mock, baseobj, | ||
2043 | 43 | "GetCapabilities", nullptr, G_VARIANT_TYPE("as"), | ||
2044 | 44 | capspython.c_str(), nullptr); | ||
2045 | 45 | |||
2046 | 46 | dbus_test_dbus_mock_object_add_method(mock, baseobj, | ||
2047 | 47 | "GetServerInformation", nullptr, G_VARIANT_TYPE("(ssss)"), | ||
2048 | 48 | "ret = ['notification-mock', 'Testing harness', '1.0', '1.1']", nullptr); | ||
2049 | 49 | |||
2050 | 50 | dbus_test_dbus_mock_object_add_method(mock, baseobj, | ||
2051 | 51 | "Notify", G_VARIANT_TYPE("(susssasa{sv}i)"), G_VARIANT_TYPE("u"), | ||
2052 | 52 | "ret = 10", nullptr); | ||
2053 | 53 | |||
2054 | 54 | dbus_test_dbus_mock_object_add_method(mock, baseobj, | ||
2055 | 55 | "CloseNotification", G_VARIANT_TYPE("u"), nullptr, | ||
2056 | 56 | "", nullptr); | ||
2057 | 57 | } | ||
2058 | 58 | |||
2059 | 59 | ~NotificationsMock () { | ||
2060 | 60 | g_debug("Destroying the Notifications Mock"); | ||
2061 | 61 | g_clear_object(&mock); | ||
2062 | 62 | } | ||
2063 | 63 | |||
2064 | 64 | std::string vector2py (std::vector<std::string> vect) { | ||
2065 | 65 | std::string retval("[ "); | ||
2066 | 66 | |||
2067 | 67 | std::for_each(vect.begin(), vect.end() - 1, [&retval](std::string entry) { | ||
2068 | 68 | retval += "'"; | ||
2069 | 69 | retval += entry; | ||
2070 | 70 | retval += "', "; | ||
2071 | 71 | }); | ||
2072 | 72 | |||
2073 | 73 | retval += "'"; | ||
2074 | 74 | retval += *(vect.end() - 1); | ||
2075 | 75 | retval += "']"; | ||
2076 | 76 | |||
2077 | 77 | return retval; | ||
2078 | 78 | } | ||
2079 | 79 | |||
2080 | 80 | operator std::shared_ptr<DbusTestTask> () { | ||
2081 | 81 | std::shared_ptr<DbusTestTask> retval(DBUS_TEST_TASK(g_object_ref(mock)), [](DbusTestTask * task) { g_clear_object(&task); }); | ||
2082 | 82 | return retval; | ||
2083 | 83 | } | ||
2084 | 84 | |||
2085 | 85 | operator DbusTestTask* () { | ||
2086 | 86 | return DBUS_TEST_TASK(mock); | ||
2087 | 87 | } | ||
2088 | 88 | |||
2089 | 89 | operator DbusTestDbusMock* () { | ||
2090 | 90 | return mock; | ||
2091 | 91 | } | ||
2092 | 92 | |||
2093 | 93 | struct Notification { | ||
2094 | 94 | std::string app_name; | ||
2095 | 95 | unsigned int replace_id; | ||
2096 | 96 | std::string app_icon; | ||
2097 | 97 | std::string summary; | ||
2098 | 98 | std::string body; | ||
2099 | 99 | std::vector<std::string> actions; | ||
2100 | 100 | std::map<std::string, std::shared_ptr<GVariant>> hints; | ||
2101 | 101 | int timeout; | ||
2102 | 102 | }; | ||
2103 | 103 | |||
2104 | 104 | std::shared_ptr<GVariant> childGet (GVariant * tuple, gsize index) { | ||
2105 | 105 | return std::shared_ptr<GVariant>(g_variant_get_child_value(tuple, index), | ||
2106 | 106 | [](GVariant * v){ if (v != nullptr) g_variant_unref(v); }); | ||
2107 | 107 | } | ||
2108 | 108 | |||
2109 | 109 | std::vector<Notification> getNotifications (void) { | ||
2110 | 110 | std::vector<Notification> notifications; | ||
2111 | 111 | |||
2112 | 112 | unsigned int cnt, i; | ||
2113 | 113 | auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, baseobj, "Notify", &cnt, nullptr); | ||
2114 | 114 | |||
2115 | 115 | for (i = 0; i < cnt; i++) { | ||
2116 | 116 | auto call = calls[i]; | ||
2117 | 117 | Notification notification; | ||
2118 | 118 | |||
2119 | 119 | notification.app_name = g_variant_get_string(childGet(call.params, 0).get(), nullptr); | ||
2120 | 120 | notification.replace_id = g_variant_get_uint32(childGet(call.params, 1).get()); | ||
2121 | 121 | notification.app_icon = g_variant_get_string(childGet(call.params, 2).get(), nullptr); | ||
2122 | 122 | notification.summary = g_variant_get_string(childGet(call.params, 3).get(), nullptr); | ||
2123 | 123 | notification.body = g_variant_get_string(childGet(call.params, 4).get(), nullptr); | ||
2124 | 124 | notification.timeout = g_variant_get_int32(childGet(call.params, 7).get()); | ||
2125 | 125 | |||
2126 | 126 | auto vactions = childGet(call.params, 5); | ||
2127 | 127 | GVariantIter iactions = {0}; | ||
2128 | 128 | g_variant_iter_init(&iactions, vactions.get()); | ||
2129 | 129 | const gchar * action = nullptr; | ||
2130 | 130 | while (g_variant_iter_loop(&iactions, "&s", &action)) { | ||
2131 | 131 | std::string saction(action); | ||
2132 | 132 | notification.actions.push_back(saction); | ||
2133 | 133 | } | ||
2134 | 134 | |||
2135 | 135 | auto vhints = childGet(call.params, 6); | ||
2136 | 136 | GVariantIter ihints = {0}; | ||
2137 | 137 | g_variant_iter_init(&ihints, vhints.get()); | ||
2138 | 138 | const gchar * hint_key = nullptr; | ||
2139 | 139 | GVariant * hint_value = nullptr; | ||
2140 | 140 | while (g_variant_iter_loop(&ihints, "{&sv}", &hint_key, &hint_value)) { | ||
2141 | 141 | std::string key(hint_key); | ||
2142 | 142 | std::shared_ptr<GVariant> value(g_variant_ref(hint_value), [](GVariant * v){ if (v != nullptr) g_variant_unref(v); }); | ||
2143 | 143 | notification.hints[key] = value; | ||
2144 | 144 | } | ||
2145 | 145 | |||
2146 | 146 | notifications.push_back(notification); | ||
2147 | 147 | } | ||
2148 | 148 | |||
2149 | 149 | return notifications; | ||
2150 | 150 | } | ||
2151 | 151 | |||
2152 | 152 | bool clearNotifications (void) { | ||
2153 | 153 | return dbus_test_dbus_mock_object_clear_method_calls(mock, baseobj, nullptr); | ||
2154 | 154 | } | ||
2155 | 155 | }; | ||
2156 | 0 | 156 | ||
2157 | === added file 'tests/notifications-test.cc' | |||
2158 | --- tests/notifications-test.cc 1970-01-01 00:00:00 +0000 | |||
2159 | +++ tests/notifications-test.cc 2015-02-14 02:38:32 +0000 | |||
2160 | @@ -0,0 +1,348 @@ | |||
2161 | 1 | /* | ||
2162 | 2 | * Copyright © 2015 Canonical Ltd. | ||
2163 | 3 | * | ||
2164 | 4 | * This program is free software; you can redistribute it and/or modify | ||
2165 | 5 | * it under the terms of the GNU General Public License as published by | ||
2166 | 6 | * the Free Software Foundation; version 3. | ||
2167 | 7 | * | ||
2168 | 8 | * This program is distributed in the hope that it will be useful, | ||
2169 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2170 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2171 | 11 | * GNU General Public License for more details. | ||
2172 | 12 | * | ||
2173 | 13 | * You should have received a copy of the GNU General Public License | ||
2174 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2175 | 15 | * | ||
2176 | 16 | * Authors: | ||
2177 | 17 | * Ted Gould <ted@canonical.com> | ||
2178 | 18 | */ | ||
2179 | 19 | |||
2180 | 20 | #include <memory> | ||
2181 | 21 | |||
2182 | 22 | #include <gtest/gtest.h> | ||
2183 | 23 | #include <gio/gio.h> | ||
2184 | 24 | #include <libdbustest/dbus-test.h> | ||
2185 | 25 | #include <libnotify/notify.h> | ||
2186 | 26 | |||
2187 | 27 | #include "notifications-mock.h" | ||
2188 | 28 | #include "gtest-gvariant.h" | ||
2189 | 29 | |||
2190 | 30 | extern "C" { | ||
2191 | 31 | #include "indicator-sound-service.h" | ||
2192 | 32 | #include "vala-mocks.h" | ||
2193 | 33 | } | ||
2194 | 34 | |||
2195 | 35 | class NotificationsTest : public ::testing::Test | ||
2196 | 36 | { | ||
2197 | 37 | protected: | ||
2198 | 38 | DbusTestService * service = NULL; | ||
2199 | 39 | |||
2200 | 40 | GDBusConnection * session = NULL; | ||
2201 | 41 | std::shared_ptr<NotificationsMock> notifications; | ||
2202 | 42 | |||
2203 | 43 | virtual void SetUp() { | ||
2204 | 44 | g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, TRUE); | ||
2205 | 45 | g_setenv("GSETTINGS_BACKEND", "memory", TRUE); | ||
2206 | 46 | |||
2207 | 47 | service = dbus_test_service_new(NULL); | ||
2208 | 48 | dbus_test_service_set_bus(service, DBUS_TEST_SERVICE_BUS_SESSION); | ||
2209 | 49 | |||
2210 | 50 | /* Useful for debugging test failures, not needed all the time (until it fails) */ | ||
2211 | 51 | #if 0 | ||
2212 | 52 | auto bustle = std::shared_ptr<DbusTestTask>([]() { | ||
2213 | 53 | DbusTestTask * bustle = DBUS_TEST_TASK(dbus_test_bustle_new("notifications-test.bustle")); | ||
2214 | 54 | dbus_test_task_set_name(bustle, "Bustle"); | ||
2215 | 55 | dbus_test_task_set_bus(bustle, DBUS_TEST_SERVICE_BUS_SESSION); | ||
2216 | 56 | return bustle; | ||
2217 | 57 | }(), [](DbusTestTask * bustle) { | ||
2218 | 58 | g_clear_object(&bustle); | ||
2219 | 59 | }); | ||
2220 | 60 | dbus_test_service_add_task(service, bustle.get()); | ||
2221 | 61 | #endif | ||
2222 | 62 | |||
2223 | 63 | notifications = std::make_shared<NotificationsMock>(); | ||
2224 | 64 | |||
2225 | 65 | dbus_test_service_add_task(service, (DbusTestTask*)*notifications); | ||
2226 | 66 | dbus_test_service_start_tasks(service); | ||
2227 | 67 | |||
2228 | 68 | session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); | ||
2229 | 69 | ASSERT_NE(nullptr, session); | ||
2230 | 70 | g_dbus_connection_set_exit_on_close(session, FALSE); | ||
2231 | 71 | g_object_add_weak_pointer(G_OBJECT(session), (gpointer *)&session); | ||
2232 | 72 | |||
2233 | 73 | /* This is done in main.c */ | ||
2234 | 74 | notify_init("indicator-sound"); | ||
2235 | 75 | } | ||
2236 | 76 | |||
2237 | 77 | virtual void TearDown() { | ||
2238 | 78 | if (notify_is_initted()) | ||
2239 | 79 | notify_uninit(); | ||
2240 | 80 | |||
2241 | 81 | notifications.reset(); | ||
2242 | 82 | g_clear_object(&service); | ||
2243 | 83 | |||
2244 | 84 | g_object_unref(session); | ||
2245 | 85 | |||
2246 | 86 | unsigned int cleartry = 0; | ||
2247 | 87 | while (session != NULL && cleartry < 100) { | ||
2248 | 88 | loop(100); | ||
2249 | 89 | cleartry++; | ||
2250 | 90 | } | ||
2251 | 91 | |||
2252 | 92 | ASSERT_EQ(nullptr, session); | ||
2253 | 93 | } | ||
2254 | 94 | |||
2255 | 95 | static gboolean timeout_cb (gpointer user_data) { | ||
2256 | 96 | GMainLoop * loop = static_cast<GMainLoop *>(user_data); | ||
2257 | 97 | g_main_loop_quit(loop); | ||
2258 | 98 | return G_SOURCE_REMOVE; | ||
2259 | 99 | } | ||
2260 | 100 | |||
2261 | 101 | void loop (unsigned int ms) { | ||
2262 | 102 | GMainLoop * loop = g_main_loop_new(NULL, FALSE); | ||
2263 | 103 | g_timeout_add(ms, timeout_cb, loop); | ||
2264 | 104 | g_main_loop_run(loop); | ||
2265 | 105 | g_main_loop_unref(loop); | ||
2266 | 106 | } | ||
2267 | 107 | |||
2268 | 108 | static int unref_idle (gpointer user_data) { | ||
2269 | 109 | g_variant_unref(static_cast<GVariant *>(user_data)); | ||
2270 | 110 | return G_SOURCE_REMOVE; | ||
2271 | 111 | } | ||
2272 | 112 | |||
2273 | 113 | std::shared_ptr<MediaPlayerList> playerListMock () { | ||
2274 | 114 | auto playerList = std::shared_ptr<MediaPlayerList>( | ||
2275 | 115 | MEDIA_PLAYER_LIST(media_player_list_mock_new()), | ||
2276 | 116 | [](MediaPlayerList * list) { | ||
2277 | 117 | g_clear_object(&list); | ||
2278 | 118 | }); | ||
2279 | 119 | return playerList; | ||
2280 | 120 | } | ||
2281 | 121 | |||
2282 | 122 | std::shared_ptr<VolumeControl> volumeControlMock () { | ||
2283 | 123 | auto volumeControl = std::shared_ptr<VolumeControl>( | ||
2284 | 124 | VOLUME_CONTROL(volume_control_mock_new()), | ||
2285 | 125 | [](VolumeControl * control){ | ||
2286 | 126 | g_clear_object(&control); | ||
2287 | 127 | }); | ||
2288 | 128 | return volumeControl; | ||
2289 | 129 | } | ||
2290 | 130 | |||
2291 | 131 | std::shared_ptr<IndicatorSoundService> standardService (std::shared_ptr<VolumeControl> volumeControl, std::shared_ptr<MediaPlayerList> playerList) { | ||
2292 | 132 | auto soundService = std::shared_ptr<IndicatorSoundService>( | ||
2293 | 133 | indicator_sound_service_new(playerList.get(), volumeControl.get(), nullptr), | ||
2294 | 134 | [](IndicatorSoundService * service){ | ||
2295 | 135 | g_clear_object(&service); | ||
2296 | 136 | }); | ||
2297 | 137 | |||
2298 | 138 | return soundService; | ||
2299 | 139 | } | ||
2300 | 140 | }; | ||
2301 | 141 | |||
2302 | 142 | TEST_F(NotificationsTest, BasicObject) { | ||
2303 | 143 | auto soundService = standardService(volumeControlMock(), playerListMock()); | ||
2304 | 144 | |||
2305 | 145 | /* Give some time settle */ | ||
2306 | 146 | loop(50); | ||
2307 | 147 | |||
2308 | 148 | /* Auto free */ | ||
2309 | 149 | } | ||
2310 | 150 | |||
2311 | 151 | TEST_F(NotificationsTest, VolumeChanges) { | ||
2312 | 152 | auto volumeControl = volumeControlMock(); | ||
2313 | 153 | auto soundService = standardService(volumeControl, playerListMock()); | ||
2314 | 154 | |||
2315 | 155 | /* Set a volume */ | ||
2316 | 156 | notifications->clearNotifications(); | ||
2317 | 157 | volume_control_set_volume(volumeControl.get(), 0.50); | ||
2318 | 158 | loop(50); | ||
2319 | 159 | auto notev = notifications->getNotifications(); | ||
2320 | 160 | ASSERT_EQ(1, notev.size()); | ||
2321 | 161 | EXPECT_EQ("indicator-sound", notev[0].app_name); | ||
2322 | 162 | EXPECT_EQ("Volume", notev[0].summary); | ||
2323 | 163 | EXPECT_EQ(0, notev[0].actions.size()); | ||
2324 | 164 | EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-private-synchronous"]); | ||
2325 | 165 | EXPECT_GVARIANT_EQ("@i 50", notev[0].hints["value"]); | ||
2326 | 166 | |||
2327 | 167 | /* Set a different volume */ | ||
2328 | 168 | notifications->clearNotifications(); | ||
2329 | 169 | volume_control_set_volume(volumeControl.get(), 0.60); | ||
2330 | 170 | loop(50); | ||
2331 | 171 | notev = notifications->getNotifications(); | ||
2332 | 172 | ASSERT_EQ(1, notev.size()); | ||
2333 | 173 | EXPECT_GVARIANT_EQ("@i 60", notev[0].hints["value"]); | ||
2334 | 174 | |||
2335 | 175 | /* Set the same volume */ | ||
2336 | 176 | notifications->clearNotifications(); | ||
2337 | 177 | volume_control_set_volume(volumeControl.get(), 0.60); | ||
2338 | 178 | loop(50); | ||
2339 | 179 | notev = notifications->getNotifications(); | ||
2340 | 180 | ASSERT_EQ(0, notev.size()); | ||
2341 | 181 | |||
2342 | 182 | /* Change just a little */ | ||
2343 | 183 | notifications->clearNotifications(); | ||
2344 | 184 | volume_control_set_volume(volumeControl.get(), 0.60001); | ||
2345 | 185 | loop(50); | ||
2346 | 186 | notev = notifications->getNotifications(); | ||
2347 | 187 | ASSERT_EQ(0, notev.size()); | ||
2348 | 188 | } | ||
2349 | 189 | |||
2350 | 190 | TEST_F(NotificationsTest, StreamChanges) { | ||
2351 | 191 | auto volumeControl = volumeControlMock(); | ||
2352 | 192 | auto soundService = standardService(volumeControl, playerListMock()); | ||
2353 | 193 | |||
2354 | 194 | /* Set a volume */ | ||
2355 | 195 | notifications->clearNotifications(); | ||
2356 | 196 | volume_control_set_volume(volumeControl.get(), 0.5); | ||
2357 | 197 | loop(50); | ||
2358 | 198 | auto notev = notifications->getNotifications(); | ||
2359 | 199 | ASSERT_EQ(1, notev.size()); | ||
2360 | 200 | |||
2361 | 201 | /* Change Streams, no volume change */ | ||
2362 | 202 | notifications->clearNotifications(); | ||
2363 | 203 | volume_control_mock_set_mock_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), "alarm"); | ||
2364 | 204 | volume_control_set_volume(volumeControl.get(), 0.5); | ||
2365 | 205 | loop(50); | ||
2366 | 206 | notev = notifications->getNotifications(); | ||
2367 | 207 | EXPECT_EQ(0, notev.size()); | ||
2368 | 208 | |||
2369 | 209 | /* Change Streams, volume change */ | ||
2370 | 210 | notifications->clearNotifications(); | ||
2371 | 211 | volume_control_mock_set_mock_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), "alert"); | ||
2372 | 212 | volume_control_set_volume(volumeControl.get(), 0.60); | ||
2373 | 213 | loop(50); | ||
2374 | 214 | notev = notifications->getNotifications(); | ||
2375 | 215 | EXPECT_EQ(0, notev.size()); | ||
2376 | 216 | |||
2377 | 217 | /* Change Streams, no volume change, volume up */ | ||
2378 | 218 | notifications->clearNotifications(); | ||
2379 | 219 | volume_control_mock_set_mock_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), "multimedia"); | ||
2380 | 220 | volume_control_set_volume(volumeControl.get(), 0.60); | ||
2381 | 221 | loop(50); | ||
2382 | 222 | volume_control_set_volume(volumeControl.get(), 0.65); | ||
2383 | 223 | notev = notifications->getNotifications(); | ||
2384 | 224 | EXPECT_EQ(1, notev.size()); | ||
2385 | 225 | EXPECT_GVARIANT_EQ("@i 65", notev[0].hints["value"]); | ||
2386 | 226 | } | ||
2387 | 227 | |||
2388 | 228 | TEST_F(NotificationsTest, IconTesting) { | ||
2389 | 229 | auto volumeControl = volumeControlMock(); | ||
2390 | 230 | auto soundService = standardService(volumeControl, playerListMock()); | ||
2391 | 231 | |||
2392 | 232 | /* Set an initial volume */ | ||
2393 | 233 | notifications->clearNotifications(); | ||
2394 | 234 | volume_control_set_volume(volumeControl.get(), 0.5); | ||
2395 | 235 | loop(50); | ||
2396 | 236 | auto notev = notifications->getNotifications(); | ||
2397 | 237 | ASSERT_EQ(1, notev.size()); | ||
2398 | 238 | |||
2399 | 239 | /* Generate a set of notifications */ | ||
2400 | 240 | notifications->clearNotifications(); | ||
2401 | 241 | for (float i = 0.0; i < 1.01; i += 0.1) { | ||
2402 | 242 | volume_control_set_volume(volumeControl.get(), i); | ||
2403 | 243 | } | ||
2404 | 244 | |||
2405 | 245 | loop(50); | ||
2406 | 246 | notev = notifications->getNotifications(); | ||
2407 | 247 | ASSERT_EQ(11, notev.size()); | ||
2408 | 248 | |||
2409 | 249 | EXPECT_EQ("audio-volume-muted", notev[0].app_icon); | ||
2410 | 250 | EXPECT_EQ("audio-volume-low", notev[1].app_icon); | ||
2411 | 251 | EXPECT_EQ("audio-volume-low", notev[2].app_icon); | ||
2412 | 252 | EXPECT_EQ("audio-volume-medium", notev[3].app_icon); | ||
2413 | 253 | EXPECT_EQ("audio-volume-medium", notev[4].app_icon); | ||
2414 | 254 | EXPECT_EQ("audio-volume-medium", notev[5].app_icon); | ||
2415 | 255 | EXPECT_EQ("audio-volume-medium", notev[6].app_icon); | ||
2416 | 256 | EXPECT_EQ("audio-volume-high", notev[7].app_icon); | ||
2417 | 257 | EXPECT_EQ("audio-volume-high", notev[8].app_icon); | ||
2418 | 258 | EXPECT_EQ("audio-volume-high", notev[9].app_icon); | ||
2419 | 259 | EXPECT_EQ("audio-volume-high", notev[10].app_icon); | ||
2420 | 260 | } | ||
2421 | 261 | |||
2422 | 262 | TEST_F(NotificationsTest, ServerRestart) { | ||
2423 | 263 | auto volumeControl = volumeControlMock(); | ||
2424 | 264 | auto soundService = standardService(volumeControl, playerListMock()); | ||
2425 | 265 | |||
2426 | 266 | /* Set a volume */ | ||
2427 | 267 | notifications->clearNotifications(); | ||
2428 | 268 | volume_control_set_volume(volumeControl.get(), 0.50); | ||
2429 | 269 | loop(50); | ||
2430 | 270 | auto notev = notifications->getNotifications(); | ||
2431 | 271 | ASSERT_EQ(1, notev.size()); | ||
2432 | 272 | |||
2433 | 273 | /* Restart server without sync notifications */ | ||
2434 | 274 | notifications->clearNotifications(); | ||
2435 | 275 | dbus_test_service_remove_task(service, (DbusTestTask*)*notifications); | ||
2436 | 276 | notifications.reset(); | ||
2437 | 277 | |||
2438 | 278 | loop(50); | ||
2439 | 279 | |||
2440 | 280 | notifications = std::make_shared<NotificationsMock>(std::vector<std::string>({"body", "body-markup", "icon-static"})); | ||
2441 | 281 | dbus_test_service_add_task(service, (DbusTestTask*)*notifications); | ||
2442 | 282 | dbus_test_task_run((DbusTestTask*)*notifications); | ||
2443 | 283 | |||
2444 | 284 | /* Change the volume */ | ||
2445 | 285 | notifications->clearNotifications(); | ||
2446 | 286 | volume_control_set_volume(volumeControl.get(), 0.60); | ||
2447 | 287 | loop(50); | ||
2448 | 288 | notev = notifications->getNotifications(); | ||
2449 | 289 | ASSERT_EQ(0, notev.size()); | ||
2450 | 290 | |||
2451 | 291 | /* Put a good server back */ | ||
2452 | 292 | dbus_test_service_remove_task(service, (DbusTestTask*)*notifications); | ||
2453 | 293 | notifications.reset(); | ||
2454 | 294 | |||
2455 | 295 | loop(50); | ||
2456 | 296 | |||
2457 | 297 | notifications = std::make_shared<NotificationsMock>(); | ||
2458 | 298 | dbus_test_service_add_task(service, (DbusTestTask*)*notifications); | ||
2459 | 299 | dbus_test_task_run((DbusTestTask*)*notifications); | ||
2460 | 300 | |||
2461 | 301 | /* Change the volume again */ | ||
2462 | 302 | notifications->clearNotifications(); | ||
2463 | 303 | volume_control_set_volume(volumeControl.get(), 0.70); | ||
2464 | 304 | loop(50); | ||
2465 | 305 | notev = notifications->getNotifications(); | ||
2466 | 306 | ASSERT_EQ(1, notev.size()); | ||
2467 | 307 | } | ||
2468 | 308 | |||
2469 | 309 | TEST_F(NotificationsTest, HighVolume) { | ||
2470 | 310 | auto volumeControl = volumeControlMock(); | ||
2471 | 311 | auto soundService = standardService(volumeControl, playerListMock()); | ||
2472 | 312 | |||
2473 | 313 | /* Set a volume */ | ||
2474 | 314 | notifications->clearNotifications(); | ||
2475 | 315 | volume_control_set_volume(volumeControl.get(), 0.50); | ||
2476 | 316 | loop(50); | ||
2477 | 317 | auto notev = notifications->getNotifications(); | ||
2478 | 318 | ASSERT_EQ(1, notev.size()); | ||
2479 | 319 | EXPECT_EQ("Volume", notev[0].summary); | ||
2480 | 320 | EXPECT_GVARIANT_EQ("@s 'false'", notev[0].hints["x-canonical-value-bar-tint"]); | ||
2481 | 321 | |||
2482 | 322 | /* Set high volume with volume change */ | ||
2483 | 323 | notifications->clearNotifications(); | ||
2484 | 324 | volume_control_mock_set_mock_high_volume(VOLUME_CONTROL_MOCK(volumeControl.get()), TRUE); | ||
2485 | 325 | volume_control_set_volume(volumeControl.get(), 0.90); | ||
2486 | 326 | loop(50); | ||
2487 | 327 | notev = notifications->getNotifications(); | ||
2488 | 328 | ASSERT_LT(0, notev.size()); /* This passes with one or two since it would just be an update to the first if a second was sent */ | ||
2489 | 329 | EXPECT_EQ("Volume", notev[0].summary); | ||
2490 | 330 | EXPECT_EQ("High volume", notev[0].body); | ||
2491 | 331 | EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-value-bar-tint"]); | ||
2492 | 332 | |||
2493 | 333 | /* Move it back */ | ||
2494 | 334 | volume_control_mock_set_mock_high_volume(VOLUME_CONTROL_MOCK(volumeControl.get()), FALSE); | ||
2495 | 335 | volume_control_set_volume(volumeControl.get(), 0.50); | ||
2496 | 336 | loop(50); | ||
2497 | 337 | |||
2498 | 338 | /* Set high volume without level change */ | ||
2499 | 339 | /* NOTE: This can happen if headphones are plugged in */ | ||
2500 | 340 | notifications->clearNotifications(); | ||
2501 | 341 | volume_control_mock_set_mock_high_volume(VOLUME_CONTROL_MOCK(volumeControl.get()), TRUE); | ||
2502 | 342 | loop(50); | ||
2503 | 343 | notev = notifications->getNotifications(); | ||
2504 | 344 | ASSERT_EQ(1, notev.size()); | ||
2505 | 345 | EXPECT_EQ("Volume", notev[0].summary); | ||
2506 | 346 | EXPECT_EQ("High volume", notev[0].body); | ||
2507 | 347 | EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-value-bar-tint"]); | ||
2508 | 348 | } | ||
2509 | 0 | 349 | ||
2510 | === modified file 'tests/pa-mock.cpp' | |||
2511 | --- tests/pa-mock.cpp 2015-01-29 14:34:50 +0000 | |||
2512 | +++ tests/pa-mock.cpp 2015-02-14 02:38:32 +0000 | |||
2513 | @@ -23,6 +23,7 @@ | |||
2514 | 23 | 23 | ||
2515 | 24 | #include <pulse/pulseaudio.h> | 24 | #include <pulse/pulseaudio.h> |
2516 | 25 | #include <pulse/glib-mainloop.h> | 25 | #include <pulse/glib-mainloop.h> |
2517 | 26 | #include <pulse/ext-stream-restore.h> | ||
2518 | 26 | #include <gio/gio.h> | 27 | #include <gio/gio.h> |
2519 | 27 | #include <math.h> | 28 | #include <math.h> |
2520 | 28 | 29 | ||
2521 | @@ -280,6 +281,12 @@ | |||
2522 | 280 | return dummy_operation(); | 281 | return dummy_operation(); |
2523 | 281 | } | 282 | } |
2524 | 282 | 283 | ||
2525 | 284 | pa_operation * | ||
2526 | 285 | pa_context_get_sink_input_info_list (pa_context *c, pa_sink_input_info_cb_t cb, void * userdata) | ||
2527 | 286 | { | ||
2528 | 287 | return pa_context_get_sink_input_info (c, 0, cb, userdata); | ||
2529 | 288 | } | ||
2530 | 289 | |||
2531 | 283 | pa_operation* | 290 | pa_operation* |
2532 | 284 | pa_context_get_source_info_by_name (pa_context *c, const char * name, pa_source_info_cb_t cb, void *userdata) | 291 | pa_context_get_source_info_by_name (pa_context *c, const char * name, pa_source_info_cb_t cb, void *userdata) |
2533 | 285 | { | 292 | { |
2534 | @@ -569,3 +576,18 @@ | |||
2535 | 569 | return cvol; | 576 | return cvol; |
2536 | 570 | } | 577 | } |
2537 | 571 | 578 | ||
2538 | 579 | /* ******************************* | ||
2539 | 580 | * ext-stream-restore.h | ||
2540 | 581 | * *******************************/ | ||
2541 | 582 | |||
2542 | 583 | pa_operation * | ||
2543 | 584 | pa_ext_stream_restore_test (pa_context * c, pa_ext_stream_restore_test_cb_t callback, void * userdata) | ||
2544 | 585 | { | ||
2545 | 586 | reinterpret_cast<PAMockContext*>(c)->idleOnce( | ||
2546 | 587 | [c, callback, userdata]() { | ||
2547 | 588 | if (callback != nullptr) | ||
2548 | 589 | callback(c, 1, userdata); | ||
2549 | 590 | }); | ||
2550 | 591 | |||
2551 | 592 | return dummy_operation(); | ||
2552 | 593 | } | ||
2553 | 572 | 594 | ||
2554 | === added file 'tests/volume-control-mock.vala' | |||
2555 | --- tests/volume-control-mock.vala 1970-01-01 00:00:00 +0000 | |||
2556 | +++ tests/volume-control-mock.vala 2015-02-14 02:38:32 +0000 | |||
2557 | @@ -0,0 +1,43 @@ | |||
2558 | 1 | /* | ||
2559 | 2 | * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- | ||
2560 | 3 | * Copyright © 2015 Canonical Ltd. | ||
2561 | 4 | * | ||
2562 | 5 | * This program is free software; you can redistribute it and/or modify | ||
2563 | 6 | * it under the terms of the GNU General Public License as published by | ||
2564 | 7 | * the Free Software Foundation; version 3. | ||
2565 | 8 | * | ||
2566 | 9 | * This program is distributed in the hope that it will be useful, | ||
2567 | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2568 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2569 | 12 | * GNU General Public License for more details. | ||
2570 | 13 | * | ||
2571 | 14 | * You should have received a copy of the GNU General Public License | ||
2572 | 15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2573 | 16 | * | ||
2574 | 17 | * Authors: | ||
2575 | 18 | * Ted Gould <ted@canonical.com> | ||
2576 | 19 | */ | ||
2577 | 20 | |||
2578 | 21 | public class VolumeControlMock : VolumeControl | ||
2579 | 22 | { | ||
2580 | 23 | public string mock_stream { get; set; default = "multimedia"; } | ||
2581 | 24 | public override string stream { get { return mock_stream; } } | ||
2582 | 25 | public override bool ready { get; set; } | ||
2583 | 26 | public override bool active_mic { get; set; } | ||
2584 | 27 | public bool mock_high_volume { get; set; } | ||
2585 | 28 | public override bool high_volume { get { return mock_high_volume; } } | ||
2586 | 29 | public bool mock_mute { get; set; } | ||
2587 | 30 | public override bool mute { get { return mock_mute; } set { } } | ||
2588 | 31 | public bool mock_is_playing { get; set; } | ||
2589 | 32 | public override bool is_playing { get { return mock_is_playing; } set { } } | ||
2590 | 33 | public override double volume { get; set; } | ||
2591 | 34 | public override double mic_volume { get; set; } | ||
2592 | 35 | |||
2593 | 36 | public VolumeControlMock() { | ||
2594 | 37 | ready = true; | ||
2595 | 38 | this.notify["mock-stream"].connect(() => this.notify_property("stream")); | ||
2596 | 39 | this.notify["mock-high-volume"].connect(() => this.notify_property("high-volume")); | ||
2597 | 40 | this.notify["mock-mute"].connect(() => this.notify_property("mute")); | ||
2598 | 41 | this.notify["mock-is-playing"].connect(() => this.notify_property("is-playing")); | ||
2599 | 42 | } | ||
2600 | 43 | } | ||
2601 | 0 | 44 | ||
2602 | === modified file 'tests/volume-control-test.cc' | |||
2603 | --- tests/volume-control-test.cc 2015-01-27 17:42:26 +0000 | |||
2604 | +++ tests/volume-control-test.cc 2015-02-14 02:38:32 +0000 | |||
2605 | @@ -23,6 +23,7 @@ | |||
2606 | 23 | 23 | ||
2607 | 24 | extern "C" { | 24 | extern "C" { |
2608 | 25 | #include "indicator-sound-service.h" | 25 | #include "indicator-sound-service.h" |
2609 | 26 | #include "vala-mocks.h" | ||
2610 | 26 | } | 27 | } |
2611 | 27 | 28 | ||
2612 | 28 | class VolumeControlTest : public ::testing::Test | 29 | class VolumeControlTest : public ::testing::Test |
2613 | @@ -71,13 +72,14 @@ | |||
2614 | 71 | }; | 72 | }; |
2615 | 72 | 73 | ||
2616 | 73 | TEST_F(VolumeControlTest, BasicObject) { | 74 | TEST_F(VolumeControlTest, BasicObject) { |
2618 | 74 | VolumeControl * control = volume_control_new(); | 75 | FocusTrackerMock * focustracker = focus_tracker_mock_new(); |
2619 | 76 | VolumeControlPulse * control = volume_control_pulse_new(FOCUS_TRACKER(focustracker)); | ||
2620 | 75 | 77 | ||
2621 | 76 | /* Setup the PA backend */ | 78 | /* Setup the PA backend */ |
2622 | 77 | loop(100); | 79 | loop(100); |
2623 | 78 | 80 | ||
2624 | 79 | /* Ready */ | 81 | /* Ready */ |
2626 | 80 | EXPECT_TRUE(volume_control_get_ready(control)); | 82 | EXPECT_TRUE(volume_control_get_ready(VOLUME_CONTROL(control))); |
2627 | 81 | 83 | ||
2628 | 82 | g_clear_object(&control); | 84 | g_clear_object(&control); |
2629 | 83 | } | 85 | } |
2630 | 84 | 86 | ||
2631 | === added file 'vapi/libpulse-ext-stream-restore.vapi' | |||
2632 | --- vapi/libpulse-ext-stream-restore.vapi 1970-01-01 00:00:00 +0000 | |||
2633 | +++ vapi/libpulse-ext-stream-restore.vapi 2015-02-14 02:38:32 +0000 | |||
2634 | @@ -0,0 +1,22 @@ | |||
2635 | 1 | |||
2636 | 2 | using GLib; | ||
2637 | 3 | using PulseAudio; | ||
2638 | 4 | |||
2639 | 5 | [CCode (cheader_filename="pulse/ext-stream-restore.h")] | ||
2640 | 6 | namespace PulseAudio.Extension.StreamRestore { | ||
2641 | 7 | public struct Info { | ||
2642 | 8 | string name; | ||
2643 | 9 | PulseAudio.ChannelMap channel_map; | ||
2644 | 10 | PulseAudio.CVolume volume; | ||
2645 | 11 | string device; | ||
2646 | 12 | int mute; | ||
2647 | 13 | } | ||
2648 | 14 | |||
2649 | 15 | public delegate void TestCb (PulseAudio.Context context, uint32 version); | ||
2650 | 16 | [CCode (cname="pa_ext_stream_restore_test")] | ||
2651 | 17 | PulseAudio.Operation? test (PulseAudio.Context context, TestCb? callback = null); | ||
2652 | 18 | |||
2653 | 19 | public delegate void ReadCb (PulseAudio.Context context, PulseAudio.Extension.StreamRestore.Info[] streams, int eol); | ||
2654 | 20 | [CCode (cname="pa_ext_stream_restore_read")] | ||
2655 | 21 | PulseAudio.Operation? read (PulseAudio.Context context, ReadCb? callback = null); | ||
2656 | 22 | } |