Merge lp:~charlesk/indicator-sound/volume-warning into lp:indicator-sound/15.10
- volume-warning
- Merge into trunk.15.10
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Xavi Garcia | ||||
Approved revision: | 642 | ||||
Merged at revision: | 522 | ||||
Proposed branch: | lp:~charlesk/indicator-sound/volume-warning | ||||
Merge into: | lp:indicator-sound/15.10 | ||||
Prerequisite: | lp:~xavi-garcia-mena/indicator-sound/action-sync-volume | ||||
Diff against target: |
3381 lines (+1469/-896) 31 files modified
CMakeLists.txt (+2/-0) debian/control (+1/-1) po/POTFILES.in (+4/-0) src/CMakeLists.txt (+60/-1) src/Makefile.am.THIS (+0/-39) src/accounts-service-user.vala (+2/-2) src/info-notification.vala (+123/-0) src/main.c (+11/-3) src/media-player-mpris.vala (+3/-3) src/media-player-user.vala (+1/-2) src/mpris2-interfaces.vala (+9/-9) src/notification.vala (+70/-0) src/options-gsettings.vala (+87/-0) src/options.vala (+28/-0) src/service.vala (+114/-477) src/volume-control-pulse.vala (+84/-286) src/volume-control.vala (+18/-14) src/volume-warning-pulse.vala (+211/-0) src/volume-warning.vala (+216/-0) src/warn-notification.vala (+59/-0) tests/CMakeLists.txt (+10/-1) tests/integration/indicator-sound-test-base.cpp (+25/-7) tests/integration/indicator-sound-test-base.h (+2/-0) tests/integration/test-indicator.cpp (+8/-7) tests/notifications-mock.h (+1/-1) tests/notifications-test.cc (+208/-29) tests/options-mock.vala (+28/-0) tests/pa-mock.cpp (+19/-0) tests/volume-control-mock.vala (+19/-13) tests/volume-control-test.cc (+6/-1) tests/volume-warning-mock.vala (+40/-0) |
||||
To merge this branch: | bzr merge lp:~charlesk/indicator-sound/volume-warning | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Xavi Garcia | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Review via email: mp+281411@code.launchpad.net |
Commit message
Be more selective about when to show and dismiss the High Volume Warning Dialog.
Description of the change
Be more selective about when to show and dismiss the High Volume Warning Dialog.
PS Jenkins bot (ps-jenkins) wrote : | # |
- 611. By charles kerr <email address hidden>
-
remove unused property Notification.
visible
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:611
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 612. By charles kerr <email address hidden>
-
fix rounding regression in info-notification's volume percentage
- 613. By charles kerr <email address hidden>
-
simplify the Options class' properties
- 614. By charles kerr <email address hidden>
-
when showing volume % in notification, clamp to the range of [0%..100%]
- 615. By charles kerr <email address hidden>
-
when showing volume notifications, show as a percentage of max-volume
- 616. By charles kerr <email address hidden>
-
when max-volume changes, update volume action state & info notification
- 617. By charles kerr <email address hidden>
-
fix and re-enable NotificationsTe
st::ExtendendVo lumeNotificatio n
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:617
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 618. By charles kerr <email address hidden>
-
make VolumeControl:
:active_ output easier to mock for tests - 619. By charles kerr <email address hidden>
-
fill out volume-
warning- mock's mocking capabilities - 620. By charles kerr <email address hidden>
-
in mock Notify server, support actions required for volume warnings
- 621. By charles kerr <email address hidden>
-
add new Notifications test for volume-warning requiring headphones
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:621
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 622. By charles kerr <email address hidden>
-
rename VolumeWarning.
high_volume_ approved as VolumeWarning. approved After being moved into a class named "VolumeWarning", the high_volume_
prefix was redunant. - 623. By charles kerr <email address hidden>
-
in Options, make const strings const
- 624. By charles kerr <email address hidden>
-
copyediting: whitespace
- 625. By charles kerr <email address hidden>
-
in info-warning's icon picking code, fold redundant cases into one.
It looks like these switch statements are intended for future expansion,
so leaving the basic switching logic intact - 626. By charles kerr <email address hidden>
-
update POTFILES.in
- 627. By charles kerr <email address hidden>
-
copyediting: yet more whitespace
- 628. By charles kerr <email address hidden>
-
simplify some code in volume-warning and volume-
warning- pulse - 629. By charles kerr <email address hidden>
-
use predefined VariantTypes instead of reinventing the wheel
- 630. By charles kerr <email address hidden>
-
fix references to unowned strings returned by Proplist.gets()
- 631. By charles kerr <email address hidden>
-
fix references to unowned strings returned by Environment.
get_variable( ) - 632. By charles kerr <email address hidden>
-
use the 'unowned' keyword when picking from lists of hardcoded strings
- 633. By charles kerr <email address hidden>
-
in icon picking code, fold redundant cases together
- 634. By charles kerr <email address hidden>
-
re-add notifications-test to tests
- 635. By charles kerr <email address hidden>
-
expand volume warning test to include all trigger requirements
- 636. By charles kerr <email address hidden>
-
use a custom timer to speed up TriggerWarning's many test combinations
- 637. By charles kerr <email address hidden>
-
in notifications-test, make the loop test more flexible
- 638. By charles kerr <email address hidden>
-
typo fix: s/Usb/USB/ in ui strings
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:638
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Xavi Garcia (xavi-garcia-mena) wrote : | # |
It looks better than the initial approach.
What I see, though, is that you moved some of the new strings for the OSD notifications to different files...
I don´t know if that´s going to be a problem with the translations if we don´t land this today.
I also see you changed a couple of strings (from Usb to USB).
I´m not approving yet, as it still has those weird conflicts and it´s a bit hard to read, but overall it looks good to me :)
- 639. By Charles Kerr
-
revert previous commit to preserve translation strings (eg, s/USB/Usb/)
- 640. By Charles Kerr
-
sync with trunk
Charles Kerr (charlesk) wrote : | # |
> What I see, though, is that you moved some of the new strings for the OSD notifications to different files...
Moving the strings from one file to another should be alright as long as the new files are included in POTFILES.in, which they are.
> I also see you changed a couple of strings (from Usb to USB).
Agreed, that's bad timing.
Reverted r639.
> I´m not approving yet, as it still has those weird conflicts and it´s a bit hard to read
Working on the conflicts now. I hope you meant that's why it's hard to read ;)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:621
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:640
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 641. By Charles Kerr
-
sync with trunk.15.10
- 642. By Charles Kerr
-
re-enable notifications-test
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:642
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Xavi Garcia (xavi-garcia-mena) wrote : | # |
Yeah, it was hard to read due to the conflicts.
Approving, it looks good to me. Thanks!
- 643. By Charles Kerr
-
in volume-
warning- pulse, get/set the sink input's volume rather than the sink's volume, just as volume- control- pulse does - 644. By Charles Kerr
-
remove newly-dead variable VolumeWarningPu
lse._update_ sink_timer - 645. By Charles Kerr
-
change a tracer GLib.message() call to GLib.debug()
- 646. By Charles Kerr
-
in the integration tests, clamp the random volume selection to a range that won't cause the loud-volume-warning code to interfere with the test
- 647. By Charles Kerr
-
disable integration test PhoneChangeRole
Volume, amd64 failure seems unrelated to these changes & needs fixing in separate MP - 648. By Charles Kerr
-
in tests' PulseAudio mock, add pa_context_
get_sink_ input_info_ list() implementation
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2014-11-17 21:00:12 +0000 |
3 | +++ CMakeLists.txt 2016-01-13 20:35:42 +0000 |
4 | @@ -24,12 +24,14 @@ |
5 | set(SOURCE_BINARY_DIR "${CMAKE_BINARY_DIR}/src") |
6 | |
7 | set(PULSE_AUDIO_REQUIRED_VERSION 0.9.19) |
8 | +set(GLIB_2_0_REQUIRED_VERSION 2.32) |
9 | set(GIO_2_0_REQUIRED_VERSION 2.25.13) |
10 | set(URL_DISPATCHER_1_REQUIRED_VERSION 1) |
11 | |
12 | pkg_check_modules( |
13 | PULSEAUDIO REQUIRED |
14 | libpulse-mainloop-glib>=${PULSE_AUDIO_REQUIRED_VERSION} |
15 | + glib-2.0>=${GLIB_2_0_REQUIRED_VERSION} |
16 | gio-unix-2.0>=${GIO_2_0_REQUIRED_VERSION} |
17 | url-dispatcher-1>=${URL_DISPATCHER_1_REQUIRED_VERSION} |
18 | ) |
19 | |
20 | === modified file 'debian/control' |
21 | --- debian/control 2015-11-11 11:48:43 +0000 |
22 | +++ debian/control 2016-01-13 20:35:42 +0000 |
23 | @@ -18,7 +18,7 @@ |
24 | libaccountsservice-dev, |
25 | libdbustest1-dev (>= 15.04.0), |
26 | libgirepository1.0-dev, |
27 | - libglib2.0-dev (>= 2.22.3), |
28 | + libglib2.0-dev (>= 2.32.0), |
29 | libgtest-dev, |
30 | libqtdbusmock1-dev (>= 0.3), |
31 | libqtdbustest1-dev, |
32 | |
33 | === modified file 'po/POTFILES.in' |
34 | --- po/POTFILES.in 2013-09-11 09:47:53 +0000 |
35 | +++ po/POTFILES.in 2016-01-13 20:35:42 +0000 |
36 | @@ -1,3 +1,7 @@ |
37 | [encoding: UTF-8] |
38 | +src/info-notification.vala |
39 | +src/options-gsettings.vala |
40 | src/service.vala |
41 | src/sound-menu.vala |
42 | +src/warn-notification.vala |
43 | + |
44 | |
45 | === modified file 'src/CMakeLists.txt' |
46 | --- src/CMakeLists.txt 2015-12-23 13:35:46 +0000 |
47 | +++ src/CMakeLists.txt 2016-01-13 20:35:42 +0000 |
48 | @@ -32,6 +32,7 @@ |
49 | OPTIONS |
50 | --ccode |
51 | --thread |
52 | + --target-glib=${GLIB_2_0_REQUIRED_VERSION} |
53 | --vapidir=${CMAKE_SOURCE_DIR}/vapi/ |
54 | --vapidir=. |
55 | --pkg=url-dispatcher |
56 | @@ -39,23 +40,79 @@ |
57 | ) |
58 | |
59 | vala_add(indicator-sound-service |
60 | + notification.vala |
61 | +) |
62 | +vala_add(indicator-sound-service |
63 | + info-notification.vala |
64 | + DEPENDS |
65 | + notification |
66 | + volume-control |
67 | + options |
68 | +) |
69 | +vala_add(indicator-sound-service |
70 | + warn-notification.vala |
71 | + DEPENDS |
72 | + notification |
73 | +) |
74 | +vala_add(indicator-sound-service |
75 | service.vala |
76 | DEPENDS |
77 | sound-menu |
78 | volume-control |
79 | volume-control-pulse |
80 | + notification |
81 | + info-notification |
82 | + volume-warning |
83 | + options |
84 | + options-gsettings |
85 | media-player |
86 | media-player-list |
87 | mpris2-interfaces |
88 | accounts-service-user |
89 | ) |
90 | vala_add(indicator-sound-service |
91 | + options.vala |
92 | + DEPENDS |
93 | + volume-control |
94 | + volume-control-pulse |
95 | +) |
96 | +vala_add(indicator-sound-service |
97 | + options-gsettings.vala |
98 | + DEPENDS |
99 | + options |
100 | + volume-control-pulse |
101 | + volume-control |
102 | +) |
103 | +vala_add(indicator-sound-service |
104 | volume-control.vala |
105 | + DEPENDS |
106 | + options |
107 | + volume-control-pulse |
108 | ) |
109 | vala_add(indicator-sound-service |
110 | volume-control-pulse.vala |
111 | DEPENDS |
112 | - volume-control |
113 | + options |
114 | + volume-control |
115 | +) |
116 | +vala_add(indicator-sound-service |
117 | + volume-warning.vala |
118 | + DEPENDS |
119 | + options |
120 | + volume-control-pulse |
121 | + volume-control |
122 | + warn-notification |
123 | + notification |
124 | +) |
125 | +vala_add(indicator-sound-service |
126 | + volume-warning-pulse.vala |
127 | + DEPENDS |
128 | + volume-warning |
129 | + options |
130 | + volume-control-pulse |
131 | + volume-control |
132 | + warn-notification |
133 | + notification |
134 | ) |
135 | vala_add(indicator-sound-service |
136 | media-player.vala |
137 | @@ -104,6 +161,8 @@ |
138 | DEPENDS |
139 | media-player |
140 | volume-control |
141 | + options |
142 | + volume-control-pulse |
143 | ) |
144 | vala_add(indicator-sound-service |
145 | accounts-service-user.vala |
146 | |
147 | === removed file 'src/Makefile.am.THIS' |
148 | --- src/Makefile.am.THIS 2013-08-26 14:51:33 +0000 |
149 | +++ src/Makefile.am.THIS 1970-01-01 00:00:00 +0000 |
150 | @@ -1,39 +0,0 @@ |
151 | -pkglibexec_PROGRAMS = indicator-sound-service |
152 | - |
153 | -indicator_sound_service_SOURCES = \ |
154 | - service.vala \ |
155 | - main.vala \ |
156 | - volume-control.vala \ |
157 | - media-player.vala \ |
158 | - media-player-list.vala \ |
159 | - mpris2-interfaces.vala \ |
160 | - freedesktop-interfaces.vala \ |
161 | - sound-menu.vala \ |
162 | - bus-watch-namespace.c \ |
163 | - bus-watch-namespace.h |
164 | - |
165 | -indicator_sound_service_VALAFLAGS = \ |
166 | - --ccode \ |
167 | - --vapidir=$(top_srcdir)/vapi/ \ |
168 | - --vapidir=./ \ |
169 | - --thread \ |
170 | - --pkg config \ |
171 | - --pkg gio-2.0 \ |
172 | - --pkg gio-unix-2.0 \ |
173 | - --pkg libxml-2.0 \ |
174 | - --pkg libpulse \ |
175 | - --pkg libpulse-mainloop-glib \ |
176 | - --pkg bus-watcher \ |
177 | - --target-glib=2.36 |
178 | - |
179 | -# -w to disable warnings for vala-generated code |
180 | -indicator_sound_service_CFLAGS = $(PULSEAUDIO_CFLAGS) \ |
181 | - $(SOUNDSERVICE_CFLAGS) \ |
182 | - $(GCONF_CFLAGS) \ |
183 | - $(COVERAGE_CFLAGS) \ |
184 | - -DLIBEXECDIR=\"$(libexecdir)\" \ |
185 | - -w \ |
186 | - -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" |
187 | - |
188 | -indicator_sound_service_LDADD = $(PULSEAUDIO_LIBS) $(SOUNDSERVICE_LIBS) $(GCONF_LIBS) |
189 | -indicator_sound_service_LDFLAGS = $(COVERAGE_LDFLAGS) |
190 | |
191 | === modified file 'src/accounts-service-user.vala' |
192 | --- src/accounts-service-user.vala 2014-12-09 15:55:37 +0000 |
193 | +++ src/accounts-service-user.vala 2016-01-13 20:35:42 +0000 |
194 | @@ -185,7 +185,7 @@ |
195 | this.privacyproxy = Bus.get_proxy.end (res); |
196 | |
197 | (this.privacyproxy as DBusProxy).g_properties_changed.connect((proxy, changed, invalid) => { |
198 | - var welcomeval = changed.lookup_value("MessagesWelcomeScreen", new VariantType("b")); |
199 | + var welcomeval = changed.lookup_value("MessagesWelcomeScreen", VariantType.BOOLEAN); |
200 | if (welcomeval != null) { |
201 | debug("Messages on welcome screen changed"); |
202 | this.showDataOnGreeter = welcomeval.get_boolean(); |
203 | @@ -204,7 +204,7 @@ |
204 | this.syssoundproxy = Bus.get_proxy.end (res); |
205 | |
206 | (this.syssoundproxy as DBusProxy).g_properties_changed.connect((proxy, changed, invalid) => { |
207 | - var silentvar = changed.lookup_value("SilentMode", new VariantType("b")); |
208 | + var silentvar = changed.lookup_value("SilentMode", VariantType.BOOLEAN); |
209 | if (silentvar != null) { |
210 | debug("Silent Mode changed"); |
211 | this._silentMode = silentvar.get_boolean(); |
212 | |
213 | === added file 'src/info-notification.vala' |
214 | --- src/info-notification.vala 1970-01-01 00:00:00 +0000 |
215 | +++ src/info-notification.vala 2016-01-13 20:35:42 +0000 |
216 | @@ -0,0 +1,123 @@ |
217 | +/* |
218 | + * Copyright 2015 Canonical Ltd. |
219 | + * |
220 | + * This program is free software; you can redistribute it and/or modify |
221 | + * it under the terms of the GNU General Public License as published by |
222 | + * the Free Software Foundation; version 3. |
223 | + * |
224 | + * This program is distributed in the hope that it will be useful, |
225 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
226 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
227 | + * GNU General Public License for more details. |
228 | + * |
229 | + * You should have received a copy of the GNU General Public License |
230 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
231 | + * |
232 | + * Authors: |
233 | + * Charles Kerr <charles.kerr@canonical.com> |
234 | + */ |
235 | + |
236 | +using Notify; |
237 | + |
238 | +public class IndicatorSound.InfoNotification: Notification |
239 | +{ |
240 | + protected override Notify.Notification create_notification () { |
241 | + return new Notify.Notification (_("Volume"), "", "audio-volume-muted"); |
242 | + } |
243 | + |
244 | + public void show (VolumeControl.ActiveOutput active_output, |
245 | + double volume, |
246 | + bool is_high_volume) { |
247 | + if (!notify_server_supports ("x-canonical-private-synchronous")) |
248 | + return; |
249 | + |
250 | + /* Determine Label */ |
251 | + unowned string volume_label = get_notification_label (active_output); |
252 | + |
253 | + /* Choose an icon */ |
254 | + unowned string icon = get_volume_notification_icon (active_output, volume, is_high_volume); |
255 | + |
256 | + /* Reset the notification */ |
257 | + var n = _notification; |
258 | + n.update (_("Volume"), volume_label, icon); |
259 | + n.clear_hints(); |
260 | + n.set_hint ("x-canonical-non-shaped-icon", "true"); |
261 | + n.set_hint ("x-canonical-private-synchronous", "true"); |
262 | + n.set_hint ("x-canonical-value-bar-tint", is_high_volume ? "true" : "false"); |
263 | + n.set_hint ("value", ((int32)((volume * 100.0) + 0.5)).clamp(0, 100)); |
264 | + show_notification (); |
265 | + } |
266 | + |
267 | + private static unowned string get_notification_label (VolumeControl.ActiveOutput active_output) { |
268 | + |
269 | + switch (active_output) { |
270 | + case VolumeControl.ActiveOutput.SPEAKERS: |
271 | + return _("Speakers"); |
272 | + case VolumeControl.ActiveOutput.HEADPHONES: |
273 | + return _("Headphones"); |
274 | + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
275 | + return _("Bluetooth headphones"); |
276 | + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
277 | + return _("Bluetooth speaker"); |
278 | + case VolumeControl.ActiveOutput.USB_SPEAKER: |
279 | + return _("Usb speaker"); |
280 | + case VolumeControl.ActiveOutput.USB_HEADPHONES: |
281 | + return _("Usb headphones"); |
282 | + case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
283 | + return _("HDMI speaker"); |
284 | + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
285 | + return _("HDMI headphones"); |
286 | + default: |
287 | + return ""; |
288 | + } |
289 | + } |
290 | + |
291 | + private static unowned string get_volume_notification_icon (VolumeControl.ActiveOutput active_output, |
292 | + double volume, |
293 | + bool is_high_volume) { |
294 | + |
295 | + if (!is_high_volume) |
296 | + return get_volume_icon (active_output, volume); |
297 | + |
298 | + switch (active_output) { |
299 | + case VolumeControl.ActiveOutput.SPEAKERS: |
300 | + case VolumeControl.ActiveOutput.HEADPHONES: |
301 | + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
302 | + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
303 | + case VolumeControl.ActiveOutput.USB_SPEAKER: |
304 | + case VolumeControl.ActiveOutput.USB_HEADPHONES: |
305 | + case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
306 | + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
307 | + return "audio-volume-high"; |
308 | + |
309 | + default: |
310 | + return ""; |
311 | + } |
312 | + } |
313 | + |
314 | + private static unowned string get_volume_icon (VolumeControl.ActiveOutput active_output, |
315 | + double volume) |
316 | + { |
317 | + switch (active_output) { |
318 | + case VolumeControl.ActiveOutput.SPEAKERS: |
319 | + case VolumeControl.ActiveOutput.HEADPHONES: |
320 | + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
321 | + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
322 | + case VolumeControl.ActiveOutput.USB_SPEAKER: |
323 | + case VolumeControl.ActiveOutput.USB_HEADPHONES: |
324 | + case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
325 | + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
326 | + if (volume <= 0.0) |
327 | + return "audio-volume-muted"; |
328 | + if (volume <= 0.3) |
329 | + return "audio-volume-low"; |
330 | + if (volume <= 0.7) |
331 | + return "audio-volume-medium"; |
332 | + return "audio-volume-high"; |
333 | + |
334 | + default: |
335 | + return ""; |
336 | + } |
337 | + } |
338 | +} |
339 | + |
340 | |
341 | === modified file 'src/main.c' |
342 | --- src/main.c 2015-03-10 19:16:26 +0000 |
343 | +++ src/main.c 2016-01-13 20:35:42 +0000 |
344 | @@ -22,6 +22,7 @@ |
345 | #include "config.h" |
346 | |
347 | static IndicatorSoundService * service = NULL; |
348 | +static pa_glib_mainloop * pgloop = NULL; |
349 | |
350 | static gboolean |
351 | sigterm_handler (gpointer data) |
352 | @@ -46,8 +47,10 @@ |
353 | gpointer user_data) |
354 | { |
355 | MediaPlayerList * playerlist = NULL; |
356 | + IndicatorSoundOptions * options = NULL; |
357 | VolumeControlPulse * volume = NULL; |
358 | AccountsServiceUser * accounts = NULL; |
359 | + VolumeWarning * warning = NULL; |
360 | |
361 | |
362 | if (g_strcmp0("lightdm", g_get_user_name()) == 0) { |
363 | @@ -57,20 +60,24 @@ |
364 | accounts = accounts_service_user_new(); |
365 | } |
366 | |
367 | - volume = volume_control_pulse_new(); |
368 | + pgloop = pa_glib_mainloop_new(NULL); |
369 | + options = indicator_sound_options_gsettings_new(); |
370 | + volume = volume_control_pulse_new(options, pgloop); |
371 | + warning = volume_warning_pulse_new(options, pgloop); |
372 | |
373 | - service = indicator_sound_service_new (playerlist, volume, accounts); |
374 | + service = indicator_sound_service_new (playerlist, volume, accounts, options, warning); |
375 | |
376 | g_clear_object(&playerlist); |
377 | + g_clear_object(&options); |
378 | g_clear_object(&volume); |
379 | g_clear_object(&accounts); |
380 | + g_clear_object(&warning); |
381 | } |
382 | |
383 | int |
384 | main (int argc, char ** argv) |
385 | { |
386 | GMainLoop * loop = NULL; |
387 | - |
388 | bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); |
389 | setlocale (LC_ALL, ""); |
390 | bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); |
391 | @@ -95,6 +102,7 @@ |
392 | g_main_loop_run(loop); |
393 | |
394 | g_clear_object(&service); |
395 | + g_clear_pointer(&pgloop, pa_glib_mainloop_free); |
396 | |
397 | notify_uninit(); |
398 | |
399 | |
400 | === modified file 'src/media-player-mpris.vala' |
401 | --- src/media-player-mpris.vala 2015-10-21 13:16:58 +0000 |
402 | +++ src/media-player-mpris.vala 2016-01-13 20:35:42 +0000 |
403 | @@ -226,7 +226,7 @@ |
404 | if (this.play_when_attached) { |
405 | /* wait a little before calling PlayPause, some players need some time to |
406 | set themselves up */ |
407 | - Timeout.add (1000, () => { proxy.PlayPause.begin (); return false; } ); |
408 | + Timeout.add (1000, () => { proxy.PlayPause.begin (); return Source.REMOVE; } ); |
409 | this.play_when_attached = false; |
410 | } |
411 | } |
412 | @@ -269,7 +269,7 @@ |
413 | return; |
414 | } |
415 | |
416 | - Timeout.add (500, () => { this.fetch_playlists (); return false; } ); |
417 | + Timeout.add (500, () => { this.fetch_playlists (); return Source.REMOVE; } ); |
418 | } |
419 | |
420 | /* some players (e.g. Spotify) don't follow the spec closely and pass single strings in metadata fields |
421 | @@ -295,7 +295,7 @@ |
422 | this.playbackstatus_changed (); |
423 | } |
424 | |
425 | - var metadata = changed_properties.lookup_value ("Metadata", new VariantType ("a{sv}")); |
426 | + var metadata = changed_properties.lookup_value ("Metadata", VariantType.VARDICT); |
427 | if (metadata != null) |
428 | this.update_current_track (metadata); |
429 | } |
430 | |
431 | === modified file 'src/media-player-user.vala' |
432 | --- src/media-player-user.vala 2014-03-04 21:40:28 +0000 |
433 | +++ src/media-player-user.vala 2016-01-13 20:35:42 +0000 |
434 | @@ -75,8 +75,7 @@ |
435 | |
436 | properties_queued.remove_all(); |
437 | |
438 | - /* Remove source */ |
439 | - return false; |
440 | + return Source.REMOVE; |
441 | } |
442 | |
443 | /* Turns the DBus names into the object properties */ |
444 | |
445 | === modified file 'src/mpris2-interfaces.vala' |
446 | --- src/mpris2-interfaces.vala 2015-09-15 12:16:10 +0000 |
447 | +++ src/mpris2-interfaces.vala 2016-01-13 20:35:42 +0000 |
448 | @@ -1,18 +1,18 @@ |
449 | /* |
450 | -Copyright 2010 Canonical Ltd. |
451 | +Copyright 2010-2015 Canonical Ltd. |
452 | Authors: |
453 | Conor Curran <conor.curran@canonical.com> |
454 | |
455 | -This program is free software: you can redistribute it and/or modify it |
456 | -under the terms of the GNU General Public License version 3, as published |
457 | +This program is free software: you can redistribute it and/or modify it |
458 | +under the terms of the GNU General Public License version 3, as published |
459 | by the Free Software Foundation. |
460 | |
461 | -This program is distributed in the hope that it will be useful, but |
462 | -WITHOUT ANY WARRANTY; without even the implied warranties of |
463 | -MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
464 | +This program is distributed in the hope that it will be useful, but |
465 | +WITHOUT ANY WARRANTY; without even the implied warranties of |
466 | +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
467 | PURPOSE. See the GNU General Public License for more details. |
468 | |
469 | -You should have received a copy of the GNU General Public License along |
470 | +You should have received a copy of the GNU General Public License along |
471 | with this program. If not, see <http://www.gnu.org/licenses/>. |
472 | */ |
473 | |
474 | @@ -36,8 +36,8 @@ |
475 | public interface MprisPlayer : Object { |
476 | // properties |
477 | public abstract HashTable<string, Variant?> Metadata{owned get; set;} |
478 | - public abstract int32 Position{owned get; set;} |
479 | - public abstract string? PlaybackStatus{owned get; set;} |
480 | + public abstract int64 Position{owned get; set;} |
481 | + public abstract string? PlaybackStatus{owned get; set;} |
482 | public abstract bool CanPlay{owned get; set;} |
483 | public abstract bool CanGoNext{owned get; set;} |
484 | public abstract bool CanGoPrevious{owned get; set;} |
485 | |
486 | === added file 'src/notification.vala' |
487 | --- src/notification.vala 1970-01-01 00:00:00 +0000 |
488 | +++ src/notification.vala 2016-01-13 20:35:42 +0000 |
489 | @@ -0,0 +1,70 @@ |
490 | +/* |
491 | + * Copyright 2015 Canonical Ltd. |
492 | + * |
493 | + * This program is free software; you can redistribute it and/or modify |
494 | + * it under the terms of the GNU General Public License as published by |
495 | + * the Free Software Foundation; version 3. |
496 | + * |
497 | + * This program is distributed in the hope that it will be useful, |
498 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
499 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
500 | + * GNU General Public License for more details. |
501 | + * |
502 | + * You should have received a copy of the GNU General Public License |
503 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
504 | + * |
505 | + * Authors: |
506 | + * Charles Kerr <charles.kerr@canonical.com> |
507 | + */ |
508 | + |
509 | +public abstract class IndicatorSound.Notification: Object |
510 | +{ |
511 | + public Notification () { |
512 | + BusWatcher.watch_namespace ( |
513 | + GLib.BusType.SESSION, |
514 | + "org.freedesktop.Notifications", |
515 | + () => { debug ("Notifications name appeared"); }, |
516 | + () => { debug ("Notifications name vanshed"); _server_caps = null; }); |
517 | + |
518 | + _notification = create_notification (); |
519 | + } |
520 | + |
521 | + public void close () { |
522 | + var n = _notification; |
523 | + |
524 | + return_if_fail (n != null); |
525 | + |
526 | + if (n.id != 0) { |
527 | + try { |
528 | + n.close (); |
529 | + } catch (GLib.Error e) { |
530 | + GLib.warning ("Unable to close notification: %s", e.message); |
531 | + } |
532 | + } |
533 | + } |
534 | + |
535 | + ~Notification () { |
536 | + close (); |
537 | + } |
538 | + |
539 | + protected abstract Notify.Notification create_notification (); |
540 | + |
541 | + protected void show_notification () { |
542 | + try { |
543 | + _notification.show (); |
544 | + } catch (GLib.Error e) { |
545 | + GLib.warning ("Unable to show notification: %s", e.message); |
546 | + } |
547 | + } |
548 | + |
549 | + protected bool notify_server_supports (string cap) { |
550 | + if (_server_caps == null) |
551 | + _server_caps = Notify.get_server_caps (); |
552 | + |
553 | + return _server_caps.find_custom (cap, strcmp) != null; |
554 | + } |
555 | + |
556 | + protected Notify.Notification _notification = null; |
557 | + |
558 | + private static List<string> _server_caps = null; |
559 | +} |
560 | |
561 | === added file 'src/options-gsettings.vala' |
562 | --- src/options-gsettings.vala 1970-01-01 00:00:00 +0000 |
563 | +++ src/options-gsettings.vala 2016-01-13 20:35:42 +0000 |
564 | @@ -0,0 +1,87 @@ |
565 | +/* |
566 | + * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- |
567 | + * Copyright 2015 Canonical Ltd. |
568 | + * |
569 | + * This program is free software; you can redistribute it and/or modify |
570 | + * it under the terms of the GNU General Public License as published by |
571 | + * the Free Software Foundation; version 3. |
572 | + * |
573 | + * This program is distributed in the hope that it will be useful, |
574 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
575 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
576 | + * GNU General Public License for more details. |
577 | + * |
578 | + * You should have received a copy of the GNU General Public License |
579 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
580 | + * |
581 | + * Authors: |
582 | + * Charles Kerr <charles.kerr@canonical.com> |
583 | + */ |
584 | + |
585 | +using PulseAudio; |
586 | + |
587 | +public class IndicatorSound.OptionsGSettings : Options |
588 | +{ |
589 | + public OptionsGSettings() { |
590 | + init_max_volume(); |
591 | + init_loud_volume(); |
592 | + } |
593 | + |
594 | + ~OptionsGSettings() { |
595 | + } |
596 | + |
597 | + private Settings _settings = new Settings ("com.canonical.indicator.sound"); |
598 | + private Settings _shared_settings = new Settings ("com.ubuntu.sound"); |
599 | + |
600 | + /** MAX VOLUME PROPERTY **/ |
601 | + |
602 | + private static const string AMP_dB_KEY = "amplified-volume-decibels"; |
603 | + private static const string NORMAL_dB_KEY = "normal-volume-decibels"; |
604 | + private static const string ALLOW_AMP_KEY = "allow-amplified-volume"; |
605 | + |
606 | + private void init_max_volume() { |
607 | + _settings.changed[NORMAL_dB_KEY].connect(() => update_max_volume()); |
608 | + _settings.changed[AMP_dB_KEY].connect(() => update_max_volume()); |
609 | + _shared_settings.changed[ALLOW_AMP_KEY].connect(() => update_max_volume()); |
610 | + update_max_volume(); |
611 | + } |
612 | + private void update_max_volume () { |
613 | + set_max_volume_(calculate_max_volume()); |
614 | + } |
615 | + protected void set_max_volume_ (double vol) { |
616 | + if (max_volume != vol) { |
617 | + debug("changing max_volume from %f to %f", this.max_volume, vol); |
618 | + max_volume = vol; |
619 | + } |
620 | + } |
621 | + private double calculate_max_volume () { |
622 | + unowned string decibel_key = _shared_settings.get_boolean(ALLOW_AMP_KEY) |
623 | + ? AMP_dB_KEY |
624 | + : NORMAL_dB_KEY; |
625 | + var volume_dB = _settings.get_double(decibel_key); |
626 | + var volume_sw = PulseAudio.Volume.sw_from_dB (volume_dB); |
627 | + return VolumeControlPulse.volume_to_double (volume_sw); |
628 | + } |
629 | + |
630 | + |
631 | + /** LOUD VOLUME **/ |
632 | + |
633 | + private static const string LOUD_ENABLED_KEY = "warning-volume-enabled"; |
634 | + private static const string LOUD_DECIBEL_KEY = "warning-volume-decibels"; |
635 | + |
636 | + private void init_loud_volume() { |
637 | + _settings.changed[LOUD_ENABLED_KEY].connect(() => update_loud_volume()); |
638 | + _settings.changed[LOUD_DECIBEL_KEY].connect(() => update_loud_volume()); |
639 | + update_loud_volume(); |
640 | + } |
641 | + private void update_loud_volume() { |
642 | + |
643 | + var vol = PulseAudio.Volume.sw_from_dB (_settings.get_double (LOUD_DECIBEL_KEY)); |
644 | + if (loud_volume != vol) |
645 | + loud_volume = vol; |
646 | + |
647 | + var enabled = _settings.get_boolean(LOUD_ENABLED_KEY); |
648 | + if (loud_warning_enabled != enabled) |
649 | + loud_warning_enabled = enabled; |
650 | + } |
651 | +} |
652 | |
653 | === added file 'src/options.vala' |
654 | --- src/options.vala 1970-01-01 00:00:00 +0000 |
655 | +++ src/options.vala 2016-01-13 20:35:42 +0000 |
656 | @@ -0,0 +1,28 @@ |
657 | +/* |
658 | + * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- |
659 | + * Copyright 2015 Canonical Ltd. |
660 | + * |
661 | + * This program is free software; you can redistribute it and/or modify |
662 | + * it under the terms of the GNU General Public License as published by |
663 | + * the Free Software Foundation; version 3. |
664 | + * |
665 | + * This program is distributed in the hope that it will be useful, |
666 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
667 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
668 | + * GNU General Public License for more details. |
669 | + * |
670 | + * You should have received a copy of the GNU General Public License |
671 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
672 | + * |
673 | + * Authors: |
674 | + * Charles Kerr <charles.kerr@canonical.com> |
675 | + */ |
676 | + |
677 | +public abstract class IndicatorSound.Options : Object |
678 | +{ |
679 | + public double max_volume { get; protected set; default = 1.0; } |
680 | + |
681 | + public uint loud_volume { get; protected set; default = PulseAudio.Volume.sw_from_dB(8); } |
682 | + |
683 | + public bool loud_warning_enabled { get; protected set; default = true; } |
684 | +} |
685 | |
686 | === modified file 'src/service.vala' |
687 | --- src/service.vala 2016-01-05 15:08:24 +0000 |
688 | +++ src/service.vala 2016-01-13 20:35:42 +0000 |
689 | @@ -20,35 +20,25 @@ |
690 | public class IndicatorSound.Service: Object { |
691 | DBusConnection bus; |
692 | |
693 | - /** |
694 | - * A copy of volume_control.volume made before just warn_notification |
695 | - * is shown. Since the volume is clamped during the warning, we cache |
696 | - * the previous volume to use iff the user hits "OK". |
697 | - */ |
698 | - VolumeControl.Volume _pre_warn_volume = null; |
699 | + public Service (MediaPlayerList playerlist, VolumeControl volume, AccountsServiceUser? accounts, Options options, VolumeWarning volume_warning) { |
700 | |
701 | - public Service (MediaPlayerList playerlist, VolumeControl volume, AccountsServiceUser? accounts) { |
702 | try { |
703 | bus = Bus.get_sync(GLib.BusType.SESSION); |
704 | } catch (GLib.Error e) { |
705 | error("Unable to get DBus session bus: %s", e.message); |
706 | } |
707 | |
708 | - info_notification = new Notify.Notification(_("Volume"), "", "audio-volume-muted"); |
709 | + _options = options; |
710 | + _options.notify["max-volume"].connect(() => { |
711 | + update_volume_action_state(); |
712 | + this.update_notification(); |
713 | + }); |
714 | |
715 | - warn_notification = new Notify.Notification(_("Volume"), _("High volume can damage your hearing."), "audio-volume-high"); |
716 | - warn_notification.set_hint ("x-canonical-non-shaped-icon", "true"); |
717 | - warn_notification.set_hint ("x-canonical-snap-decisions", "true"); |
718 | - warn_notification.set_hint ("x-canonical-private-affirmative-tint", "true"); |
719 | - warn_notification.closed.connect((n) => { |
720 | - n.clear_actions(); |
721 | - waiting_user_approve_warn=false; |
722 | - increment_volume_sync_action(); |
723 | + _volume_warning = volume_warning; |
724 | + _volume_warning.notify["active"].connect(() => { |
725 | + this.increment_volume_sync_action(); |
726 | + this.update_notification(); |
727 | }); |
728 | - BusWatcher.watch_namespace (GLib.BusType.SESSION, |
729 | - "org.freedesktop.Notifications", |
730 | - () => { debug("Notifications name appeared"); }, |
731 | - () => { debug("Notifications name vanshed"); notify_server_caps_checked = false; }); |
732 | |
733 | this.settings = new Settings ("com.canonical.indicator.sound"); |
734 | |
735 | @@ -56,8 +46,26 @@ |
736 | this.notify["visible"].connect ( () => this.update_root_icon () ); |
737 | |
738 | this.volume_control = volume; |
739 | - this.volume_control.active_output_changed.connect (this.update_root_icon); |
740 | - this.volume_control.active_output_changed.connect (this.update_notification); |
741 | + this.volume_control.active_output_changed.connect(() => { |
742 | + bool headphones; |
743 | + switch(volume_control.active_output()) { |
744 | + case VolumeControl.ActiveOutput.HEADPHONES: |
745 | + case VolumeControl.ActiveOutput.USB_HEADPHONES: |
746 | + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
747 | + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
748 | + headphones = true; |
749 | + break; |
750 | + |
751 | + default: |
752 | + headphones = false; |
753 | + break; |
754 | + } |
755 | + message("setting _volume_warning.headphones_active to %d", (int)headphones); |
756 | + _volume_warning.headphones_active = headphones; |
757 | + |
758 | + update_root_icon(); |
759 | + update_notification(); |
760 | + }); |
761 | |
762 | this.accounts_service = accounts; |
763 | /* If we're on the greeter, don't export */ |
764 | @@ -94,7 +102,7 @@ |
765 | }); |
766 | |
767 | this.menus.@foreach ( (profile, menu) => { |
768 | - this.volume_control.bind_property ("high-volume", menu, "show-high-volume-warning", BindingFlags.SYNC_CREATE); |
769 | + _volume_warning.bind_property ("high-volume", menu, "show-high-volume-warning", BindingFlags.SYNC_CREATE); |
770 | }); |
771 | |
772 | this.menus.@foreach ( (profile, menu) => { |
773 | @@ -112,7 +120,7 @@ |
774 | block_info_notifications = state.get_boolean(); |
775 | if (block_info_notifications) { |
776 | debug("Indicator is shown"); |
777 | - close_notification(info_notification); |
778 | + _info_notification.close(); |
779 | } else { |
780 | debug("Indicator is hidden"); |
781 | } |
782 | @@ -128,33 +136,11 @@ |
783 | this.menus.@foreach ( (profile, menu) => menu.export (bus, @"/com/canonical/indicator/sound/$profile")); |
784 | } |
785 | |
786 | - private void close_notification(Notify.Notification? n) { |
787 | - return_if_fail (n != null); |
788 | - if (n.id != 0) { |
789 | - try { |
790 | - n.close(); |
791 | - } catch (GLib.Error e) { |
792 | - warning("Unable to close notification: %s", e.message); |
793 | - } |
794 | - } |
795 | - } |
796 | - |
797 | - private void show_notification(Notify.Notification? n) { |
798 | - return_if_fail (n != null); |
799 | - try { |
800 | - n.show (); |
801 | - } catch (GLib.Error e) { |
802 | - warning ("Unable to show notification: %s", e.message); |
803 | - } |
804 | - } |
805 | - |
806 | ~Service() { |
807 | debug("Destroying Service Object"); |
808 | |
809 | clear_acts_player(); |
810 | |
811 | - stop_clamp_to_high_timeout(); |
812 | - |
813 | if (this.player_action_update_id > 0) { |
814 | Source.remove (this.player_action_update_id); |
815 | this.player_action_update_id = 0; |
816 | @@ -203,19 +189,29 @@ |
817 | bool syncing_preferred_players = false; |
818 | AccountsServiceUser? accounts_service = null; |
819 | bool export_to_accounts_service = false; |
820 | - private Notify.Notification info_notification; |
821 | - private Notify.Notification warn_notification; |
822 | + private Options _options; |
823 | + private VolumeWarning _volume_warning; |
824 | + private IndicatorSound.InfoNotification _info_notification = new IndicatorSound.InfoNotification(); |
825 | |
826 | const double volume_step_percentage = 0.06; |
827 | |
828 | private void activate_scroll_action (SimpleAction action, Variant? param) { |
829 | - int delta = param.get_int32(); /* positive for up, negative for down */ |
830 | - double v = volume_control.volume.volume + volume_step_percentage * delta; |
831 | - volume_control.set_volume_clamp (v, VolumeControl.VolumeReasons.USER_KEYPRESS); |
832 | + int direction = param.get_int32(); // positive for up, negative for down |
833 | + message("scroll: %d", direction); |
834 | + |
835 | + if (_volume_warning.active) { |
836 | + _volume_warning.user_keypress(direction>0 |
837 | + ? VolumeWarning.Key.VOLUME_UP |
838 | + : VolumeWarning.Key.VOLUME_DOWN); |
839 | + } else { |
840 | + double delta = volume_step_percentage * direction; |
841 | + double v = volume_control.volume.volume + delta; |
842 | + volume_control.set_volume_clamp (v, VolumeControl.VolumeReasons.USER_KEYPRESS); |
843 | + } |
844 | } |
845 | |
846 | void activate_desktop_settings (SimpleAction action, Variant? param) { |
847 | - var env = Environment.get_variable ("DESKTOP_SESSION"); |
848 | + unowned string env = Environment.get_variable ("DESKTOP_SESSION"); |
849 | string cmd; |
850 | |
851 | if (Environment.get_variable ("MIR_SOCKET") != null) |
852 | @@ -256,7 +252,7 @@ |
853 | |
854 | void update_root_icon () { |
855 | double volume = this.volume_control.volume.volume; |
856 | - string icon = get_volume_root_icon (volume, this.volume_control.mute, volume_control.active_output); |
857 | + unowned string icon = get_volume_root_icon (volume, this.volume_control.mute, volume_control.active_output()); |
858 | |
859 | string accessible_name; |
860 | if (this.volume_control.mute) { |
861 | @@ -270,7 +266,7 @@ |
862 | } |
863 | |
864 | var root_action = actions.lookup_action ("root") as SimpleAction; |
865 | - var builder = new VariantBuilder (new VariantType ("a{sv}")); |
866 | + var builder = new VariantBuilder (VariantType.VARDICT); |
867 | builder.add ("{sv}", "title", new Variant.string (_("Sound"))); |
868 | builder.add ("{sv}", "accessible-desc", new Variant.string (accessible_name)); |
869 | builder.add ("{sv}", "icon", serialize_themed_icon (icon)); |
870 | @@ -278,397 +274,57 @@ |
871 | root_action.set_state (builder.end()); |
872 | } |
873 | |
874 | - private bool notify_server_caps_checked = false; |
875 | - private bool notify_server_supports_actions = false; |
876 | - private bool notify_server_supports_sync = false; |
877 | private bool block_info_notifications = false; |
878 | - private bool waiting_user_approve_warn = false; |
879 | - |
880 | - private string get_volume_icon (double volume, VolumeControl.ActiveOutput active_output) |
881 | - { |
882 | - string icon = ""; |
883 | - switch (active_output) |
884 | - { |
885 | - case VolumeControl.ActiveOutput.SPEAKERS: |
886 | - if (volume <= 0.0) |
887 | - icon = "audio-volume-muted"; |
888 | - else if (volume <= 0.3) |
889 | - icon = "audio-volume-low"; |
890 | - else if (volume <= 0.7) |
891 | - icon = "audio-volume-medium"; |
892 | - else |
893 | - icon = "audio-volume-high"; |
894 | - break; |
895 | - case VolumeControl.ActiveOutput.HEADPHONES: |
896 | - if (volume <= 0.0) |
897 | - icon = "audio-volume-muted"; |
898 | - else if (volume <= 0.3) |
899 | - icon = "audio-volume-low"; |
900 | - else if (volume <= 0.7) |
901 | - icon = "audio-volume-medium"; |
902 | - else |
903 | - icon = "audio-volume-high"; |
904 | - break; |
905 | - case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
906 | - if (volume <= 0.0) |
907 | - icon = "audio-volume-muted"; |
908 | - else if (volume <= 0.3) |
909 | - icon = "audio-volume-low"; |
910 | - else if (volume <= 0.7) |
911 | - icon = "audio-volume-medium"; |
912 | - else |
913 | - icon = "audio-volume-high"; |
914 | - break; |
915 | - case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
916 | - if (volume <= 0.0) |
917 | - icon = "audio-volume-muted"; |
918 | - else if (volume <= 0.3) |
919 | - icon = "audio-volume-low"; |
920 | - else if (volume <= 0.7) |
921 | - icon = "audio-volume-medium"; |
922 | - else |
923 | - icon = "audio-volume-high"; |
924 | - break; |
925 | - case VolumeControl.ActiveOutput.USB_SPEAKER: |
926 | - if (volume <= 0.0) |
927 | - icon = "audio-volume-muted"; |
928 | - else if (volume <= 0.3) |
929 | - icon = "audio-volume-low"; |
930 | - else if (volume <= 0.7) |
931 | - icon = "audio-volume-medium"; |
932 | - else |
933 | - icon = "audio-volume-high"; |
934 | - break; |
935 | - case VolumeControl.ActiveOutput.USB_HEADPHONES: |
936 | - if (volume <= 0.0) |
937 | - icon = "audio-volume-muted"; |
938 | - else if (volume <= 0.3) |
939 | - icon = "audio-volume-low"; |
940 | - else if (volume <= 0.7) |
941 | - icon = "audio-volume-medium"; |
942 | - else |
943 | - icon = "audio-volume-high"; |
944 | - break; |
945 | - case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
946 | - if (volume <= 0.0) |
947 | - icon = "audio-volume-muted"; |
948 | - else if (volume <= 0.3) |
949 | - icon = "audio-volume-low"; |
950 | - else if (volume <= 0.7) |
951 | - icon = "audio-volume-medium"; |
952 | - else |
953 | - icon = "audio-volume-high"; |
954 | - break; |
955 | - case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
956 | - if (volume <= 0.0) |
957 | - icon = "audio-volume-muted"; |
958 | - else if (volume <= 0.3) |
959 | - icon = "audio-volume-low"; |
960 | - else if (volume <= 0.7) |
961 | - icon = "audio-volume-medium"; |
962 | - else |
963 | - icon = "audio-volume-high"; |
964 | - break; |
965 | - } |
966 | - return icon; |
967 | - } |
968 | - |
969 | - private string get_volume_root_icon_by_volume (double volume, VolumeControl.ActiveOutput active_output) |
970 | - { |
971 | - string icon = ""; |
972 | - switch (active_output) |
973 | - { |
974 | - case VolumeControl.ActiveOutput.SPEAKERS: |
975 | - if (volume <= 0.0) |
976 | - icon = "audio-volume-muted-panel"; |
977 | - else if (volume <= 0.3) |
978 | - icon = "audio-volume-low-panel"; |
979 | - else if (volume <= 0.7) |
980 | - icon = "audio-volume-medium-panel"; |
981 | - else |
982 | - icon = "audio-volume-high-panel"; |
983 | - break; |
984 | - case VolumeControl.ActiveOutput.HEADPHONES: |
985 | - if (volume <= 0.0) |
986 | - icon = "audio-volume-muted-panel"; |
987 | - else if (volume <= 0.3) |
988 | - icon = "audio-volume-low-panel"; |
989 | - else if (volume <= 0.7) |
990 | - icon = "audio-volume-medium-panel"; |
991 | - else |
992 | - icon = "audio-volume-high-panel"; |
993 | - break; |
994 | - case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
995 | - if (volume <= 0.0) |
996 | - icon = "audio-volume-muted-panel"; |
997 | - else if (volume <= 0.3) |
998 | - icon = "audio-volume-low-panel"; |
999 | - else if (volume <= 0.7) |
1000 | - icon = "audio-volume-medium-panel"; |
1001 | - else |
1002 | - icon = "audio-volume-high-panel"; |
1003 | - break; |
1004 | - case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
1005 | - if (volume <= 0.0) |
1006 | - icon = "audio-volume-muted-panel"; |
1007 | - else if (volume <= 0.3) |
1008 | - icon = "audio-volume-low-panel"; |
1009 | - else if (volume <= 0.7) |
1010 | - icon = "audio-volume-medium-panel"; |
1011 | - else |
1012 | - icon = "audio-volume-high-panel"; |
1013 | - break; |
1014 | - case VolumeControl.ActiveOutput.USB_SPEAKER: |
1015 | - if (volume <= 0.0) |
1016 | - icon = "audio-volume-muted-panel"; |
1017 | - else if (volume <= 0.3) |
1018 | - icon = "audio-volume-low-panel"; |
1019 | - else if (volume <= 0.7) |
1020 | - icon = "audio-volume-medium-panel"; |
1021 | - else |
1022 | - icon = "audio-volume-high-panel"; |
1023 | - break; |
1024 | - case VolumeControl.ActiveOutput.USB_HEADPHONES: |
1025 | - if (volume <= 0.0) |
1026 | - icon = "audio-volume-muted-panel"; |
1027 | - else if (volume <= 0.3) |
1028 | - icon = "audio-volume-low-panel"; |
1029 | - else if (volume <= 0.7) |
1030 | - icon = "audio-volume-medium-panel"; |
1031 | - else |
1032 | - icon = "audio-volume-high-panel"; |
1033 | - break; |
1034 | - case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
1035 | - if (volume <= 0.0) |
1036 | - icon = "audio-volume-muted-panel"; |
1037 | - else if (volume <= 0.3) |
1038 | - icon = "audio-volume-low-panel"; |
1039 | - else if (volume <= 0.7) |
1040 | - icon = "audio-volume-medium-panel"; |
1041 | - else |
1042 | - icon = "audio-volume-high-panel"; |
1043 | - break; |
1044 | - case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
1045 | - if (volume <= 0.0) |
1046 | - icon = "audio-volume-muted-panel"; |
1047 | - else if (volume <= 0.3) |
1048 | - icon = "audio-volume-low-panel"; |
1049 | - else if (volume <= 0.7) |
1050 | - icon = "audio-volume-medium-panel"; |
1051 | - else |
1052 | - icon = "audio-volume-high-panel"; |
1053 | - break; |
1054 | - } |
1055 | - return icon; |
1056 | - } |
1057 | - |
1058 | - private string get_volume_notification_icon (double volume, bool loud, VolumeControl.ActiveOutput active_output) { |
1059 | - string icon = ""; |
1060 | - if (loud) { |
1061 | - switch (active_output) |
1062 | - { |
1063 | - case VolumeControl.ActiveOutput.SPEAKERS: |
1064 | - icon = "audio-volume-high"; |
1065 | - break; |
1066 | - case VolumeControl.ActiveOutput.HEADPHONES: |
1067 | - icon = "audio-volume-high"; |
1068 | - break; |
1069 | - case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
1070 | - icon = "audio-volume-high"; |
1071 | - break; |
1072 | - case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
1073 | - icon = "audio-volume-high"; |
1074 | - break; |
1075 | - case VolumeControl.ActiveOutput.USB_SPEAKER: |
1076 | - icon = "audio-volume-high"; |
1077 | - break; |
1078 | - case VolumeControl.ActiveOutput.USB_HEADPHONES: |
1079 | - icon = "audio-volume-high"; |
1080 | - break; |
1081 | - case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
1082 | - icon = "audio-volume-high"; |
1083 | - break; |
1084 | - case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
1085 | - icon = "audio-volume-high"; |
1086 | - break; |
1087 | - } |
1088 | - } else { |
1089 | - icon = get_volume_icon (volume, active_output); |
1090 | - } |
1091 | - return icon; |
1092 | - } |
1093 | - |
1094 | - private string get_volume_root_icon (double volume, bool mute, VolumeControl.ActiveOutput active_output) { |
1095 | - string icon = ""; |
1096 | - switch (active_output) |
1097 | - { |
1098 | - case VolumeControl.ActiveOutput.SPEAKERS: |
1099 | - if (mute || volume <= 0.0) |
1100 | - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; |
1101 | - else if (this.accounts_service != null && this.accounts_service.silentMode) |
1102 | - icon = "audio-volume-muted-panel"; |
1103 | - else |
1104 | - icon = get_volume_root_icon_by_volume (volume, active_output); |
1105 | - break; |
1106 | - case VolumeControl.ActiveOutput.HEADPHONES: |
1107 | - if (mute || volume <= 0.0) |
1108 | - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; |
1109 | - else if (this.accounts_service != null && this.accounts_service.silentMode) |
1110 | - icon = "audio-volume-muted-panel"; |
1111 | - else |
1112 | - icon = get_volume_root_icon_by_volume (volume, active_output); |
1113 | - break; |
1114 | - case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
1115 | - if (mute || volume <= 0.0) |
1116 | - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; |
1117 | - else if (this.accounts_service != null && this.accounts_service.silentMode) |
1118 | - icon = "audio-volume-muted-panel"; |
1119 | - else |
1120 | - icon = get_volume_root_icon_by_volume (volume, active_output); |
1121 | - break; |
1122 | - case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
1123 | - if (mute || volume <= 0.0) |
1124 | - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; |
1125 | - else if (this.accounts_service != null && this.accounts_service.silentMode) |
1126 | - icon = "audio-volume-muted-panel"; |
1127 | - else |
1128 | - icon = get_volume_root_icon_by_volume (volume, active_output); |
1129 | - break; |
1130 | - case VolumeControl.ActiveOutput.USB_SPEAKER: |
1131 | - if (mute || volume <= 0.0) |
1132 | - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; |
1133 | - else if (this.accounts_service != null && this.accounts_service.silentMode) |
1134 | - icon = "audio-volume-muted-panel"; |
1135 | - else |
1136 | - icon = get_volume_root_icon_by_volume (volume, active_output); |
1137 | - break; |
1138 | - case VolumeControl.ActiveOutput.USB_HEADPHONES: |
1139 | - if (mute || volume <= 0.0) |
1140 | - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; |
1141 | - else if (this.accounts_service != null && this.accounts_service.silentMode) |
1142 | - icon = "audio-volume-muted-panel"; |
1143 | - else |
1144 | - icon = get_volume_root_icon_by_volume (volume, active_output); |
1145 | - break; |
1146 | - case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
1147 | - if (mute || volume <= 0.0) |
1148 | - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; |
1149 | - else if (this.accounts_service != null && this.accounts_service.silentMode) |
1150 | - icon = "audio-volume-muted-panel"; |
1151 | - else |
1152 | - icon = get_volume_root_icon_by_volume (volume, active_output); |
1153 | - break; |
1154 | - case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
1155 | - if (mute || volume <= 0.0) |
1156 | - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; |
1157 | - else if (this.accounts_service != null && this.accounts_service.silentMode) |
1158 | - icon = "audio-volume-muted-panel"; |
1159 | - else |
1160 | - icon = get_volume_root_icon_by_volume (volume, active_output); |
1161 | - break; |
1162 | - } |
1163 | - return icon; |
1164 | - } |
1165 | - |
1166 | - private string get_notification_label () { |
1167 | - string volume_label = ""; |
1168 | - switch (volume_control.active_output) |
1169 | - { |
1170 | - case VolumeControl.ActiveOutput.SPEAKERS: |
1171 | - volume_label = _("Speakers"); |
1172 | - break; |
1173 | - case VolumeControl.ActiveOutput.HEADPHONES: |
1174 | - volume_label = _("Headphones"); |
1175 | - break; |
1176 | - case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
1177 | - volume_label = _("Bluetooth headphones"); |
1178 | - break; |
1179 | - case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
1180 | - volume_label = _("Bluetooth speaker"); |
1181 | - break; |
1182 | - case VolumeControl.ActiveOutput.USB_SPEAKER: |
1183 | - volume_label = _("Usb speaker"); |
1184 | - break; |
1185 | - case VolumeControl.ActiveOutput.USB_HEADPHONES: |
1186 | - volume_label = _("Usb headphones"); |
1187 | - break; |
1188 | - case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
1189 | - volume_label = _("HDMI speaker"); |
1190 | - break; |
1191 | - case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
1192 | - volume_label = _("HDMI headphones"); |
1193 | - break; |
1194 | - } |
1195 | - |
1196 | - return volume_label; |
1197 | + |
1198 | + private static unowned string get_volume_root_icon_by_volume (double volume, VolumeControl.ActiveOutput active_output) { |
1199 | + switch (active_output) { |
1200 | + case VolumeControl.ActiveOutput.SPEAKERS: |
1201 | + case VolumeControl.ActiveOutput.HEADPHONES: |
1202 | + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
1203 | + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
1204 | + case VolumeControl.ActiveOutput.USB_SPEAKER: |
1205 | + case VolumeControl.ActiveOutput.USB_HEADPHONES: |
1206 | + case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
1207 | + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
1208 | + if (volume <= 0.0) |
1209 | + return "audio-volume-muted-panel"; |
1210 | + if (volume <= 0.3) |
1211 | + return "audio-volume-low-panel"; |
1212 | + if (volume <= 0.7) |
1213 | + return "audio-volume-medium-panel"; |
1214 | + return "audio-volume-high-panel"; |
1215 | + |
1216 | + default: |
1217 | + return ""; |
1218 | + } |
1219 | + } |
1220 | + |
1221 | + private unowned string get_volume_root_icon (double volume, bool mute, VolumeControl.ActiveOutput active_output) { |
1222 | + switch (active_output) { |
1223 | + case VolumeControl.ActiveOutput.SPEAKERS: |
1224 | + case VolumeControl.ActiveOutput.HEADPHONES: |
1225 | + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: |
1226 | + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: |
1227 | + case VolumeControl.ActiveOutput.USB_SPEAKER: |
1228 | + case VolumeControl.ActiveOutput.USB_HEADPHONES: |
1229 | + case VolumeControl.ActiveOutput.HDMI_SPEAKER: |
1230 | + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: |
1231 | + if (mute || volume <= 0.0) |
1232 | + return this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; |
1233 | + if (this.accounts_service != null && this.accounts_service.silentMode) |
1234 | + return "audio-volume-muted-panel"; |
1235 | + return get_volume_root_icon_by_volume (volume, active_output); |
1236 | + |
1237 | + default: |
1238 | + return ""; |
1239 | + } |
1240 | } |
1241 | |
1242 | private void update_notification () { |
1243 | - |
1244 | - List<string> caps = Notify.get_server_caps (); |
1245 | - notify_server_supports_actions = caps.find_custom ("actions", strcmp) != null; |
1246 | - notify_server_supports_sync = caps.find_custom ("x-canonical-private-synchronous", strcmp) != null; |
1247 | - notify_server_caps_checked = true; |
1248 | - |
1249 | - var loud = volume_control.high_volume; |
1250 | - bool ignore_warning_this_time = this.volume_control.ignore_high_volume; |
1251 | - var warn = loud |
1252 | - && this.notify_server_supports_actions |
1253 | - && !this.volume_control.high_volume_approved |
1254 | - && !ignore_warning_this_time; |
1255 | - if (waiting_user_approve_warn && volume_control.below_warning_volume) { |
1256 | - volume_control.set_warning_volume(); |
1257 | - close_notification(warn_notification); |
1258 | - } |
1259 | - if (warn) { |
1260 | - close_notification(info_notification); |
1261 | - if (_pre_warn_volume == null) { |
1262 | - _pre_warn_volume = new VolumeControl.Volume(); |
1263 | - _pre_warn_volume.volume = volume_control.volume.volume; |
1264 | - _pre_warn_volume.reason = volume_control.volume.reason; |
1265 | - } |
1266 | - warn_notification.clear_actions(); |
1267 | - warn_notification.add_action ("ok", _("OK"), (n, a) => { |
1268 | - stop_clamp_to_high_timeout(); |
1269 | - volume_control.approve_high_volume (); |
1270 | - // restore the volume the user introduced |
1271 | - VolumeControl.Volume vol = new VolumeControl.Volume(); |
1272 | - vol.volume = volume_control.get_pre_clamped_volume(); |
1273 | - vol.reason = VolumeControl.VolumeReasons.USER_KEYPRESS; |
1274 | - _pre_warn_volume = null; |
1275 | - volume_control.volume = vol; |
1276 | - |
1277 | - waiting_user_approve_warn = false; |
1278 | - }); |
1279 | - warn_notification.add_action ("cancel", _("Cancel"), (n, a) => { |
1280 | - _pre_warn_volume = null; |
1281 | - waiting_user_approve_warn = false; |
1282 | - increment_volume_sync_action(); |
1283 | - }); |
1284 | - waiting_user_approve_warn = true; |
1285 | - show_notification(warn_notification); |
1286 | - } else { |
1287 | - if (!waiting_user_approve_warn) { |
1288 | - close_notification(warn_notification); |
1289 | - |
1290 | - if (notify_server_supports_sync && !block_info_notifications && !ignore_warning_this_time) { |
1291 | - /* Determine Label */ |
1292 | - string volume_label = get_notification_label (); |
1293 | - |
1294 | - /* Choose an icon */ |
1295 | - string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_output); |
1296 | - |
1297 | - /* Reset the notification */ |
1298 | - var n = this.info_notification; |
1299 | - n.update (_("Volume"), volume_label, icon); |
1300 | - n.clear_hints(); |
1301 | - n.set_hint ("x-canonical-non-shaped-icon", "true"); |
1302 | - n.set_hint ("x-canonical-private-synchronous", "true"); |
1303 | - n.set_hint ("x-canonical-value-bar-tint", loud ? "true" : "false"); |
1304 | - n.set_hint ("value", (int32)Math.round(get_volume_percent() * 100.0)); |
1305 | - show_notification(n); |
1306 | - } |
1307 | - } |
1308 | + if (!_volume_warning.active && !block_info_notifications) { |
1309 | + _info_notification.show(this.volume_control.active_output(), |
1310 | + get_volume_percent(), |
1311 | + _volume_warning.high_volume); |
1312 | } |
1313 | } |
1314 | |
1315 | @@ -738,7 +394,7 @@ |
1316 | this.mute_blocks_sound = false; |
1317 | this.sound_was_blocked_timeout_id = 0; |
1318 | this.update_root_icon (); |
1319 | - return false; |
1320 | + return Source.REMOVE; |
1321 | }); |
1322 | } |
1323 | |
1324 | @@ -750,10 +406,10 @@ |
1325 | |
1326 | /* return the current volume in the range of [0.0, 1.0] */ |
1327 | private double get_volume_percent() { |
1328 | - return volume_control.volume.volume / this.volume_control.max_volume; |
1329 | + return volume_control.volume.volume / _options.max_volume; |
1330 | } |
1331 | |
1332 | - /* volume control's range can vary depending on its max_volume property, |
1333 | + /* volume control's range can vary depending on options.max_volume, |
1334 | * but the action always needs to be in [0.0, 1.0]... */ |
1335 | private Variant create_volume_action_state() { |
1336 | return new Variant.double (get_volume_percent()); |
1337 | @@ -768,14 +424,14 @@ |
1338 | volume_action = new SimpleAction.stateful ("volume", VariantType.INT32, create_volume_action_state()); |
1339 | |
1340 | volume_action.change_state.connect ( (action, val) => { |
1341 | - double v = val.get_double () * this.volume_control.max_volume; |
1342 | + double v = val.get_double () * _options.max_volume; |
1343 | volume_control.set_volume_clamp (v, VolumeControl.VolumeReasons.USER_KEYPRESS); |
1344 | }); |
1345 | |
1346 | /* activating this action changes the volume by the amount given in the parameter */ |
1347 | volume_action.activate.connect ((a,p) => activate_scroll_action(a,p)); |
1348 | |
1349 | - this.volume_control.notify["max-volume"].connect(() => { |
1350 | + _options.notify["max-volume"].connect(() => { |
1351 | update_volume_action_state(); |
1352 | }); |
1353 | |
1354 | @@ -788,9 +444,6 @@ |
1355 | if (reason == VolumeControl.VolumeReasons.USER_KEYPRESS || |
1356 | reason == VolumeControl.VolumeReasons.DEVICE_OUTPUT_CHANGE) |
1357 | this.update_notification (); |
1358 | - |
1359 | - if ((warn_notification.id != 0) && (_pre_warn_volume != null)) |
1360 | - clamp_to_high_soon(); |
1361 | }); |
1362 | |
1363 | this.volume_control.bind_property ("ready", volume_action, "enabled", BindingFlags.SYNC_CREATE); |
1364 | @@ -815,12 +468,19 @@ |
1365 | return mic_volume_action; |
1366 | } |
1367 | |
1368 | + private Variant create_high_volume_action_state() { |
1369 | + return new Variant.boolean (_volume_warning.high_volume); |
1370 | + } |
1371 | + private void update_high_volume_action_state() { |
1372 | + high_volume_action.set_state(create_high_volume_action_state()); |
1373 | + } |
1374 | + |
1375 | SimpleAction high_volume_action; |
1376 | Action create_high_volume_action () { |
1377 | - high_volume_action = new SimpleAction.stateful("high-volume", null, new Variant.boolean (this.volume_control.high_volume)); |
1378 | + high_volume_action = new SimpleAction.stateful("high-volume", null, create_high_volume_action_state()); |
1379 | |
1380 | - this.volume_control.notify["high-volume"].connect( () => { |
1381 | - high_volume_action.set_state(new Variant.boolean (this.volume_control.high_volume)); |
1382 | + _volume_warning.notify["high-volume"].connect( () => { |
1383 | + update_high_volume_action_state(); |
1384 | update_notification(); |
1385 | }); |
1386 | |
1387 | @@ -842,7 +502,7 @@ |
1388 | uint export_actions = 0; |
1389 | |
1390 | Variant action_state_for_player (MediaPlayer player, bool show_track = true) { |
1391 | - var builder = new VariantBuilder (new VariantType ("a{sv}")); |
1392 | + var builder = new VariantBuilder (VariantType.VARDICT); |
1393 | builder.add ("{sv}", "running", new Variant ("b", player.is_running)); |
1394 | builder.add ("{sv}", "state", new Variant ("s", player.state)); |
1395 | if (player.current_track != null && show_track) { |
1396 | @@ -881,7 +541,7 @@ |
1397 | clear_acts_player(); |
1398 | |
1399 | this.player_action_update_id = 0; |
1400 | - return false; |
1401 | + return Source.REMOVE; |
1402 | } |
1403 | |
1404 | void eventually_update_player_actions () { |
1405 | @@ -956,27 +616,4 @@ |
1406 | |
1407 | this.update_preferred_players (); |
1408 | } |
1409 | - |
1410 | - /** VOLUME CLAMPING **/ |
1411 | - |
1412 | - private uint _clamp_to_high_timeout = 0; |
1413 | - |
1414 | - private void stop_clamp_to_high_timeout() { |
1415 | - if (_clamp_to_high_timeout != 0) { |
1416 | - Source.remove(_clamp_to_high_timeout); |
1417 | - _clamp_to_high_timeout = 0; |
1418 | - } |
1419 | - } |
1420 | - |
1421 | - private void clamp_to_high_soon() { |
1422 | - const uint interval_msec = 200; |
1423 | - if (_clamp_to_high_timeout == 0) |
1424 | - _clamp_to_high_timeout = Timeout.add(interval_msec, clamp_to_high_idle); |
1425 | - } |
1426 | - |
1427 | - private bool clamp_to_high_idle() { |
1428 | - _clamp_to_high_timeout = 0; |
1429 | - volume_control.clamp_to_high_volume(); |
1430 | - return false; // Source.REMOVE; |
1431 | - } |
1432 | } |
1433 | |
1434 | === modified file 'src/volume-control-pulse.vala' |
1435 | --- src/volume-control-pulse.vala 2016-01-05 15:08:24 +0000 |
1436 | +++ src/volume-control-pulse.vala 2016-01-13 20:35:42 +0000 |
1437 | @@ -22,9 +22,6 @@ |
1438 | using Notify; |
1439 | using Gee; |
1440 | |
1441 | -[CCode(cname="pa_cvolume_set", cheader_filename = "pulse/volume.h")] |
1442 | -extern unowned PulseAudio.CVolume? vol_set (PulseAudio.CVolume? cv, uint channels, PulseAudio.Volume v); |
1443 | - |
1444 | [DBus (name="com.canonical.UnityGreeter.List")] |
1445 | interface GreeterListInterface : Object |
1446 | { |
1447 | @@ -34,19 +31,14 @@ |
1448 | |
1449 | public class VolumeControlPulse : VolumeControl |
1450 | { |
1451 | - /* this is static to ensure it being freed after @context (loop does not have ref counting) */ |
1452 | - private static PulseAudio.GLibMainLoop loop; |
1453 | + private unowned PulseAudio.GLibMainLoop loop = null; |
1454 | |
1455 | private uint _reconnect_timer = 0; |
1456 | |
1457 | private PulseAudio.Context context; |
1458 | private bool _mute = true; |
1459 | - private bool _is_playing = false; |
1460 | - private bool _ignore_warning_this_time = false; |
1461 | private VolumeControl.Volume _volume = new VolumeControl.Volume(); |
1462 | private double _mic_volume = 0.0; |
1463 | - private Settings _settings = new Settings ("com.canonical.indicator.sound"); |
1464 | - private Settings _shared_settings = new Settings ("com.ubuntu.sound"); |
1465 | |
1466 | /* Used by the pulseaudio stream restore extension */ |
1467 | private DBusConnection _pconn; |
1468 | @@ -57,22 +49,6 @@ |
1469 | private bool _pulse_use_stream_restore = false; |
1470 | private int32 _active_sink_input = -1; |
1471 | private string[] _valid_roles = {"multimedia", "alert", "alarm", "phone"}; |
1472 | - public override string stream { |
1473 | - get { |
1474 | - if (_active_sink_input == -1) |
1475 | - return "alert"; |
1476 | - var path = _sink_input_hash[_active_sink_input]; |
1477 | - if (path == _objp_role_multimedia) |
1478 | - return "multimedia"; |
1479 | - if (path == _objp_role_alert) |
1480 | - return "alert"; |
1481 | - if (path == _objp_role_alarm) |
1482 | - return "alarm"; |
1483 | - if (path == _objp_role_phone) |
1484 | - return "phone"; |
1485 | - return "alert"; |
1486 | - } |
1487 | - } |
1488 | private string? _objp_role_multimedia = null; |
1489 | private string? _objp_role_alert = null; |
1490 | private string? _objp_role_alarm = null; |
1491 | @@ -87,40 +63,28 @@ |
1492 | private uint _accountservice_volume_timer = 0; |
1493 | private bool _send_next_local_volume = false; |
1494 | private double _account_service_volume = 0.0; |
1495 | - private bool _active_port_headphone = false; |
1496 | private VolumeControl.ActiveOutput _active_output = VolumeControl.ActiveOutput.SPEAKERS; |
1497 | |
1498 | - /** true when connected to the pulse server */ |
1499 | - public override bool ready { get; private set; } |
1500 | - |
1501 | /** true when a microphone is active **/ |
1502 | public override bool active_mic { get; private set; default = false; } |
1503 | |
1504 | - public VolumeControlPulse () |
1505 | + public VolumeControlPulse (IndicatorSound.Options options, PulseAudio.GLibMainLoop loop) |
1506 | { |
1507 | + base(options); |
1508 | + |
1509 | _volume.volume = 0.0; |
1510 | _volume.reason = VolumeControl.VolumeReasons.PULSE_CHANGE; |
1511 | |
1512 | - if (loop == null) |
1513 | - loop = new PulseAudio.GLibMainLoop (); |
1514 | + this.loop = loop; |
1515 | |
1516 | _mute_cancellable = new Cancellable (); |
1517 | _volume_cancellable = new Cancellable (); |
1518 | |
1519 | - init_all_properties(); |
1520 | - |
1521 | setup_accountsservice.begin (); |
1522 | |
1523 | this.reconnect_to_pulse (); |
1524 | } |
1525 | |
1526 | - private void init_all_properties() |
1527 | - { |
1528 | - init_max_volume(); |
1529 | - init_high_volume(); |
1530 | - init_high_volume_approved(); |
1531 | - } |
1532 | - |
1533 | ~VolumeControlPulse () |
1534 | { |
1535 | stop_all_timers(); |
1536 | @@ -134,10 +98,9 @@ |
1537 | } |
1538 | stop_local_volume_timer(); |
1539 | stop_account_service_volume_timer(); |
1540 | - stop_high_volume_approved_timer(); |
1541 | } |
1542 | |
1543 | - private VolumeControl.ActiveOutput calculate_active_output (SinkInfo? sink) { |
1544 | + public static VolumeControl.ActiveOutput calculate_active_output (SinkInfo? sink) { |
1545 | |
1546 | VolumeControl.ActiveOutput ret_output = VolumeControl.ActiveOutput.SPEAKERS; |
1547 | /* Check if the current active port is headset/headphone */ |
1548 | @@ -155,9 +118,8 @@ |
1549 | (sink.active_port != null && |
1550 | (sink.active_port.name.contains("headset") || |
1551 | sink.active_port.name.contains("headphone")))) { |
1552 | - _active_port_headphone = true; |
1553 | // check if it's a bluetooth device |
1554 | - var device_bus = sink.proplist.gets ("device.bus"); |
1555 | + unowned string device_bus = sink.proplist.gets ("device.bus"); |
1556 | if (device_bus != null && device_bus == "bluetooth") { |
1557 | ret_output = VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES; |
1558 | } else if (device_bus != null && device_bus == "usb") { |
1559 | @@ -169,8 +131,7 @@ |
1560 | } |
1561 | } else { |
1562 | // speaker |
1563 | - _active_port_headphone = false; |
1564 | - var device_bus = sink.proplist.gets ("device.bus"); |
1565 | + unowned string device_bus = sink.proplist.gets ("device.bus"); |
1566 | if (device_bus != null && device_bus == "bluetooth") { |
1567 | ret_output = VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER; |
1568 | } else if (device_bus != null && device_bus == "usb") { |
1569 | @@ -245,28 +206,19 @@ |
1570 | } |
1571 | |
1572 | var playing = (i.state == PulseAudio.SinkState.RUNNING); |
1573 | - if (_is_playing != playing) |
1574 | - { |
1575 | - _is_playing = playing; |
1576 | - this.notify_property ("is-playing"); |
1577 | - } |
1578 | - |
1579 | - // store the current status of the active output |
1580 | - VolumeControl.ActiveOutput active_output_before = active_output; |
1581 | - |
1582 | - // calculate the output |
1583 | - _active_output = calculate_active_output (i); |
1584 | - |
1585 | - // check if the output has changed, if so... emit a signal |
1586 | - VolumeControl.ActiveOutput active_output_now = active_output; |
1587 | - if (active_output_now != active_output_before && |
1588 | - (active_output_now != VolumeControl.ActiveOutput.CALL_MODE && |
1589 | - active_output_before != VolumeControl.ActiveOutput.CALL_MODE)) { |
1590 | - this.active_output_changed (active_output_now); |
1591 | - if (active_output_now == VolumeControl.ActiveOutput.SPEAKERS) { |
1592 | - _high_volume_approved = false; |
1593 | - } |
1594 | - update_high_volume(); |
1595 | + if (is_playing != playing) |
1596 | + is_playing = playing; |
1597 | + |
1598 | + var oldval = _active_output; |
1599 | + var newval = calculate_active_output(i); |
1600 | + |
1601 | + _active_output = newval; |
1602 | + |
1603 | + // Emit a change signal iff CALL_MODE wasn't involved. (FIXME: yuck.) |
1604 | + if ((oldval != VolumeControl.ActiveOutput.CALL_MODE) && |
1605 | + (newval != VolumeControl.ActiveOutput.CALL_MODE) && |
1606 | + (oldval != newval)) { |
1607 | + this.active_output_changed (newval); |
1608 | } |
1609 | |
1610 | if (_pulse_use_stream_restore == false && |
1611 | @@ -345,10 +297,6 @@ |
1612 | var vol = new VolumeControl.Volume(); |
1613 | vol.volume = volume_to_double (lvolume); |
1614 | vol.reason = VolumeControl.VolumeReasons.PULSE_CHANGE; |
1615 | - // Ignore changes from PULSE to avoid issues with |
1616 | - // some apps that change the volume in the sink |
1617 | - // We only take into account volume changes from the user |
1618 | - this._ignore_warning_this_time = true; |
1619 | this.volume = vol; |
1620 | } |
1621 | } |
1622 | @@ -358,6 +306,21 @@ |
1623 | return message; |
1624 | } |
1625 | |
1626 | + private VolumeControl.Stream calculate_active_stream() |
1627 | + { |
1628 | + if (_active_sink_input != -1) { |
1629 | + var path = _sink_input_hash[_active_sink_input]; |
1630 | + if (path == _objp_role_multimedia) |
1631 | + return Stream.MULTIMEDIA; |
1632 | + if (path == _objp_role_alarm) |
1633 | + return Stream.ALARM; |
1634 | + if (path == _objp_role_phone) |
1635 | + return Stream.PHONE; |
1636 | + } |
1637 | + |
1638 | + return VolumeControl.Stream.ALERT; |
1639 | + } |
1640 | + |
1641 | private async void update_active_sink_input (int32 index) |
1642 | { |
1643 | if ((index == -1) || (index != _active_sink_input && index in _sink_input_list)) { |
1644 | @@ -365,10 +328,14 @@ |
1645 | if (index != -1) |
1646 | sink_input_objp = _sink_input_hash.get (index); |
1647 | _active_sink_input = index; |
1648 | + var stream = calculate_active_stream(); |
1649 | + if (active_stream != stream) { |
1650 | + active_stream = stream; |
1651 | + } |
1652 | |
1653 | /* Listen for role volume changes from pulse itself (external clients) */ |
1654 | try { |
1655 | - var builder = new VariantBuilder (new VariantType ("ao")); |
1656 | + var builder = new VariantBuilder (VariantType.OBJECT_PATH_ARRAY); |
1657 | builder.add ("o", sink_input_objp); |
1658 | |
1659 | yield _pconn.call ("org.PulseAudio.Core1", "/org/pulseaudio/core1", |
1660 | @@ -393,10 +360,6 @@ |
1661 | var vol = new VolumeControl.Volume(); |
1662 | vol.volume = volume_to_double (volume); |
1663 | vol.reason = VolumeControl.VolumeReasons.VOLUME_STREAM_CHANGE; |
1664 | - // Ignore changes from PULSE to avoid issues with |
1665 | - // some apps that change the volume in the sink |
1666 | - // We only take into account volume changes from the user |
1667 | - this._ignore_warning_this_time = true; |
1668 | this.volume = vol; |
1669 | } catch (GLib.Error e) { |
1670 | warning ("unable to get volume for active role %s (%s)", sink_input_objp, e.message); |
1671 | @@ -407,7 +370,7 @@ |
1672 | private void add_sink_input_into_list (SinkInputInfo sink_input) |
1673 | { |
1674 | /* We're only adding ones that are not corked and with a valid role */ |
1675 | - var role = sink_input.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); |
1676 | + unowned string role = sink_input.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); |
1677 | |
1678 | if (role != null && role in _valid_roles) { |
1679 | if (sink_input.corked == 0 || role == "phone") { |
1680 | @@ -477,7 +440,7 @@ |
1681 | if (i == null) |
1682 | return; |
1683 | |
1684 | - var role = i.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); |
1685 | + unowned string role = i.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); |
1686 | if (role == "phone" || role == "production") |
1687 | this.active_mic = true; |
1688 | } |
1689 | @@ -499,7 +462,7 @@ |
1690 | c.set_subscribe_callback (context_events_cb); |
1691 | update_sink (); |
1692 | update_source (); |
1693 | - this.ready = true; |
1694 | + this.ready = true; // true because we're connected to the pulse server |
1695 | break; |
1696 | |
1697 | case Context.State.FAILED: |
1698 | @@ -518,7 +481,7 @@ |
1699 | { |
1700 | _reconnect_timer = 0; |
1701 | reconnect_to_pulse (); |
1702 | - return false; // G_SOURCE_REMOVE |
1703 | + return Source.REMOVE; |
1704 | } |
1705 | |
1706 | void reconnect_to_pulse () |
1707 | @@ -540,7 +503,7 @@ |
1708 | this.context = new PulseAudio.Context (loop.get_api(), null, props); |
1709 | this.context.set_state_callback (context_state_callback); |
1710 | |
1711 | - var server_string = Environment.get_variable("PULSE_SERVER"); |
1712 | + unowned string server_string = Environment.get_variable("PULSE_SERVER"); |
1713 | if (context.connect(server_string, Context.Flags.NOFAIL, null) < 0) |
1714 | warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno())); |
1715 | } |
1716 | @@ -590,30 +553,19 @@ |
1717 | } |
1718 | } |
1719 | |
1720 | - public override bool is_playing |
1721 | + public override VolumeControl.ActiveOutput active_output() |
1722 | { |
1723 | - get |
1724 | - { |
1725 | - return this._is_playing; |
1726 | - } |
1727 | - } |
1728 | - |
1729 | - public override VolumeControl.ActiveOutput active_output |
1730 | - { |
1731 | - get |
1732 | - { |
1733 | - return _active_output; |
1734 | - } |
1735 | + return _active_output; |
1736 | } |
1737 | |
1738 | /* Volume operations */ |
1739 | - private static PulseAudio.Volume double_to_volume (double vol) |
1740 | + public static PulseAudio.Volume double_to_volume (double vol) |
1741 | { |
1742 | double tmp = (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED) * vol; |
1743 | return (PulseAudio.Volume)tmp + PulseAudio.Volume.MUTED; |
1744 | } |
1745 | |
1746 | - private static double volume_to_double (PulseAudio.Volume vol) |
1747 | + public static double volume_to_double (PulseAudio.Volume vol) |
1748 | { |
1749 | double tmp = (double)(vol - PulseAudio.Volume.MUTED); |
1750 | return tmp / (double)(PulseAudio.Volume.NORM - PulseAudio.Volume.MUTED); |
1751 | @@ -668,8 +620,6 @@ |
1752 | active_role_objp, "org.freedesktop.DBus.Properties", "Set", |
1753 | new Variant ("(ssv)", "org.PulseAudio.Ext.StreamRestore1.RestoreEntry", "Volume", volume), |
1754 | null, DBusCallFlags.NONE, -1); |
1755 | - |
1756 | - debug ("Set volume to %f on path %s", vol, active_role_objp); |
1757 | } catch (GLib.Error e) { |
1758 | lock (_pa_volume_sig_count) { |
1759 | _pa_volume_sig_count--; |
1760 | @@ -687,7 +637,7 @@ |
1761 | void set_mic_volume_get_server_info_cb (PulseAudio.Context c, PulseAudio.ServerInfo? i) { |
1762 | if (i != null) { |
1763 | unowned CVolume cvol = CVolume (); |
1764 | - cvol = vol_set (cvol, 1, double_to_volume (_mic_volume)); |
1765 | + cvol.set (1, double_to_volume (_mic_volume)); |
1766 | c.set_source_volume_by_name (i.default_source_name, cvol, set_mic_volume_success_cb); |
1767 | } |
1768 | } |
1769 | @@ -716,169 +666,8 @@ |
1770 | && volume_changed) { |
1771 | start_local_volume_timer(); |
1772 | } |
1773 | - |
1774 | - update_high_volume(); |
1775 | - } |
1776 | - } |
1777 | - |
1778 | - /** MAX VOLUME PROPERTY **/ |
1779 | - |
1780 | - private void init_max_volume() { |
1781 | - _settings.changed["normal-volume-decibels"].connect(() => update_max_volume()); |
1782 | - _settings.changed["amplified-volume-decibels"].connect(() => update_max_volume()); |
1783 | - _shared_settings.changed["allow-amplified-volume"].connect(() => update_max_volume()); |
1784 | - update_max_volume(); |
1785 | - } |
1786 | - private void update_max_volume () { |
1787 | - var new_max_volume = calculate_max_volume(); |
1788 | - if (max_volume != new_max_volume) { |
1789 | - debug("changing max_volume from %f to %f", this.max_volume, new_max_volume); |
1790 | - max_volume = calculate_max_volume(); |
1791 | - } |
1792 | - } |
1793 | - private double calculate_max_volume () { |
1794 | - unowned string decibel_key = _shared_settings.get_boolean("allow-amplified-volume") |
1795 | - ? "amplified-volume-decibels" |
1796 | - : "normal-volume-decibels"; |
1797 | - var volume_dB = _settings.get_double(decibel_key); |
1798 | - var volume_sw = PulseAudio.Volume.sw_from_dB (volume_dB); |
1799 | - return volume_to_double (volume_sw); |
1800 | - } |
1801 | - |
1802 | - /** HIGH VOLUME PROPERTY **/ |
1803 | - |
1804 | - private bool _warning_volume_enabled; |
1805 | - private double _warning_volume_norms; /* 1.0 == PA_VOLUME_NORM */ |
1806 | - private bool _high_volume = false; |
1807 | - public override bool ignore_high_volume { |
1808 | - get { |
1809 | - if (_ignore_warning_this_time) { |
1810 | - warning("Ignore"); |
1811 | - _ignore_warning_this_time = false; |
1812 | - return true; |
1813 | - } |
1814 | - return false; |
1815 | - } |
1816 | - set { } |
1817 | - } |
1818 | - public override bool high_volume { |
1819 | - get { return this._high_volume; } |
1820 | - private set { this._high_volume = value; } |
1821 | - } |
1822 | - public override bool below_warning_volume { |
1823 | - get { return this._volume.volume < this._warning_volume_norms; } |
1824 | - private set { } |
1825 | - } |
1826 | - private void init_high_volume() { |
1827 | - _settings.changed["warning-volume-enabled"].connect(() => update_high_volume_cache()); |
1828 | - _settings.changed["warning-volume-decibels"].connect(() => update_high_volume_cache()); |
1829 | - update_high_volume_cache(); |
1830 | - } |
1831 | - private void update_high_volume_cache() { |
1832 | - var volume_dB = _settings.get_double ("warning-volume-decibels"); |
1833 | - var volume_sw = PulseAudio.Volume.sw_from_dB (volume_dB); |
1834 | - var volume_norms = volume_to_double (volume_sw); |
1835 | - _warning_volume_norms = volume_norms; |
1836 | - _warning_volume_enabled = _settings.get_boolean("warning-volume-enabled"); |
1837 | - debug("updating high volume cache... enabled %d dB %f sw %lu norm %f", (int)_warning_volume_enabled, volume_dB, volume_sw, volume_norms); |
1838 | - update_high_volume(); |
1839 | - } |
1840 | - private void update_high_volume() { |
1841 | - var new_high_volume = calculate_high_volume(); |
1842 | - if (high_volume != new_high_volume) { |
1843 | - debug("changing high_volume from %d to %d", (int)high_volume, (int)new_high_volume); |
1844 | - high_volume = new_high_volume; |
1845 | - } |
1846 | - } |
1847 | - private bool calculate_high_volume() { |
1848 | - return calculate_high_volume_from_volume(_volume.volume); |
1849 | - } |
1850 | - private bool calculate_high_volume_from_volume(double volume) { |
1851 | - return _active_port_headphone |
1852 | - && _warning_volume_enabled |
1853 | - && volume > _warning_volume_norms |
1854 | - && (stream == "multimedia"); |
1855 | - } |
1856 | - |
1857 | - public override void clamp_to_high_volume() { |
1858 | - if (_high_volume && (_volume.volume > _warning_volume_norms)) { |
1859 | - var vol = new VolumeControl.Volume(); |
1860 | - vol.volume = _volume.volume.clamp(0, _warning_volume_norms); |
1861 | - vol.reason = _volume.reason; |
1862 | - debug("Clamping from %f down to %f", _volume.volume, vol.volume); |
1863 | - volume = vol; |
1864 | - } |
1865 | - } |
1866 | - |
1867 | - public override void set_warning_volume() { |
1868 | - var vol = new VolumeControl.Volume(); |
1869 | - vol.volume = _warning_volume_norms; |
1870 | - vol.reason = _volume.reason; |
1871 | - debug("Setting warning level volume from %f down to %f", _volume.volume, vol.volume); |
1872 | - volume = vol; |
1873 | - } |
1874 | - |
1875 | - /** HIGH VOLUME APPROVED PROPERTY **/ |
1876 | - |
1877 | - private bool _high_volume_approved = false; |
1878 | - private uint _high_volume_approved_timer = 0; |
1879 | - private int64 _high_volume_approved_at = 0; |
1880 | - private int64 _high_volume_approved_ttl_usec = 0; |
1881 | - public override bool high_volume_approved { |
1882 | - get { return this._high_volume_approved; } |
1883 | - private set { this._high_volume_approved = value; } |
1884 | - } |
1885 | - private void init_high_volume_approved() { |
1886 | - _settings.changed["warning-volume-confirmation-ttl"].connect(() => update_high_volume_approved_cache()); |
1887 | - update_high_volume_approved_cache(); |
1888 | - } |
1889 | - private void update_high_volume_approved_cache() { |
1890 | - _high_volume_approved_ttl_usec = _settings.get_int("warning-volume-confirmation-ttl"); |
1891 | - _high_volume_approved_ttl_usec *= 1000000; |
1892 | - |
1893 | - update_high_volume_approved(); |
1894 | - update_high_volume_approved_timer(); |
1895 | - } |
1896 | - private void update_high_volume_approved_timer() { |
1897 | - stop_high_volume_approved_timer(); |
1898 | - if (_high_volume_approved_at != 0) { |
1899 | - int64 expires_at = _high_volume_approved_at + _high_volume_approved_ttl_usec; |
1900 | - int64 now = GLib.get_monotonic_time(); |
1901 | - if (expires_at > now) { |
1902 | - var seconds_left = 1 + ((expires_at - now) / 1000000); |
1903 | - _high_volume_approved_timer = Timeout.add_seconds((uint)seconds_left, on_high_volume_approved_timer); |
1904 | - } |
1905 | - } |
1906 | - } |
1907 | - private void stop_high_volume_approved_timer() { |
1908 | - if (_high_volume_approved_timer != 0) { |
1909 | - Source.remove (_high_volume_approved_timer); |
1910 | - _high_volume_approved_timer = 0; |
1911 | - } |
1912 | - } |
1913 | - private bool on_high_volume_approved_timer() { |
1914 | - _high_volume_approved_timer = 0; |
1915 | - update_high_volume_approved(); |
1916 | - return false; /* Source.REMOVE */ |
1917 | - } |
1918 | - private void update_high_volume_approved() { |
1919 | - var new_high_volume_approved = calculate_high_volume_approved(); |
1920 | - if (high_volume_approved != new_high_volume_approved) { |
1921 | - debug("changing high_volume_approved from %d to %d", (int)high_volume_approved, (int)new_high_volume_approved); |
1922 | - high_volume_approved = new_high_volume_approved; |
1923 | - } |
1924 | - } |
1925 | - private bool calculate_high_volume_approved() { |
1926 | - int64 now = GLib.get_monotonic_time(); |
1927 | - return (_high_volume_approved_at != 0) |
1928 | - && (_high_volume_approved_at + _high_volume_approved_ttl_usec >= now); |
1929 | - } |
1930 | - public override void approve_high_volume() { |
1931 | - _high_volume_approved_at = GLib.get_monotonic_time(); |
1932 | - update_high_volume_approved(); |
1933 | - update_high_volume_approved_timer(); |
1934 | - } |
1935 | - |
1936 | + } |
1937 | + } |
1938 | |
1939 | /** MIC VOLUME PROPERTY */ |
1940 | |
1941 | @@ -895,16 +684,11 @@ |
1942 | } |
1943 | } |
1944 | |
1945 | - /* PulseAudio Dbus (Stream Restore) logic */ |
1946 | - private void reconnect_pulse_dbus () |
1947 | + public static DBusConnection? create_pulse_dbus_connection() |
1948 | { |
1949 | unowned string pulse_dbus_server_env = Environment.get_variable ("PULSE_DBUS_SERVER"); |
1950 | string address; |
1951 | |
1952 | - /* In case of a reconnect */ |
1953 | - _pulse_use_stream_restore = false; |
1954 | - _pa_volume_sig_count = 0; |
1955 | - |
1956 | if (pulse_dbus_server_env != null) { |
1957 | address = pulse_dbus_server_env; |
1958 | } else { |
1959 | @@ -915,7 +699,7 @@ |
1960 | conn = Bus.get_sync (BusType.SESSION); |
1961 | } catch (GLib.IOError e) { |
1962 | warning ("unable to get the dbus session bus: %s", e.message); |
1963 | - return; |
1964 | + return null; |
1965 | } |
1966 | |
1967 | try { |
1968 | @@ -927,27 +711,41 @@ |
1969 | address = props.get_string (); |
1970 | } catch (GLib.Error e) { |
1971 | warning ("unable to get pulse unix socket: %s", e.message); |
1972 | - return; |
1973 | + return null; |
1974 | } |
1975 | } |
1976 | |
1977 | - debug ("PulseAudio dbus unix socket: %s", address); |
1978 | + DBusConnection conn = null; |
1979 | try { |
1980 | - _pconn = new DBusConnection.for_address_sync (address, DBusConnectionFlags.AUTHENTICATION_CLIENT); |
1981 | + conn = new DBusConnection.for_address_sync (address, DBusConnectionFlags.AUTHENTICATION_CLIENT); |
1982 | } catch (GLib.Error e) { |
1983 | + GLib.warning("Unable to connect to dbus server at '%s': %s", address, e.message); |
1984 | /* If it fails, it means the dbus pulse extension is not available */ |
1985 | + } |
1986 | + GLib.debug ("PulseAudio dbus address is '%s', connection is '%p'", address, conn); |
1987 | + return conn; |
1988 | + } |
1989 | + |
1990 | + /* PulseAudio Dbus (Stream Restore) logic */ |
1991 | + private void reconnect_pulse_dbus () |
1992 | + { |
1993 | + /* In case of a reconnect */ |
1994 | + _pulse_use_stream_restore = false; |
1995 | + _pa_volume_sig_count = 0; |
1996 | + |
1997 | + _pconn = create_pulse_dbus_connection(); |
1998 | + if (_pconn == null) |
1999 | return; |
2000 | - } |
2001 | |
2002 | /* For pulse dbus related events */ |
2003 | _pconn.add_filter (pulse_dbus_filter); |
2004 | |
2005 | /* Check if the 4 currently supported media roles are already available in StreamRestore |
2006 | * Roles: multimedia, alert, alarm and phone */ |
2007 | - _objp_role_multimedia = stream_restore_get_object_path ("sink-input-by-media-role:multimedia"); |
2008 | - _objp_role_alert = stream_restore_get_object_path ("sink-input-by-media-role:alert"); |
2009 | - _objp_role_alarm = stream_restore_get_object_path ("sink-input-by-media-role:alarm"); |
2010 | - _objp_role_phone = stream_restore_get_object_path ("sink-input-by-media-role:phone"); |
2011 | + _objp_role_multimedia = stream_restore_get_object_path (_pconn, "sink-input-by-media-role:multimedia"); |
2012 | + _objp_role_alert = stream_restore_get_object_path (_pconn, "sink-input-by-media-role:alert"); |
2013 | + _objp_role_alarm = stream_restore_get_object_path (_pconn, "sink-input-by-media-role:alarm"); |
2014 | + _objp_role_phone = stream_restore_get_object_path (_pconn, "sink-input-by-media-role:phone"); |
2015 | |
2016 | /* Only use stream restore if every used role is available */ |
2017 | if (_objp_role_multimedia != null && _objp_role_alert != null && _objp_role_alarm != null && _objp_role_phone != null) { |
2018 | @@ -958,10 +756,10 @@ |
2019 | } |
2020 | } |
2021 | |
2022 | - private string? stream_restore_get_object_path (string name) { |
2023 | + public static string? stream_restore_get_object_path (DBusConnection pconn, string name) { |
2024 | string? objp = null; |
2025 | try { |
2026 | - Variant props_variant = _pconn.call_sync ("org.PulseAudio.Ext.StreamRestore1", |
2027 | + Variant props_variant = pconn.call_sync ("org.PulseAudio.Ext.StreamRestore1", |
2028 | "/org/pulseaudio/stream_restore1", "org.PulseAudio.Ext.StreamRestore1", |
2029 | "GetEntryByName", new Variant ("(s)", name), null, DBusCallFlags.NONE, -1); |
2030 | /* Workaround for older versions of vala that don't provide get_objv */ |
2031 | @@ -977,7 +775,7 @@ |
2032 | /* AccountsService operations */ |
2033 | private void accountsservice_props_changed_cb (DBusProxy proxy, Variant changed_properties, string[]? invalidated_properties) |
2034 | { |
2035 | - Variant volume_variant = changed_properties.lookup_value ("Volume", new VariantType ("d")); |
2036 | + Variant volume_variant = changed_properties.lookup_value ("Volume", VariantType.DOUBLE); |
2037 | if (volume_variant != null) { |
2038 | var volume = volume_variant.get_double (); |
2039 | if (volume >= 0) { |
2040 | @@ -987,7 +785,7 @@ |
2041 | } |
2042 | } |
2043 | |
2044 | - Variant mute_variant = changed_properties.lookup_value ("Muted", new VariantType ("b")); |
2045 | + Variant mute_variant = changed_properties.lookup_value ("Muted", VariantType.BOOLEAN); |
2046 | if (mute_variant != null) { |
2047 | var mute = mute_variant.get_boolean (); |
2048 | set_mute_internal (mute); |
2049 | @@ -1061,7 +859,7 @@ |
2050 | yield setup_user_proxy (); |
2051 | } else { |
2052 | // We are in a user session. We just need our own proxy |
2053 | - var username = Environment.get_variable ("USER"); |
2054 | + unowned string username = Environment.get_variable ("USER"); |
2055 | if (username != "" && username != null) { |
2056 | yield setup_user_proxy (username); |
2057 | } |
2058 | @@ -1128,7 +926,7 @@ |
2059 | _send_next_local_volume = false; |
2060 | start_local_volume_timer (); |
2061 | } |
2062 | - return false; // G_SOURCE_REMOVE |
2063 | + return Source.REMOVE; |
2064 | } |
2065 | |
2066 | private void start_account_service_volume_timer() |
2067 | @@ -1160,6 +958,6 @@ |
2068 | { |
2069 | _accountservice_volume_timer = 0; |
2070 | start_account_service_volume_timer (); |
2071 | - return false; // G_SOURCE_REMOVE |
2072 | + return Source.REMOVE; |
2073 | } |
2074 | } |
2075 | |
2076 | === modified file 'src/volume-control.vala' |
2077 | --- src/volume-control.vala 2016-01-05 15:08:24 +0000 |
2078 | +++ src/volume-control.vala 2016-01-13 20:35:42 +0000 |
2079 | @@ -40,36 +40,39 @@ |
2080 | CALL_MODE |
2081 | } |
2082 | |
2083 | + public enum Stream { |
2084 | + ALERT, |
2085 | + MULTIMEDIA, |
2086 | + ALARM, |
2087 | + PHONE |
2088 | + } |
2089 | + |
2090 | public class Volume : Object { |
2091 | public double volume; |
2092 | public VolumeReasons reason; |
2093 | } |
2094 | |
2095 | - public virtual string stream { get { return ""; } } |
2096 | - public virtual bool ready { get { return false; } set { } } |
2097 | + protected IndicatorSound.Options _options = null; |
2098 | + |
2099 | + public VolumeControl(IndicatorSound.Options options) { |
2100 | + _options = options; |
2101 | + } |
2102 | + |
2103 | + public Stream active_stream { get; protected set; default = Stream.ALERT; } |
2104 | + public bool ready { get; protected set; default = false; } |
2105 | public virtual bool active_mic { get { return false; } set { } } |
2106 | - public virtual bool high_volume { get { return false; } protected set { } } |
2107 | - public virtual bool ignore_high_volume { get { return false; } protected set { } } |
2108 | - public virtual bool below_warning_volume { get { return false; } protected set { } } |
2109 | public virtual bool mute { get { return false; } } |
2110 | - public virtual bool is_playing { get { return false; } } |
2111 | - public virtual VolumeControl.ActiveOutput active_output { get { return VolumeControl.ActiveOutput.SPEAKERS; } } |
2112 | + public bool is_playing { get; protected set; default = false; } |
2113 | private Volume _volume; |
2114 | private double _pre_clamp_volume; |
2115 | public virtual Volume volume { get { return _volume; } set { } } |
2116 | public virtual double mic_volume { get { return 0.0; } set { } } |
2117 | - public virtual double max_volume { get { return 1.0; } protected set { } } |
2118 | - |
2119 | - public virtual bool high_volume_approved { get { return false; } protected set { } } |
2120 | - public virtual void approve_high_volume() { } |
2121 | - public virtual void clamp_to_high_volume() { } |
2122 | - public virtual void set_warning_volume() { } |
2123 | |
2124 | public abstract void set_mute (bool mute); |
2125 | |
2126 | public void set_volume_clamp (double unclamped, VolumeControl.VolumeReasons reason) { |
2127 | var v = new VolumeControl.Volume(); |
2128 | - v.volume = unclamped.clamp (0.0, this.max_volume); |
2129 | + v.volume = unclamped.clamp (0.0, _options.max_volume); |
2130 | v.reason = reason; |
2131 | this.volume = v; |
2132 | _pre_clamp_volume = unclamped; |
2133 | @@ -79,5 +82,6 @@ |
2134 | return _pre_clamp_volume; |
2135 | } |
2136 | |
2137 | + public abstract VolumeControl.ActiveOutput active_output(); |
2138 | public signal void active_output_changed (VolumeControl.ActiveOutput active_output); |
2139 | } |
2140 | |
2141 | === added file 'src/volume-warning-pulse.vala' |
2142 | --- src/volume-warning-pulse.vala 1970-01-01 00:00:00 +0000 |
2143 | +++ src/volume-warning-pulse.vala 2016-01-13 20:35:42 +0000 |
2144 | @@ -0,0 +1,211 @@ |
2145 | +/* |
2146 | + * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- |
2147 | + * |
2148 | + * Copyright 2015 Canonical Ltd. |
2149 | + * |
2150 | + * This program is free software; you can redistribute it and/or modify |
2151 | + * it under the terms of the GNU General Public License as published by |
2152 | + * the Free Software Foundation; version 3. |
2153 | + * |
2154 | + * This program is distributed in the hope that it will be useful, |
2155 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2156 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2157 | + * GNU General Public License for more details. |
2158 | + * |
2159 | + * You should have received a copy of the GNU General Public License |
2160 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2161 | + * |
2162 | + * Authors: |
2163 | + * Charles Kerr <charles.kerr@canonical.com> |
2164 | + */ |
2165 | + |
2166 | +using PulseAudio; |
2167 | + |
2168 | +/** |
2169 | + * A VolumeWarning that uses PulseAudio to |
2170 | + * (a) implement sound_system_set_multimedia_volume() and |
2171 | + * (b) keep the multimedia_active and multimedia_volume properties up-to-date |
2172 | + */ |
2173 | +public class VolumeWarningPulse : VolumeWarning |
2174 | +{ |
2175 | + public VolumeWarningPulse (IndicatorSound.Options options, |
2176 | + PulseAudio.GLibMainLoop pgloop) { |
2177 | + base (options); |
2178 | + |
2179 | + _pgloop = pgloop; |
2180 | + pulse_reconnect (); |
2181 | + } |
2182 | + |
2183 | + ~VolumeWarningPulse () { |
2184 | + clear_timer (ref _pulse_reconnect_timer); |
2185 | + clear_timer (ref _pending_sink_inputs_timer); |
2186 | + pulse_disconnect (); |
2187 | + } |
2188 | + |
2189 | + protected override void preshow () { |
2190 | + /* showing the dialog can change the sink input index (bug #1484589) |
2191 | + * so cache it here for later use in sound_system_set_multimedia_volume() */ |
2192 | + _target_sink_input_index = _multimedia_sink_input_index; |
2193 | + } |
2194 | + |
2195 | + protected override void sound_system_set_multimedia_volume (PulseAudio.Volume volume) { |
2196 | + var index = _target_sink_input_index; |
2197 | + |
2198 | + return_if_fail (_pulse_context != null); |
2199 | + return_if_fail (index != PulseAudio.INVALID_INDEX); |
2200 | + return_if_fail (volume != PulseAudio.Volume.INVALID); |
2201 | + |
2202 | + unowned CVolume cvol = CVolume (); |
2203 | + cvol.set (1, volume); |
2204 | + debug ("setting multimedia (sink_input index %d) volume to %s", (int)index, cvol.to_string ()); |
2205 | + _pulse_context.set_sink_input_volume (index, cvol); |
2206 | + } |
2207 | + |
2208 | + private unowned PulseAudio.GLibMainLoop _pgloop = null; |
2209 | + private PulseAudio.Context _pulse_context = null; |
2210 | + private uint _pulse_reconnect_timer = 0; |
2211 | + private uint _pending_sink_inputs_timer = 0; |
2212 | + private GenericSet<uint32> _pending_sink_inputs = new GenericSet<uint32>(direct_hash, direct_equal); |
2213 | + |
2214 | + private uint soon_interval_msec = 500; |
2215 | + |
2216 | + private uint32 _target_sink_input_index = PulseAudio.INVALID_INDEX; |
2217 | + private uint32 _multimedia_sink_input_index = PulseAudio.INVALID_INDEX; |
2218 | + |
2219 | + /***/ |
2220 | + |
2221 | + private bool is_active_multimedia (SinkInputInfo i) { |
2222 | + return (i.corked == 0) && |
2223 | + (i.proplist.gets(PulseAudio.Proplist.PROP_MEDIA_ROLE) == "multimedia"); |
2224 | + |
2225 | + } |
2226 | + |
2227 | + private void clear_multimedia () { |
2228 | + _multimedia_sink_input_index = PulseAudio.INVALID_INDEX; |
2229 | + multimedia_volume = PulseAudio.Volume.INVALID; |
2230 | + multimedia_active = false; |
2231 | + } |
2232 | + |
2233 | + private void on_sink_input_info (Context c, SinkInputInfo? i, int eol) { |
2234 | + |
2235 | + if (i == null) |
2236 | + return; |
2237 | + |
2238 | + if (is_active_multimedia (i)) { |
2239 | + GLib.debug ("on_sink_input_info() setting multimedia sink input index to %d, sink index to %d", (int)i.index, (int)i.sink); |
2240 | + _multimedia_sink_input_index = i.index; |
2241 | + multimedia_volume = i.volume.max (); |
2242 | + multimedia_active = true; |
2243 | + } |
2244 | + else if (i.index == _multimedia_sink_input_index) { |
2245 | + clear_multimedia (); |
2246 | + } |
2247 | + } |
2248 | + |
2249 | + private void update_all_sink_inputs () { |
2250 | + _pulse_context.get_sink_input_info_list (on_sink_input_info); |
2251 | + } |
2252 | + private void update_sink_input (uint32 index) { |
2253 | + _pulse_context.get_sink_input_info (index, on_sink_input_info); |
2254 | + } |
2255 | + |
2256 | + private void update_sink_input_soon (uint32 index) { |
2257 | + |
2258 | + _pending_sink_inputs.add (index); |
2259 | + |
2260 | + if (_pending_sink_inputs_timer == 0) { |
2261 | + _pending_sink_inputs_timer = Timeout.add (soon_interval_msec, () => { |
2262 | + _pending_sink_inputs_timer = 0; |
2263 | + _pending_sink_inputs.foreach ((i) => update_sink_input (i)); |
2264 | + _pending_sink_inputs.remove_all (); |
2265 | + return Source.REMOVE; |
2266 | + }); |
2267 | + } |
2268 | + } |
2269 | + |
2270 | + private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index) { |
2271 | + switch (t & Context.SubscriptionEventType.FACILITY_MASK) |
2272 | + { |
2273 | + case Context.SubscriptionEventType.SINK_INPUT: |
2274 | + switch (t & Context.SubscriptionEventType.TYPE_MASK) |
2275 | + { |
2276 | + // if a SinkInput changed, get its updated info |
2277 | + // to keep our multimedia indices up-to-date |
2278 | + case Context.SubscriptionEventType.NEW: |
2279 | + case Context.SubscriptionEventType.CHANGE: |
2280 | + update_sink_input_soon (index); |
2281 | + break; |
2282 | + |
2283 | + // if the multimedia sink input was removed, |
2284 | + // reset our mm fields and look for a new mm sink input |
2285 | + case Context.SubscriptionEventType.REMOVE: |
2286 | + if (index == _multimedia_sink_input_index) { |
2287 | + clear_multimedia (); |
2288 | + update_all_sink_inputs (); |
2289 | + } |
2290 | + break; |
2291 | + |
2292 | + default: |
2293 | + GLib.debug ("Sink input event not known."); |
2294 | + break; |
2295 | + } |
2296 | + break; |
2297 | + |
2298 | + default: |
2299 | + break; |
2300 | + } |
2301 | + } |
2302 | + |
2303 | + private void pulse_context_state_callback (Context c) { |
2304 | + switch (c.get_state ()) { |
2305 | + case Context.State.READY: |
2306 | + c.set_subscribe_callback (context_events_cb); |
2307 | + c.subscribe (PulseAudio.Context.SubscriptionMask.SINK | |
2308 | + PulseAudio.Context.SubscriptionMask.SINK_INPUT); |
2309 | + update_all_sink_inputs (); |
2310 | + break; |
2311 | + |
2312 | + case Context.State.FAILED: |
2313 | + case Context.State.TERMINATED: |
2314 | + pulse_reconnect_soon (); |
2315 | + break; |
2316 | + |
2317 | + default: |
2318 | + break; |
2319 | + } |
2320 | + } |
2321 | + |
2322 | + private void pulse_disconnect () { |
2323 | + if (_pulse_context != null) { |
2324 | + _pulse_context.disconnect (); |
2325 | + _pulse_context = null; |
2326 | + } |
2327 | + } |
2328 | + |
2329 | + private void pulse_reconnect_soon () { |
2330 | + if (_pulse_reconnect_timer == 0) { |
2331 | + _pulse_reconnect_timer = Timeout.add_seconds (2, () => { |
2332 | + _pulse_reconnect_timer = 0; |
2333 | + pulse_reconnect (); |
2334 | + return Source.REMOVE; |
2335 | + }); |
2336 | + } |
2337 | + } |
2338 | + |
2339 | + void pulse_reconnect () { |
2340 | + pulse_disconnect (); |
2341 | + |
2342 | + var props = new Proplist (); |
2343 | + props.sets (Proplist.PROP_APPLICATION_NAME, "Ubuntu Audio Settings"); |
2344 | + props.sets (Proplist.PROP_APPLICATION_ID, "com.canonical.settings.sound"); |
2345 | + props.sets (Proplist.PROP_APPLICATION_ICON_NAME, "multimedia-volume-control"); |
2346 | + props.sets (Proplist.PROP_APPLICATION_VERSION, "0.1"); |
2347 | + |
2348 | + _pulse_context = new PulseAudio.Context (_pgloop.get_api(), null, props); |
2349 | + _pulse_context.set_state_callback (pulse_context_state_callback); |
2350 | + |
2351 | + unowned string server_string = Environment.get_variable ("PULSE_SERVER"); |
2352 | + if (_pulse_context.connect (server_string, Context.Flags.NOFAIL, null) < 0) |
2353 | + GLib.warning ("pa_context_connect() failed: %s\n", PulseAudio.strerror(_pulse_context.errno())); |
2354 | + } |
2355 | +} |
2356 | |
2357 | === added file 'src/volume-warning.vala' |
2358 | --- src/volume-warning.vala 1970-01-01 00:00:00 +0000 |
2359 | +++ src/volume-warning.vala 2016-01-13 20:35:42 +0000 |
2360 | @@ -0,0 +1,216 @@ |
2361 | +/* |
2362 | + * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- |
2363 | + * |
2364 | + * Copyright 2015 Canonical Ltd. |
2365 | + * |
2366 | + * This program is free software; you can redistribute it and/or modify |
2367 | + * it under the terms of the GNU General Public License as published by |
2368 | + * the Free Software Foundation; version 3. |
2369 | + * |
2370 | + * This program is distributed in the hope that it will be useful, |
2371 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2372 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2373 | + * GNU General Public License for more details. |
2374 | + * |
2375 | + * You should have received a copy of the GNU General Public License |
2376 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2377 | + * |
2378 | + * Authors: |
2379 | + * Charles Kerr <charles.kerr@canonical.com> |
2380 | + */ |
2381 | + |
2382 | +using PulseAudio; |
2383 | + |
2384 | +public abstract class VolumeWarning : Object |
2385 | +{ |
2386 | + // true if headphones are in use |
2387 | + public bool headphones_active { get; set; default = false; } |
2388 | + |
2389 | + // true if the warning dialog is being shown |
2390 | + public bool active { get; protected set; default = false; } |
2391 | + |
2392 | + // true if we're playing unapproved loud multimedia over headphones |
2393 | + public bool high_volume { get; protected set; default = false; } |
2394 | + |
2395 | + public enum Key { |
2396 | + VOLUME_UP, |
2397 | + VOLUME_DOWN |
2398 | + } |
2399 | + |
2400 | + public void user_keypress (Key key) { |
2401 | + if ((key == Key.VOLUME_DOWN) && active) { |
2402 | + _notification.close (); |
2403 | + on_user_response (IndicatorSound.WarnNotification.Response.CANCEL); |
2404 | + } |
2405 | + } |
2406 | + |
2407 | + public VolumeWarning (IndicatorSound.Options options) { |
2408 | + |
2409 | + _options = options; |
2410 | + |
2411 | + init_high_volume (); |
2412 | + init_approved (); |
2413 | + |
2414 | + _notification.user_responded.connect ((n, r) => on_user_response (r)); |
2415 | + } |
2416 | + |
2417 | + ~VolumeWarning () { |
2418 | + clear_timer (ref _approved_timer); |
2419 | + } |
2420 | + |
2421 | + /*** |
2422 | + **** |
2423 | + ***/ |
2424 | + |
2425 | + // true if the user has approved high volumes recently |
2426 | + protected bool approved { get; set; default = false; } |
2427 | + |
2428 | + // true if multimedia is currently playing |
2429 | + protected bool multimedia_active { get; set; default = false; } |
2430 | + |
2431 | + /* Cached value of the multimedia volume reported by pulse. |
2432 | + Setting this only updates the cache -- to change the volume, |
2433 | + use sound_system_set_multimedia_volume. |
2434 | + NB: This PulseAudio.Volume is typed as uint to unconfuse valac. */ |
2435 | + protected uint multimedia_volume { get; set; default = PulseAudio.Volume.INVALID; } |
2436 | + |
2437 | + protected abstract void sound_system_set_multimedia_volume (PulseAudio.Volume volume); |
2438 | + |
2439 | + protected void clear_timer (ref uint timer) { |
2440 | + if (timer != 0) { |
2441 | + Source.remove (timer); |
2442 | + timer = 0; |
2443 | + } |
2444 | + } |
2445 | + |
2446 | + private IndicatorSound.Options _options; |
2447 | + |
2448 | + /** |
2449 | + *** HIGH VOLUME PROPERTY |
2450 | + **/ |
2451 | + |
2452 | + private void init_high_volume () { |
2453 | + const string self_keys[] = { |
2454 | + "multimedia-volume", |
2455 | + "multimedia-active", |
2456 | + "headphones-active", |
2457 | + "high-volume-approved" |
2458 | + }; |
2459 | + foreach (var key in self_keys) |
2460 | + this.notify[key].connect (() => update_high_volume ()); |
2461 | + |
2462 | + const string options_keys[] = { |
2463 | + "loud-volume", |
2464 | + "loud-warning-enabled" |
2465 | + }; |
2466 | + foreach (var key in options_keys) |
2467 | + _options.notify[key].connect (() => update_high_volume ()); |
2468 | + |
2469 | + update_high_volume (); |
2470 | + } |
2471 | + |
2472 | + private void update_high_volume () { |
2473 | + |
2474 | + var newval = _options.loud_warning_enabled |
2475 | + && headphones_active |
2476 | + && multimedia_active |
2477 | + && !approved |
2478 | + && (multimedia_volume != PulseAudio.Volume.INVALID) |
2479 | + && (multimedia_volume >= _options.loud_volume); |
2480 | + |
2481 | + if (high_volume != newval) { |
2482 | + debug ("changing high_volume from %d to %d", (int)high_volume, (int)newval); |
2483 | + if (newval && !active) |
2484 | + activate (); |
2485 | + high_volume = newval; |
2486 | + } |
2487 | + } |
2488 | + |
2489 | + /** |
2490 | + *** HIGH VOLUME APPROVED PROPERTY |
2491 | + **/ |
2492 | + |
2493 | + private Settings _settings = new Settings ("com.canonical.indicator.sound"); |
2494 | + private static const string TTL_KEY = "warning-volume-confirmation-ttl"; |
2495 | + private uint _approved_timer = 0; |
2496 | + private int64 _approved_at = 0; |
2497 | + private int64 _approved_ttl_usec = 0; |
2498 | + |
2499 | + private void approve_high_volume () { |
2500 | + _approved_at = GLib.get_monotonic_time (); |
2501 | + update_approved (); |
2502 | + update_approved_timer (); |
2503 | + } |
2504 | + |
2505 | + private void init_approved () { |
2506 | + _settings.changed[TTL_KEY].connect (() => update_approved_cache ()); |
2507 | + update_approved_cache (); |
2508 | + } |
2509 | + private void update_approved_cache () { |
2510 | + _approved_ttl_usec = _settings.get_int (TTL_KEY); |
2511 | + _approved_ttl_usec *= 1000000; |
2512 | + |
2513 | + update_approved (); |
2514 | + update_approved_timer (); |
2515 | + } |
2516 | + private void update_approved_timer () { |
2517 | + |
2518 | + clear_timer (ref _approved_timer); |
2519 | + |
2520 | + if (_approved_at == 0) |
2521 | + return; |
2522 | + |
2523 | + int64 expires_at = _approved_at + _approved_ttl_usec; |
2524 | + int64 now = GLib.get_monotonic_time (); |
2525 | + if (expires_at > now) { |
2526 | + var seconds_left = 1 + ((expires_at - now) / 1000000); |
2527 | + _approved_timer = Timeout.add_seconds ((uint)seconds_left, () => { |
2528 | + _approved_timer = 0; |
2529 | + update_approved (); |
2530 | + return Source.REMOVE; |
2531 | + }); |
2532 | + } |
2533 | + } |
2534 | + private void update_approved () { |
2535 | + var new_approved = calculate_approved (); |
2536 | + if (approved != new_approved) { |
2537 | + debug ("changing approved from %d to %d", (int)approved, (int)new_approved); |
2538 | + approved = new_approved; |
2539 | + } |
2540 | + } |
2541 | + private bool calculate_approved () { |
2542 | + int64 now = GLib.get_monotonic_time (); |
2543 | + return (_approved_at != 0) |
2544 | + && (_approved_at + _approved_ttl_usec >= now); |
2545 | + } |
2546 | + |
2547 | + // NOTIFICATION |
2548 | + |
2549 | + private IndicatorSound.WarnNotification _notification = new IndicatorSound.WarnNotification (); |
2550 | + private PulseAudio.Volume _ok_volume = PulseAudio.Volume.INVALID; |
2551 | + |
2552 | + protected virtual void preshow () {} |
2553 | + |
2554 | + private void activate () { |
2555 | + preshow (); |
2556 | + _ok_volume = multimedia_volume; |
2557 | + |
2558 | + _notification.show (); |
2559 | + this.active = true; |
2560 | + |
2561 | + // lower the volume to just under the warning level |
2562 | + sound_system_set_multimedia_volume (_options.loud_volume-1); |
2563 | + } |
2564 | + |
2565 | + private void on_user_response (IndicatorSound.WarnNotification.Response response) { |
2566 | + |
2567 | + this.active = false; |
2568 | + |
2569 | + if (response == IndicatorSound.WarnNotification.Response.OK) { |
2570 | + approve_high_volume (); |
2571 | + sound_system_set_multimedia_volume (_ok_volume); |
2572 | + } |
2573 | + |
2574 | + _ok_volume = PulseAudio.Volume.INVALID; |
2575 | + } |
2576 | +} |
2577 | |
2578 | === added file 'src/warn-notification.vala' |
2579 | --- src/warn-notification.vala 1970-01-01 00:00:00 +0000 |
2580 | +++ src/warn-notification.vala 2016-01-13 20:35:42 +0000 |
2581 | @@ -0,0 +1,59 @@ |
2582 | +/* |
2583 | + * Copyright 2015 Canonical Ltd. |
2584 | + * |
2585 | + * This program is free software; you can redistribute it and/or modify |
2586 | + * it under the terms of the GNU General Public License as published by |
2587 | + * the Free Software Foundation; version 3. |
2588 | + * |
2589 | + * This program is distributed in the hope that it will be useful, |
2590 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2591 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2592 | + * GNU General Public License for more details. |
2593 | + * |
2594 | + * You should have received a copy of the GNU General Public License |
2595 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2596 | + * |
2597 | + * Authors: |
2598 | + * Charles Kerr <charles.kerr@canonical.com> |
2599 | + */ |
2600 | + |
2601 | +public class IndicatorSound.WarnNotification: Notification |
2602 | +{ |
2603 | + public enum Response { |
2604 | + CANCEL, |
2605 | + OK |
2606 | + } |
2607 | + |
2608 | + public signal void user_responded (Response response); |
2609 | + |
2610 | + protected override Notify.Notification create_notification () { |
2611 | + var n = new Notify.Notification ( |
2612 | + _("Volume"), |
2613 | + _("High volume can damage your hearing."), |
2614 | + "audio-volume-high"); |
2615 | + n.set_hint ("x-canonical-non-shaped-icon", "true"); |
2616 | + n.set_hint ("x-canonical-snap-decisions", "true"); |
2617 | + n.set_hint ("x-canonical-private-affirmative-tint", "true"); |
2618 | + n.closed.connect ((n) => { |
2619 | + n.clear_actions (); |
2620 | + }); |
2621 | + return n; |
2622 | + } |
2623 | + |
2624 | + public bool show () { |
2625 | + |
2626 | + if (!notify_server_supports ("actions")) |
2627 | + return false; |
2628 | + |
2629 | + _notification.clear_actions (); |
2630 | + _notification.add_action ("ok", _("OK"), (n, a) => { |
2631 | + user_responded (Response.OK); |
2632 | + }); |
2633 | + _notification.add_action ("cancel", _("Cancel"), (n, a) => { |
2634 | + user_responded (Response.CANCEL); |
2635 | + }); |
2636 | + show_notification(); |
2637 | + |
2638 | + return true; |
2639 | + } |
2640 | +} |
2641 | |
2642 | === modified file 'tests/CMakeLists.txt' |
2643 | --- tests/CMakeLists.txt 2016-01-05 15:08:24 +0000 |
2644 | +++ tests/CMakeLists.txt 2016-01-13 20:35:42 +0000 |
2645 | @@ -72,9 +72,17 @@ |
2646 | ) |
2647 | |
2648 | vala_add(vala-mocks |
2649 | + options-mock.vala |
2650 | +) |
2651 | + |
2652 | +vala_add(vala-mocks |
2653 | volume-control-mock.vala |
2654 | ) |
2655 | |
2656 | +vala_add(vala-mocks |
2657 | + volume-warning-mock.vala |
2658 | +) |
2659 | + |
2660 | vala_finish(vala-mocks |
2661 | SOURCES |
2662 | vala_mocks_VALA_SOURCES |
2663 | @@ -169,6 +177,7 @@ |
2664 | target_link_libraries ( |
2665 | volume-control-test |
2666 | indicator-sound-service-lib |
2667 | + vala-mocks-lib |
2668 | pulse-mock |
2669 | gtest-static |
2670 | ${TEST_LIBRARIES} |
2671 | @@ -209,7 +218,7 @@ |
2672 | ${TEST_LIBRARIES} |
2673 | ) |
2674 | |
2675 | -#add_test(notifications-test notifications-test) |
2676 | +add_test(notifications-test notifications-test) |
2677 | |
2678 | ########################### |
2679 | # Accounts Service User |
2680 | |
2681 | === modified file 'tests/integration/indicator-sound-test-base.cpp' |
2682 | --- tests/integration/indicator-sound-test-base.cpp 2016-01-05 15:08:24 +0000 |
2683 | +++ tests/integration/indicator-sound-test-base.cpp 2016-01-13 20:35:42 +0000 |
2684 | @@ -163,13 +163,31 @@ |
2685 | << "com.canonical.indicator.sound" |
2686 | << "interested-media-players" |
2687 | << "[]"); |
2688 | - if (!clearPlayers.waitForStarted()) |
2689 | - return false; |
2690 | - |
2691 | - if (!clearPlayers.waitForFinished()) |
2692 | - return false; |
2693 | - |
2694 | - return clearPlayers.exitCode() == 0; |
2695 | + |
2696 | + return runProcess(clearPlayers); |
2697 | +} |
2698 | + |
2699 | +bool IndicatorSoundTestBase::resetAllowAmplifiedVolume() |
2700 | +{ |
2701 | + QProcess proc; |
2702 | + |
2703 | + proc.start("gsettings", QStringList() |
2704 | + << "reset" |
2705 | + << "com.ubuntu.sound" |
2706 | + << "allow-amplified-volume"); |
2707 | + |
2708 | + return runProcess(proc); |
2709 | +} |
2710 | + |
2711 | +bool IndicatorSoundTestBase::runProcess(QProcess& proc) |
2712 | +{ |
2713 | + if (!proc.waitForStarted()) |
2714 | + return false; |
2715 | + |
2716 | + if (!proc.waitForFinished()) |
2717 | + return false; |
2718 | + |
2719 | + return proc.exitCode() == 0; |
2720 | } |
2721 | |
2722 | bool IndicatorSoundTestBase::startTestMprisPlayer(QString const& playerName) |
2723 | |
2724 | === modified file 'tests/integration/indicator-sound-test-base.h' |
2725 | --- tests/integration/indicator-sound-test-base.h 2016-01-05 15:08:24 +0000 |
2726 | +++ tests/integration/indicator-sound-test-base.h 2016-01-13 20:35:42 +0000 |
2727 | @@ -77,6 +77,8 @@ |
2728 | void startAccountsService(); |
2729 | |
2730 | bool clearGSettingsPlayers(); |
2731 | + bool resetAllowAmplifiedVolume(); |
2732 | + bool runProcess(QProcess&); |
2733 | |
2734 | bool startTestMprisPlayer(QString const& playerName); |
2735 | |
2736 | |
2737 | === modified file 'tests/integration/test-indicator.cpp' |
2738 | --- tests/integration/test-indicator.cpp 2016-01-05 15:08:24 +0000 |
2739 | +++ tests/integration/test-indicator.cpp 2016-01-13 20:35:42 +0000 |
2740 | @@ -32,7 +32,7 @@ |
2741 | { |
2742 | }; |
2743 | |
2744 | -TEST_F(TestIndicator, PhoneChangeRoleVolume) |
2745 | +TEST_F(TestIndicator, DISABLED_PhoneChangeRoleVolume) |
2746 | { |
2747 | double INITIAL_VOLUME = 0.0; |
2748 | |
2749 | @@ -46,11 +46,11 @@ |
2750 | // start now the indicator, so it picks the new volumes |
2751 | ASSERT_NO_THROW(startIndicator()); |
2752 | |
2753 | - // Generate a random volume |
2754 | + // Generate a random volume in the range [0...0.33] |
2755 | QTime now = QTime::currentTime(); |
2756 | qsrand(now.msec()); |
2757 | - int randInt = qrand() % 100; |
2758 | - double randomVolume = randInt / 100.0; |
2759 | + int randInt = qrand() % 33; |
2760 | + const double randomVolume = randInt / 100.0; |
2761 | |
2762 | QSignalSpy &userAccountsSpy = *signal_spy_volume_changed_; |
2763 | // set an initial volume to the alert role |
2764 | @@ -542,6 +542,7 @@ |
2765 | { |
2766 | double INITIAL_VOLUME = 0.0; |
2767 | |
2768 | + EXPECT_TRUE(resetAllowAmplifiedVolume()); |
2769 | ASSERT_NO_THROW(startAccountsService()); |
2770 | ASSERT_NO_THROW(startPulseDesktop()); |
2771 | |
2772 | @@ -556,11 +557,11 @@ |
2773 | // start now the indicator, so it picks the new volumes |
2774 | ASSERT_NO_THROW(startIndicator()); |
2775 | |
2776 | - // Generate a random volume |
2777 | + // Generate a random volume in the range [0...0.33] |
2778 | QTime now = QTime::currentTime(); |
2779 | qsrand(now.msec()); |
2780 | - int randInt = qrand() % 100; |
2781 | - double randomVolume = randInt / 100.0; |
2782 | + int randInt = qrand() % 33; |
2783 | + const double randomVolume = randInt / 100.0; |
2784 | |
2785 | // play a test sound, it should NOT change the role in the indicator |
2786 | EXPECT_TRUE(startTestSound("multimedia")); |
2787 | |
2788 | === modified file 'tests/notifications-mock.h' |
2789 | --- tests/notifications-mock.h 2015-02-12 21:12:38 +0000 |
2790 | +++ tests/notifications-mock.h 2016-01-13 20:35:42 +0000 |
2791 | @@ -30,7 +30,7 @@ |
2792 | DbusTestDbusMockObject * baseobj = nullptr; |
2793 | |
2794 | public: |
2795 | - 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"}) { |
2796 | + NotificationsMock (std::vector<std::string> capabilities = {"actions", "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"}) { |
2797 | mock = dbus_test_dbus_mock_new("org.freedesktop.Notifications"); |
2798 | dbus_test_task_set_bus(DBUS_TEST_TASK(mock), DBUS_TEST_SERVICE_BUS_SESSION); |
2799 | dbus_test_task_set_name(DBUS_TEST_TASK(mock), "Notify"); |
2800 | |
2801 | === modified file 'tests/notifications-test.cc' |
2802 | --- tests/notifications-test.cc 2015-12-23 13:22:35 +0000 |
2803 | +++ tests/notifications-test.cc 2016-01-13 20:35:42 +0000 |
2804 | @@ -1,5 +1,5 @@ |
2805 | /* |
2806 | - * Copyright © 2015 Canonical Ltd. |
2807 | + * Copyright © 2015-2016 Canonical Ltd. |
2808 | * |
2809 | * This program is free software; you can redistribute it and/or modify |
2810 | * it under the terms of the GNU General Public License as published by |
2811 | @@ -15,8 +15,10 @@ |
2812 | * |
2813 | * Authors: |
2814 | * Ted Gould <ted@canonical.com> |
2815 | + * Charles Kerr <charles.kerr@canonical.com> |
2816 | */ |
2817 | |
2818 | +#include <algorithm> |
2819 | #include <memory> |
2820 | |
2821 | #include <gtest/gtest.h> |
2822 | @@ -105,6 +107,39 @@ |
2823 | g_main_loop_unref(loop); |
2824 | } |
2825 | |
2826 | + void loop_until(const std::function<bool()>& test, unsigned int max_ms=50, unsigned int test_interval_ms=10) { |
2827 | + |
2828 | + // g_timeout's callback only allows a single pointer, |
2829 | + // so use a temporary stack struct to wedge everything into one pointer |
2830 | + struct CallbackData { |
2831 | + const std::function<bool()>& test; |
2832 | + const gint64 deadline; |
2833 | + GMainLoop* loop = g_main_loop_new(nullptr, false); |
2834 | + CallbackData (const std::function<bool()>& f, unsigned int max_ms): |
2835 | + test{f}, |
2836 | + deadline{g_get_monotonic_time() + (max_ms*1000)} {} |
2837 | + ~CallbackData() {g_main_loop_unref(loop);} |
2838 | + } data(test, max_ms); |
2839 | + |
2840 | + // tell the timer to stop looping on success or deadline |
2841 | + auto timerfunc = [](gpointer gdata) -> gboolean { |
2842 | + auto& data = *static_cast<CallbackData*>(gdata); |
2843 | + if (!data.test() && (g_get_monotonic_time() < data.deadline)) |
2844 | + return G_SOURCE_CONTINUE; |
2845 | + g_main_loop_quit(data.loop); |
2846 | + return G_SOURCE_REMOVE; |
2847 | + }; |
2848 | + |
2849 | + // start looping |
2850 | + g_timeout_add (std::min(max_ms, test_interval_ms), timerfunc, &data); |
2851 | + g_main_loop_run(data.loop); |
2852 | + } |
2853 | + |
2854 | + void loop_until_notifications(unsigned int max_seconds=1) { |
2855 | + auto test = [this]{ return !notifications->getNotifications().empty(); }; |
2856 | + loop_until(test, max_seconds); |
2857 | + } |
2858 | + |
2859 | static int unref_idle (gpointer user_data) { |
2860 | g_variant_unref(static_cast<GVariant *>(user_data)); |
2861 | return G_SOURCE_REMOVE; |
2862 | @@ -119,18 +154,40 @@ |
2863 | return playerList; |
2864 | } |
2865 | |
2866 | - std::shared_ptr<VolumeControl> volumeControlMock () { |
2867 | + std::shared_ptr<IndicatorSoundOptions> optionsMock () { |
2868 | + auto options = std::shared_ptr<IndicatorSoundOptions>( |
2869 | + INDICATOR_SOUND_OPTIONS(options_mock_new()), |
2870 | + [](IndicatorSoundOptions * options){ |
2871 | + g_clear_object(&options); |
2872 | + }); |
2873 | + return options; |
2874 | + } |
2875 | + |
2876 | + std::shared_ptr<VolumeControl> volumeControlMock (const std::shared_ptr<IndicatorSoundOptions>& optionsMock) { |
2877 | auto volumeControl = std::shared_ptr<VolumeControl>( |
2878 | - VOLUME_CONTROL(volume_control_mock_new()), |
2879 | + VOLUME_CONTROL(volume_control_mock_new(optionsMock.get())), |
2880 | [](VolumeControl * control){ |
2881 | g_clear_object(&control); |
2882 | }); |
2883 | return volumeControl; |
2884 | } |
2885 | |
2886 | - std::shared_ptr<IndicatorSoundService> standardService (std::shared_ptr<VolumeControl> volumeControl, std::shared_ptr<MediaPlayerList> playerList) { |
2887 | + std::shared_ptr<VolumeWarning> volumeWarningMock (const std::shared_ptr<IndicatorSoundOptions>& optionsMock) { |
2888 | + auto volumeWarning = std::shared_ptr<VolumeWarning>( |
2889 | + VOLUME_WARNING(volume_warning_mock_new(optionsMock.get())), |
2890 | + [](VolumeWarning * warning){ |
2891 | + g_clear_object(&warning); |
2892 | + }); |
2893 | + return volumeWarning; |
2894 | + } |
2895 | + |
2896 | + std::shared_ptr<IndicatorSoundService> standardService ( |
2897 | + const std::shared_ptr<VolumeControl>& volumeControl, |
2898 | + const std::shared_ptr<MediaPlayerList>& playerList, |
2899 | + const std::shared_ptr<IndicatorSoundOptions>& options, |
2900 | + const std::shared_ptr<VolumeWarning>& warning) { |
2901 | auto soundService = std::shared_ptr<IndicatorSoundService>( |
2902 | - indicator_sound_service_new(playerList.get(), volumeControl.get(), nullptr), |
2903 | + indicator_sound_service_new(playerList.get(), volumeControl.get(), nullptr, options.get(), warning.get()), |
2904 | [](IndicatorSoundService * service){ |
2905 | g_clear_object(&service); |
2906 | }); |
2907 | @@ -165,10 +222,14 @@ |
2908 | |
2909 | g_clear_object(&bus); |
2910 | } |
2911 | + |
2912 | }; |
2913 | |
2914 | TEST_F(NotificationsTest, BasicObject) { |
2915 | - auto soundService = standardService(volumeControlMock(), playerListMock()); |
2916 | + auto options = optionsMock(); |
2917 | + auto volumeControl = volumeControlMock(options); |
2918 | + auto volumeWarning = volumeWarningMock(options); |
2919 | + auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning); |
2920 | |
2921 | /* Give some time settle */ |
2922 | loop(50); |
2923 | @@ -177,8 +238,10 @@ |
2924 | } |
2925 | |
2926 | TEST_F(NotificationsTest, VolumeChanges) { |
2927 | - auto volumeControl = volumeControlMock(); |
2928 | - auto soundService = standardService(volumeControl, playerListMock()); |
2929 | + auto options = optionsMock(); |
2930 | + auto volumeControl = volumeControlMock(options); |
2931 | + auto volumeWarning = volumeWarningMock(options); |
2932 | + auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning); |
2933 | |
2934 | /* Set a volume */ |
2935 | notifications->clearNotifications(); |
2936 | @@ -216,8 +279,10 @@ |
2937 | } |
2938 | |
2939 | TEST_F(NotificationsTest, StreamChanges) { |
2940 | - auto volumeControl = volumeControlMock(); |
2941 | - auto soundService = standardService(volumeControl, playerListMock()); |
2942 | + auto options = optionsMock(); |
2943 | + auto volumeControl = volumeControlMock(options); |
2944 | + auto volumeWarning = volumeWarningMock(options); |
2945 | + auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning); |
2946 | |
2947 | /* Set a volume */ |
2948 | notifications->clearNotifications(); |
2949 | @@ -228,7 +293,7 @@ |
2950 | |
2951 | /* Change Streams, no volume change */ |
2952 | notifications->clearNotifications(); |
2953 | - volume_control_mock_set_mock_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), "alarm"); |
2954 | + volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_ALARM); |
2955 | setMockVolume(volumeControl, 0.5, VOLUME_CONTROL_VOLUME_REASONS_VOLUME_STREAM_CHANGE); |
2956 | loop(50); |
2957 | notev = notifications->getNotifications(); |
2958 | @@ -236,7 +301,7 @@ |
2959 | |
2960 | /* Change Streams, volume change */ |
2961 | notifications->clearNotifications(); |
2962 | - volume_control_mock_set_mock_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), "alert"); |
2963 | + volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_ALERT); |
2964 | setMockVolume(volumeControl, 0.6, VOLUME_CONTROL_VOLUME_REASONS_VOLUME_STREAM_CHANGE); |
2965 | loop(50); |
2966 | notev = notifications->getNotifications(); |
2967 | @@ -244,7 +309,7 @@ |
2968 | |
2969 | /* Change Streams, no volume change, volume up */ |
2970 | notifications->clearNotifications(); |
2971 | - volume_control_mock_set_mock_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), "multimedia"); |
2972 | + volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_MULTIMEDIA); |
2973 | setMockVolume(volumeControl, 0.6, VOLUME_CONTROL_VOLUME_REASONS_VOLUME_STREAM_CHANGE); |
2974 | loop(50); |
2975 | setMockVolume(volumeControl, 0.65); |
2976 | @@ -254,8 +319,10 @@ |
2977 | } |
2978 | |
2979 | TEST_F(NotificationsTest, IconTesting) { |
2980 | - auto volumeControl = volumeControlMock(); |
2981 | - auto soundService = standardService(volumeControl, playerListMock()); |
2982 | + auto options = optionsMock(); |
2983 | + auto volumeControl = volumeControlMock(options); |
2984 | + auto volumeWarning = volumeWarningMock(options); |
2985 | + auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning); |
2986 | |
2987 | /* Set an initial volume */ |
2988 | notifications->clearNotifications(); |
2989 | @@ -288,8 +355,10 @@ |
2990 | } |
2991 | |
2992 | TEST_F(NotificationsTest, ServerRestart) { |
2993 | - auto volumeControl = volumeControlMock(); |
2994 | - auto soundService = standardService(volumeControl, playerListMock()); |
2995 | + auto options = optionsMock(); |
2996 | + auto volumeControl = volumeControlMock(options); |
2997 | + auto volumeWarning = volumeWarningMock(options); |
2998 | + auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning); |
2999 | |
3000 | /* Set a volume */ |
3001 | notifications->clearNotifications(); |
3002 | @@ -335,8 +404,10 @@ |
3003 | } |
3004 | |
3005 | TEST_F(NotificationsTest, HighVolume) { |
3006 | - auto volumeControl = volumeControlMock(); |
3007 | - auto soundService = standardService(volumeControl, playerListMock()); |
3008 | + auto options = optionsMock(); |
3009 | + auto volumeControl = volumeControlMock(options); |
3010 | + auto volumeWarning = volumeWarningMock(options); |
3011 | + auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning); |
3012 | |
3013 | /* Set a volume */ |
3014 | notifications->clearNotifications(); |
3015 | @@ -350,7 +421,7 @@ |
3016 | |
3017 | /* Set high volume with volume change */ |
3018 | notifications->clearNotifications(); |
3019 | - volume_control_mock_set_high_volume(VOLUME_CONTROL_MOCK(volumeControl.get()), true); |
3020 | + volume_warning_mock_set_high_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), true); |
3021 | setMockVolume(volumeControl, 0.90); |
3022 | loop(50); |
3023 | notev = notifications->getNotifications(); |
3024 | @@ -360,14 +431,14 @@ |
3025 | EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-value-bar-tint"]); |
3026 | |
3027 | /* Move it back */ |
3028 | - volume_control_mock_set_high_volume(VOLUME_CONTROL_MOCK(volumeControl.get()), false); |
3029 | + volume_warning_mock_set_high_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), false); |
3030 | setMockVolume(volumeControl, 0.50); |
3031 | loop(50); |
3032 | |
3033 | /* Set high volume without level change */ |
3034 | /* NOTE: This can happen if headphones are plugged in */ |
3035 | notifications->clearNotifications(); |
3036 | - volume_control_mock_set_high_volume(VOLUME_CONTROL_MOCK(volumeControl.get()), TRUE); |
3037 | + volume_warning_mock_set_high_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), true); |
3038 | loop(50); |
3039 | notev = notifications->getNotifications(); |
3040 | ASSERT_EQ(1, notev.size()); |
3041 | @@ -377,8 +448,10 @@ |
3042 | } |
3043 | |
3044 | TEST_F(NotificationsTest, MenuHide) { |
3045 | - auto volumeControl = volumeControlMock(); |
3046 | - auto soundService = standardService(volumeControl, playerListMock()); |
3047 | + auto options = optionsMock(); |
3048 | + auto volumeControl = volumeControlMock(options); |
3049 | + auto volumeWarning = volumeWarningMock(options); |
3050 | + auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning); |
3051 | |
3052 | /* Set a volume */ |
3053 | notifications->clearNotifications(); |
3054 | @@ -406,9 +479,11 @@ |
3055 | EXPECT_EQ(1, notev.size()); |
3056 | } |
3057 | |
3058 | -TEST_F(NotificationsTest, DISABLED_ExtendendVolumeNotification) { |
3059 | - auto volumeControl = volumeControlMock(); |
3060 | - auto soundService = standardService(volumeControl, playerListMock()); |
3061 | +TEST_F(NotificationsTest, ExtendendVolumeNotification) { |
3062 | + auto options = optionsMock(); |
3063 | + auto volumeControl = volumeControlMock(options); |
3064 | + auto volumeWarning = volumeWarningMock(options); |
3065 | + auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning); |
3066 | |
3067 | /* Set a volume */ |
3068 | notifications->clearNotifications(); |
3069 | @@ -424,7 +499,8 @@ |
3070 | |
3071 | /* Allow an amplified volume */ |
3072 | notifications->clearNotifications(); |
3073 | - //indicator_sound_service_set_allow_amplified_volume(soundService.get(), TRUE); |
3074 | + volume_control_mock_mock_set_active_stream(VOLUME_CONTROL_MOCK(volumeControl.get()), VOLUME_CONTROL_STREAM_ALARM); |
3075 | + options_mock_mock_set_max_volume(OPTIONS_MOCK(options.get()), 1.5); |
3076 | loop(50); |
3077 | notev = notifications->getNotifications(); |
3078 | ASSERT_EQ(1, notev.size()); |
3079 | @@ -440,9 +516,112 @@ |
3080 | |
3081 | /* Put back */ |
3082 | notifications->clearNotifications(); |
3083 | - //indicator_sound_service_set_allow_amplified_volume(soundService.get(), FALSE); |
3084 | + options_mock_mock_set_max_volume(OPTIONS_MOCK(options.get()), 1.0); |
3085 | loop(50); |
3086 | notev = notifications->getNotifications(); |
3087 | ASSERT_EQ(1, notev.size()); |
3088 | EXPECT_GVARIANT_EQ("@i 100", notev[0].hints["value"]); |
3089 | } |
3090 | + |
3091 | +TEST_F(NotificationsTest, TriggerWarning) { |
3092 | + |
3093 | + // Tests all the conditions needed to trigger a volume warning. |
3094 | + // There are many possible combinations, so this test is slow. :P |
3095 | + |
3096 | + const struct { |
3097 | + bool expected; |
3098 | + VolumeControlActiveOutput output; |
3099 | + } test_outputs[] = { |
3100 | + { false, VOLUME_CONTROL_ACTIVE_OUTPUT_SPEAKERS }, |
3101 | + { true, VOLUME_CONTROL_ACTIVE_OUTPUT_HEADPHONES }, |
3102 | + { true, VOLUME_CONTROL_ACTIVE_OUTPUT_BLUETOOTH_HEADPHONES }, |
3103 | + { false, VOLUME_CONTROL_ACTIVE_OUTPUT_BLUETOOTH_SPEAKER }, |
3104 | + { false, VOLUME_CONTROL_ACTIVE_OUTPUT_USB_SPEAKER }, |
3105 | + { true, VOLUME_CONTROL_ACTIVE_OUTPUT_USB_HEADPHONES }, |
3106 | + { false, VOLUME_CONTROL_ACTIVE_OUTPUT_HDMI_SPEAKER }, |
3107 | + { true, VOLUME_CONTROL_ACTIVE_OUTPUT_HDMI_HEADPHONES }, |
3108 | + { false, VOLUME_CONTROL_ACTIVE_OUTPUT_CALL_MODE } |
3109 | + }; |
3110 | + |
3111 | + const struct { |
3112 | + bool expected; |
3113 | + pa_volume_t volume; |
3114 | + pa_volume_t loud_volume; |
3115 | + } test_volumes[] = { |
3116 | + { false, 50, 100 }, |
3117 | + { false, 99, 100 }, |
3118 | + { true, 100, 100 }, |
3119 | + { true, 101, 100 } |
3120 | + }; |
3121 | + |
3122 | + const struct { |
3123 | + bool expected; |
3124 | + bool approved; |
3125 | + } test_approved[] = { |
3126 | + { true, false }, |
3127 | + { false, true } |
3128 | + }; |
3129 | + |
3130 | + const struct { |
3131 | + bool expected; |
3132 | + bool warnings_enabled; |
3133 | + } test_warnings_enabled[] = { |
3134 | + { true, true }, |
3135 | + { false, false } |
3136 | + }; |
3137 | + |
3138 | + const struct { |
3139 | + bool expected; |
3140 | + bool multimedia_active; |
3141 | + } test_multimedia_active[] = { |
3142 | + { true, true }, |
3143 | + { false, false } |
3144 | + }; |
3145 | + |
3146 | + for (const auto& outputs : test_outputs) { |
3147 | + for (const auto& volumes : test_volumes) { |
3148 | + for (const auto& approved : test_approved) { |
3149 | + for (const auto& warnings_enabled : test_warnings_enabled) { |
3150 | + for (const auto& multimedia_active : test_multimedia_active) { |
3151 | + |
3152 | + notifications->clearNotifications(); |
3153 | + |
3154 | + // instantiate the test subjects |
3155 | + auto options = optionsMock(); |
3156 | + auto volumeControl = volumeControlMock(options); |
3157 | + auto volumeWarning = volumeWarningMock(options); |
3158 | + auto soundService = standardService(volumeControl, playerListMock(), options, volumeWarning); |
3159 | + |
3160 | + // run the test |
3161 | + options_mock_mock_set_loud_volume(OPTIONS_MOCK(options.get()), volumes.loud_volume); |
3162 | + options_mock_mock_set_loud_warning_enabled(OPTIONS_MOCK(options.get()), warnings_enabled.warnings_enabled); |
3163 | + volume_warning_mock_set_approved(VOLUME_WARNING_MOCK(volumeWarning.get()), approved.approved); |
3164 | + volume_warning_mock_set_multimedia_volume(VOLUME_WARNING_MOCK(volumeWarning.get()), volumes.volume); |
3165 | + volume_warning_mock_set_multimedia_active(VOLUME_WARNING_MOCK(volumeWarning.get()), multimedia_active.multimedia_active); |
3166 | + volume_control_mock_mock_set_active_output(VOLUME_CONTROL_MOCK(volumeControl.get()), outputs.output); |
3167 | + |
3168 | + loop_until_notifications(); |
3169 | + |
3170 | + // check the result |
3171 | + auto notev = notifications->getNotifications(); |
3172 | + const bool warning_expected = outputs.expected && volumes.expected && approved.expected && warnings_enabled.expected && multimedia_active.expected; |
3173 | + if (warning_expected) { |
3174 | + EXPECT_TRUE(volume_warning_get_active(volumeWarning.get())); |
3175 | + ASSERT_EQ(1, notev.size()); |
3176 | + EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-snap-decisions"]); |
3177 | + EXPECT_GVARIANT_EQ(nullptr, notev[0].hints["x-canonical-private-synchronous"]); |
3178 | + } |
3179 | + else { |
3180 | + EXPECT_FALSE(volume_warning_get_active(volumeWarning.get())); |
3181 | + ASSERT_EQ(1, notev.size()); |
3182 | + EXPECT_GVARIANT_EQ(nullptr, notev[0].hints["x-canonical-snap-decisions"]); |
3183 | + EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-private-synchronous"]); |
3184 | + } |
3185 | + |
3186 | + } // multimedia_active |
3187 | + } // warnings_enabled |
3188 | + } // approved |
3189 | + } // volumes |
3190 | + } // outputs |
3191 | +} |
3192 | + |
3193 | |
3194 | === added file 'tests/options-mock.vala' |
3195 | --- tests/options-mock.vala 1970-01-01 00:00:00 +0000 |
3196 | +++ tests/options-mock.vala 2016-01-13 20:35:42 +0000 |
3197 | @@ -0,0 +1,28 @@ |
3198 | +/* |
3199 | + * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- |
3200 | + * Copyright © 2015 Canonical Ltd. |
3201 | + * |
3202 | + * This program is free software; you can redistribute it and/or modify |
3203 | + * it under the terms of the GNU General Public License as published by |
3204 | + * the Free Software Foundation; version 3. |
3205 | + * |
3206 | + * This program is distributed in the hope that it will be useful, |
3207 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3208 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3209 | + * GNU General Public License for more details. |
3210 | + * |
3211 | + * You should have received a copy of the GNU General Public License |
3212 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3213 | + * |
3214 | + * Authors: |
3215 | + * Charles Kerr <charles.kerr@canonical.com> |
3216 | + */ |
3217 | + |
3218 | +public class OptionsMock : IndicatorSound.Options |
3219 | +{ |
3220 | + public void mock_set_max_volume (double val) { max_volume = val; } |
3221 | + |
3222 | + public void mock_set_loud_volume (PulseAudio.Volume val) { loud_volume = val; } |
3223 | + |
3224 | + public void mock_set_loud_warning_enabled (bool val) { loud_warning_enabled = val; } |
3225 | +} |
3226 | |
3227 | === modified file 'tests/pa-mock.cpp' |
3228 | --- tests/pa-mock.cpp 2015-01-29 14:34:50 +0000 |
3229 | +++ tests/pa-mock.cpp 2016-01-13 20:35:42 +0000 |
3230 | @@ -351,6 +351,25 @@ |
3231 | return dummy_operation(); |
3232 | } |
3233 | |
3234 | +pa_operation* |
3235 | +pa_context_get_sink_input_info_list(pa_context *c, pa_sink_input_info_cb_t cb, void *userdata) |
3236 | +{ |
3237 | + reinterpret_cast<PAMockContext*>(c)->idleOnce( |
3238 | + [c, cb, userdata]() { |
3239 | + |
3240 | + pa_sink_input_info sink_input; |
3241 | + sink_input.name = "default-sink-input"; |
3242 | + sink_input.proplist = nullptr; |
3243 | + sink_input.has_volume = false; |
3244 | + |
3245 | + if (cb != nullptr) |
3246 | + cb(c, &sink_input, true, userdata); |
3247 | + }); |
3248 | + |
3249 | + return dummy_operation(); |
3250 | +} |
3251 | + |
3252 | + |
3253 | /* ******************************* |
3254 | * subscribe.h |
3255 | * *******************************/ |
3256 | |
3257 | === modified file 'tests/volume-control-mock.vala' |
3258 | --- tests/volume-control-mock.vala 2015-08-11 22:19:05 +0000 |
3259 | +++ tests/volume-control-mock.vala 2016-01-13 20:35:42 +0000 |
3260 | @@ -20,27 +20,33 @@ |
3261 | |
3262 | public class VolumeControlMock : VolumeControl |
3263 | { |
3264 | - private bool _high_volume = false; |
3265 | - public override bool high_volume { get { return _high_volume; } protected set { _high_volume = value; } } |
3266 | - public void set_high_volume(bool b) { high_volume = b; } |
3267 | - |
3268 | - public string mock_stream { get; set; default = "multimedia"; } |
3269 | - public override string stream { get { return mock_stream; } } |
3270 | - public override bool ready { get; set; } |
3271 | + public void mock_set_is_ready(bool b) { ready = b; } |
3272 | + public void mock_set_active_stream(VolumeControl.Stream s) { active_stream = s; } |
3273 | + public void mock_set_is_playing(bool b) { is_playing = b; } |
3274 | public override bool active_mic { get; set; } |
3275 | public bool mock_mute { get; set; } |
3276 | public override bool mute { get { return mock_mute; } } |
3277 | - public bool mock_is_playing { get; set; } |
3278 | - public override bool is_playing { get { return mock_is_playing; } } |
3279 | private VolumeControl.Volume _vol = new VolumeControl.Volume(); |
3280 | public override VolumeControl.Volume volume { get { return _vol; } set { _vol = value; }} |
3281 | public override double mic_volume { get; set; } |
3282 | |
3283 | public override void set_mute (bool mute) { |
3284 | - |
3285 | - } |
3286 | - |
3287 | - public VolumeControlMock() { |
3288 | + } |
3289 | + |
3290 | + private VolumeControl.ActiveOutput _active_output = VolumeControl.ActiveOutput.SPEAKERS; |
3291 | + |
3292 | + public override VolumeControl.ActiveOutput active_output() { |
3293 | + return _active_output; |
3294 | + } |
3295 | + |
3296 | + public void mock_set_active_output (VolumeControl.ActiveOutput val) { |
3297 | + _active_output = val; |
3298 | + this.active_output_changed(val); |
3299 | + } |
3300 | + |
3301 | + public VolumeControlMock(IndicatorSound.Options options) { |
3302 | + base(options); |
3303 | + |
3304 | ready = true; |
3305 | this.notify["mock-stream"].connect(() => this.notify_property("stream")); |
3306 | this.notify["mock-high-volume"].connect(() => this.notify_property("high-volume")); |
3307 | |
3308 | === modified file 'tests/volume-control-test.cc' |
3309 | --- tests/volume-control-test.cc 2015-08-07 19:28:41 +0000 |
3310 | +++ tests/volume-control-test.cc 2016-01-13 20:35:42 +0000 |
3311 | @@ -23,6 +23,7 @@ |
3312 | |
3313 | extern "C" { |
3314 | #include "indicator-sound-service.h" |
3315 | +#include "vala-mocks.h" |
3316 | } |
3317 | |
3318 | class VolumeControlTest : public ::testing::Test |
3319 | @@ -75,7 +76,9 @@ |
3320 | }; |
3321 | |
3322 | TEST_F(VolumeControlTest, BasicObject) { |
3323 | - VolumeControlPulse * control = volume_control_pulse_new(); |
3324 | + auto options = options_mock_new(); |
3325 | + auto pgloop = pa_glib_mainloop_new(NULL); |
3326 | + auto control = volume_control_pulse_new(INDICATOR_SOUND_OPTIONS(options), pgloop); |
3327 | |
3328 | /* Setup the PA backend */ |
3329 | loop(100); |
3330 | @@ -84,4 +87,6 @@ |
3331 | EXPECT_TRUE(volume_control_get_ready(VOLUME_CONTROL(control))); |
3332 | |
3333 | g_clear_object(&control); |
3334 | + g_clear_object(&options); |
3335 | + g_clear_pointer(&pgloop, pa_glib_mainloop_free); |
3336 | } |
3337 | |
3338 | === added file 'tests/volume-warning-mock.vala' |
3339 | --- tests/volume-warning-mock.vala 1970-01-01 00:00:00 +0000 |
3340 | +++ tests/volume-warning-mock.vala 2016-01-13 20:35:42 +0000 |
3341 | @@ -0,0 +1,40 @@ |
3342 | +/* |
3343 | + * -*- Mode:Vala; indent-tabs-mode:t; tab-width:4; encoding:utf8 -*- |
3344 | + * Copyright © 2015 Canonical Ltd. |
3345 | + * |
3346 | + * This program is free software; you can redistribute it and/or modify |
3347 | + * it under the terms of the GNU General Public License as published by |
3348 | + * the Free Software Foundation; version 3. |
3349 | + * |
3350 | + * This program is distributed in the hope that it will be useful, |
3351 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3352 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3353 | + * GNU General Public License for more details. |
3354 | + * |
3355 | + * You should have received a copy of the GNU General Public License |
3356 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3357 | + * |
3358 | + * Authors: |
3359 | + * Ted Gould <ted@canonical.com> |
3360 | + */ |
3361 | + |
3362 | +public class VolumeWarningMock : VolumeWarning |
3363 | +{ |
3364 | + public void set_high_volume(bool val) { high_volume = val; } |
3365 | + |
3366 | + public VolumeWarningMock(IndicatorSound.Options options) { |
3367 | + base(options); |
3368 | + } |
3369 | + |
3370 | + protected override void sound_system_set_multimedia_volume(PulseAudio.Volume volume) { |
3371 | + GLib.message("volume-warning-mock setting multimedia volume to %d", (int)volume); |
3372 | + } |
3373 | + |
3374 | + public void set_multimedia_active(bool val) { multimedia_active = val; } |
3375 | + |
3376 | + public void set_multimedia_volume(PulseAudio.Volume val) { multimedia_volume = val; } |
3377 | + |
3378 | + public void set_approved(bool val) { approved = val; } |
3379 | + |
3380 | + public bool is_approved() { return approved; } |
3381 | +} |
FAILED: Continuous integration, rev:610 jenkins. qa.ubuntu. com/job/ indicator- sound-ci/ 297/ jenkins. qa.ubuntu. com/job/ indicator- sound-wily- amd64-ci/ 73/console jenkins. qa.ubuntu. com/job/ indicator- sound-wily- armhf-ci/ 73/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/indicator- sound-ci/ 297/rebuild
http://