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

Proposed by Xavi Garcia
Status: Approved
Approved by: Pete Woods
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) Approve
Pete Woods (community) Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
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.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
506. By Xavi Garcia

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

507. By CI Train Bot Account

Releasing 12.10.2+15.10.20151019-0ubuntu1

508. By Xavi Garcia

Fixed conflict. Updated changelog to be in sync with trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Pete Woods (pete-woods) wrote :

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

review: Needs Fixing
509. By Xavi Garcia

Erased log statements and commented lines

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Pete Woods (pete-woods) :
review: Approve
Revision history for this message
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
Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

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

510. By Xavi Garcia

Fixes after review

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

Thanks for the fixes Xavi!

review: Approve

Unmerged revisions

510. By Xavi Garcia

Fixes after review

509. By Xavi Garcia

Erased log statements and commented lines

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/control'
--- debian/control 2015-02-27 17:52:05 +0000
+++ debian/control 2015-10-28 09:41:22 +0000
@@ -5,11 +5,13 @@
5XSBC-Original-Maintainer: Conor Curran <conor.curran@canonical.com>5XSBC-Original-Maintainer: Conor Curran <conor.curran@canonical.com>
6Build-Depends: debhelper (>= 9.0),6Build-Depends: debhelper (>= 9.0),
7 cmake,7 cmake,
8 cmake-extras (>= 0.4),
8 dbus,9 dbus,
9 dbus-test-runner (>> 14.04.0+14.04.20150120.1),10 dbus-test-runner (>> 14.04.0+14.04.20150120.1),
10 dh-translations,11 dh-translations,
11 gir1.2-accountsservice-1.0,12 gir1.2-accountsservice-1.0,
12 gnome-common,13 gnome-common,
14 google-mock (>= 1.6.0+svn437),
13 gsettings-ubuntu-schemas,15 gsettings-ubuntu-schemas,
14 autotools-dev,16 autotools-dev,
15 valac (>= 0.20),17 valac (>= 0.20),
@@ -18,13 +20,22 @@
18 libgirepository1.0-dev,20 libgirepository1.0-dev,
19 libglib2.0-dev (>= 2.22.3),21 libglib2.0-dev (>= 2.22.3),
20 libgtest-dev,22 libgtest-dev,
23 libqtdbusmock1-dev (>= 0.3),
24 libqtdbustest1-dev,
25 libunity-api-dev,
21 liburl-dispatcher1-dev,26 liburl-dispatcher1-dev,
22 libpulse-dev (>= 1:4.0-0ubuntu21),27 libpulse-dev (>= 1:4.0-0ubuntu21),
23 libpulse-mainloop-glib0 (>= 0.9.18),28 libpulse-mainloop-glib0 (>= 0.9.18),
24 libnotify-dev,29 libnotify-dev,
25 libgee-dev,30 libgee-dev,
26 libxml2-dev,31 libxml2-dev,
32 pulseaudio,
27 python3-dbusmock,33 python3-dbusmock,
34 qt5-default,
35 qtbase5-dev,
36 qtbase5-dev-tools,
37 qtdeclarative5-dev,
38 qtdeclarative5-dev-tools,
28Standards-Version: 3.9.439Standards-Version: 3.9.4
29Homepage: https://launchpad.net/indicator-sound40Homepage: https://launchpad.net/indicator-sound
30# If you aren't a member of ~indicator-applet-developers but need to upload41# If you aren't a member of ~indicator-applet-developers but need to upload
3142
=== added directory 'include'
=== added file 'include/CMakeLists.txt'
--- include/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ include/CMakeLists.txt 2015-10-28 09:41:22 +0000
@@ -0,0 +1,1 @@
1add_subdirectory(unity)
02
=== added directory 'include/unity'
=== added file 'include/unity/CMakeLists.txt'
--- include/unity/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ include/unity/CMakeLists.txt 2015-10-28 09:41:22 +0000
@@ -0,0 +1,1 @@
1add_subdirectory(gmenuharness)
02
=== added directory 'include/unity/gmenuharness'
=== added file 'include/unity/gmenuharness/MatchResult.h'
--- include/unity/gmenuharness/MatchResult.h 1970-01-01 00:00:00 +0000
+++ include/unity/gmenuharness/MatchResult.h 2015-10-28 09:41:22 +0000
@@ -0,0 +1,66 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Pete Woods <pete.woods@canonical.com>
17 */
18
19#pragma once
20
21#include <vector>
22#include <memory>
23#include <string>
24
25namespace unity
26{
27
28namespace gmenuharness
29{
30
31class MatchResult
32{
33public:
34 MatchResult();
35
36 MatchResult(MatchResult&& other);
37
38 MatchResult(const MatchResult& other);
39
40 MatchResult& operator=(const MatchResult& other);
41
42 MatchResult& operator=(MatchResult&& other);
43
44 ~MatchResult() = default;
45
46 MatchResult createChild() const;
47
48 void failure(const std::vector<unsigned int>& location, const std::string& message);
49
50 void merge(const MatchResult& other);
51
52 bool success() const;
53
54 bool hasTimedOut() const;
55
56 std::string concat_failures() const;
57
58protected:
59 struct Priv;
60
61 std::shared_ptr<Priv> p;
62};
63
64} // namespace gmenuharness
65
66} // namespace unity
067
=== added file 'include/unity/gmenuharness/MatchUtils.h'
--- include/unity/gmenuharness/MatchUtils.h 1970-01-01 00:00:00 +0000
+++ include/unity/gmenuharness/MatchUtils.h 2015-10-28 09:41:22 +0000
@@ -0,0 +1,42 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Pete Woods <pete.woods@canonical.com>
17 */
18
19#pragma once
20
21#include <memory>
22#include <string>
23
24#include <gio/gio.h>
25
26namespace unity
27{
28
29namespace gmenuharness
30{
31
32void waitForCore(GObject* obj, const std::string& signalName, unsigned int timeout = 10);
33
34void menuWaitForItems(const std::shared_ptr<GMenuModel>& menu, unsigned int timeout = 10);
35
36void g_object_deleter(gpointer object);
37
38void gvariant_deleter(GVariant* varptr);
39
40} //namespace gmenuharness
41
42} // namespace unity
043
=== added file 'include/unity/gmenuharness/MenuItemMatcher.h'
--- include/unity/gmenuharness/MenuItemMatcher.h 1970-01-01 00:00:00 +0000
+++ include/unity/gmenuharness/MenuItemMatcher.h 2015-10-28 09:41:22 +0000
@@ -0,0 +1,143 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Pete Woods <pete.woods@canonical.com>
17 */
18
19#pragma once
20
21#include <map>
22#include <memory>
23#include <string>
24
25#include <gio/gio.h>
26
27namespace unity
28{
29
30namespace gmenuharness
31{
32
33class MatchResult;
34
35class MenuItemMatcher
36{
37public:
38 enum class Mode
39 {
40 all,
41 starts_with,
42 ends_with
43 };
44
45 enum class Type
46 {
47 plain,
48 checkbox,
49 radio
50 };
51
52 static MenuItemMatcher checkbox();
53
54 static MenuItemMatcher radio();
55
56 MenuItemMatcher();
57
58 ~MenuItemMatcher();
59
60 MenuItemMatcher(const MenuItemMatcher& other);
61
62 MenuItemMatcher(MenuItemMatcher&& other);
63
64 MenuItemMatcher& operator=(const MenuItemMatcher& other);
65
66 MenuItemMatcher& operator=(MenuItemMatcher&& other);
67
68 MenuItemMatcher& type(Type type);
69
70 MenuItemMatcher& label(const std::string& label);
71
72 MenuItemMatcher& action(const std::string& action);
73
74 MenuItemMatcher& state_icons(const std::vector<std::string>& state);
75
76 MenuItemMatcher& icon(const std::string& icon);
77
78 MenuItemMatcher& themed_icon(const std::string& iconName, const std::vector<std::string>& icons);
79
80 MenuItemMatcher& widget(const std::string& widget);
81
82 MenuItemMatcher& pass_through_attribute(const std::string& actionName, const std::shared_ptr<GVariant>& value);
83
84 MenuItemMatcher& pass_through_boolean_attribute(const std::string& actionName, bool value);
85
86 MenuItemMatcher& pass_through_string_attribute(const std::string& actionName, const std::string& value);
87
88 MenuItemMatcher& pass_through_double_attribute(const std::string& actionName, double value);
89
90 MenuItemMatcher& round_doubles(double maxDifference);
91
92 MenuItemMatcher& attribute(const std::string& name, const std::shared_ptr<GVariant>& value);
93
94 MenuItemMatcher& boolean_attribute(const std::string& name, bool value);
95
96 MenuItemMatcher& string_attribute(const std::string& name, const std::string& value);
97
98 MenuItemMatcher& int32_attribute(const std::string& name, int value);
99
100 MenuItemMatcher& int64_attribute(const std::string& name, int value);
101
102 MenuItemMatcher& double_attribute(const std::string& name, double value);
103
104 MenuItemMatcher& attribute_not_set(const std::string& name);
105
106 MenuItemMatcher& toggled(bool toggled);
107
108 MenuItemMatcher& mode(Mode mode);
109
110 MenuItemMatcher& submenu();
111
112 MenuItemMatcher& section();
113
114 MenuItemMatcher& is_empty();
115
116 MenuItemMatcher& has_exactly(std::size_t children);
117
118 MenuItemMatcher& item(const MenuItemMatcher& item);
119
120 MenuItemMatcher& item(MenuItemMatcher&& item);
121
122 MenuItemMatcher& pass_through_activate(const std::string& action, const std::shared_ptr<GVariant>& parameter = nullptr);
123
124 MenuItemMatcher& activate(const std::shared_ptr<GVariant>& parameter = nullptr);
125
126 MenuItemMatcher& set_pass_through_action_state(const std::string& action, const std::shared_ptr<GVariant>& state);
127
128 MenuItemMatcher& set_action_state(const std::shared_ptr<GVariant>& state);
129
130 void match(MatchResult& matchResult, const std::vector<unsigned int>& location,
131 const std::shared_ptr<GMenuModel>& menu,
132 std::map<std::string, std::shared_ptr<GActionGroup>>& actions,
133 unsigned int index) const;
134
135protected:
136 struct Priv;
137
138 std::shared_ptr<Priv> p;
139};
140
141} // namespace gmenuharness
142
143} // namespace unity
0144
=== added file 'include/unity/gmenuharness/MenuMatcher.h'
--- include/unity/gmenuharness/MenuMatcher.h 1970-01-01 00:00:00 +0000
+++ include/unity/gmenuharness/MenuMatcher.h 2015-10-28 09:41:22 +0000
@@ -0,0 +1,95 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Pete Woods <pete.woods@canonical.com>
17 */
18
19#pragma once
20
21#define EXPECT_MATCHRESULT(statement) \
22do {\
23 auto result = (statement);\
24 GTEST_TEST_BOOLEAN_(result.success(), #statement, false, true, \
25 GTEST_NONFATAL_FAILURE_) << result.concat_failures().c_str(); \
26} while (0)
27
28#include <unity/gmenuharness/MatchResult.h>
29#include <unity/gmenuharness/MenuItemMatcher.h>
30
31#include <memory>
32#include <vector>
33
34namespace unity
35{
36
37namespace gmenuharness
38{
39
40class MenuMatcher
41{
42public:
43 class Parameters
44 {
45 public:
46 Parameters(
47 const std::string& busName,
48 const std::vector<std::pair<std::string, std::string>>& actions,
49 const std::string& menuObjectPath);
50
51 ~Parameters();
52
53 Parameters(const Parameters& other);
54
55 Parameters(Parameters&& other);
56
57 Parameters& operator=(const Parameters& other);
58
59 Parameters& operator=(Parameters&& other);
60
61 protected:
62 friend MenuMatcher;
63
64 struct Priv;
65
66 std::shared_ptr<Priv> p;
67 };
68
69 MenuMatcher(const Parameters& parameters);
70
71 ~MenuMatcher();
72
73 MenuMatcher(const MenuMatcher& other) = delete;
74
75 MenuMatcher(MenuMatcher&& other) = delete;
76
77 MenuMatcher& operator=(const MenuMatcher& other) = delete;
78
79 MenuMatcher& operator=(MenuMatcher&& other) = delete;
80
81 MenuMatcher& item(const MenuItemMatcher& item);
82
83 MatchResult match() const;
84
85 void match(MatchResult& matchResult) const;
86
87protected:
88 struct Priv;
89
90 std::shared_ptr<Priv> p;
91};
92
93} // gmenuharness
94
95} // unity
096
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 2015-02-19 16:23:01 +0000
+++ src/CMakeLists.txt 2015-10-28 09:41:22 +0000
@@ -8,12 +8,12 @@
8set(VAPI_PATH "${CMAKE_CURRENT_BINARY_DIR}/indicator-sound-service.vapi")8set(VAPI_PATH "${CMAKE_CURRENT_BINARY_DIR}/indicator-sound-service.vapi")
99
10vapi_gen(accounts-service10vapi_gen(accounts-service
11 LIBRARY11 LIBRARY
12 accounts-service12 accounts-service
13 PACKAGES13 PACKAGES
14 gio-2.014 gio-2.0
15 INPUT15 INPUT
16 /usr/share/gir-1.0/AccountsService-1.0.gir16 /usr/share/gir-1.0/AccountsService-1.0.gir
17)17)
1818
19vala_init(indicator-sound-service19vala_init(indicator-sound-service
@@ -70,7 +70,7 @@
70 media-player-user.vala70 media-player-user.vala
71 DEPENDS71 DEPENDS
72 media-player72 media-player
73 accounts-service-sound-settings73 accounts-service-sound-settings
74 greeter-broadcast74 greeter-broadcast
75)75)
76vala_add(indicator-sound-service76vala_add(indicator-sound-service
@@ -103,6 +103,7 @@
103 sound-menu.vala103 sound-menu.vala
104 DEPENDS104 DEPENDS
105 media-player105 media-player
106 volume-control
106)107)
107vala_add(indicator-sound-service108vala_add(indicator-sound-service
108 accounts-service-user.vala109 accounts-service-user.vala
@@ -164,8 +165,8 @@
164)165)
165166
166add_library(167add_library(
167 indicator-sound-service-lib STATIC168 indicator-sound-service-lib STATIC
168 ${INDICATOR_SOUND_SOURCES}169 ${INDICATOR_SOUND_SOURCES}
169)170)
170171
171target_link_libraries(172target_link_libraries(
@@ -206,3 +207,4 @@
206 RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/indicator-sound/207 RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/indicator-sound/
207)208)
208209
210add_subdirectory(gmenuharness)
209211
=== added directory 'src/gmenuharness'
=== added file 'src/gmenuharness/CMakeLists.txt'
--- src/gmenuharness/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ src/gmenuharness/CMakeLists.txt 2015-10-28 09:41:22 +0000
@@ -0,0 +1,17 @@
1pkg_check_modules(UNITY_API libunity-api>=0.1.3 REQUIRED)
2include_directories(${UNITY_API_INCLUDE_DIRS})
3
4include_directories("${CMAKE_SOURCE_DIR}/include")
5
6add_library(
7 gmenuharness-shared SHARED
8 MatchResult.cpp
9 MatchUtils.cpp
10 MenuItemMatcher.cpp
11 MenuMatcher.cpp
12)
13
14target_link_libraries(
15 gmenuharness-shared
16 ${GLIB_LDFLAGS}
17)
018
=== added file 'src/gmenuharness/MatchResult.cpp'
--- src/gmenuharness/MatchResult.cpp 1970-01-01 00:00:00 +0000
+++ src/gmenuharness/MatchResult.cpp 2015-10-28 09:41:22 +0000
@@ -0,0 +1,187 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Pete Woods <pete.woods@canonical.com>
17 */
18
19#include <unity/gmenuharness/MatchResult.h>
20
21#include <chrono>
22#include <map>
23#include <sstream>
24#include <iostream>
25
26using namespace std;
27
28namespace unity
29{
30
31namespace gmenuharness
32{
33
34namespace
35{
36
37
38static void printLocation(ostream& ss, const vector<unsigned int>& location, bool first)
39{
40 for (auto i : location)
41 {
42 ss << " ";
43 if (first)
44 {
45 ss << i;
46 }
47 else
48 {
49 ss << " ";
50 }
51 }
52 ss << " ";
53}
54
55struct compare_vector
56{
57 bool operator()(const vector<unsigned int>& a,
58 const vector<unsigned int>& b) const
59 {
60 auto p1 = a.begin();
61 auto p2 = b.begin();
62
63 while (p1 != a.end())
64 {
65 if (p2 == b.end())
66 {
67 return false;
68 }
69 if (*p2 > *p1)
70 {
71 return true;
72 }
73 if (*p1 > *p2)
74 {
75 return false;
76 }
77
78 ++p1;
79 ++p2;
80 }
81
82 if (p2 != b.end())
83 {
84 return true;
85 }
86
87 return false;
88 }
89};
90}
91
92struct MatchResult::Priv
93{
94 bool m_success = true;
95
96 map<vector<unsigned int>, vector<string>, compare_vector> m_failures;
97
98 chrono::time_point<chrono::system_clock> m_timeout = chrono::system_clock::now() + chrono::seconds(10);
99};
100
101MatchResult::MatchResult() :
102 p(new Priv)
103{
104}
105
106MatchResult::MatchResult(MatchResult&& other)
107{
108 *this = move(other);
109}
110
111MatchResult::MatchResult(const MatchResult& other) :
112 p(new Priv)
113{
114 *this = other;
115}
116
117MatchResult& MatchResult::operator=(const MatchResult& other)
118{
119 p->m_success = other.p->m_success;
120 p->m_failures= other.p->m_failures;
121 return *this;
122}
123
124MatchResult& MatchResult::operator=(MatchResult&& other)
125{
126 p = move(other.p);
127 return *this;
128}
129
130MatchResult MatchResult::createChild() const
131{
132 MatchResult child;
133 child.p->m_timeout = p->m_timeout;
134 return child;
135}
136
137void MatchResult::failure(const vector<unsigned int>& location, const string& message)
138{
139 p->m_success = false;
140 auto it = p->m_failures.find(location);
141 if (it == p->m_failures.end())
142 {
143 it = p->m_failures.insert(make_pair(location, vector<string>())).first;
144 }
145 it->second.emplace_back(message);
146}
147
148void MatchResult::merge(const MatchResult& other)
149{
150 p->m_success &= other.p->m_success;
151 for (const auto& e : other.p->m_failures)
152 {
153 p->m_failures.insert(make_pair(e.first, e.second));
154 }
155}
156
157bool MatchResult::success() const
158{
159 return p->m_success;
160}
161
162bool MatchResult::hasTimedOut() const
163{
164 auto now = chrono::system_clock::now();
165 return (now >= p->m_timeout);
166}
167
168string MatchResult::concat_failures() const
169{
170 stringstream ss;
171 ss << "Failed expectations:" << endl;
172 for (const auto& failure : p->m_failures)
173 {
174 bool first = true;
175 for (const string& s: failure.second)
176 {
177 printLocation(ss, failure.first, first);
178 first = false;
179 ss << s << endl;
180 }
181 }
182 return ss.str();
183}
184
185} // namespace gmenuharness
186
187} // namespace unity
0188
=== added file 'src/gmenuharness/MatchUtils.cpp'
--- src/gmenuharness/MatchUtils.cpp 1970-01-01 00:00:00 +0000
+++ src/gmenuharness/MatchUtils.cpp 2015-10-28 09:41:22 +0000
@@ -0,0 +1,74 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Pete Woods <pete.woods@canonical.com>
17 */
18
19#include <unity/gmenuharness/MatchUtils.h>
20
21#include <unity/util/ResourcePtr.h>
22
23using namespace std;
24namespace util = unity::util;
25
26namespace unity
27{
28
29namespace gmenuharness
30{
31
32void waitForCore (GObject * obj, const string& signalName, unsigned int timeout) {
33 shared_ptr<GMainLoop> loop(g_main_loop_new(nullptr, false), &g_main_loop_unref);
34
35 /* Our two exit criteria */
36 util::ResourcePtr<gulong, function<void(gulong)>> signal(
37 g_signal_connect_swapped(obj, signalName.c_str(),
38 G_CALLBACK(g_main_loop_quit), loop.get()),
39 [obj](gulong s)
40 {
41 g_signal_handler_disconnect(obj, s);
42 });
43
44 util::ResourcePtr<guint, function<void(guint)>> timer(g_timeout_add(timeout,
45 [](gpointer user_data) -> gboolean
46 {
47 g_main_loop_quit((GMainLoop *)user_data);
48 return G_SOURCE_CONTINUE;
49 },
50 loop.get()),
51 &g_source_remove);
52
53 /* Wait for sync */
54 g_main_loop_run(loop.get());
55}
56
57void menuWaitForItems(const shared_ptr<GMenuModel>& menu, unsigned int timeout)
58{
59 waitForCore(G_OBJECT(menu.get()), "items-changed", timeout);
60}
61
62void g_object_deleter(gpointer object)
63{
64 g_clear_object(&object);
65}
66
67void gvariant_deleter(GVariant* varptr)
68{
69 g_clear_pointer(&varptr, g_variant_unref);
70}
71
72} // namespace gmenuharness
73
74} // namespace unity
075
=== added file 'src/gmenuharness/MenuItemMatcher.cpp'
--- src/gmenuharness/MenuItemMatcher.cpp 1970-01-01 00:00:00 +0000
+++ src/gmenuharness/MenuItemMatcher.cpp 2015-10-28 09:41:22 +0000
@@ -0,0 +1,1011 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Pete Woods <pete.woods@canonical.com>
17 */
18
19#include <unity/gmenuharness/MatchResult.h>
20#include <unity/gmenuharness/MatchUtils.h>
21#include <unity/gmenuharness/MenuItemMatcher.h>
22
23#include <iostream>
24#include <vector>
25#include <map>
26
27using namespace std;
28
29namespace unity
30{
31
32namespace gmenuharness
33{
34
35namespace
36{
37
38enum class LinkType
39{
40 any,
41 section,
42 submenu
43};
44
45static string bool_to_string(bool value)
46{
47 return value? "true" : "false";
48}
49
50static shared_ptr<GVariant> get_action_group_attribute(const shared_ptr<GActionGroup>& actionGroup, const gchar* attribute)
51{
52 shared_ptr<GVariant> value(
53 g_action_group_get_action_state(actionGroup.get(), attribute),
54 &gvariant_deleter);
55 return value;
56}
57
58static shared_ptr<GVariant> get_attribute(const shared_ptr<GMenuItem> menuItem, const gchar* attribute)
59{
60 shared_ptr<GVariant> value(
61 g_menu_item_get_attribute_value(menuItem.get(), attribute, nullptr),
62 &gvariant_deleter);
63 return value;
64}
65
66static string get_string_attribute(const shared_ptr<GMenuItem> menuItem, const gchar* attribute)
67{
68 string result;
69 char* temp = nullptr;
70 if (g_menu_item_get_attribute(menuItem.get(), attribute, "s", &temp))
71 {
72 result = temp;
73 g_free(temp);
74 }
75 return result;
76}
77
78static pair<string, string> split_action(const string& action)
79{
80 auto index = action.find('.');
81
82 if (index == string::npos)
83 {
84 return make_pair(string(), action);
85 }
86
87 return make_pair(action.substr(0, index), action.substr(index + 1, action.size()));
88}
89
90static string type_to_string(MenuItemMatcher::Type type)
91{
92 switch(type)
93 {
94 case MenuItemMatcher::Type::plain:
95 return "plain";
96 case MenuItemMatcher::Type::checkbox:
97 return "checkbox";
98 case MenuItemMatcher::Type::radio:
99 return "radio";
100 }
101
102 return string();
103}
104}
105
106struct MenuItemMatcher::Priv
107{
108 void all(MatchResult& matchResult, const vector<unsigned int>& location,
109 const shared_ptr<GMenuModel>& menu,
110 map<string, shared_ptr<GActionGroup>>& actions)
111 {
112 int count = g_menu_model_get_n_items(menu.get());
113
114 if (m_items.size() != (unsigned int) count)
115 {
116 matchResult.failure(
117 location,
118 "Expected " + to_string(m_items.size())
119 + " children, but found " + to_string(count));
120 return;
121 }
122
123 for (size_t i = 0; i < m_items.size(); ++i)
124 {
125 const auto& matcher = m_items.at(i);
126 matcher.match(matchResult, location, menu, actions, i);
127 }
128 }
129
130 void startsWith(MatchResult& matchResult, const vector<unsigned int>& location,
131 const shared_ptr<GMenuModel>& menu,
132 map<string, shared_ptr<GActionGroup>>& actions)
133 {
134 int count = g_menu_model_get_n_items(menu.get());
135 if (m_items.size() > (unsigned int) count)
136 {
137 matchResult.failure(
138 location,
139 "Expected at least " + to_string(m_items.size())
140 + " children, but found " + to_string(count));
141 return;
142 }
143
144 for (size_t i = 0; i < m_items.size(); ++i)
145 {
146 const auto& matcher = m_items.at(i);
147 matcher.match(matchResult, location, menu, actions, i);
148 }
149 }
150
151 void endsWith(MatchResult& matchResult, const vector<unsigned int>& location,
152 const shared_ptr<GMenuModel>& menu,
153 map<string, shared_ptr<GActionGroup>>& actions)
154 {
155 int count = g_menu_model_get_n_items(menu.get());
156 if (m_items.size() > (unsigned int) count)
157 {
158 matchResult.failure(
159 location,
160 "Expected at least " + to_string(m_items.size())
161 + " children, but found " + to_string(count));
162 return;
163 }
164
165 // match the last N items
166 size_t j;
167 for (size_t i = count - m_items.size(), j = 0; i < count && j < m_items.size(); ++i, ++j)
168 {
169 const auto& matcher = m_items.at(j);
170 matcher.match(matchResult, location, menu, actions, i);
171 }
172 }
173
174 Type m_type = Type::plain;
175
176 Mode m_mode = Mode::all;
177
178 LinkType m_linkType = LinkType::any;
179
180 shared_ptr<size_t> m_expectedSize;
181
182 shared_ptr<string> m_label;
183
184 shared_ptr<string> m_icon;
185
186 map<string, vector<std::string>> m_themed_icons;
187
188 shared_ptr<string> m_action;
189
190 vector<std::string> m_state_icons;
191
192 vector<pair<string, shared_ptr<GVariant>>> m_attributes;
193
194 vector<string> m_not_exist_attributes;
195
196 vector<pair<string, shared_ptr<GVariant>>> m_pass_through_attributes;
197
198 shared_ptr<bool> m_isToggled;
199
200 vector<MenuItemMatcher> m_items;
201
202 vector<pair<string, shared_ptr<GVariant>>> m_activations;
203
204 vector<pair<string, shared_ptr<GVariant>>> m_setActionStates;
205
206 double m_maxDifference = 0.0;
207};
208
209MenuItemMatcher MenuItemMatcher::checkbox()
210{
211 MenuItemMatcher matcher;
212 matcher.type(Type::checkbox);
213 return matcher;
214}
215
216MenuItemMatcher MenuItemMatcher::radio()
217{
218 MenuItemMatcher matcher;
219 matcher.type(Type::radio);
220 return matcher;
221}
222
223MenuItemMatcher::MenuItemMatcher() :
224 p(new Priv)
225{
226}
227
228MenuItemMatcher::~MenuItemMatcher()
229{
230}
231
232MenuItemMatcher::MenuItemMatcher(const MenuItemMatcher& other) :
233 p(new Priv)
234{
235 *this = other;
236}
237
238MenuItemMatcher::MenuItemMatcher(MenuItemMatcher&& other)
239{
240 *this = move(other);
241}
242
243MenuItemMatcher& MenuItemMatcher::operator=(const MenuItemMatcher& other)
244{
245 p->m_type = other.p->m_type;
246 p->m_mode = other.p->m_mode;
247 p->m_expectedSize = other.p->m_expectedSize;
248 p->m_label = other.p->m_label;
249 p->m_icon = other.p->m_icon;
250 p->m_themed_icons = other.p->m_themed_icons;
251 p->m_action = other.p->m_action;
252 p->m_state_icons = other.p->m_state_icons;
253 p->m_attributes = other.p->m_attributes;
254 p->m_not_exist_attributes = other.p->m_not_exist_attributes;
255 p->m_pass_through_attributes = other.p->m_pass_through_attributes;
256 p->m_isToggled = other.p->m_isToggled;
257 p->m_linkType = other.p->m_linkType;
258 p->m_items = other.p->m_items;
259 p->m_activations = other.p->m_activations;
260 p->m_setActionStates = other.p->m_setActionStates;
261 p->m_maxDifference = other.p->m_maxDifference;
262 return *this;
263}
264
265MenuItemMatcher& MenuItemMatcher::operator=(MenuItemMatcher&& other)
266{
267 p = move(other.p);
268 return *this;
269}
270
271MenuItemMatcher& MenuItemMatcher::type(Type type)
272{
273 p->m_type = type;
274 return *this;
275}
276
277MenuItemMatcher& MenuItemMatcher::label(const string& label)
278{
279 p->m_label = make_shared<string>(label);
280 return *this;
281}
282
283MenuItemMatcher& MenuItemMatcher::action(const string& action)
284{
285 p->m_action = make_shared<string>(action);
286 return *this;
287}
288
289MenuItemMatcher& MenuItemMatcher::state_icons(const std::vector<std::string>& state_icons)
290{
291 p->m_state_icons = state_icons;
292 return *this;
293}
294
295MenuItemMatcher& MenuItemMatcher::icon(const string& icon)
296{
297 p->m_icon = make_shared<string>(icon);
298 return *this;
299}
300
301MenuItemMatcher& MenuItemMatcher::themed_icon(const std::string& iconName, const std::vector<std::string>& icons)
302{
303 p->m_themed_icons[iconName] = icons;
304 return *this;
305}
306
307MenuItemMatcher& MenuItemMatcher::widget(const string& widget)
308{
309 return string_attribute("x-canonical-type", widget);
310}
311
312MenuItemMatcher& MenuItemMatcher::pass_through_attribute(const string& actionName, const shared_ptr<GVariant>& value)
313{
314 p->m_pass_through_attributes.emplace_back(actionName, value);
315 return *this;
316}
317
318MenuItemMatcher& MenuItemMatcher::pass_through_boolean_attribute(const string& actionName, bool value)
319{
320 return pass_through_attribute(
321 actionName,
322 shared_ptr<GVariant>(g_variant_new_boolean(value),
323 &gvariant_deleter));
324}
325
326MenuItemMatcher& MenuItemMatcher::pass_through_string_attribute(const string& actionName, const string& value)
327{
328 return pass_through_attribute(
329 actionName,
330 shared_ptr<GVariant>(g_variant_new_string(value.c_str()),
331 &gvariant_deleter));
332}
333
334MenuItemMatcher& MenuItemMatcher::pass_through_double_attribute(const std::string& actionName, double value)
335{
336 return pass_through_attribute(
337 actionName,
338 shared_ptr<GVariant>(g_variant_new_double(value),
339 &gvariant_deleter));
340}
341
342MenuItemMatcher& MenuItemMatcher::round_doubles(double maxDifference)
343{
344 p->m_maxDifference = maxDifference;
345 return *this;
346}
347
348MenuItemMatcher& MenuItemMatcher::attribute(const string& name, const shared_ptr<GVariant>& value)
349{
350 p->m_attributes.emplace_back(name, value);
351 return *this;
352}
353
354MenuItemMatcher& MenuItemMatcher::boolean_attribute(const string& name, bool value)
355{
356 return attribute(
357 name,
358 shared_ptr<GVariant>(g_variant_new_boolean(value),
359 &gvariant_deleter));
360}
361
362MenuItemMatcher& MenuItemMatcher::string_attribute(const string& name, const string& value)
363{
364 return attribute(
365 name,
366 shared_ptr<GVariant>(g_variant_new_string(value.c_str()),
367 &gvariant_deleter));
368}
369
370MenuItemMatcher& MenuItemMatcher::int32_attribute(const std::string& name, int value)
371{
372 return attribute(
373 name,
374 shared_ptr<GVariant>(g_variant_new_int32 (value),
375 &gvariant_deleter));
376}
377
378MenuItemMatcher& MenuItemMatcher::int64_attribute(const std::string& name, int value)
379{
380 return attribute(
381 name,
382 shared_ptr<GVariant>(g_variant_new_int64 (value),
383 &gvariant_deleter));
384}
385
386MenuItemMatcher& MenuItemMatcher::double_attribute(const std::string& name, double value)
387{
388 return attribute(
389 name,
390 shared_ptr<GVariant>(g_variant_new_double (value),
391 &gvariant_deleter));
392}
393
394MenuItemMatcher& MenuItemMatcher::attribute_not_set(const std::string& name)
395{
396 p->m_not_exist_attributes.emplace_back (name);
397 return *this;
398}
399
400MenuItemMatcher& MenuItemMatcher::toggled(bool isToggled)
401{
402 p->m_isToggled = make_shared<bool>(isToggled);
403 return *this;
404}
405
406MenuItemMatcher& MenuItemMatcher::submenu()
407{
408 p->m_linkType = LinkType::submenu;
409 return *this;
410}
411
412MenuItemMatcher& MenuItemMatcher::section()
413{
414 p->m_linkType = LinkType::section;
415 return *this;
416}
417
418MenuItemMatcher& MenuItemMatcher::is_empty()
419{
420 return has_exactly(0);
421}
422
423MenuItemMatcher& MenuItemMatcher::has_exactly(size_t children)
424{
425 p->m_expectedSize = make_shared<size_t>(children);
426 return *this;
427}
428
429MenuItemMatcher& MenuItemMatcher::item(const MenuItemMatcher& item)
430{
431 p->m_items.emplace_back(item);
432 return *this;
433}
434
435MenuItemMatcher& MenuItemMatcher::item(MenuItemMatcher&& item)
436{
437 p->m_items.emplace_back(item);
438 return *this;
439}
440
441MenuItemMatcher& MenuItemMatcher::pass_through_activate(std::string const& action, const shared_ptr<GVariant>& parameter)
442{
443 p->m_activations.emplace_back(action, parameter);
444 return *this;
445}
446
447MenuItemMatcher& MenuItemMatcher::activate(const shared_ptr<GVariant>& parameter)
448{
449 p->m_activations.emplace_back(string(), parameter);
450 return *this;
451}
452
453MenuItemMatcher& MenuItemMatcher::set_pass_through_action_state(const std::string& action, const std::shared_ptr<GVariant>& state)
454{
455 p->m_setActionStates.emplace_back(action, state);
456 return *this;
457}
458
459MenuItemMatcher& MenuItemMatcher::set_action_state(const std::shared_ptr<GVariant>& state)
460{
461 p->m_setActionStates.emplace_back("", state);
462 return *this;
463}
464
465MenuItemMatcher& MenuItemMatcher::mode(Mode mode)
466{
467 p->m_mode = mode;
468 return *this;
469}
470
471void MenuItemMatcher::match(
472 MatchResult& matchResult,
473 const vector<unsigned int>& parentLocation,
474 const shared_ptr<GMenuModel>& menu,
475 map<string, shared_ptr<GActionGroup>>& actions,
476 unsigned int index) const
477{
478 shared_ptr<GMenuItem> menuItem(g_menu_item_new_from_model(menu.get(), index), &g_object_deleter);
479
480 vector<unsigned int> location(parentLocation);
481 location.emplace_back(index);
482
483 string action = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ACTION);
484
485 bool isCheckbox = false;
486 bool isRadio = false;
487 bool isToggled = false;
488
489 pair<string, string> idPair;
490 shared_ptr<GActionGroup> actionGroup;
491 shared_ptr<GVariant> state;
492
493 if (!action.empty())
494 {
495 idPair = split_action(action);
496 actionGroup = actions[idPair.first];
497 state = shared_ptr<GVariant>(g_action_group_get_action_state(actionGroup.get(),
498 idPair.second.c_str()),
499 &gvariant_deleter);
500 auto attributeTarget = get_attribute(menuItem, G_MENU_ATTRIBUTE_TARGET);
501
502 if (attributeTarget && state)
503 {
504 isToggled = g_variant_equal(state.get(), attributeTarget.get());
505 isRadio = true;
506 }
507 else if (state
508 && g_variant_is_of_type(state.get(), G_VARIANT_TYPE_BOOLEAN))
509 {
510 isToggled = g_variant_get_boolean(state.get());
511 isCheckbox = true;
512 }
513 }
514
515 Type actualType = Type::plain;
516 if (isCheckbox)
517 {
518 actualType = Type::checkbox;
519 }
520 else if (isRadio)
521 {
522 actualType = Type::radio;
523 }
524
525 if (actualType != p->m_type)
526 {
527 matchResult.failure(
528 location,
529 "Expected " + type_to_string(p->m_type) + ", found "
530 + type_to_string(actualType));
531 }
532
533 // check themed icons
534 map<string, vector<string>>::iterator iter;
535 for (iter = p->m_themed_icons.begin(); iter != p->m_themed_icons.end(); ++iter)
536 {
537 auto icon_val = g_menu_item_get_attribute_value(menuItem.get(), (*iter).first.c_str(), nullptr);
538 if (!icon_val)
539 {
540 matchResult.failure(
541 location,
542 "Expected themed icon " + (*iter).first + " was not found");
543 }
544
545 auto gicon = g_icon_deserialize(icon_val);
546 if (!gicon || !G_IS_THEMED_ICON(gicon))
547 {
548 matchResult.failure(
549 location,
550 "Expected attribute " + (*iter).first + " is not a themed icon");
551 }
552 else
553 {
554 auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon));
555 int nb_icons = 0;
556 while(iconNames[nb_icons])
557 {
558 ++nb_icons;
559 }
560
561 if (nb_icons != (*iter).second.size())
562 {
563 matchResult.failure(
564 location,
565 "Expected " + to_string((*iter).second.size()) +
566 " icons for themed icon [" + (*iter).first +
567 "], but " + to_string(nb_icons) + " were found.");
568 }
569 else
570 {
571 // now compare all the icons
572 for (int i = 0; i < nb_icons; ++i)
573 {
574 if ((*iter).second[i] != iconNames[i])
575 {
576 matchResult.failure(
577 location,
578 "Icon at position " + to_string(i) +
579 " for themed icon [" + (*iter).first +
580 "], mismatchs. Expected: " + iconNames[i] + " but found " + (*iter).second[i]);
581 }
582 }
583 }
584 }
585 g_object_unref(gicon);
586 }
587
588 string label = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_LABEL);
589 if (p->m_label && (*p->m_label) != label)
590 {
591 matchResult.failure(
592 location,
593 "Expected label '" + *p->m_label + "', but found '" + label
594 + "'");
595 }
596
597 string icon = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ICON);
598 if (p->m_icon && (*p->m_icon) != icon)
599 {
600 matchResult.failure(
601 location,
602 "Expected icon '" + *p->m_icon + "', but found '" + icon + "'");
603 }
604
605 if (p->m_action && (*p->m_action) != action)
606 {
607 matchResult.failure(
608 location,
609 "Expected action '" + *p->m_action + "', but found '" + action
610 + "'");
611 }
612
613 if (!p->m_state_icons.empty() && !state)
614 {
615 matchResult.failure(
616 location,
617 "Expected state icons but no state was found");
618 }
619 else if (!p->m_state_icons.empty() && state &&
620 !g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT))
621 {
622 matchResult.failure(
623 location,
624 "Expected state icons vardict, found "
625 + type_to_string(actualType));
626 }
627 else if (!p->m_state_icons.empty() && state &&
628 g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT))
629 {
630 std::vector<std::string> actual_state_icons;
631 GVariantIter it;
632 gchar* key;
633 GVariant* value;
634
635 g_variant_iter_init(&it, state.get());
636 while (g_variant_iter_loop(&it, "{sv}", &key, &value))
637 {
638 if (std::string(key) == "icon") {
639 auto gicon = g_icon_deserialize(value);
640 if (G_IS_THEMED_ICON(gicon))
641 {
642 auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon));
643 // Just take the first icon in the list (there is only ever one)
644 actual_state_icons.push_back(iconNames[0]);
645 }
646 g_object_unref(gicon);
647 }
648 else if (std::string(key) == "icons" && g_variant_is_of_type(value, G_VARIANT_TYPE("av")))
649 {
650 // If we find "icons" in the map, clear any icons we may have found in "icon",
651 // then break from the loop as we have found all icons now.
652 actual_state_icons.clear();
653 GVariantIter icon_it;
654 GVariant* icon_value;
655
656 g_variant_iter_init(&icon_it, value);
657 while (g_variant_iter_loop(&icon_it, "v", &icon_value))
658 {
659 auto gicon = g_icon_deserialize(icon_value);
660 if (G_IS_THEMED_ICON(gicon))
661 {
662 auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon));
663 // Just take the first icon in the list (there is only ever one)
664 actual_state_icons.push_back(iconNames[0]);
665 }
666 g_object_unref(gicon);
667 }
668 // We're breaking out of g_variant_iter_loop here so clean up
669 g_variant_unref(value);
670 g_free(key);
671 break;
672 }
673 }
674
675 if (p->m_state_icons != actual_state_icons)
676 {
677 std::string expected_icons;
678 for (unsigned int i = 0; i < p->m_state_icons.size(); ++i)
679 {
680 expected_icons += i == 0 ? p->m_state_icons[i] : ", " + p->m_state_icons[i];
681 }
682 std::string actual_icons;
683 for (unsigned int i = 0; i < actual_state_icons.size(); ++i)
684 {
685 actual_icons += i == 0 ? actual_state_icons[i] : ", " + actual_state_icons[i];
686 }
687 matchResult.failure(
688 location,
689 "Expected state_icons == {" + expected_icons
690 + "} but found {" + actual_icons + "}");
691 }
692 }
693
694 for (const auto& e: p->m_pass_through_attributes)
695 {
696 string actionName = get_string_attribute(menuItem, e.first.c_str());
697 if (actionName.empty())
698 {
699 matchResult.failure(
700 location,
701 "Could not find action name '" + e.first + "'");
702 }
703 else
704 {
705 auto passThroughIdPair = split_action(actionName);
706 auto actionGroup = actions[passThroughIdPair.first];
707 if (actionGroup)
708 {
709 auto value = get_action_group_attribute(
710 actionGroup, passThroughIdPair.second.c_str());
711 if (!value)
712 {
713 matchResult.failure(
714 location,
715 "Expected pass-through attribute '" + e.first
716 + "' was not present");
717 }
718 else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get())))
719 {
720 std::string expectedType = g_variant_get_type_string(e.second.get());
721 std::string actualType = g_variant_get_type_string(value.get());
722 matchResult.failure(
723 location,
724 "Expected pass-through attribute type '" + expectedType
725 + "' but found '" + actualType + "'");
726 }
727 else if (g_variant_compare(e.second.get(), value.get()))
728 {
729 bool reportMismatch = true;
730 if (g_strcmp0(g_variant_get_type_string(value.get()),"d") == 0 && p->m_maxDifference != 0.0)
731 {
732 auto actualDouble = g_variant_get_double(value.get());
733 auto expectedDouble = g_variant_get_double(e.second.get());
734 auto difference = actualDouble-expectedDouble;
735 if (difference < 0) difference = difference * -1.0;
736 if (difference <= p->m_maxDifference)
737 {
738 reportMismatch = false;
739 }
740 }
741 if (reportMismatch)
742 {
743 gchar* expectedString = g_variant_print(e.second.get(), true);
744 gchar* actualString = g_variant_print(value.get(), true);
745 matchResult.failure(
746 location,
747 "Expected pass-through attribute '" + e.first
748 + "' == " + expectedString + " but found "
749 + actualString);
750
751 g_free(expectedString);
752 g_free(actualString);
753 }
754 }
755 }
756 else
757 {
758 matchResult.failure(location, "Could not find action group for ID '" + passThroughIdPair.first + "'");
759 }
760 }
761 }
762
763 for (const auto& e: p->m_attributes)
764 {
765 auto value = get_attribute(menuItem, e.first.c_str());
766 if (!value)
767 {
768 matchResult.failure(location,
769 "Expected attribute '" + e.first
770 + "' could not be found");
771 }
772 else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get())))
773 {
774 std::string expectedType = g_variant_get_type_string(e.second.get());
775 std::string actualType = g_variant_get_type_string(value.get());
776 matchResult.failure(
777 location,
778 "Expected attribute type '" + expectedType
779 + "' but found '" + actualType + "'");
780 }
781 else if (g_variant_compare(e.second.get(), value.get()))
782 {
783 gchar* expectedString = g_variant_print(e.second.get(), true);
784 gchar* actualString = g_variant_print(value.get(), true);
785 matchResult.failure(
786 location,
787 "Expected attribute '" + e.first + "' == " + expectedString
788 + ", but found " + actualString);
789 g_free(expectedString);
790 g_free(actualString);
791 }
792 }
793
794 for (const auto& e: p->m_not_exist_attributes)
795 {
796 auto value = get_attribute(menuItem, e.c_str());
797 if (value)
798 {
799 matchResult.failure(location,
800 "Not expected attribute '" + e
801 + "' was found");
802 }
803 }
804
805 if (p->m_isToggled && (*p->m_isToggled) != isToggled)
806 {
807 matchResult.failure(
808 location,
809 "Expected toggled = " + bool_to_string(*p->m_isToggled)
810 + ", but found " + bool_to_string(isToggled));
811 }
812
813 if (!matchResult.success())
814 {
815 return;
816 }
817
818 if (!p->m_items.empty() || p->m_expectedSize)
819 {
820 shared_ptr<GMenuModel> link;
821
822 switch (p->m_linkType)
823 {
824 case LinkType::any:
825 {
826 link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter);
827 if (!link)
828 {
829 link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter);
830 }
831 break;
832 }
833 case LinkType::submenu:
834 {
835 link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter);
836 break;
837 }
838 case LinkType::section:
839 {
840 link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter);
841 break;
842 }
843 }
844
845
846 if (!link)
847 {
848 if (p->m_expectedSize)
849 {
850 matchResult.failure(
851 location,
852 "Expected " + to_string(*p->m_expectedSize)
853 + " children, but found none");
854 }
855 else
856 {
857 matchResult.failure(
858 location,
859 "Expected " + to_string(p->m_items.size())
860 + " children, but found none");
861 }
862 return;
863 }
864 else
865 {
866 while (true)
867 {
868 MatchResult childMatchResult(matchResult.createChild());
869
870 if (p->m_expectedSize
871 && *p->m_expectedSize
872 != (unsigned int) g_menu_model_get_n_items(
873 link.get()))
874 {
875 childMatchResult.failure(
876 location,
877 "Expected " + to_string(*p->m_expectedSize)
878 + " child items, but found "
879 + to_string(
880 g_menu_model_get_n_items(
881 link.get())));
882 }
883 else if (!p->m_items.empty())
884 {
885 switch (p->m_mode)
886 {
887 case Mode::all:
888 p->all(childMatchResult, location, link, actions);
889 break;
890 case Mode::starts_with:
891 p->startsWith(childMatchResult, location, link, actions);
892 break;
893 case Mode::ends_with:
894 p->endsWith(childMatchResult, location, link, actions);
895 break;
896 }
897 }
898
899 if (childMatchResult.success())
900 {
901 matchResult.merge(childMatchResult);
902 break;
903 }
904 else
905 {
906 if (matchResult.hasTimedOut())
907 {
908 matchResult.merge(childMatchResult);
909 break;
910 }
911 menuWaitForItems(link);
912 }
913 }
914 }
915 }
916
917
918 for (const auto& a: p->m_setActionStates)
919 {
920 auto stateAction = action;
921 auto stateIdPair = idPair;
922 auto stateActionGroup = actionGroup;
923 if (!a.first.empty())
924 {
925 stateAction = get_string_attribute(menuItem, a.first.c_str());;
926 stateIdPair = split_action(stateAction);
927 stateActionGroup = actions[stateIdPair.first];
928 }
929
930 if (stateAction.empty())
931 {
932 matchResult.failure(
933 location,
934 "Tried to set action state, but no action was found");
935 }
936 else if(!stateActionGroup)
937 {
938 matchResult.failure(
939 location,
940 "Tried to set action state for action group '" + stateIdPair.first
941 + "', but action group wasn't found");
942 }
943 else if (!g_action_group_has_action(stateActionGroup.get(), stateIdPair.second.c_str()))
944 {
945 matchResult.failure(
946 location,
947 "Tried to set action state for action '" + stateAction
948 + "', but action was not found");
949 }
950 else
951 {
952 g_action_group_change_action_state(stateActionGroup.get(), stateIdPair.second.c_str(),
953 g_variant_ref(a.second.get()));
954 }
955
956 // FIXME this is a dodgy way to ensure the action state change gets dispatched
957 menuWaitForItems(menu, 100);
958 }
959
960 for (const auto& a: p->m_activations)
961 {
962 string tmpAction = action;
963 auto tmpIdPair = idPair;
964 auto tmpActionGroup = actionGroup;
965 if (!a.first.empty())
966 {
967 tmpAction = get_string_attribute(menuItem, a.first.c_str());
968 tmpIdPair = split_action(tmpAction);
969 tmpActionGroup = actions[tmpIdPair.first];
970 }
971
972 if (tmpAction.empty())
973 {
974 matchResult.failure(
975 location,
976 "Tried to activate action, but no action was found");
977 }
978 else if(!tmpActionGroup)
979 {
980 matchResult.failure(
981 location,
982 "Tried to activate action group '" + tmpIdPair.first
983 + "', but action group wasn't found");
984 }
985 else if (!g_action_group_has_action(tmpActionGroup.get(), tmpIdPair.second.c_str()))
986 {
987 matchResult.failure(
988 location,
989 "Tried to activate action '" + tmpAction + "', but action was not found");
990 }
991 else
992 {
993 if (a.second)
994 {
995 g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(),
996 g_variant_ref(a.second.get()));
997 }
998 else
999 {
1000 g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(), nullptr);
1001 }
1002
1003 // FIXME this is a dodgy way to ensure the activation gets dispatched
1004 menuWaitForItems(menu, 100);
1005 }
1006 }
1007}
1008
1009} // namepsace gmenuharness
1010
1011} // namespace unity
01012
=== added file 'src/gmenuharness/MenuMatcher.cpp'
--- src/gmenuharness/MenuMatcher.cpp 1970-01-01 00:00:00 +0000
+++ src/gmenuharness/MenuMatcher.cpp 2015-10-28 09:41:22 +0000
@@ -0,0 +1,208 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Pete Woods <pete.woods@canonical.com>
17 */
18
19#include <unity/gmenuharness/MenuMatcher.h>
20#include <unity/gmenuharness/MatchUtils.h>
21
22#include <iostream>
23
24#include <gio/gio.h>
25
26using namespace std;
27
28namespace unity
29{
30
31namespace gmenuharness
32{
33
34namespace
35{
36
37static void gdbus_connection_deleter(GDBusConnection* connection)
38{
39// if (!g_dbus_connection_is_closed(connection))
40// {
41// g_dbus_connection_close_sync(connection, nullptr, nullptr);
42// }
43 g_clear_object(&connection);
44}
45}
46
47struct MenuMatcher::Parameters::Priv
48{
49 string m_busName;
50
51 vector<pair<string, string>> m_actions;
52
53 string m_menuObjectPath;
54};
55
56MenuMatcher::Parameters::Parameters(const string& busName,
57 const vector<pair<string, string>>& actions,
58 const string& menuObjectPath) :
59 p(new Priv)
60{
61 p->m_busName = busName;
62 p->m_actions = actions;
63 p->m_menuObjectPath = menuObjectPath;
64}
65
66MenuMatcher::Parameters::~Parameters()
67{
68}
69
70MenuMatcher::Parameters::Parameters(const Parameters& other) :
71 p(new Priv)
72{
73 *this = other;
74}
75
76MenuMatcher::Parameters::Parameters(Parameters&& other)
77{
78 *this = move(other);
79}
80
81MenuMatcher::Parameters& MenuMatcher::Parameters::operator=(const Parameters& other)
82{
83 p->m_busName = other.p->m_busName;
84 p->m_actions = other.p->m_actions;
85 p->m_menuObjectPath = other.p->m_menuObjectPath;
86 return *this;
87}
88
89MenuMatcher::Parameters& MenuMatcher::Parameters::operator=(Parameters&& other)
90{
91 p = move(other.p);
92 return *this;
93}
94
95struct MenuMatcher::Priv
96{
97 Priv(const Parameters& parameters) :
98 m_parameters(parameters)
99 {
100 }
101
102 Parameters m_parameters;
103
104 vector<MenuItemMatcher> m_items;
105
106 shared_ptr<GDBusConnection> m_system;
107
108 shared_ptr<GDBusConnection> m_session;
109
110 shared_ptr<GMenuModel> m_menu;
111
112 map<string, shared_ptr<GActionGroup>> m_actions;
113};
114
115MenuMatcher::MenuMatcher(const Parameters& parameters) :
116 p(new Priv(parameters))
117{
118 p->m_system.reset(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr),
119 &gdbus_connection_deleter);
120 g_dbus_connection_set_exit_on_close(p->m_system.get(), false);
121
122 p->m_session.reset(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr),
123 &gdbus_connection_deleter);
124 g_dbus_connection_set_exit_on_close(p->m_session.get(), false);
125
126 p->m_menu.reset(
127 G_MENU_MODEL(
128 g_dbus_menu_model_get(
129 p->m_session.get(),
130 p->m_parameters.p->m_busName.c_str(),
131 p->m_parameters.p->m_menuObjectPath.c_str())),
132 &g_object_deleter);
133
134 for (const auto& action : p->m_parameters.p->m_actions)
135 {
136 shared_ptr<GActionGroup> actionGroup(
137 G_ACTION_GROUP(
138 g_dbus_action_group_get(
139 p->m_session.get(),
140 p->m_parameters.p->m_busName.c_str(),
141 action.second.c_str())),
142 &g_object_deleter);
143 p->m_actions[action.first] = actionGroup;
144 }
145}
146
147MenuMatcher::~MenuMatcher()
148{
149}
150
151MenuMatcher& MenuMatcher::item(const MenuItemMatcher& item)
152{
153 p->m_items.emplace_back(item);
154 return *this;
155}
156
157void MenuMatcher::match(MatchResult& matchResult) const
158{
159 vector<unsigned int> location;
160
161 while (true)
162 {
163 MatchResult childMatchResult(matchResult.createChild());
164
165 int menuSize = g_menu_model_get_n_items(p->m_menu.get());
166 if (p->m_items.size() > (unsigned int) menuSize)
167 {
168 childMatchResult.failure(
169 location,
170 "Row count mismatch, expected " + to_string(p->m_items.size())
171 + " but found " + to_string(menuSize));
172 }
173 else
174 {
175 for (size_t i = 0; i < p->m_items.size(); ++i)
176 {
177 const auto& matcher = p->m_items.at(i);
178 matcher.match(childMatchResult, location, p->m_menu, p->m_actions, i);
179 }
180 }
181
182 if (childMatchResult.success())
183 {
184 matchResult.merge(childMatchResult);
185 break;
186 }
187 else
188 {
189 if (matchResult.hasTimedOut())
190 {
191 matchResult.merge(childMatchResult);
192 break;
193 }
194 menuWaitForItems(p->m_menu);
195 }
196 }
197}
198
199MatchResult MenuMatcher::match() const
200{
201 MatchResult matchResult;
202 match(matchResult);
203 return matchResult;
204}
205
206} // namespace gmenuharness
207
208} // namespace unity
0209
=== modified file 'src/service.vala'
--- src/service.vala 2015-10-02 14:07:44 +0000
+++ src/service.vala 2015-10-28 09:41:22 +0000
@@ -43,7 +43,7 @@
43 warn_notification.closed.connect((n) => { n.clear_actions(); });43 warn_notification.closed.connect((n) => { n.clear_actions(); });
44 BusWatcher.watch_namespace (GLib.BusType.SESSION,44 BusWatcher.watch_namespace (GLib.BusType.SESSION,
45 "org.freedesktop.Notifications",45 "org.freedesktop.Notifications",
46 () => { debug("Notifications name appeared"); notify_server_caps_checked = false; },46 () => { debug("Notifications name appeared"); },
47 () => { debug("Notifications name vanshed"); notify_server_caps_checked = false; });47 () => { debug("Notifications name vanshed"); notify_server_caps_checked = false; });
4848
49 this.settings = new Settings ("com.canonical.indicator.sound");49 this.settings = new Settings ("com.canonical.indicator.sound");
@@ -52,6 +52,8 @@
52 this.notify["visible"].connect ( () => this.update_root_icon () );52 this.notify["visible"].connect ( () => this.update_root_icon () );
5353
54 this.volume_control = volume;54 this.volume_control = volume;
55 this.volume_control.active_output_changed.connect (this.update_root_icon);
56 this.volume_control.active_output_changed.connect (this.update_notification);
5557
56 this.accounts_service = accounts;58 this.accounts_service = accounts;
57 /* If we're on the greeter, don't export */59 /* If we're on the greeter, don't export */
@@ -90,6 +92,10 @@
90 this.volume_control.bind_property ("high-volume", menu, "show-high-volume-warning", BindingFlags.SYNC_CREATE);92 this.volume_control.bind_property ("high-volume", menu, "show-high-volume-warning", BindingFlags.SYNC_CREATE);
91 });93 });
9294
95 this.menus.@foreach ( (profile, menu) => {
96 this.volume_control.active_output_changed.connect (menu.update_volume_slider);
97 });
98
93 this.sync_preferred_players ();99 this.sync_preferred_players ();
94 this.settings.changed["interested-media-players"].connect ( () => {100 this.settings.changed["interested-media-players"].connect ( () => {
95 this.sync_preferred_players ();101 this.sync_preferred_players ();
@@ -245,17 +251,7 @@
245251
246 void update_root_icon () {252 void update_root_icon () {
247 double volume = this.volume_control.volume.volume;253 double volume = this.volume_control.volume.volume;
248 string icon;254 string icon = get_volume_root_icon (volume, this.volume_control.mute, volume_control.active_output);
249 if (this.volume_control.mute || volume <= 0.0)
250 icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
251 else if (this.accounts_service != null && this.accounts_service.silentMode)
252 icon = "audio-volume-muted-panel";
253 else if (volume <= 0.3)
254 icon = "audio-volume-low-panel";
255 else if (volume <= 0.7)
256 icon = "audio-volume-medium-panel";
257 else
258 icon = "audio-volume-high-panel";
259255
260 string accessible_name;256 string accessible_name;
261 if (this.volume_control.mute) {257 if (this.volume_control.mute) {
@@ -281,6 +277,293 @@
281 private bool notify_server_supports_actions = false;277 private bool notify_server_supports_actions = false;
282 private bool notify_server_supports_sync = false;278 private bool notify_server_supports_sync = false;
283 private bool block_info_notifications = false;279 private bool block_info_notifications = false;
280 private bool waiting_user_approve_warn = false;
281
282 private string get_volume_icon (double volume, VolumeControl.ActiveOutput active_output)
283 {
284 string icon = "";
285 switch (active_output)
286 {
287 case VolumeControl.ActiveOutput.SPEAKERS:
288 if (volume <= 0.0)
289 icon = "audio-volume-muted";
290 else if (volume <= 0.3)
291 icon = "audio-volume-low";
292 else if (volume <= 0.7)
293 icon = "audio-volume-medium";
294 else
295 icon = "audio-volume-high";
296 break;
297 case VolumeControl.ActiveOutput.HEADPHONES:
298 if (volume <= 0.0)
299 icon = "audio-volume-muted";
300 else if (volume <= 0.3)
301 icon = "audio-volume-low";
302 else if (volume <= 0.7)
303 icon = "audio-volume-medium";
304 else
305 icon = "audio-volume-high";
306 break;
307 case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
308 if (volume <= 0.0)
309 icon = "audio-volume-muted";
310 else if (volume <= 0.3)
311 icon = "audio-volume-low";
312 else if (volume <= 0.7)
313 icon = "audio-volume-medium";
314 else
315 icon = "audio-volume-high";
316 break;
317 case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
318 if (volume <= 0.0)
319 icon = "audio-volume-muted";
320 else if (volume <= 0.3)
321 icon = "audio-volume-low";
322 else if (volume <= 0.7)
323 icon = "audio-volume-medium";
324 else
325 icon = "audio-volume-high";
326 break;
327 case VolumeControl.ActiveOutput.USB_SPEAKER:
328 if (volume <= 0.0)
329 icon = "audio-volume-muted";
330 else if (volume <= 0.3)
331 icon = "audio-volume-low";
332 else if (volume <= 0.7)
333 icon = "audio-volume-medium";
334 else
335 icon = "audio-volume-high";
336 break;
337 case VolumeControl.ActiveOutput.USB_HEADPHONES:
338 if (volume <= 0.0)
339 icon = "audio-volume-muted";
340 else if (volume <= 0.3)
341 icon = "audio-volume-low";
342 else if (volume <= 0.7)
343 icon = "audio-volume-medium";
344 else
345 icon = "audio-volume-high";
346 break;
347 case VolumeControl.ActiveOutput.HDMI_SPEAKER:
348 if (volume <= 0.0)
349 icon = "audio-volume-muted";
350 else if (volume <= 0.3)
351 icon = "audio-volume-low";
352 else if (volume <= 0.7)
353 icon = "audio-volume-medium";
354 else
355 icon = "audio-volume-high";
356 break;
357 case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
358 if (volume <= 0.0)
359 icon = "audio-volume-muted";
360 else if (volume <= 0.3)
361 icon = "audio-volume-low";
362 else if (volume <= 0.7)
363 icon = "audio-volume-medium";
364 else
365 icon = "audio-volume-high";
366 break;
367 }
368 return icon;
369 }
370
371 private string get_volume_root_icon_by_volume (double volume, VolumeControl.ActiveOutput active_output)
372 {
373 string icon = "";
374 switch (active_output)
375 {
376 case VolumeControl.ActiveOutput.SPEAKERS:
377 if (volume <= 0.0)
378 icon = "audio-volume-muted-panel";
379 else if (volume <= 0.3)
380 icon = "audio-volume-low-panel";
381 else if (volume <= 0.7)
382 icon = "audio-volume-medium-panel";
383 else
384 icon = "audio-volume-high-panel";
385 break;
386 case VolumeControl.ActiveOutput.HEADPHONES:
387 if (volume <= 0.0)
388 icon = "audio-volume-muted-panel";
389 else if (volume <= 0.3)
390 icon = "audio-volume-low-panel";
391 else if (volume <= 0.7)
392 icon = "audio-volume-medium-panel";
393 else
394 icon = "audio-volume-high-panel";
395 break;
396 case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
397 if (volume <= 0.0)
398 icon = "audio-volume-muted-panel";
399 else if (volume <= 0.3)
400 icon = "audio-volume-low-panel";
401 else if (volume <= 0.7)
402 icon = "audio-volume-medium-panel";
403 else
404 icon = "audio-volume-high-panel";
405 break;
406 case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
407 if (volume <= 0.0)
408 icon = "audio-volume-muted-panel";
409 else if (volume <= 0.3)
410 icon = "audio-volume-low-panel";
411 else if (volume <= 0.7)
412 icon = "audio-volume-medium-panel";
413 else
414 icon = "audio-volume-high-panel";
415 break;
416 case VolumeControl.ActiveOutput.USB_SPEAKER:
417 if (volume <= 0.0)
418 icon = "audio-volume-muted-panel";
419 else if (volume <= 0.3)
420 icon = "audio-volume-low-panel";
421 else if (volume <= 0.7)
422 icon = "audio-volume-medium-panel";
423 else
424 icon = "audio-volume-high-panel";
425 break;
426 case VolumeControl.ActiveOutput.USB_HEADPHONES:
427 if (volume <= 0.0)
428 icon = "audio-volume-muted-panel";
429 else if (volume <= 0.3)
430 icon = "audio-volume-low-panel";
431 else if (volume <= 0.7)
432 icon = "audio-volume-medium-panel";
433 else
434 icon = "audio-volume-high-panel";
435 break;
436 case VolumeControl.ActiveOutput.HDMI_SPEAKER:
437 if (volume <= 0.0)
438 icon = "audio-volume-muted-panel";
439 else if (volume <= 0.3)
440 icon = "audio-volume-low-panel";
441 else if (volume <= 0.7)
442 icon = "audio-volume-medium-panel";
443 else
444 icon = "audio-volume-high-panel";
445 break;
446 case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
447 if (volume <= 0.0)
448 icon = "audio-volume-muted-panel";
449 else if (volume <= 0.3)
450 icon = "audio-volume-low-panel";
451 else if (volume <= 0.7)
452 icon = "audio-volume-medium-panel";
453 else
454 icon = "audio-volume-high-panel";
455 break;
456 }
457 return icon;
458 }
459
460 private string get_volume_notification_icon (double volume, bool loud, VolumeControl.ActiveOutput active_output) {
461 string icon = "";
462 if (loud) {
463 switch (active_output)
464 {
465 case VolumeControl.ActiveOutput.SPEAKERS:
466 icon = "audio-volume-high";
467 break;
468 case VolumeControl.ActiveOutput.HEADPHONES:
469 icon = "audio-volume-high";
470 break;
471 case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
472 icon = "audio-volume-high";
473 break;
474 case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
475 icon = "audio-volume-high";
476 break;
477 case VolumeControl.ActiveOutput.USB_SPEAKER:
478 icon = "audio-volume-high";
479 break;
480 case VolumeControl.ActiveOutput.USB_HEADPHONES:
481 icon = "audio-volume-high";
482 break;
483 case VolumeControl.ActiveOutput.HDMI_SPEAKER:
484 icon = "audio-volume-high";
485 break;
486 case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
487 icon = "audio-volume-high";
488 break;
489 }
490 } else {
491 icon = get_volume_icon (volume, active_output);
492 }
493 return icon;
494 }
495
496 private string get_volume_root_icon (double volume, bool mute, VolumeControl.ActiveOutput active_output) {
497 string icon = "";
498 switch (active_output)
499 {
500 case VolumeControl.ActiveOutput.SPEAKERS:
501 if (mute || volume <= 0.0)
502 icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
503 else if (this.accounts_service != null && this.accounts_service.silentMode)
504 icon = "audio-volume-muted-panel";
505 else
506 icon = get_volume_root_icon_by_volume (volume, active_output);
507 break;
508 case VolumeControl.ActiveOutput.HEADPHONES:
509 if (mute || volume <= 0.0)
510 icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
511 else if (this.accounts_service != null && this.accounts_service.silentMode)
512 icon = "audio-volume-muted-panel";
513 else
514 icon = get_volume_root_icon_by_volume (volume, active_output);
515 break;
516 case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
517 if (mute || volume <= 0.0)
518 icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
519 else if (this.accounts_service != null && this.accounts_service.silentMode)
520 icon = "audio-volume-muted-panel";
521 else
522 icon = get_volume_root_icon_by_volume (volume, active_output);
523 break;
524 case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
525 if (mute || volume <= 0.0)
526 icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
527 else if (this.accounts_service != null && this.accounts_service.silentMode)
528 icon = "audio-volume-muted-panel";
529 else
530 icon = get_volume_root_icon_by_volume (volume, active_output);
531 break;
532 case VolumeControl.ActiveOutput.USB_SPEAKER:
533 if (mute || volume <= 0.0)
534 icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
535 else if (this.accounts_service != null && this.accounts_service.silentMode)
536 icon = "audio-volume-muted-panel";
537 else
538 icon = get_volume_root_icon_by_volume (volume, active_output);
539 break;
540 case VolumeControl.ActiveOutput.USB_HEADPHONES:
541 if (mute || volume <= 0.0)
542 icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
543 else if (this.accounts_service != null && this.accounts_service.silentMode)
544 icon = "audio-volume-muted-panel";
545 else
546 icon = get_volume_root_icon_by_volume (volume, active_output);
547 break;
548 case VolumeControl.ActiveOutput.HDMI_SPEAKER:
549 if (mute || volume <= 0.0)
550 icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
551 else if (this.accounts_service != null && this.accounts_service.silentMode)
552 icon = "audio-volume-muted-panel";
553 else
554 icon = get_volume_root_icon_by_volume (volume, active_output);
555 break;
556 case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
557 if (mute || volume <= 0.0)
558 icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel";
559 else if (this.accounts_service != null && this.accounts_service.silentMode)
560 icon = "audio-volume-muted-panel";
561 else
562 icon = get_volume_root_icon_by_volume (volume, active_output);
563 break;
564 }
565 return icon;
566 }
284567
285 private void update_notification () {568 private void update_notification () {
286569
@@ -312,47 +595,72 @@
312 _pre_warn_volume = null;595 _pre_warn_volume = null;
313 volume_control.volume = tmp;596 volume_control.volume = tmp;
314 }597 }
598 waiting_user_approve_warn = false;
315 });599 });
316 warn_notification.add_action ("cancel", _("Cancel"), (n, a) => {600 warn_notification.add_action ("cancel", _("Cancel"), (n, a) => {
317 _pre_warn_volume = null;601 _pre_warn_volume = null;
602 waiting_user_approve_warn = false;
318 });603 });
604 waiting_user_approve_warn = true;
319 show_notification(warn_notification);605 show_notification(warn_notification);
320 } else {606 } else {
321 close_notification(warn_notification);607 if (!waiting_user_approve_warn) {
322608 close_notification(warn_notification);
323 if (notify_server_supports_sync && !block_info_notifications) {609
324610 if (notify_server_supports_sync && !block_info_notifications) {
325 /* Determine Label */611
326 unowned string volume_label = loud612 /* Determine Label */
327 ? _("High volume can damage your hearing.")613 string volume_label = loud
328 : "";614 ? _("High volume can damage your hearing.")
329615 : "";
330 /* Choose an icon */616
331 unowned string icon;617 if (volume_label == "") {
332 if (loud) {618 if (volume_control.active_output == VolumeControl.ActiveOutput.SPEAKERS) {
333 icon = "audio-volume-high";619 volume_label = _("Speakers");
334 } else {620 }
335 var volume = volume_control.volume.volume;621
336622 if (volume_control.active_output == VolumeControl.ActiveOutput.HEADPHONES) {
337 if (volume <= 0.0)623 volume_label = _("Headphones");
338 icon = "audio-volume-muted";624 }
339 else if (volume <= 0.3)625
340 icon = "audio-volume-low";626 if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES) {
341 else if (volume <= 0.7)627 volume_label = _("Bluetooth headphones");
342 icon = "audio-volume-medium";628 }
343 else629
344 icon = "audio-volume-high";630 if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER) {
631 volume_label = _("Bluetooth speaker");
632 }
633
634 if (volume_control.active_output == VolumeControl.ActiveOutput.USB_SPEAKER) {
635 volume_label = _("Usb speaker");
636 }
637
638 if (volume_control.active_output == VolumeControl.ActiveOutput.USB_HEADPHONES) {
639 volume_label = _("Usb headphones");
640 }
641
642 if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_SPEAKER) {
643 volume_label = _("HDMI speaker");
644 }
645
646 if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_HEADPHONES) {
647 volume_label = _("HDMI headphones");
648 }
649 }
650
651 /* Choose an icon */
652 string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_output);
653
654 /* Reset the notification */
655 var n = this.info_notification;
656 n.update (_("Volume"), volume_label, icon);
657 n.clear_hints();
658 n.set_hint ("x-canonical-non-shaped-icon", "true");
659 n.set_hint ("x-canonical-private-synchronous", "true");
660 n.set_hint ("x-canonical-value-bar-tint", loud ? "true" : "false");
661 n.set_hint ("value", (int32)Math.round(get_volume_percent() * 100.0));
662 show_notification(n);
345 }663 }
346
347 /* Reset the notification */
348 var n = this.info_notification;
349 n.update (_("Volume"), volume_label, icon);
350 n.clear_hints();
351 n.set_hint ("x-canonical-non-shaped-icon", "true");
352 n.set_hint ("x-canonical-private-synchronous", "true");
353 n.set_hint ("x-canonical-value-bar-tint", loud ? "true" : "false");
354 n.set_hint ("value", (int32)Math.round(get_volume_percent() * 100.0));
355 show_notification(n);
356 }664 }
357 }665 }
358 }666 }
359667
=== modified file 'src/sound-menu.vala'
--- src/sound-menu.vala 2015-09-15 12:16:10 +0000
+++ src/sound-menu.vala 2015-10-28 09:41:22 +0000
@@ -71,6 +71,7 @@
71 this.notify_handlers = new HashTable<MediaPlayer, ulong> (direct_hash, direct_equal);71 this.notify_handlers = new HashTable<MediaPlayer, ulong> (direct_hash, direct_equal);
7272
73 this.greeter_players = (flags & DisplayFlags.GREETER_PLAYERS) != 0;73 this.greeter_players = (flags & DisplayFlags.GREETER_PLAYERS) != 0;
74
74 }75 }
7576
76 ~SoundMenu () {77 ~SoundMenu () {
@@ -193,6 +194,43 @@
193 this.notify_handlers.remove (player);194 this.notify_handlers.remove (player);
194 }195 }
195196
197 public void update_volume_slider (VolumeControl.ActiveOutput active_output) {
198 int index = find_action (this.volume_section, "indicator.volume");
199 if (index != -1) {
200 string label = "Volume";
201 switch (active_output) {
202 case VolumeControl.ActiveOutput.SPEAKERS:
203 label = _("Volume");
204 break;
205 case VolumeControl.ActiveOutput.HEADPHONES:
206 label = _("Volume (Headphones)");
207 break;
208 case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER:
209 label = _("Volume (Bluetooth)");
210 break;
211 case VolumeControl.ActiveOutput.USB_SPEAKER:
212 label = _("Volume (Usb)");
213 break;
214 case VolumeControl.ActiveOutput.HDMI_SPEAKER:
215 label = _("Volume (HDMI)");
216 break;
217 case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES:
218 label = _("Volume (Bluetooth headphones)");
219 break;
220 case VolumeControl.ActiveOutput.USB_HEADPHONES:
221 label = _("Volume (Usb headphones)");
222 break;
223 case VolumeControl.ActiveOutput.HDMI_HEADPHONES:
224 label = _("Volume (HDMI headphones)");
225 break;
226 }
227 this.volume_section.remove (index);
228 this.volume_section.insert_item (index, this.create_slider_menu_item (_(label), "indicator.volume(0)", 0.0, 1.0, 0.01,
229 "audio-volume-low-zero-panel",
230 "audio-volume-high-panel"));
231 }
232 }
233
196 public Menu root;234 public Menu root;
197 public Menu menu;235 public Menu menu;
198 Menu volume_section;236 Menu volume_section;
199237
=== modified file 'src/volume-control-pulse.vala'
--- src/volume-control-pulse.vala 2015-08-12 18:16:40 +0000
+++ src/volume-control-pulse.vala 2015-10-28 09:41:22 +0000
@@ -87,6 +87,7 @@
87 private bool _send_next_local_volume = false;87 private bool _send_next_local_volume = false;
88 private double _account_service_volume = 0.0;88 private double _account_service_volume = 0.0;
89 private bool _active_port_headphone = false;89 private bool _active_port_headphone = false;
90 private VolumeControl.ActiveOutput _active_output = VolumeControl.ActiveOutput.SPEAKERS;
9091
91 /** true when connected to the pulse server */92 /** true when connected to the pulse server */
92 public override bool ready { get; private set; }93 public override bool ready { get; private set; }
@@ -135,6 +136,49 @@
135 stop_high_volume_approved_timer();136 stop_high_volume_approved_timer();
136 }137 }
137138
139 private VolumeControl.ActiveOutput calculate_active_output (SinkInfo? sink) {
140
141 VolumeControl.ActiveOutput ret_output = VolumeControl.ActiveOutput.SPEAKERS;
142 /* Check if the current active port is headset/headphone */
143 /* There is not easy way to check if the port is a headset/headphone besides
144 * checking for the port name. On touch (with the pulseaudio droid element)
145 * the headset/headphone port is called 'output-headset' and 'output-headphone'.
146 * On the desktop this is usually called 'analog-output-headphones' */
147 // look if it's a headset/headphones
148 if (sink.name == "indicator_sound_test_headphones" ||
149 (sink.active_port != null &&
150 (sink.active_port.name.contains("headset") ||
151 sink.active_port.name.contains("headphone")))) {
152 _active_port_headphone = true;
153 // check if it's a bluetooth device
154 var device_bus = sink.proplist.gets ("device.bus");
155 if (device_bus != null && device_bus == "bluetooth") {
156 ret_output = VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES;
157 } else if (device_bus != null && device_bus == "usb") {
158 ret_output = VolumeControl.ActiveOutput.USB_HEADPHONES;
159 } else if (device_bus != null && device_bus == "hdmi") {
160 ret_output = VolumeControl.ActiveOutput.HDMI_HEADPHONES;
161 } else {
162 ret_output = VolumeControl.ActiveOutput.HEADPHONES;
163 }
164 } else {
165 // speaker
166 _active_port_headphone = false;
167 var device_bus = sink.proplist.gets ("device.bus");
168 if (device_bus != null && device_bus == "bluetooth") {
169 ret_output = VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER;
170 } else if (device_bus != null && device_bus == "usb") {
171 ret_output = VolumeControl.ActiveOutput.USB_SPEAKER;
172 } else if (device_bus != null && device_bus == "hdmi") {
173 ret_output = VolumeControl.ActiveOutput.HDMI_SPEAKER;
174 } else {
175 ret_output = VolumeControl.ActiveOutput.SPEAKERS;
176 }
177 }
178
179 return ret_output;
180 }
181
138 /* PulseAudio logic*/182 /* PulseAudio logic*/
139 private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index)183 private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index)
140 {184 {
@@ -201,18 +245,20 @@
201 this.notify_property ("is-playing");245 this.notify_property ("is-playing");
202 }246 }
203247
204 /* Check if the current active port is headset/headphone */248 // store the current status of the active output
205 /* There is not easy way to check if the port is a headset/headphone besides249 VolumeControl.ActiveOutput active_output_before = active_output;
206 * checking for the port name. On touch (with the pulseaudio droid element)250
207 * the headset/headphone port is called 'output-headset' and 'output-headphone'.251 // calculate the output
208 * On the desktop this is usually called 'analog-output-headphones' */252 _active_output = calculate_active_output (i);
209 if (i.active_port != null &&253
210 (i.active_port.name == "output-wired_headset" ||254 // check if the output has changed, if so... emit a signal
211 i.active_port.name == "output-wired_headphone" ||255 VolumeControl.ActiveOutput active_output_now = active_output;
212 i.active_port.name == "analog-output-headphones")) {256 if (active_output_now != active_output_before) {
213 _active_port_headphone = true;257 this.active_output_changed (active_output_now);
214 } else {258 if (active_output_now == VolumeControl.ActiveOutput.SPEAKERS) {
215 _active_port_headphone = false;259 _high_volume_approved = false;
260 }
261 update_high_volume();
216 }262 }
217263
218 if (_pulse_use_stream_restore == false &&264 if (_pulse_use_stream_restore == false &&
@@ -478,7 +524,8 @@
478 this.context = new PulseAudio.Context (loop.get_api(), null, props);524 this.context = new PulseAudio.Context (loop.get_api(), null, props);
479 this.context.set_state_callback (context_state_callback);525 this.context.set_state_callback (context_state_callback);
480526
481 if (context.connect(null, Context.Flags.NOFAIL, null) < 0)527 var server_string = Environment.get_variable("PULSE_SERVER");
528 if (context.connect(server_string, Context.Flags.NOFAIL, null) < 0)
482 warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno()));529 warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno()));
483 }530 }
484531
@@ -535,6 +582,14 @@
535 }582 }
536 }583 }
537584
585 public override VolumeControl.ActiveOutput active_output
586 {
587 get
588 {
589 return _active_output;
590 }
591 }
592
538 /* Volume operations */593 /* Volume operations */
539 private static PulseAudio.Volume double_to_volume (double vol)594 private static PulseAudio.Volume double_to_volume (double vol)
540 {595 {
@@ -710,7 +765,7 @@
710 private bool calculate_high_volume_from_volume(double volume) {765 private bool calculate_high_volume_from_volume(double volume) {
711 return _active_port_headphone766 return _active_port_headphone
712 && _warning_volume_enabled767 && _warning_volume_enabled
713 && volume >= _warning_volume_norms768 && volume > _warning_volume_norms
714 && (stream == "multimedia");769 && (stream == "multimedia");
715 }770 }
716771
717772
=== modified file 'src/volume-control.vala'
--- src/volume-control.vala 2015-08-12 14:00:56 +0000
+++ src/volume-control.vala 2015-10-28 09:41:22 +0000
@@ -28,6 +28,17 @@
28 VOLUME_STREAM_CHANGE28 VOLUME_STREAM_CHANGE
29 }29 }
3030
31 public enum ActiveOutput {
32 SPEAKERS,
33 HEADPHONES,
34 BLUETOOTH_HEADPHONES,
35 BLUETOOTH_SPEAKER,
36 USB_SPEAKER,
37 USB_HEADPHONES,
38 HDMI_SPEAKER,
39 HDMI_HEADPHONES
40 }
41
31 public class Volume : Object {42 public class Volume : Object {
32 public double volume;43 public double volume;
33 public VolumeReasons reason;44 public VolumeReasons reason;
@@ -39,6 +50,7 @@
39 public virtual bool high_volume { get { return false; } protected set { } }50 public virtual bool high_volume { get { return false; } protected set { } }
40 public virtual bool mute { get { return false; } }51 public virtual bool mute { get { return false; } }
41 public virtual bool is_playing { get { return false; } }52 public virtual bool is_playing { get { return false; } }
53 public virtual VolumeControl.ActiveOutput active_output { get { return VolumeControl.ActiveOutput.SPEAKERS; } }
42 private Volume _volume;54 private Volume _volume;
43 public virtual Volume volume { get { return _volume; } set { } }55 public virtual Volume volume { get { return _volume; } set { } }
44 public virtual double mic_volume { get { return 0.0; } set { } }56 public virtual double mic_volume { get { return 0.0; } set { } }
@@ -56,4 +68,6 @@
56 v.reason = reason;68 v.reason = reason;
57 this.volume = v;69 this.volume = v;
58 }70 }
71
72 public signal void active_output_changed (VolumeControl.ActiveOutput active_output);
59}73}
6074
=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 2015-08-11 21:42:20 +0000
+++ tests/CMakeLists.txt 2015-10-28 09:41:22 +0000
@@ -5,10 +5,10 @@
55
6include_directories(${GTEST_INCLUDE_DIR})6include_directories(${GTEST_INCLUDE_DIR})
77
8add_library (gtest STATIC8add_library (gtest-static STATIC
9 ${GTEST_SOURCE_DIR}/gtest-all.cc9 ${GTEST_SOURCE_DIR}/gtest-all.cc
10 ${GTEST_SOURCE_DIR}/gtest_main.cc)10 ${GTEST_SOURCE_DIR}/gtest_main.cc)
11target_link_libraries(gtest ${GTEST_LIBS})11target_link_libraries(gtest-static ${GTEST_LIBS})
1212
13###########################13###########################
14# GSettings Schema14# GSettings Schema
@@ -22,7 +22,8 @@
22# GSettings:22# GSettings:
23# compile the indicator-sound schema into a gschemas.compiled file in this directory,23# compile the indicator-sound schema into a gschemas.compiled file in this directory,
24# and help the tests to find that file by setting -DSCHEMA_DIR24# and help the tests to find that file by setting -DSCHEMA_DIR
25set (SCHEMA_DIR "${CMAKE_CURRENT_BINARY_DIR}/gsettings-schemas")25set (XDG_DATA_DIRS "${CMAKE_CURRENT_BINARY_DIR}/gsettings-schemas")
26set (SCHEMA_DIR "${XDG_DATA_DIRS}/glib-2.0/schemas")
26add_definitions(-DSCHEMA_DIR="${SCHEMA_DIR}")27add_definitions(-DSCHEMA_DIR="${SCHEMA_DIR}")
27execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas28execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas
28 OUTPUT_VARIABLE COMPILE_SCHEMA_EXECUTABLE29 OUTPUT_VARIABLE COMPILE_SCHEMA_EXECUTABLE
@@ -31,6 +32,7 @@
31 DEPENDS ${CMAKE_SOURCE_DIR}/data/com.canonical.indicator.sound.gschema.xml32 DEPENDS ${CMAKE_SOURCE_DIR}/data/com.canonical.indicator.sound.gschema.xml
32 COMMAND mkdir -p ${SCHEMA_DIR}33 COMMAND mkdir -p ${SCHEMA_DIR}
33 COMMAND cp -f ${CMAKE_SOURCE_DIR}/data/*gschema.xml ${SCHEMA_DIR}34 COMMAND cp -f ${CMAKE_SOURCE_DIR}/data/*gschema.xml ${SCHEMA_DIR}
35 COMMAND cp -f /usr/share/glib-2.0/schemas/com.ubuntu.sound.gschema.xml ${SCHEMA_DIR}
34 COMMAND ${COMPILE_SCHEMA_EXECUTABLE} ${SCHEMA_DIR})36 COMMAND ${COMPILE_SCHEMA_EXECUTABLE} ${SCHEMA_DIR})
3537
36###########################38###########################
@@ -41,74 +43,74 @@
41set(VALA_MOCKS_SYMBOLS_PATH "${CMAKE_CURRENT_BINARY_DIR}/vala-mocks.def")43set(VALA_MOCKS_SYMBOLS_PATH "${CMAKE_CURRENT_BINARY_DIR}/vala-mocks.def")
4244
43vala_init(vala-mocks45vala_init(vala-mocks
44 DEPENDS46 DEPENDS
45 indicator-sound-service-lib47 indicator-sound-service-lib
46 PACKAGES48 PACKAGES
47 config49 config
48 gio-2.050 gio-2.0
49 gio-unix-2.051 gio-unix-2.0
50 libxml-2.052 libxml-2.0
51 libpulse53 libpulse
52 libpulse-mainloop-glib54 libpulse-mainloop-glib
53 libnotify55 libnotify
54 accounts-service56 accounts-service
55 indicator-sound-service57 indicator-sound-service
56 OPTIONS58 OPTIONS
57 --ccode59 --ccode
58 --thread60 --thread
59 --vapidir=${CMAKE_BINARY_DIR}/src/61 --vapidir=${CMAKE_BINARY_DIR}/src/
60 --vapidir=${CMAKE_SOURCE_DIR}/vapi/62 --vapidir=${CMAKE_SOURCE_DIR}/vapi/
61 --vapidir=.63 --vapidir=.
62)64)
6365
64vala_add(vala-mocks66vala_add(vala-mocks
65 media-player-mock.vala67 media-player-mock.vala
66)68)
6769
68vala_add(vala-mocks70vala_add(vala-mocks
69 media-player-list-mock.vala71 media-player-list-mock.vala
70)72)
7173
72vala_add(vala-mocks74vala_add(vala-mocks
73 volume-control-mock.vala75 volume-control-mock.vala
74)76)
7577
76vala_finish(vala-mocks78vala_finish(vala-mocks
77 SOURCES79 SOURCES
78 vala_mocks_VALA_SOURCES80 vala_mocks_VALA_SOURCES
79 OUTPUTS81 OUTPUTS
80 vala_mocks_VALA_C82 vala_mocks_VALA_C
81 GENERATE_HEADER83 GENERATE_HEADER
82 ${VALA_MOCKS_HEADER_PATH}84 ${VALA_MOCKS_HEADER_PATH}
83 GENERATE_SYMBOLS85 GENERATE_SYMBOLS
84 ${VALA_MOCKS_SYMBOLS_PATH}86 ${VALA_MOCKS_SYMBOLS_PATH}
85)87)
8688
87set_source_files_properties(89set_source_files_properties(
88 ${vala_mocks_VALA_SOURCES}90 ${vala_mocks_VALA_SOURCES}
89 PROPERTIES91 PROPERTIES
90 HEADER_FILE_ONLY TRUE92 HEADER_FILE_ONLY TRUE
91)93)
9294
93set(95set(
94 VALA_MOCKS_SOURCES96 VALA_MOCKS_SOURCES
95 ${vala_mocks_VALA_SOURCES}97 ${vala_mocks_VALA_SOURCES}
96 ${vala_mocks_VALA_C}98 ${vala_mocks_VALA_C}
97 ${VALA_MOCKS_SYMBOLS_PATH}99 ${VALA_MOCKS_SYMBOLS_PATH}
98)100)
99101
100add_definitions(102add_definitions(
101 -Wno-unused-but-set-variable103 -Wno-unused-but-set-variable
102)104)
103105
104add_library(106add_library(
105 vala-mocks-lib STATIC107 vala-mocks-lib STATIC
106 ${VALA_MOCKS_SOURCES}108 ${VALA_MOCKS_SOURCES}
107)109)
108110
109target_link_libraries(111target_link_libraries(
110 vala-mocks-lib112 vala-mocks-lib
111 indicator-sound-service-lib113 indicator-sound-service-lib
112)114)
113115
114include_directories(${CMAKE_CURRENT_BINARY_DIR})116include_directories(${CMAKE_CURRENT_BINARY_DIR})
@@ -118,9 +120,9 @@
118###########################120###########################
119121
120add_library(122add_library(
121 pulse-mock123 pulse-mock
122 SHARED124 SHARED
123 pa-mock.cpp125 pa-mock.cpp
124)126)
125127
126target_link_libraries (pulse-mock ${PULSEAUDIO_LIBRARIES})128target_link_libraries (pulse-mock ${PULSEAUDIO_LIBRARIES})
@@ -131,7 +133,7 @@
131133
132include_directories(${CMAKE_SOURCE_DIR}/src)134include_directories(${CMAKE_SOURCE_DIR}/src)
133add_executable (name-watch-test name-watch-test.cc ${CMAKE_SOURCE_DIR}/src/bus-watch-namespace.c)135add_executable (name-watch-test name-watch-test.cc ${CMAKE_SOURCE_DIR}/src/bus-watch-namespace.c)
134target_link_libraries (name-watch-test gtest ${SOUNDSERVICE_LIBRARIES})136target_link_libraries (name-watch-test gtest-static ${SOUNDSERVICE_LIBRARIES})
135add_test(name-watch-test name-watch-test)137add_test(name-watch-test name-watch-test)
136138
137###########################139###########################
@@ -141,21 +143,21 @@
141include_directories(${CMAKE_SOURCE_DIR}/src)143include_directories(${CMAKE_SOURCE_DIR}/src)
142add_executable (accounts-service-user-test accounts-service-user.cc)144add_executable (accounts-service-user-test accounts-service-user.cc)
143target_link_libraries (145target_link_libraries (
144 accounts-service-user-test146 accounts-service-user-test
145 indicator-sound-service-lib147 indicator-sound-service-lib
146 vala-mocks-lib148 vala-mocks-lib
147 gtest149 gtest-static
148 ${SOUNDSERVICE_LIBRARIES}150 ${SOUNDSERVICE_LIBRARIES}
149 ${TEST_LIBRARIES}151 ${TEST_LIBRARIES}
150)152)
151153
152# Split tests to work around libaccountservice sucking154# Split tests to work around libaccountservice sucking
153add_test(accounts-service-user-test-basic155add_test(accounts-service-user-test-basic
154 accounts-service-user-test --gtest_filter=AccountsServiceUserTest.BasicObject156 accounts-service-user-test --gtest_filter=AccountsServiceUserTest.BasicObject
155)157)
156158
157add_test(accounts-service-user-test-player159add_test(accounts-service-user-test-player
158 accounts-service-user-test --gtest_filter=AccountsServiceUserTest.SetMediaPlayer160 accounts-service-user-test --gtest_filter=AccountsServiceUserTest.SetMediaPlayer
159)161)
160162
161###########################163###########################
@@ -165,11 +167,11 @@
165include_directories(${CMAKE_SOURCE_DIR}/src)167include_directories(${CMAKE_SOURCE_DIR}/src)
166add_executable (volume-control-test volume-control-test.cc gschemas.compiled)168add_executable (volume-control-test volume-control-test.cc gschemas.compiled)
167target_link_libraries (169target_link_libraries (
168 volume-control-test170 volume-control-test
169 indicator-sound-service-lib171 indicator-sound-service-lib
170 pulse-mock172 pulse-mock
171 gtest173 gtest-static
172 ${TEST_LIBRARIES}174 ${TEST_LIBRARIES}
173)175)
174176
175add_test(volume-control-test volume-control-test)177add_test(volume-control-test volume-control-test)
@@ -181,12 +183,12 @@
181include_directories(${CMAKE_SOURCE_DIR}/src)183include_directories(${CMAKE_SOURCE_DIR}/src)
182add_executable (sound-menu-test sound-menu.cc)184add_executable (sound-menu-test sound-menu.cc)
183target_link_libraries (185target_link_libraries (
184 sound-menu-test186 sound-menu-test
185 indicator-sound-service-lib187 indicator-sound-service-lib
186 vala-mocks-lib188 vala-mocks-lib
187 gtest189 gtest-static
188 ${SOUNDSERVICE_LIBRARIES}190 ${SOUNDSERVICE_LIBRARIES}
189 ${TEST_LIBRARIES}191 ${TEST_LIBRARIES}
190)192)
191193
192add_test(sound-menu-test sound-menu-test)194add_test(sound-menu-test sound-menu-test)
@@ -198,13 +200,13 @@
198include_directories(${CMAKE_SOURCE_DIR}/src)200include_directories(${CMAKE_SOURCE_DIR}/src)
199add_executable (notifications-test notifications-test.cc)201add_executable (notifications-test notifications-test.cc)
200target_link_libraries (202target_link_libraries (
201 notifications-test203 notifications-test
202 indicator-sound-service-lib204 indicator-sound-service-lib
203 vala-mocks-lib205 vala-mocks-lib
204 pulse-mock206 pulse-mock
205 gtest207 gtest-static
206 ${SOUNDSERVICE_LIBRARIES}208 ${SOUNDSERVICE_LIBRARIES}
207 ${TEST_LIBRARIES}209 ${TEST_LIBRARIES}
208)210)
209211
210add_test(notifications-test notifications-test)212add_test(notifications-test notifications-test)
@@ -216,23 +218,23 @@
216include_directories(${CMAKE_SOURCE_DIR}/src)218include_directories(${CMAKE_SOURCE_DIR}/src)
217add_executable (media-player-user-test media-player-user.cc)219add_executable (media-player-user-test media-player-user.cc)
218target_link_libraries (220target_link_libraries (
219 media-player-user-test221 media-player-user-test
220 indicator-sound-service-lib222 indicator-sound-service-lib
221 vala-mocks-lib223 vala-mocks-lib
222 gtest224 gtest-static
223 ${SOUNDSERVICE_LIBRARIES}225 ${SOUNDSERVICE_LIBRARIES}
224 ${TEST_LIBRARIES}226 ${TEST_LIBRARIES}
225)227)
226228
227# Split tests to work around libaccountservice sucking229# Split tests to work around libaccountservice sucking
228add_test(media-player-user-test-basic230add_test(media-player-user-test-basic
229 media-player-user-test --gtest_filter=MediaPlayerUserTest.BasicObject231 media-player-user-test --gtest_filter=MediaPlayerUserTest.BasicObject
230)232)
231add_test(media-player-user-test-dataset233add_test(media-player-user-test-dataset
232 media-player-user-test --gtest_filter=MediaPlayerUserTest.DataSet234 media-player-user-test --gtest_filter=MediaPlayerUserTest.DataSet
233)235)
234add_test(media-player-user-test-timeout236add_test(media-player-user-test-timeout
235 media-player-user-test --gtest_filter=MediaPlayerUserTest.TimeoutTest237 media-player-user-test --gtest_filter=MediaPlayerUserTest.TimeoutTest
236)238)
237239
238###########################240###########################
@@ -242,20 +244,20 @@
242include_directories(${CMAKE_SOURCE_DIR}/src)244include_directories(${CMAKE_SOURCE_DIR}/src)
243add_executable (greeter-list-test greeter-list.cc)245add_executable (greeter-list-test greeter-list.cc)
244target_link_libraries (246target_link_libraries (
245 greeter-list-test247 greeter-list-test
246 indicator-sound-service-lib248 indicator-sound-service-lib
247 vala-mocks-lib249 vala-mocks-lib
248 gtest250 gtest-static
249 ${SOUNDSERVICE_LIBRARIES}251 ${SOUNDSERVICE_LIBRARIES}
250 ${TEST_LIBRARIES}252 ${TEST_LIBRARIES}
251)253)
252254
253# Split tests to work around libaccountservice sucking255# Split tests to work around libaccountservice sucking
254add_test(greeter-list-test-basic256add_test(greeter-list-test-basic
255 greeter-list-test --gtest_filter=GreeterListTest.BasicObject257 greeter-list-test --gtest_filter=GreeterListTest.BasicObject
256)258)
257add_test(greeter-list-test-iterator259add_test(greeter-list-test-iterator
258 greeter-list-test --gtest_filter=GreeterListTest.BasicIterator260 greeter-list-test --gtest_filter=GreeterListTest.BasicIterator
259)261)
260262
261###########################263###########################
@@ -263,18 +265,22 @@
263###########################265###########################
264266
265add_definitions(267add_definitions(
266 -DINDICATOR_SOUND_SERVICE_BINARY="${CMAKE_BINARY_DIR}/src/indicator-sound-service"268 -DINDICATOR_SOUND_SERVICE_BINARY="${CMAKE_BINARY_DIR}/src/indicator-sound-service"
267 -DPA_MOCK_LIB="${CMAKE_CURRENT_BINARY_DIR}/libpulse-mock.so"269 -DPA_MOCK_LIB="${CMAKE_CURRENT_BINARY_DIR}/libpulse-mock.so"
268)270)
269add_executable (indicator-test indicator-test.cc gschemas.compiled)271add_executable (indicator-test indicator-test.cc gschemas.compiled)
270target_link_libraries (272target_link_libraries (
271 indicator-test273 indicator-test
272 gtest274 gtest-static
273 ${SOUNDSERVICE_LIBRARIES}275 ${SOUNDSERVICE_LIBRARIES}
274 ${TEST_LIBRARIES}276 ${TEST_LIBRARIES}
275)277)
276278
277# Split tests to work around libaccountservice sucking279# Split tests to work around libaccountservice sucking
278add_test(indcator-test280add_test(indcator-test
279 indicator-test281 indicator-test
280)282)
283
284add_subdirectory(integration)
285add_subdirectory(dbus-types)
286add_subdirectory(service-mocks)
281\ No newline at end of file287\ No newline at end of file
282288
=== added directory 'tests/dbus-types'
=== added file 'tests/dbus-types/CMakeLists.txt'
--- tests/dbus-types/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/CMakeLists.txt 2015-10-28 09:41:22 +0000
@@ -0,0 +1,53 @@
1set(CMAKE_AUTOMOC ON)
2set(CMAKE_INCLUDE_CURRENT_DIR ON)
3
4find_package(Qt5DBus REQUIRED)
5include_directories(${Qt5DBus_INCLUDE_DIRS})
6
7add_definitions(-DQT_NO_KEYWORDS=1)
8
9set(dbusinterface_streamrestore_xml "org.PulseAudio.Ext.StreamRestore1.xml")
10set_source_files_properties(${dbusinterface_streamrestore_xml} PROPERTIES
11 CLASSNAME StreamRestoreInterface)
12
13set(dbusinterface_accounts_xml "org.freedesktop.Accounts.xml")
14set_source_files_properties(${dbusinterface_accounts_xml} PROPERTIES
15 CLASSNAME AccountsInterface)
16
17set(dbusinterface_accountssound_xml "com.ubuntu.AccountsService.Sound.xml")
18set_source_files_properties(${dbusinterface_accountssound_xml} PROPERTIES
19 CLASSNAME AccountsSoundInterface)
20
21set(dbusinterface_properties_xml "org.freedesktop.DBus.Properties.xml")
22set_source_files_properties(${dbusinterface_properties_xml} PROPERTIES
23 CLASSNAME DBusPropertiesInterface
24 NO_NAMESPACE YES
25 INCLUDE "dbus-types.h")
26
27set(dbusinterface_actions_xml "org.gtk.Actions.xml")
28set_source_files_properties(${dbusinterface_actions_xml} PROPERTIES
29 CLASSNAME MenusInterface)
30
31set(dbusinterface_notifications_xml "org.freedesktop.Notifications.xml")
32set_source_files_properties(${dbusinterface_notifications_xml} PROPERTIES
33 CLASSNAME NotificationsInterface)
34
35qt5_add_dbus_interface(interface_files ${dbusinterface_streamrestore_xml} stream_restore_interface)
36qt5_add_dbus_interface(interface_files ${dbusinterface_properties_xml} dbus_properties_interface)
37qt5_add_dbus_interface(interface_files ${dbusinterface_accounts_xml} dbus_accounts_interface)
38qt5_add_dbus_interface(interface_files ${dbusinterface_accountssound_xml} dbus_accountssound_interface)
39qt5_add_dbus_interface(interface_files ${dbusinterface_actions_xml} dbus_menus_interface)
40qt5_add_dbus_interface(interface_files ${dbusinterface_notifications_xml} dbus_notifications_interface)
41
42add_library(
43 sound-indicator-dbus-interfaces
44 STATIC
45 ${interface_files}
46 pulseaudio-volume.cpp
47)
48
49qt5_use_modules(
50 sound-indicator-dbus-interfaces
51 Core
52 DBus
53)
054
=== added file 'tests/dbus-types/com.ubuntu.AccountsService.Sound.xml'
--- tests/dbus-types/com.ubuntu.AccountsService.Sound.xml 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/com.ubuntu.AccountsService.Sound.xml 2015-10-28 09:41:22 +0000
@@ -0,0 +1,9 @@
1<node>
2 <interface name="com.ubuntu.AccountsService.Sound">
3 <method name="Set">
4 <arg direction="in" type="s" name="interface" />
5 <arg direction="in" type="s" name="property" />
6 <arg direction="out" type="o" name="path" />
7 </method>
8 </interface>
9</node>
010
=== added file 'tests/dbus-types/dbus-types.h'
--- tests/dbus-types/dbus-types.h 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/dbus-types.h 2015-10-28 09:41:22 +0000
@@ -0,0 +1,48 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
17 */
18#pragma once
19
20#include <QDBusMetaType>
21#include "pulseaudio-volume.h"
22
23namespace DBusTypes
24{
25 inline void registerMetaTypes()
26 {
27 PulseaudioVolume::registerMetaType();
28 PulseaudioVolumeArray::registerMetaType();
29 }
30
31 static constexpr char const* DBUS_NAME = "com.canonical.indicator.sound";
32
33 static constexpr char const* DBUS_PULSE = "org.PulseAudio1";
34
35 static constexpr char const* ACCOUNTS_SERVICE = "org.freedesktop.Accounts";
36
37 static constexpr char const* STREAM_RESTORE_NAME = "org.PulseAudio.Ext.StreamRestore1";
38
39 static constexpr char const* STREAM_RESTORE_PATH = "/org/pulseaudio/stream_restore1";
40
41 static constexpr char const* STREAM_RESTORE_ENTRY_NAME = "org.PulseAudio.Ext.StreamRestore1.RestoreEntry";
42
43 static constexpr char const* MAIN_SERVICE_PATH = "/com/canonical/indicator/sound";
44
45 static constexpr char const* ACTIONS_INTERFACE = "org.gtk.Actions";
46
47} // namespace DBusTypes
48
049
=== added file 'tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml'
--- tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml 2015-10-28 09:41:22 +0000
@@ -0,0 +1,7 @@
1<node>
2 <interface name="org.PulseAudio.Ext.StreamRestore1">
3 <method name="GetEntryByName">
4 <arg direction="in" type="s" name="entry" />
5 <arg direction="out" type="o" name="value" />
6 </interface>
7</node>
08
=== added file 'tests/dbus-types/org.freedesktop.Accounts.xml'
--- tests/dbus-types/org.freedesktop.Accounts.xml 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/org.freedesktop.Accounts.xml 2015-10-28 09:41:22 +0000
@@ -0,0 +1,8 @@
1<node>
2 <interface name="org.freedesktop.Accounts">
3 <method name="FindUserByName">
4 <arg direction="in" type="s" name="user" />
5 <arg direction="out" type="o" name="path" />
6 </method>
7 </interface>
8</node>
09
=== added file 'tests/dbus-types/org.freedesktop.DBus.Properties.xml'
--- tests/dbus-types/org.freedesktop.DBus.Properties.xml 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/org.freedesktop.DBus.Properties.xml 2015-10-28 09:41:22 +0000
@@ -0,0 +1,22 @@
1<node>
2 <interface name="org.freedesktop.DBus.Properties">
3 <method name="Set">
4 <arg direction="in" type="s" name="entry" />
5 <arg direction="in" type="s" name="property" />
6 <arg direction="in" type="v" name="value" />
7 </method>
8
9 <method name="Get">
10 <arg direction="in" type="s" name="entry" />
11 <arg direction="in" type="s" name="property" />
12 <arg direction="out" type="v" name="value" />
13 </method>
14
15 <signal name="PropertiesChanged">
16 <arg type="s" name="interface_name"/>
17 <arg type="a{sv}" name="changed_properties"/>
18 <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
19 <arg type="as" name="invalidated_properties"/>
20 </signal>
21 </interface>
22</node>
023
=== added file 'tests/dbus-types/org.freedesktop.Notifications.xml'
--- tests/dbus-types/org.freedesktop.Notifications.xml 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/org.freedesktop.Notifications.xml 2015-10-28 09:41:22 +0000
@@ -0,0 +1,47 @@
1<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
2"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
3<node>
4 <interface name="org.freedesktop.Notifications">
5 <signal name="NotificationClosed">
6 <arg name="id" type="u" direction="out"/>
7 <arg name="reason" type="u" direction="out"/>
8 </signal>
9 <signal name="ActionInvoked">
10 <arg name="id" type="u" direction="out"/>
11 <arg name="action_key" type="s" direction="out"/>
12 </signal>
13 <signal name="dataChanged">
14 <arg name="id" type="u" direction="out"/>
15 </signal>
16 <method name="CloseNotification">
17 <arg name="id" type="u" direction="in"/>
18 </method>
19 <method name="GetServerInformation">
20 <arg name="name" type="s" direction="out"/>
21 <arg name="vendor" type="s" direction="out"/>
22 <arg name="version" type="s" direction="out"/>
23 <arg name="specVersion" type="s" direction="out"/>
24 </method>
25 <method name="GetCapabilities">
26 <arg type="as" direction="out"/>
27 </method>
28 <method name="Notify">
29 <arg type="u" direction="out"/>
30 <arg name="app_name" type="s" direction="in"/>
31 <arg name="replaces_id" type="u" direction="in"/>
32 <arg name="app_icon" type="s" direction="in"/>
33 <arg name="summary" type="s" direction="in"/>
34 <arg name="body" type="s" direction="in"/>
35 <arg name="actions" type="as" direction="in"/>
36 <arg name="hints" type="a{sv}" direction="in"/>
37 <annotation name="org.qtproject.QtDBus.QtTypeName.In6" value="QVariantMap"/>
38 <arg name="expire_timeout" type="i" direction="in"/>
39 </method>
40 <method name="onDataChanged">
41 <arg name="id" type="u" direction="in"/>
42 </method>
43 <method name="onCompleted">
44 <arg name="id" type="u" direction="in"/>
45 </method>
46 </interface>
47</node>
048
=== added file 'tests/dbus-types/org.gtk.Actions.xml'
--- tests/dbus-types/org.gtk.Actions.xml 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/org.gtk.Actions.xml 2015-10-28 09:41:22 +0000
@@ -0,0 +1,13 @@
1<node>
2 <interface name="org.gtk.Actions">
3 <signal name="Changed">
4 <arg name="removedActions" type="as" direction="in" />
5 <arg name="actionsFlags" type="a{sb}" direction="in" />
6 <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
7 <arg name="actionsChanged" type="a{sv}" direction="in" />
8 <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
9 <arg name="actionsAdded" type="a{s(bgav)}" direction="in" />
10 <annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QVariantMap"/>
11 </signal>
12 </interface>
13</node>
014
=== added file 'tests/dbus-types/pulseaudio-volume.cpp'
--- tests/dbus-types/pulseaudio-volume.cpp 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/pulseaudio-volume.cpp 2015-10-28 09:41:22 +0000
@@ -0,0 +1,156 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
17 */
18#include "dbus-types.h"
19
20PulseaudioVolume::PulseaudioVolume() :
21 type_(0),
22 volume_(10)
23{
24}
25
26PulseaudioVolume::PulseaudioVolume(unsigned int type, unsigned int volume) :
27 type_(type)
28 , volume_(volume)
29{
30}
31
32PulseaudioVolume::PulseaudioVolume(const PulseaudioVolume &other) :
33 type_(other.type_),
34 volume_(other.volume_)
35{
36}
37
38PulseaudioVolume& PulseaudioVolume::operator=(const PulseaudioVolume &other)
39{
40 type_ = other.type_;
41 volume_ = other.volume_;
42
43 return *this;
44}
45
46PulseaudioVolume::~PulseaudioVolume()
47{
48}
49
50unsigned int PulseaudioVolume::getType() const
51{
52 return type_;
53}
54
55unsigned int PulseaudioVolume::getVolume() const
56{
57 return volume_;
58}
59
60void PulseaudioVolume::registerMetaType()
61{
62 qRegisterMetaType<PulseaudioVolume>("PulseaudioVolume");
63
64 qDBusRegisterMetaType<PulseaudioVolume>();
65}
66
67QDBusArgument &operator<<(QDBusArgument &argument, const PulseaudioVolume& volume)
68{
69 argument.beginStructure();
70 argument << volume.type_;
71 argument << volume.volume_;
72 argument.endStructure();
73
74 return argument;
75}
76
77const QDBusArgument &operator>>(const QDBusArgument &argument, PulseaudioVolume &volume)
78{
79 argument.beginStructure();
80 argument >> volume.type_;
81 argument >> volume.volume_;
82 argument.endStructure();
83
84 return argument;
85}
86
87PulseaudioVolumeArray::PulseaudioVolumeArray()
88{
89}
90
91PulseaudioVolumeArray::PulseaudioVolumeArray(const PulseaudioVolumeArray &other) :
92 volume_array_(other.volume_array_)
93{
94}
95
96PulseaudioVolumeArray& PulseaudioVolumeArray::operator=(const PulseaudioVolumeArray &other)
97{
98 volume_array_ = other.volume_array_;
99
100 return *this;
101}
102
103PulseaudioVolumeArray::~PulseaudioVolumeArray()
104{
105}
106
107int PulseaudioVolumeArray::getNumItems() const
108{
109 return volume_array_.size();
110}
111
112PulseaudioVolume PulseaudioVolumeArray::getItem(int i) const
113{
114 if (i < volume_array_.size())
115 {
116 return volume_array_[i];
117 }
118 return PulseaudioVolume();
119}
120
121void PulseaudioVolumeArray::addItem(PulseaudioVolume const &item)
122{
123 volume_array_.push_back(item);
124}
125
126void PulseaudioVolumeArray::registerMetaType()
127{
128 qRegisterMetaType<PulseaudioVolumeArray>("PulseaudioVolumeArray");
129
130 qDBusRegisterMetaType<PulseaudioVolumeArray>();
131}
132
133QDBusArgument &operator<<(QDBusArgument &argument, const PulseaudioVolumeArray& volume)
134{
135 argument.beginArray( qMetaTypeId<PulseaudioVolume>() );
136 for (int i = 0; i < volume.volume_array_.size(); ++ i)
137 {
138 PulseaudioVolume item = volume.getItem(i);
139 argument << item;
140 }
141 argument.endArray();
142 return argument;
143}
144
145const QDBusArgument &operator>>(const QDBusArgument &argument, PulseaudioVolumeArray &volume)
146{
147 argument.beginArray();
148 while ( !argument.atEnd() ) {
149 PulseaudioVolume item;
150 argument >> item;
151 volume.volume_array_.push_back(item);
152 }
153 argument.endArray();
154
155 return argument;
156}
0157
=== added file 'tests/dbus-types/pulseaudio-volume.h'
--- tests/dbus-types/pulseaudio-volume.h 1970-01-01 00:00:00 +0000
+++ tests/dbus-types/pulseaudio-volume.h 2015-10-28 09:41:22 +0000
@@ -0,0 +1,69 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
17 */
18#pragma once
19
20#include <QtDBus>
21
22class PulseaudioVolume
23{
24public:
25 PulseaudioVolume();
26 PulseaudioVolume(unsigned int type, unsigned int volume);
27 PulseaudioVolume(const PulseaudioVolume &other);
28 PulseaudioVolume& operator=(const PulseaudioVolume &other);
29 ~PulseaudioVolume();
30
31 friend QDBusArgument &operator<<(QDBusArgument &argument, PulseaudioVolume const & volume);
32 friend const QDBusArgument &operator>>(QDBusArgument const & argument, PulseaudioVolume &volume);
33
34 unsigned int getType() const;
35 unsigned int getVolume() const;
36
37 //register Message with the Qt type system
38 static void registerMetaType();
39
40private:
41 unsigned int type_;
42 unsigned int volume_;
43};
44
45class PulseaudioVolumeArray
46{
47public:
48 PulseaudioVolumeArray();
49 PulseaudioVolumeArray(QString const &interface, QString const &property, QDBusVariant const& value);
50 PulseaudioVolumeArray(const PulseaudioVolumeArray &other);
51 PulseaudioVolumeArray& operator=(const PulseaudioVolumeArray &other);
52 ~PulseaudioVolumeArray();
53
54 friend QDBusArgument &operator<<(QDBusArgument &argument, PulseaudioVolumeArray const & volume);
55 friend const QDBusArgument &operator>>(QDBusArgument const & argument, PulseaudioVolumeArray &volume);
56
57 int getNumItems() const;
58 PulseaudioVolume getItem(int i) const;
59 void addItem(PulseaudioVolume const &item);
60
61 //register Message with the Qt type system
62 static void registerMetaType();
63
64private:
65 QVector<PulseaudioVolume> volume_array_;
66};
67
68Q_DECLARE_METATYPE(PulseaudioVolume)
69Q_DECLARE_METATYPE(PulseaudioVolumeArray)
070
=== added directory 'tests/integration'
=== added file 'tests/integration/CMakeLists.txt'
--- tests/integration/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/integration/CMakeLists.txt 2015-10-28 09:41:22 +0000
@@ -0,0 +1,132 @@
1set(CMAKE_AUTOMOC ON)
2set(CMAKE_INCLUDE_CURRENT_DIR ON)
3
4include(FindGMock)
5
6#pkg_check_modules(GMENUHARNESS REQUIRED libgmenuharness REQUIRED)
7#include_directories(${GMENUHARNESS_INCLUDE_DIRS})
8include_directories("${CMAKE_SOURCE_DIR}/include")
9
10pkg_check_modules(QTDBUSTEST REQUIRED libqtdbustest-1 REQUIRED)
11include_directories(${QTDBUSTEST_INCLUDE_DIRS})
12
13pkg_check_modules(QTDBUSMOCK REQUIRED libqtdbusmock-1 REQUIRED)
14include_directories(${QTDBUSMOCK_INCLUDE_DIRS})
15
16find_package(Qt5Test REQUIRED)
17include_directories(${Qt5Test_INCLUDE_DIRS})
18
19find_package(Qt5DBus REQUIRED)
20include_directories(${Qt5DBus_INCLUDE_DIRS})
21
22include_directories(${CMAKE_CURRENT_SOURCE_DIR})
23include_directories(${GMOCK_INCLUDE_DIRS})
24include_directories(${GTEST_INCLUDE_DIRS})
25
26include_directories("${CMAKE_SOURCE_DIR}/tests/dbus-types")
27include_directories("${CMAKE_BINARY_DIR}/tests/dbus-types")
28
29add_definitions(-DSOUND_SERVICE_BIN="${CMAKE_BINARY_DIR}/src/indicator-sound-service"
30 -DSTREAM_RESTORE_TABLE="${CMAKE_SOURCE_DIR}/tests/integration/touch-stream-restore.table"
31 -DVOLUME_SET_BIN="${CMAKE_BINARY_DIR}/tests/integration/set-volume"
32 -DACCOUNTS_SERVICE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/accounts-mock/accounts-service-sound"
33 -DMEDIA_PLAYER_MPRIS_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock"
34 -DMEDIA_PLAYER_MPRIS_UPDATE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock-update"
35 -DTEST_SOUND="${CMAKE_SOURCE_DIR}/tests/integration/test-sound.wav"
36 -DQT_NO_KEYWORDS=1
37 -DXDG_DATA_DIRS="${XDG_DATA_DIRS}"
38)
39
40set(GLIB_REQUIRED_VERSION 2.26)
41
42pkg_check_modules(
43 GLIB REQUIRED
44 glib-2.0>=${GLIB_REQUIRED_VERSION}
45 gio-2.0>=${GLIB_REQUIRED_VERSION}
46)
47include_directories(${GLIB_INCLUDE_DIRS})
48
49set(
50 INTEGRATION_TESTS_SRC
51 indicator-sound-test-base.cpp
52 test-indicator.cpp
53 utils/dbus-pulse-volume.cpp
54 main.cpp
55)
56
57add_executable(
58 integration-tests
59 ${INTEGRATION_TESTS_SRC}
60)
61
62qt5_use_modules(
63 integration-tests
64 Core
65 DBus
66 Test
67)
68
69target_link_libraries(
70 integration-tests
71 sound-indicator-dbus-interfaces
72 ${QTDBUSMOCK_LDFLAGS}
73 ${QTDBUSTEST_LDFLAGS}
74 ${GTEST_LIBRARIES}
75 ${GMOCK_LIBRARIES}
76# ${GMENUHARNESS_LDFLAGS}
77 ${GLIB_LDFLAGS}
78 gmenuharness-shared
79)
80
81add_test(
82 integration-tests
83 integration-tests
84)
85
86set(
87 SET-VOLUME-SRC
88 utils/dbus-pulse-volume.cpp
89 utils/set-volume.cpp
90)
91
92set(
93 GET-VOLUME-SRC
94 utils/dbus-pulse-volume.cpp
95 utils/get-volume.cpp
96)
97
98add_executable(
99 set-volume
100 ${SET-VOLUME-SRC}
101)
102
103add_executable(
104 get-volume
105 ${GET-VOLUME-SRC}
106)
107
108qt5_use_modules(
109 set-volume
110 Core
111 DBus
112 Test
113)
114
115qt5_use_modules(
116 get-volume
117 Core
118 DBus
119 Test
120)
121
122target_link_libraries(
123 get-volume
124 sound-indicator-dbus-interfaces
125)
126
127target_link_libraries(
128 set-volume
129 sound-indicator-dbus-interfaces
130)
131
132#add_subdirectory(utils)
0\ No newline at end of file133\ No newline at end of file
1134
=== added file 'tests/integration/Copy of test-sound.wav'
2Binary 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 differ135Binary 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
=== added file 'tests/integration/indicator-sound-test-base.cpp'
--- tests/integration/indicator-sound-test-base.cpp 1970-01-01 00:00:00 +0000
+++ tests/integration/indicator-sound-test-base.cpp 2015-10-28 09:41:22 +0000
@@ -0,0 +1,807 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
17 */
18
19#include "indicator-sound-test-base.h"
20
21#include "dbus_menus_interface.h"
22#include "dbus_properties_interface.h"
23#include "dbus_accounts_interface.h"
24#include "dbus_accountssound_interface.h"
25#include "dbus_notifications_interface.h"
26#include "dbus-types.h"
27
28#include <gio/gio.h>
29#include <chrono>
30#include <thread>
31
32#include <QSignalSpy>
33#include "utils/dbus-pulse-volume.h"
34
35using namespace QtDBusTest;
36using namespace QtDBusMock;
37using namespace std;
38using namespace testing;
39namespace mh = unity::gmenuharness;
40
41namespace
42{
43 const int MAX_TIME_WAITING_FOR_NOTIFICATIONS = 2000;
44}
45
46IndicatorSoundTestBase::IndicatorSoundTestBase() :
47 dbusMock(dbusTestRunner)
48{
49}
50
51IndicatorSoundTestBase::~IndicatorSoundTestBase()
52{
53}
54
55void IndicatorSoundTestBase::SetUp()
56{
57 setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, true);
58 setenv("DBUS_SYSTEM_BUS_ADDRESS", dbusTestRunner.systemBus().toStdString().c_str(), true);
59 setenv("DBUS_SESSION_BUS_ADDRESS", dbusTestRunner.sessionBus().toStdString().c_str(), true);
60 dbusMock.registerNotificationDaemon();
61
62 dbusTestRunner.startServices();
63
64 auto& notifications = notificationsMockInterface();
65 notifications.AddMethod("org.freedesktop.Notifications",
66 "GetCapabilities",
67 "",
68 "as",
69 "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']"
70 ).waitForFinished();
71
72 int waitedTime = 0;
73 while (!dbusTestRunner.sessionConnection().interface()->isServiceRegistered("org.freedesktop.Notifications") && waitedTime < MAX_TIME_WAITING_FOR_NOTIFICATIONS)
74 {
75 std::this_thread::sleep_for(std::chrono::milliseconds(10));
76 waitedTime += 10;
77 }
78}
79
80void IndicatorSoundTestBase::TearDown()
81{
82 unsetenv("XDG_DATA_DIRS");
83 unsetenv("PULSE_SERVER");
84 unsetenv("DBUS_SYSTEM_BUS_ADDRESS");
85}
86
87void gvariant_deleter(GVariant* varptr)
88{
89 if (varptr != nullptr)
90 {
91 g_variant_unref(varptr);
92 }
93}
94
95std::shared_ptr<GVariant> IndicatorSoundTestBase::volume_variant(double volume)
96{
97 GVariantBuilder builder;
98
99 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
100 g_variant_builder_add(&builder,
101 "{sv}",
102 "title",
103 g_variant_new_string("_Sound"));
104
105 g_variant_builder_add(&builder,
106 "{sv}",
107 "accessible-desc",
108 g_variant_new_string("_Sound"));
109
110 auto icon = g_themed_icon_new("icon");
111 g_variant_builder_add(&builder,
112 "{sv}",
113 "icon",
114 g_icon_serialize(icon));
115
116 g_variant_builder_add(&builder,
117 "{sv}",
118 "visible",
119 g_variant_new_boolean(true));
120 return shared_ptr<GVariant>(g_variant_builder_end(&builder), &gvariant_deleter);
121}
122
123bool IndicatorSoundTestBase::setStreamRestoreVolume(QString const &role, double volume)
124{
125 QProcess setVolume;
126 setVolume.start(VOLUME_SET_BIN, QStringList()
127 << role
128 << QString("%1").arg(volume));
129 if (!setVolume.waitForStarted())
130 return false;
131
132 if (!setVolume.waitForFinished())
133 return false;
134
135 return setVolume.exitCode() == 0;
136}
137
138bool IndicatorSoundTestBase::setSinkVolume(double volume)
139{
140 QString volume_percentage = QString("%1\%").arg(volume*100);
141 QProcess setVolume;
142 setVolume.start("pactl", QStringList()
143 << "-s"
144 << "127.0.0.1"
145 << "set-sink-volume"
146 << "0"
147 << volume_percentage);
148 if (!setVolume.waitForStarted())
149 return false;
150
151 if (!setVolume.waitForFinished())
152 return false;
153
154 return setVolume.exitCode() == 0;
155}
156
157bool IndicatorSoundTestBase::clearGSettingsPlayers()
158{
159 QProcess clearPlayers;
160
161 clearPlayers.start("gsettings", QStringList()
162 << "set"
163 << "com.canonical.indicator.sound"
164 << "interested-media-players"
165 << "[]");
166 if (!clearPlayers.waitForStarted())
167 return false;
168
169 if (!clearPlayers.waitForFinished())
170 return false;
171
172 return clearPlayers.exitCode() == 0;
173}
174
175bool IndicatorSoundTestBase::startTestMprisPlayer(QString const& playerName)
176{
177 testPlayer1.terminate();
178 testPlayer1.start(MEDIA_PLAYER_MPRIS_BIN, QStringList()
179 << playerName);
180 if (!testPlayer1.waitForStarted())
181 return false;
182
183
184 return true;
185}
186
187bool IndicatorSoundTestBase::setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value)
188{
189 QProcess setProperty;
190 QString strValue;
191 strValue = value ? "true" : "false";
192
193 setProperty.start(MEDIA_PLAYER_MPRIS_UPDATE_BIN, QStringList()
194 << testPlayer
195 << property
196 << strValue);
197 if (!setProperty.waitForStarted())
198 return false;
199
200 if (!setProperty.waitForFinished())
201 return false;
202
203 return setProperty.exitCode() == 0;
204}
205
206bool IndicatorSoundTestBase::startTestSound(QString const &role)
207{
208 testSoundProcess.terminate();
209 testSoundProcess.start("paplay", QStringList()
210 << "-s"
211 << "127.0.0.1"
212 << TEST_SOUND
213 << QString("--property=media.role=%1").arg(role));
214
215 if (!testSoundProcess.waitForStarted())
216 return false;
217
218 return true;
219}
220
221void IndicatorSoundTestBase::stopTestSound()
222{
223 testSoundProcess.terminate();
224}
225
226void IndicatorSoundTestBase::startPulseDesktop(DevicePortType speakerPort, DevicePortType headphonesPort)
227{
228 try
229 {
230 pulseaudio.reset(
231 new QProcessDBusService(DBusTypes::DBUS_PULSE,
232 QDBusConnection::SessionBus,
233 "pulseaudio",
234 QStringList() << "--start"
235 << "-vvvv"
236 << "--disable-shm=true"
237 << "--daemonize=false"
238 << "--use-pid-file=false"
239 << "--system=false"
240 << "--exit-idle-time=-1"
241 << "-n"
242 << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort))
243 << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort))
244 << "--log-target=file:/tmp/pulse-daemon.log"
245 << "--load=module-dbus-protocol"
246 << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
247 ));
248 pulseaudio->start(dbusTestRunner.sessionConnection());
249 }
250 catch (exception const& e)
251 {
252 cout << "pulseaudio(): " << e.what() << endl;
253 throw;
254 }
255}
256
257void IndicatorSoundTestBase::startPulsePhone(DevicePortType speakerPort, DevicePortType headphonesPort)
258{
259 try
260 {
261 pulseaudio.reset(
262 new QProcessDBusService(DBusTypes::DBUS_PULSE,
263 QDBusConnection::SessionBus,
264 "pulseaudio",
265 QStringList() << "--start"
266 << "-vvvv"
267 << "--disable-shm=true"
268 << "--daemonize=false"
269 << "--use-pid-file=false"
270 << "--system=false"
271 << "--exit-idle-time=-1"
272 << "-n"
273 << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort))
274 << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort))
275 << "--log-target=file:/tmp/pulse-daemon.log"
276 << QString("--load=module-stream-restore restore_device=false restore_muted=false fallback_table=%1").arg(STREAM_RESTORE_TABLE)
277 << "--load=module-dbus-protocol"
278 << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
279 ));
280 pulseaudio->start(dbusTestRunner.sessionConnection());
281 }
282 catch (exception const& e)
283 {
284 cout << "pulseaudio(): " << e.what() << endl;
285 throw;
286 }
287}
288
289void IndicatorSoundTestBase::startAccountsService()
290{
291 try
292 {
293 accountsService.reset(
294 new QProcessDBusService(DBusTypes::ACCOUNTS_SERVICE,
295 QDBusConnection::SystemBus,
296 ACCOUNTS_SERVICE_BIN,
297 QStringList()));
298 accountsService->start(dbusTestRunner.systemConnection());
299
300 initializeAccountsInterface();
301 }
302 catch (exception const& e)
303 {
304 cout << "accountsService(): " << e.what() << endl;
305 throw;
306 }
307}
308
309void IndicatorSoundTestBase::startIndicator()
310{
311 try
312 {
313 setenv("PULSE_SERVER", "127.0.0.1", true);
314 indicator.reset(
315 new QProcessDBusService(DBusTypes::DBUS_NAME,
316 QDBusConnection::SessionBus,
317 SOUND_SERVICE_BIN,
318 QStringList()));
319 indicator->start(dbusTestRunner.sessionConnection());
320 }
321 catch (exception const& e)
322 {
323 cout << "startIndicator(): " << e.what() << endl;
324 throw;
325 }
326}
327
328mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters()
329{
330 return mh::MenuMatcher::Parameters(
331 "com.canonical.indicator.sound",
332 { { "indicator", "/com/canonical/indicator/sound" } },
333 "/com/canonical/indicator/sound/desktop");
334}
335
336mh::MenuMatcher::Parameters IndicatorSoundTestBase::phoneParameters()
337{
338 return mh::MenuMatcher::Parameters(
339 "com.canonical.indicator.sound",
340 { { "indicator", "/com/canonical/indicator/sound" } },
341 "/com/canonical/indicator/sound/phone");
342}
343
344unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double volume, QString const &label)
345{
346 return mh::MenuItemMatcher().radio()
347 .label(label.toStdString())
348 .round_doubles(0.1)
349 .int32_attribute("target", 0)
350 .double_attribute("min-value", 0.0)
351 .double_attribute("max-value", 1.0)
352 .double_attribute("step", 0.01)
353 .string_attribute("x-canonical-type", "com.canonical.unity.slider")
354 .themed_icon("max-icon", {"audio-volume-high-panel", "audio-volume-high", "audio-volume", "audio"})
355 .themed_icon("min-icon", {"audio-volume-low-zero-panel", "audio-volume-low-zero", "audio-volume-low", "audio-volume", "audio"})
356 .pass_through_double_attribute("action", volume);
357}
358
359unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::silentModeSwitch(bool toggled)
360{
361 return mh::MenuItemMatcher::checkbox()
362 .label("Silent Mode")
363 .action("indicator.silent-mode")
364 .toggled(toggled);
365}
366
367bool IndicatorSoundTestBase::waitMenuChange()
368{
369 if (signal_spy_menu_changed_)
370 {
371 return signal_spy_menu_changed_->wait();
372 }
373 return false;
374}
375
376bool IndicatorSoundTestBase::initializeMenuChangedSignal()
377{
378 if (!menu_interface_)
379 {
380 menu_interface_.reset(new MenusInterface("com.canonical.indicator.sound",
381 "/com/canonical/indicator/sound",
382 dbusTestRunner.sessionConnection(), 0));
383 }
384 if (menu_interface_)
385 {
386 qDebug() << "Waiting for signal";
387 signal_spy_menu_changed_.reset(new QSignalSpy(menu_interface_.get(), &MenusInterface::Changed));
388 }
389 if (!menu_interface_ || !signal_spy_menu_changed_)
390 {
391 return false;
392 }
393 return true;
394}
395
396bool IndicatorSoundTestBase::waitVolumeChangedInIndicator()
397{
398 if (signal_spy_volume_changed_)
399 {
400 return signal_spy_volume_changed_->wait();
401 }
402 return false;
403}
404
405void IndicatorSoundTestBase::initializeAccountsInterface()
406{
407 auto username = qgetenv("USER");
408 if (username != "")
409 {
410 std::unique_ptr<AccountsInterface> accountsInterface(new AccountsInterface("org.freedesktop.Accounts",
411 "/org/freedesktop/Accounts",
412 dbusTestRunner.systemConnection(), 0));
413
414 QDBusReply<QDBusObjectPath> userResp = accountsInterface->call(QLatin1String("FindUserByName"),
415 QLatin1String(username));
416
417 if (!userResp.isValid())
418 {
419 qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << userResp.error().message();
420 }
421
422 auto userPath = userResp.value().path();
423 if (userPath != "")
424 {
425 std::unique_ptr<AccountsSoundInterface> soundInterface(new AccountsSoundInterface("org.freedesktop.Accounts",
426 userPath,
427 dbusTestRunner.systemConnection(), 0));
428
429 accounts_interface_.reset(new DBusPropertiesInterface("org.freedesktop.Accounts",
430 userPath,
431 dbusTestRunner.systemConnection(), 0));
432 if (!accounts_interface_->isValid())
433 {
434 qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << accounts_interface_->lastError().message();
435 }
436 signal_spy_volume_changed_.reset(new QSignalSpy(accounts_interface_.get(),&DBusPropertiesInterface::PropertiesChanged));
437 }
438 }
439}
440
441OrgFreedesktopDBusMockInterface& IndicatorSoundTestBase::notificationsMockInterface()
442{
443 return dbusMock.mockInterface("org.freedesktop.Notifications",
444 "/org/freedesktop/Notifications",
445 "org.freedesktop.Notifications",
446 QDBusConnection::SessionBus);
447}
448
449bool IndicatorSoundTestBase::setActionValue(const QString & action, QVariant value)
450{
451 QDBusInterface actionsInterface(DBusTypes::DBUS_NAME,
452 DBusTypes::MAIN_SERVICE_PATH,
453 DBusTypes::ACTIONS_INTERFACE,
454 dbusTestRunner.sessionConnection());
455
456 QDBusVariant dbusVar(value);
457 auto resp = actionsInterface.call("SetState",
458 action,
459 QVariant::fromValue(dbusVar),
460 QVariant::fromValue(QVariantMap()));
461
462 if (resp.type() == QDBusMessage::ErrorMessage)
463 {
464 qCritical() << "IndicatorSoundTestBase::setActionValue(): Failed to set value for action "
465 << action
466 << " "
467 << resp.errorMessage();
468 return false;
469 }
470 else
471 {
472 return true;
473 }
474}
475
476bool IndicatorSoundTestBase::pressNotificationButton(int id, const QString & button)
477{
478 OrgFreedesktopDBusMockInterface actionsInterface("org.freedesktop.Notifications",
479 "/org/freedesktop/Notifications",
480 dbusTestRunner.sessionConnection());
481
482 actionsInterface.EmitSignal(
483 "org.freedesktop.Notifications",
484 "ActionInvoked", "us", QVariantList() << id << button);
485
486 return true;
487}
488
489bool IndicatorSoundTestBase::qDBusArgumentToMap(QVariant const& variant, QVariantMap& map)
490{
491 if (variant.canConvert<QDBusArgument>())
492 {
493 QDBusArgument value(variant.value<QDBusArgument>());
494 if (value.currentType() == QDBusArgument::MapType)
495 {
496 value >> map;
497 return true;
498 }
499 }
500 return false;
501}
502
503void IndicatorSoundTestBase::checkVolumeNotification(double volume, QString const& label, bool isLoud, QVariantList call)
504{
505 QString icon;
506 if (volume <= 0.0)
507 {
508 icon = "audio-volume-muted";
509 }
510 else if (volume <= 0.3)
511 {
512 icon = "audio-volume-low";
513 }
514 else if (volume <= 0.7)
515 {
516 icon = "audio-volume-medium";
517 }
518 else
519 {
520 icon = "audio-volume-high";
521 }
522
523 ASSERT_NE(call.size(), 0);
524 EXPECT_EQ("Notify", call.at(0));
525
526 QVariantList const& args(call.at(1).toList());
527 ASSERT_EQ(8, args.size());
528 EXPECT_EQ("indicator-sound", args.at(0));
529 EXPECT_EQ(icon, args.at(2));
530 EXPECT_EQ("Volume", args.at(3));
531 EXPECT_EQ(label, args.at(4));
532 EXPECT_EQ(QStringList(), args.at(5));
533
534 QVariantMap hints;
535 ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints));
536 ASSERT_TRUE(hints.contains("value"));
537 ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon"));
538 ASSERT_TRUE(hints.contains("x-canonical-value-bar-tint"));
539 ASSERT_TRUE(hints.contains("x-canonical-private-synchronous"));
540
541 EXPECT_EQ(volume*100, hints["value"]);
542 EXPECT_EQ(true, hints["x-canonical-non-shaped-icon"]);
543 EXPECT_EQ(isLoud, hints["x-canonical-value-bar-tint"]);
544 EXPECT_EQ(true, hints["x-canonical-private-synchronous"]);
545}
546
547void IndicatorSoundTestBase::checkHighVolumeNotification(QVariantList call)
548{
549 ASSERT_NE(call.size(), 0);
550 EXPECT_EQ("Notify", call.at(0));
551
552 QVariantList const& args(call.at(1).toList());
553 ASSERT_EQ(8, args.size());
554 EXPECT_EQ("indicator-sound", args.at(0));
555 EXPECT_EQ("Volume", args.at(3));
556}
557
558void IndicatorSoundTestBase::checkCloseNotification(int id, QVariantList call)
559{
560 EXPECT_EQ("CloseNotification", call.at(0));
561 QVariantList const& args(call.at(1).toList());
562 ASSERT_EQ(1, args.size());
563}
564
565void IndicatorSoundTestBase::checkNotificationWithNoArgs(QString const& method, QVariantList call)
566{
567 EXPECT_EQ(method, call.at(0));
568 QVariantList const& args(call.at(1).toList());
569 ASSERT_EQ(0, args.size());
570}
571
572int IndicatorSoundTestBase::getNotificationID(QVariantList call)
573{
574 if (call.size() == 0)
575 {
576 return -1;
577 }
578 QVariantList const& args(call.at(1).toList());
579 if (args.size() != 8)
580 {
581 return -1;
582 }
583 if (args.at(0) != "indicator-sound")
584 {
585 return -1;
586 }
587
588 bool isInt;
589 int id = args.at(1).toInt(&isInt);
590 if (!isInt)
591 {
592 return -1;
593 }
594 return id;
595}
596
597bool IndicatorSoundTestBase::activateHeadphones(bool headphonesActive)
598{
599 QProcess pacltProcess;
600
601 QString defaultSinkName = "indicator_sound_test_speaker";
602 QString suspendedSinkName = "indicator_sound_test_headphones";
603 if (headphonesActive)
604 {
605 defaultSinkName = "indicator_sound_test_headphones";
606 suspendedSinkName = "indicator_sound_test_speaker";
607 }
608
609 pacltProcess.start("pactl", QStringList() << "-s"
610 << "127.0.0.1"
611 << "set-default-sink"
612 << defaultSinkName);
613 if (!pacltProcess.waitForStarted())
614 return false;
615
616 if (!pacltProcess.waitForFinished())
617 return false;
618
619 pacltProcess.start("pactl", QStringList() << "-s"
620 << "127.0.0.1"
621 << "suspend-sink"
622 << defaultSinkName
623 << "0");
624 if (!pacltProcess.waitForStarted())
625 return false;
626
627 if (!pacltProcess.waitForFinished())
628 return false;
629
630 pacltProcess.start("pactl", QStringList() << "-s"
631 << "127.0.0.1"
632 << "suspend-sink"
633 << suspendedSinkName
634 << "1");
635 if (!pacltProcess.waitForStarted())
636 return false;
637
638 if (!pacltProcess.waitForFinished())
639 return false;
640
641 return pacltProcess.exitCode() == 0;
642}
643
644QString IndicatorSoundTestBase::getDevicePortString(DevicePortType port)
645{
646 QString portString;
647
648 switch (port)
649 {
650 case WIRED:
651 portString = "wired";
652 break;
653 case BLUETOOTH:
654 portString = "bluetooth";
655 break;
656 case USB:
657 portString = "usb";
658 break;
659 case HDMI:
660 portString = "hdmi";
661 break;
662 default:
663 portString = "not_defined";
664 break;
665 }
666
667 return portString;
668}
669
670void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, DevicePortType headphonesPort)
671{
672 double INITIAL_VOLUME = 0.0;
673
674 QString speakerString;
675 QString speakerStringMenu;
676 switch(speakerPort)
677 {
678 case WIRED:
679 speakerString = "Speakers";
680 speakerStringMenu = "Volume";
681 break;
682 case BLUETOOTH:
683 speakerString = "Bluetooth speaker";
684 speakerStringMenu = "Volume (Bluetooth)";
685 break;
686 case USB:
687 speakerString = "Usb speaker";
688 speakerStringMenu = "Volume (Usb)";
689 break;
690 case HDMI:
691 speakerString = "HDMI speaker";
692 speakerStringMenu = "Volume (HDMI)";
693 break;
694 }
695
696 QString headphonesString;
697 QString headphonesStringMenu;
698 switch(headphonesPort)
699 {
700 case WIRED:
701 headphonesString = "Headphones";
702 headphonesStringMenu = "Volume (Headphones)";
703 break;
704 case BLUETOOTH:
705 headphonesString = "Bluetooth headphones";
706 headphonesStringMenu = "Volume (Bluetooth headphones)";
707 break;
708 case USB:
709 headphonesString = "Usb headphones";
710 headphonesStringMenu = "Volume (Usb headphones)";
711 break;
712 case HDMI:
713 headphonesString = "HDMI headphones";
714 headphonesStringMenu = "Volume (HDMI headphones)";
715 break;
716 }
717
718 QSignalSpy notificationsSpy(&notificationsMockInterface(),
719 SIGNAL(MethodCalled(const QString &, const QVariantList &)));
720
721 ASSERT_NO_THROW(startAccountsService());
722 ASSERT_NO_THROW(startPulsePhone(speakerPort, headphonesPort));
723
724 // initialize volumes in pulseaudio
725 EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
726 EXPECT_TRUE(setStreamRestoreVolume("multimedia", INITIAL_VOLUME));
727
728 // start now the indicator, so it picks the new volumes
729 ASSERT_NO_THROW(startIndicator());
730
731 // if the speaker is the normal one it does not emit any notification, as that's
732 // the default one.
733 // for the rest it notifies the output
734 if (speakerPort != WIRED)
735 {
736 WAIT_FOR_SIGNALS(notificationsSpy, 3);
737
738 // the first time we also have the calls to
739 // GetServerInformation and GetCapabilities
740 checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
741 checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
742 checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(2));
743 notificationsSpy.clear();
744 }
745
746 // activate the headphones
747 EXPECT_TRUE(activateHeadphones(true));
748
749 if (speakerPort == WIRED)
750 {
751 WAIT_FOR_SIGNALS(notificationsSpy, 3);
752
753 // the first time we also have the calls to
754 // GetServerInformation and GetCapabilities
755 checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
756 checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
757 checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(2));
758 notificationsSpy.clear();
759 }
760 else
761 {
762 WAIT_FOR_SIGNALS(notificationsSpy, 1);
763 checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(0));
764 notificationsSpy.clear();
765 }
766
767 // check the label in the menu
768 EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
769 .item(mh::MenuItemMatcher()
770 .action("indicator.root")
771 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
772 .string_attribute("x-canonical-scroll-action", "indicator.scroll")
773 .string_attribute("x-canonical-secondary-action", "indicator.mute")
774 .string_attribute("submenu-action", "indicator.indicator-shown")
775 .mode(mh::MenuItemMatcher::Mode::starts_with)
776 .submenu()
777 .item(mh::MenuItemMatcher()
778 .section()
779 .item(silentModeSwitch(false))
780 .item(volumeSlider(INITIAL_VOLUME, headphonesStringMenu))
781 )
782 ).match());
783
784 // deactivate the headphones
785 EXPECT_TRUE(activateHeadphones(false));
786
787 WAIT_FOR_SIGNALS(notificationsSpy, 1);
788 checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(0));
789 notificationsSpy.clear();
790
791 // check the label in the menu
792 EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
793 .item(mh::MenuItemMatcher()
794 .action("indicator.root")
795 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
796 .string_attribute("x-canonical-scroll-action", "indicator.scroll")
797 .string_attribute("x-canonical-secondary-action", "indicator.mute")
798 .string_attribute("submenu-action", "indicator.indicator-shown")
799 .mode(mh::MenuItemMatcher::Mode::starts_with)
800 .submenu()
801 .item(mh::MenuItemMatcher()
802 .section()
803 .item(silentModeSwitch(false))
804 .item(volumeSlider(INITIAL_VOLUME, speakerStringMenu))
805 )
806 ).match());
807}
0808
=== added file 'tests/integration/indicator-sound-test-base.h'
--- tests/integration/indicator-sound-test-base.h 1970-01-01 00:00:00 +0000
+++ tests/integration/indicator-sound-test-base.h 2015-10-28 09:41:22 +0000
@@ -0,0 +1,146 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
17 */
18
19#pragma once
20
21#include <libqtdbustest/DBusTestRunner.h>
22#include <libqtdbustest/QProcessDBusService.h>
23#include <libqtdbusmock/DBusMock.h>
24
25#include <unity/gmenuharness/MatchUtils.h>
26#include <unity/gmenuharness/MenuMatcher.h>
27
28#include <gmock/gmock.h>
29#include <gtest/gtest.h>
30
31class MenusInterface;
32class DBusPulseVolume;
33class DBusPropertiesInterface;
34class QSignalSpy;
35
36#define WAIT_FOR_SIGNALS(signalSpy, signalsExpected)\
37{\
38 while (signalSpy.size() < signalsExpected)\
39 {\
40 ASSERT_TRUE(signalSpy.wait());\
41 }\
42 ASSERT_EQ(signalsExpected, signalSpy.size());\
43}
44
45class IndicatorSoundTestBase: public testing::Test
46{
47public:
48 IndicatorSoundTestBase();
49
50 ~IndicatorSoundTestBase();
51
52 enum DevicePortType
53 {
54 WIRED,
55 BLUETOOTH,
56 USB,
57 HDMI
58 };
59
60protected:
61 void SetUp() override;
62 void TearDown() override;
63
64 void startIndicator();
65 void startPulseDesktop(DevicePortType speakerPort=WIRED, DevicePortType headphonesPort=WIRED);
66 void startPulsePhone(DevicePortType speakerPort=WIRED, DevicePortType headphonesPort=WIRED);
67 void startAccountsService();
68
69 bool clearGSettingsPlayers();
70
71 bool startTestMprisPlayer(QString const& playerName);
72
73 bool setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value);
74
75 bool setStreamRestoreVolume(QString const &role, double volume);
76
77 bool setSinkVolume(double volume);
78
79 bool startTestSound(QString const &role);
80
81 void stopTestSound();
82
83 static std::shared_ptr<GVariant> volume_variant(double volume);
84
85 static unity::gmenuharness::MenuMatcher::Parameters desktopParameters();
86
87 static unity::gmenuharness::MenuMatcher::Parameters phoneParameters();
88
89 static unity::gmenuharness::MenuItemMatcher volumeSlider(double volume, QString const &label);
90
91 static unity::gmenuharness::MenuItemMatcher silentModeSwitch(bool toggled);
92
93 bool waitMenuChange();
94
95 bool initializeMenuChangedSignal();
96
97 bool waitVolumeChangedInIndicator();
98
99 void initializeAccountsInterface();
100
101 OrgFreedesktopDBusMockInterface& notificationsMockInterface();
102
103 bool setActionValue(const QString & action, QVariant value);
104
105 bool pressNotificationButton(int id, const QString & button);
106
107 bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map);
108
109 void checkVolumeNotification(double volume, QString const& label, bool isLoud, QVariantList call);
110
111 void checkHighVolumeNotification(QVariantList call);
112
113 void checkCloseNotification(int id, QVariantList call);
114
115 void checkNotificationWithNoArgs(QString const& method, QVariantList call);
116
117 int getNotificationID(QVariantList call);
118
119 bool activateHeadphones(bool headphonesActive);
120
121 QString getDevicePortString(DevicePortType port);
122
123 void checkPortDevicesLabels(DevicePortType speakerPort, DevicePortType headphonesPort);
124
125 QtDBusTest::DBusTestRunner dbusTestRunner;
126
127 QtDBusMock::DBusMock dbusMock;
128
129 QtDBusTest::DBusServicePtr indicator;
130
131 QtDBusTest::DBusServicePtr pulseaudio;
132
133 QtDBusTest::DBusServicePtr accountsService;
134
135 QProcess testSoundProcess;
136
137 QProcess testPlayer1;
138
139 std::unique_ptr<MenusInterface> menu_interface_;
140
141 std::unique_ptr<DBusPropertiesInterface> accounts_interface_;
142
143 std::unique_ptr<QSignalSpy> signal_spy_volume_changed_;
144
145 std::unique_ptr<QSignalSpy> signal_spy_menu_changed_;
146};
0147
=== added file 'tests/integration/main.cpp'
--- tests/integration/main.cpp 1970-01-01 00:00:00 +0000
+++ tests/integration/main.cpp 2015-10-28 09:41:22 +0000
@@ -0,0 +1,58 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors:
17 * Pete Woods <pete.woods@canonical.com>
18 */
19
20//#include <config.h>
21
22#include <QCoreApplication>
23#include <QTimer>
24#include <gtest/gtest.h>
25
26#include <libqtdbusmock/DBusMock.h>
27
28#include "dbus-types.h"
29
30using namespace QtDBusMock;
31
32class Runner: public QObject
33{
34 Q_OBJECT
35public Q_SLOTS:
36 void run()
37 {
38 QCoreApplication::exit(RUN_ALL_TESTS());
39 }
40};
41
42int main(int argc, char **argv)
43{
44 qputenv("LANG", "C.UTF-8");
45 unsetenv("LC_ALL");
46
47 QCoreApplication application(argc, argv);
48 DBusMock::registerMetaTypes();
49 DBusTypes::registerMetaTypes();
50 ::testing::InitGoogleTest(&argc, argv);
51
52 Runner runner;
53 QTimer::singleShot(0, &runner, SLOT(run()));
54
55 return application.exec();
56}
57
58#include "main.moc"
059
=== added file 'tests/integration/test-indicator.cpp'
--- tests/integration/test-indicator.cpp 1970-01-01 00:00:00 +0000
+++ tests/integration/test-indicator.cpp 2015-10-28 09:41:22 +0000
@@ -0,0 +1,963 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3, as published
6 * by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranties of
10 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 * PURPOSE. See the GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
17 */
18
19#include <indicator-sound-test-base.h>
20
21#include <QDebug>
22#include <QTestEventLoop>
23#include <QSignalSpy>
24
25using namespace std;
26using namespace testing;
27namespace mh = unity::gmenuharness;
28namespace
29{
30
31class TestIndicator: public IndicatorSoundTestBase
32{
33};
34
35TEST_F(TestIndicator, PhoneChangeRoleVolume)
36{
37 double INITIAL_VOLUME = 0.0;
38
39 ASSERT_NO_THROW(startAccountsService());
40 ASSERT_NO_THROW(startPulsePhone());
41
42 // initialize volumes in pulseaudio
43 EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
44 EXPECT_TRUE(setStreamRestoreVolume("multimedia", INITIAL_VOLUME));
45
46 // start now the indicator, so it picks the new volumes
47 ASSERT_NO_THROW(startIndicator());
48
49 // Generate a random volume
50 QTime now = QTime::currentTime();
51 qsrand(now.msec());
52 int randInt = qrand() % 100;
53 double randomVolume = randInt / 100.0;
54
55 QSignalSpy &userAccountsSpy = *signal_spy_volume_changed_;
56 // set an initial volume to the alert role
57 userAccountsSpy.clear();
58 setStreamRestoreVolume("alert", 1.0);
59 WAIT_FOR_SIGNALS(userAccountsSpy, 2);
60
61 userAccountsSpy.clear();
62 // play a test sound, it should change the role in the indicator
63 EXPECT_TRUE(startTestSound("multimedia"));
64
65 // this time we only expect 1 signal as it's only the indicator
66 // updating the value
67 WAIT_FOR_SIGNALS(userAccountsSpy, 1);
68 //EXPECT_TRUE(waitVolumeChangedInIndicator());
69
70 userAccountsSpy.clear();
71 // set the random volume to the multimedia role
72 EXPECT_TRUE(setStreamRestoreVolume("multimedia", randomVolume));
73 if (randomVolume != INITIAL_VOLUME)
74 {
75 WAIT_FOR_SIGNALS(userAccountsSpy, 1);
76 }
77
78 // check the indicator
79 EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
80 .item(mh::MenuItemMatcher()
81 .action("indicator.root")
82 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
83 .string_attribute("x-canonical-scroll-action", "indicator.scroll")
84 .string_attribute("x-canonical-secondary-action", "indicator.mute")
85 .string_attribute("submenu-action", "indicator.indicator-shown")
86 .mode(mh::MenuItemMatcher::Mode::starts_with)
87 .submenu()
88 .item(mh::MenuItemMatcher()
89 .section()
90 .item(silentModeSwitch(false))
91 .item(volumeSlider(randomVolume, "Volume"))
92 )
93 ).match());
94
95 // check that the last item is Sound Settings
96 EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
97 .item(mh::MenuItemMatcher()
98 .action("indicator.root")
99 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
100 .string_attribute("x-canonical-secondary-action", "indicator.mute")
101 .mode(mh::MenuItemMatcher::Mode::ends_with)
102 .submenu()
103 .item(mh::MenuItemMatcher()
104 .label("Sound Settings…")
105 .action("indicator.phone-settings")
106 )
107 ).match());
108
109 userAccountsSpy.clear();
110 // stop the test sound, the role should change again to alert
111 stopTestSound();
112 if (randomVolume != 1.0)
113 {
114 // we only wait if the volume in the alert and the
115 // one set in the multimedia roles differ
116 WAIT_FOR_SIGNALS(userAccountsSpy, 1);
117 }
118
119 // check the initial volume for the alert role
120 EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
121 .item(mh::MenuItemMatcher()
122 .action("indicator.root")
123 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
124 .string_attribute("x-canonical-scroll-action", "indicator.scroll")
125 .string_attribute("x-canonical-secondary-action", "indicator.mute")
126 .string_attribute("submenu-action", "indicator.indicator-shown")
127 .mode(mh::MenuItemMatcher::Mode::starts_with)
128 .submenu()
129 .item(mh::MenuItemMatcher()
130 .section()
131 .item(silentModeSwitch(false))
132 .item(volumeSlider(1.0, "Volume"))
133 )
134 ).match());
135
136 // check that the last item is Sound Settings
137 EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
138 .item(mh::MenuItemMatcher()
139 .action("indicator.root")
140 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
141 .string_attribute("x-canonical-secondary-action", "indicator.mute")
142 .mode(mh::MenuItemMatcher::Mode::ends_with)
143 .submenu()
144 .item(mh::MenuItemMatcher()
145 .label("Sound Settings…")
146 .action("indicator.phone-settings")
147 )
148 ).match());
149}
150
151TEST_F(TestIndicator, PhoneBasicInitialVolume)
152{
153 double INITIAL_VOLUME = 0.0;
154
155 ASSERT_NO_THROW(startAccountsService());
156 EXPECT_TRUE(clearGSettingsPlayers());
157 ASSERT_NO_THROW(startPulsePhone());
158
159 // initialize volumes in pulseaudio
160 EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
161
162 // start now the indicator, so it picks the new volumes
163 ASSERT_NO_THROW(startIndicator());
164
165 EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
166 .item(mh::MenuItemMatcher()
167 .action("indicator.root")
168 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
169 .string_attribute("x-canonical-scroll-action", "indicator.scroll")
170 .string_attribute("x-canonical-secondary-action", "indicator.mute")
171 .string_attribute("submenu-action", "indicator.indicator-shown")
172 .mode(mh::MenuItemMatcher::Mode::all)
173 .submenu()
174 .item(mh::MenuItemMatcher()
175 .section()
176 .item(silentModeSwitch(false))
177 .item(volumeSlider(INITIAL_VOLUME, "Volume"))
178 )
179 .item(mh::MenuItemMatcher()
180 .label("Sound Settings…")
181 .action("indicator.phone-settings")
182 )
183 ).match());
184}
185
186TEST_F(TestIndicator, PhoneAddMprisPlayer)
187{
188 double INITIAL_VOLUME = 0.0;
189
190 ASSERT_NO_THROW(startAccountsService());
191 EXPECT_TRUE(clearGSettingsPlayers());
192 ASSERT_NO_THROW(startPulsePhone());
193
194 // initialize volumes in pulseaudio
195 EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
196
197 // start now the indicator, so it picks the new volumes
198 ASSERT_NO_THROW(startIndicator());
199
200 EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
201 .item(mh::MenuItemMatcher()
202 .action("indicator.root")
203 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
204 .string_attribute("x-canonical-scroll-action", "indicator.scroll")
205 .string_attribute("x-canonical-secondary-action", "indicator.mute")
206 .string_attribute("submenu-action", "indicator.indicator-shown")
207 .mode(mh::MenuItemMatcher::Mode::all)
208 .submenu()
209 .item(mh::MenuItemMatcher()
210 .section()
211 .item(silentModeSwitch(false))
212 .item(volumeSlider(INITIAL_VOLUME, "Volume"))
213 )
214 .item(mh::MenuItemMatcher()
215 .label("Sound Settings…")
216 .action("indicator.phone-settings")
217 )
218 ).match());
219
220 // initialize the signal spy
221 EXPECT_TRUE(initializeMenuChangedSignal());
222
223 // start the test player
224 EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
225
226 // wait for the menu change
227 EXPECT_TRUE(waitMenuChange());
228
229 // finally verify that the player is added
230 EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
231 .item(mh::MenuItemMatcher()
232 .action("indicator.root")
233 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
234 .string_attribute("x-canonical-scroll-action", "indicator.scroll")
235 .string_attribute("x-canonical-secondary-action", "indicator.mute")
236 .string_attribute("submenu-action", "indicator.indicator-shown")
237 .mode(mh::MenuItemMatcher::Mode::all)
238 .submenu()
239 .item(mh::MenuItemMatcher()
240 .section()
241 .item(silentModeSwitch(false))
242 .item(volumeSlider(INITIAL_VOLUME, "Volume"))
243 )
244 .item(mh::MenuItemMatcher()
245 .section()
246 .item(mh::MenuItemMatcher()
247 .action("indicator.testplayer1.desktop")
248 .label("TestPlayer1")
249 .themed_icon("icon", {"testplayer"})
250 .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
251 )
252 .item(mh::MenuItemMatcher()
253 .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
254 .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
255 .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
256 .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
257 )
258 )
259 .item(mh::MenuItemMatcher()
260 .label("Sound Settings…")
261 .action("indicator.phone-settings")
262 )
263 ).match());
264}
265
266TEST_F(TestIndicator, DesktopBasicInitialVolume)
267{
268 double INITIAL_VOLUME = 0.0;
269
270 ASSERT_NO_THROW(startAccountsService());
271 EXPECT_TRUE(clearGSettingsPlayers());
272 ASSERT_NO_THROW(startPulseDesktop());
273
274 // initialize volumes in pulseaudio
275 EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
276 EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
277
278 // start the test player
279 EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
280
281 // start now the indicator, so it picks the new volumes
282 ASSERT_NO_THROW(startIndicator());
283
284 EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
285 .item(mh::MenuItemMatcher()
286 .action("indicator.root")
287 .string_attribute("x-canonical-type", "com.canonical.indicator.root")
288 .string_attribute("x-canonical-secondary-action", "indicator.mute")
289 .mode(mh::MenuItemMatcher::Mode::all)
290 .submenu()
291 .item(mh::MenuItemMatcher()
292 .section()
293 .item(mh::MenuItemMatcher().checkbox()
294 .label("Mute")
295 )
296 .item(volumeSlider(INITIAL_VOLUME, "Volume"))
297 )
298 .item(mh::MenuItemMatcher()
299 .section()
300 .item(mh::MenuItemMatcher()
301 .action("indicator.testplayer1.desktop")
302 .label("TestPlayer1")
303 .themed_icon("icon", {"testplayer"})
304 .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
305 )
306 .item(mh::MenuItemMatcher()
307 .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
308 .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