Merge lp:~xavi-garcia-mena/indicator-sound/bluetooth-usb-hdmi-labels-with-tests into lp:indicator-sound/15.10

Proposed by Xavi Garcia on 2015-10-15
Status: Approved
Approved by: Pete Woods on 2015-10-27
Approved revision: 509
Proposed branch: lp:~xavi-garcia-mena/indicator-sound/bluetooth-usb-hdmi-labels-with-tests
Merge into: lp:indicator-sound/15.10
Prerequisite: lp:~xavi-garcia-mena/indicator-sound/notifications-integration-tests
Diff against target: 7104 lines (+6292/-177)
60 files modified
debian/control (+11/-0)
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 (+11/-9)
src/gmenuharness/CMakeLists.txt (+17/-0)
src/gmenuharness/MatchResult.cpp (+187/-0)
src/gmenuharness/MatchUtils.cpp (+74/-0)
src/gmenuharness/MenuItemMatcher.cpp (+1011/-0)
src/gmenuharness/MenuMatcher.cpp (+208/-0)
src/service.vala (+354/-46)
src/sound-menu.vala (+38/-0)
src/volume-control-pulse.vala (+69/-14)
src/volume-control.vala (+14/-0)
tests/CMakeLists.txt (+113/-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 (+8/-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 (+807/-0)
tests/integration/indicator-sound-test-base.h (+146/-0)
tests/integration/main.cpp (+58/-0)
tests/integration/test-indicator.cpp (+963/-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 (+1/-1)
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 (+13/-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/bluetooth-usb-hdmi-labels-with-tests
Reviewer Review Type Date Requested Status
Charles Kerr (community) 2015-10-15 Approve on 2015-10-28
Pete Woods (community) 2015-10-15 Approve on 2015-10-27
PS Jenkins bot (community) continuous-integration Needs Fixing on 2015-10-27
Review via email: mp+274536@code.launchpad.net

Commit message

This branch adds the labels to wired/bluetooth/usb/hdmi devices.
It includes integration tests with gmenuharness.

Description of the change

This branch adds the labels to wired/bluetooth/usb/hdmi devices.
It includes integration tests with gmenuharness.

To post a comment you must log in.
506. By Xavi Garcia on 2015-10-19

Wily branch for MPRIS controls Fixes: #1373313
Approved by: PS Jenkins bot, Xavi Garcia

507. By CI Train Bot Account on 2015-10-19

Releasing 12.10.2+15.10.20151019-0ubuntu1

508. By Xavi Garcia on 2015-10-21

Fixed conflict. Updated changelog to be in sync with trunk

Pete Woods (pete-woods) wrote :

Looks good, but please remove the commented code and qDebug() << "=============" type output.

review: Needs Fixing
509. By Xavi Garcia on 2015-10-26

Erased log statements and commented lines

Pete Woods (pete-woods) :
review: Approve
Charles Kerr (charlesk) wrote :

Overall this looks great. As I've said before I /love/ how readable gmenuharness makes indicator test code. All our indicator tests should look this good.

That said there are a handful bugs ranging from minor signed/unsigned mismatches to memory leaks and a crasher. Some of these are in the i-sound code/tests and some are in the harness and should also be looked at upstream by Pete.

Also, I'm stealing code that wraps ResourcePtr around gio tags.

review: Needs Fixing
Xavi Garcia (xavi-garcia-mena) wrote :

Thanks a lot for the good review, Charles!
Comments inline...

510. By Xavi Garcia on 2015-10-28

Fixes after review

Charles Kerr (charlesk) wrote :

Thanks for the fixes Xavi!

review: Approve

Unmerged revisions

510. By Xavi Garcia on 2015-10-28

Fixes after review

509. By Xavi Garcia on 2015-10-26

Erased log statements and commented lines

Preview Diff

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