aboutsummaryrefslogtreecommitdiff
path: root/tests/integration
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration')
-rw-r--r--tests/integration/CMakeLists.txt132
-rw-r--r--tests/integration/Copy of test-sound.wavbin0 -> 191989 bytes
-rw-r--r--tests/integration/indicator-sound-test-base.cpp823
-rw-r--r--tests/integration/indicator-sound-test-base.h160
-rw-r--r--tests/integration/main.cpp58
-rw-r--r--tests/integration/test-indicator.cpp981
-rw-r--r--tests/integration/test-sound.wavbin0 -> 7665640 bytes
-rwxr-xr-xtests/integration/touch-stream-restore.table4
-rw-r--r--tests/integration/utils/dbus-pulse-volume.cpp232
-rw-r--r--tests/integration/utils/dbus-pulse-volume.h57
-rw-r--r--tests/integration/utils/get-volume.cpp33
-rw-r--r--tests/integration/utils/set-volume.cpp36
12 files changed, 2516 insertions, 0 deletions
diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt
new file mode 100644
index 0000000..939e329
--- /dev/null
+++ b/tests/integration/CMakeLists.txt
@@ -0,0 +1,132 @@
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+include(FindGMock)
+
+#pkg_check_modules(GMENUHARNESS REQUIRED libgmenuharness REQUIRED)
+#include_directories(${GMENUHARNESS_INCLUDE_DIRS})
+include_directories("${CMAKE_SOURCE_DIR}/include")
+
+pkg_check_modules(QTDBUSTEST REQUIRED libqtdbustest-1 REQUIRED)
+include_directories(${QTDBUSTEST_INCLUDE_DIRS})
+
+pkg_check_modules(QTDBUSMOCK REQUIRED libqtdbusmock-1 REQUIRED)
+include_directories(${QTDBUSMOCK_INCLUDE_DIRS})
+
+find_package(Qt5Test REQUIRED)
+include_directories(${Qt5Test_INCLUDE_DIRS})
+
+find_package(Qt5DBus REQUIRED)
+include_directories(${Qt5DBus_INCLUDE_DIRS})
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+include_directories(${GMOCK_INCLUDE_DIRS})
+include_directories(${GTEST_INCLUDE_DIRS})
+
+include_directories("${CMAKE_SOURCE_DIR}/tests/dbus-types")
+include_directories("${CMAKE_BINARY_DIR}/tests/dbus-types")
+
+add_definitions(-DSOUND_SERVICE_BIN="${CMAKE_BINARY_DIR}/src/indicator-sound-service"
+ -DSTREAM_RESTORE_TABLE="${CMAKE_SOURCE_DIR}/tests/integration/touch-stream-restore.table"
+ -DVOLUME_SET_BIN="${CMAKE_BINARY_DIR}/tests/integration/set-volume"
+ -DACCOUNTS_SERVICE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/accounts-mock/accounts-service-sound"
+ -DMEDIA_PLAYER_MPRIS_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock"
+ -DMEDIA_PLAYER_MPRIS_UPDATE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock-update"
+ -DTEST_SOUND="${CMAKE_SOURCE_DIR}/tests/integration/test-sound.wav"
+ -DQT_NO_KEYWORDS=1
+ -DXDG_DATA_DIRS="${XDG_DATA_DIRS}"
+)
+
+set(GLIB_REQUIRED_VERSION 2.26)
+
+pkg_check_modules(
+ GLIB REQUIRED
+ glib-2.0>=${GLIB_REQUIRED_VERSION}
+ gio-2.0>=${GLIB_REQUIRED_VERSION}
+)
+include_directories(${GLIB_INCLUDE_DIRS})
+
+set(
+ INTEGRATION_TESTS_SRC
+ indicator-sound-test-base.cpp
+ test-indicator.cpp
+ utils/dbus-pulse-volume.cpp
+ main.cpp
+)
+
+add_executable(
+ integration-tests
+ ${INTEGRATION_TESTS_SRC}
+)
+
+qt5_use_modules(
+ integration-tests
+ Core
+ DBus
+ Test
+)
+
+target_link_libraries(
+ integration-tests
+ sound-indicator-dbus-interfaces
+ ${QTDBUSMOCK_LDFLAGS}
+ ${QTDBUSTEST_LDFLAGS}
+ ${GTEST_LIBRARIES}
+ ${GMOCK_LIBRARIES}
+# ${GMENUHARNESS_LDFLAGS}
+ ${GLIB_LDFLAGS}
+ gmenuharness-shared
+)
+
+add_test(
+ integration-tests
+ integration-tests
+)
+
+set(
+ SET-VOLUME-SRC
+ utils/dbus-pulse-volume.cpp
+ utils/set-volume.cpp
+)
+
+set(
+ GET-VOLUME-SRC
+ utils/dbus-pulse-volume.cpp
+ utils/get-volume.cpp
+)
+
+add_executable(
+ set-volume
+ ${SET-VOLUME-SRC}
+)
+
+add_executable(
+ get-volume
+ ${GET-VOLUME-SRC}
+)
+
+qt5_use_modules(
+ set-volume
+ Core
+ DBus
+ Test
+)
+
+qt5_use_modules(
+ get-volume
+ Core
+ DBus
+ Test
+)
+
+target_link_libraries(
+ get-volume
+ sound-indicator-dbus-interfaces
+)
+
+target_link_libraries(
+ set-volume
+ sound-indicator-dbus-interfaces
+)
+
+#add_subdirectory(utils) \ No newline at end of file
diff --git a/tests/integration/Copy of test-sound.wav b/tests/integration/Copy of test-sound.wav
new file mode 100644
index 0000000..709c6eb
--- /dev/null
+++ b/tests/integration/Copy of test-sound.wav
Binary files differ
diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp
new file mode 100644
index 0000000..5005e2c
--- /dev/null
+++ b/tests/integration/indicator-sound-test-base.cpp
@@ -0,0 +1,823 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
+ */
+
+#include "indicator-sound-test-base.h"
+
+#include "dbus_menus_interface.h"
+#include "dbus_properties_interface.h"
+#include "dbus_accounts_interface.h"
+#include "dbus_accountssound_interface.h"
+#include "dbus_notifications_interface.h"
+#include "dbus-types.h"
+
+#include <gio/gio.h>
+#include <chrono>
+#include <thread>
+
+#include <QSignalSpy>
+#include "utils/dbus-pulse-volume.h"
+
+using namespace QtDBusTest;
+using namespace QtDBusMock;
+using namespace std;
+using namespace testing;
+namespace mh = unity::gmenuharness;
+
+namespace
+{
+ const int MAX_TIME_WAITING_FOR_NOTIFICATIONS = 2000;
+}
+
+IndicatorSoundTestBase::IndicatorSoundTestBase() :
+ dbusMock(dbusTestRunner)
+{
+}
+
+IndicatorSoundTestBase::~IndicatorSoundTestBase()
+{
+}
+
+void IndicatorSoundTestBase::SetUp()
+{
+ setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, true);
+ setenv("DBUS_SYSTEM_BUS_ADDRESS", dbusTestRunner.systemBus().toStdString().c_str(), true);
+ setenv("DBUS_SESSION_BUS_ADDRESS", dbusTestRunner.sessionBus().toStdString().c_str(), true);
+ dbusMock.registerNotificationDaemon();
+
+ dbusTestRunner.startServices();
+
+ auto& notifications = notificationsMockInterface();
+ notifications.AddMethod("org.freedesktop.Notifications",
+ "GetCapabilities",
+ "",
+ "as",
+ "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']"
+ ).waitForFinished();
+
+ int waitedTime = 0;
+ while (!dbusTestRunner.sessionConnection().interface()->isServiceRegistered("org.freedesktop.Notifications") && waitedTime < MAX_TIME_WAITING_FOR_NOTIFICATIONS)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ waitedTime += 10;
+ }
+}
+
+void IndicatorSoundTestBase::TearDown()
+{
+ unsetenv("XDG_DATA_DIRS");
+ unsetenv("PULSE_SERVER");
+ unsetenv("DBUS_SYSTEM_BUS_ADDRESS");
+}
+
+void gvariant_deleter(GVariant* varptr)
+{
+ if (varptr != nullptr)
+ {
+ g_variant_unref(varptr);
+ }
+}
+
+std::shared_ptr<GVariant> IndicatorSoundTestBase::volume_variant(double volume)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add(&builder,
+ "{sv}",
+ "title",
+ g_variant_new_string("_Sound"));
+
+ g_variant_builder_add(&builder,
+ "{sv}",
+ "accessible-desc",
+ g_variant_new_string("_Sound"));
+
+ auto icon = g_themed_icon_new("icon");
+ g_variant_builder_add(&builder,
+ "{sv}",
+ "icon",
+ g_icon_serialize(icon));
+
+ g_variant_builder_add(&builder,
+ "{sv}",
+ "visible",
+ g_variant_new_boolean(true));
+ return shared_ptr<GVariant>(g_variant_builder_end(&builder), &gvariant_deleter);
+}
+
+bool IndicatorSoundTestBase::setStreamRestoreVolume(QString const &role, double volume)
+{
+ QProcess setVolume;
+ setVolume.start(VOLUME_SET_BIN, QStringList()
+ << role
+ << QString("%1").arg(volume));
+ if (!setVolume.waitForStarted())
+ return false;
+
+ if (!setVolume.waitForFinished())
+ return false;
+
+ return setVolume.exitCode() == 0;
+}
+
+bool IndicatorSoundTestBase::setSinkVolume(double volume)
+{
+ QString volume_percentage = QString("%1\%").arg(volume*100);
+ QProcess setVolume;
+ setVolume.start("pactl", QStringList()
+ << "-s"
+ << "127.0.0.1"
+ << "set-sink-volume"
+ << "0"
+ << volume_percentage);
+ if (!setVolume.waitForStarted())
+ return false;
+
+ if (!setVolume.waitForFinished())
+ return false;
+
+ return setVolume.exitCode() == 0;
+}
+
+bool IndicatorSoundTestBase::clearGSettingsPlayers()
+{
+ QProcess clearPlayers;
+
+ clearPlayers.start("gsettings", QStringList()
+ << "set"
+ << "com.canonical.indicator.sound"
+ << "interested-media-players"
+ << "[]");
+ if (!clearPlayers.waitForStarted())
+ return false;
+
+ if (!clearPlayers.waitForFinished())
+ return false;
+
+ return clearPlayers.exitCode() == 0;
+}
+
+bool IndicatorSoundTestBase::startTestMprisPlayer(QString const& playerName)
+{
+ testPlayer1.terminate();
+ testPlayer1.start(MEDIA_PLAYER_MPRIS_BIN, QStringList()
+ << playerName);
+ if (!testPlayer1.waitForStarted())
+ return false;
+
+
+ return true;
+}
+
+bool IndicatorSoundTestBase::setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value)
+{
+ QProcess setProperty;
+ QString strValue;
+ strValue = value ? "true" : "false";
+
+ setProperty.start(MEDIA_PLAYER_MPRIS_UPDATE_BIN, QStringList()
+ << testPlayer
+ << property
+ << strValue);
+ if (!setProperty.waitForStarted())
+ return false;
+
+ if (!setProperty.waitForFinished())
+ return false;
+
+ return setProperty.exitCode() == 0;
+}
+
+bool IndicatorSoundTestBase::startTestSound(QString const &role)
+{
+ testSoundProcess.terminate();
+ testSoundProcess.start("paplay", QStringList()
+ << "-s"
+ << "127.0.0.1"
+ << TEST_SOUND
+ << QString("--property=media.role=%1").arg(role));
+
+ if (!testSoundProcess.waitForStarted())
+ return false;
+
+ return true;
+}
+
+void IndicatorSoundTestBase::stopTestSound()
+{
+ testSoundProcess.terminate();
+}
+
+void IndicatorSoundTestBase::startPulseDesktop(DevicePortType speakerPort, DevicePortType headphonesPort)
+{
+ try
+ {
+ pulseaudio.reset(
+ new QProcessDBusService(DBusTypes::DBUS_PULSE,
+ QDBusConnection::SessionBus,
+ "pulseaudio",
+ QStringList() << "--start"
+ << "-vvvv"
+ << "--disable-shm=true"
+ << "--daemonize=false"
+ << "--use-pid-file=false"
+ << "--system=false"
+ << "--exit-idle-time=-1"
+ << "-n"
+ << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort))
+ << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort))
+ << "--log-target=file:/tmp/pulse-daemon.log"
+ << "--load=module-dbus-protocol"
+ << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
+ ));
+ pulseaudio->start(dbusTestRunner.sessionConnection());
+ }
+ catch (exception const& e)
+ {
+ cout << "pulseaudio(): " << e.what() << endl;
+ throw;
+ }
+}
+
+void IndicatorSoundTestBase::startPulsePhone(DevicePortType speakerPort, DevicePortType headphonesPort)
+{
+ try
+ {
+ pulseaudio.reset(
+ new QProcessDBusService(DBusTypes::DBUS_PULSE,
+ QDBusConnection::SessionBus,
+ "pulseaudio",
+ QStringList() << "--start"
+ << "-vvvv"
+ << "--disable-shm=true"
+ << "--daemonize=false"
+ << "--use-pid-file=false"
+ << "--system=false"
+ << "--exit-idle-time=-1"
+ << "-n"
+ << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort))
+ << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort))
+ << "--log-target=file:/tmp/pulse-daemon.log"
+ << QString("--load=module-stream-restore restore_device=false restore_muted=false fallback_table=%1").arg(STREAM_RESTORE_TABLE)
+ << "--load=module-dbus-protocol"
+ << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
+ ));
+ pulseaudio->start(dbusTestRunner.sessionConnection());
+ }
+ catch (exception const& e)
+ {
+ cout << "pulseaudio(): " << e.what() << endl;
+ throw;
+ }
+}
+
+void IndicatorSoundTestBase::startAccountsService()
+{
+ try
+ {
+ accountsService.reset(
+ new QProcessDBusService(DBusTypes::ACCOUNTS_SERVICE,
+ QDBusConnection::SystemBus,
+ ACCOUNTS_SERVICE_BIN,
+ QStringList()));
+ accountsService->start(dbusTestRunner.systemConnection());
+
+ initializeAccountsInterface();
+ }
+ catch (exception const& e)
+ {
+ cout << "accountsService(): " << e.what() << endl;
+ throw;
+ }
+}
+
+void IndicatorSoundTestBase::startIndicator()
+{
+ try
+ {
+ setenv("PULSE_SERVER", "127.0.0.1", true);
+ indicator.reset(
+ new QProcessDBusService(DBusTypes::DBUS_NAME,
+ QDBusConnection::SessionBus,
+ SOUND_SERVICE_BIN,
+ QStringList()));
+ indicator->start(dbusTestRunner.sessionConnection());
+ }
+ catch (exception const& e)
+ {
+ cout << "startIndicator(): " << e.what() << endl;
+ throw;
+ }
+}
+
+mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters()
+{
+ return mh::MenuMatcher::Parameters(
+ "com.canonical.indicator.sound",
+ { { "indicator", "/com/canonical/indicator/sound" } },
+ "/com/canonical/indicator/sound/desktop");
+}
+
+mh::MenuMatcher::Parameters IndicatorSoundTestBase::phoneParameters()
+{
+ return mh::MenuMatcher::Parameters(
+ "com.canonical.indicator.sound",
+ { { "indicator", "/com/canonical/indicator/sound" } },
+ "/com/canonical/indicator/sound/phone");
+}
+
+unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double volume, QString const &label)
+{
+ return mh::MenuItemMatcher().radio()
+ .label(label.toStdString())
+ .round_doubles(0.1)
+ .int32_attribute("target", 0)
+ .double_attribute("min-value", 0.0)
+ .double_attribute("max-value", 1.0)
+ .double_attribute("step", 0.01)
+ .string_attribute("x-canonical-type", "com.canonical.unity.slider")
+ .themed_icon("max-icon", {"audio-volume-high-panel", "audio-volume-high", "audio-volume", "audio"})
+ .themed_icon("min-icon", {"audio-volume-low-zero-panel", "audio-volume-low-zero", "audio-volume-low", "audio-volume", "audio"})
+ .pass_through_double_attribute("action", volume);
+}
+
+unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::silentModeSwitch(bool toggled)
+{
+ return mh::MenuItemMatcher::checkbox()
+ .label("Silent Mode")
+ .action("indicator.silent-mode")
+ .toggled(toggled);
+}
+
+bool IndicatorSoundTestBase::waitMenuChange()
+{
+ if (signal_spy_menu_changed_)
+ {
+ return signal_spy_menu_changed_->wait();
+ }
+ return false;
+}
+
+bool IndicatorSoundTestBase::initializeMenuChangedSignal()
+{
+ if (!menu_interface_)
+ {
+ menu_interface_.reset(new MenusInterface("com.canonical.indicator.sound",
+ "/com/canonical/indicator/sound",
+ dbusTestRunner.sessionConnection(), 0));
+ }
+ if (menu_interface_)
+ {
+ qDebug() << "Waiting for signal";
+ signal_spy_menu_changed_.reset(new QSignalSpy(menu_interface_.get(), &MenusInterface::Changed));
+ }
+ if (!menu_interface_ || !signal_spy_menu_changed_)
+ {
+ return false;
+ }
+ return true;
+}
+
+bool IndicatorSoundTestBase::waitVolumeChangedInIndicator()
+{
+ if (signal_spy_volume_changed_)
+ {
+ return signal_spy_volume_changed_->wait();
+ }
+ return false;
+}
+
+void IndicatorSoundTestBase::initializeAccountsInterface()
+{
+ auto username = qgetenv("USER");
+ if (username != "")
+ {
+ main_accounts_interface_.reset(new AccountsInterface("org.freedesktop.Accounts",
+ "/org/freedesktop/Accounts",
+ dbusTestRunner.systemConnection(), 0));
+
+ QDBusReply<QDBusObjectPath> userResp = main_accounts_interface_->call(QLatin1String("FindUserByName"),
+ QLatin1String(username));
+
+ if (!userResp.isValid())
+ {
+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << userResp.error().message();
+ }
+
+ auto userPath = userResp.value().path();
+ if (userPath != "")
+ {
+ std::unique_ptr<AccountsSoundInterface> soundInterface(new AccountsSoundInterface("org.freedesktop.Accounts",
+ userPath,
+ dbusTestRunner.systemConnection(), 0));
+
+ accounts_interface_.reset(new DBusPropertiesInterface("org.freedesktop.Accounts",
+ userPath,
+ dbusTestRunner.systemConnection(), 0));
+ if (!accounts_interface_->isValid())
+ {
+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << accounts_interface_->lastError().message();
+ }
+ signal_spy_volume_changed_.reset(new QSignalSpy(accounts_interface_.get(),&DBusPropertiesInterface::PropertiesChanged));
+ }
+ }
+}
+
+OrgFreedesktopDBusMockInterface& IndicatorSoundTestBase::notificationsMockInterface()
+{
+ return dbusMock.mockInterface("org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications",
+ QDBusConnection::SessionBus);
+}
+
+bool IndicatorSoundTestBase::setActionValue(const QString & action, QVariant value)
+{
+ QDBusInterface actionsInterface(DBusTypes::DBUS_NAME,
+ DBusTypes::MAIN_SERVICE_PATH,
+ DBusTypes::ACTIONS_INTERFACE,
+ dbusTestRunner.sessionConnection());
+
+ QDBusVariant dbusVar(value);
+ auto resp = actionsInterface.call("SetState",
+ action,
+ QVariant::fromValue(dbusVar),
+ QVariant::fromValue(QVariantMap()));
+
+ if (resp.type() == QDBusMessage::ErrorMessage)
+ {
+ qCritical() << "IndicatorSoundTestBase::setActionValue(): Failed to set value for action "
+ << action
+ << " "
+ << resp.errorMessage();
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+bool IndicatorSoundTestBase::pressNotificationButton(int id, const QString & button)
+{
+ OrgFreedesktopDBusMockInterface actionsInterface("org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications",
+ dbusTestRunner.sessionConnection());
+
+ actionsInterface.EmitSignal(
+ "org.freedesktop.Notifications",
+ "ActionInvoked", "us", QVariantList() << id << button);
+
+ return true;
+}
+
+bool IndicatorSoundTestBase::qDBusArgumentToMap(QVariant const& variant, QVariantMap& map)
+{
+ if (variant.canConvert<QDBusArgument>())
+ {
+ QDBusArgument value(variant.value<QDBusArgument>());
+ if (value.currentType() == QDBusArgument::MapType)
+ {
+ value >> map;
+ return true;
+ }
+ }
+ return false;
+}
+
+void IndicatorSoundTestBase::checkVolumeNotification(double volume, QString const& label, bool isLoud, QVariantList call)
+{
+ QString icon;
+ if (volume <= 0.0)
+ {
+ icon = "audio-volume-muted";
+ }
+ else if (volume <= 0.3)
+ {
+ icon = "audio-volume-low";
+ }
+ else if (volume <= 0.7)
+ {
+ icon = "audio-volume-medium";
+ }
+ else
+ {
+ icon = "audio-volume-high";
+ }
+
+ ASSERT_NE(call.size(), 0);
+ EXPECT_EQ("Notify", call.at(0));
+
+ QVariantList const& args(call.at(1).toList());
+ ASSERT_EQ(8, args.size());
+ EXPECT_EQ("indicator-sound", args.at(0));
+ EXPECT_EQ(icon, args.at(2));
+ EXPECT_EQ("Volume", args.at(3));
+ EXPECT_EQ(label, args.at(4));
+ EXPECT_EQ(QStringList(), args.at(5));
+
+ QVariantMap hints;
+ ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints));
+ ASSERT_TRUE(hints.contains("value"));
+ ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon"));
+ ASSERT_TRUE(hints.contains("x-canonical-value-bar-tint"));
+ ASSERT_TRUE(hints.contains("x-canonical-private-synchronous"));
+
+ EXPECT_EQ(volume*100, hints["value"]);
+ EXPECT_EQ(true, hints["x-canonical-non-shaped-icon"]);
+ EXPECT_EQ(isLoud, hints["x-canonical-value-bar-tint"]);
+ EXPECT_EQ(true, hints["x-canonical-private-synchronous"]);
+}
+
+void IndicatorSoundTestBase::checkHighVolumeNotification(QVariantList call)
+{
+ ASSERT_NE(call.size(), 0);
+ EXPECT_EQ("Notify", call.at(0));
+
+ QVariantList const& args(call.at(1).toList());
+ ASSERT_EQ(8, args.size());
+ EXPECT_EQ("indicator-sound", args.at(0));
+ EXPECT_EQ("Volume", args.at(3));
+}
+
+void IndicatorSoundTestBase::checkCloseNotification(int id, QVariantList call)
+{
+ EXPECT_EQ("CloseNotification", call.at(0));
+ QVariantList const& args(call.at(1).toList());
+ ASSERT_EQ(1, args.size());
+}
+
+void IndicatorSoundTestBase::checkNotificationWithNoArgs(QString const& method, QVariantList call)
+{
+ EXPECT_EQ(method, call.at(0));
+ QVariantList const& args(call.at(1).toList());
+ ASSERT_EQ(0, args.size());
+}
+
+int IndicatorSoundTestBase::getNotificationID(QVariantList call)
+{
+ if (call.size() == 0)
+ {
+ return -1;
+ }
+ QVariantList const& args(call.at(1).toList());
+ if (args.size() != 8)
+ {
+ return -1;
+ }
+ if (args.at(0) != "indicator-sound")
+ {
+ return -1;
+ }
+
+ bool isInt;
+ int id = args.at(1).toInt(&isInt);
+ if (!isInt)
+ {
+ return -1;
+ }
+ return id;
+}
+
+bool IndicatorSoundTestBase::activateHeadphones(bool headphonesActive)
+{
+ QProcess pacltProcess;
+
+ QString defaultSinkName = "indicator_sound_test_speaker";
+ QString suspendedSinkName = "indicator_sound_test_headphones";
+ if (headphonesActive)
+ {
+ defaultSinkName = "indicator_sound_test_headphones";
+ suspendedSinkName = "indicator_sound_test_speaker";
+ }
+
+ pacltProcess.start("pactl", QStringList() << "-s"
+ << "127.0.0.1"
+ << "set-default-sink"
+ << defaultSinkName);
+ if (!pacltProcess.waitForStarted())
+ return false;
+
+ if (!pacltProcess.waitForFinished())
+ return false;
+
+ pacltProcess.start("pactl", QStringList() << "-s"
+ << "127.0.0.1"
+ << "suspend-sink"
+ << defaultSinkName
+ << "0");
+ if (!pacltProcess.waitForStarted())
+ return false;
+
+ if (!pacltProcess.waitForFinished())
+ return false;
+
+ pacltProcess.start("pactl", QStringList() << "-s"
+ << "127.0.0.1"
+ << "suspend-sink"
+ << suspendedSinkName
+ << "1");
+ if (!pacltProcess.waitForStarted())
+ return false;
+
+ if (!pacltProcess.waitForFinished())
+ return false;
+
+ return pacltProcess.exitCode() == 0;
+}
+
+QString IndicatorSoundTestBase::getDevicePortString(DevicePortType port)
+{
+ QString portString;
+
+ switch (port)
+ {
+ case WIRED:
+ portString = "wired";
+ break;
+ case BLUETOOTH:
+ portString = "bluetooth";
+ break;
+ case USB:
+ portString = "usb";
+ break;
+ case HDMI:
+ portString = "hdmi";
+ break;
+ default:
+ portString = "not_defined";
+ break;
+ }
+
+ return portString;
+}
+
+void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, DevicePortType headphonesPort)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ QString speakerString;
+ QString speakerStringMenu;
+ switch(speakerPort)
+ {
+ case WIRED:
+ speakerString = "Speakers";
+ speakerStringMenu = "Volume";
+ break;
+ case BLUETOOTH:
+ speakerString = "Bluetooth speaker";
+ speakerStringMenu = "Volume (Bluetooth)";
+ break;
+ case USB:
+ speakerString = "Usb speaker";
+ speakerStringMenu = "Volume (Usb)";
+ break;
+ case HDMI:
+ speakerString = "HDMI speaker";
+ speakerStringMenu = "Volume (HDMI)";
+ break;
+ }
+
+ QString headphonesString;
+ QString headphonesStringMenu;
+ switch(headphonesPort)
+ {
+ case WIRED:
+ headphonesString = "Headphones";
+ headphonesStringMenu = "Volume (Headphones)";
+ break;
+ case BLUETOOTH:
+ headphonesString = "Bluetooth headphones";
+ headphonesStringMenu = "Volume (Bluetooth headphones)";
+ break;
+ case USB:
+ headphonesString = "Usb headphones";
+ headphonesStringMenu = "Volume (Usb headphones)";
+ break;
+ case HDMI:
+ headphonesString = "HDMI headphones";
+ headphonesStringMenu = "Volume (HDMI headphones)";
+ break;
+ }
+
+ QSignalSpy notificationsSpy(&notificationsMockInterface(),
+ SIGNAL(MethodCalled(const QString &, const QVariantList &)));
+
+ ASSERT_NO_THROW(startAccountsService());
+ ASSERT_NO_THROW(startPulsePhone(speakerPort, headphonesPort));
+
+ // initialize volumes in pulseaudio
+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+ EXPECT_TRUE(setStreamRestoreVolume("multimedia", INITIAL_VOLUME));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ // if the speaker is the normal one it does not emit any notification, as that's
+ // the default one.
+ // for the rest it notifies the output
+ if (speakerPort != WIRED)
+ {
+ WAIT_FOR_SIGNALS(notificationsSpy, 3);
+
+ // the first time we also have the calls to
+ // GetServerInformation and GetCapabilities
+ checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
+ checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(2));
+ notificationsSpy.clear();
+ }
+
+ notificationsSpy.clear();
+ // activate the headphones
+ EXPECT_TRUE(activateHeadphones(true));
+
+ if (speakerPort == WIRED)
+ {
+ WAIT_FOR_SIGNALS(notificationsSpy, 3);
+
+ // the first time we also have the calls to
+ // GetServerInformation and GetCapabilities
+ checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
+ checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(2));
+ notificationsSpy.clear();
+ }
+ else
+ {
+ WAIT_FOR_SIGNALS(notificationsSpy, 2);
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(1));
+ notificationsSpy.clear();
+ }
+
+ // check the label in the menu
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::starts_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(INITIAL_VOLUME, headphonesStringMenu))
+ )
+ ).match());
+
+ // deactivate the headphones
+ EXPECT_TRUE(activateHeadphones(false));
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 2);
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(1));
+ notificationsSpy.clear();
+
+ // check the label in the menu
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::starts_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(INITIAL_VOLUME, speakerStringMenu))
+ )
+ ).match());
+}
+
+bool IndicatorSoundTestBase::setVolumeUntilAccountsIsConnected(double volume)
+{
+ int RETRY_TIME = 5000;
+
+ setActionValue("volume", QVariant::fromValue(volume));
+ while(!signal_spy_volume_changed_->wait(10) && RETRY_TIME)
+ {
+ RETRY_TIME -= 10;
+ setActionValue("volume", QVariant::fromValue(volume));
+ }
+ return (signal_spy_volume_changed_->count() != 0);
+}
diff --git a/tests/integration/indicator-sound-test-base.h b/tests/integration/indicator-sound-test-base.h
new file mode 100644
index 0000000..41dd1c7
--- /dev/null
+++ b/tests/integration/indicator-sound-test-base.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
+ */
+
+#pragma once
+
+#include <libqtdbustest/DBusTestRunner.h>
+#include <libqtdbustest/QProcessDBusService.h>
+#include <libqtdbusmock/DBusMock.h>
+
+#include <unity/gmenuharness/MatchUtils.h>
+#include <unity/gmenuharness/MenuMatcher.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+class MenusInterface;
+class DBusPulseVolume;
+class DBusPropertiesInterface;
+class AccountsInterface;
+class QSignalSpy;
+
+#define WAIT_FOR_SIGNALS(signalSpy, signalsExpected)\
+{\
+ while (signalSpy.size() < signalsExpected)\
+ {\
+ ASSERT_TRUE(signalSpy.wait());\
+ }\
+ ASSERT_EQ(signalsExpected, signalSpy.size());\
+}
+
+#define WAIT_AT_LEAST_SIGNALS(signalSpy, signalsExpected)\
+{\
+ while (signalSpy.size() < signalsExpected)\
+ {\
+ ASSERT_TRUE(signalSpy.wait());\
+ }\
+ ASSERT_TRUE(signalsExpected <= signalSpy.size());\
+}
+
+class IndicatorSoundTestBase: public testing::Test
+{
+public:
+ IndicatorSoundTestBase();
+
+ ~IndicatorSoundTestBase();
+
+ enum DevicePortType
+ {
+ WIRED,
+ BLUETOOTH,
+ USB,
+ HDMI
+ };
+
+protected:
+ void SetUp() override;
+ void TearDown() override;
+
+ void startIndicator();
+ void startPulseDesktop(DevicePortType speakerPort=WIRED, DevicePortType headphonesPort=WIRED);
+ void startPulsePhone(DevicePortType speakerPort=WIRED, DevicePortType headphonesPort=WIRED);
+ void startAccountsService();
+
+ bool clearGSettingsPlayers();
+
+ bool startTestMprisPlayer(QString const& playerName);
+
+ bool setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value);
+
+ bool setStreamRestoreVolume(QString const &role, double volume);
+
+ bool setSinkVolume(double volume);
+
+ bool startTestSound(QString const &role);
+
+ void stopTestSound();
+
+ static std::shared_ptr<GVariant> volume_variant(double volume);
+
+ static unity::gmenuharness::MenuMatcher::Parameters desktopParameters();
+
+ static unity::gmenuharness::MenuMatcher::Parameters phoneParameters();
+
+ static unity::gmenuharness::MenuItemMatcher volumeSlider(double volume, QString const &label);
+
+ static unity::gmenuharness::MenuItemMatcher silentModeSwitch(bool toggled);
+
+ bool waitMenuChange();
+
+ bool initializeMenuChangedSignal();
+
+ bool waitVolumeChangedInIndicator();
+
+ void initializeAccountsInterface();
+
+ OrgFreedesktopDBusMockInterface& notificationsMockInterface();
+
+ bool setActionValue(const QString & action, QVariant value);
+
+ bool pressNotificationButton(int id, const QString & button);
+
+ bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map);
+
+ void checkVolumeNotification(double volume, QString const& label, bool isLoud, QVariantList call);
+
+ void checkHighVolumeNotification(QVariantList call);
+
+ void checkCloseNotification(int id, QVariantList call);
+
+ void checkNotificationWithNoArgs(QString const& method, QVariantList call);
+
+ int getNotificationID(QVariantList call);
+
+ bool activateHeadphones(bool headphonesActive);
+
+ QString getDevicePortString(DevicePortType port);
+
+ void checkPortDevicesLabels(DevicePortType speakerPort, DevicePortType headphonesPort);
+
+ bool setVolumeUntilAccountsIsConnected(double volume);
+
+ QtDBusTest::DBusTestRunner dbusTestRunner;
+
+ QtDBusMock::DBusMock dbusMock;
+
+ QtDBusTest::DBusServicePtr indicator;
+
+ QtDBusTest::DBusServicePtr pulseaudio;
+
+ QtDBusTest::DBusServicePtr accountsService;
+
+ QProcess testSoundProcess;
+
+ QProcess testPlayer1;
+
+ std::unique_ptr<MenusInterface> menu_interface_;
+
+ std::unique_ptr<DBusPropertiesInterface> accounts_interface_;
+
+ std::unique_ptr<AccountsInterface> main_accounts_interface_;
+
+ std::unique_ptr<QSignalSpy> signal_spy_volume_changed_;
+
+ std::unique_ptr<QSignalSpy> signal_spy_menu_changed_;
+};
diff --git a/tests/integration/main.cpp b/tests/integration/main.cpp
new file mode 100644
index 0000000..a29b3b6
--- /dev/null
+++ b/tests/integration/main.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Pete Woods <pete.woods@canonical.com>
+ */
+
+//#include <config.h>
+
+#include <QCoreApplication>
+#include <QTimer>
+#include <gtest/gtest.h>
+
+#include <libqtdbusmock/DBusMock.h>
+
+#include "dbus-types.h"
+
+using namespace QtDBusMock;
+
+class Runner: public QObject
+{
+ Q_OBJECT
+public Q_SLOTS:
+ void run()
+ {
+ QCoreApplication::exit(RUN_ALL_TESTS());
+ }
+};
+
+int main(int argc, char **argv)
+{
+ qputenv("LANG", "C.UTF-8");
+ unsetenv("LC_ALL");
+
+ QCoreApplication application(argc, argv);
+ DBusMock::registerMetaTypes();
+ DBusTypes::registerMetaTypes();
+ ::testing::InitGoogleTest(&argc, argv);
+
+ Runner runner;
+ QTimer::singleShot(0, &runner, SLOT(run()));
+
+ return application.exec();
+}
+
+#include "main.moc"
diff --git a/tests/integration/test-indicator.cpp b/tests/integration/test-indicator.cpp
new file mode 100644
index 0000000..33e62b5
--- /dev/null
+++ b/tests/integration/test-indicator.cpp
@@ -0,0 +1,981 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
+ */
+
+#include <indicator-sound-test-base.h>
+
+#include <QDebug>
+#include <QTestEventLoop>
+#include <QSignalSpy>
+
+using namespace std;
+using namespace testing;
+namespace mh = unity::gmenuharness;
+namespace
+{
+
+class TestIndicator: public IndicatorSoundTestBase
+{
+};
+
+TEST_F(TestIndicator, PhoneChangeRoleVolume)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ ASSERT_NO_THROW(startAccountsService());
+ ASSERT_NO_THROW(startPulsePhone());
+
+ // initialize volumes in pulseaudio
+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+ EXPECT_TRUE(setStreamRestoreVolume("multimedia", INITIAL_VOLUME));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ // Generate a random volume
+ QTime now = QTime::currentTime();
+ qsrand(now.msec());
+ int randInt = qrand() % 100;
+ double randomVolume = randInt / 100.0;
+
+ QSignalSpy &userAccountsSpy = *signal_spy_volume_changed_;
+ // set an initial volume to the alert role
+ userAccountsSpy.clear();
+ EXPECT_TRUE(setVolumeUntilAccountsIsConnected(1.0));
+ userAccountsSpy.clear();
+ // play a test sound, it should change the role in the indicator
+ EXPECT_TRUE(startTestSound("multimedia"));
+
+ // this time we only expect 1 signal as it's only the indicator
+ // updating the value
+ WAIT_FOR_SIGNALS(userAccountsSpy, 1);
+ //EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ userAccountsSpy.clear();
+ // set the random volume to the multimedia role
+ setActionValue("volume", QVariant::fromValue(randomVolume));
+ if (randomVolume != INITIAL_VOLUME)
+ {
+ WAIT_FOR_SIGNALS(userAccountsSpy, 1);
+ }
+
+ // check the indicator
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::starts_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(randomVolume, "Volume"))
+ )
+ ).match());
+
+ // check that the last item is Sound Settings
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::ends_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ .action("indicator.phone-settings")
+ )
+ ).match());
+
+ // initialize the signal spy
+ EXPECT_TRUE(initializeMenuChangedSignal());
+ userAccountsSpy.clear();
+ // stop the test sound, the role should change again to alert
+ stopTestSound();
+ if (randomVolume != 1.0)
+ {
+ // wait for the menu change
+ EXPECT_TRUE(waitMenuChange());
+ }
+
+ // check the initial volume for the alert role
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::starts_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(1.0, "Volume"))
+ )
+ ).match());
+
+ // check that the last item is Sound Settings
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::ends_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ .action("indicator.phone-settings")
+ )
+ ).match());
+}
+
+TEST_F(TestIndicator, PhoneBasicInitialVolume)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ ASSERT_NO_THROW(startAccountsService());
+ EXPECT_TRUE(clearGSettingsPlayers());
+ ASSERT_NO_THROW(startPulsePhone());
+
+ // initialize volumes in pulseaudio
+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ .action("indicator.phone-settings")
+ )
+ ).match());
+}
+
+TEST_F(TestIndicator, PhoneAddMprisPlayer)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ ASSERT_NO_THROW(startAccountsService());
+ EXPECT_TRUE(clearGSettingsPlayers());
+ ASSERT_NO_THROW(startPulsePhone());
+
+ // initialize volumes in pulseaudio
+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ .action("indicator.phone-settings")
+ )
+ ).match());
+
+ // initialize the signal spy
+ EXPECT_TRUE(initializeMenuChangedSignal());
+
+ // start the test player
+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
+
+ // wait for the menu change
+ EXPECT_TRUE(waitMenuChange());
+
+ // finally verify that the player is added
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher()
+ .action("indicator.testplayer1.desktop")
+ .label("TestPlayer1")
+ .themed_icon("icon", {"testplayer"})
+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
+ )
+ .item(mh::MenuItemMatcher()
+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
+ )
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ .action("indicator.phone-settings")
+ )
+ ).match());
+}
+
+TEST_F(TestIndicator, DesktopBasicInitialVolume)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ ASSERT_NO_THROW(startAccountsService());
+ EXPECT_TRUE(clearGSettingsPlayers());
+ ASSERT_NO_THROW(startPulseDesktop());
+
+ // initialize volumes in pulseaudio
+ EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+ EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
+
+ // start the test player
+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher().checkbox()
+ .label("Mute")
+ )
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher()
+ .action("indicator.testplayer1.desktop")
+ .label("TestPlayer1")
+ .themed_icon("icon", {"testplayer"})
+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
+ )
+ .item(mh::MenuItemMatcher()
+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
+ )
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ )
+ ).match());
+}
+
+TEST_F(TestIndicator, DesktopAddMprisPlayer)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ ASSERT_NO_THROW(startAccountsService());
+ EXPECT_TRUE(clearGSettingsPlayers());
+ ASSERT_NO_THROW(startPulseDesktop());
+
+ // initialize volumes in pulseaudio
+ EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+ EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
+
+ // start the test player
+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ // check that the player is added
+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher().checkbox()
+ .label("Mute")
+ )
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher()
+ .action("indicator.testplayer1.desktop")
+ .label("TestPlayer1")
+ .themed_icon("icon", {"testplayer"})
+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
+ )
+ .item(mh::MenuItemMatcher()
+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
+ )
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ )
+ ).match());
+}
+
+TEST_F(TestIndicator, DesktopMprisPlayerButtonsState)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ ASSERT_NO_THROW(startAccountsService());
+ EXPECT_TRUE(clearGSettingsPlayers());
+ ASSERT_NO_THROW(startPulseDesktop());
+
+ // initialize volumes in pulseaudio
+ EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+ EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
+
+ // start the test player
+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ // check that the player is added
+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher().checkbox()
+ .label("Mute")
+ )
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher()
+ .action("indicator.testplayer1.desktop")
+ .label("TestPlayer1")
+ .themed_icon("icon", {"testplayer"})
+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
+ )
+ .item(mh::MenuItemMatcher()
+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
+ )
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ )
+ ).match());
+
+ // change the state of CanGoNext
+ EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoNext", false));
+
+ // verify that the action changes
+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher().checkbox()
+ .label("Mute")
+ )
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher()
+ .action("indicator.testplayer1.desktop")
+ .label("TestPlayer1")
+ .themed_icon("icon", {"testplayer"})
+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
+ )
+ .item(mh::MenuItemMatcher()
+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
+ .attribute_not_set("x-canonical-next-action")
+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
+ )
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ )
+ ).match());
+
+
+ // change the state of CanGoPrevious
+ EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoPrevious", false));
+
+ // verify that the action changes
+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher().checkbox()
+ .label("Mute")
+ )
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher()
+ .action("indicator.testplayer1.desktop")
+ .label("TestPlayer1")
+ .themed_icon("icon", {"testplayer"})
+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
+ )
+ .item(mh::MenuItemMatcher()
+ .attribute_not_set("x-canonical-previous-action")
+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
+ .attribute_not_set("x-canonical-next-action")
+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
+ )
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ )
+ ).match());
+
+ // set back both to true
+ EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoNext", true));
+ EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoPrevious", true));
+
+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher().checkbox()
+ .label("Mute")
+ )
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher()
+ .action("indicator.testplayer1.desktop")
+ .label("TestPlayer1")
+ .themed_icon("icon", {"testplayer"})
+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
+ )
+ .item(mh::MenuItemMatcher()
+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
+ )
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ )
+ ).match());
+}
+
+TEST_F(TestIndicator, DesktopChangeRoleVolume)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ ASSERT_NO_THROW(startAccountsService());
+ ASSERT_NO_THROW(startPulseDesktop());
+
+ // initialize volumes in pulseaudio
+ // expect false for stream restore, because the module
+ // is not loaded in the desktop pulseaudio instance
+ EXPECT_FALSE(setStreamRestoreVolume("mutimedia", INITIAL_VOLUME));
+ EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+
+ EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ // Generate a random volume
+ QTime now = QTime::currentTime();
+ qsrand(now.msec());
+ int randInt = qrand() % 100;
+ double randomVolume = randInt / 100.0;
+
+ // play a test sound, it should NOT change the role in the indicator
+ EXPECT_TRUE(startTestSound("multimedia"));
+ EXPECT_FALSE(waitVolumeChangedInIndicator());
+
+ // set the random volume
+ EXPECT_TRUE(setSinkVolume(randomVolume));
+ if (randomVolume != INITIAL_VOLUME)
+ {
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+ }
+
+ // check the indicator
+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::starts_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(mh::MenuItemMatcher().checkbox()
+ .label("Mute")
+ )
+ .item(volumeSlider(randomVolume, "Volume"))
+ )
+ ).match());
+
+ // check that the last item is Sound Settings
+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::ends_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ )
+ ).match());
+
+ // stop the test sound, the role should change again to alert
+ stopTestSound();
+
+ // although we were playing something in the multimedia role
+ // the server does not have the streamrestore module, so
+ // the volume does not change (the role does not change)
+ EXPECT_FALSE(waitVolumeChangedInIndicator());
+
+ // check the initial volume for the alert role
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::starts_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(randomVolume, "Volume"))
+ )
+ ).match());
+
+ // check that the last item is Sound Settings
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .mode(mh::MenuItemMatcher::Mode::ends_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ .action("indicator.phone-settings")
+ )
+ ).match());
+}
+
+TEST_F(TestIndicator, PhoneNotificationVolume)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ QSignalSpy notificationsSpy(&notificationsMockInterface(),
+ SIGNAL(MethodCalled(const QString &, const QVariantList &)));
+
+ ASSERT_NO_THROW(startAccountsService());
+ EXPECT_TRUE(clearGSettingsPlayers());
+ ASSERT_NO_THROW(startPulsePhone());
+
+ // initialize volumes in pulseaudio
+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ // check the initial state
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::all)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(INITIAL_VOLUME, "Volume"))
+ )
+ .item(mh::MenuItemMatcher()
+ .label("Sound Settings…")
+ .action("indicator.phone-settings")
+ )
+ ).match());
+
+ // change volume to 1.0
+ setActionValue("volume", QVariant::fromValue(1.0));
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 3);
+
+ // the first time we also have the calls to
+ // GetServerInformation and GetCapabilities
+ checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
+ checkVolumeNotification(1.0, "Speakers", false, notificationsSpy.at(2));
+
+ notificationsSpy.clear();
+ setActionValue("volume", QVariant::fromValue(0.0));
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 2)
+
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkVolumeNotification(0.0, "Speakers", false, notificationsSpy.at(1));
+
+ notificationsSpy.clear();
+ setActionValue("volume", QVariant::fromValue(0.5));
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 2)
+
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkVolumeNotification(0.5, "Speakers", false, notificationsSpy.at(1));
+}
+
+TEST_F(TestIndicator, PhoneNotificationWarningVolume)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ QSignalSpy notificationsSpy(&notificationsMockInterface(),
+ SIGNAL(MethodCalled(const QString &, const QVariantList &)));
+
+ ASSERT_NO_THROW(startAccountsService());
+ ASSERT_NO_THROW(startPulsePhone());
+
+ // initialize volumes in pulseaudio
+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+ EXPECT_TRUE(setStreamRestoreVolume("multimedia", INITIAL_VOLUME));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ // activate the headphones
+ EXPECT_TRUE(activateHeadphones(true));
+
+ // set an initial volume to the alert role
+ setStreamRestoreVolume("alert", 1.0);
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ // play a test sound, it should change the role in the indicator
+ EXPECT_TRUE(startTestSound("multimedia"));
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 3);
+ // the first time we also have the calls to
+ // GetServerInformation and GetCapabilities
+ checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
+ checkVolumeNotification(0.0, "Headphones", false, notificationsSpy.at(2));
+ notificationsSpy.clear();
+
+ // change volume to 0.3... no warning should be emitted
+ setActionValue("volume", QVariant::fromValue(0.3));
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 2);
+
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkVolumeNotification(0.3, "Headphones", false, notificationsSpy.at(1));
+ notificationsSpy.clear();
+
+ // change volume to 0.5... no warning should be emitted
+ setActionValue("volume", QVariant::fromValue(0.5));
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 2);
+
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkVolumeNotification(0.5, "Headphones", false, notificationsSpy.at(1));
+ notificationsSpy.clear();
+
+ // change volume to 1.0... warning should be emitted
+ setActionValue("volume", QVariant::fromValue(1.0));
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 8);
+
+ // the notification is sent twice (TODO check why)
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkCloseNotification(1, notificationsSpy.at(1));
+ checkHighVolumeNotification(notificationsSpy.at(2));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(3));
+ checkCloseNotification(1, notificationsSpy.at(4));
+ checkHighVolumeNotification(notificationsSpy.at(5));
+
+ // get the last notification ID
+ int idNotification = getNotificationID(notificationsSpy.at(5));
+ ASSERT_NE(-1, idNotification);
+
+ qWarning() << "XGM: id Notification: " << idNotification;
+
+ // cancel the dialog
+ pressNotificationButton(idNotification, "cancel");
+
+ // check that the volume was clamped
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::starts_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(0.74, "Volume (Headphones)"))
+ )
+ ).match());
+
+ // try again...
+ notificationsSpy.clear();
+
+ qWarning() << "-----------------------------------------------------------";
+ // change volume to 1.0... warning should be emitted
+ setActionValue("volume", QVariant::fromValue(1.0));
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 6);
+
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkHighVolumeNotification(notificationsSpy.at(1));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(2));
+ checkHighVolumeNotification(notificationsSpy.at(3));
+
+ // get the last notification ID
+ idNotification = getNotificationID(notificationsSpy.at(1));
+ ASSERT_NE(-1, idNotification);
+
+ // this time we approve
+ pressNotificationButton(idNotification, "ok");
+
+ // check that the volume was applied
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::starts_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(1.0, "Volume (Headphones)"))
+ .item(mh::MenuItemMatcher()
+ .action("indicator.high-volume-warning-item")
+ .label("High volume can damage your hearing.")
+ )
+ )
+ ).match());
+
+ // after the warning was approved we should be able to modify the volume
+ // and don't get the warning
+ notificationsSpy.clear();
+
+ // change volume to 0.5... no warning should be emitted
+ setActionValue("volume", QVariant::fromValue(0.5));
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 6);
+
+ // check the notification TODO check why the sound indicator sends it twice
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkCloseNotification(idNotification, notificationsSpy.at(1));
+ checkVolumeNotification(0.5, "Headphones", false, notificationsSpy.at(2));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(3));
+ checkCloseNotification(idNotification, notificationsSpy.at(4));
+ checkVolumeNotification(0.5, "Headphones", false, notificationsSpy.at(5));
+
+ // check that the volume was applied
+ // and that we don't have the warning item
+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
+ .item(mh::MenuItemMatcher()
+ .action("indicator.root")
+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
+ .string_attribute("submenu-action", "indicator.indicator-shown")
+ .mode(mh::MenuItemMatcher::Mode::starts_with)
+ .submenu()
+ .item(mh::MenuItemMatcher()
+ .section()
+ .item(silentModeSwitch(false))
+ .item(volumeSlider(0.5, "Volume (Headphones)"))
+ )
+ ).match());
+
+ // now set high volume again, we should not get the warning dialog
+ // as we already approved it
+ notificationsSpy.clear();
+
+ setActionValue("volume", QVariant::fromValue(1.0));
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 4);
+
+ // check the notification TODO check why the sound indicator sends it twice
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkVolumeNotification(1.0, "Headphones", true, notificationsSpy.at(1));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(2));
+ checkVolumeNotification(1.0, "Headphones", true, notificationsSpy.at(3));
+}
+
+
+TEST_F(TestIndicator, PhoneNotificationWarningVolumeAlertMode)
+{
+ double INITIAL_VOLUME = 0.0;
+
+ QSignalSpy notificationsSpy(&notificationsMockInterface(),
+ SIGNAL(MethodCalled(const QString &, const QVariantList &)));
+
+ ASSERT_NO_THROW(startAccountsService());
+ ASSERT_NO_THROW(startPulsePhone());
+
+ // initialize volumes in pulseaudio
+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
+ EXPECT_TRUE(setStreamRestoreVolume("multimedia", INITIAL_VOLUME));
+
+ // start now the indicator, so it picks the new volumes
+ ASSERT_NO_THROW(startIndicator());
+
+ // activate the headphones
+ EXPECT_TRUE(activateHeadphones(true));
+
+ // set an initial volume to the alert role
+ setStreamRestoreVolume("alert", 1.0);
+ EXPECT_TRUE(waitVolumeChangedInIndicator());
+
+ // change volume to 0.0... no warning should be emitted
+ setActionValue("volume", QVariant::fromValue(0.0));
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 5);
+
+ // the first time we also have the calls to
+ // GetServerInformation and GetCapabilities
+ checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
+ checkVolumeNotification(0.0, "Headphones", false, notificationsSpy.at(2));
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(3));
+ checkVolumeNotification(0.0, "Headphones", false, notificationsSpy.at(4));
+ notificationsSpy.clear();
+
+ // change volume to 0.5... no warning should be emitted
+ setActionValue("volume", QVariant::fromValue(0.5));
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 2);
+
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkVolumeNotification(0.5, "Headphones", false, notificationsSpy.at(1));
+ notificationsSpy.clear();
+
+ // change volume to 1.0... no warning should be emitted, we are in alert mode
+ setActionValue("volume", QVariant::fromValue(1.0));
+
+ WAIT_FOR_SIGNALS(notificationsSpy, 2);
+
+ checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
+ checkVolumeNotification(1.0, "Headphones", false, notificationsSpy.at(1));
+ notificationsSpy.clear();
+}
+
+TEST_F(TestIndicator, PhoneNotificationHeadphoneSpeakerWiredLabels)
+{
+ checkPortDevicesLabels(WIRED, WIRED);
+}
+
+TEST_F(TestIndicator, PhoneNotificationHeadphoneSpeakerBluetoothLabels)
+{
+ checkPortDevicesLabels(BLUETOOTH, BLUETOOTH);
+}
+
+TEST_F(TestIndicator, PhoneNotificationHeadphoneSpeakerUSBLabels)
+{
+ checkPortDevicesLabels(USB, USB);
+}
+
+TEST_F(TestIndicator, PhoneNotificationHeadphoneSpeakerHDMILabels)
+{
+ checkPortDevicesLabels(HDMI, HDMI);
+}
+
+} // namespace
diff --git a/tests/integration/test-sound.wav b/tests/integration/test-sound.wav
new file mode 100644
index 0000000..f696657
--- /dev/null
+++ b/tests/integration/test-sound.wav
Binary files differ
diff --git a/tests/integration/touch-stream-restore.table b/tests/integration/touch-stream-restore.table
new file mode 100755
index 0000000..146b02e
--- /dev/null
+++ b/tests/integration/touch-stream-restore.table
@@ -0,0 +1,4 @@
+sink-input-by-media-role:multimedia -8
+sink-input-by-media-role:alert -8
+sink-input-by-media-role:alarm -8
+sink-input-by-media-role:phone -15
diff --git a/tests/integration/utils/dbus-pulse-volume.cpp b/tests/integration/utils/dbus-pulse-volume.cpp
new file mode 100644
index 0000000..c8b6ae6
--- /dev/null
+++ b/tests/integration/utils/dbus-pulse-volume.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
+ */
+
+#include "dbus-pulse-volume.h"
+
+#include "dbus_properties_interface.h"
+#include "dbus_accounts_interface.h"
+#include "dbus_accountssound_interface.h"
+#include "stream_restore_interface.h"
+
+#include <pulse/volume.h>
+
+#include <QSignalSpy>
+
+unsigned int volumeDoubleToUint(double volume)
+{
+ double tmp = (double)(PA_VOLUME_NORM - PA_VOLUME_MUTED) * volume;
+ return (unsigned int)tmp + PA_VOLUME_MUTED;
+}
+
+double volumeUIntToDoulbe(uint volume)
+{
+ double tmp = (double)(volume - PA_VOLUME_MUTED);
+ return tmp / (double)(PA_VOLUME_NORM - PA_VOLUME_MUTED);
+}
+
+DBusPulseVolume::DBusPulseVolume() :
+ QObject()
+{
+ std::unique_ptr<DBusPropertiesInterface> basicConnectionInterface(new DBusPropertiesInterface("org.PulseAudio1",
+ "/org/pulseaudio/server_lookup1",
+ QDBusConnection::sessionBus(), 0));
+
+ QDBusReply<QVariant> connection_string = basicConnectionInterface->call(QLatin1String("Get"),
+ QLatin1String("org.PulseAudio.ServerLookup1"),
+ QLatin1String("Address"));
+
+ if (!connection_string.isValid())
+ {
+ qWarning() << "DBusPulseVolume::DBusPulseVolume(): D-Bus error: " << connection_string.error().message();
+ }
+
+ connection_.reset(new QDBusConnection(QDBusConnection::connectToPeer(connection_string.value().toString(), "set-volume")));
+
+ if (connection_->isConnected())
+ {
+ interface_paths_.reset(new StreamRestoreInterface("org.PulseAudio.Ext.StreamRestore1",
+ "/org/pulseaudio/stream_restore1",
+ *(connection_.get()), 0));
+ if (interface_paths_)
+ {
+ // get the role paths
+ fillRolePath("multimedia");
+ fillRolePath("alert");
+ fillRolePath("alarm");
+ fillRolePath("phone");
+ }
+
+ initializeAccountsInterface();
+ }
+ else
+ {
+ qWarning() << "DBusPulseVolume::DBusPulseVolume(): Error connecting: " << connection_->lastError().message();
+ }
+}
+
+DBusPulseVolume::~DBusPulseVolume()
+{
+}
+
+QString DBusPulseVolume::fillRolePath(QString const &role)
+{
+ QString role_complete_name = QString("sink-input-by-media-role:") + role;
+ // get the role paths
+ QDBusReply<QDBusObjectPath> objectPath = interface_paths_->call(QLatin1String("GetEntryByName"), role_complete_name);
+ if (!objectPath.isValid())
+ {
+ qWarning() << "SetVolume::fillRolePath(): D-Bus error: " << objectPath.error().message();
+ return "";
+ }
+
+ auto role_info = std::make_shared<RoleInformation>();
+ role_info->interface_.reset(new DBusPropertiesInterface("org.PulseAudio.Ext.StreamRestore1.RestoreEntry",
+ objectPath.value().path(),
+ *(connection_.get()), 0));
+ if (!role_info->interface_)
+ {
+ qWarning() << "SetVolume::fillRolePath() - Error obtaining properties interface";
+ return "";
+ }
+ role_info->path_ = objectPath.value().path();
+ roles_map_[role] = role_info;
+ return role_info->path_;
+}
+
+bool DBusPulseVolume::setVolume(QString const & role, double volume)
+{
+ if (!interface_paths_)
+ {
+ qWarning() << "SetVolume::setVolume(): error: Volume interfaces are not initialized";
+ return false;
+ }
+
+ RolesMap::const_iterator iter = roles_map_.find(role);
+ if (iter != roles_map_.end())
+ {
+ QVariant var;
+ PulseaudioVolumeArray t;
+ PulseaudioVolume vv(0, volumeDoubleToUint(volume));
+ t.addItem(vv);
+ var.setValue(t);
+ QDBusVariant dbusVar(var);
+ QDBusReply<void> set_vol = (*iter).second->interface_->call(QLatin1String("Set"),
+ QVariant::fromValue(QString("org.PulseAudio.Ext.StreamRestore1.RestoreEntry")),
+ QVariant::fromValue(QString("Volume")),
+ QVariant::fromValue(dbusVar));
+
+ if (!set_vol.isValid())
+ {
+ qWarning() << "SetVolume::setVolume(): D-Bus error: " << set_vol.error().message();
+ return false;
+ }
+
+ if (accounts_interface_)
+ {
+ QDBusVariant dbusVar(QVariant::fromValue(volume));
+ QDBusReply<void> set_vol = accounts_interface_->call(QLatin1String("Set"),
+ QVariant::fromValue(QString("com.ubuntu.AccountsService.Sound")),
+ QVariant::fromValue(QString("Volume")),
+ QVariant::fromValue(dbusVar));
+ if (!set_vol.isValid())
+ {
+ qWarning() << "SetVolume::setVolume(): D-Bus error: " << set_vol.error().message();
+ return false;
+ }
+ }
+ }
+ else
+ {
+ qWarning() << "SetVolume::setVolume(): role " << role << " was not found.";
+ return false;
+ }
+ return true;
+}
+
+double DBusPulseVolume::getVolume(QString const & role)
+{
+ if (interface_paths_)
+ {
+ RolesMap::const_iterator iter = roles_map_.find(role);
+ if (iter != roles_map_.end())
+ {
+ QDBusReply<QVariant> prev_vol = (*iter).second->interface_->call(QLatin1String("Get"),
+ QLatin1String("org.PulseAudio.Ext.StreamRestore1.RestoreEntry"),
+ QLatin1String("Volume"));
+
+ if (!prev_vol.isValid())
+ {
+ qWarning() << "SetVolume::setVolume(): D-Bus error: " << prev_vol.error().message();
+ }
+ QDBusArgument arg = prev_vol.value().value<QDBusArgument>();
+ PulseaudioVolumeArray element;
+ arg >> element;
+ return volumeUIntToDoulbe(element.getItem(0).getVolume());
+ }
+ }
+ return -1.0;
+}
+
+void DBusPulseVolume::initializeAccountsInterface()
+{
+ auto username = qgetenv("USER");
+ if (username != "")
+ {
+ qDebug() << "Setting Accounts interface for user: " << username;
+ std::unique_ptr<AccountsInterface> setInterface(new AccountsInterface("org.freedesktop.Accounts",
+ "/org/freedesktop/Accounts",
+ QDBusConnection::systemBus(), 0));
+
+ QDBusReply<QDBusObjectPath> userResp = setInterface->call(QLatin1String("FindUserByName"),
+ QLatin1String(username));
+
+ if (!userResp.isValid())
+ {
+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << userResp.error().message();
+ }
+ auto userPath = userResp.value().path();
+ if (userPath != "")
+ {
+ std::unique_ptr<AccountsSoundInterface> soundInterface(new AccountsSoundInterface("org.freedesktop.Accounts",
+ userPath,
+ QDBusConnection::systemBus(), 0));
+
+ accounts_interface_.reset(new DBusPropertiesInterface("org.freedesktop.Accounts",
+ userPath,
+ soundInterface->connection(), 0));
+ if (!accounts_interface_->isValid())
+ {
+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << accounts_interface_->lastError().message();
+ }
+ signal_spy_volume_changed_.reset(new QSignalSpy(accounts_interface_.get(),&DBusPropertiesInterface::PropertiesChanged));
+ }
+ }
+}
+
+bool DBusPulseVolume::waitForVolumeChangedInAccountsService()
+{
+ if (signal_spy_volume_changed_)
+ {
+ return signal_spy_volume_changed_->wait();
+ }
+ else
+ {
+ qWarning() << "DBusPulseVolume::waitForVolumeChangedInAccountsService(): signal was not instantiated";
+ }
+ return false;
+}
diff --git a/tests/integration/utils/dbus-pulse-volume.h b/tests/integration/utils/dbus-pulse-volume.h
new file mode 100644
index 0000000..2d55e89
--- /dev/null
+++ b/tests/integration/utils/dbus-pulse-volume.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
+ */
+#pragma once
+
+#include <QObject>
+#include <QDBusConnection>
+
+#include <map>
+#include <memory>
+
+class StreamRestoreInterface;
+class DBusPropertiesInterface;
+class AccountsInterface;
+class AccountsSoundInterface;
+class QSignalSpy;
+
+struct RoleInformation
+{
+ std::unique_ptr<DBusPropertiesInterface> interface_;
+ QString path_;
+};
+
+class DBusPulseVolume : public QObject
+{
+public:
+ DBusPulseVolume();
+ ~DBusPulseVolume();
+
+ bool setVolume(QString const & role, double volume);
+ double getVolume(QString const & role);
+ bool waitForVolumeChangedInAccountsService();
+
+protected:
+ QString fillRolePath(QString const &role);
+ void initializeAccountsInterface();
+ std::unique_ptr<QDBusConnection> connection_;
+ std::unique_ptr<StreamRestoreInterface> interface_paths_;
+ typedef std::map<QString, std::shared_ptr<RoleInformation>> RolesMap;
+ RolesMap roles_map_;
+ std::unique_ptr<DBusPropertiesInterface> accounts_interface_;
+ std::unique_ptr<QSignalSpy> signal_spy_volume_changed_;
+};
diff --git a/tests/integration/utils/get-volume.cpp b/tests/integration/utils/get-volume.cpp
new file mode 100644
index 0000000..309da36
--- /dev/null
+++ b/tests/integration/utils/get-volume.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
+ */
+
+#include "dbus-types.h"
+
+#include <string>
+#include "dbus-pulse-volume.h"
+
+int main(int argc, char **argv)
+{
+ DBusTypes::registerMetaTypes();
+ if (argc == 2)
+ {
+ DBusPulseVolume volume;
+ volume.getVolume(argv[1]);
+ }
+ return 0;
+}
diff --git a/tests/integration/utils/set-volume.cpp b/tests/integration/utils/set-volume.cpp
new file mode 100644
index 0000000..d29e5ef
--- /dev/null
+++ b/tests/integration/utils/set-volume.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 Canonical, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
+ */
+
+#include "dbus-types.h"
+
+#include <string>
+#include "dbus-pulse-volume.h"
+
+int main(int argc, char **argv)
+{
+ DBusTypes::registerMetaTypes();
+ if (argc == 3)
+ {
+ DBusPulseVolume volume;
+ if(!volume.setVolume(argv[1], std::stod(argv[2])))
+ {
+ return 1;
+ }
+ }
+ return 0;
+}