Merge lp:~xavi-garcia-mena/indicator-sound/show-microphone-volume-external-mic into lp:indicator-sound/15.10

Proposed by Xavi Garcia
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
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.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

LGTM. One optional suggestion inline.

review: Approve
Revision history for this message
Charles Kerr (charlesk) wrote :

Oh, and one question: why no tests?

Revision history for this message
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.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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;

Subscribers

People subscribed via source and target branches