Merge lp:~xavi-garcia-mena/indicator-sound/show-microphone-volume-external-mic into lp:indicator-sound/15.10
- show-microphone-volume-external-mic
- Merge into trunk.15.10
Status: | Merged |
---|---|
Approved by: | Xavi Garcia |
Approved revision: | 540 |
Merged at revision: | 532 |
Proposed branch: | lp:~xavi-garcia-mena/indicator-sound/show-microphone-volume-external-mic |
Merge into: | lp:indicator-sound/15.10 |
Prerequisite: | lp:~xavi-garcia-mena/indicator-sound/last-running-player-accounts-service |
Diff against target: |
447 lines (+300/-41) 4 files modified
src/volume-control-pulse.vala (+31/-2) tests/integration/indicator-sound-test-base.cpp (+101/-39) tests/integration/indicator-sound-test-base.h (+6/-0) tests/integration/test-indicator.cpp (+162/-0) |
To merge this branch: | bzr merge lp:~xavi-garcia-mena/indicator-sound/show-microphone-volume-external-mic |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Charles Kerr (community) | Approve | ||
Review via email: mp+287928@code.launchpad.net |
Commit message
Add changes to show up the microphone controls when an external microphone is detected.
Description of the change
This branch adds changes to show up the microphone controls when an external microphone is detected.
PS Jenkins bot (ps-jenkins) wrote : | # |
Charles Kerr (charlesk) wrote : | # |
LGTM. One optional suggestion inline.
Charles Kerr (charlesk) wrote : | # |
Oh, and one question: why no tests?
Xavi Garcia (xavi-garcia-mena) wrote : | # |
Thanks for the review, Charles.
I've added that function.
I'm already working on tests...
I need to find out how to add some pulseaudio logic in order to test sources with certain properties.
We'll need also to add sinks using the microphone, simulating a fake client recording audio...
As we are already testing sink outputs I don't think sinks are going to be an issue.
I hope to finish that soon.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:540
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'src/volume-control-pulse.vala' |
2 | --- src/volume-control-pulse.vala 2016-03-04 15:27:08 +0000 |
3 | +++ src/volume-control-pulse.vala 2016-03-04 15:27:08 +0000 |
4 | @@ -54,6 +54,8 @@ |
5 | private double _account_service_volume = 0.0; |
6 | private VolumeControl.ActiveOutput _active_output = VolumeControl.ActiveOutput.SPEAKERS; |
7 | private AccountsServiceAccess _accounts_service_access; |
8 | + private bool _external_mic_detected = false; |
9 | + private bool _source_sink_mic_activated = false; |
10 | |
11 | /** true when a microphone is active **/ |
12 | public override bool active_mic { get; private set; default = false; } |
13 | @@ -139,6 +141,22 @@ |
14 | return ret_output; |
15 | } |
16 | |
17 | + private bool is_external_mic (SourceInfo? sink) { |
18 | + if (sink.name.contains ("indicator_sound_test_mic")) { |
19 | + return true; |
20 | + } |
21 | + if (sink.active_port != null && |
22 | + ( (sink.active_port.name.contains ("headphone") || |
23 | + sink.active_port.name.contains ("headset") || |
24 | + sink.active_port.name.contains ("mic") ) && |
25 | + (!sink.active_port.name.contains ("internal") && |
26 | + !sink.active_port.name.contains ("builtin")) )) { |
27 | + return true; |
28 | + } |
29 | + return false; |
30 | + } |
31 | + |
32 | + |
33 | /* PulseAudio logic*/ |
34 | private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index) |
35 | { |
36 | @@ -180,7 +198,8 @@ |
37 | break; |
38 | |
39 | case Context.SubscriptionEventType.REMOVE: |
40 | - this.active_mic = false; |
41 | + this._source_sink_mic_activated = false; |
42 | + this.active_mic = _external_mic_detected; |
43 | break; |
44 | } |
45 | break; |
46 | @@ -229,6 +248,14 @@ |
47 | if (i == null) |
48 | return; |
49 | |
50 | + if (is_external_mic (i)) { |
51 | + this.active_mic = true; |
52 | + _external_mic_detected = true; |
53 | + } else { |
54 | + this.active_mic = _source_sink_mic_activated; |
55 | + _external_mic_detected = false; |
56 | + } |
57 | + |
58 | if (_mic_volume != volume_to_double (i.volume.values[0])) |
59 | { |
60 | _mic_volume = volume_to_double (i.volume.values[0]); |
61 | @@ -434,8 +461,10 @@ |
62 | return; |
63 | |
64 | unowned string role = i.proplist.gets (PulseAudio.Proplist.PROP_MEDIA_ROLE); |
65 | - if (role == "phone" || role == "production") |
66 | + if (role == "phone" || role == "production") { |
67 | this.active_mic = true; |
68 | + this._source_sink_mic_activated = true; |
69 | + } |
70 | } |
71 | |
72 | private void context_state_callback (Context c) |
73 | |
74 | === modified file 'tests/integration/indicator-sound-test-base.cpp' |
75 | --- tests/integration/indicator-sound-test-base.cpp 2016-03-04 15:27:08 +0000 |
76 | +++ tests/integration/indicator-sound-test-base.cpp 2016-03-04 15:27:08 +0000 |
77 | @@ -296,6 +296,7 @@ |
78 | << "-n" |
79 | << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort)) |
80 | << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort)) |
81 | + << QString("--load=module-null-sink sink_name=indicator_sound_test_mic") |
82 | << "--log-target=file:/tmp/pulse-daemon.log" |
83 | << "--load=module-dbus-protocol" |
84 | << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1" |
85 | @@ -327,6 +328,7 @@ |
86 | << "-n" |
87 | << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort)) |
88 | << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort)) |
89 | + << QString("--load=module-null-sink sink_name=indicator_sound_test_mic") |
90 | << "--log-target=file:/tmp/pulse-daemon.log" |
91 | << QString("--load=module-stream-restore restore_device=false restore_muted=false fallback_table=%1").arg(STREAM_RESTORE_TABLE) |
92 | << "--load=module-dbus-protocol" |
93 | @@ -411,6 +413,20 @@ |
94 | .pass_through_double_attribute("action", volume); |
95 | } |
96 | |
97 | +unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::micSlider(double volume, QString const &label) |
98 | +{ |
99 | + return mh::MenuItemMatcher() |
100 | + .label(label.toStdString()) |
101 | + .round_doubles(0.1) |
102 | + .double_attribute("min-value", 0.0) |
103 | + .double_attribute("max-value", 1.0) |
104 | + .double_attribute("step", 0.01) |
105 | + .string_attribute("x-canonical-type", "com.canonical.unity.slider") |
106 | + .themed_icon("max-icon", {"audio-input-microphone-high-panel", "audio-input-microphone-high", "audio-input-microphone", "audio-input", "audio"}) |
107 | + .themed_icon("min-icon", {"audio-input-microphone-low-zero-panel", "audio-input-microphone-low-zero", "audio-input-microphone-low", "audio-input-microphone", "audio-input", "audio"}) |
108 | + .pass_through_double_attribute("action", volume); |
109 | +} |
110 | + |
111 | unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::silentModeSwitch(bool toggled) |
112 | { |
113 | return mh::MenuItemMatcher::checkbox() |
114 | @@ -673,51 +689,97 @@ |
115 | return id; |
116 | } |
117 | |
118 | +bool IndicatorSoundTestBase::setDefaultSinkOrSource(bool runForSinks, const QString & active, const QStringList & inactive) |
119 | +{ |
120 | + QString setDefaultCommand = runForSinks ? "set-default-sink" : "set-default-source"; |
121 | + QString suspendCommand = "suspend-sink"; |
122 | + |
123 | + QProcess pacltProcess; |
124 | + |
125 | + QString activeSinkOrSource = runForSinks ? active : active + ".monitor"; |
126 | + |
127 | + pacltProcess.start("pactl", QStringList() << "-s" |
128 | + << "127.0.0.1" |
129 | + << setDefaultCommand |
130 | + << activeSinkOrSource); |
131 | + if (!pacltProcess.waitForStarted()) |
132 | + { |
133 | + return false; |
134 | + } |
135 | + |
136 | + if (!pacltProcess.waitForFinished()) |
137 | + { |
138 | + return false; |
139 | + } |
140 | + |
141 | + pacltProcess.start("pactl", QStringList() << "-s" |
142 | + << "127.0.0.1" |
143 | + << suspendCommand |
144 | + << active |
145 | + << "0"); |
146 | + if (!pacltProcess.waitForStarted()) |
147 | + { |
148 | + return false; |
149 | + } |
150 | + |
151 | + if (!pacltProcess.waitForFinished()) |
152 | + { |
153 | + return false; |
154 | + } |
155 | + |
156 | + if (pacltProcess.exitCode() != 0) |
157 | + { |
158 | + return false; |
159 | + } |
160 | + for (int i = 0; i < inactive.size(); ++i) |
161 | + { |
162 | + pacltProcess.start("pactl", QStringList() << "-s" |
163 | + << "127.0.0.1" |
164 | + << suspendCommand |
165 | + << inactive.at(i) |
166 | + << "1"); |
167 | + if (!pacltProcess.waitForStarted()) |
168 | + { |
169 | + return false; |
170 | + } |
171 | + |
172 | + if (!pacltProcess.waitForFinished()) |
173 | + { |
174 | + return false; |
175 | + } |
176 | + if (pacltProcess.exitCode() != 0) |
177 | + { |
178 | + return false; |
179 | + } |
180 | + } |
181 | + |
182 | + return pacltProcess.exitCode() == 0; |
183 | +} |
184 | + |
185 | bool IndicatorSoundTestBase::activateHeadphones(bool headphonesActive) |
186 | { |
187 | - QProcess pacltProcess; |
188 | - |
189 | QString defaultSinkName = "indicator_sound_test_speaker"; |
190 | - QString suspendedSinkName = "indicator_sound_test_headphones"; |
191 | + QStringList suspendedSinks = { "indicator_sound_test_mic", "indicator_sound_test_headphones" }; |
192 | if (headphonesActive) |
193 | { |
194 | defaultSinkName = "indicator_sound_test_headphones"; |
195 | - suspendedSinkName = "indicator_sound_test_speaker"; |
196 | - } |
197 | - |
198 | - pacltProcess.start("pactl", QStringList() << "-s" |
199 | - << "127.0.0.1" |
200 | - << "set-default-sink" |
201 | - << defaultSinkName); |
202 | - if (!pacltProcess.waitForStarted()) |
203 | - return false; |
204 | - |
205 | - if (!pacltProcess.waitForFinished()) |
206 | - return false; |
207 | - |
208 | - pacltProcess.start("pactl", QStringList() << "-s" |
209 | - << "127.0.0.1" |
210 | - << "suspend-sink" |
211 | - << defaultSinkName |
212 | - << "0"); |
213 | - if (!pacltProcess.waitForStarted()) |
214 | - return false; |
215 | - |
216 | - if (!pacltProcess.waitForFinished()) |
217 | - return false; |
218 | - |
219 | - pacltProcess.start("pactl", QStringList() << "-s" |
220 | - << "127.0.0.1" |
221 | - << "suspend-sink" |
222 | - << suspendedSinkName |
223 | - << "1"); |
224 | - if (!pacltProcess.waitForStarted()) |
225 | - return false; |
226 | - |
227 | - if (!pacltProcess.waitForFinished()) |
228 | - return false; |
229 | - |
230 | - return pacltProcess.exitCode() == 0; |
231 | + suspendedSinks = QStringList{ "indicator_sound_test_speaker", "indicator_sound_test_mic" }; |
232 | + } |
233 | + return setDefaultSinkOrSource(true, defaultSinkName, suspendedSinks); |
234 | +} |
235 | + |
236 | +bool IndicatorSoundTestBase::plugExternalMic(bool activate) |
237 | +{ |
238 | + QString defaultSinkName = "indicator_sound_test_mic"; |
239 | + QStringList suspendedSinks = { "indicator_sound_test_speaker", "indicator_sound_test_headphones" }; |
240 | + |
241 | + if (!activate) |
242 | + { |
243 | + defaultSinkName = "indicator_sound_test_speaker"; |
244 | + suspendedSinks = QStringList{ "indicator_sound_test_mic", "indicator_sound_test_headphones" }; |
245 | + } |
246 | + |
247 | + return setDefaultSinkOrSource(false, defaultSinkName, suspendedSinks); |
248 | } |
249 | |
250 | QString IndicatorSoundTestBase::getDevicePortString(DevicePortType port) |
251 | |
252 | === modified file 'tests/integration/indicator-sound-test-base.h' |
253 | --- tests/integration/indicator-sound-test-base.h 2016-03-04 15:27:08 +0000 |
254 | +++ tests/integration/indicator-sound-test-base.h 2016-03-04 15:27:08 +0000 |
255 | @@ -102,6 +102,8 @@ |
256 | |
257 | static unity::gmenuharness::MenuItemMatcher volumeSlider(double volume, QString const &label); |
258 | |
259 | + static unity::gmenuharness::MenuItemMatcher micSlider(double volume, QString const &label); |
260 | + |
261 | static unity::gmenuharness::MenuItemMatcher silentModeSwitch(bool toggled); |
262 | |
263 | bool waitMenuChange(); |
264 | @@ -132,6 +134,10 @@ |
265 | |
266 | bool activateHeadphones(bool headphonesActive); |
267 | |
268 | + bool plugExternalMic(bool activate); |
269 | + |
270 | + bool setDefaultSinkOrSource(bool runForSinks, const QString & active, const QStringList & inactive); |
271 | + |
272 | QString getDevicePortString(DevicePortType port); |
273 | |
274 | void checkPortDevicesLabels(DevicePortType speakerPort, DevicePortType headphonesPort); |
275 | |
276 | === modified file 'tests/integration/test-indicator.cpp' |
277 | --- tests/integration/test-indicator.cpp 2016-03-04 15:27:08 +0000 |
278 | +++ tests/integration/test-indicator.cpp 2016-03-04 15:27:08 +0000 |
279 | @@ -32,6 +32,168 @@ |
280 | { |
281 | }; |
282 | |
283 | +TEST_F(TestIndicator, PhoneTestExternalMicInOut) |
284 | +{ |
285 | + double INITIAL_VOLUME = 0.0; |
286 | + |
287 | + ASSERT_NO_THROW(startAccountsService()); |
288 | + EXPECT_TRUE(clearGSettingsPlayers()); |
289 | + ASSERT_NO_THROW(startPulsePhone()); |
290 | + |
291 | + // initialize volumes in pulseaudio |
292 | + EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); |
293 | + |
294 | + // start now the indicator, so it picks the new volumes |
295 | + ASSERT_NO_THROW(startIndicator()); |
296 | + |
297 | + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) |
298 | + .item(mh::MenuItemMatcher() |
299 | + .action("indicator.root") |
300 | + .string_attribute("x-canonical-type", "com.canonical.indicator.root") |
301 | + .string_attribute("x-canonical-scroll-action", "indicator.scroll") |
302 | + .string_attribute("x-canonical-secondary-action", "indicator.mute") |
303 | + .string_attribute("submenu-action", "indicator.indicator-shown") |
304 | + .mode(mh::MenuItemMatcher::Mode::all) |
305 | + .submenu() |
306 | + .item(mh::MenuItemMatcher() |
307 | + .section() |
308 | + .item(silentModeSwitch(false)) |
309 | + .item(volumeSlider(INITIAL_VOLUME, "Volume")) |
310 | + ) |
311 | + .item(mh::MenuItemMatcher() |
312 | + .label("Sound Settings…") |
313 | + .action("indicator.phone-settings") |
314 | + ) |
315 | + ).match()); |
316 | + |
317 | + EXPECT_TRUE(plugExternalMic(true)); |
318 | + |
319 | + // check that we have the mic slider now. |
320 | + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) |
321 | + .item(mh::MenuItemMatcher() |
322 | + .action("indicator.root") |
323 | + .string_attribute("x-canonical-type", "com.canonical.indicator.root") |
324 | + .string_attribute("x-canonical-scroll-action", "indicator.scroll") |
325 | + .string_attribute("x-canonical-secondary-action", "indicator.mute") |
326 | + .string_attribute("submenu-action", "indicator.indicator-shown") |
327 | + .mode(mh::MenuItemMatcher::Mode::all) |
328 | + .submenu() |
329 | + .item(mh::MenuItemMatcher() |
330 | + .section() |
331 | + .item(silentModeSwitch(false)) |
332 | + .item(volumeSlider(INITIAL_VOLUME, "Volume")) |
333 | + .item(micSlider(1.0, "Microphone Volume")) |
334 | + ) |
335 | + .item(mh::MenuItemMatcher() |
336 | + .label("Sound Settings…") |
337 | + .action("indicator.phone-settings") |
338 | + ) |
339 | + ).match()); |
340 | + |
341 | + |
342 | + // unplug the external mic |
343 | + EXPECT_TRUE(plugExternalMic(false)); |
344 | + |
345 | + // and check that there's no mic slider |
346 | + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) |
347 | + .item(mh::MenuItemMatcher() |
348 | + .action("indicator.root") |
349 | + .string_attribute("x-canonical-type", "com.canonical.indicator.root") |
350 | + .string_attribute("x-canonical-scroll-action", "indicator.scroll") |
351 | + .string_attribute("x-canonical-secondary-action", "indicator.mute") |
352 | + .string_attribute("submenu-action", "indicator.indicator-shown") |
353 | + .mode(mh::MenuItemMatcher::Mode::all) |
354 | + .submenu() |
355 | + .item(mh::MenuItemMatcher() |
356 | + .section() |
357 | + .item(silentModeSwitch(false)) |
358 | + .item(volumeSlider(INITIAL_VOLUME, "Volume")) |
359 | + ) |
360 | + .item(mh::MenuItemMatcher() |
361 | + .label("Sound Settings…") |
362 | + .action("indicator.phone-settings") |
363 | + ) |
364 | + ).match()); |
365 | +} |
366 | + |
367 | +TEST_F(TestIndicator, DesktopTestExternalMicInOut) |
368 | +{ |
369 | + double INITIAL_VOLUME = 0.0; |
370 | + |
371 | + ASSERT_NO_THROW(startAccountsService()); |
372 | + EXPECT_TRUE(clearGSettingsPlayers()); |
373 | + ASSERT_NO_THROW(startPulseDesktop()); |
374 | + |
375 | + // initialize volumes in pulseaudio |
376 | + EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); |
377 | + EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME)); |
378 | + |
379 | + // start now the indicator, so it picks the new volumes |
380 | + ASSERT_NO_THROW(startIndicator()); |
381 | + |
382 | + EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) |
383 | + .item(mh::MenuItemMatcher() |
384 | + .action("indicator.root") |
385 | + .string_attribute("x-canonical-type", "com.canonical.indicator.root") |
386 | + .string_attribute("x-canonical-secondary-action", "indicator.mute") |
387 | + .mode(mh::MenuItemMatcher::Mode::all) |
388 | + .submenu() |
389 | + .item(mh::MenuItemMatcher() |
390 | + .section() |
391 | + .item(mh::MenuItemMatcher().checkbox() |
392 | + .label("Mute") |
393 | + ) |
394 | + .item(volumeSlider(INITIAL_VOLUME, "Volume")) |
395 | + ) |
396 | + .item(mh::MenuItemMatcher() |
397 | + .label("Sound Settings…") |
398 | + ) |
399 | + ).match()); |
400 | + |
401 | + EXPECT_TRUE(plugExternalMic(true)); |
402 | + |
403 | + EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) |
404 | + .item(mh::MenuItemMatcher() |
405 | + .action("indicator.root") |
406 | + .string_attribute("x-canonical-type", "com.canonical.indicator.root") |
407 | + .string_attribute("x-canonical-secondary-action", "indicator.mute") |
408 | + .mode(mh::MenuItemMatcher::Mode::all) |
409 | + .submenu() |
410 | + .item(mh::MenuItemMatcher() |
411 | + .section() |
412 | + .item(mh::MenuItemMatcher().checkbox() |
413 | + .label("Mute") |
414 | + ) |
415 | + .item(volumeSlider(INITIAL_VOLUME, "Volume")) |
416 | + .item(micSlider(1.0, "Microphone Volume")) |
417 | + ) |
418 | + .item(mh::MenuItemMatcher() |
419 | + .label("Sound Settings…") |
420 | + ) |
421 | + ).match()); |
422 | + |
423 | + EXPECT_TRUE(plugExternalMic(false)); |
424 | + |
425 | + EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) |
426 | + .item(mh::MenuItemMatcher() |
427 | + .action("indicator.root") |
428 | + .string_attribute("x-canonical-type", "com.canonical.indicator.root") |
429 | + .string_attribute("x-canonical-secondary-action", "indicator.mute") |
430 | + .mode(mh::MenuItemMatcher::Mode::all) |
431 | + .submenu() |
432 | + .item(mh::MenuItemMatcher() |
433 | + .section() |
434 | + .item(mh::MenuItemMatcher().checkbox() |
435 | + .label("Mute") |
436 | + ) |
437 | + .item(volumeSlider(INITIAL_VOLUME, "Volume")) |
438 | + ) |
439 | + .item(mh::MenuItemMatcher() |
440 | + .label("Sound Settings…") |
441 | + ) |
442 | + ).match()); |
443 | +} |
444 | + |
445 | TEST_F(TestIndicator, DISABLED_PhoneChangeRoleVolume) |
446 | { |
447 | double INITIAL_VOLUME = 0.0; |
PASSED: Continuous integration, rev:538 jenkins. qa.ubuntu. com/job/ indicator- sound-ci/ 319/ jenkins. qa.ubuntu. com/job/ indicator- sound-wily- amd64-ci/ 94 jenkins. qa.ubuntu. com/job/ indicator- sound-wily- armhf-ci/ 95 jenkins. qa.ubuntu. com/job/ indicator- sound-wily- armhf-ci/ 95/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/indicator- sound-ci/ 319/rebuild
http://