Merge lp:~xavi-garcia-mena/indicator-sound/restore-osd-notifications into lp:indicator-sound/15.10

Proposed by Xavi Garcia
Status: Merged
Approved by: Xavi Garcia
Approved revision: 514
Merged at revision: 518
Proposed branch: lp:~xavi-garcia-mena/indicator-sound/restore-osd-notifications
Merge into: lp:indicator-sound/15.10
Diff against target: 7143 lines (+6337/-185)
59 files modified
include/CMakeLists.txt (+1/-0)
include/unity/CMakeLists.txt (+1/-0)
include/unity/gmenuharness/MatchResult.h (+66/-0)
include/unity/gmenuharness/MatchUtils.h (+42/-0)
include/unity/gmenuharness/MenuItemMatcher.h (+143/-0)
include/unity/gmenuharness/MenuMatcher.h (+95/-0)
src/CMakeLists.txt (+12/-9)
src/gmenuharness/CMakeLists.txt (+17/-0)
src/gmenuharness/MatchResult.cpp (+187/-0)
src/gmenuharness/MatchUtils.cpp (+77/-0)
src/gmenuharness/MenuItemMatcher.cpp (+1008/-0)
src/gmenuharness/MenuMatcher.cpp (+208/-0)
src/service.vala (+355/-52)
src/sound-menu.vala (+38/-0)
src/volume-control-pulse.vala (+69/-14)
src/volume-control.vala (+14/-0)
tests/CMakeLists.txt (+114/-107)
tests/dbus-types/CMakeLists.txt (+53/-0)
tests/dbus-types/com.ubuntu.AccountsService.Sound.xml (+9/-0)
tests/dbus-types/dbus-types.h (+48/-0)
tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml (+7/-0)
tests/dbus-types/org.freedesktop.Accounts.xml (+9/-0)
tests/dbus-types/org.freedesktop.DBus.Properties.xml (+22/-0)
tests/dbus-types/org.freedesktop.Notifications.xml (+47/-0)
tests/dbus-types/org.gtk.Actions.xml (+13/-0)
tests/dbus-types/pulseaudio-volume.cpp (+156/-0)
tests/dbus-types/pulseaudio-volume.h (+69/-0)
tests/integration/CMakeLists.txt (+132/-0)
tests/integration/indicator-sound-test-base.cpp (+823/-0)
tests/integration/indicator-sound-test-base.h (+160/-0)
tests/integration/main.cpp (+58/-0)
tests/integration/test-indicator.cpp (+981/-0)
tests/integration/touch-stream-restore.table (+4/-0)
tests/integration/utils/dbus-pulse-volume.cpp (+232/-0)
tests/integration/utils/dbus-pulse-volume.h (+57/-0)
tests/integration/utils/get-volume.cpp (+33/-0)
tests/integration/utils/set-volume.cpp (+36/-0)
tests/notifications-test.cc (+3/-3)
tests/service-mocks/CMakeLists.txt (+2/-0)
tests/service-mocks/DBusPropertiesNotifier.cpp (+41/-0)
tests/service-mocks/DBusPropertiesNotifier.h (+48/-0)
tests/service-mocks/accounts-mock/AccountsDefs.h (+37/-0)
tests/service-mocks/accounts-mock/AccountsMock.cpp (+40/-0)
tests/service-mocks/accounts-mock/AccountsMock.h (+50/-0)
tests/service-mocks/accounts-mock/AccountsServiceSoundMock.cpp (+48/-0)
tests/service-mocks/accounts-mock/AccountsServiceSoundMock.h (+58/-0)
tests/service-mocks/accounts-mock/CMakeLists.txt (+42/-0)
tests/service-mocks/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml (+6/-0)
tests/service-mocks/accounts-mock/main.cpp (+63/-0)
tests/service-mocks/accounts-mock/org.freedesktop.Accounts.Mock.xml (+15/-0)
tests/service-mocks/media-player-mpris-mock/CMakeLists.txt (+63/-0)
tests/service-mocks/media-player-mpris-mock/MediaPlayerMprisDefs.h (+37/-0)
tests/service-mocks/media-player-mpris-mock/MediaPlayerMprisMock.cpp (+103/-0)
tests/service-mocks/media-player-mpris-mock/MediaPlayerMprisMock.h (+77/-0)
tests/service-mocks/media-player-mpris-mock/applications/testplayer1.desktop (+21/-0)
tests/service-mocks/media-player-mpris-mock/main.cpp (+64/-0)
tests/service-mocks/media-player-mpris-mock/org.mpris.MediaPlayer2.Player.xml (+24/-0)
tests/service-mocks/media-player-mpris-mock/org.mpris.MediaPlayer2.xml (+6/-0)
tests/service-mocks/media-player-mpris-mock/player-update.cpp (+93/-0)
To merge this branch: bzr merge lp:~xavi-garcia-mena/indicator-sound/restore-osd-notifications
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Xavi Garcia Approve
Review via email: mp+281290@code.launchpad.net

Commit message

This branch just readds the OSD notifications code, that was reverted in trunk as the corresponding silo was also rolled back after landing.

Description of the change

This branch just readds the OSD notifications code, that was reverted in trunk as the corresponding silo was also rolled back after landing.

To post a comment you must log in.
Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Approving myself as it was already reviewed and approved.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
515. By Xavi Garcia

restore debian/changelog changes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'include'
2=== added file 'include/CMakeLists.txt'
3--- include/CMakeLists.txt 1970-01-01 00:00:00 +0000
4+++ include/CMakeLists.txt 2016-01-05 11:11:34 +0000
5@@ -0,0 +1,1 @@
6+add_subdirectory(unity)
7
8=== added directory 'include/unity'
9=== added file 'include/unity/CMakeLists.txt'
10--- include/unity/CMakeLists.txt 1970-01-01 00:00:00 +0000
11+++ include/unity/CMakeLists.txt 2016-01-05 11:11:34 +0000
12@@ -0,0 +1,1 @@
13+add_subdirectory(gmenuharness)
14
15=== added directory 'include/unity/gmenuharness'
16=== added file 'include/unity/gmenuharness/MatchResult.h'
17--- include/unity/gmenuharness/MatchResult.h 1970-01-01 00:00:00 +0000
18+++ include/unity/gmenuharness/MatchResult.h 2016-01-05 11:11:34 +0000
19@@ -0,0 +1,66 @@
20+/*
21+ * Copyright © 2014 Canonical Ltd.
22+ *
23+ * This program is free software: you can redistribute it and/or modify it
24+ * under the terms of the GNU Lesser General Public License version 3,
25+ * as published by the Free Software Foundation.
26+ *
27+ * This program is distributed in the hope that it will be useful,
28+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
29+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30+ * GNU Lesser General Public License for more details.
31+ *
32+ * You should have received a copy of the GNU Lesser General Public License
33+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
34+ *
35+ * Authored by: Pete Woods <pete.woods@canonical.com>
36+ */
37+
38+#pragma once
39+
40+#include <vector>
41+#include <memory>
42+#include <string>
43+
44+namespace unity
45+{
46+
47+namespace gmenuharness
48+{
49+
50+class MatchResult
51+{
52+public:
53+ MatchResult();
54+
55+ MatchResult(MatchResult&& other);
56+
57+ MatchResult(const MatchResult& other);
58+
59+ MatchResult& operator=(const MatchResult& other);
60+
61+ MatchResult& operator=(MatchResult&& other);
62+
63+ ~MatchResult() = default;
64+
65+ MatchResult createChild() const;
66+
67+ void failure(const std::vector<unsigned int>& location, const std::string& message);
68+
69+ void merge(const MatchResult& other);
70+
71+ bool success() const;
72+
73+ bool hasTimedOut() const;
74+
75+ std::string concat_failures() const;
76+
77+protected:
78+ struct Priv;
79+
80+ std::shared_ptr<Priv> p;
81+};
82+
83+} // namespace gmenuharness
84+
85+} // namespace unity
86
87=== added file 'include/unity/gmenuharness/MatchUtils.h'
88--- include/unity/gmenuharness/MatchUtils.h 1970-01-01 00:00:00 +0000
89+++ include/unity/gmenuharness/MatchUtils.h 2016-01-05 11:11:34 +0000
90@@ -0,0 +1,42 @@
91+/*
92+ * Copyright © 2014 Canonical Ltd.
93+ *
94+ * This program is free software: you can redistribute it and/or modify it
95+ * under the terms of the GNU Lesser General Public License version 3,
96+ * as published by the Free Software Foundation.
97+ *
98+ * This program is distributed in the hope that it will be useful,
99+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
100+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
101+ * GNU Lesser General Public License for more details.
102+ *
103+ * You should have received a copy of the GNU Lesser General Public License
104+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
105+ *
106+ * Authored by: Pete Woods <pete.woods@canonical.com>
107+ */
108+
109+#pragma once
110+
111+#include <memory>
112+#include <string>
113+
114+#include <gio/gio.h>
115+
116+namespace unity
117+{
118+
119+namespace gmenuharness
120+{
121+
122+void waitForCore(GObject* obj, const std::string& signalName, unsigned int timeout = 10);
123+
124+void menuWaitForItems(const std::shared_ptr<GMenuModel>& menu, unsigned int timeout = 10);
125+
126+void g_object_deleter(gpointer object);
127+
128+void gvariant_deleter(GVariant* varptr);
129+
130+} //namespace gmenuharness
131+
132+} // namespace unity
133
134=== added file 'include/unity/gmenuharness/MenuItemMatcher.h'
135--- include/unity/gmenuharness/MenuItemMatcher.h 1970-01-01 00:00:00 +0000
136+++ include/unity/gmenuharness/MenuItemMatcher.h 2016-01-05 11:11:34 +0000
137@@ -0,0 +1,143 @@
138+/*
139+ * Copyright © 2014 Canonical Ltd.
140+ *
141+ * This program is free software: you can redistribute it and/or modify it
142+ * under the terms of the GNU Lesser General Public License version 3,
143+ * as published by the Free Software Foundation.
144+ *
145+ * This program is distributed in the hope that it will be useful,
146+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
147+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
148+ * GNU Lesser General Public License for more details.
149+ *
150+ * You should have received a copy of the GNU Lesser General Public License
151+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
152+ *
153+ * Authored by: Pete Woods <pete.woods@canonical.com>
154+ */
155+
156+#pragma once
157+
158+#include <map>
159+#include <memory>
160+#include <string>
161+
162+#include <gio/gio.h>
163+
164+namespace unity
165+{
166+
167+namespace gmenuharness
168+{
169+
170+class MatchResult;
171+
172+class MenuItemMatcher
173+{
174+public:
175+ enum class Mode
176+ {
177+ all,
178+ starts_with,
179+ ends_with
180+ };
181+
182+ enum class Type
183+ {
184+ plain,
185+ checkbox,
186+ radio
187+ };
188+
189+ static MenuItemMatcher checkbox();
190+
191+ static MenuItemMatcher radio();
192+
193+ MenuItemMatcher();
194+
195+ ~MenuItemMatcher();
196+
197+ MenuItemMatcher(const MenuItemMatcher& other);
198+
199+ MenuItemMatcher(MenuItemMatcher&& other);
200+
201+ MenuItemMatcher& operator=(const MenuItemMatcher& other);
202+
203+ MenuItemMatcher& operator=(MenuItemMatcher&& other);
204+
205+ MenuItemMatcher& type(Type type);
206+
207+ MenuItemMatcher& label(const std::string& label);
208+
209+ MenuItemMatcher& action(const std::string& action);
210+
211+ MenuItemMatcher& state_icons(const std::vector<std::string>& state);
212+
213+ MenuItemMatcher& icon(const std::string& icon);
214+
215+ MenuItemMatcher& themed_icon(const std::string& iconName, const std::vector<std::string>& icons);
216+
217+ MenuItemMatcher& widget(const std::string& widget);
218+
219+ MenuItemMatcher& pass_through_attribute(const std::string& actionName, const std::shared_ptr<GVariant>& value);
220+
221+ MenuItemMatcher& pass_through_boolean_attribute(const std::string& actionName, bool value);
222+
223+ MenuItemMatcher& pass_through_string_attribute(const std::string& actionName, const std::string& value);
224+
225+ MenuItemMatcher& pass_through_double_attribute(const std::string& actionName, double value);
226+
227+ MenuItemMatcher& round_doubles(double maxDifference);
228+
229+ MenuItemMatcher& attribute(const std::string& name, const std::shared_ptr<GVariant>& value);
230+
231+ MenuItemMatcher& boolean_attribute(const std::string& name, bool value);
232+
233+ MenuItemMatcher& string_attribute(const std::string& name, const std::string& value);
234+
235+ MenuItemMatcher& int32_attribute(const std::string& name, int value);
236+
237+ MenuItemMatcher& int64_attribute(const std::string& name, int value);
238+
239+ MenuItemMatcher& double_attribute(const std::string& name, double value);
240+
241+ MenuItemMatcher& attribute_not_set(const std::string& name);
242+
243+ MenuItemMatcher& toggled(bool toggled);
244+
245+ MenuItemMatcher& mode(Mode mode);
246+
247+ MenuItemMatcher& submenu();
248+
249+ MenuItemMatcher& section();
250+
251+ MenuItemMatcher& is_empty();
252+
253+ MenuItemMatcher& has_exactly(std::size_t children);
254+
255+ MenuItemMatcher& item(const MenuItemMatcher& item);
256+
257+ MenuItemMatcher& item(MenuItemMatcher&& item);
258+
259+ MenuItemMatcher& pass_through_activate(const std::string& action, const std::shared_ptr<GVariant>& parameter = nullptr);
260+
261+ MenuItemMatcher& activate(const std::shared_ptr<GVariant>& parameter = nullptr);
262+
263+ MenuItemMatcher& set_pass_through_action_state(const std::string& action, const std::shared_ptr<GVariant>& state);
264+
265+ MenuItemMatcher& set_action_state(const std::shared_ptr<GVariant>& state);
266+
267+ void match(MatchResult& matchResult, const std::vector<unsigned int>& location,
268+ const std::shared_ptr<GMenuModel>& menu,
269+ std::map<std::string, std::shared_ptr<GActionGroup>>& actions,
270+ int index) const;
271+
272+protected:
273+ struct Priv;
274+
275+ std::shared_ptr<Priv> p;
276+};
277+
278+} // namespace gmenuharness
279+
280+} // namespace unity
281
282=== added file 'include/unity/gmenuharness/MenuMatcher.h'
283--- include/unity/gmenuharness/MenuMatcher.h 1970-01-01 00:00:00 +0000
284+++ include/unity/gmenuharness/MenuMatcher.h 2016-01-05 11:11:34 +0000
285@@ -0,0 +1,95 @@
286+/*
287+ * Copyright © 2014 Canonical Ltd.
288+ *
289+ * This program is free software: you can redistribute it and/or modify it
290+ * under the terms of the GNU Lesser General Public License version 3,
291+ * as published by the Free Software Foundation.
292+ *
293+ * This program is distributed in the hope that it will be useful,
294+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
295+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
296+ * GNU Lesser General Public License for more details.
297+ *
298+ * You should have received a copy of the GNU Lesser General Public License
299+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
300+ *
301+ * Authored by: Pete Woods <pete.woods@canonical.com>
302+ */
303+
304+#pragma once
305+
306+#define EXPECT_MATCHRESULT(statement) \
307+do {\
308+ auto result = (statement);\
309+ GTEST_TEST_BOOLEAN_(result.success(), #statement, false, true, \
310+ GTEST_NONFATAL_FAILURE_) << result.concat_failures().c_str(); \
311+} while (0)
312+
313+#include <unity/gmenuharness/MatchResult.h>
314+#include <unity/gmenuharness/MenuItemMatcher.h>
315+
316+#include <memory>
317+#include <vector>
318+
319+namespace unity
320+{
321+
322+namespace gmenuharness
323+{
324+
325+class MenuMatcher
326+{
327+public:
328+ class Parameters
329+ {
330+ public:
331+ Parameters(
332+ const std::string& busName,
333+ const std::vector<std::pair<std::string, std::string>>& actions,
334+ const std::string& menuObjectPath);
335+
336+ ~Parameters();
337+
338+ Parameters(const Parameters& other);
339+
340+ Parameters(Parameters&& other);
341+
342+ Parameters& operator=(const Parameters& other);
343+
344+ Parameters& operator=(Parameters&& other);
345+
346+ protected:
347+ friend MenuMatcher;
348+
349+ struct Priv;
350+
351+ std::shared_ptr<Priv> p;
352+ };
353+
354+ MenuMatcher(const Parameters& parameters);
355+
356+ ~MenuMatcher();
357+
358+ MenuMatcher(const MenuMatcher& other) = delete;
359+
360+ MenuMatcher(MenuMatcher&& other) = delete;
361+
362+ MenuMatcher& operator=(const MenuMatcher& other) = delete;
363+
364+ MenuMatcher& operator=(MenuMatcher&& other) = delete;
365+
366+ MenuMatcher& item(const MenuItemMatcher& item);
367+
368+ MatchResult match() const;
369+
370+ void match(MatchResult& matchResult) const;
371+
372+protected:
373+ struct Priv;
374+
375+ std::shared_ptr<Priv> p;
376+};
377+
378+} // gmenuharness
379+
380+} // unity
381
382=== modified file 'src/CMakeLists.txt'
383--- src/CMakeLists.txt 2015-12-23 11:08:16 +0000
384+++ src/CMakeLists.txt 2016-01-05 11:11:34 +0000
385@@ -8,12 +8,12 @@
386 set(VAPI_PATH "${CMAKE_CURRENT_BINARY_DIR}/indicator-sound-service.vapi")
387
388 vapi_gen(accounts-service
389- LIBRARY
390- accounts-service
391- PACKAGES
392- gio-2.0
393- INPUT
394- /usr/share/gir-1.0/AccountsService-1.0.gir
395+ LIBRARY
396+ accounts-service
397+ PACKAGES
398+ gio-2.0
399+ INPUT
400+ /usr/share/gir-1.0/AccountsService-1.0.gir
401 )
402
403 vala_init(indicator-sound-service
404@@ -70,7 +70,7 @@
405 media-player-user.vala
406 DEPENDS
407 media-player
408- accounts-service-sound-settings
409+ accounts-service-sound-settings
410 greeter-broadcast
411 )
412 vala_add(indicator-sound-service
413@@ -103,6 +103,7 @@
414 sound-menu.vala
415 DEPENDS
416 media-player
417+ volume-control
418 )
419 vala_add(indicator-sound-service
420 accounts-service-user.vala
421@@ -164,8 +165,8 @@
422 )
423
424 add_library(
425- indicator-sound-service-lib STATIC
426- ${INDICATOR_SOUND_SOURCES}
427+ indicator-sound-service-lib STATIC
428+ ${INDICATOR_SOUND_SOURCES}
429 )
430
431 target_link_libraries(
432@@ -206,3 +207,5 @@
433 RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/indicator-sound/
434 )
435
436+# Disable integration tests
437+# add_subdirectory(gmenuharness)
438
439=== added directory 'src/gmenuharness'
440=== added file 'src/gmenuharness/CMakeLists.txt'
441--- src/gmenuharness/CMakeLists.txt 1970-01-01 00:00:00 +0000
442+++ src/gmenuharness/CMakeLists.txt 2016-01-05 11:11:34 +0000
443@@ -0,0 +1,17 @@
444+pkg_check_modules(UNITY_API libunity-api>=0.1.3 REQUIRED)
445+include_directories(${UNITY_API_INCLUDE_DIRS})
446+
447+include_directories("${CMAKE_SOURCE_DIR}/include")
448+
449+add_library(
450+ gmenuharness-shared SHARED
451+ MatchResult.cpp
452+ MatchUtils.cpp
453+ MenuItemMatcher.cpp
454+ MenuMatcher.cpp
455+)
456+
457+target_link_libraries(
458+ gmenuharness-shared
459+ ${GLIB_LDFLAGS}
460+)
461
462=== added file 'src/gmenuharness/MatchResult.cpp'
463--- src/gmenuharness/MatchResult.cpp 1970-01-01 00:00:00 +0000
464+++ src/gmenuharness/MatchResult.cpp 2016-01-05 11:11:34 +0000
465@@ -0,0 +1,187 @@
466+/*
467+ * Copyright © 2014 Canonical Ltd.
468+ *
469+ * This program is free software: you can redistribute it and/or modify it
470+ * under the terms of the GNU Lesser General Public License version 3,
471+ * as published by the Free Software Foundation.
472+ *
473+ * This program is distributed in the hope that it will be useful,
474+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
475+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
476+ * GNU Lesser General Public License for more details.
477+ *
478+ * You should have received a copy of the GNU Lesser General Public License
479+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
480+ *
481+ * Authored by: Pete Woods <pete.woods@canonical.com>
482+ */
483+
484+#include <unity/gmenuharness/MatchResult.h>
485+
486+#include <chrono>
487+#include <map>
488+#include <sstream>
489+#include <iostream>
490+
491+using namespace std;
492+
493+namespace unity
494+{
495+
496+namespace gmenuharness
497+{
498+
499+namespace
500+{
501+
502+
503+static void printLocation(ostream& ss, const vector<unsigned int>& location, bool first)
504+{
505+ for (int i : location)
506+ {
507+ ss << " ";
508+ if (first)
509+ {
510+ ss << i;
511+ }
512+ else
513+ {
514+ ss << " ";
515+ }
516+ }
517+ ss << " ";
518+}
519+
520+struct compare_vector
521+{
522+ bool operator()(const vector<unsigned int>& a,
523+ const vector<unsigned int>& b) const
524+ {
525+ auto p1 = a.begin();
526+ auto p2 = b.begin();
527+
528+ while (p1 != a.end())
529+ {
530+ if (p2 == b.end())
531+ {
532+ return false;
533+ }
534+ if (*p2 > *p1)
535+ {
536+ return true;
537+ }
538+ if (*p1 > *p2)
539+ {
540+ return false;
541+ }
542+
543+ ++p1;
544+ ++p2;
545+ }
546+
547+ if (p2 != b.end())
548+ {
549+ return true;
550+ }
551+
552+ return false;
553+ }
554+};
555+}
556+
557+struct MatchResult::Priv
558+{
559+ bool m_success = true;
560+
561+ map<vector<unsigned int>, vector<string>, compare_vector> m_failures;
562+
563+ chrono::time_point<chrono::system_clock> m_timeout = chrono::system_clock::now() + chrono::seconds(10);
564+};
565+
566+MatchResult::MatchResult() :
567+ p(new Priv)
568+{
569+}
570+
571+MatchResult::MatchResult(MatchResult&& other)
572+{
573+ *this = move(other);
574+}
575+
576+MatchResult::MatchResult(const MatchResult& other) :
577+ p(new Priv)
578+{
579+ *this = other;
580+}
581+
582+MatchResult& MatchResult::operator=(const MatchResult& other)
583+{
584+ p->m_success = other.p->m_success;
585+ p->m_failures= other.p->m_failures;
586+ return *this;
587+}
588+
589+MatchResult& MatchResult::operator=(MatchResult&& other)
590+{
591+ p = move(other.p);
592+ return *this;
593+}
594+
595+MatchResult MatchResult::createChild() const
596+{
597+ MatchResult child;
598+ child.p->m_timeout = p->m_timeout;
599+ return child;
600+}
601+
602+void MatchResult::failure(const vector<unsigned int>& location, const string& message)
603+{
604+ p->m_success = false;
605+ auto it = p->m_failures.find(location);
606+ if (it == p->m_failures.end())
607+ {
608+ it = p->m_failures.insert(make_pair(location, vector<string>())).first;
609+ }
610+ it->second.emplace_back(message);
611+}
612+
613+void MatchResult::merge(const MatchResult& other)
614+{
615+ p->m_success &= other.p->m_success;
616+ for (const auto& e : other.p->m_failures)
617+ {
618+ p->m_failures.insert(make_pair(e.first, e.second));
619+ }
620+}
621+
622+bool MatchResult::success() const
623+{
624+ return p->m_success;
625+}
626+
627+bool MatchResult::hasTimedOut() const
628+{
629+ auto now = chrono::system_clock::now();
630+ return (now >= p->m_timeout);
631+}
632+
633+string MatchResult::concat_failures() const
634+{
635+ stringstream ss;
636+ ss << "Failed expectations:" << endl;
637+ for (const auto& failure : p->m_failures)
638+ {
639+ bool first = true;
640+ for (const string& s: failure.second)
641+ {
642+ printLocation(ss, failure.first, first);
643+ first = false;
644+ ss << s << endl;
645+ }
646+ }
647+ return ss.str();
648+}
649+
650+} // namespace gmenuharness
651+
652+} // namespace unity
653
654=== added file 'src/gmenuharness/MatchUtils.cpp'
655--- src/gmenuharness/MatchUtils.cpp 1970-01-01 00:00:00 +0000
656+++ src/gmenuharness/MatchUtils.cpp 2016-01-05 11:11:34 +0000
657@@ -0,0 +1,77 @@
658+/*
659+ * Copyright © 2014 Canonical Ltd.
660+ *
661+ * This program is free software: you can redistribute it and/or modify it
662+ * under the terms of the GNU Lesser General Public License version 3,
663+ * as published by the Free Software Foundation.
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 Lesser General Public License for more details.
669+ *
670+ * You should have received a copy of the GNU Lesser General Public License
671+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
672+ *
673+ * Authored by: Pete Woods <pete.woods@canonical.com>
674+ */
675+
676+#include <unity/gmenuharness/MatchUtils.h>
677+
678+#include <unity/util/ResourcePtr.h>
679+
680+using namespace std;
681+namespace util = unity::util;
682+
683+namespace unity
684+{
685+
686+namespace gmenuharness
687+{
688+
689+void waitForCore (GObject * obj, const string& signalName, unsigned int timeout) {
690+ shared_ptr<GMainLoop> loop(g_main_loop_new(nullptr, false), &g_main_loop_unref);
691+
692+ /* Our two exit criteria */
693+ util::ResourcePtr<gulong, function<void(gulong)>> signal(
694+ g_signal_connect_swapped(obj, signalName.c_str(),
695+ G_CALLBACK(g_main_loop_quit), loop.get()),
696+ [obj](gulong s)
697+ {
698+ g_signal_handler_disconnect(obj, s);
699+ });
700+
701+ util::ResourcePtr<guint, function<void(guint)>> timer(g_timeout_add(timeout,
702+ [](gpointer user_data) -> gboolean
703+ {
704+ g_main_loop_quit((GMainLoop *)user_data);
705+ return G_SOURCE_CONTINUE;
706+ },
707+ loop.get()),
708+ &g_source_remove);
709+
710+ /* Wait for sync */
711+ g_main_loop_run(loop.get());
712+}
713+
714+void menuWaitForItems(const shared_ptr<GMenuModel>& menu, unsigned int timeout)
715+{
716+ waitForCore(G_OBJECT(menu.get()), "items-changed", timeout);
717+}
718+
719+void g_object_deleter(gpointer object)
720+{
721+ g_clear_object(&object);
722+}
723+
724+void gvariant_deleter(GVariant* varptr)
725+{
726+ if (varptr != nullptr)
727+ {
728+ g_variant_unref(varptr);
729+ }
730+}
731+
732+} // namespace gmenuharness
733+
734+} // namespace unity
735
736=== added file 'src/gmenuharness/MenuItemMatcher.cpp'
737--- src/gmenuharness/MenuItemMatcher.cpp 1970-01-01 00:00:00 +0000
738+++ src/gmenuharness/MenuItemMatcher.cpp 2016-01-05 11:11:34 +0000
739@@ -0,0 +1,1008 @@
740+/*
741+ * Copyright © 2014 Canonical Ltd.
742+ *
743+ * This program is free software: you can redistribute it and/or modify it
744+ * under the terms of the GNU Lesser General Public License version 3,
745+ * as published by the Free Software Foundation.
746+ *
747+ * This program is distributed in the hope that it will be useful,
748+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
749+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
750+ * GNU Lesser General Public License for more details.
751+ *
752+ * You should have received a copy of the GNU Lesser General Public License
753+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
754+ *
755+ * Authored by: Pete Woods <pete.woods@canonical.com>
756+ */
757+
758+#include <unity/gmenuharness/MatchResult.h>
759+#include <unity/gmenuharness/MatchUtils.h>
760+#include <unity/gmenuharness/MenuItemMatcher.h>
761+
762+#include <iostream>
763+#include <vector>
764+#include <map>
765+
766+using namespace std;
767+
768+namespace unity
769+{
770+
771+namespace gmenuharness
772+{
773+
774+namespace
775+{
776+
777+enum class LinkType
778+{
779+ any,
780+ section,
781+ submenu
782+};
783+
784+static string bool_to_string(bool value)
785+{
786+ return value? "true" : "false";
787+}
788+
789+static shared_ptr<GVariant> get_action_group_attribute(const shared_ptr<GActionGroup>& actionGroup, const gchar* attribute)
790+{
791+ shared_ptr<GVariant> value(
792+ g_action_group_get_action_state(actionGroup.get(), attribute),
793+ &gvariant_deleter);
794+ return value;
795+}
796+
797+static shared_ptr<GVariant> get_attribute(const shared_ptr<GMenuItem> menuItem, const gchar* attribute)
798+{
799+ shared_ptr<GVariant> value(
800+ g_menu_item_get_attribute_value(menuItem.get(), attribute, nullptr),
801+ &gvariant_deleter);
802+ return value;
803+}
804+
805+static string get_string_attribute(const shared_ptr<GMenuItem> menuItem, const gchar* attribute)
806+{
807+ string result;
808+ char* temp = nullptr;
809+ if (g_menu_item_get_attribute(menuItem.get(), attribute, "s", &temp))
810+ {
811+ result = temp;
812+ g_free(temp);
813+ }
814+ return result;
815+}
816+
817+static pair<string, string> split_action(const string& action)
818+{
819+ auto index = action.find('.');
820+
821+ if (index == string::npos)
822+ {
823+ return make_pair(string(), action);
824+ }
825+
826+ return make_pair(action.substr(0, index), action.substr(index + 1, action.size()));
827+}
828+
829+static string type_to_string(MenuItemMatcher::Type type)
830+{
831+ switch(type)
832+ {
833+ case MenuItemMatcher::Type::plain:
834+ return "plain";
835+ case MenuItemMatcher::Type::checkbox:
836+ return "checkbox";
837+ case MenuItemMatcher::Type::radio:
838+ return "radio";
839+ }
840+
841+ return string();
842+}
843+}
844+
845+struct MenuItemMatcher::Priv
846+{
847+ void all(MatchResult& matchResult, const vector<unsigned int>& location,
848+ const shared_ptr<GMenuModel>& menu,
849+ map<string, shared_ptr<GActionGroup>>& actions)
850+ {
851+ int count = g_menu_model_get_n_items(menu.get());
852+
853+ if (m_items.size() != (unsigned int) count)
854+ {
855+ matchResult.failure(
856+ location,
857+ "Expected " + to_string(m_items.size())
858+ + " children, but found " + to_string(count));
859+ return;
860+ }
861+
862+ for (size_t i = 0; i < m_items.size(); ++i)
863+ {
864+ const auto& matcher = m_items.at(i);
865+ matcher.match(matchResult, location, menu, actions, i);
866+ }
867+ }
868+
869+ void startsWith(MatchResult& matchResult, const vector<unsigned int>& location,
870+ const shared_ptr<GMenuModel>& menu,
871+ map<string, shared_ptr<GActionGroup>>& actions)
872+ {
873+ int count = g_menu_model_get_n_items(menu.get());
874+ if (m_items.size() > (unsigned int) count)
875+ {
876+ matchResult.failure(
877+ location,
878+ "Expected at least " + to_string(m_items.size())
879+ + " children, but found " + to_string(count));
880+ return;
881+ }
882+
883+ for (size_t i = 0; i < m_items.size(); ++i)
884+ {
885+ const auto& matcher = m_items.at(i);
886+ matcher.match(matchResult, location, menu, actions, i);
887+ }
888+ }
889+
890+ void endsWith(MatchResult& matchResult, const vector<unsigned int>& location,
891+ const shared_ptr<GMenuModel>& menu,
892+ map<string, shared_ptr<GActionGroup>>& actions)
893+ {
894+ int count = g_menu_model_get_n_items(menu.get());
895+ if (m_items.size() > (unsigned int) count)
896+ {
897+ matchResult.failure(
898+ location,
899+ "Expected at least " + to_string(m_items.size())
900+ + " children, but found " + to_string(count));
901+ return;
902+ }
903+
904+ // match the last N items
905+ size_t j;
906+ for (size_t i = count - m_items.size(), j = 0; i < count && j < m_items.size(); ++i, ++j)
907+ {
908+ const auto& matcher = m_items.at(j);
909+ matcher.match(matchResult, location, menu, actions, i);
910+ }
911+ }
912+
913+ Type m_type = Type::plain;
914+
915+ Mode m_mode = Mode::all;
916+
917+ LinkType m_linkType = LinkType::any;
918+
919+ shared_ptr<size_t> m_expectedSize;
920+
921+ shared_ptr<string> m_label;
922+
923+ shared_ptr<string> m_icon;
924+
925+ map<shared_ptr<string>, vector<std::string>> m_themed_icons;
926+
927+ shared_ptr<string> m_action;
928+
929+ vector<std::string> m_state_icons;
930+
931+ vector<pair<string, shared_ptr<GVariant>>> m_attributes;
932+
933+ vector<string> m_not_exist_attributes;
934+
935+ vector<pair<string, shared_ptr<GVariant>>> m_pass_through_attributes;
936+
937+ shared_ptr<bool> m_isToggled;
938+
939+ vector<MenuItemMatcher> m_items;
940+
941+ vector<pair<string, shared_ptr<GVariant>>> m_activations;
942+
943+ vector<pair<string, shared_ptr<GVariant>>> m_setActionStates;
944+
945+ double m_maxDifference = 0.0;
946+};
947+
948+MenuItemMatcher MenuItemMatcher::checkbox()
949+{
950+ MenuItemMatcher matcher;
951+ matcher.type(Type::checkbox);
952+ return matcher;
953+}
954+
955+MenuItemMatcher MenuItemMatcher::radio()
956+{
957+ MenuItemMatcher matcher;
958+ matcher.type(Type::radio);
959+ return matcher;
960+}
961+
962+MenuItemMatcher::MenuItemMatcher() :
963+ p(new Priv)
964+{
965+}
966+
967+MenuItemMatcher::~MenuItemMatcher()
968+{
969+}
970+
971+MenuItemMatcher::MenuItemMatcher(const MenuItemMatcher& other) :
972+ p(new Priv)
973+{
974+ *this = other;
975+}
976+
977+MenuItemMatcher::MenuItemMatcher(MenuItemMatcher&& other)
978+{
979+ *this = move(other);
980+}
981+
982+MenuItemMatcher& MenuItemMatcher::operator=(const MenuItemMatcher& other)
983+{
984+ p->m_type = other.p->m_type;
985+ p->m_mode = other.p->m_mode;
986+ p->m_expectedSize = other.p->m_expectedSize;
987+ p->m_label = other.p->m_label;
988+ p->m_icon = other.p->m_icon;
989+ p->m_themed_icons = other.p->m_themed_icons;
990+ p->m_action = other.p->m_action;
991+ p->m_state_icons = other.p->m_state_icons;
992+ p->m_attributes = other.p->m_attributes;
993+ p->m_not_exist_attributes = other.p->m_not_exist_attributes;
994+ p->m_pass_through_attributes = other.p->m_pass_through_attributes;
995+ p->m_isToggled = other.p->m_isToggled;
996+ p->m_linkType = other.p->m_linkType;
997+ p->m_items = other.p->m_items;
998+ p->m_activations = other.p->m_activations;
999+ p->m_setActionStates = other.p->m_setActionStates;
1000+ p->m_maxDifference = other.p->m_maxDifference;
1001+ return *this;
1002+}
1003+
1004+MenuItemMatcher& MenuItemMatcher::operator=(MenuItemMatcher&& other)
1005+{
1006+ p = move(other.p);
1007+ return *this;
1008+}
1009+
1010+MenuItemMatcher& MenuItemMatcher::type(Type type)
1011+{
1012+ p->m_type = type;
1013+ return *this;
1014+}
1015+
1016+MenuItemMatcher& MenuItemMatcher::label(const string& label)
1017+{
1018+ p->m_label = make_shared<string>(label);
1019+ return *this;
1020+}
1021+
1022+MenuItemMatcher& MenuItemMatcher::action(const string& action)
1023+{
1024+ p->m_action = make_shared<string>(action);
1025+ return *this;
1026+}
1027+
1028+MenuItemMatcher& MenuItemMatcher::state_icons(const std::vector<std::string>& state_icons)
1029+{
1030+ p->m_state_icons = state_icons;
1031+ return *this;
1032+}
1033+
1034+MenuItemMatcher& MenuItemMatcher::icon(const string& icon)
1035+{
1036+ p->m_icon = make_shared<string>(icon);
1037+ return *this;
1038+}
1039+
1040+MenuItemMatcher& MenuItemMatcher::themed_icon(const std::string& iconName, const std::vector<std::string>& icons)
1041+{
1042+ p->m_themed_icons[make_shared<string>(iconName)] = icons;
1043+ return *this;
1044+}
1045+
1046+MenuItemMatcher& MenuItemMatcher::widget(const string& widget)
1047+{
1048+ return string_attribute("x-canonical-type", widget);
1049+}
1050+
1051+MenuItemMatcher& MenuItemMatcher::pass_through_attribute(const string& actionName, const shared_ptr<GVariant>& value)
1052+{
1053+ p->m_pass_through_attributes.emplace_back(actionName, value);
1054+ return *this;
1055+}
1056+
1057+MenuItemMatcher& MenuItemMatcher::pass_through_boolean_attribute(const string& actionName, bool value)
1058+{
1059+ return pass_through_attribute(
1060+ actionName,
1061+ shared_ptr<GVariant>(g_variant_new_boolean(value),
1062+ &gvariant_deleter));
1063+}
1064+
1065+MenuItemMatcher& MenuItemMatcher::pass_through_string_attribute(const string& actionName, const string& value)
1066+{
1067+ return pass_through_attribute(
1068+ actionName,
1069+ shared_ptr<GVariant>(g_variant_new_string(value.c_str()),
1070+ &gvariant_deleter));
1071+}
1072+
1073+MenuItemMatcher& MenuItemMatcher::pass_through_double_attribute(const std::string& actionName, double value)
1074+{
1075+ return pass_through_attribute(
1076+ actionName,
1077+ shared_ptr<GVariant>(g_variant_new_double(value),
1078+ &gvariant_deleter));
1079+}
1080+
1081+MenuItemMatcher& MenuItemMatcher::round_doubles(double maxDifference)
1082+{
1083+ p->m_maxDifference = maxDifference;
1084+ return *this;
1085+}
1086+
1087+MenuItemMatcher& MenuItemMatcher::attribute(const string& name, const shared_ptr<GVariant>& value)
1088+{
1089+ p->m_attributes.emplace_back(name, value);
1090+ return *this;
1091+}
1092+
1093+MenuItemMatcher& MenuItemMatcher::boolean_attribute(const string& name, bool value)
1094+{
1095+ return attribute(
1096+ name,
1097+ shared_ptr<GVariant>(g_variant_new_boolean(value),
1098+ &gvariant_deleter));
1099+}
1100+
1101+MenuItemMatcher& MenuItemMatcher::string_attribute(const string& name, const string& value)
1102+{
1103+ return attribute(
1104+ name,
1105+ shared_ptr<GVariant>(g_variant_new_string(value.c_str()),
1106+ &gvariant_deleter));
1107+}
1108+
1109+MenuItemMatcher& MenuItemMatcher::int32_attribute(const std::string& name, int value)
1110+{
1111+ return attribute(
1112+ name,
1113+ shared_ptr<GVariant>(g_variant_new_int32 (value),
1114+ &gvariant_deleter));
1115+}
1116+
1117+MenuItemMatcher& MenuItemMatcher::int64_attribute(const std::string& name, int value)
1118+{
1119+ return attribute(
1120+ name,
1121+ shared_ptr<GVariant>(g_variant_new_int64 (value),
1122+ &gvariant_deleter));
1123+}
1124+
1125+MenuItemMatcher& MenuItemMatcher::double_attribute(const std::string& name, double value)
1126+{
1127+ return attribute(
1128+ name,
1129+ shared_ptr<GVariant>(g_variant_new_double (value),
1130+ &gvariant_deleter));
1131+}
1132+
1133+MenuItemMatcher& MenuItemMatcher::attribute_not_set(const std::string& name)
1134+{
1135+ p->m_not_exist_attributes.emplace_back (name);
1136+ return *this;
1137+}
1138+
1139+MenuItemMatcher& MenuItemMatcher::toggled(bool isToggled)
1140+{
1141+ p->m_isToggled = make_shared<bool>(isToggled);
1142+ return *this;
1143+}
1144+
1145+MenuItemMatcher& MenuItemMatcher::mode(Mode mode)
1146+{
1147+ p->m_mode = mode;
1148+ return *this;
1149+}
1150+
1151+MenuItemMatcher& MenuItemMatcher::submenu()
1152+{
1153+ p->m_linkType = LinkType::submenu;
1154+ return *this;
1155+}
1156+
1157+MenuItemMatcher& MenuItemMatcher::section()
1158+{
1159+ p->m_linkType = LinkType::section;
1160+ return *this;
1161+}
1162+
1163+MenuItemMatcher& MenuItemMatcher::is_empty()
1164+{
1165+ return has_exactly(0);
1166+}
1167+
1168+MenuItemMatcher& MenuItemMatcher::has_exactly(size_t children)
1169+{
1170+ p->m_expectedSize = make_shared<size_t>(children);
1171+ return *this;
1172+}
1173+
1174+MenuItemMatcher& MenuItemMatcher::item(const MenuItemMatcher& item)
1175+{
1176+ p->m_items.emplace_back(item);
1177+ return *this;
1178+}
1179+
1180+MenuItemMatcher& MenuItemMatcher::item(MenuItemMatcher&& item)
1181+{
1182+ p->m_items.emplace_back(item);
1183+ return *this;
1184+}
1185+
1186+MenuItemMatcher& MenuItemMatcher::pass_through_activate(std::string const& action, const shared_ptr<GVariant>& parameter)
1187+{
1188+ p->m_activations.emplace_back(action, parameter);
1189+ return *this;
1190+}
1191+
1192+MenuItemMatcher& MenuItemMatcher::activate(const shared_ptr<GVariant>& parameter)
1193+{
1194+ p->m_activations.emplace_back(string(), parameter);
1195+ return *this;
1196+}
1197+
1198+MenuItemMatcher& MenuItemMatcher::set_pass_through_action_state(const std::string& action, const std::shared_ptr<GVariant>& state)
1199+{
1200+ p->m_setActionStates.emplace_back(action, state);
1201+ return *this;
1202+}
1203+
1204+MenuItemMatcher& MenuItemMatcher::set_action_state(const std::shared_ptr<GVariant>& state)
1205+{
1206+ p->m_setActionStates.emplace_back("", state);
1207+ return *this;
1208+}
1209+
1210+void MenuItemMatcher::match(
1211+ MatchResult& matchResult,
1212+ const vector<unsigned int>& parentLocation,
1213+ const shared_ptr<GMenuModel>& menu,
1214+ map<string, shared_ptr<GActionGroup>>& actions,
1215+ int index) const
1216+{
1217+ shared_ptr<GMenuItem> menuItem(g_menu_item_new_from_model(menu.get(), index), &g_object_deleter);
1218+
1219+ vector<unsigned int> location(parentLocation);
1220+ location.emplace_back(index);
1221+
1222+ string action = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ACTION);
1223+
1224+ bool isCheckbox = false;
1225+ bool isRadio = false;
1226+ bool isToggled = false;
1227+
1228+ pair<string, string> idPair;
1229+ shared_ptr<GActionGroup> actionGroup;
1230+ shared_ptr<GVariant> state;
1231+
1232+ if (!action.empty())
1233+ {
1234+ idPair = split_action(action);
1235+ actionGroup = actions[idPair.first];
1236+ state = shared_ptr<GVariant>(g_action_group_get_action_state(actionGroup.get(),
1237+ idPair.second.c_str()),
1238+ &gvariant_deleter);
1239+ auto attributeTarget = get_attribute(menuItem, G_MENU_ATTRIBUTE_TARGET);
1240+
1241+ if (attributeTarget && state)
1242+ {
1243+ isToggled = g_variant_equal(state.get(), attributeTarget.get());
1244+ isRadio = true;
1245+ }
1246+ else if (state
1247+ && g_variant_is_of_type(state.get(), G_VARIANT_TYPE_BOOLEAN))
1248+ {
1249+ isToggled = g_variant_get_boolean(state.get());
1250+ isCheckbox = true;
1251+ }
1252+ }
1253+
1254+ Type actualType = Type::plain;
1255+ if (isCheckbox)
1256+ {
1257+ actualType = Type::checkbox;
1258+ }
1259+ else if (isRadio)
1260+ {
1261+ actualType = Type::radio;
1262+ }
1263+
1264+ if (actualType != p->m_type)
1265+ {
1266+ matchResult.failure(
1267+ location,
1268+ "Expected " + type_to_string(p->m_type) + ", found "
1269+ + type_to_string(actualType));
1270+ }
1271+
1272+ // check themed icons
1273+ map<shared_ptr<string>, vector<string>>::iterator iter;
1274+ for (iter = p->m_themed_icons.begin(); iter != p->m_themed_icons.end(); ++iter)
1275+ {
1276+ auto icon_val = g_menu_item_get_attribute_value(menuItem.get(), (*iter).first->c_str(), nullptr);
1277+ if (!icon_val)
1278+ {
1279+ matchResult.failure(
1280+ location,
1281+ "Expected themed icon " + (*(*iter).first) + " was not found");
1282+ }
1283+
1284+ auto gicon = g_icon_deserialize(icon_val);
1285+ if (!gicon || !G_IS_THEMED_ICON(gicon))
1286+ {
1287+ matchResult.failure(
1288+ location,
1289+ "Expected attribute " + (*(*iter).first) + " is not a themed icon");
1290+ }
1291+ auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon));
1292+ int nb_icons = 0;
1293+ while(iconNames[nb_icons])
1294+ {
1295+ ++nb_icons;
1296+ }
1297+
1298+ if (nb_icons != (*iter).second.size())
1299+ {
1300+ matchResult.failure(
1301+ location,
1302+ "Expected " + to_string((*iter).second.size()) +
1303+ " icons for themed icon [" + (*(*iter).first) +
1304+ "], but " + to_string(nb_icons) + " were found.");
1305+ }
1306+ else
1307+ {
1308+ // now compare all the icons
1309+ for (int i = 0; i < nb_icons; ++i)
1310+ {
1311+ if (string(iconNames[i]) != (*iter).second[i])
1312+ {
1313+ matchResult.failure(
1314+ location,
1315+ "Icon at position " + to_string(i) +
1316+ " for themed icon [" + (*(*iter).first) +
1317+ "], mismatchs. Expected: " + iconNames[i] + " but found " + (*iter).second[i]);
1318+ }
1319+ }
1320+ }
1321+ g_object_unref(gicon);
1322+ }
1323+
1324+ string label = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_LABEL);
1325+ if (p->m_label && (*p->m_label) != label)
1326+ {
1327+ matchResult.failure(
1328+ location,
1329+ "Expected label '" + *p->m_label + "', but found '" + label
1330+ + "'");
1331+ }
1332+
1333+ string icon = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ICON);
1334+ if (p->m_icon && (*p->m_icon) != icon)
1335+ {
1336+ matchResult.failure(
1337+ location,
1338+ "Expected icon '" + *p->m_icon + "', but found '" + icon + "'");
1339+ }
1340+
1341+ if (p->m_action && (*p->m_action) != action)
1342+ {
1343+ matchResult.failure(
1344+ location,
1345+ "Expected action '" + *p->m_action + "', but found '" + action
1346+ + "'");
1347+ }
1348+
1349+ if (!p->m_state_icons.empty() && !state)
1350+ {
1351+ matchResult.failure(
1352+ location,
1353+ "Expected state icons but no state was found");
1354+ }
1355+ else if (!p->m_state_icons.empty() && state &&
1356+ !g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT))
1357+ {
1358+ matchResult.failure(
1359+ location,
1360+ "Expected state icons vardict, found "
1361+ + type_to_string(actualType));
1362+ }
1363+ else if (!p->m_state_icons.empty() && state &&
1364+ g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT))
1365+ {
1366+ std::vector<std::string> actual_state_icons;
1367+ GVariantIter it;
1368+ gchar* key;
1369+ GVariant* value;
1370+
1371+ g_variant_iter_init(&it, state.get());
1372+ while (g_variant_iter_loop(&it, "{sv}", &key, &value))
1373+ {
1374+ if (std::string(key) == "icon") {
1375+ auto gicon = g_icon_deserialize(value);
1376+ if (gicon && G_IS_THEMED_ICON(gicon))
1377+ {
1378+ auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon));
1379+ // Just take the first icon in the list (there is only ever one)
1380+ actual_state_icons.push_back(iconNames[0]);
1381+ g_object_unref(gicon);
1382+ }
1383+ }
1384+ else if (std::string(key) == "icons" && g_variant_is_of_type(value, G_VARIANT_TYPE("av")))
1385+ {
1386+ // If we find "icons" in the map, clear any icons we may have found in "icon",
1387+ // then break from the loop as we have found all icons now.
1388+ actual_state_icons.clear();
1389+ GVariantIter icon_it;
1390+ GVariant* icon_value;
1391+
1392+ g_variant_iter_init(&icon_it, value);
1393+ while (g_variant_iter_loop(&icon_it, "v", &icon_value))
1394+ {
1395+ auto gicon = g_icon_deserialize(icon_value);
1396+ if (gicon && G_IS_THEMED_ICON(gicon))
1397+ {
1398+ auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon));
1399+ // Just take the first icon in the list (there is only ever one)
1400+ actual_state_icons.push_back(iconNames[0]);
1401+ g_object_unref(gicon);
1402+ }
1403+ }
1404+ // We're breaking out of g_variant_iter_loop here so clean up
1405+ g_variant_unref(value);
1406+ g_free(key);
1407+ break;
1408+ }
1409+ }
1410+
1411+ if (p->m_state_icons != actual_state_icons)
1412+ {
1413+ std::string expected_icons;
1414+ for (unsigned int i = 0; i < p->m_state_icons.size(); ++i)
1415+ {
1416+ expected_icons += i == 0 ? p->m_state_icons[i] : ", " + p->m_state_icons[i];
1417+ }
1418+ std::string actual_icons;
1419+ for (unsigned int i = 0; i < actual_state_icons.size(); ++i)
1420+ {
1421+ actual_icons += i == 0 ? actual_state_icons[i] : ", " + actual_state_icons[i];
1422+ }
1423+ matchResult.failure(
1424+ location,
1425+ "Expected state_icons == {" + expected_icons
1426+ + "} but found {" + actual_icons + "}");
1427+ }
1428+ }
1429+
1430+ for (const auto& e: p->m_pass_through_attributes)
1431+ {
1432+ string actionName = get_string_attribute(menuItem, e.first.c_str());
1433+ if (actionName.empty())
1434+ {
1435+ matchResult.failure(
1436+ location,
1437+ "Could not find action name '" + e.first + "'");
1438+ }
1439+ else
1440+ {
1441+ auto passThroughIdPair = split_action(actionName);
1442+ auto actionGroup = actions[passThroughIdPair.first];
1443+ if (actionGroup)
1444+ {
1445+ auto value = get_action_group_attribute(
1446+ actionGroup, passThroughIdPair.second.c_str());
1447+ if (!value)
1448+ {
1449+ matchResult.failure(
1450+ location,
1451+ "Expected pass-through attribute '" + e.first
1452+ + "' was not present");
1453+ }
1454+ else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get())))
1455+ {
1456+ std::string expectedType = g_variant_get_type_string(e.second.get());
1457+ std::string actualType = g_variant_get_type_string(value.get());
1458+ matchResult.failure(
1459+ location,
1460+ "Expected pass-through attribute type '" + expectedType
1461+ + "' but found '" + actualType + "'");
1462+ }
1463+ else if (g_variant_compare(e.second.get(), value.get()))
1464+ {
1465+ bool reportMismatch = true;
1466+ if (g_strcmp0(g_variant_get_type_string(value.get()),"d") == 0 && p->m_maxDifference)
1467+ {
1468+ auto actualDouble = g_variant_get_double(value.get());
1469+ auto expectedDouble = g_variant_get_double(e.second.get());
1470+ auto difference = actualDouble-expectedDouble;
1471+ if (difference < 0) difference = difference * -1.0;
1472+ if (difference <= p->m_maxDifference)
1473+ {
1474+ reportMismatch = false;
1475+ }
1476+ }
1477+ if (reportMismatch)
1478+ {
1479+ gchar* expectedString = g_variant_print(e.second.get(), true);
1480+ gchar* actualString = g_variant_print(value.get(), true);
1481+ matchResult.failure(
1482+ location,
1483+ "Expected pass-through attribute '" + e.first
1484+ + "' == " + expectedString + " but found "
1485+ + actualString);
1486+
1487+ g_free(expectedString);
1488+ g_free(actualString);
1489+ }
1490+ }
1491+ }
1492+ else
1493+ {
1494+ matchResult.failure(location, "Could not find action group for ID '" + passThroughIdPair.first + "'");
1495+ }
1496+ }
1497+ }
1498+
1499+ for (const auto& e: p->m_attributes)
1500+ {
1501+ auto value = get_attribute(menuItem, e.first.c_str());
1502+ if (!value)
1503+ {
1504+ matchResult.failure(location,
1505+ "Expected attribute '" + e.first
1506+ + "' could not be found");
1507+ }
1508+ else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get())))
1509+ {
1510+ std::string expectedType = g_variant_get_type_string(e.second.get());
1511+ std::string actualType = g_variant_get_type_string(value.get());
1512+ matchResult.failure(
1513+ location,
1514+ "Expected attribute type '" + expectedType
1515+ + "' but found '" + actualType + "'");
1516+ }
1517+ else if (g_variant_compare(e.second.get(), value.get()))
1518+ {
1519+ gchar* expectedString = g_variant_print(e.second.get(), true);
1520+ gchar* actualString = g_variant_print(value.get(), true);
1521+ matchResult.failure(
1522+ location,
1523+ "Expected attribute '" + e.first + "' == " + expectedString
1524+ + ", but found " + actualString);
1525+ g_free(expectedString);
1526+ g_free(actualString);
1527+ }
1528+ }
1529+
1530+ for (const auto& e: p->m_not_exist_attributes)
1531+ {
1532+ auto value = get_attribute(menuItem, e.c_str());
1533+ if (value)
1534+ {
1535+ matchResult.failure(location,
1536+ "Not expected attribute '" + e
1537+ + "' was found");
1538+ }
1539+ }
1540+
1541+ if (p->m_isToggled && (*p->m_isToggled) != isToggled)
1542+ {
1543+ matchResult.failure(
1544+ location,
1545+ "Expected toggled = " + bool_to_string(*p->m_isToggled)
1546+ + ", but found " + bool_to_string(isToggled));
1547+ }
1548+
1549+ if (!matchResult.success())
1550+ {
1551+ return;
1552+ }
1553+
1554+ if (!p->m_items.empty() || p->m_expectedSize)
1555+ {
1556+ shared_ptr<GMenuModel> link;
1557+
1558+ switch (p->m_linkType)
1559+ {
1560+ case LinkType::any:
1561+ {
1562+ link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter);
1563+ if (!link)
1564+ {
1565+ link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter);
1566+ }
1567+ break;
1568+ }
1569+ case LinkType::submenu:
1570+ {
1571+ link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter);
1572+ break;
1573+ }
1574+ case LinkType::section:
1575+ {
1576+ link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter);
1577+ break;
1578+ }
1579+ }
1580+
1581+
1582+ if (!link)
1583+ {
1584+ if (p->m_expectedSize)
1585+ {
1586+ matchResult.failure(
1587+ location,
1588+ "Expected " + to_string(*p->m_expectedSize)
1589+ + " children, but found none");
1590+ }
1591+ else
1592+ {
1593+ matchResult.failure(
1594+ location,
1595+ "Expected " + to_string(p->m_items.size())
1596+ + " children, but found none");
1597+ }
1598+ return;
1599+ }
1600+ else
1601+ {
1602+ while (true)
1603+ {
1604+ MatchResult childMatchResult(matchResult.createChild());
1605+
1606+ if (p->m_expectedSize
1607+ && *p->m_expectedSize
1608+ != (unsigned int) g_menu_model_get_n_items(
1609+ link.get()))
1610+ {
1611+ childMatchResult.failure(
1612+ location,
1613+ "Expected " + to_string(*p->m_expectedSize)
1614+ + " child items, but found "
1615+ + to_string(
1616+ g_menu_model_get_n_items(
1617+ link.get())));
1618+ }
1619+ else if (!p->m_items.empty())
1620+ {
1621+ switch (p->m_mode)
1622+ {
1623+ case Mode::all:
1624+ p->all(childMatchResult, location, link, actions);
1625+ break;
1626+ case Mode::starts_with:
1627+ p->startsWith(childMatchResult, location, link, actions);
1628+ break;
1629+ case Mode::ends_with:
1630+ p->endsWith(childMatchResult, location, link, actions);
1631+ break;
1632+ }
1633+ }
1634+
1635+ if (childMatchResult.success())
1636+ {
1637+ matchResult.merge(childMatchResult);
1638+ break;
1639+ }
1640+ else
1641+ {
1642+ if (matchResult.hasTimedOut())
1643+ {
1644+ matchResult.merge(childMatchResult);
1645+ break;
1646+ }
1647+ menuWaitForItems(link);
1648+ }
1649+ }
1650+ }
1651+ }
1652+
1653+
1654+ for (const auto& a: p->m_setActionStates)
1655+ {
1656+ auto stateAction = action;
1657+ auto stateIdPair = idPair;
1658+ auto stateActionGroup = actionGroup;
1659+ if (!a.first.empty())
1660+ {
1661+ stateAction = get_string_attribute(menuItem, a.first.c_str());;
1662+ stateIdPair = split_action(stateAction);
1663+ stateActionGroup = actions[stateIdPair.first];
1664+ }
1665+
1666+ if (stateAction.empty())
1667+ {
1668+ matchResult.failure(
1669+ location,
1670+ "Tried to set action state, but no action was found");
1671+ }
1672+ else if(!stateActionGroup)
1673+ {
1674+ matchResult.failure(
1675+ location,
1676+ "Tried to set action state for action group '" + stateIdPair.first
1677+ + "', but action group wasn't found");
1678+ }
1679+ else if (!g_action_group_has_action(stateActionGroup.get(), stateIdPair.second.c_str()))
1680+ {
1681+ matchResult.failure(
1682+ location,
1683+ "Tried to set action state for action '" + stateAction
1684+ + "', but action was not found");
1685+ }
1686+ else
1687+ {
1688+ g_action_group_change_action_state(stateActionGroup.get(), stateIdPair.second.c_str(),
1689+ g_variant_ref(a.second.get()));
1690+ }
1691+
1692+ // FIXME this is a dodgy way to ensure the action state change gets dispatched
1693+ menuWaitForItems(menu, 100);
1694+ }
1695+
1696+ for (const auto& a: p->m_activations)
1697+ {
1698+ string tmpAction = action;
1699+ auto tmpIdPair = idPair;
1700+ auto tmpActionGroup = actionGroup;
1701+ if (!a.first.empty())
1702+ {
1703+ tmpAction = get_string_attribute(menuItem, a.first.c_str());
1704+ tmpIdPair = split_action(tmpAction);
1705+ tmpActionGroup = actions[tmpIdPair.first];
1706+ }
1707+
1708+ if (tmpAction.empty())
1709+ {
1710+ matchResult.failure(
1711+ location,
1712+ "Tried to activate action, but no action was found");
1713+ }
1714+ else if(!tmpActionGroup)
1715+ {
1716+ matchResult.failure(
1717+ location,
1718+ "Tried to activate action group '" + tmpIdPair.first
1719+ + "', but action group wasn't found");
1720+ }
1721+ else if (!g_action_group_has_action(tmpActionGroup.get(), tmpIdPair.second.c_str()))
1722+ {
1723+ matchResult.failure(
1724+ location,
1725+ "Tried to activate action '" + tmpAction + "', but action was not found");
1726+ }
1727+ else
1728+ {
1729+ if (a.second)
1730+ {
1731+ g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(),
1732+ g_variant_ref(a.second.get()));
1733+ }
1734+ else
1735+ {
1736+ g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(), nullptr);
1737+ }
1738+
1739+ // FIXME this is a dodgy way to ensure the activation gets dispatched
1740+ menuWaitForItems(menu, 100);
1741+ }
1742+ }
1743+}
1744+
1745+} // namepsace gmenuharness
1746+
1747+} // namespace unity
1748
1749=== added file 'src/gmenuharness/MenuMatcher.cpp'
1750--- src/gmenuharness/MenuMatcher.cpp 1970-01-01 00:00:00 +0000
1751+++ src/gmenuharness/MenuMatcher.cpp 2016-01-05 11:11:34 +0000
1752@@ -0,0 +1,208 @@
1753+/*
1754+ * Copyright © 2014 Canonical Ltd.
1755+ *
1756+ * This program is free software: you can redistribute it and/or modify it
1757+ * under the terms of the GNU Lesser General Public License version 3,
1758+ * as published by the Free Software Foundation.
1759+ *
1760+ * This program is distributed in the hope that it will be useful,
1761+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1762+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1763+ * GNU Lesser General Public License for more details.
1764+ *
1765+ * You should have received a copy of the GNU Lesser General Public License
1766+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1767+ *
1768+ * Authored by: Pete Woods <pete.woods@canonical.com>
1769+ */
1770+
1771+#include <unity/gmenuharness/MenuMatcher.h>
1772+#include <unity/gmenuharness/MatchUtils.h>
1773+
1774+#include <iostream>
1775+
1776+#include <gio/gio.h>
1777+
1778+using namespace std;
1779+
1780+namespace unity
1781+{
1782+
1783+namespace gmenuharness
1784+{
1785+
1786+namespace
1787+{
1788+
1789+static void gdbus_connection_deleter(GDBusConnection* connection)
1790+{
1791+// if (!g_dbus_connection_is_closed(connection))
1792+// {
1793+// g_dbus_connection_close_sync(connection, nullptr, nullptr);
1794+// }
1795+ g_clear_object(&connection);
1796+}
1797+}
1798+
1799+struct MenuMatcher::Parameters::Priv
1800+{
1801+ string m_busName;
1802+
1803+ vector<pair<string, string>> m_actions;
1804+
1805+ string m_menuObjectPath;
1806+};
1807+
1808+MenuMatcher::Parameters::Parameters(const string& busName,
1809+ const vector<pair<string, string>>& actions,
1810+ const string& menuObjectPath) :
1811+ p(new Priv)
1812+{
1813+ p->m_busName = busName;
1814+ p->m_actions = actions;
1815+ p->m_menuObjectPath = menuObjectPath;
1816+}
1817+
1818+MenuMatcher::Parameters::~Parameters()
1819+{
1820+}
1821+
1822+MenuMatcher::Parameters::Parameters(const Parameters& other) :
1823+ p(new Priv)
1824+{
1825+ *this = other;
1826+}
1827+
1828+MenuMatcher::Parameters::Parameters(Parameters&& other)
1829+{
1830+ *this = move(other);
1831+}
1832+
1833+MenuMatcher::Parameters& MenuMatcher::Parameters::operator=(const Parameters& other)
1834+{
1835+ p->m_busName = other.p->m_busName;
1836+ p->m_actions = other.p->m_actions;
1837+ p->m_menuObjectPath = other.p->m_menuObjectPath;
1838+ return *this;
1839+}
1840+
1841+MenuMatcher::Parameters& MenuMatcher::Parameters::operator=(Parameters&& other)
1842+{
1843+ p = move(other.p);
1844+ return *this;
1845+}
1846+
1847+struct MenuMatcher::Priv
1848+{
1849+ Priv(const Parameters& parameters) :
1850+ m_parameters(parameters)
1851+ {
1852+ }
1853+
1854+ Parameters m_parameters;
1855+
1856+ vector<MenuItemMatcher> m_items;
1857+
1858+ shared_ptr<GDBusConnection> m_system;
1859+
1860+ shared_ptr<GDBusConnection> m_session;
1861+
1862+ shared_ptr<GMenuModel> m_menu;
1863+
1864+ map<string, shared_ptr<GActionGroup>> m_actions;
1865+};
1866+
1867+MenuMatcher::MenuMatcher(const Parameters& parameters) :
1868+ p(new Priv(parameters))
1869+{
1870+ p->m_system.reset(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr),
1871+ &gdbus_connection_deleter);
1872+ g_dbus_connection_set_exit_on_close(p->m_system.get(), false);
1873+
1874+ p->m_session.reset(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr),
1875+ &gdbus_connection_deleter);
1876+ g_dbus_connection_set_exit_on_close(p->m_session.get(), false);
1877+
1878+ p->m_menu.reset(
1879+ G_MENU_MODEL(
1880+ g_dbus_menu_model_get(
1881+ p->m_session.get(),
1882+ p->m_parameters.p->m_busName.c_str(),
1883+ p->m_parameters.p->m_menuObjectPath.c_str())),
1884+ &g_object_deleter);
1885+
1886+ for (const auto& action : p->m_parameters.p->m_actions)
1887+ {
1888+ shared_ptr<GActionGroup> actionGroup(
1889+ G_ACTION_GROUP(
1890+ g_dbus_action_group_get(
1891+ p->m_session.get(),
1892+ p->m_parameters.p->m_busName.c_str(),
1893+ action.second.c_str())),
1894+ &g_object_deleter);
1895+ p->m_actions[action.first] = actionGroup;
1896+ }
1897+}
1898+
1899+MenuMatcher::~MenuMatcher()
1900+{
1901+}
1902+
1903+MenuMatcher& MenuMatcher::item(const MenuItemMatcher& item)
1904+{
1905+ p->m_items.emplace_back(item);
1906+ return *this;
1907+}
1908+
1909+void MenuMatcher::match(MatchResult& matchResult) const
1910+{
1911+ vector<unsigned int> location;
1912+
1913+ while (true)
1914+ {
1915+ MatchResult childMatchResult(matchResult.createChild());
1916+
1917+ int menuSize = g_menu_model_get_n_items(p->m_menu.get());
1918+ if (p->m_items.size() > (unsigned int) menuSize)
1919+ {
1920+ childMatchResult.failure(
1921+ location,
1922+ "Row count mismatch, expected " + to_string(p->m_items.size())
1923+ + " but found " + to_string(menuSize));
1924+ }
1925+ else
1926+ {
1927+ for (size_t i = 0; i < p->m_items.size(); ++i)
1928+ {
1929+ const auto& matcher = p->m_items.at(i);
1930+ matcher.match(childMatchResult, location, p->m_menu, p->m_actions, i);
1931+ }
1932+ }
1933+
1934+ if (childMatchResult.success())
1935+ {
1936+ matchResult.merge(childMatchResult);
1937+ break;
1938+ }
1939+ else
1940+ {
1941+ if (matchResult.hasTimedOut())
1942+ {
1943+ matchResult.merge(childMatchResult);
1944+ break;
1945+ }
1946+ menuWaitForItems(p->m_menu);
1947+ }
1948+ }
1949+}
1950+
1951+MatchResult MenuMatcher::match() const
1952+{
1953+ MatchResult matchResult;
1954+ match(matchResult);
1955+ return matchResult;
1956+}
1957+
1958+} // namespace gmenuharness
1959+
1960+} // namespace unity
1961
1962=== modified file 'src/service.vala'
1963--- src/service.vala 2015-12-23 11:08:16 +0000
1964+++ src/service.vala 2016-01-05 11:11:34 +0000
1965@@ -43,7 +43,7 @@
1966 warn_notification.closed.connect((n) => { n.clear_actions(); });
1967 BusWatcher.watch_namespace (GLib.BusType.SESSION,
1968 "org.freedesktop.Notifications",
1969- () => { debug("Notifications name appeared"); notify_server_caps_checked = false; },
1970+ () => { debug("Notifications name appeared"); },
1971 () => { debug("Notifications name vanshed"); notify_server_caps_checked = false; });
1972
1973 this.settings = new Settings ("com.canonical.indicator.sound");
1974@@ -52,6 +52,8 @@
1975 this.notify["visible"].connect ( () => this.update_root_icon () );
1976
1977 this.volume_control = volume;
1978+ this.volume_control.active_output_changed.connect (this.update_root_icon);
1979+ this.volume_control.active_output_changed.connect (this.update_notification);
1980
1981 this.accounts_service = accounts;
1982 /* If we're on the greeter, don't export */
1983@@ -90,6 +92,10 @@
1984 this.volume_control.bind_property ("high-volume", menu, "show-high-volume-warning", BindingFlags.SYNC_CREATE);
1985 });
1986
1987+ this.menus.@foreach ( (profile, menu) => {
1988+ this.volume_control.active_output_changed.connect (menu.update_volume_slider);
1989+ });
1990+
1991 this.sync_preferred_players ();
1992 this.settings.changed["interested-media-players"].connect ( () => {
1993 this.sync_preferred_players ();
1994@@ -245,17 +251,7 @@
1995
1996 void update_root_icon () {
1997 double volume = this.volume_control.volume.volume;
1998- string icon;
1999- if (this.volume_control.mute || volume <= 0.0)
2000- icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
2001- else if (this.accounts_service != null && this.accounts_service.silentMode)
2002- icon = "audio-volume-muted-panel";
2003- else if (volume <= 0.3)
2004- icon = "audio-volume-low-panel";
2005- else if (volume <= 0.7)
2006- icon = "audio-volume-medium-panel";
2007- else
2008- icon = "audio-volume-high-panel";
2009+ string icon = get_volume_root_icon (volume, this.volume_control.mute, volume_control.active_output);
2010
2011 string accessible_name;
2012 if (this.volume_control.mute) {
2013@@ -281,15 +277,333 @@
2014 private bool notify_server_supports_actions = false;
2015 private bool notify_server_supports_sync = false;
2016 private bool block_info_notifications = false;
2017+ private bool waiting_user_approve_warn = false;
2018+
2019+ private string get_volume_icon (double volume, VolumeControl.ActiveOutput active_output)
2020+ {
2021+ string icon = "";
2022+ switch (active_output)
2023+ {
2024+ case VolumeControl.ActiveOutput.SPEAKERS:
2025+ if (volume <= 0.0)
2026+ icon = "audio-volume-muted";
2027+ else if (volume <= 0.3)
2028+ icon = "audio-volume-low";
2029+ else if (volume <= 0.7)
2030+ icon = "audio-volume-medium";
2031+ else
2032+ icon = "audio-volume-high";
2033+ break;
2034+ case VolumeControl.ActiveOutput.HEADPHONES:
2035+ if (volume <= 0.0)
2036+ icon = "audio-volume-muted";
2037+ else if (volume <= 0.3)
2038+ icon = "audio-volume-low";
2039+ else if (volume <= 0.7)
2040+ icon = "audio-volume-medium";
2041+ else
2042+ icon = "audio-volume-high";
2043+ break;
2044+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
2045+ if (volume <= 0.0)
2046+ icon = "audio-volume-muted";
2047+ else if (volume <= 0.3)
2048+ icon = "audio-volume-low";
2049+ else if (volume <= 0.7)
2050+ icon = "audio-volume-medium";
2051+ else
2052+ icon = "audio-volume-high";
2053+ break;
2054+ case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
2055+ if (volume <= 0.0)
2056+ icon = "audio-volume-muted";
2057+ else if (volume <= 0.3)
2058+ icon = "audio-volume-low";
2059+ else if (volume <= 0.7)
2060+ icon = "audio-volume-medium";
2061+ else
2062+ icon = "audio-volume-high";
2063+ break;
2064+ case VolumeControl.ActiveOutput.USB_SPEAKER:
2065+ if (volume <= 0.0)
2066+ icon = "audio-volume-muted";
2067+ else if (volume <= 0.3)
2068+ icon = "audio-volume-low";
2069+ else if (volume <= 0.7)
2070+ icon = "audio-volume-medium";
2071+ else
2072+ icon = "audio-volume-high";
2073+ break;
2074+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
2075+ if (volume <= 0.0)
2076+ icon = "audio-volume-muted";
2077+ else if (volume <= 0.3)
2078+ icon = "audio-volume-low";
2079+ else if (volume <= 0.7)
2080+ icon = "audio-volume-medium";
2081+ else
2082+ icon = "audio-volume-high";
2083+ break;
2084+ case VolumeControl.ActiveOutput.HDMI_SPEAKER:
2085+ if (volume <= 0.0)
2086+ icon = "audio-volume-muted";
2087+ else if (volume <= 0.3)
2088+ icon = "audio-volume-low";
2089+ else if (volume <= 0.7)
2090+ icon = "audio-volume-medium";
2091+ else
2092+ icon = "audio-volume-high";
2093+ break;
2094+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
2095+ if (volume <= 0.0)
2096+ icon = "audio-volume-muted";
2097+ else if (volume <= 0.3)
2098+ icon = "audio-volume-low";
2099+ else if (volume <= 0.7)
2100+ icon = "audio-volume-medium";
2101+ else
2102+ icon = "audio-volume-high";
2103+ break;
2104+ }
2105+ return icon;
2106+ }
2107+
2108+ private string get_volume_root_icon_by_volume (double volume, VolumeControl.ActiveOutput active_output)
2109+ {
2110+ string icon = "";
2111+ switch (active_output)
2112+ {
2113+ case VolumeControl.ActiveOutput.SPEAKERS:
2114+ if (volume <= 0.0)
2115+ icon = "audio-volume-muted-panel";
2116+ else if (volume <= 0.3)
2117+ icon = "audio-volume-low-panel";
2118+ else if (volume <= 0.7)
2119+ icon = "audio-volume-medium-panel";
2120+ else
2121+ icon = "audio-volume-high-panel";
2122+ break;
2123+ case VolumeControl.ActiveOutput.HEADPHONES:
2124+ if (volume <= 0.0)
2125+ icon = "audio-volume-muted-panel";
2126+ else if (volume <= 0.3)
2127+ icon = "audio-volume-low-panel";
2128+ else if (volume <= 0.7)
2129+ icon = "audio-volume-medium-panel";
2130+ else
2131+ icon = "audio-volume-high-panel";
2132+ break;
2133+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
2134+ if (volume <= 0.0)
2135+ icon = "audio-volume-muted-panel";
2136+ else if (volume <= 0.3)
2137+ icon = "audio-volume-low-panel";
2138+ else if (volume <= 0.7)
2139+ icon = "audio-volume-medium-panel";
2140+ else
2141+ icon = "audio-volume-high-panel";
2142+ break;
2143+ case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
2144+ if (volume <= 0.0)
2145+ icon = "audio-volume-muted-panel";
2146+ else if (volume <= 0.3)
2147+ icon = "audio-volume-low-panel";
2148+ else if (volume <= 0.7)
2149+ icon = "audio-volume-medium-panel";
2150+ else
2151+ icon = "audio-volume-high-panel";
2152+ break;
2153+ case VolumeControl.ActiveOutput.USB_SPEAKER:
2154+ if (volume <= 0.0)
2155+ icon = "audio-volume-muted-panel";
2156+ else if (volume <= 0.3)
2157+ icon = "audio-volume-low-panel";
2158+ else if (volume <= 0.7)
2159+ icon = "audio-volume-medium-panel";
2160+ else
2161+ icon = "audio-volume-high-panel";
2162+ break;
2163+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
2164+ if (volume <= 0.0)
2165+ icon = "audio-volume-muted-panel";
2166+ else if (volume <= 0.3)
2167+ icon = "audio-volume-low-panel";
2168+ else if (volume <= 0.7)
2169+ icon = "audio-volume-medium-panel";
2170+ else
2171+ icon = "audio-volume-high-panel";
2172+ break;
2173+ case VolumeControl.ActiveOutput.HDMI_SPEAKER:
2174+ if (volume <= 0.0)
2175+ icon = "audio-volume-muted-panel";
2176+ else if (volume <= 0.3)
2177+ icon = "audio-volume-low-panel";
2178+ else if (volume <= 0.7)
2179+ icon = "audio-volume-medium-panel";
2180+ else
2181+ icon = "audio-volume-high-panel";
2182+ break;
2183+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
2184+ if (volume <= 0.0)
2185+ icon = "audio-volume-muted-panel";
2186+ else if (volume <= 0.3)
2187+ icon = "audio-volume-low-panel";
2188+ else if (volume <= 0.7)
2189+ icon = "audio-volume-medium-panel";
2190+ else
2191+ icon = "audio-volume-high-panel";
2192+ break;
2193+ }
2194+ return icon;
2195+ }
2196+
2197+ private string get_volume_notification_icon (double volume, bool loud, VolumeControl.ActiveOutput active_output) {
2198+ string icon = "";
2199+ if (loud) {
2200+ switch (active_output)
2201+ {
2202+ case VolumeControl.ActiveOutput.SPEAKERS:
2203+ icon = "audio-volume-high";
2204+ break;
2205+ case VolumeControl.ActiveOutput.HEADPHONES:
2206+ icon = "audio-volume-high";
2207+ break;
2208+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
2209+ icon = "audio-volume-high";
2210+ break;
2211+ case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
2212+ icon = "audio-volume-high";
2213+ break;
2214+ case VolumeControl.ActiveOutput.USB_SPEAKER:
2215+ icon = "audio-volume-high";
2216+ break;
2217+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
2218+ icon = "audio-volume-high";
2219+ break;
2220+ case VolumeControl.ActiveOutput.HDMI_SPEAKER:
2221+ icon = "audio-volume-high";
2222+ break;
2223+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
2224+ icon = "audio-volume-high";
2225+ break;
2226+ }
2227+ } else {
2228+ icon = get_volume_icon (volume, active_output);
2229+ }
2230+ return icon;
2231+ }
2232+
2233+ private string get_volume_root_icon (double volume, bool mute, VolumeControl.ActiveOutput active_output) {
2234+ string icon = "";
2235+ switch (active_output)
2236+ {
2237+ case VolumeControl.ActiveOutput.SPEAKERS:
2238+ if (mute || volume <= 0.0)
2239+ icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
2240+ else if (this.accounts_service != null && this.accounts_service.silentMode)
2241+ icon = "audio-volume-muted-panel";
2242+ else
2243+ icon = get_volume_root_icon_by_volume (volume, active_output);
2244+ break;
2245+ case VolumeControl.ActiveOutput.HEADPHONES:
2246+ if (mute || volume <= 0.0)
2247+ icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
2248+ else if (this.accounts_service != null && this.accounts_service.silentMode)
2249+ icon = "audio-volume-muted-panel";
2250+ else
2251+ icon = get_volume_root_icon_by_volume (volume, active_output);
2252+ break;
2253+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
2254+ if (mute || volume <= 0.0)
2255+ icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
2256+ else if (this.accounts_service != null && this.accounts_service.silentMode)
2257+ icon = "audio-volume-muted-panel";
2258+ else
2259+ icon = get_volume_root_icon_by_volume (volume, active_output);
2260+ break;
2261+ case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
2262+ if (mute || volume <= 0.0)
2263+ icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
2264+ else if (this.accounts_service != null && this.accounts_service.silentMode)
2265+ icon = "audio-volume-muted-panel";
2266+ else
2267+ icon = get_volume_root_icon_by_volume (volume, active_output);
2268+ break;
2269+ case VolumeControl.ActiveOutput.USB_SPEAKER:
2270+ if (mute || volume <= 0.0)
2271+ icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
2272+ else if (this.accounts_service != null && this.accounts_service.silentMode)
2273+ icon = "audio-volume-muted-panel";
2274+ else
2275+ icon = get_volume_root_icon_by_volume (volume, active_output);
2276+ break;
2277+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
2278+ if (mute || volume <= 0.0)
2279+ icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
2280+ else if (this.accounts_service != null && this.accounts_service.silentMode)
2281+ icon = "audio-volume-muted-panel";
2282+ else
2283+ icon = get_volume_root_icon_by_volume (volume, active_output);
2284+ break;
2285+ case VolumeControl.ActiveOutput.HDMI_SPEAKER:
2286+ if (mute || volume <= 0.0)
2287+ icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
2288+ else if (this.accounts_service != null && this.accounts_service.silentMode)
2289+ icon = "audio-volume-muted-panel";
2290+ else
2291+ icon = get_volume_root_icon_by_volume (volume, active_output);
2292+ break;
2293+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
2294+ if (mute || volume <= 0.0)
2295+ icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
2296+ else if (this.accounts_service != null && this.accounts_service.silentMode)
2297+ icon = "audio-volume-muted-panel";
2298+ else
2299+ icon = get_volume_root_icon_by_volume (volume, active_output);
2300+ break;
2301+ }
2302+ return icon;
2303+ }
2304+
2305+ private string get_notification_label () {
2306+ string volume_label = "";
2307+ switch (volume_control.active_output)
2308+ {
2309+ case VolumeControl.ActiveOutput.SPEAKERS:
2310+ volume_label = _("Speakers");
2311+ break;
2312+ case VolumeControl.ActiveOutput.HEADPHONES:
2313+ volume_label = _("Headphones");
2314+ break;
2315+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
2316+ volume_label = _("Bluetooth headphones");
2317+ break;
2318+ case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
2319+ volume_label = _("Bluetooth speaker");
2320+ break;
2321+ case VolumeControl.ActiveOutput.USB_SPEAKER:
2322+ volume_label = _("Usb speaker");
2323+ break;
2324+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
2325+ volume_label = _("Usb headphones");
2326+ break;
2327+ case VolumeControl.ActiveOutput.HDMI_SPEAKER:
2328+ volume_label = _("HDMI speaker");
2329+ break;
2330+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
2331+ volume_label = _("HDMI headphones");
2332+ break;
2333+ }
2334+
2335+ return volume_label;
2336+ }
2337
2338 private void update_notification () {
2339
2340- if (!notify_server_caps_checked) {
2341- List<string> caps = Notify.get_server_caps ();
2342- notify_server_supports_actions = caps.find_custom ("actions", strcmp) != null;
2343- notify_server_supports_sync = caps.find_custom ("x-canonical-private-synchronous", strcmp) != null;
2344- notify_server_caps_checked = true;
2345- }
2346+ List<string> caps = Notify.get_server_caps ();
2347+ notify_server_supports_actions = caps.find_custom ("actions", strcmp) != null;
2348+ notify_server_supports_sync = caps.find_custom ("x-canonical-private-synchronous", strcmp) != null;
2349+ notify_server_caps_checked = true;
2350
2351 var loud = volume_control.high_volume;
2352 var warn = loud
2353@@ -312,47 +626,36 @@
2354 _pre_warn_volume = null;
2355 volume_control.volume = tmp;
2356 }
2357+ waiting_user_approve_warn = false;
2358 });
2359 warn_notification.add_action ("cancel", _("Cancel"), (n, a) => {
2360 _pre_warn_volume = null;
2361+ waiting_user_approve_warn = false;
2362 });
2363+ waiting_user_approve_warn = true;
2364 show_notification(warn_notification);
2365 } else {
2366- close_notification(warn_notification);
2367-
2368- if (notify_server_supports_sync && !block_info_notifications) {
2369-
2370- /* Determine Label */
2371- unowned string volume_label = loud
2372- ? _("High volume can damage your hearing.")
2373- : "";
2374-
2375- /* Choose an icon */
2376- unowned string icon;
2377- if (loud) {
2378- icon = "audio-volume-high";
2379- } else {
2380- var volume = volume_control.volume.volume;
2381-
2382- if (volume <= 0.0)
2383- icon = "audio-volume-muted";
2384- else if (volume <= 0.3)
2385- icon = "audio-volume-low";
2386- else if (volume <= 0.7)
2387- icon = "audio-volume-medium";
2388- else
2389- icon = "audio-volume-high";
2390+ if (!waiting_user_approve_warn) {
2391+ close_notification(warn_notification);
2392+
2393+ if (notify_server_supports_sync && !block_info_notifications) {
2394+
2395+ /* Determine Label */
2396+ string volume_label = get_notification_label ();
2397+
2398+ /* Choose an icon */
2399+ string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_output);
2400+
2401+ /* Reset the notification */
2402+ var n = this.info_notification;
2403+ n.update (_("Volume"), volume_label, icon);
2404+ n.clear_hints();
2405+ n.set_hint ("x-canonical-non-shaped-icon", "true");
2406+ n.set_hint ("x-canonical-private-synchronous", "true");
2407+ n.set_hint ("x-canonical-value-bar-tint", loud ? "true" : "false");
2408+ n.set_hint ("value", (int32)Math.round(get_volume_percent() * 100.0));
2409+ show_notification(n);
2410 }
2411-
2412- /* Reset the notification */
2413- var n = this.info_notification;
2414- n.update (_("Volume"), volume_label, icon);
2415- n.clear_hints();
2416- n.set_hint ("x-canonical-non-shaped-icon", "true");
2417- n.set_hint ("x-canonical-private-synchronous", "true");
2418- n.set_hint ("x-canonical-value-bar-tint", loud ? "true" : "false");
2419- n.set_hint ("value", (int32)Math.round(get_volume_percent() * 100.0));
2420- show_notification(n);
2421 }
2422 }
2423 }
2424
2425=== modified file 'src/sound-menu.vala'
2426--- src/sound-menu.vala 2015-12-23 11:08:16 +0000
2427+++ src/sound-menu.vala 2016-01-05 11:11:34 +0000
2428@@ -71,6 +71,7 @@
2429 this.notify_handlers = new HashTable<MediaPlayer, ulong> (direct_hash, direct_equal);
2430
2431 this.greeter_players = (flags & DisplayFlags.GREETER_PLAYERS) != 0;
2432+
2433 }
2434
2435 ~SoundMenu () {
2436@@ -193,6 +194,43 @@
2437 this.notify_handlers.remove (player);
2438 }
2439
2440+ public void update_volume_slider (VolumeControl.ActiveOutput active_output) {
2441+ int index = find_action (this.volume_section, "indicator.volume");
2442+ if (index != -1) {
2443+ string label = "Volume";
2444+ switch (active_output) {
2445+ case VolumeControl.ActiveOutput.SPEAKERS:
2446+ label = _("Volume");
2447+ break;
2448+ case VolumeControl.ActiveOutput.HEADPHONES:
2449+ label = _("Volume (Headphones)");
2450+ break;
2451+ case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
2452+ label = _("Volume (Bluetooth)");
2453+ break;
2454+ case VolumeControl.ActiveOutput.USB_SPEAKER:
2455+ label = _("Volume (Usb)");
2456+ break;
2457+ case VolumeControl.ActiveOutput.HDMI_SPEAKER:
2458+ label = _("Volume (HDMI)");
2459+ break;
2460+ case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
2461+ label = _("Volume (Bluetooth headphones)");
2462+ break;
2463+ case VolumeControl.ActiveOutput.USB_HEADPHONES:
2464+ label = _("Volume (Usb headphones)");
2465+ break;
2466+ case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
2467+ label = _("Volume (HDMI headphones)");
2468+ break;
2469+ }
2470+ this.volume_section.remove (index);
2471+ this.volume_section.insert_item (index, this.create_slider_menu_item (_(label), "indicator.volume(0)", 0.0, 1.0, 0.01,
2472+ "audio-volume-low-zero-panel",
2473+ "audio-volume-high-panel"));
2474+ }
2475+ }
2476+
2477 public Menu root;
2478 public Menu menu;
2479 Menu volume_section;
2480
2481=== modified file 'src/volume-control-pulse.vala'
2482--- src/volume-control-pulse.vala 2015-12-23 11:08:16 +0000
2483+++ src/volume-control-pulse.vala 2016-01-05 11:11:34 +0000
2484@@ -87,6 +87,7 @@
2485 private bool _send_next_local_volume = false;
2486 private double _account_service_volume = 0.0;
2487 private bool _active_port_headphone = false;
2488+ private VolumeControl.ActiveOutput _active_output = VolumeControl.ActiveOutput.SPEAKERS;
2489
2490 /** true when connected to the pulse server */
2491 public override bool ready { get; private set; }
2492@@ -135,6 +136,49 @@
2493 stop_high_volume_approved_timer();
2494 }
2495
2496+ private VolumeControl.ActiveOutput calculate_active_output (SinkInfo? sink) {
2497+
2498+ VolumeControl.ActiveOutput ret_output = VolumeControl.ActiveOutput.SPEAKERS;
2499+ /* Check if the current active port is headset/headphone */
2500+ /* There is not easy way to check if the port is a headset/headphone besides
2501+ * checking for the port name. On touch (with the pulseaudio droid element)
2502+ * the headset/headphone port is called 'output-headset' and 'output-headphone'.
2503+ * On the desktop this is usually called 'analog-output-headphones' */
2504+ // look if it's a headset/headphones
2505+ if (sink.name == "indicator_sound_test_headphones" ||
2506+ (sink.active_port != null &&
2507+ (sink.active_port.name.contains("headset") ||
2508+ sink.active_port.name.contains("headphone")))) {
2509+ _active_port_headphone = true;
2510+ // check if it's a bluetooth device
2511+ var device_bus = sink.proplist.gets ("device.bus");
2512+ if (device_bus != null && device_bus == "bluetooth") {
2513+ ret_output = VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES;
2514+ } else if (device_bus != null && device_bus == "usb") {
2515+ ret_output = VolumeControl.ActiveOutput.USB_HEADPHONES;
2516+ } else if (device_bus != null && device_bus == "hdmi") {
2517+ ret_output = VolumeControl.ActiveOutput.HDMI_HEADPHONES;
2518+ } else {
2519+ ret_output = VolumeControl.ActiveOutput.HEADPHONES;
2520+ }
2521+ } else {
2522+ // speaker
2523+ _active_port_headphone = false;
2524+ var device_bus = sink.proplist.gets ("device.bus");
2525+ if (device_bus != null && device_bus == "bluetooth") {
2526+ ret_output = VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER;
2527+ } else if (device_bus != null && device_bus == "usb") {
2528+ ret_output = VolumeControl.ActiveOutput.USB_SPEAKER;
2529+ } else if (device_bus != null && device_bus == "hdmi") {
2530+ ret_output = VolumeControl.ActiveOutput.HDMI_SPEAKER;
2531+ } else {
2532+ ret_output = VolumeControl.ActiveOutput.SPEAKERS;
2533+ }
2534+ }
2535+
2536+ return ret_output;
2537+ }
2538+
2539 /* PulseAudio logic*/
2540 private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index)
2541 {
2542@@ -201,18 +245,20 @@
2543 this.notify_property ("is-playing");
2544 }
2545
2546- /* Check if the current active port is headset/headphone */
2547- /* There is not easy way to check if the port is a headset/headphone besides
2548- * checking for the port name. On touch (with the pulseaudio droid element)
2549- * the headset/headphone port is called 'output-headset' and 'output-headphone'.
2550- * On the desktop this is usually called 'analog-output-headphones' */
2551- if (i.active_port != null &&
2552- (i.active_port.name == "output-wired_headset" ||
2553- i.active_port.name == "output-wired_headphone" ||
2554- i.active_port.name == "analog-output-headphones")) {
2555- _active_port_headphone = true;
2556- } else {
2557- _active_port_headphone = false;
2558+ // store the current status of the active output
2559+ VolumeControl.ActiveOutput active_output_before = active_output;
2560+
2561+ // calculate the output
2562+ _active_output = calculate_active_output (i);
2563+
2564+ // check if the output has changed, if so... emit a signal
2565+ VolumeControl.ActiveOutput active_output_now = active_output;
2566+ if (active_output_now != active_output_before) {
2567+ this.active_output_changed (active_output_now);
2568+ if (active_output_now == VolumeControl.ActiveOutput.SPEAKERS) {
2569+ _high_volume_approved = false;
2570+ }
2571+ update_high_volume();
2572 }
2573
2574 if (_pulse_use_stream_restore == false &&
2575@@ -478,7 +524,8 @@
2576 this.context = new PulseAudio.Context (loop.get_api(), null, props);
2577 this.context.set_state_callback (context_state_callback);
2578
2579- if (context.connect(null, Context.Flags.NOFAIL, null) < 0)
2580+ var server_string = Environment.get_variable("PULSE_SERVER");
2581+ if (context.connect(server_string, Context.Flags.NOFAIL, null) < 0)
2582 warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno()));
2583 }
2584
2585@@ -535,6 +582,14 @@
2586 }
2587 }
2588
2589+ public override VolumeControl.ActiveOutput active_output
2590+ {
2591+ get
2592+ {
2593+ return _active_output;
2594+ }
2595+ }
2596+
2597 /* Volume operations */
2598 private static PulseAudio.Volume double_to_volume (double vol)
2599 {
2600@@ -710,7 +765,7 @@
2601 private bool calculate_high_volume_from_volume(double volume) {
2602 return _active_port_headphone
2603 && _warning_volume_enabled
2604- && volume >= _warning_volume_norms
2605+ && volume > _warning_volume_norms
2606 && (stream == "multimedia");
2607 }
2608
2609
2610=== modified file 'src/volume-control.vala'
2611--- src/volume-control.vala 2015-12-23 11:08:16 +0000
2612+++ src/volume-control.vala 2016-01-05 11:11:34 +0000
2613@@ -28,6 +28,17 @@
2614 VOLUME_STREAM_CHANGE
2615 }
2616
2617+ public enum ActiveOutput {
2618+ SPEAKERS,
2619+ HEADPHONES,
2620+ BLUETOOTH_HEADPHONES,
2621+ BLUETOOTH_SPEAKER,
2622+ USB_SPEAKER,
2623+ USB_HEADPHONES,
2624+ HDMI_SPEAKER,
2625+ HDMI_HEADPHONES
2626+ }
2627+
2628 public class Volume : Object {
2629 public double volume;
2630 public VolumeReasons reason;
2631@@ -39,6 +50,7 @@
2632 public virtual bool high_volume { get { return false; } protected set { } }
2633 public virtual bool mute { get { return false; } }
2634 public virtual bool is_playing { get { return false; } }
2635+ public virtual VolumeControl.ActiveOutput active_output { get { return VolumeControl.ActiveOutput.SPEAKERS; } }
2636 private Volume _volume;
2637 public virtual Volume volume { get { return _volume; } set { } }
2638 public virtual double mic_volume { get { return 0.0; } set { } }
2639@@ -56,4 +68,6 @@
2640 v.reason = reason;
2641 this.volume = v;
2642 }
2643+
2644+ public signal void active_output_changed (VolumeControl.ActiveOutput active_output);
2645 }
2646
2647=== modified file 'tests/CMakeLists.txt'
2648--- tests/CMakeLists.txt 2015-12-23 11:08:16 +0000
2649+++ tests/CMakeLists.txt 2016-01-05 11:11:34 +0000
2650@@ -5,10 +5,10 @@
2651
2652 include_directories(${GTEST_INCLUDE_DIR})
2653
2654-add_library (gtest STATIC
2655+add_library (gtest-static STATIC
2656 ${GTEST_SOURCE_DIR}/gtest-all.cc
2657 ${GTEST_SOURCE_DIR}/gtest_main.cc)
2658-target_link_libraries(gtest ${GTEST_LIBS})
2659+target_link_libraries(gtest-static ${GTEST_LIBS})
2660
2661 ###########################
2662 # GSettings Schema
2663@@ -22,7 +22,8 @@
2664 # GSettings:
2665 # compile the indicator-sound schema into a gschemas.compiled file in this directory,
2666 # and help the tests to find that file by setting -DSCHEMA_DIR
2667-set (SCHEMA_DIR "${CMAKE_CURRENT_BINARY_DIR}/gsettings-schemas")
2668+set (XDG_DATA_DIRS "${CMAKE_CURRENT_BINARY_DIR}/gsettings-schemas")
2669+set (SCHEMA_DIR "${XDG_DATA_DIRS}/glib-2.0/schemas")
2670 add_definitions(-DSCHEMA_DIR="${SCHEMA_DIR}")
2671 execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas
2672 OUTPUT_VARIABLE COMPILE_SCHEMA_EXECUTABLE
2673@@ -31,6 +32,7 @@
2674 DEPENDS ${CMAKE_SOURCE_DIR}/data/com.canonical.indicator.sound.gschema.xml
2675 COMMAND mkdir -p ${SCHEMA_DIR}
2676 COMMAND cp -f ${CMAKE_SOURCE_DIR}/data/*gschema.xml ${SCHEMA_DIR}
2677+ COMMAND cp -f /usr/share/glib-2.0/schemas/com.ubuntu.sound.gschema.xml ${SCHEMA_DIR}
2678 COMMAND ${COMPILE_SCHEMA_EXECUTABLE} ${SCHEMA_DIR})
2679
2680 ###########################
2681@@ -41,74 +43,74 @@
2682 set(VALA_MOCKS_SYMBOLS_PATH "${CMAKE_CURRENT_BINARY_DIR}/vala-mocks.def")
2683
2684 vala_init(vala-mocks
2685- DEPENDS
2686- indicator-sound-service-lib
2687- PACKAGES
2688- config
2689- gio-2.0
2690- gio-unix-2.0
2691- libxml-2.0
2692- libpulse
2693- libpulse-mainloop-glib
2694- libnotify
2695- accounts-service
2696- indicator-sound-service
2697- OPTIONS
2698- --ccode
2699- --thread
2700- --vapidir=${CMAKE_BINARY_DIR}/src/
2701- --vapidir=${CMAKE_SOURCE_DIR}/vapi/
2702- --vapidir=.
2703-)
2704-
2705-vala_add(vala-mocks
2706- media-player-mock.vala
2707-)
2708-
2709-vala_add(vala-mocks
2710- media-player-list-mock.vala
2711-)
2712-
2713-vala_add(vala-mocks
2714- volume-control-mock.vala
2715+ DEPENDS
2716+ indicator-sound-service-lib
2717+ PACKAGES
2718+ config
2719+ gio-2.0
2720+ gio-unix-2.0
2721+ libxml-2.0
2722+ libpulse
2723+ libpulse-mainloop-glib
2724+ libnotify
2725+ accounts-service
2726+ indicator-sound-service
2727+ OPTIONS
2728+ --ccode
2729+ --thread
2730+ --vapidir=${CMAKE_BINARY_DIR}/src/
2731+ --vapidir=${CMAKE_SOURCE_DIR}/vapi/
2732+ --vapidir=.
2733+)
2734+
2735+vala_add(vala-mocks
2736+ media-player-mock.vala
2737+)
2738+
2739+vala_add(vala-mocks
2740+ media-player-list-mock.vala
2741+)
2742+
2743+vala_add(vala-mocks
2744+ volume-control-mock.vala
2745 )
2746
2747 vala_finish(vala-mocks
2748- SOURCES
2749- vala_mocks_VALA_SOURCES
2750- OUTPUTS
2751- vala_mocks_VALA_C
2752- GENERATE_HEADER
2753- ${VALA_MOCKS_HEADER_PATH}
2754- GENERATE_SYMBOLS
2755- ${VALA_MOCKS_SYMBOLS_PATH}
2756+ SOURCES
2757+ vala_mocks_VALA_SOURCES
2758+ OUTPUTS
2759+ vala_mocks_VALA_C
2760+ GENERATE_HEADER
2761+ ${VALA_MOCKS_HEADER_PATH}
2762+ GENERATE_SYMBOLS
2763+ ${VALA_MOCKS_SYMBOLS_PATH}
2764 )
2765
2766 set_source_files_properties(
2767- ${vala_mocks_VALA_SOURCES}
2768- PROPERTIES
2769- HEADER_FILE_ONLY TRUE
2770+ ${vala_mocks_VALA_SOURCES}
2771+ PROPERTIES
2772+ HEADER_FILE_ONLY TRUE
2773 )
2774
2775 set(
2776- VALA_MOCKS_SOURCES
2777- ${vala_mocks_VALA_SOURCES}
2778- ${vala_mocks_VALA_C}
2779- ${VALA_MOCKS_SYMBOLS_PATH}
2780+ VALA_MOCKS_SOURCES
2781+ ${vala_mocks_VALA_SOURCES}
2782+ ${vala_mocks_VALA_C}
2783+ ${VALA_MOCKS_SYMBOLS_PATH}
2784 )
2785
2786 add_definitions(
2787- -Wno-unused-but-set-variable
2788+ -Wno-unused-but-set-variable
2789 )
2790
2791 add_library(
2792- vala-mocks-lib STATIC
2793- ${VALA_MOCKS_SOURCES}
2794+ vala-mocks-lib STATIC
2795+ ${VALA_MOCKS_SOURCES}
2796 )
2797
2798 target_link_libraries(
2799- vala-mocks-lib
2800- indicator-sound-service-lib
2801+ vala-mocks-lib
2802+ indicator-sound-service-lib
2803 )
2804
2805 include_directories(${CMAKE_CURRENT_BINARY_DIR})
2806@@ -118,9 +120,9 @@
2807 ###########################
2808
2809 add_library(
2810- pulse-mock
2811- SHARED
2812- pa-mock.cpp
2813+ pulse-mock
2814+ SHARED
2815+ pa-mock.cpp
2816 )
2817
2818 target_link_libraries (pulse-mock ${PULSEAUDIO_LIBRARIES})
2819@@ -131,7 +133,7 @@
2820
2821 include_directories(${CMAKE_SOURCE_DIR}/src)
2822 add_executable (name-watch-test name-watch-test.cc ${CMAKE_SOURCE_DIR}/src/bus-watch-namespace.c)
2823-target_link_libraries (name-watch-test gtest ${SOUNDSERVICE_LIBRARIES})
2824+target_link_libraries (name-watch-test gtest-static ${SOUNDSERVICE_LIBRARIES})
2825 add_test(name-watch-test name-watch-test)
2826
2827 ###########################
2828@@ -141,21 +143,21 @@
2829 include_directories(${CMAKE_SOURCE_DIR}/src)
2830 add_executable (accounts-service-user-test accounts-service-user.cc)
2831 target_link_libraries (
2832- accounts-service-user-test
2833- indicator-sound-service-lib
2834- vala-mocks-lib
2835- gtest
2836- ${SOUNDSERVICE_LIBRARIES}
2837- ${TEST_LIBRARIES}
2838+ accounts-service-user-test
2839+ indicator-sound-service-lib
2840+ vala-mocks-lib
2841+ gtest-static
2842+ ${SOUNDSERVICE_LIBRARIES}
2843+ ${TEST_LIBRARIES}
2844 )
2845
2846 # Split tests to work around libaccountservice sucking
2847 add_test(accounts-service-user-test-basic
2848- accounts-service-user-test --gtest_filter=AccountsServiceUserTest.BasicObject
2849+ accounts-service-user-test --gtest_filter=AccountsServiceUserTest.BasicObject
2850 )
2851
2852 add_test(accounts-service-user-test-player
2853- accounts-service-user-test --gtest_filter=AccountsServiceUserTest.SetMediaPlayer
2854+ accounts-service-user-test --gtest_filter=AccountsServiceUserTest.SetMediaPlayer
2855 )
2856
2857 ###########################
2858@@ -165,11 +167,11 @@
2859 include_directories(${CMAKE_SOURCE_DIR}/src)
2860 add_executable (volume-control-test volume-control-test.cc gschemas.compiled)
2861 target_link_libraries (
2862- volume-control-test
2863- indicator-sound-service-lib
2864- pulse-mock
2865- gtest
2866- ${TEST_LIBRARIES}
2867+ volume-control-test
2868+ indicator-sound-service-lib
2869+ pulse-mock
2870+ gtest-static
2871+ ${TEST_LIBRARIES}
2872 )
2873
2874 add_test(volume-control-test volume-control-test)
2875@@ -181,12 +183,12 @@
2876 include_directories(${CMAKE_SOURCE_DIR}/src)
2877 add_executable (sound-menu-test sound-menu.cc)
2878 target_link_libraries (
2879- sound-menu-test
2880- indicator-sound-service-lib
2881- vala-mocks-lib
2882- gtest
2883- ${SOUNDSERVICE_LIBRARIES}
2884- ${TEST_LIBRARIES}
2885+ sound-menu-test
2886+ indicator-sound-service-lib
2887+ vala-mocks-lib
2888+ gtest-static
2889+ ${SOUNDSERVICE_LIBRARIES}
2890+ ${TEST_LIBRARIES}
2891 )
2892
2893 add_test(sound-menu-test sound-menu-test)
2894@@ -198,13 +200,13 @@
2895 include_directories(${CMAKE_SOURCE_DIR}/src)
2896 add_executable (notifications-test notifications-test.cc)
2897 target_link_libraries (
2898- notifications-test
2899- indicator-sound-service-lib
2900- vala-mocks-lib
2901- pulse-mock
2902- gtest
2903- ${SOUNDSERVICE_LIBRARIES}
2904- ${TEST_LIBRARIES}
2905+ notifications-test
2906+ indicator-sound-service-lib
2907+ vala-mocks-lib
2908+ pulse-mock
2909+ gtest-static
2910+ ${SOUNDSERVICE_LIBRARIES}
2911+ ${TEST_LIBRARIES}
2912 )
2913
2914 add_test(notifications-test notifications-test)
2915@@ -216,23 +218,23 @@
2916 include_directories(${CMAKE_SOURCE_DIR}/src)
2917 add_executable (media-player-user-test media-player-user.cc)
2918 target_link_libraries (
2919- media-player-user-test
2920- indicator-sound-service-lib
2921- vala-mocks-lib
2922- gtest
2923- ${SOUNDSERVICE_LIBRARIES}
2924- ${TEST_LIBRARIES}
2925+ media-player-user-test
2926+ indicator-sound-service-lib
2927+ vala-mocks-lib
2928+ gtest-static
2929+ ${SOUNDSERVICE_LIBRARIES}
2930+ ${TEST_LIBRARIES}
2931 )
2932
2933 # Split tests to work around libaccountservice sucking
2934 add_test(media-player-user-test-basic
2935- media-player-user-test --gtest_filter=MediaPlayerUserTest.BasicObject
2936+ media-player-user-test --gtest_filter=MediaPlayerUserTest.BasicObject
2937 )
2938 add_test(media-player-user-test-dataset
2939- media-player-user-test --gtest_filter=MediaPlayerUserTest.DataSet
2940+ media-player-user-test --gtest_filter=MediaPlayerUserTest.DataSet
2941 )
2942 add_test(media-player-user-test-timeout
2943- media-player-user-test --gtest_filter=MediaPlayerUserTest.TimeoutTest
2944+ media-player-user-test --gtest_filter=MediaPlayerUserTest.TimeoutTest
2945 )
2946
2947 ###########################
2948@@ -242,20 +244,20 @@
2949 include_directories(${CMAKE_SOURCE_DIR}/src)
2950 add_executable (greeter-list-test greeter-list.cc)
2951 target_link_libraries (
2952- greeter-list-test
2953- indicator-sound-service-lib
2954- vala-mocks-lib
2955- gtest
2956- ${SOUNDSERVICE_LIBRARIES}
2957- ${TEST_LIBRARIES}
2958+ greeter-list-test
2959+ indicator-sound-service-lib
2960+ vala-mocks-lib
2961+ gtest-static
2962+ ${SOUNDSERVICE_LIBRARIES}
2963+ ${TEST_LIBRARIES}
2964 )
2965
2966 # Split tests to work around libaccountservice sucking
2967 add_test(greeter-list-test-basic
2968- greeter-list-test --gtest_filter=GreeterListTest.BasicObject
2969+ greeter-list-test --gtest_filter=GreeterListTest.BasicObject
2970 )
2971 add_test(greeter-list-test-iterator
2972- greeter-list-test --gtest_filter=GreeterListTest.BasicIterator
2973+ greeter-list-test --gtest_filter=GreeterListTest.BasicIterator
2974 )
2975
2976 ###########################
2977@@ -263,18 +265,23 @@
2978 ###########################
2979
2980 add_definitions(
2981- -DINDICATOR_SOUND_SERVICE_BINARY="${CMAKE_BINARY_DIR}/src/indicator-sound-service"
2982- -DPA_MOCK_LIB="${CMAKE_CURRENT_BINARY_DIR}/libpulse-mock.so"
2983+ -DINDICATOR_SOUND_SERVICE_BINARY="${CMAKE_BINARY_DIR}/src/indicator-sound-service"
2984+ -DPA_MOCK_LIB="${CMAKE_CURRENT_BINARY_DIR}/libpulse-mock.so"
2985 )
2986 add_executable (indicator-test indicator-test.cc gschemas.compiled)
2987 target_link_libraries (
2988- indicator-test
2989- gtest
2990- ${SOUNDSERVICE_LIBRARIES}
2991- ${TEST_LIBRARIES}
2992+ indicator-test
2993+ gtest-static
2994+ ${SOUNDSERVICE_LIBRARIES}
2995+ ${TEST_LIBRARIES}
2996 )
2997
2998 # Split tests to work around libaccountservice sucking
2999 add_test(indcator-test
3000- indicator-test
3001+ indicator-test
3002 )
3003+
3004+# Disable integration tests
3005+# add_subdirectory(integration)
3006+# add_subdirectory(dbus-types)
3007+# add_subdirectory(service-mocks)
3008
3009=== added directory 'tests/dbus-types'
3010=== added file 'tests/dbus-types/CMakeLists.txt'
3011--- tests/dbus-types/CMakeLists.txt 1970-01-01 00:00:00 +0000
3012+++ tests/dbus-types/CMakeLists.txt 2016-01-05 11:11:34 +0000
3013@@ -0,0 +1,53 @@
3014+set(CMAKE_AUTOMOC ON)
3015+set(CMAKE_INCLUDE_CURRENT_DIR ON)
3016+
3017+find_package(Qt5DBus REQUIRED)
3018+include_directories(${Qt5DBus_INCLUDE_DIRS})
3019+
3020+add_definitions(-DQT_NO_KEYWORDS=1)
3021+
3022+set(dbusinterface_streamrestore_xml "org.PulseAudio.Ext.StreamRestore1.xml")
3023+set_source_files_properties(${dbusinterface_streamrestore_xml} PROPERTIES
3024+ CLASSNAME StreamRestoreInterface)
3025+
3026+set(dbusinterface_accounts_xml "org.freedesktop.Accounts.xml")
3027+set_source_files_properties(${dbusinterface_accounts_xml} PROPERTIES
3028+ CLASSNAME AccountsInterface)
3029+
3030+set(dbusinterface_accountssound_xml "com.ubuntu.AccountsService.Sound.xml")
3031+set_source_files_properties(${dbusinterface_accountssound_xml} PROPERTIES
3032+ CLASSNAME AccountsSoundInterface)
3033+
3034+set(dbusinterface_properties_xml "org.freedesktop.DBus.Properties.xml")
3035+set_source_files_properties(${dbusinterface_properties_xml} PROPERTIES
3036+ CLASSNAME DBusPropertiesInterface
3037+ NO_NAMESPACE YES
3038+ INCLUDE "dbus-types.h")
3039+
3040+set(dbusinterface_actions_xml "org.gtk.Actions.xml")
3041+set_source_files_properties(${dbusinterface_actions_xml} PROPERTIES
3042+ CLASSNAME MenusInterface)
3043+
3044+set(dbusinterface_notifications_xml "org.freedesktop.Notifications.xml")
3045+set_source_files_properties(${dbusinterface_notifications_xml} PROPERTIES
3046+ CLASSNAME NotificationsInterface)
3047+
3048+qt5_add_dbus_interface(interface_files ${dbusinterface_streamrestore_xml} stream_restore_interface)
3049+qt5_add_dbus_interface(interface_files ${dbusinterface_properties_xml} dbus_properties_interface)
3050+qt5_add_dbus_interface(interface_files ${dbusinterface_accounts_xml} dbus_accounts_interface)
3051+qt5_add_dbus_interface(interface_files ${dbusinterface_accountssound_xml} dbus_accountssound_interface)
3052+qt5_add_dbus_interface(interface_files ${dbusinterface_actions_xml} dbus_menus_interface)
3053+qt5_add_dbus_interface(interface_files ${dbusinterface_notifications_xml} dbus_notifications_interface)
3054+
3055+add_library(
3056+ sound-indicator-dbus-interfaces
3057+ STATIC
3058+ ${interface_files}
3059+ pulseaudio-volume.cpp
3060+)
3061+
3062+qt5_use_modules(
3063+ sound-indicator-dbus-interfaces
3064+ Core
3065+ DBus
3066+)
3067
3068=== added file 'tests/dbus-types/com.ubuntu.AccountsService.Sound.xml'
3069--- tests/dbus-types/com.ubuntu.AccountsService.Sound.xml 1970-01-01 00:00:00 +0000
3070+++ tests/dbus-types/com.ubuntu.AccountsService.Sound.xml 2016-01-05 11:11:34 +0000
3071@@ -0,0 +1,9 @@
3072+<node>
3073+ <interface name="com.ubuntu.AccountsService.Sound">
3074+ <method name="Set">
3075+ <arg direction="in" type="s" name="interface" />
3076+ <arg direction="in" type="s" name="property" />
3077+ <arg direction="out" type="o" name="path" />
3078+ </method>
3079+ </interface>
3080+</node>
3081
3082=== added file 'tests/dbus-types/dbus-types.h'
3083--- tests/dbus-types/dbus-types.h 1970-01-01 00:00:00 +0000
3084+++ tests/dbus-types/dbus-types.h 2016-01-05 11:11:34 +0000
3085@@ -0,0 +1,48 @@
3086+/*
3087+ * Copyright (C) 2015 Canonical, Ltd.
3088+ *
3089+ * This program is free software: you can redistribute it and/or modify it
3090+ * under the terms of the GNU General Public License version 3, as published
3091+ * by the Free Software Foundation.
3092+ *
3093+ * This program is distributed in the hope that it will be useful, but
3094+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3095+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3096+ * PURPOSE. See the GNU General Public License for more details.
3097+ *
3098+ * You should have received a copy of the GNU General Public License along
3099+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3100+ *
3101+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
3102+ */
3103+#pragma once
3104+
3105+#include <QDBusMetaType>
3106+#include "pulseaudio-volume.h"
3107+
3108+namespace DBusTypes
3109+{
3110+ inline void registerMetaTypes()
3111+ {
3112+ PulseaudioVolume::registerMetaType();
3113+ PulseaudioVolumeArray::registerMetaType();
3114+ }
3115+
3116+ static constexpr char const* DBUS_NAME = "com.canonical.indicator.sound";
3117+
3118+ static constexpr char const* DBUS_PULSE = "org.PulseAudio1";
3119+
3120+ static constexpr char const* ACCOUNTS_SERVICE = "org.freedesktop.Accounts";
3121+
3122+ static constexpr char const* STREAM_RESTORE_NAME = "org.PulseAudio.Ext.StreamRestore1";
3123+
3124+ static constexpr char const* STREAM_RESTORE_PATH = "/org/pulseaudio/stream_restore1";
3125+
3126+ static constexpr char const* STREAM_RESTORE_ENTRY_NAME = "org.PulseAudio.Ext.StreamRestore1.RestoreEntry";
3127+
3128+ static constexpr char const* MAIN_SERVICE_PATH = "/com/canonical/indicator/sound";
3129+
3130+ static constexpr char const* ACTIONS_INTERFACE = "org.gtk.Actions";
3131+
3132+} // namespace DBusTypes
3133+
3134
3135=== added file 'tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml'
3136--- tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml 1970-01-01 00:00:00 +0000
3137+++ tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml 2016-01-05 11:11:34 +0000
3138@@ -0,0 +1,7 @@
3139+<node>
3140+ <interface name="org.PulseAudio.Ext.StreamRestore1">
3141+ <method name="GetEntryByName">
3142+ <arg direction="in" type="s" name="entry" />
3143+ <arg direction="out" type="o" name="value" />
3144+ </interface>
3145+</node>
3146
3147=== added file 'tests/dbus-types/org.freedesktop.Accounts.xml'
3148--- tests/dbus-types/org.freedesktop.Accounts.xml 1970-01-01 00:00:00 +0000
3149+++ tests/dbus-types/org.freedesktop.Accounts.xml 2016-01-05 11:11:34 +0000
3150@@ -0,0 +1,9 @@
3151+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3152+<node>
3153+ <interface name="org.freedesktop.Accounts">
3154+ <method name="FindUserByName">
3155+ <arg direction="in" type="s" name="user" />
3156+ <arg direction="out" type="o" name="path" />
3157+ </method>
3158+ </interface>
3159+</node>
3160
3161=== added file 'tests/dbus-types/org.freedesktop.DBus.Properties.xml'
3162--- tests/dbus-types/org.freedesktop.DBus.Properties.xml 1970-01-01 00:00:00 +0000
3163+++ tests/dbus-types/org.freedesktop.DBus.Properties.xml 2016-01-05 11:11:34 +0000
3164@@ -0,0 +1,22 @@
3165+<node>
3166+ <interface name="org.freedesktop.DBus.Properties">
3167+ <method name="Set">
3168+ <arg direction="in" type="s" name="entry" />
3169+ <arg direction="in" type="s" name="property" />
3170+ <arg direction="in" type="v" name="value" />
3171+ </method>
3172+
3173+ <method name="Get">
3174+ <arg direction="in" type="s" name="entry" />
3175+ <arg direction="in" type="s" name="property" />
3176+ <arg direction="out" type="v" name="value" />
3177+ </method>
3178+
3179+ <signal name="PropertiesChanged">
3180+ <arg type="s" name="interface_name"/>
3181+ <arg type="a{sv}" name="changed_properties"/>
3182+ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
3183+ <arg type="as" name="invalidated_properties"/>
3184+ </signal>
3185+ </interface>
3186+</node>
3187
3188=== added file 'tests/dbus-types/org.freedesktop.Notifications.xml'
3189--- tests/dbus-types/org.freedesktop.Notifications.xml 1970-01-01 00:00:00 +0000
3190+++ tests/dbus-types/org.freedesktop.Notifications.xml 2016-01-05 11:11:34 +0000
3191@@ -0,0 +1,47 @@
3192+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
3193+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3194+<node>
3195+ <interface name="org.freedesktop.Notifications">
3196+ <signal name="NotificationClosed">
3197+ <arg name="id" type="u" direction="out"/>
3198+ <arg name="reason" type="u" direction="out"/>
3199+ </signal>
3200+ <signal name="ActionInvoked">
3201+ <arg name="id" type="u" direction="out"/>
3202+ <arg name="action_key" type="s" direction="out"/>
3203+ </signal>
3204+ <signal name="dataChanged">
3205+ <arg name="id" type="u" direction="out"/>
3206+ </signal>
3207+ <method name="CloseNotification">
3208+ <arg name="id" type="u" direction="in"/>
3209+ </method>
3210+ <method name="GetServerInformation">
3211+ <arg name="name" type="s" direction="out"/>
3212+ <arg name="vendor" type="s" direction="out"/>
3213+ <arg name="version" type="s" direction="out"/>
3214+ <arg name="specVersion" type="s" direction="out"/>
3215+ </method>
3216+ <method name="GetCapabilities">
3217+ <arg type="as" direction="out"/>
3218+ </method>
3219+ <method name="Notify">
3220+ <arg type="u" direction="out"/>
3221+ <arg name="app_name" type="s" direction="in"/>
3222+ <arg name="replaces_id" type="u" direction="in"/>
3223+ <arg name="app_icon" type="s" direction="in"/>
3224+ <arg name="summary" type="s" direction="in"/>
3225+ <arg name="body" type="s" direction="in"/>
3226+ <arg name="actions" type="as" direction="in"/>
3227+ <arg name="hints" type="a{sv}" direction="in"/>
3228+ <annotation name="org.qtproject.QtDBus.QtTypeName.In6" value="QVariantMap"/>
3229+ <arg name="expire_timeout" type="i" direction="in"/>
3230+ </method>
3231+ <method name="onDataChanged">
3232+ <arg name="id" type="u" direction="in"/>
3233+ </method>
3234+ <method name="onCompleted">
3235+ <arg name="id" type="u" direction="in"/>
3236+ </method>
3237+ </interface>
3238+</node>
3239
3240=== added file 'tests/dbus-types/org.gtk.Actions.xml'
3241--- tests/dbus-types/org.gtk.Actions.xml 1970-01-01 00:00:00 +0000
3242+++ tests/dbus-types/org.gtk.Actions.xml 2016-01-05 11:11:34 +0000
3243@@ -0,0 +1,13 @@
3244+<node>
3245+ <interface name="org.gtk.Actions">
3246+ <signal name="Changed">
3247+ <arg name="removedActions" type="as" direction="in" />
3248+ <arg name="actionsFlags" type="a{sb}" direction="in" />
3249+ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
3250+ <arg name="actionsChanged" type="a{sv}" direction="in" />
3251+ <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
3252+ <arg name="actionsAdded" type="a{s(bgav)}" direction="in" />
3253+ <annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QVariantMap"/>
3254+ </signal>
3255+ </interface>
3256+</node>
3257
3258=== added file 'tests/dbus-types/pulseaudio-volume.cpp'
3259--- tests/dbus-types/pulseaudio-volume.cpp 1970-01-01 00:00:00 +0000
3260+++ tests/dbus-types/pulseaudio-volume.cpp 2016-01-05 11:11:34 +0000
3261@@ -0,0 +1,156 @@
3262+/*
3263+ * Copyright (C) 2015 Canonical, Ltd.
3264+ *
3265+ * This program is free software: you can redistribute it and/or modify it
3266+ * under the terms of the GNU General Public License version 3, as published
3267+ * by the Free Software Foundation.
3268+ *
3269+ * This program is distributed in the hope that it will be useful, but
3270+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3271+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3272+ * PURPOSE. See the GNU General Public License for more details.
3273+ *
3274+ * You should have received a copy of the GNU General Public License along
3275+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3276+ *
3277+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
3278+ */
3279+#include "dbus-types.h"
3280+
3281+PulseaudioVolume::PulseaudioVolume() :
3282+ type_(0),
3283+ volume_(10)
3284+{
3285+}
3286+
3287+PulseaudioVolume::PulseaudioVolume(unsigned int type, unsigned int volume) :
3288+ type_(type)
3289+ , volume_(volume)
3290+{
3291+}
3292+
3293+PulseaudioVolume::PulseaudioVolume(const PulseaudioVolume &other) :
3294+ type_(other.type_),
3295+ volume_(other.volume_)
3296+{
3297+}
3298+
3299+PulseaudioVolume& PulseaudioVolume::operator=(const PulseaudioVolume &other)
3300+{
3301+ type_ = other.type_;
3302+ volume_ = other.volume_;
3303+
3304+ return *this;
3305+}
3306+
3307+PulseaudioVolume::~PulseaudioVolume()
3308+{
3309+}
3310+
3311+unsigned int PulseaudioVolume::getType() const
3312+{
3313+ return type_;
3314+}
3315+
3316+unsigned int PulseaudioVolume::getVolume() const
3317+{
3318+ return volume_;
3319+}
3320+
3321+void PulseaudioVolume::registerMetaType()
3322+{
3323+ qRegisterMetaType<PulseaudioVolume>("PulseaudioVolume");
3324+
3325+ qDBusRegisterMetaType<PulseaudioVolume>();
3326+}
3327+
3328+QDBusArgument &operator<<(QDBusArgument &argument, const PulseaudioVolume& volume)
3329+{
3330+ argument.beginStructure();
3331+ argument << volume.type_;
3332+ argument << volume.volume_;
3333+ argument.endStructure();
3334+
3335+ return argument;
3336+}
3337+
3338+const QDBusArgument &operator>>(const QDBusArgument &argument, PulseaudioVolume &volume)
3339+{
3340+ argument.beginStructure();
3341+ argument >> volume.type_;
3342+ argument >> volume.volume_;
3343+ argument.endStructure();
3344+
3345+ return argument;
3346+}
3347+
3348+PulseaudioVolumeArray::PulseaudioVolumeArray()
3349+{
3350+}
3351+
3352+PulseaudioVolumeArray::PulseaudioVolumeArray(const PulseaudioVolumeArray &other) :
3353+ volume_array_(other.volume_array_)
3354+{
3355+}
3356+
3357+PulseaudioVolumeArray& PulseaudioVolumeArray::operator=(const PulseaudioVolumeArray &other)
3358+{
3359+ volume_array_ = other.volume_array_;
3360+
3361+ return *this;
3362+}
3363+
3364+PulseaudioVolumeArray::~PulseaudioVolumeArray()
3365+{
3366+}
3367+
3368+int PulseaudioVolumeArray::getNumItems() const
3369+{
3370+ return volume_array_.size();
3371+}
3372+
3373+PulseaudioVolume PulseaudioVolumeArray::getItem(int i) const
3374+{
3375+ if (i < volume_array_.size())
3376+ {
3377+ return volume_array_[i];
3378+ }
3379+ return PulseaudioVolume();
3380+}
3381+
3382+void PulseaudioVolumeArray::addItem(PulseaudioVolume const &item)
3383+{
3384+ volume_array_.push_back(item);
3385+}
3386+
3387+void PulseaudioVolumeArray::registerMetaType()
3388+{
3389+ qRegisterMetaType<PulseaudioVolumeArray>("PulseaudioVolumeArray");
3390+
3391+ qDBusRegisterMetaType<PulseaudioVolumeArray>();
3392+}
3393+
3394+QDBusArgument &operator<<(QDBusArgument &argument, const PulseaudioVolumeArray& volume)
3395+{
3396+ argument.beginArray( qMetaTypeId<PulseaudioVolume>() );
3397+ for (int i = 0; i < volume.volume_array_.size(); ++ i)
3398+ {
3399+ PulseaudioVolume item = volume.getItem(i);
3400+ argument << item;
3401+ }
3402+ argument.endArray();
3403+ return argument;
3404+}
3405+
3406+const QDBusArgument &operator>>(const QDBusArgument &argument, PulseaudioVolumeArray &volume)
3407+{
3408+ argument.beginArray();
3409+ while ( !argument.atEnd() ) {
3410+ PulseaudioVolume item;
3411+ argument >> item;
3412+ volume.volume_array_.push_back(item);
3413+ }
3414+ argument.endArray();
3415+
3416+ return argument;
3417+}
3418
3419=== added file 'tests/dbus-types/pulseaudio-volume.h'
3420--- tests/dbus-types/pulseaudio-volume.h 1970-01-01 00:00:00 +0000
3421+++ tests/dbus-types/pulseaudio-volume.h 2016-01-05 11:11:34 +0000
3422@@ -0,0 +1,69 @@
3423+/*
3424+ * Copyright (C) 2015 Canonical, Ltd.
3425+ *
3426+ * This program is free software: you can redistribute it and/or modify it
3427+ * under the terms of the GNU General Public License version 3, as published
3428+ * by the Free Software Foundation.
3429+ *
3430+ * This program is distributed in the hope that it will be useful, but
3431+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3432+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3433+ * PURPOSE. See the GNU General Public License for more details.
3434+ *
3435+ * You should have received a copy of the GNU General Public License along
3436+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3437+ *
3438+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
3439+ */
3440+#pragma once
3441+
3442+#include <QtDBus>
3443+
3444+class PulseaudioVolume
3445+{
3446+public:
3447+ PulseaudioVolume();
3448+ PulseaudioVolume(unsigned int type, unsigned int volume);
3449+ PulseaudioVolume(const PulseaudioVolume &other);
3450+ PulseaudioVolume& operator=(const PulseaudioVolume &other);
3451+ ~PulseaudioVolume();
3452+
3453+ friend QDBusArgument &operator<<(QDBusArgument &argument, PulseaudioVolume const & volume);
3454+ friend const QDBusArgument &operator>>(QDBusArgument const & argument, PulseaudioVolume &volume);
3455+
3456+ unsigned int getType() const;
3457+ unsigned int getVolume() const;
3458+
3459+ //register Message with the Qt type system
3460+ static void registerMetaType();
3461+
3462+private:
3463+ unsigned int type_;
3464+ unsigned int volume_;
3465+};
3466+
3467+class PulseaudioVolumeArray
3468+{
3469+public:
3470+ PulseaudioVolumeArray();
3471+ PulseaudioVolumeArray(QString const &interface, QString const &property, QDBusVariant const& value);
3472+ PulseaudioVolumeArray(const PulseaudioVolumeArray &other);
3473+ PulseaudioVolumeArray& operator=(const PulseaudioVolumeArray &other);
3474+ ~PulseaudioVolumeArray();
3475+
3476+ friend QDBusArgument &operator<<(QDBusArgument &argument, PulseaudioVolumeArray const & volume);
3477+ friend const QDBusArgument &operator>>(QDBusArgument const & argument, PulseaudioVolumeArray &volume);
3478+
3479+ int getNumItems() const;
3480+ PulseaudioVolume getItem(int i) const;
3481+ void addItem(PulseaudioVolume const &item);
3482+
3483+ //register Message with the Qt type system
3484+ static void registerMetaType();
3485+
3486+private:
3487+ QVector<PulseaudioVolume> volume_array_;
3488+};
3489+
3490+Q_DECLARE_METATYPE(PulseaudioVolume)
3491+Q_DECLARE_METATYPE(PulseaudioVolumeArray)
3492
3493=== added directory 'tests/integration'
3494=== added file 'tests/integration/CMakeLists.txt'
3495--- tests/integration/CMakeLists.txt 1970-01-01 00:00:00 +0000
3496+++ tests/integration/CMakeLists.txt 2016-01-05 11:11:34 +0000
3497@@ -0,0 +1,132 @@
3498+set(CMAKE_AUTOMOC ON)
3499+set(CMAKE_INCLUDE_CURRENT_DIR ON)
3500+
3501+include(FindGMock)
3502+
3503+#pkg_check_modules(GMENUHARNESS REQUIRED libgmenuharness REQUIRED)
3504+#include_directories(${GMENUHARNESS_INCLUDE_DIRS})
3505+include_directories("${CMAKE_SOURCE_DIR}/include")
3506+
3507+pkg_check_modules(QTDBUSTEST REQUIRED libqtdbustest-1 REQUIRED)
3508+include_directories(${QTDBUSTEST_INCLUDE_DIRS})
3509+
3510+pkg_check_modules(QTDBUSMOCK REQUIRED libqtdbusmock-1 REQUIRED)
3511+include_directories(${QTDBUSMOCK_INCLUDE_DIRS})
3512+
3513+find_package(Qt5Test REQUIRED)
3514+include_directories(${Qt5Test_INCLUDE_DIRS})
3515+
3516+find_package(Qt5DBus REQUIRED)
3517+include_directories(${Qt5DBus_INCLUDE_DIRS})
3518+
3519+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
3520+include_directories(${GMOCK_INCLUDE_DIRS})
3521+include_directories(${GTEST_INCLUDE_DIRS})
3522+
3523+include_directories("${CMAKE_SOURCE_DIR}/tests/dbus-types")
3524+include_directories("${CMAKE_BINARY_DIR}/tests/dbus-types")
3525+
3526+add_definitions(-DSOUND_SERVICE_BIN="${CMAKE_BINARY_DIR}/src/indicator-sound-service"
3527+ -DSTREAM_RESTORE_TABLE="${CMAKE_SOURCE_DIR}/tests/integration/touch-stream-restore.table"
3528+ -DVOLUME_SET_BIN="${CMAKE_BINARY_DIR}/tests/integration/set-volume"
3529+ -DACCOUNTS_SERVICE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/accounts-mock/accounts-service-sound"
3530+ -DMEDIA_PLAYER_MPRIS_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock"
3531+ -DMEDIA_PLAYER_MPRIS_UPDATE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock-update"
3532+ -DTEST_SOUND="${CMAKE_SOURCE_DIR}/tests/integration/test-sound.wav"
3533+ -DQT_NO_KEYWORDS=1
3534+ -DXDG_DATA_DIRS="${XDG_DATA_DIRS}"
3535+)
3536+
3537+set(GLIB_REQUIRED_VERSION 2.26)
3538+
3539+pkg_check_modules(
3540+ GLIB REQUIRED
3541+ glib-2.0>=${GLIB_REQUIRED_VERSION}
3542+ gio-2.0>=${GLIB_REQUIRED_VERSION}
3543+)
3544+include_directories(${GLIB_INCLUDE_DIRS})
3545+
3546+set(
3547+ INTEGRATION_TESTS_SRC
3548+ indicator-sound-test-base.cpp
3549+ test-indicator.cpp
3550+ utils/dbus-pulse-volume.cpp
3551+ main.cpp
3552+)
3553+
3554+add_executable(
3555+ integration-tests
3556+ ${INTEGRATION_TESTS_SRC}
3557+)
3558+
3559+qt5_use_modules(
3560+ integration-tests
3561+ Core
3562+ DBus
3563+ Test
3564+)
3565+
3566+target_link_libraries(
3567+ integration-tests
3568+ sound-indicator-dbus-interfaces
3569+ ${QTDBUSMOCK_LDFLAGS}
3570+ ${QTDBUSTEST_LDFLAGS}
3571+ ${GTEST_LIBRARIES}
3572+ ${GMOCK_LIBRARIES}
3573+# ${GMENUHARNESS_LDFLAGS}
3574+ ${GLIB_LDFLAGS}
3575+ gmenuharness-shared
3576+)
3577+
3578+add_test(
3579+ integration-tests
3580+ integration-tests
3581+)
3582+
3583+set(
3584+ SET-VOLUME-SRC
3585+ utils/dbus-pulse-volume.cpp
3586+ utils/set-volume.cpp
3587+)
3588+
3589+set(
3590+ GET-VOLUME-SRC
3591+ utils/dbus-pulse-volume.cpp
3592+ utils/get-volume.cpp
3593+)
3594+
3595+add_executable(
3596+ set-volume
3597+ ${SET-VOLUME-SRC}
3598+)
3599+
3600+add_executable(
3601+ get-volume
3602+ ${GET-VOLUME-SRC}
3603+)
3604+
3605+qt5_use_modules(
3606+ set-volume
3607+ Core
3608+ DBus
3609+ Test
3610+)
3611+
3612+qt5_use_modules(
3613+ get-volume
3614+ Core
3615+ DBus
3616+ Test
3617+)
3618+
3619+target_link_libraries(
3620+ get-volume
3621+ sound-indicator-dbus-interfaces
3622+)
3623+
3624+target_link_libraries(
3625+ set-volume
3626+ sound-indicator-dbus-interfaces
3627+)
3628+
3629+#add_subdirectory(utils)
3630\ No newline at end of file
3631
3632=== added file 'tests/integration/Copy of test-sound.wav'
3633Binary files tests/integration/Copy of test-sound.wav 1970-01-01 00:00:00 +0000 and tests/integration/Copy of test-sound.wav 2016-01-05 11:11:34 +0000 differ
3634=== added file 'tests/integration/indicator-sound-test-base.cpp'
3635--- tests/integration/indicator-sound-test-base.cpp 1970-01-01 00:00:00 +0000
3636+++ tests/integration/indicator-sound-test-base.cpp 2016-01-05 11:11:34 +0000
3637@@ -0,0 +1,823 @@
3638+/*
3639+ * Copyright (C) 2015 Canonical, Ltd.
3640+ *
3641+ * This program is free software: you can redistribute it and/or modify it
3642+ * under the terms of the GNU General Public License version 3, as published
3643+ * by the Free Software Foundation.
3644+ *
3645+ * This program is distributed in the hope that it will be useful, but
3646+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3647+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3648+ * PURPOSE. See the GNU General Public License for more details.
3649+ *
3650+ * You should have received a copy of the GNU General Public License along
3651+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3652+ *
3653+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
3654+ */
3655+
3656+#include "indicator-sound-test-base.h"
3657+
3658+#include "dbus_menus_interface.h"
3659+#include "dbus_properties_interface.h"
3660+#include "dbus_accounts_interface.h"
3661+#include "dbus_accountssound_interface.h"
3662+#include "dbus_notifications_interface.h"
3663+#include "dbus-types.h"
3664+
3665+#include <gio/gio.h>
3666+#include <chrono>
3667+#include <thread>
3668+
3669+#include <QSignalSpy>
3670+#include "utils/dbus-pulse-volume.h"
3671+
3672+using namespace QtDBusTest;
3673+using namespace QtDBusMock;
3674+using namespace std;
3675+using namespace testing;
3676+namespace mh = unity::gmenuharness;
3677+
3678+namespace
3679+{
3680+ const int MAX_TIME_WAITING_FOR_NOTIFICATIONS = 2000;
3681+}
3682+
3683+IndicatorSoundTestBase::IndicatorSoundTestBase() :
3684+ dbusMock(dbusTestRunner)
3685+{
3686+}
3687+
3688+IndicatorSoundTestBase::~IndicatorSoundTestBase()
3689+{
3690+}
3691+
3692+void IndicatorSoundTestBase::SetUp()
3693+{
3694+ setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, true);
3695+ setenv("DBUS_SYSTEM_BUS_ADDRESS", dbusTestRunner.systemBus().toStdString().c_str(), true);
3696+ setenv("DBUS_SESSION_BUS_ADDRESS", dbusTestRunner.sessionBus().toStdString().c_str(), true);
3697+ dbusMock.registerNotificationDaemon();
3698+
3699+ dbusTestRunner.startServices();
3700+
3701+ auto& notifications = notificationsMockInterface();
3702+ notifications.AddMethod("org.freedesktop.Notifications",
3703+ "GetCapabilities",
3704+ "",
3705+ "as",
3706+ "ret = ['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']"
3707+ ).waitForFinished();
3708+
3709+ int waitedTime = 0;
3710+ while (!dbusTestRunner.sessionConnection().interface()->isServiceRegistered("org.freedesktop.Notifications") && waitedTime < MAX_TIME_WAITING_FOR_NOTIFICATIONS)
3711+ {
3712+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
3713+ waitedTime += 10;
3714+ }
3715+}
3716+
3717+void IndicatorSoundTestBase::TearDown()
3718+{
3719+ unsetenv("XDG_DATA_DIRS");
3720+ unsetenv("PULSE_SERVER");
3721+ unsetenv("DBUS_SYSTEM_BUS_ADDRESS");
3722+}
3723+
3724+void gvariant_deleter(GVariant* varptr)
3725+{
3726+ if (varptr != nullptr)
3727+ {
3728+ g_variant_unref(varptr);
3729+ }
3730+}
3731+
3732+std::shared_ptr<GVariant> IndicatorSoundTestBase::volume_variant(double volume)
3733+{
3734+ GVariantBuilder builder;
3735+
3736+ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
3737+ g_variant_builder_add(&builder,
3738+ "{sv}",
3739+ "title",
3740+ g_variant_new_string("_Sound"));
3741+
3742+ g_variant_builder_add(&builder,
3743+ "{sv}",
3744+ "accessible-desc",
3745+ g_variant_new_string("_Sound"));
3746+
3747+ auto icon = g_themed_icon_new("icon");
3748+ g_variant_builder_add(&builder,
3749+ "{sv}",
3750+ "icon",
3751+ g_icon_serialize(icon));
3752+
3753+ g_variant_builder_add(&builder,
3754+ "{sv}",
3755+ "visible",
3756+ g_variant_new_boolean(true));
3757+ return shared_ptr<GVariant>(g_variant_builder_end(&builder), &gvariant_deleter);
3758+}
3759+
3760+bool IndicatorSoundTestBase::setStreamRestoreVolume(QString const &role, double volume)
3761+{
3762+ QProcess setVolume;
3763+ setVolume.start(VOLUME_SET_BIN, QStringList()
3764+ << role
3765+ << QString("%1").arg(volume));
3766+ if (!setVolume.waitForStarted())
3767+ return false;
3768+
3769+ if (!setVolume.waitForFinished())
3770+ return false;
3771+
3772+ return setVolume.exitCode() == 0;
3773+}
3774+
3775+bool IndicatorSoundTestBase::setSinkVolume(double volume)
3776+{
3777+ QString volume_percentage = QString("%1\%").arg(volume*100);
3778+ QProcess setVolume;
3779+ setVolume.start("pactl", QStringList()
3780+ << "-s"
3781+ << "127.0.0.1"
3782+ << "set-sink-volume"
3783+ << "0"
3784+ << volume_percentage);
3785+ if (!setVolume.waitForStarted())
3786+ return false;
3787+
3788+ if (!setVolume.waitForFinished())
3789+ return false;
3790+
3791+ return setVolume.exitCode() == 0;
3792+}
3793+
3794+bool IndicatorSoundTestBase::clearGSettingsPlayers()
3795+{
3796+ QProcess clearPlayers;
3797+
3798+ clearPlayers.start("gsettings", QStringList()
3799+ << "set"
3800+ << "com.canonical.indicator.sound"
3801+ << "interested-media-players"
3802+ << "[]");
3803+ if (!clearPlayers.waitForStarted())
3804+ return false;
3805+
3806+ if (!clearPlayers.waitForFinished())
3807+ return false;
3808+
3809+ return clearPlayers.exitCode() == 0;
3810+}
3811+
3812+bool IndicatorSoundTestBase::startTestMprisPlayer(QString const& playerName)
3813+{
3814+ testPlayer1.terminate();
3815+ testPlayer1.start(MEDIA_PLAYER_MPRIS_BIN, QStringList()
3816+ << playerName);
3817+ if (!testPlayer1.waitForStarted())
3818+ return false;
3819+
3820+
3821+ return true;
3822+}
3823+
3824+bool IndicatorSoundTestBase::setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value)
3825+{
3826+ QProcess setProperty;
3827+ QString strValue;
3828+ strValue = value ? "true" : "false";
3829+
3830+ setProperty.start(MEDIA_PLAYER_MPRIS_UPDATE_BIN, QStringList()
3831+ << testPlayer
3832+ << property
3833+ << strValue);
3834+ if (!setProperty.waitForStarted())
3835+ return false;
3836+
3837+ if (!setProperty.waitForFinished())
3838+ return false;
3839+
3840+ return setProperty.exitCode() == 0;
3841+}
3842+
3843+bool IndicatorSoundTestBase::startTestSound(QString const &role)
3844+{
3845+ testSoundProcess.terminate();
3846+ testSoundProcess.start("paplay", QStringList()
3847+ << "-s"
3848+ << "127.0.0.1"
3849+ << TEST_SOUND
3850+ << QString("--property=media.role=%1").arg(role));
3851+
3852+ if (!testSoundProcess.waitForStarted())
3853+ return false;
3854+
3855+ return true;
3856+}
3857+
3858+void IndicatorSoundTestBase::stopTestSound()
3859+{
3860+ testSoundProcess.terminate();
3861+}
3862+
3863+void IndicatorSoundTestBase::startPulseDesktop(DevicePortType speakerPort, DevicePortType headphonesPort)
3864+{
3865+ try
3866+ {
3867+ pulseaudio.reset(
3868+ new QProcessDBusService(DBusTypes::DBUS_PULSE,
3869+ QDBusConnection::SessionBus,
3870+ "pulseaudio",
3871+ QStringList() << "--start"
3872+ << "-vvvv"
3873+ << "--disable-shm=true"
3874+ << "--daemonize=false"
3875+ << "--use-pid-file=false"
3876+ << "--system=false"
3877+ << "--exit-idle-time=-1"
3878+ << "-n"
3879+ << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort))
3880+ << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort))
3881+ << "--log-target=file:/tmp/pulse-daemon.log"
3882+ << "--load=module-dbus-protocol"
3883+ << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
3884+ ));
3885+ pulseaudio->start(dbusTestRunner.sessionConnection());
3886+ }
3887+ catch (exception const& e)
3888+ {
3889+ cout << "pulseaudio(): " << e.what() << endl;
3890+ throw;
3891+ }
3892+}
3893+
3894+void IndicatorSoundTestBase::startPulsePhone(DevicePortType speakerPort, DevicePortType headphonesPort)
3895+{
3896+ try
3897+ {
3898+ pulseaudio.reset(
3899+ new QProcessDBusService(DBusTypes::DBUS_PULSE,
3900+ QDBusConnection::SessionBus,
3901+ "pulseaudio",
3902+ QStringList() << "--start"
3903+ << "-vvvv"
3904+ << "--disable-shm=true"
3905+ << "--daemonize=false"
3906+ << "--use-pid-file=false"
3907+ << "--system=false"
3908+ << "--exit-idle-time=-1"
3909+ << "-n"
3910+ << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort))
3911+ << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort))
3912+ << "--log-target=file:/tmp/pulse-daemon.log"
3913+ << QString("--load=module-stream-restore restore_device=false restore_muted=false fallback_table=%1").arg(STREAM_RESTORE_TABLE)
3914+ << "--load=module-dbus-protocol"
3915+ << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
3916+ ));
3917+ pulseaudio->start(dbusTestRunner.sessionConnection());
3918+ }
3919+ catch (exception const& e)
3920+ {
3921+ cout << "pulseaudio(): " << e.what() << endl;
3922+ throw;
3923+ }
3924+}
3925+
3926+void IndicatorSoundTestBase::startAccountsService()
3927+{
3928+ try
3929+ {
3930+ accountsService.reset(
3931+ new QProcessDBusService(DBusTypes::ACCOUNTS_SERVICE,
3932+ QDBusConnection::SystemBus,
3933+ ACCOUNTS_SERVICE_BIN,
3934+ QStringList()));
3935+ accountsService->start(dbusTestRunner.systemConnection());
3936+
3937+ initializeAccountsInterface();
3938+ }
3939+ catch (exception const& e)
3940+ {
3941+ cout << "accountsService(): " << e.what() << endl;
3942+ throw;
3943+ }
3944+}
3945+
3946+void IndicatorSoundTestBase::startIndicator()
3947+{
3948+ try
3949+ {
3950+ setenv("PULSE_SERVER", "127.0.0.1", true);
3951+ indicator.reset(
3952+ new QProcessDBusService(DBusTypes::DBUS_NAME,
3953+ QDBusConnection::SessionBus,
3954+ SOUND_SERVICE_BIN,
3955+ QStringList()));
3956+ indicator->start(dbusTestRunner.sessionConnection());
3957+ }
3958+ catch (exception const& e)
3959+ {
3960+ cout << "startIndicator(): " << e.what() << endl;
3961+ throw;
3962+ }
3963+}
3964+
3965+mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters()
3966+{
3967+ return mh::MenuMatcher::Parameters(
3968+ "com.canonical.indicator.sound",
3969+ { { "indicator", "/com/canonical/indicator/sound" } },
3970+ "/com/canonical/indicator/sound/desktop");
3971+}
3972+
3973+mh::MenuMatcher::Parameters IndicatorSoundTestBase::phoneParameters()
3974+{
3975+ return mh::MenuMatcher::Parameters(
3976+ "com.canonical.indicator.sound",
3977+ { { "indicator", "/com/canonical/indicator/sound" } },
3978+ "/com/canonical/indicator/sound/phone");
3979+}
3980+
3981+unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double volume, QString const &label)
3982+{
3983+ return mh::MenuItemMatcher().radio()
3984+ .label(label.toStdString())
3985+ .round_doubles(0.1)
3986+ .int32_attribute("target", 0)
3987+ .double_attribute("min-value", 0.0)
3988+ .double_attribute("max-value", 1.0)
3989+ .double_attribute("step", 0.01)
3990+ .string_attribute("x-canonical-type", "com.canonical.unity.slider")
3991+ .themed_icon("max-icon", {"audio-volume-high-panel", "audio-volume-high", "audio-volume", "audio"})
3992+ .themed_icon("min-icon", {"audio-volume-low-zero-panel", "audio-volume-low-zero", "audio-volume-low", "audio-volume", "audio"})
3993+ .pass_through_double_attribute("action", volume);
3994+}
3995+
3996+unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::silentModeSwitch(bool toggled)
3997+{
3998+ return mh::MenuItemMatcher::checkbox()
3999+ .label("Silent Mode")
4000+ .action("indicator.silent-mode")
4001+ .toggled(toggled);
4002+}
4003+
4004+bool IndicatorSoundTestBase::waitMenuChange()
4005+{
4006+ if (signal_spy_menu_changed_)
4007+ {
4008+ return signal_spy_menu_changed_->wait();
4009+ }
4010+ return false;
4011+}
4012+
4013+bool IndicatorSoundTestBase::initializeMenuChangedSignal()
4014+{
4015+ if (!menu_interface_)
4016+ {
4017+ menu_interface_.reset(new MenusInterface("com.canonical.indicator.sound",
4018+ "/com/canonical/indicator/sound",
4019+ dbusTestRunner.sessionConnection(), 0));
4020+ }
4021+ if (menu_interface_)
4022+ {
4023+ qDebug() << "Waiting for signal";
4024+ signal_spy_menu_changed_.reset(new QSignalSpy(menu_interface_.get(), &MenusInterface::Changed));
4025+ }
4026+ if (!menu_interface_ || !signal_spy_menu_changed_)
4027+ {
4028+ return false;
4029+ }
4030+ return true;
4031+}
4032+
4033+bool IndicatorSoundTestBase::waitVolumeChangedInIndicator()
4034+{
4035+ if (signal_spy_volume_changed_)
4036+ {
4037+ return signal_spy_volume_changed_->wait();
4038+ }
4039+ return false;
4040+}
4041+
4042+void IndicatorSoundTestBase::initializeAccountsInterface()
4043+{
4044+ auto username = qgetenv("USER");
4045+ if (username != "")
4046+ {
4047+ main_accounts_interface_.reset(new AccountsInterface("org.freedesktop.Accounts",
4048+ "/org/freedesktop/Accounts",
4049+ dbusTestRunner.systemConnection(), 0));
4050+
4051+ QDBusReply<QDBusObjectPath> userResp = main_accounts_interface_->call(QLatin1String("FindUserByName"),
4052+ QLatin1String(username));
4053+
4054+ if (!userResp.isValid())
4055+ {
4056+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << userResp.error().message();
4057+ }
4058+
4059+ auto userPath = userResp.value().path();
4060+ if (userPath != "")
4061+ {
4062+ std::unique_ptr<AccountsSoundInterface> soundInterface(new AccountsSoundInterface("org.freedesktop.Accounts",
4063+ userPath,
4064+ dbusTestRunner.systemConnection(), 0));
4065+
4066+ accounts_interface_.reset(new DBusPropertiesInterface("org.freedesktop.Accounts",
4067+ userPath,
4068+ dbusTestRunner.systemConnection(), 0));
4069+ if (!accounts_interface_->isValid())
4070+ {
4071+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << accounts_interface_->lastError().message();
4072+ }
4073+ signal_spy_volume_changed_.reset(new QSignalSpy(accounts_interface_.get(),&DBusPropertiesInterface::PropertiesChanged));
4074+ }
4075+ }
4076+}
4077+
4078+OrgFreedesktopDBusMockInterface& IndicatorSoundTestBase::notificationsMockInterface()
4079+{
4080+ return dbusMock.mockInterface("org.freedesktop.Notifications",
4081+ "/org/freedesktop/Notifications",
4082+ "org.freedesktop.Notifications",
4083+ QDBusConnection::SessionBus);
4084+}
4085+
4086+bool IndicatorSoundTestBase::setActionValue(const QString & action, QVariant value)
4087+{
4088+ QDBusInterface actionsInterface(DBusTypes::DBUS_NAME,
4089+ DBusTypes::MAIN_SERVICE_PATH,
4090+ DBusTypes::ACTIONS_INTERFACE,
4091+ dbusTestRunner.sessionConnection());
4092+
4093+ QDBusVariant dbusVar(value);
4094+ auto resp = actionsInterface.call("SetState",
4095+ action,
4096+ QVariant::fromValue(dbusVar),
4097+ QVariant::fromValue(QVariantMap()));
4098+
4099+ if (resp.type() == QDBusMessage::ErrorMessage)
4100+ {
4101+ qCritical() << "IndicatorSoundTestBase::setActionValue(): Failed to set value for action "
4102+ << action
4103+ << " "
4104+ << resp.errorMessage();
4105+ return false;
4106+ }
4107+ else
4108+ {
4109+ return true;
4110+ }
4111+}
4112+
4113+bool IndicatorSoundTestBase::pressNotificationButton(int id, const QString & button)
4114+{
4115+ OrgFreedesktopDBusMockInterface actionsInterface("org.freedesktop.Notifications",
4116+ "/org/freedesktop/Notifications",
4117+ dbusTestRunner.sessionConnection());
4118+
4119+ actionsInterface.EmitSignal(
4120+ "org.freedesktop.Notifications",
4121+ "ActionInvoked", "us", QVariantList() << id << button);
4122+
4123+ return true;
4124+}
4125+
4126+bool IndicatorSoundTestBase::qDBusArgumentToMap(QVariant const& variant, QVariantMap& map)
4127+{
4128+ if (variant.canConvert<QDBusArgument>())
4129+ {
4130+ QDBusArgument value(variant.value<QDBusArgument>());
4131+ if (value.currentType() == QDBusArgument::MapType)
4132+ {
4133+ value >> map;
4134+ return true;
4135+ }
4136+ }
4137+ return false;
4138+}
4139+
4140+void IndicatorSoundTestBase::checkVolumeNotification(double volume, QString const& label, bool isLoud, QVariantList call)
4141+{
4142+ QString icon;
4143+ if (volume <= 0.0)
4144+ {
4145+ icon = "audio-volume-muted";
4146+ }
4147+ else if (volume <= 0.3)
4148+ {
4149+ icon = "audio-volume-low";
4150+ }
4151+ else if (volume <= 0.7)
4152+ {
4153+ icon = "audio-volume-medium";
4154+ }
4155+ else
4156+ {
4157+ icon = "audio-volume-high";
4158+ }
4159+
4160+ ASSERT_NE(call.size(), 0);
4161+ EXPECT_EQ("Notify", call.at(0));
4162+
4163+ QVariantList const& args(call.at(1).toList());
4164+ ASSERT_EQ(8, args.size());
4165+ EXPECT_EQ("indicator-sound", args.at(0));
4166+ EXPECT_EQ(icon, args.at(2));
4167+ EXPECT_EQ("Volume", args.at(3));
4168+ EXPECT_EQ(label, args.at(4));
4169+ EXPECT_EQ(QStringList(), args.at(5));
4170+
4171+ QVariantMap hints;
4172+ ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints));
4173+ ASSERT_TRUE(hints.contains("value"));
4174+ ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon"));
4175+ ASSERT_TRUE(hints.contains("x-canonical-value-bar-tint"));
4176+ ASSERT_TRUE(hints.contains("x-canonical-private-synchronous"));
4177+
4178+ EXPECT_EQ(volume*100, hints["value"]);
4179+ EXPECT_EQ(true, hints["x-canonical-non-shaped-icon"]);
4180+ EXPECT_EQ(isLoud, hints["x-canonical-value-bar-tint"]);
4181+ EXPECT_EQ(true, hints["x-canonical-private-synchronous"]);
4182+}
4183+
4184+void IndicatorSoundTestBase::checkHighVolumeNotification(QVariantList call)
4185+{
4186+ ASSERT_NE(call.size(), 0);
4187+ EXPECT_EQ("Notify", call.at(0));
4188+
4189+ QVariantList const& args(call.at(1).toList());
4190+ ASSERT_EQ(8, args.size());
4191+ EXPECT_EQ("indicator-sound", args.at(0));
4192+ EXPECT_EQ("Volume", args.at(3));
4193+}
4194+
4195+void IndicatorSoundTestBase::checkCloseNotification(int id, QVariantList call)
4196+{
4197+ EXPECT_EQ("CloseNotification", call.at(0));
4198+ QVariantList const& args(call.at(1).toList());
4199+ ASSERT_EQ(1, args.size());
4200+}
4201+
4202+void IndicatorSoundTestBase::checkNotificationWithNoArgs(QString const& method, QVariantList call)
4203+{
4204+ EXPECT_EQ(method, call.at(0));
4205+ QVariantList const& args(call.at(1).toList());
4206+ ASSERT_EQ(0, args.size());
4207+}
4208+
4209+int IndicatorSoundTestBase::getNotificationID(QVariantList call)
4210+{
4211+ if (call.size() == 0)
4212+ {
4213+ return -1;
4214+ }
4215+ QVariantList const& args(call.at(1).toList());
4216+ if (args.size() != 8)
4217+ {
4218+ return -1;
4219+ }
4220+ if (args.at(0) != "indicator-sound")
4221+ {
4222+ return -1;
4223+ }
4224+
4225+ bool isInt;
4226+ int id = args.at(1).toInt(&isInt);
4227+ if (!isInt)
4228+ {
4229+ return -1;
4230+ }
4231+ return id;
4232+}
4233+
4234+bool IndicatorSoundTestBase::activateHeadphones(bool headphonesActive)
4235+{
4236+ QProcess pacltProcess;
4237+
4238+ QString defaultSinkName = "indicator_sound_test_speaker";
4239+ QString suspendedSinkName = "indicator_sound_test_headphones";
4240+ if (headphonesActive)
4241+ {
4242+ defaultSinkName = "indicator_sound_test_headphones";
4243+ suspendedSinkName = "indicator_sound_test_speaker";
4244+ }
4245+
4246+ pacltProcess.start("pactl", QStringList() << "-s"
4247+ << "127.0.0.1"
4248+ << "set-default-sink"
4249+ << defaultSinkName);
4250+ if (!pacltProcess.waitForStarted())
4251+ return false;
4252+
4253+ if (!pacltProcess.waitForFinished())
4254+ return false;
4255+
4256+ pacltProcess.start("pactl", QStringList() << "-s"
4257+ << "127.0.0.1"
4258+ << "suspend-sink"
4259+ << defaultSinkName
4260+ << "0");
4261+ if (!pacltProcess.waitForStarted())
4262+ return false;
4263+
4264+ if (!pacltProcess.waitForFinished())
4265+ return false;
4266+
4267+ pacltProcess.start("pactl", QStringList() << "-s"
4268+ << "127.0.0.1"
4269+ << "suspend-sink"
4270+ << suspendedSinkName
4271+ << "1");
4272+ if (!pacltProcess.waitForStarted())
4273+ return false;
4274+
4275+ if (!pacltProcess.waitForFinished())
4276+ return false;
4277+
4278+ return pacltProcess.exitCode() == 0;
4279+}
4280+
4281+QString IndicatorSoundTestBase::getDevicePortString(DevicePortType port)
4282+{
4283+ QString portString;
4284+
4285+ switch (port)
4286+ {
4287+ case WIRED:
4288+ portString = "wired";
4289+ break;
4290+ case BLUETOOTH:
4291+ portString = "bluetooth";
4292+ break;
4293+ case USB:
4294+ portString = "usb";
4295+ break;
4296+ case HDMI:
4297+ portString = "hdmi";
4298+ break;
4299+ default:
4300+ portString = "not_defined";
4301+ break;
4302+ }
4303+
4304+ return portString;
4305+}
4306+
4307+void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, DevicePortType headphonesPort)
4308+{
4309+ double INITIAL_VOLUME = 0.0;
4310+
4311+ QString speakerString;
4312+ QString speakerStringMenu;
4313+ switch(speakerPort)
4314+ {
4315+ case WIRED:
4316+ speakerString = "Speakers";
4317+ speakerStringMenu = "Volume";
4318+ break;
4319+ case BLUETOOTH:
4320+ speakerString = "Bluetooth speaker";
4321+ speakerStringMenu = "Volume (Bluetooth)";
4322+ break;
4323+ case USB:
4324+ speakerString = "Usb speaker";
4325+ speakerStringMenu = "Volume (Usb)";
4326+ break;
4327+ case HDMI:
4328+ speakerString = "HDMI speaker";
4329+ speakerStringMenu = "Volume (HDMI)";
4330+ break;
4331+ }
4332+
4333+ QString headphonesString;
4334+ QString headphonesStringMenu;
4335+ switch(headphonesPort)
4336+ {
4337+ case WIRED:
4338+ headphonesString = "Headphones";
4339+ headphonesStringMenu = "Volume (Headphones)";
4340+ break;
4341+ case BLUETOOTH:
4342+ headphonesString = "Bluetooth headphones";
4343+ headphonesStringMenu = "Volume (Bluetooth headphones)";
4344+ break;
4345+ case USB:
4346+ headphonesString = "Usb headphones";
4347+ headphonesStringMenu = "Volume (Usb headphones)";
4348+ break;
4349+ case HDMI:
4350+ headphonesString = "HDMI headphones";
4351+ headphonesStringMenu = "Volume (HDMI headphones)";
4352+ break;
4353+ }
4354+
4355+ QSignalSpy notificationsSpy(&notificationsMockInterface(),
4356+ SIGNAL(MethodCalled(const QString &, const QVariantList &)));
4357+
4358+ ASSERT_NO_THROW(startAccountsService());
4359+ ASSERT_NO_THROW(startPulsePhone(speakerPort, headphonesPort));
4360+
4361+ // initialize volumes in pulseaudio
4362+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
4363+ EXPECT_TRUE(setStreamRestoreVolume("multimedia", INITIAL_VOLUME));
4364+
4365+ // start now the indicator, so it picks the new volumes
4366+ ASSERT_NO_THROW(startIndicator());
4367+
4368+ // if the speaker is the normal one it does not emit any notification, as that's
4369+ // the default one.
4370+ // for the rest it notifies the output
4371+ if (speakerPort != WIRED)
4372+ {
4373+ WAIT_FOR_SIGNALS(notificationsSpy, 3);
4374+
4375+ // the first time we also have the calls to
4376+ // GetServerInformation and GetCapabilities
4377+ checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
4378+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
4379+ checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(2));
4380+ notificationsSpy.clear();
4381+ }
4382+
4383+ notificationsSpy.clear();
4384+ // activate the headphones
4385+ EXPECT_TRUE(activateHeadphones(true));
4386+
4387+ if (speakerPort == WIRED)
4388+ {
4389+ WAIT_FOR_SIGNALS(notificationsSpy, 3);
4390+
4391+ // the first time we also have the calls to
4392+ // GetServerInformation and GetCapabilities
4393+ checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
4394+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
4395+ checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(2));
4396+ notificationsSpy.clear();
4397+ }
4398+ else
4399+ {
4400+ WAIT_FOR_SIGNALS(notificationsSpy, 2);
4401+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
4402+ checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(1));
4403+ notificationsSpy.clear();
4404+ }
4405+
4406+ // check the label in the menu
4407+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4408+ .item(mh::MenuItemMatcher()
4409+ .action("indicator.root")
4410+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4411+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
4412+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4413+ .string_attribute("submenu-action", "indicator.indicator-shown")
4414+ .mode(mh::MenuItemMatcher::Mode::starts_with)
4415+ .submenu()
4416+ .item(mh::MenuItemMatcher()
4417+ .section()
4418+ .item(silentModeSwitch(false))
4419+ .item(volumeSlider(INITIAL_VOLUME, headphonesStringMenu))
4420+ )
4421+ ).match());
4422+
4423+ // deactivate the headphones
4424+ EXPECT_TRUE(activateHeadphones(false));
4425+
4426+ WAIT_FOR_SIGNALS(notificationsSpy, 2);
4427+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
4428+ checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(1));
4429+ notificationsSpy.clear();
4430+
4431+ // check the label in the menu
4432+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4433+ .item(mh::MenuItemMatcher()
4434+ .action("indicator.root")
4435+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4436+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
4437+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4438+ .string_attribute("submenu-action", "indicator.indicator-shown")
4439+ .mode(mh::MenuItemMatcher::Mode::starts_with)
4440+ .submenu()
4441+ .item(mh::MenuItemMatcher()
4442+ .section()
4443+ .item(silentModeSwitch(false))
4444+ .item(volumeSlider(INITIAL_VOLUME, speakerStringMenu))
4445+ )
4446+ ).match());
4447+}
4448+
4449+bool IndicatorSoundTestBase::setVolumeUntilAccountsIsConnected(double volume)
4450+{
4451+ int RETRY_TIME = 5000;
4452+
4453+ setActionValue("volume", QVariant::fromValue(volume));
4454+ while(!signal_spy_volume_changed_->wait(10) && RETRY_TIME)
4455+ {
4456+ RETRY_TIME -= 10;
4457+ setActionValue("volume", QVariant::fromValue(volume));
4458+ }
4459+ return (signal_spy_volume_changed_->count() != 0);
4460+}
4461
4462=== added file 'tests/integration/indicator-sound-test-base.h'
4463--- tests/integration/indicator-sound-test-base.h 1970-01-01 00:00:00 +0000
4464+++ tests/integration/indicator-sound-test-base.h 2016-01-05 11:11:34 +0000
4465@@ -0,0 +1,160 @@
4466+/*
4467+ * Copyright (C) 2015 Canonical, Ltd.
4468+ *
4469+ * This program is free software: you can redistribute it and/or modify it
4470+ * under the terms of the GNU General Public License version 3, as published
4471+ * by the Free Software Foundation.
4472+ *
4473+ * This program is distributed in the hope that it will be useful, but
4474+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4475+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4476+ * PURPOSE. See the GNU General Public License for more details.
4477+ *
4478+ * You should have received a copy of the GNU General Public License along
4479+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4480+ *
4481+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4482+ */
4483+
4484+#pragma once
4485+
4486+#include <libqtdbustest/DBusTestRunner.h>
4487+#include <libqtdbustest/QProcessDBusService.h>
4488+#include <libqtdbusmock/DBusMock.h>
4489+
4490+#include <unity/gmenuharness/MatchUtils.h>
4491+#include <unity/gmenuharness/MenuMatcher.h>
4492+
4493+#include <gmock/gmock.h>
4494+#include <gtest/gtest.h>
4495+
4496+class MenusInterface;
4497+class DBusPulseVolume;
4498+class DBusPropertiesInterface;
4499+class AccountsInterface;
4500+class QSignalSpy;
4501+
4502+#define WAIT_FOR_SIGNALS(signalSpy, signalsExpected)\
4503+{\
4504+ while (signalSpy.size() < signalsExpected)\
4505+ {\
4506+ ASSERT_TRUE(signalSpy.wait());\
4507+ }\
4508+ ASSERT_EQ(signalsExpected, signalSpy.size());\
4509+}
4510+
4511+#define WAIT_AT_LEAST_SIGNALS(signalSpy, signalsExpected)\
4512+{\
4513+ while (signalSpy.size() < signalsExpected)\
4514+ {\
4515+ ASSERT_TRUE(signalSpy.wait());\
4516+ }\
4517+ ASSERT_TRUE(signalsExpected <= signalSpy.size());\
4518+}
4519+
4520+class IndicatorSoundTestBase: public testing::Test
4521+{
4522+public:
4523+ IndicatorSoundTestBase();
4524+
4525+ ~IndicatorSoundTestBase();
4526+
4527+ enum DevicePortType
4528+ {
4529+ WIRED,
4530+ BLUETOOTH,
4531+ USB,
4532+ HDMI
4533+ };
4534+
4535+protected:
4536+ void SetUp() override;
4537+ void TearDown() override;
4538+
4539+ void startIndicator();
4540+ void startPulseDesktop(DevicePortType speakerPort=WIRED, DevicePortType headphonesPort=WIRED);
4541+ void startPulsePhone(DevicePortType speakerPort=WIRED, DevicePortType headphonesPort=WIRED);
4542+ void startAccountsService();
4543+
4544+ bool clearGSettingsPlayers();
4545+
4546+ bool startTestMprisPlayer(QString const& playerName);
4547+
4548+ bool setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value);
4549+
4550+ bool setStreamRestoreVolume(QString const &role, double volume);
4551+
4552+ bool setSinkVolume(double volume);
4553+
4554+ bool startTestSound(QString const &role);
4555+
4556+ void stopTestSound();
4557+
4558+ static std::shared_ptr<GVariant> volume_variant(double volume);
4559+
4560+ static unity::gmenuharness::MenuMatcher::Parameters desktopParameters();
4561+
4562+ static unity::gmenuharness::MenuMatcher::Parameters phoneParameters();
4563+
4564+ static unity::gmenuharness::MenuItemMatcher volumeSlider(double volume, QString const &label);
4565+
4566+ static unity::gmenuharness::MenuItemMatcher silentModeSwitch(bool toggled);
4567+
4568+ bool waitMenuChange();
4569+
4570+ bool initializeMenuChangedSignal();
4571+
4572+ bool waitVolumeChangedInIndicator();
4573+
4574+ void initializeAccountsInterface();
4575+
4576+ OrgFreedesktopDBusMockInterface& notificationsMockInterface();
4577+
4578+ bool setActionValue(const QString & action, QVariant value);
4579+
4580+ bool pressNotificationButton(int id, const QString & button);
4581+
4582+ bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map);
4583+
4584+ void checkVolumeNotification(double volume, QString const& label, bool isLoud, QVariantList call);
4585+
4586+ void checkHighVolumeNotification(QVariantList call);
4587+
4588+ void checkCloseNotification(int id, QVariantList call);
4589+
4590+ void checkNotificationWithNoArgs(QString const& method, QVariantList call);
4591+
4592+ int getNotificationID(QVariantList call);
4593+
4594+ bool activateHeadphones(bool headphonesActive);
4595+
4596+ QString getDevicePortString(DevicePortType port);
4597+
4598+ void checkPortDevicesLabels(DevicePortType speakerPort, DevicePortType headphonesPort);
4599+
4600+ bool setVolumeUntilAccountsIsConnected(double volume);
4601+
4602+ QtDBusTest::DBusTestRunner dbusTestRunner;
4603+
4604+ QtDBusMock::DBusMock dbusMock;
4605+
4606+ QtDBusTest::DBusServicePtr indicator;
4607+
4608+ QtDBusTest::DBusServicePtr pulseaudio;
4609+
4610+ QtDBusTest::DBusServicePtr accountsService;
4611+
4612+ QProcess testSoundProcess;
4613+
4614+ QProcess testPlayer1;
4615+
4616+ std::unique_ptr<MenusInterface> menu_interface_;
4617+
4618+ std::unique_ptr<DBusPropertiesInterface> accounts_interface_;
4619+
4620+ std::unique_ptr<AccountsInterface> main_accounts_interface_;
4621+
4622+ std::unique_ptr<QSignalSpy> signal_spy_volume_changed_;
4623+
4624+ std::unique_ptr<QSignalSpy> signal_spy_menu_changed_;
4625+};
4626
4627=== added file 'tests/integration/main.cpp'
4628--- tests/integration/main.cpp 1970-01-01 00:00:00 +0000
4629+++ tests/integration/main.cpp 2016-01-05 11:11:34 +0000
4630@@ -0,0 +1,58 @@
4631+/*
4632+ * Copyright © 2014 Canonical Ltd.
4633+ *
4634+ * This program is free software: you can redistribute it and/or modify it
4635+ * under the terms of the GNU Lesser General Public License version 3,
4636+ * as published by the Free Software Foundation.
4637+ *
4638+ * This program is distributed in the hope that it will be useful,
4639+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4640+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4641+ * GNU Lesser General Public License for more details.
4642+ *
4643+ * You should have received a copy of the GNU Lesser General Public License
4644+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4645+ *
4646+ * Authors:
4647+ * Pete Woods <pete.woods@canonical.com>
4648+ */
4649+
4650+//#include <config.h>
4651+
4652+#include <QCoreApplication>
4653+#include <QTimer>
4654+#include <gtest/gtest.h>
4655+
4656+#include <libqtdbusmock/DBusMock.h>
4657+
4658+#include "dbus-types.h"
4659+
4660+using namespace QtDBusMock;
4661+
4662+class Runner: public QObject
4663+{
4664+ Q_OBJECT
4665+public Q_SLOTS:
4666+ void run()
4667+ {
4668+ QCoreApplication::exit(RUN_ALL_TESTS());
4669+ }
4670+};
4671+
4672+int main(int argc, char **argv)
4673+{
4674+ qputenv("LANG", "C.UTF-8");
4675+ unsetenv("LC_ALL");
4676+
4677+ QCoreApplication application(argc, argv);
4678+ DBusMock::registerMetaTypes();
4679+ DBusTypes::registerMetaTypes();
4680+ ::testing::InitGoogleTest(&argc, argv);
4681+
4682+ Runner runner;
4683+ QTimer::singleShot(0, &runner, SLOT(run()));
4684+
4685+ return application.exec();
4686+}
4687+
4688+#include "main.moc"
4689
4690=== added file 'tests/integration/test-indicator.cpp'
4691--- tests/integration/test-indicator.cpp 1970-01-01 00:00:00 +0000
4692+++ tests/integration/test-indicator.cpp 2016-01-05 11:11:34 +0000
4693@@ -0,0 +1,981 @@
4694+/*
4695+ * Copyright (C) 2015 Canonical, Ltd.
4696+ *
4697+ * This program is free software: you can redistribute it and/or modify it
4698+ * under the terms of the GNU General Public License version 3, as published
4699+ * by the Free Software Foundation.
4700+ *
4701+ * This program is distributed in the hope that it will be useful, but
4702+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4703+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4704+ * PURPOSE. See the GNU General Public License for more details.
4705+ *
4706+ * You should have received a copy of the GNU General Public License along
4707+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4708+ *
4709+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4710+ */
4711+
4712+#include <indicator-sound-test-base.h>
4713+
4714+#include <QDebug>
4715+#include <QTestEventLoop>
4716+#include <QSignalSpy>
4717+
4718+using namespace std;
4719+using namespace testing;
4720+namespace mh = unity::gmenuharness;
4721+namespace
4722+{
4723+
4724+class TestIndicator: public IndicatorSoundTestBase
4725+{
4726+};
4727+
4728+TEST_F(TestIndicator, PhoneChangeRoleVolume)
4729+{
4730+ double INITIAL_VOLUME = 0.0;
4731+
4732+ ASSERT_NO_THROW(startAccountsService());
4733+ ASSERT_NO_THROW(startPulsePhone());
4734+
4735+ // initialize volumes in pulseaudio
4736+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
4737+ EXPECT_TRUE(setStreamRestoreVolume("multimedia", INITIAL_VOLUME));
4738+
4739+ // start now the indicator, so it picks the new volumes
4740+ ASSERT_NO_THROW(startIndicator());
4741+
4742+ // Generate a random volume
4743+ QTime now = QTime::currentTime();
4744+ qsrand(now.msec());
4745+ int randInt = qrand() % 100;
4746+ double randomVolume = randInt / 100.0;
4747+
4748+ QSignalSpy &userAccountsSpy = *signal_spy_volume_changed_;
4749+ // set an initial volume to the alert role
4750+ userAccountsSpy.clear();
4751+ EXPECT_TRUE(setVolumeUntilAccountsIsConnected(1.0));
4752+ userAccountsSpy.clear();
4753+ // play a test sound, it should change the role in the indicator
4754+ EXPECT_TRUE(startTestSound("multimedia"));
4755+
4756+ // this time we only expect 1 signal as it's only the indicator
4757+ // updating the value
4758+ WAIT_FOR_SIGNALS(userAccountsSpy, 1);
4759+ //EXPECT_TRUE(waitVolumeChangedInIndicator());
4760+
4761+ userAccountsSpy.clear();
4762+ // set the random volume to the multimedia role
4763+ setActionValue("volume", QVariant::fromValue(randomVolume));
4764+ if (randomVolume != INITIAL_VOLUME)
4765+ {
4766+ WAIT_FOR_SIGNALS(userAccountsSpy, 1);
4767+ }
4768+
4769+ // check the indicator
4770+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4771+ .item(mh::MenuItemMatcher()
4772+ .action("indicator.root")
4773+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4774+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
4775+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4776+ .string_attribute("submenu-action", "indicator.indicator-shown")
4777+ .mode(mh::MenuItemMatcher::Mode::starts_with)
4778+ .submenu()
4779+ .item(mh::MenuItemMatcher()
4780+ .section()
4781+ .item(silentModeSwitch(false))
4782+ .item(volumeSlider(randomVolume, "Volume"))
4783+ )
4784+ ).match());
4785+
4786+ // check that the last item is Sound Settings
4787+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4788+ .item(mh::MenuItemMatcher()
4789+ .action("indicator.root")
4790+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4791+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4792+ .mode(mh::MenuItemMatcher::Mode::ends_with)
4793+ .submenu()
4794+ .item(mh::MenuItemMatcher()
4795+ .label("Sound Settings…")
4796+ .action("indicator.phone-settings")
4797+ )
4798+ ).match());
4799+
4800+ // initialize the signal spy
4801+ EXPECT_TRUE(initializeMenuChangedSignal());
4802+ userAccountsSpy.clear();
4803+ // stop the test sound, the role should change again to alert
4804+ stopTestSound();
4805+ if (randomVolume != 1.0)
4806+ {
4807+ // wait for the menu change
4808+ EXPECT_TRUE(waitMenuChange());
4809+ }
4810+
4811+ // check the initial volume for the alert role
4812+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4813+ .item(mh::MenuItemMatcher()
4814+ .action("indicator.root")
4815+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4816+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
4817+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4818+ .string_attribute("submenu-action", "indicator.indicator-shown")
4819+ .mode(mh::MenuItemMatcher::Mode::starts_with)
4820+ .submenu()
4821+ .item(mh::MenuItemMatcher()
4822+ .section()
4823+ .item(silentModeSwitch(false))
4824+ .item(volumeSlider(1.0, "Volume"))
4825+ )
4826+ ).match());
4827+
4828+ // check that the last item is Sound Settings
4829+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4830+ .item(mh::MenuItemMatcher()
4831+ .action("indicator.root")
4832+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4833+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4834+ .mode(mh::MenuItemMatcher::Mode::ends_with)
4835+ .submenu()
4836+ .item(mh::MenuItemMatcher()
4837+ .label("Sound Settings…")
4838+ .action("indicator.phone-settings")
4839+ )
4840+ ).match());
4841+}
4842+
4843+TEST_F(TestIndicator, PhoneBasicInitialVolume)
4844+{
4845+ double INITIAL_VOLUME = 0.0;
4846+
4847+ ASSERT_NO_THROW(startAccountsService());
4848+ EXPECT_TRUE(clearGSettingsPlayers());
4849+ ASSERT_NO_THROW(startPulsePhone());
4850+
4851+ // initialize volumes in pulseaudio
4852+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
4853+
4854+ // start now the indicator, so it picks the new volumes
4855+ ASSERT_NO_THROW(startIndicator());
4856+
4857+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4858+ .item(mh::MenuItemMatcher()
4859+ .action("indicator.root")
4860+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4861+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
4862+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4863+ .string_attribute("submenu-action", "indicator.indicator-shown")
4864+ .mode(mh::MenuItemMatcher::Mode::all)
4865+ .submenu()
4866+ .item(mh::MenuItemMatcher()
4867+ .section()
4868+ .item(silentModeSwitch(false))
4869+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
4870+ )
4871+ .item(mh::MenuItemMatcher()
4872+ .label("Sound Settings…")
4873+ .action("indicator.phone-settings")
4874+ )
4875+ ).match());
4876+}
4877+
4878+TEST_F(TestIndicator, PhoneAddMprisPlayer)
4879+{
4880+ double INITIAL_VOLUME = 0.0;
4881+
4882+ ASSERT_NO_THROW(startAccountsService());
4883+ EXPECT_TRUE(clearGSettingsPlayers());
4884+ ASSERT_NO_THROW(startPulsePhone());
4885+
4886+ // initialize volumes in pulseaudio
4887+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
4888+
4889+ // start now the indicator, so it picks the new volumes
4890+ ASSERT_NO_THROW(startIndicator());
4891+
4892+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4893+ .item(mh::MenuItemMatcher()
4894+ .action("indicator.root")
4895+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4896+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
4897+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4898+ .string_attribute("submenu-action", "indicator.indicator-shown")
4899+ .mode(mh::MenuItemMatcher::Mode::all)
4900+ .submenu()
4901+ .item(mh::MenuItemMatcher()
4902+ .section()
4903+ .item(silentModeSwitch(false))
4904+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
4905+ )
4906+ .item(mh::MenuItemMatcher()
4907+ .label("Sound Settings…")
4908+ .action("indicator.phone-settings")
4909+ )
4910+ ).match());
4911+
4912+ // initialize the signal spy
4913+ EXPECT_TRUE(initializeMenuChangedSignal());
4914+
4915+ // start the test player
4916+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
4917+
4918+ // wait for the menu change
4919+ EXPECT_TRUE(waitMenuChange());
4920+
4921+ // finally verify that the player is added
4922+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4923+ .item(mh::MenuItemMatcher()
4924+ .action("indicator.root")
4925+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4926+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
4927+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4928+ .string_attribute("submenu-action", "indicator.indicator-shown")
4929+ .mode(mh::MenuItemMatcher::Mode::all)
4930+ .submenu()
4931+ .item(mh::MenuItemMatcher()
4932+ .section()
4933+ .item(silentModeSwitch(false))
4934+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
4935+ )
4936+ .item(mh::MenuItemMatcher()
4937+ .section()
4938+ .item(mh::MenuItemMatcher()
4939+ .action("indicator.testplayer1.desktop")
4940+ .label("TestPlayer1")
4941+ .themed_icon("icon", {"testplayer"})
4942+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
4943+ )
4944+ .item(mh::MenuItemMatcher()
4945+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
4946+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
4947+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
4948+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
4949+ )
4950+ )
4951+ .item(mh::MenuItemMatcher()
4952+ .label("Sound Settings…")
4953+ .action("indicator.phone-settings")
4954+ )
4955+ ).match());
4956+}
4957+
4958+TEST_F(TestIndicator, DesktopBasicInitialVolume)
4959+{
4960+ double INITIAL_VOLUME = 0.0;
4961+
4962+ ASSERT_NO_THROW(startAccountsService());
4963+ EXPECT_TRUE(clearGSettingsPlayers());
4964+ ASSERT_NO_THROW(startPulseDesktop());
4965+
4966+ // initialize volumes in pulseaudio
4967+ EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
4968+ EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
4969+
4970+ // start the test player
4971+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
4972+
4973+ // start now the indicator, so it picks the new volumes
4974+ ASSERT_NO_THROW(startIndicator());
4975+
4976+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
4977+ .item(mh::MenuItemMatcher()
4978+ .action("indicator.root")
4979+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4980+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4981+ .mode(mh::MenuItemMatcher::Mode::all)
4982+ .submenu()
4983+ .item(mh::MenuItemMatcher()
4984+ .section()
4985+ .item(mh::MenuItemMatcher().checkbox()
4986+ .label("Mute")
4987+ )
4988+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
4989+ )
4990+ .item(mh::MenuItemMatcher()
4991+ .section()
4992+ .item(mh::MenuItemMatcher()
4993+ .action("indicator.testplayer1.desktop")
4994+ .label("TestPlayer1")
4995+ .themed_icon("icon", {"testplayer"})
4996+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
4997+ )
4998+ .item(mh::MenuItemMatcher()
4999+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
5000+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches