From 58f10c864fd301fbe509c232488cab7b5c99a6b8 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Mon, 7 Sep 2015 15:11:19 +0200 Subject: test version for gmenuharness --- src/CMakeLists.txt | 19 +- src/gmenuharness/CMakeLists.txt | 17 + src/gmenuharness/MatchResult.cpp | 187 ++++++++ src/gmenuharness/MatchUtils.cpp | 77 +++ src/gmenuharness/MenuItemMatcher.cpp | 902 +++++++++++++++++++++++++++++++++++ src/gmenuharness/MenuMatcher.cpp | 208 ++++++++ src/volume-control-pulse.vala | 10 +- 7 files changed, 1407 insertions(+), 13 deletions(-) create mode 100644 src/gmenuharness/CMakeLists.txt create mode 100644 src/gmenuharness/MatchResult.cpp create mode 100644 src/gmenuharness/MatchUtils.cpp create mode 100644 src/gmenuharness/MenuItemMatcher.cpp create mode 100644 src/gmenuharness/MenuMatcher.cpp (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 194dfc9..4a25deb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,12 +8,12 @@ set(SYMBOLS_PATH "${CMAKE_CURRENT_BINARY_DIR}/indicator-sound-service.def") set(VAPI_PATH "${CMAKE_CURRENT_BINARY_DIR}/indicator-sound-service.vapi") vapi_gen(accounts-service - LIBRARY - accounts-service - PACKAGES - gio-2.0 - INPUT - /usr/share/gir-1.0/AccountsService-1.0.gir + LIBRARY + accounts-service + PACKAGES + gio-2.0 + INPUT + /usr/share/gir-1.0/AccountsService-1.0.gir ) vala_init(indicator-sound-service @@ -70,7 +70,7 @@ vala_add(indicator-sound-service media-player-user.vala DEPENDS media-player - accounts-service-sound-settings + accounts-service-sound-settings greeter-broadcast ) vala_add(indicator-sound-service @@ -164,8 +164,8 @@ add_definitions( ) add_library( - indicator-sound-service-lib STATIC - ${INDICATOR_SOUND_SOURCES} + indicator-sound-service-lib STATIC + ${INDICATOR_SOUND_SOURCES} ) target_link_libraries( @@ -206,3 +206,4 @@ install( RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/indicator-sound/ ) +add_subdirectory(gmenuharness) diff --git a/src/gmenuharness/CMakeLists.txt b/src/gmenuharness/CMakeLists.txt new file mode 100644 index 0000000..c9e613a --- /dev/null +++ b/src/gmenuharness/CMakeLists.txt @@ -0,0 +1,17 @@ +pkg_check_modules(UNITY_API libunity-api>=0.1.3 REQUIRED) +include_directories(${UNITY_API_INCLUDE_DIRS}) + +include_directories("${CMAKE_SOURCE_DIR}/include") + +add_library( + gmenuharness-shared SHARED + MatchResult.cpp + MatchUtils.cpp + MenuItemMatcher.cpp + MenuMatcher.cpp +) + +target_link_libraries( + gmenuharness-shared + ${GLIB_LDFLAGS} +) diff --git a/src/gmenuharness/MatchResult.cpp b/src/gmenuharness/MatchResult.cpp new file mode 100644 index 0000000..40629aa --- /dev/null +++ b/src/gmenuharness/MatchResult.cpp @@ -0,0 +1,187 @@ +/* + * 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 . + * + * Authored by: Pete Woods + */ + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace unity +{ + +namespace gmenuharness +{ + +namespace +{ + + +static void printLocation(ostream& ss, const vector& location, bool first) +{ + for (int i : location) + { + ss << " "; + if (first) + { + ss << i; + } + else + { + ss << " "; + } + } + ss << " "; +} + +struct compare_vector +{ + bool operator()(const vector& a, + const vector& b) const + { + auto p1 = a.begin(); + auto p2 = b.begin(); + + while (p1 != a.end()) + { + if (p2 == b.end()) + { + return false; + } + if (*p2 > *p1) + { + return true; + } + if (*p1 > *p2) + { + return false; + } + + ++p1; + ++p2; + } + + if (p2 != b.end()) + { + return true; + } + + return false; + } +}; +} + +struct MatchResult::Priv +{ + bool m_success = true; + + map, vector, compare_vector> m_failures; + + chrono::time_point m_timeout = chrono::system_clock::now() + chrono::seconds(10); +}; + +MatchResult::MatchResult() : + p(new Priv) +{ +} + +MatchResult::MatchResult(MatchResult&& other) +{ + *this = move(other); +} + +MatchResult::MatchResult(const MatchResult& other) : + p(new Priv) +{ + *this = other; +} + +MatchResult& MatchResult::operator=(const MatchResult& other) +{ + p->m_success = other.p->m_success; + p->m_failures= other.p->m_failures; + return *this; +} + +MatchResult& MatchResult::operator=(MatchResult&& other) +{ + p = move(other.p); + return *this; +} + +MatchResult MatchResult::createChild() const +{ + MatchResult child; + child.p->m_timeout = p->m_timeout; + return child; +} + +void MatchResult::failure(const vector& location, const string& message) +{ + p->m_success = false; + auto it = p->m_failures.find(location); + if (it == p->m_failures.end()) + { + it = p->m_failures.insert(make_pair(location, vector())).first; + } + it->second.emplace_back(message); +} + +void MatchResult::merge(const MatchResult& other) +{ + p->m_success &= other.p->m_success; + for (const auto& e : other.p->m_failures) + { + p->m_failures.insert(make_pair(e.first, e.second)); + } +} + +bool MatchResult::success() const +{ + return p->m_success; +} + +bool MatchResult::hasTimedOut() const +{ + auto now = chrono::system_clock::now(); + return (now >= p->m_timeout); +} + +string MatchResult::concat_failures() const +{ + stringstream ss; + ss << "Failed expectations:" << endl; + for (const auto& failure : p->m_failures) + { + bool first = true; + for (const string& s: failure.second) + { + printLocation(ss, failure.first, first); + first = false; + ss << s << endl; + } + } + return ss.str(); +} + +} // namespace gmenuharness + +} // namespace unity diff --git a/src/gmenuharness/MatchUtils.cpp b/src/gmenuharness/MatchUtils.cpp new file mode 100644 index 0000000..7b87a25 --- /dev/null +++ b/src/gmenuharness/MatchUtils.cpp @@ -0,0 +1,77 @@ +/* + * 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 . + * + * Authored by: Pete Woods + */ + +#include + +#include + +using namespace std; +namespace util = unity::util; + +namespace unity +{ + +namespace gmenuharness +{ + +void waitForCore (GObject * obj, const string& signalName, unsigned int timeout) { + shared_ptr loop(g_main_loop_new(nullptr, false), &g_main_loop_unref); + + /* Our two exit criteria */ + util::ResourcePtr> signal( + g_signal_connect_swapped(obj, signalName.c_str(), + G_CALLBACK(g_main_loop_quit), loop.get()), + [obj](gulong s) + { + g_signal_handler_disconnect(obj, s); + }); + + util::ResourcePtr> timer(g_timeout_add(timeout, + [](gpointer user_data) -> gboolean + { + g_main_loop_quit((GMainLoop *)user_data); + return G_SOURCE_CONTINUE; + }, + loop.get()), + &g_source_remove); + + /* Wait for sync */ + g_main_loop_run(loop.get()); +} + +void menuWaitForItems(const shared_ptr& menu, unsigned int timeout) +{ + waitForCore(G_OBJECT(menu.get()), "items-changed", timeout); +} + +void g_object_deleter(gpointer object) +{ + g_clear_object(&object); +} + +void gvariant_deleter(GVariant* varptr) +{ + if (varptr != nullptr) + { + g_variant_unref(varptr); + } +} + +} // namespace gmenuharness + +} // namespace unity diff --git a/src/gmenuharness/MenuItemMatcher.cpp b/src/gmenuharness/MenuItemMatcher.cpp new file mode 100644 index 0000000..2280ef5 --- /dev/null +++ b/src/gmenuharness/MenuItemMatcher.cpp @@ -0,0 +1,902 @@ +/* + * 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 . + * + * Authored by: Pete Woods + */ + +#include +#include +#include + +#include +#include + +using namespace std; + +namespace unity +{ + +namespace gmenuharness +{ + +namespace +{ + +enum class LinkType +{ + any, + section, + submenu +}; + +static string bool_to_string(bool value) +{ + return value? "true" : "false"; +} + +static shared_ptr get_action_group_attribute(const shared_ptr& actionGroup, const gchar* attribute) +{ + shared_ptr value( + g_action_group_get_action_state(actionGroup.get(), attribute), + &gvariant_deleter); + return value; +} + +static shared_ptr get_attribute(const shared_ptr menuItem, const gchar* attribute) +{ + shared_ptr value( + g_menu_item_get_attribute_value(menuItem.get(), attribute, nullptr), + &gvariant_deleter); + return value; +} + +static string get_string_attribute(const shared_ptr menuItem, const gchar* attribute) +{ + string result; + char* temp = nullptr; + if (g_menu_item_get_attribute(menuItem.get(), attribute, "s", &temp)) + { + result = temp; + g_free(temp); + } + return result; +} + +static pair split_action(const string& action) +{ + auto index = action.find('.'); + + if (index == string::npos) + { + return make_pair(string(), action); + } + + return make_pair(action.substr(0, index), action.substr(index + 1, action.size())); +} + +static string type_to_string(MenuItemMatcher::Type type) +{ + switch(type) + { + case MenuItemMatcher::Type::plain: + return "plain"; + case MenuItemMatcher::Type::checkbox: + return "checkbox"; + case MenuItemMatcher::Type::radio: + return "radio"; + } + + return string(); +} +} + +struct MenuItemMatcher::Priv +{ + void all(MatchResult& matchResult, const vector& location, + const shared_ptr& menu, + map>& actions) + { + int count = g_menu_model_get_n_items(menu.get()); + + if (m_items.size() != (unsigned int) count) + { + matchResult.failure( + location, + "Expected " + to_string(m_items.size()) + + " children, but found " + to_string(count)); + return; + } + + for (size_t i = 0; i < m_items.size(); ++i) + { + const auto& matcher = m_items.at(i); + matcher.match(matchResult, location, menu, actions, i); + } + } + + void startsWith(MatchResult& matchResult, const vector& location, + const shared_ptr& menu, + map>& actions) + { + int count = g_menu_model_get_n_items(menu.get()); + if (m_items.size() > (unsigned int) count) + { + matchResult.failure( + location, + "Expected at least " + to_string(m_items.size()) + + " children, but found " + to_string(count)); + return; + } + + for (size_t i = 0; i < m_items.size(); ++i) + { + const auto& matcher = m_items.at(i); + matcher.match(matchResult, location, menu, actions, i); + } + } + + void endsWith(MatchResult& matchResult, const vector& location, + const shared_ptr& menu, + map>& actions) + { + int count = g_menu_model_get_n_items(menu.get()); + if (m_items.size() > (unsigned int) count) + { + matchResult.failure( + location, + "Expected at least " + to_string(m_items.size()) + + " children, but found " + to_string(count)); + return; + } + + // match the last N items + size_t j; + for (size_t i = count - m_items.size(), j = 0; i < count && j < m_items.size(); ++i, ++j) + { + const auto& matcher = m_items.at(j); + matcher.match(matchResult, location, menu, actions, i); + } + } + + Type m_type = Type::plain; + + Mode m_mode = Mode::all; + + LinkType m_linkType = LinkType::any; + + shared_ptr m_expectedSize; + + shared_ptr m_label; + + shared_ptr m_icon; + + shared_ptr m_action; + + vector m_state_icons; + + vector>> m_attributes; + + vector>> m_pass_through_attributes; + + shared_ptr m_isToggled; + + vector m_items; + + vector>> m_activations; + + vector>> m_setActionStates; + + double m_maxDifference = 0.0; +}; + +MenuItemMatcher MenuItemMatcher::checkbox() +{ + MenuItemMatcher matcher; + matcher.type(Type::checkbox); + return matcher; +} + +MenuItemMatcher MenuItemMatcher::radio() +{ + MenuItemMatcher matcher; + matcher.type(Type::radio); + return matcher; +} + +MenuItemMatcher::MenuItemMatcher() : + p(new Priv) +{ +} + +MenuItemMatcher::~MenuItemMatcher() +{ +} + +MenuItemMatcher::MenuItemMatcher(const MenuItemMatcher& other) : + p(new Priv) +{ + *this = other; +} + +MenuItemMatcher::MenuItemMatcher(MenuItemMatcher&& other) +{ + *this = move(other); +} + +MenuItemMatcher& MenuItemMatcher::operator=(const MenuItemMatcher& other) +{ + p->m_type = other.p->m_type; + p->m_mode = other.p->m_mode; + p->m_expectedSize = other.p->m_expectedSize; + p->m_label = other.p->m_label; + p->m_icon = other.p->m_icon; + p->m_action = other.p->m_action; + p->m_state_icons = other.p->m_state_icons; + p->m_attributes = other.p->m_attributes; + p->m_pass_through_attributes = other.p->m_pass_through_attributes; + p->m_isToggled = other.p->m_isToggled; + p->m_linkType = other.p->m_linkType; + p->m_items = other.p->m_items; + p->m_activations = other.p->m_activations; + p->m_setActionStates = other.p->m_setActionStates; + p->m_maxDifference = other.p->m_maxDifference; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::operator=(MenuItemMatcher&& other) +{ + p = move(other.p); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::type(Type type) +{ + p->m_type = type; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::label(const string& label) +{ + p->m_label = make_shared(label); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::action(const string& action) +{ + p->m_action = make_shared(action); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::state_icons(const std::vector& state_icons) +{ + p->m_state_icons = state_icons; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::icon(const string& icon) +{ + p->m_icon = make_shared(icon); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::widget(const string& widget) +{ + return string_attribute("x-canonical-type", widget); +} + +MenuItemMatcher& MenuItemMatcher::pass_through_attribute(const string& actionName, const shared_ptr& value) +{ + p->m_pass_through_attributes.emplace_back(actionName, value); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::pass_through_boolean_attribute(const string& actionName, bool value) +{ + return pass_through_attribute( + actionName, + shared_ptr(g_variant_new_boolean(value), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::pass_through_string_attribute(const string& actionName, const string& value) +{ + return pass_through_attribute( + actionName, + shared_ptr(g_variant_new_string(value.c_str()), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::pass_through_double_attribute(const std::string& actionName, double value) +{ + return pass_through_attribute( + actionName, + shared_ptr(g_variant_new_double(value), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::round_doubles(double maxDifference) +{ + p->m_maxDifference = maxDifference; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::attribute(const string& name, const shared_ptr& value) +{ + p->m_attributes.emplace_back(name, value); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::boolean_attribute(const string& name, bool value) +{ + return attribute( + name, + shared_ptr(g_variant_new_boolean(value), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::string_attribute(const string& name, const string& value) +{ + return attribute( + name, + shared_ptr(g_variant_new_string(value.c_str()), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::toggled(bool isToggled) +{ + p->m_isToggled = make_shared(isToggled); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::mode(Mode mode) +{ + p->m_mode = mode; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::submenu() +{ + p->m_linkType = LinkType::submenu; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::section() +{ + p->m_linkType = LinkType::section; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::is_empty() +{ + return has_exactly(0); +} + +MenuItemMatcher& MenuItemMatcher::has_exactly(size_t children) +{ + p->m_expectedSize = make_shared(children); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::item(const MenuItemMatcher& item) +{ + p->m_items.emplace_back(item); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::item(MenuItemMatcher&& item) +{ + p->m_items.emplace_back(item); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::pass_through_activate(std::string const& action, const shared_ptr& parameter) +{ + p->m_activations.emplace_back(action, parameter); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::activate(const shared_ptr& parameter) +{ + p->m_activations.emplace_back(string(), parameter); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::set_pass_through_action_state(const std::string& action, const std::shared_ptr& state) +{ + p->m_setActionStates.emplace_back(action, state); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::set_action_state(const std::shared_ptr& state) +{ + p->m_setActionStates.emplace_back("", state); + return *this; +} + +void MenuItemMatcher::match( + MatchResult& matchResult, + const vector& parentLocation, + const shared_ptr& menu, + map>& actions, + int index) const +{ + shared_ptr menuItem(g_menu_item_new_from_model(menu.get(), index), &g_object_deleter); + + vector location(parentLocation); + location.emplace_back(index); + + string action = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ACTION); + + bool isCheckbox = false; + bool isRadio = false; + bool isToggled = false; + + pair idPair; + shared_ptr actionGroup; + shared_ptr state; + + if (!action.empty()) + { + idPair = split_action(action); + actionGroup = actions[idPair.first]; + state = shared_ptr(g_action_group_get_action_state(actionGroup.get(), + idPair.second.c_str()), + &gvariant_deleter); + auto attributeTarget = get_attribute(menuItem, G_MENU_ATTRIBUTE_TARGET); + + if (attributeTarget && state) + { + isToggled = g_variant_equal(state.get(), attributeTarget.get()); + isRadio = true; + } + else if (state + && g_variant_is_of_type(state.get(), G_VARIANT_TYPE_BOOLEAN)) + { + isToggled = g_variant_get_boolean(state.get()); + isCheckbox = true; + } + } + + Type actualType = Type::plain; + if (isCheckbox) + { + actualType = Type::checkbox; + } + else if (isRadio) + { + actualType = Type::radio; + } + + if (actualType != p->m_type) + { + matchResult.failure( + location, + "Expected " + type_to_string(p->m_type) + ", found " + + type_to_string(actualType)); + } + + string label = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_LABEL); + if (p->m_label && (*p->m_label) != label) + { + matchResult.failure( + location, + "Expected label '" + *p->m_label + "', but found '" + label + + "'"); + } + + string icon = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ICON); + if (p->m_icon && (*p->m_icon) != icon) + { + matchResult.failure( + location, + "Expected icon '" + *p->m_icon + "', but found '" + icon + "'"); + } + + if (p->m_action && (*p->m_action) != action) + { + matchResult.failure( + location, + "Expected action '" + *p->m_action + "', but found '" + action + + "'"); + } + + if (!p->m_state_icons.empty() && !state) + { + matchResult.failure( + location, + "Expected state icons but no state was found"); + } + else if (!p->m_state_icons.empty() && state && + !g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT)) + { + matchResult.failure( + location, + "Expected state icons vardict, found " + + type_to_string(actualType)); + } + else if (!p->m_state_icons.empty() && state && + g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT)) + { + std::vector actual_state_icons; + GVariantIter it; + gchar* key; + GVariant* value; + + g_variant_iter_init(&it, state.get()); + while (g_variant_iter_loop(&it, "{sv}", &key, &value)) + { + if (std::string(key) == "icon") { + auto gicon = g_icon_deserialize(value); + if (gicon && G_IS_THEMED_ICON(gicon)) + { + auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon)); + // Just take the first icon in the list (there is only ever one) + actual_state_icons.push_back(iconNames[0]); + g_object_unref(gicon); + } + } + else if (std::string(key) == "icons" && g_variant_is_of_type(value, G_VARIANT_TYPE("av"))) + { + // If we find "icons" in the map, clear any icons we may have found in "icon", + // then break from the loop as we have found all icons now. + actual_state_icons.clear(); + GVariantIter icon_it; + GVariant* icon_value; + + g_variant_iter_init(&icon_it, value); + while (g_variant_iter_loop(&icon_it, "v", &icon_value)) + { + auto gicon = g_icon_deserialize(icon_value); + if (gicon && G_IS_THEMED_ICON(gicon)) + { + auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon)); + // Just take the first icon in the list (there is only ever one) + actual_state_icons.push_back(iconNames[0]); + g_object_unref(gicon); + } + } + // We're breaking out of g_variant_iter_loop here so clean up + g_variant_unref(value); + g_free(key); + break; + } + } + + if (p->m_state_icons != actual_state_icons) + { + std::string expected_icons; + for (unsigned int i = 0; i < p->m_state_icons.size(); ++i) + { + expected_icons += i == 0 ? p->m_state_icons[i] : ", " + p->m_state_icons[i]; + } + std::string actual_icons; + for (unsigned int i = 0; i < actual_state_icons.size(); ++i) + { + actual_icons += i == 0 ? actual_state_icons[i] : ", " + actual_state_icons[i]; + } + matchResult.failure( + location, + "Expected state_icons == {" + expected_icons + + "} but found {" + actual_icons + "}"); + } + } + + for (const auto& e: p->m_pass_through_attributes) + { + string actionName = get_string_attribute(menuItem, e.first.c_str()); + if (actionName.empty()) + { + matchResult.failure( + location, + "Could not find action name '" + e.first + "'"); + } + else + { + auto passThroughIdPair = split_action(actionName); + auto actionGroup = actions[passThroughIdPair.first]; + if (actionGroup) + { + auto value = get_action_group_attribute( + actionGroup, passThroughIdPair.second.c_str()); + if (!value) + { + matchResult.failure( + location, + "Expected pass-through attribute '" + e.first + + "' was not present"); + } + else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get()))) + { + std::string expectedType = g_variant_get_type_string(e.second.get()); + std::string actualType = g_variant_get_type_string(value.get()); + matchResult.failure( + location, + "Expected pass-through attribute type '" + expectedType + + "' but found '" + actualType + "'"); + } + else if (g_variant_compare(e.second.get(), value.get())) + { + bool reportMismatch = true; + if (g_strcmp0(g_variant_get_type_string(value.get()),"d") == 0 && p->m_maxDifference) + { + auto actualDouble = g_variant_get_double(value.get()); + auto expectedDouble = g_variant_get_double(e.second.get()); + auto difference = actualDouble-expectedDouble; + if (difference < 0) difference = difference * -1.0; + if (difference <= p->m_maxDifference) + { + reportMismatch = false; + } + } + if (reportMismatch) + { + gchar* expectedString = g_variant_print(e.second.get(), true); + gchar* actualString = g_variant_print(value.get(), true); + matchResult.failure( + location, + "Expected pass-through attribute '" + e.first + + "' == " + expectedString + " but found " + + actualString); + + g_free(expectedString); + g_free(actualString); + } + } + } + else + { + matchResult.failure(location, "Could not find action group for ID '" + passThroughIdPair.first + "'"); + } + } + } + + for (const auto& e: p->m_attributes) + { + auto value = get_attribute(menuItem, e.first.c_str()); + if (!value) + { + matchResult.failure(location, + "Expected attribute '" + e.first + + "' could not be found"); + } + else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get()))) + { + std::string expectedType = g_variant_get_type_string(e.second.get()); + std::string actualType = g_variant_get_type_string(value.get()); + matchResult.failure( + location, + "Expected attribute type '" + expectedType + + "' but found '" + actualType + "'"); + } + else if (g_variant_compare(e.second.get(), value.get())) + { + gchar* expectedString = g_variant_print(e.second.get(), true); + gchar* actualString = g_variant_print(value.get(), true); + matchResult.failure( + location, + "Expected attribute '" + e.first + "' == " + expectedString + + ", but found " + actualString); + g_free(expectedString); + g_free(actualString); + } + } + + if (p->m_isToggled && (*p->m_isToggled) != isToggled) + { + matchResult.failure( + location, + "Expected toggled = " + bool_to_string(*p->m_isToggled) + + ", but found " + bool_to_string(isToggled)); + } + + if (!matchResult.success()) + { + return; + } + + if (!p->m_items.empty() || p->m_expectedSize) + { + shared_ptr link; + + switch (p->m_linkType) + { + case LinkType::any: + { + link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter); + if (!link) + { + link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter); + } + break; + } + case LinkType::submenu: + { + link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter); + break; + } + case LinkType::section: + { + link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter); + break; + } + } + + + if (!link) + { + if (p->m_expectedSize) + { + matchResult.failure( + location, + "Expected " + to_string(*p->m_expectedSize) + + " children, but found none"); + } + else + { + matchResult.failure( + location, + "Expected " + to_string(p->m_items.size()) + + " children, but found none"); + } + return; + } + else + { + while (true) + { + MatchResult childMatchResult(matchResult.createChild()); + + if (p->m_expectedSize + && *p->m_expectedSize + != (unsigned int) g_menu_model_get_n_items( + link.get())) + { + childMatchResult.failure( + location, + "Expected " + to_string(*p->m_expectedSize) + + " child items, but found " + + to_string( + g_menu_model_get_n_items( + link.get()))); + } + else if (!p->m_items.empty()) + { + switch (p->m_mode) + { + case Mode::all: + p->all(childMatchResult, location, link, actions); + break; + case Mode::starts_with: + p->startsWith(childMatchResult, location, link, actions); + break; + case Mode::ends_with: + p->endsWith(childMatchResult, location, link, actions); + break; + } + } + + if (childMatchResult.success()) + { + matchResult.merge(childMatchResult); + break; + } + else + { + if (matchResult.hasTimedOut()) + { + matchResult.merge(childMatchResult); + break; + } + menuWaitForItems(link); + } + } + } + } + + + for (const auto& a: p->m_setActionStates) + { + auto stateAction = action; + auto stateIdPair = idPair; + auto stateActionGroup = actionGroup; + if (!a.first.empty()) + { + stateAction = get_string_attribute(menuItem, a.first.c_str());; + stateIdPair = split_action(stateAction); + stateActionGroup = actions[stateIdPair.first]; + } + + if (stateAction.empty()) + { + matchResult.failure( + location, + "Tried to set action state, but no action was found"); + } + else if(!stateActionGroup) + { + matchResult.failure( + location, + "Tried to set action state for action group '" + stateIdPair.first + + "', but action group wasn't found"); + } + else if (!g_action_group_has_action(stateActionGroup.get(), stateIdPair.second.c_str())) + { + matchResult.failure( + location, + "Tried to set action state for action '" + stateAction + + "', but action was not found"); + } + else + { + g_action_group_change_action_state(stateActionGroup.get(), stateIdPair.second.c_str(), + g_variant_ref(a.second.get())); + } + + // FIXME this is a dodgy way to ensure the action state change gets dispatched + menuWaitForItems(menu, 100); + } + + for (const auto& a: p->m_activations) + { + string tmpAction = action; + auto tmpIdPair = idPair; + auto tmpActionGroup = actionGroup; + if (!a.first.empty()) + { + tmpAction = get_string_attribute(menuItem, a.first.c_str()); + tmpIdPair = split_action(tmpAction); + tmpActionGroup = actions[tmpIdPair.first]; + } + + if (tmpAction.empty()) + { + matchResult.failure( + location, + "Tried to activate action, but no action was found"); + } + else if(!tmpActionGroup) + { + matchResult.failure( + location, + "Tried to activate action group '" + tmpIdPair.first + + "', but action group wasn't found"); + } + else if (!g_action_group_has_action(tmpActionGroup.get(), tmpIdPair.second.c_str())) + { + matchResult.failure( + location, + "Tried to activate action '" + tmpAction + "', but action was not found"); + } + else + { + if (a.second) + { + g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(), + g_variant_ref(a.second.get())); + } + else + { + g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(), nullptr); + } + + // FIXME this is a dodgy way to ensure the activation gets dispatched + menuWaitForItems(menu, 100); + } + } +} + +} // namepsace gmenuharness + +} // namespace unity diff --git a/src/gmenuharness/MenuMatcher.cpp b/src/gmenuharness/MenuMatcher.cpp new file mode 100644 index 0000000..5bb4fbd --- /dev/null +++ b/src/gmenuharness/MenuMatcher.cpp @@ -0,0 +1,208 @@ +/* + * 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 . + * + * Authored by: Pete Woods + */ + +#include +#include + +#include + +#include + +using namespace std; + +namespace unity +{ + +namespace gmenuharness +{ + +namespace +{ + +static void gdbus_connection_deleter(GDBusConnection* connection) +{ +// if (!g_dbus_connection_is_closed(connection)) +// { +// g_dbus_connection_close_sync(connection, nullptr, nullptr); +// } + g_clear_object(&connection); +} +} + +struct MenuMatcher::Parameters::Priv +{ + string m_busName; + + vector> m_actions; + + string m_menuObjectPath; +}; + +MenuMatcher::Parameters::Parameters(const string& busName, + const vector>& actions, + const string& menuObjectPath) : + p(new Priv) +{ + p->m_busName = busName; + p->m_actions = actions; + p->m_menuObjectPath = menuObjectPath; +} + +MenuMatcher::Parameters::~Parameters() +{ +} + +MenuMatcher::Parameters::Parameters(const Parameters& other) : + p(new Priv) +{ + *this = other; +} + +MenuMatcher::Parameters::Parameters(Parameters&& other) +{ + *this = move(other); +} + +MenuMatcher::Parameters& MenuMatcher::Parameters::operator=(const Parameters& other) +{ + p->m_busName = other.p->m_busName; + p->m_actions = other.p->m_actions; + p->m_menuObjectPath = other.p->m_menuObjectPath; + return *this; +} + +MenuMatcher::Parameters& MenuMatcher::Parameters::operator=(Parameters&& other) +{ + p = move(other.p); + return *this; +} + +struct MenuMatcher::Priv +{ + Priv(const Parameters& parameters) : + m_parameters(parameters) + { + } + + Parameters m_parameters; + + vector m_items; + + shared_ptr m_system; + + shared_ptr m_session; + + shared_ptr m_menu; + + map> m_actions; +}; + +MenuMatcher::MenuMatcher(const Parameters& parameters) : + p(new Priv(parameters)) +{ + p->m_system.reset(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr), + &gdbus_connection_deleter); + g_dbus_connection_set_exit_on_close(p->m_system.get(), false); + + p->m_session.reset(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr), + &gdbus_connection_deleter); + g_dbus_connection_set_exit_on_close(p->m_session.get(), false); + + p->m_menu.reset( + G_MENU_MODEL( + g_dbus_menu_model_get( + p->m_session.get(), + p->m_parameters.p->m_busName.c_str(), + p->m_parameters.p->m_menuObjectPath.c_str())), + &g_object_deleter); + + for (const auto& action : p->m_parameters.p->m_actions) + { + shared_ptr actionGroup( + G_ACTION_GROUP( + g_dbus_action_group_get( + p->m_session.get(), + p->m_parameters.p->m_busName.c_str(), + action.second.c_str())), + &g_object_deleter); + p->m_actions[action.first] = actionGroup; + } +} + +MenuMatcher::~MenuMatcher() +{ +} + +MenuMatcher& MenuMatcher::item(const MenuItemMatcher& item) +{ + p->m_items.emplace_back(item); + return *this; +} + +void MenuMatcher::match(MatchResult& matchResult) const +{ + vector location; + + while (true) + { + MatchResult childMatchResult(matchResult.createChild()); + + int menuSize = g_menu_model_get_n_items(p->m_menu.get()); + if (p->m_items.size() > (unsigned int) menuSize) + { + childMatchResult.failure( + location, + "Row count mismatch, expected " + to_string(p->m_items.size()) + + " but found " + to_string(menuSize)); + } + else + { + for (size_t i = 0; i < p->m_items.size(); ++i) + { + const auto& matcher = p->m_items.at(i); + matcher.match(childMatchResult, location, p->m_menu, p->m_actions, i); + } + } + + if (childMatchResult.success()) + { + matchResult.merge(childMatchResult); + break; + } + else + { + if (matchResult.hasTimedOut()) + { + matchResult.merge(childMatchResult); + break; + } + menuWaitForItems(p->m_menu); + } + } +} + +MatchResult MenuMatcher::match() const +{ + MatchResult matchResult; + match(matchResult); + return matchResult; +} + +} // namespace gmenuharness + +} // namespace unity diff --git a/src/volume-control-pulse.vala b/src/volume-control-pulse.vala index 87af258..b60d97e 100644 --- a/src/volume-control-pulse.vala +++ b/src/volume-control-pulse.vala @@ -275,7 +275,7 @@ public class VolumeControlPulse : VolumeControl } /* We only care about signals if our internal count is zero */ - if (sig_count == 0) { + //if (sig_count == 0) { /* Extract volume and make sure it's not a side effect of us setting it */ Variant body = message.get_body (); Variant varray = body.get_child_value (0); @@ -293,7 +293,7 @@ public class VolumeControlPulse : VolumeControl vol.reason = VolumeControl.VolumeReasons.PULSE_CHANGE; this.volume = vol; } - } + //} } } @@ -478,7 +478,9 @@ public class VolumeControlPulse : VolumeControl this.context = new PulseAudio.Context (loop.get_api(), null, props); this.context.set_state_callback (context_state_callback); - if (context.connect(null, Context.Flags.NOFAIL, null) < 0) + var server_string = Environment.get_variable("PULSE_SERVER"); + warning("XGM: PULSE_SERVER=%s", server_string); + if (context.connect(server_string, Context.Flags.NOFAIL, null) < 0) warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno())); } @@ -627,7 +629,7 @@ public class VolumeControlPulse : VolumeControl } set { var volume_changed = (value.volume != _volume.volume); - debug("Setting volume to %f for profile %d because %d", value.volume, _active_sink_input, value.reason); + warning("Setting volume to %f for profile %d because %d", value.volume, _active_sink_input, value.reason); _volume = value; -- cgit v1.2.3 From 09fc613f1ce55910698aca6b8b5d909ac3ae77a0 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Fri, 18 Sep 2015 15:15:59 +0200 Subject: Added AccountsService Mock to the integration tests --- src/volume-control-pulse.vala | 3 +- tests/CMakeLists.txt | 3 +- tests/accounts-mock/AccountsDefs.h | 26 ++++++++++ tests/accounts-mock/AccountsMock.cpp | 38 ++++++++++++++ tests/accounts-mock/AccountsMock.h | 35 +++++++++++++ tests/accounts-mock/AccountsServiceSoundMock.cpp | 60 ++++++++++++++++++++++ tests/accounts-mock/AccountsServiceSoundMock.h | 44 ++++++++++++++++ tests/accounts-mock/CMakeLists.txt | 40 +++++++++++++++ .../com.ubuntu.AccountsService.Sound.Mock.xml | 6 +++ tests/accounts-mock/main.cpp | 58 +++++++++++++++++++++ .../org.freedesktop.Accounts.Mock.xml | 13 +++++ tests/dbus-types/dbus-types.h | 2 + tests/integration/CMakeLists.txt | 1 + tests/integration/indicator-sound-test-base.cpp | 27 +++++++--- tests/integration/indicator-sound-test-base.h | 3 ++ tests/integration/test-indicator.cpp | 2 + 16 files changed, 352 insertions(+), 9 deletions(-) create mode 100644 tests/accounts-mock/AccountsDefs.h create mode 100644 tests/accounts-mock/AccountsMock.cpp create mode 100644 tests/accounts-mock/AccountsMock.h create mode 100644 tests/accounts-mock/AccountsServiceSoundMock.cpp create mode 100644 tests/accounts-mock/AccountsServiceSoundMock.h create mode 100644 tests/accounts-mock/CMakeLists.txt create mode 100644 tests/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml create mode 100644 tests/accounts-mock/main.cpp create mode 100644 tests/accounts-mock/org.freedesktop.Accounts.Mock.xml (limited to 'src') diff --git a/src/volume-control-pulse.vala b/src/volume-control-pulse.vala index b60d97e..950cdaf 100644 --- a/src/volume-control-pulse.vala +++ b/src/volume-control-pulse.vala @@ -479,7 +479,6 @@ public class VolumeControlPulse : VolumeControl this.context.set_state_callback (context_state_callback); var server_string = Environment.get_variable("PULSE_SERVER"); - warning("XGM: PULSE_SERVER=%s", server_string); if (context.connect(server_string, Context.Flags.NOFAIL, null) < 0) warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno())); } @@ -629,7 +628,7 @@ public class VolumeControlPulse : VolumeControl } set { var volume_changed = (value.volume != _volume.volume); - warning("Setting volume to %f for profile %d because %d", value.volume, _active_sink_input, value.reason); + debug("Setting volume to %f for profile %d because %d", value.volume, _active_sink_input, value.reason); _volume = value; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4c28cdf..d818a2d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -280,4 +280,5 @@ add_test(indcator-test ) add_subdirectory(integration) -add_subdirectory(dbus-types) \ No newline at end of file +add_subdirectory(dbus-types) +add_subdirectory(accounts-mock) \ No newline at end of file diff --git a/tests/accounts-mock/AccountsDefs.h b/tests/accounts-mock/AccountsDefs.h new file mode 100644 index 0000000..e2b24ab --- /dev/null +++ b/tests/accounts-mock/AccountsDefs.h @@ -0,0 +1,26 @@ +/* + * 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 . + * + * Author: Xavi Garcia + */ +#pragma once + +namespace +{ + constexpr const char ACCOUNTS_SERVICE[] = "org.freedesktop.Accounts"; + constexpr const char USER_PATH[] = "/org/freedesktop/Accounts/UserTest"; + constexpr const char ACCOUNTS_PATH[] = "/org/freedesktop/Accounts"; + constexpr const char ACCOUNTS_SOUND_INTERFACE[] = "com.ubuntu.AccountsService.Sound"; +} diff --git a/tests/accounts-mock/AccountsMock.cpp b/tests/accounts-mock/AccountsMock.cpp new file mode 100644 index 0000000..72f86a6 --- /dev/null +++ b/tests/accounts-mock/AccountsMock.cpp @@ -0,0 +1,38 @@ +/* + * 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 . + * + * Author: Xavi Garcia + */ +#include + +#include "AccountsMock.h" +#include "AccountsDefs.h" + +AccountsMock::AccountsMock(QObject* parent) + : QObject(parent) +{ +} + +AccountsMock::~AccountsMock() = default; + +QDBusObjectPath AccountsMock::FindUserByName(QString const & username) const +{ + return QDBusObjectPath(USER_PATH); +} + +QDBusObjectPath AccountsMock::FindUserById(int64_t uid) const +{ + return QDBusObjectPath(USER_PATH); +} diff --git a/tests/accounts-mock/AccountsMock.h b/tests/accounts-mock/AccountsMock.h new file mode 100644 index 0000000..759443c --- /dev/null +++ b/tests/accounts-mock/AccountsMock.h @@ -0,0 +1,35 @@ +/* + * 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 . + * + * Author: Xavi Garcia + */ +#pragma once + +#include +#include +#include + +class AccountsMock : public QObject, protected QDBusContext +{ + Q_OBJECT + +public Q_SLOTS: + QDBusObjectPath FindUserByName(QString const & username) const; + QDBusObjectPath FindUserById(int64_t uid) const; + +public: + AccountsMock(QObject* parent = 0); + virtual ~AccountsMock(); +}; diff --git a/tests/accounts-mock/AccountsServiceSoundMock.cpp b/tests/accounts-mock/AccountsServiceSoundMock.cpp new file mode 100644 index 0000000..10f4f8d --- /dev/null +++ b/tests/accounts-mock/AccountsServiceSoundMock.cpp @@ -0,0 +1,60 @@ +/* + * 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 . + * + * Author: Xavi Garcia + */ +#include +#include +#include + +#include "AccountsServiceSoundMock.h" +#include "AccountsDefs.h" + +AccountsServiceSoundMock::AccountsServiceSoundMock(QObject* parent) + : QObject(parent) + , volume_(0.0) +{ +} + +AccountsServiceSoundMock::~AccountsServiceSoundMock() = default; + +double AccountsServiceSoundMock::volume() const +{ + return volume_; +} + +void AccountsServiceSoundMock::setVolume(double volume) +{ + volume_ = volume; + notifyPropertyChanged(ACCOUNTS_SOUND_INTERFACE, + USER_PATH, + "Volume"); +} + +void AccountsServiceSoundMock::notifyPropertyChanged(QString const & interface, + QString const & path, + QString const & propertyName) +{ + QDBusMessage signal = QDBusMessage::createSignal( + path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged"); + signal << interface; + QVariantMap changedProps; + changedProps.insert(propertyName, property(propertyName.toStdString().c_str())); + signal << changedProps; + signal << QStringList(); + QDBusConnection::systemBus().send(signal); +} diff --git a/tests/accounts-mock/AccountsServiceSoundMock.h b/tests/accounts-mock/AccountsServiceSoundMock.h new file mode 100644 index 0000000..2be996f --- /dev/null +++ b/tests/accounts-mock/AccountsServiceSoundMock.h @@ -0,0 +1,44 @@ +/* + * 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 . + * + * Author: Xavi Garcia + */ +#pragma once + +#include +#include + +class AccountsServiceSoundMock : public QObject, protected QDBusContext +{ + Q_OBJECT +// Q_CLASSINFO("D-Bus Interface", "test.com.ubuntu.AccountsService.Sound") + Q_PROPERTY(double Volume READ volume WRITE setVolume) + +public Q_SLOTS: + double volume() const; + void setVolume(double volume); + +public: + AccountsServiceSoundMock(QObject* parent = 0); + virtual ~AccountsServiceSoundMock(); + +protected: + void notifyPropertyChanged(QString const & interface, + QString const & path, + QString const & propertyName); + +private: + double volume_; +}; diff --git a/tests/accounts-mock/CMakeLists.txt b/tests/accounts-mock/CMakeLists.txt new file mode 100644 index 0000000..fcfab2d --- /dev/null +++ b/tests/accounts-mock/CMakeLists.txt @@ -0,0 +1,40 @@ +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +find_package(Qt5DBus REQUIRED) +include_directories(${Qt5DBus_INCLUDE_DIRS}) + +add_definitions(-DQT_NO_KEYWORDS=1) + +set(dbusinterface_sound_xml "com.ubuntu.AccountsService.Sound.Mock.xml") +set_source_files_properties(${dbusinterface_sound_xml} PROPERTIES + CLASSNAME AccountsServiceSoundMockInterface) + +qt5_add_dbus_interface(interface_files ${dbusinterface_sound_xml} accountsservice_sound_interface) + +qt5_add_dbus_adaptor(adaptor_files + com.ubuntu.AccountsService.Sound.Mock.xml + AccountsServiceSoundMock.h + AccountsServiceSoundMock + AccountsServiceSoundMockAdaptor) + +qt5_add_dbus_adaptor(adaptor_files + org.freedesktop.Accounts.Mock.xml + AccountsMock.h + AccountsMock + AccountsMockAdaptor) + +add_executable( + accounts-service-sound + ${interface_files} + ${adaptor_files} + AccountsServiceSoundMock.cpp + AccountsMock.cpp + main.cpp +) + +qt5_use_modules( + accounts-service-sound + Core + DBus +) diff --git a/tests/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml b/tests/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml new file mode 100644 index 0000000..859cd46 --- /dev/null +++ b/tests/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/accounts-mock/main.cpp b/tests/accounts-mock/main.cpp new file mode 100644 index 0000000..ca1a426 --- /dev/null +++ b/tests/accounts-mock/main.cpp @@ -0,0 +1,58 @@ +/* + * 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 . + * + * Author: Xavi Garcia + */ +#include +#include + +#include + +#include "AccountsDefs.h" +#include "AccountsServiceSoundMock.h" +#include "AccountsServiceSoundMockAdaptor.h" +#include "AccountsMock.h" +#include "AccountsMockAdaptor.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + QDBusConnection connection = QDBusConnection::systemBus(); + if (!connection.interface()->isServiceRegistered(ACCOUNTS_SERVICE)) + { + auto service = new AccountsServiceSoundMock(&app); + new SoundAdaptor(service); + + auto accounts_service = new AccountsMock(&app); + new AccountsAdaptor(accounts_service); + + if (!connection.registerService(ACCOUNTS_SERVICE)) + { + qFatal("Could not register AccountsService Volume service."); + } + + if (!connection.registerObject(USER_PATH, service)) + { + qFatal("Could not register AccountsService Volume object."); + } + + if (!connection.registerObject(ACCOUNTS_PATH, accounts_service)) + { + qFatal("Could not register Accounts object."); + } + } + qDebug() << "Service is already registered!."; + return app.exec(); +} diff --git a/tests/accounts-mock/org.freedesktop.Accounts.Mock.xml b/tests/accounts-mock/org.freedesktop.Accounts.Mock.xml new file mode 100644 index 0000000..f284d54 --- /dev/null +++ b/tests/accounts-mock/org.freedesktop.Accounts.Mock.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tests/dbus-types/dbus-types.h b/tests/dbus-types/dbus-types.h index f747458..8c7f253 100644 --- a/tests/dbus-types/dbus-types.h +++ b/tests/dbus-types/dbus-types.h @@ -32,6 +32,8 @@ namespace DBusTypes static constexpr char const* DBUS_PULSE = "org.PulseAudio1"; + static constexpr char const* ACCOUNTS_SERVICE = "org.freedesktop.Accounts"; + static constexpr char const* STREAM_RESTORE_NAME = "org.PulseAudio.Ext.StreamRestore1"; static constexpr char const* STREAM_RESTORE_PATH = "/org/pulseaudio/stream_restore1"; diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt index 2e4004a..ace155a 100644 --- a/tests/integration/CMakeLists.txt +++ b/tests/integration/CMakeLists.txt @@ -29,6 +29,7 @@ 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/accounts-mock/accounts-service-sound" -DTEST_SOUND="${CMAKE_SOURCE_DIR}/tests/integration/test-sound.wav" -DQT_NO_KEYWORDS=1 ) diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index 6e05efe..10717c7 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -112,12 +112,31 @@ void IndicatorSoundTestBase::startPulse() } } +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); - setenv("DBUS_SYSTEM_BUS_ADDRESS", dbusTestRunner.systemBus().toStdString().c_str(), true); indicator.reset( new QProcessDBusService(DBusTypes::DBUS_NAME, QDBusConnection::SessionBus, @@ -144,7 +163,7 @@ mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters() void IndicatorSoundTestBase::SetUp() { - initializeAccountsInterface(); + setenv("DBUS_SYSTEM_BUS_ADDRESS", dbusTestRunner.systemBus().toStdString().c_str(), true); } void IndicatorSoundTestBase::TearDown() @@ -216,7 +235,6 @@ bool IndicatorSoundTestBase::waitMenuChange() bool IndicatorSoundTestBase::waitVolumeChangedInIndicator() { - qDebug() << "IndicatorSoundTestBase::waitVolumeChangedInIndicator() signal " << (void *)signal_spy_volume_changed_.get(); if (signal_spy_volume_changed_) { return signal_spy_volume_changed_->wait(); @@ -229,11 +247,9 @@ void IndicatorSoundTestBase::initializeAccountsInterface() auto username = qgetenv("USER"); if (username != "") { - qDebug() << "Setting Accounts interface for user: " << username; std::unique_ptr setInterface(new AccountsInterface("org.freedesktop.Accounts", "/org/freedesktop/Accounts", QDBusConnection::systemBus(), 0)); - qDebug() << "Interface: " << setInterface.get(); QDBusReply userResp = setInterface->call(QLatin1String("FindUserByName"), QLatin1String(username)); @@ -252,7 +268,6 @@ void IndicatorSoundTestBase::initializeAccountsInterface() accounts_interface_.reset(new DBusPropertiesInterface("org.freedesktop.Accounts", userPath, soundInterface->connection(), 0)); - qDebug() << "Interface for setting volume: " << accounts_interface_.get(); if (!accounts_interface_->isValid()) { qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << accounts_interface_->lastError().message(); diff --git a/tests/integration/indicator-sound-test-base.h b/tests/integration/indicator-sound-test-base.h index 81d8204..04a579f 100644 --- a/tests/integration/indicator-sound-test-base.h +++ b/tests/integration/indicator-sound-test-base.h @@ -46,6 +46,7 @@ protected: void startIndicator(); void startPulse(); + void startAccountsService(); bool setVolume(QString const &role, double volume); @@ -73,6 +74,8 @@ protected: QtDBusTest::DBusServicePtr pulseaudio; + QtDBusTest::DBusServicePtr accountsService; + QProcess testSoundProcess; std::unique_ptr menu_interface_; diff --git a/tests/integration/test-indicator.cpp b/tests/integration/test-indicator.cpp index 8803f91..bb5adec 100644 --- a/tests/integration/test-indicator.cpp +++ b/tests/integration/test-indicator.cpp @@ -36,6 +36,7 @@ TEST_F(TestIndicator, ChangeRoleVolume) { double INITIAL_VOLUME = 0.0; + ASSERT_NO_THROW(startAccountsService()); ASSERT_NO_THROW(startPulse()); // initialize volumes in pulseaudio @@ -140,6 +141,7 @@ TEST_F(TestIndicator, BasicInitialVolume) { double INITIAL_VOLUME = 0.0; + ASSERT_NO_THROW(startAccountsService()); ASSERT_NO_THROW(startPulse()); // initialize volumes in pulseaudio -- cgit v1.2.3 From 913282e093a723b7e3a7bb899a77a068edafcd01 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Sun, 20 Sep 2015 20:16:27 +0200 Subject: Updated set-volume utility --- src/volume-control-pulse.vala | 2 +- tests/integration/utils/dbus-pulse-volume.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/volume-control-pulse.vala b/src/volume-control-pulse.vala index 950cdaf..042c1c1 100644 --- a/src/volume-control-pulse.vala +++ b/src/volume-control-pulse.vala @@ -628,7 +628,7 @@ public class VolumeControlPulse : VolumeControl } set { var volume_changed = (value.volume != _volume.volume); - debug("Setting volume to %f for profile %d because %d", value.volume, _active_sink_input, value.reason); + warning("Setting volume to %f for profile %d because %d", value.volume, _active_sink_input, value.reason); _volume = value; diff --git a/tests/integration/utils/dbus-pulse-volume.cpp b/tests/integration/utils/dbus-pulse-volume.cpp index 6989754..b42ea15 100644 --- a/tests/integration/utils/dbus-pulse-volume.cpp +++ b/tests/integration/utils/dbus-pulse-volume.cpp @@ -56,9 +56,9 @@ DBusPulseVolume::DBusPulseVolume() : qDebug() << "********************************Connetion: " << connection_string.value().toString(); -// connection_.reset(new QDBusConnection(QDBusConnection::connectToPeer(connection_string.value().toString(), "set-volume"))); + connection_.reset(new QDBusConnection(QDBusConnection::connectToPeer(connection_string.value().toString(), "set-volume"))); - connection_.reset(new QDBusConnection(QDBusConnection::connectToPeer("unix:path=/run/user/1000/pulse/dbus-socket", "set-volume"))); +// connection_.reset(new QDBusConnection(QDBusConnection::connectToPeer("unix:path=/run/user/1000/pulse/dbus-socket", "set-volume"))); qDebug() << "Is connected " << connection_->isConnected(); if (connection_->isConnected()) -- cgit v1.2.3 From 22de41f3cc382adbf06be8642af5cfa7ec664c8e Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Mon, 21 Sep 2015 16:01:33 +0200 Subject: Added separated integration tests for desktop and phone, with different instances of pulseaudio --- include/unity/gmenuharness/MenuItemMatcher.h | 4 + src/gmenuharness/MenuItemMatcher.cpp | 16 ++ tests/integration/indicator-sound-test-base.cpp | 76 +++++++++- tests/integration/indicator-sound-test-base.h | 11 +- tests/integration/test-indicator.cpp | 186 +++++++++++++++++++++--- tests/integration/utils/dbus-pulse-volume.cpp | 5 + 6 files changed, 275 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/include/unity/gmenuharness/MenuItemMatcher.h b/include/unity/gmenuharness/MenuItemMatcher.h index dc1d6f3..6f7f94c 100644 --- a/include/unity/gmenuharness/MenuItemMatcher.h +++ b/include/unity/gmenuharness/MenuItemMatcher.h @@ -93,6 +93,10 @@ public: MenuItemMatcher& string_attribute(const std::string& name, const std::string& value); + MenuItemMatcher& int32_attribute(const std::string& name, int value); + + MenuItemMatcher& double_attribute(const std::string& name, double value); + MenuItemMatcher& toggled(bool toggled); MenuItemMatcher& mode(Mode mode); diff --git a/src/gmenuharness/MenuItemMatcher.cpp b/src/gmenuharness/MenuItemMatcher.cpp index 2280ef5..ab22364 100644 --- a/src/gmenuharness/MenuItemMatcher.cpp +++ b/src/gmenuharness/MenuItemMatcher.cpp @@ -354,6 +354,22 @@ MenuItemMatcher& MenuItemMatcher::string_attribute(const string& name, const str &gvariant_deleter)); } +MenuItemMatcher& MenuItemMatcher::int32_attribute(const std::string& name, int value) +{ + return attribute( + name, + shared_ptr(g_variant_new_int32 (value), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::double_attribute(const std::string& name, double value) +{ + return attribute( + name, + shared_ptr(g_variant_new_double (value), + &gvariant_deleter)); +} + MenuItemMatcher& MenuItemMatcher::toggled(bool isToggled) { p->m_isToggled = make_shared(isToggled); diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index 49e2ec5..a839fbc 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -45,7 +45,7 @@ IndicatorSoundTestBase::~IndicatorSoundTestBase() } -bool IndicatorSoundTestBase::setVolume(QString const &role, double volume) +bool IndicatorSoundTestBase::setStreamRestoreVolume(QString const &role, double volume) { QProcess setVolume; setVolume.start(VOLUME_SET_BIN, QStringList() @@ -60,6 +60,25 @@ bool IndicatorSoundTestBase::setVolume(QString const &role, double volume) 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::startTestSound(QString const &role) { testSoundProcess.terminate(); @@ -81,7 +100,37 @@ void IndicatorSoundTestBase::stopTestSound() testSoundProcess.terminate(); } -void IndicatorSoundTestBase::startPulse() +void IndicatorSoundTestBase::startPulseDesktop() +{ + 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" + << "--load=module-null-sink" + << "--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() { try { @@ -151,8 +200,6 @@ void IndicatorSoundTestBase::startIndicator() } } -// /usr/bin/pulseaudio --start -vvvv --disable-shm=true --daemonize=false --use-pid-file=false --system=false --exit-idle-time=-1 -n "--load=module-null-sink sink_name=multimedia" --load=module-stream-restore - mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters() { return mh::MenuMatcher::Parameters( @@ -161,6 +208,14 @@ mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters() "/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"); +} + void IndicatorSoundTestBase::SetUp() { setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true); @@ -216,9 +271,22 @@ unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double return mh::MenuItemMatcher().radio() .label("Volume") .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") .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 (!menu_interface_) diff --git a/tests/integration/indicator-sound-test-base.h b/tests/integration/indicator-sound-test-base.h index 04a579f..019efc9 100644 --- a/tests/integration/indicator-sound-test-base.h +++ b/tests/integration/indicator-sound-test-base.h @@ -45,10 +45,13 @@ protected: void TearDown() override; void startIndicator(); - void startPulse(); + void startPulseDesktop(); + void startPulsePhone(); void startAccountsService(); - bool setVolume(QString const &role, double volume); + bool setStreamRestoreVolume(QString const &role, double volume); + + bool setSinkVolume(double volume); bool startTestSound(QString const &role); @@ -58,8 +61,12 @@ protected: static unity::gmenuharness::MenuMatcher::Parameters desktopParameters(); + static unity::gmenuharness::MenuMatcher::Parameters phoneParameters(); + static unity::gmenuharness::MenuItemMatcher volumeSlider(double volume); + static unity::gmenuharness::MenuItemMatcher silentModeSwitch(bool toggled); + bool waitMenuChange(); bool waitVolumeChangedInIndicator(); diff --git a/tests/integration/test-indicator.cpp b/tests/integration/test-indicator.cpp index bb5adec..a248658 100644 --- a/tests/integration/test-indicator.cpp +++ b/tests/integration/test-indicator.cpp @@ -32,16 +32,15 @@ class TestIndicator: public IndicatorSoundTestBase { }; -TEST_F(TestIndicator, ChangeRoleVolume) +TEST_F(TestIndicator, PhoneChangeRoleVolume) { double INITIAL_VOLUME = 0.0; ASSERT_NO_THROW(startAccountsService()); - ASSERT_NO_THROW(startPulse()); + ASSERT_NO_THROW(startPulsePhone()); // initialize volumes in pulseaudio - EXPECT_TRUE(setVolume("mutimedia", INITIAL_VOLUME)); - EXPECT_TRUE(setVolume("alert", INITIAL_VOLUME)); + EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); // start now the indicator, so it picks the new volumes ASSERT_NO_THROW(startIndicator()); @@ -53,7 +52,7 @@ TEST_F(TestIndicator, ChangeRoleVolume) double randomVolume = randInt / 100.0; // set an initial volume to the alert role - setVolume("alert", 1.0); + setStreamRestoreVolume("alert", 1.0); EXPECT_TRUE(waitVolumeChangedInIndicator()); // play a test sound, it should change the role in the indicator @@ -61,31 +60,31 @@ TEST_F(TestIndicator, ChangeRoleVolume) EXPECT_TRUE(waitVolumeChangedInIndicator()); // set the random volume to the multimedia role - EXPECT_TRUE(setVolume("multimedia", randomVolume)); + EXPECT_TRUE(setStreamRestoreVolume("multimedia", randomVolume)); if (randomVolume != INITIAL_VOLUME) { EXPECT_TRUE(waitVolumeChangedInIndicator()); } // check the indicator - EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) + 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(mh::MenuItemMatcher().checkbox() - .label("Mute") - ) + .item(silentModeSwitch(false)) .item(volumeSlider(randomVolume)) ) ).match()); // check that the last item is Sound Settings - EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) .item(mh::MenuItemMatcher() .action("indicator.root") .string_attribute("x-canonical-type", "com.canonical.indicator.root") @@ -94,6 +93,7 @@ TEST_F(TestIndicator, ChangeRoleVolume) .submenu() .item(mh::MenuItemMatcher() .label("Sound Settings…") + .action("indicator.phone-settings") ) ).match()); @@ -106,6 +106,97 @@ TEST_F(TestIndicator, ChangeRoleVolume) EXPECT_TRUE(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(1.0)) + ) + ).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()); + 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 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(INITIAL_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, DesktopBasicInitialVolume) +{ + double INITIAL_VOLUME = 0.0; + + ASSERT_NO_THROW(startAccountsService()); + ASSERT_NO_THROW(startPulseDesktop()); + + // initialize volumes in pulseaudio + 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()); + // check the initial volume for the alert role EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) .item(mh::MenuItemMatcher() @@ -119,7 +210,7 @@ TEST_F(TestIndicator, ChangeRoleVolume) .item(mh::MenuItemMatcher().checkbox() .label("Mute") ) - .item(volumeSlider(1.0)) + .item(volumeSlider(INITIAL_VOLUME)) ) ).match()); @@ -137,20 +228,42 @@ TEST_F(TestIndicator, ChangeRoleVolume) ).match()); } -TEST_F(TestIndicator, BasicInitialVolume) +TEST_F(TestIndicator, DesktopChangeRoleVolume) { double INITIAL_VOLUME = 0.0; ASSERT_NO_THROW(startAccountsService()); - ASSERT_NO_THROW(startPulse()); + ASSERT_NO_THROW(startPulseDesktop()); // initialize volumes in pulseaudio - EXPECT_TRUE(setVolume("alert", INITIAL_VOLUME)); + // 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()); - // check the initial volume for the alert role + // 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_FALSE(waitVolumeChangedInIndicator()); + } + + // check the indicator EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) .item(mh::MenuItemMatcher() .action("indicator.root") @@ -163,7 +276,7 @@ TEST_F(TestIndicator, BasicInitialVolume) .item(mh::MenuItemMatcher().checkbox() .label("Mute") ) - .item(volumeSlider(INITIAL_VOLUME)) + .item(volumeSlider(randomVolume)) ) ).match()); @@ -179,6 +292,45 @@ TEST_F(TestIndicator, BasicInitialVolume) .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)) + ) + ).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()); } } // namespace diff --git a/tests/integration/utils/dbus-pulse-volume.cpp b/tests/integration/utils/dbus-pulse-volume.cpp index a282f77..c8b6ae6 100644 --- a/tests/integration/utils/dbus-pulse-volume.cpp +++ b/tests/integration/utils/dbus-pulse-volume.cpp @@ -150,6 +150,11 @@ bool DBusPulseVolume::setVolume(QString const & role, double volume) } } } + else + { + qWarning() << "SetVolume::setVolume(): role " << role << " was not found."; + return false; + } return true; } -- cgit v1.2.3 From b9ef4f6abdefb7b1d31d34f35b742016f1b5b87f Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Tue, 22 Sep 2015 11:52:43 +0200 Subject: Added themed_icon to integration tests --- include/unity/gmenuharness/MenuItemMatcher.h | 4 ++ src/gmenuharness/MenuItemMatcher.cpp | 70 +++++++++++++++++++++++++ tests/integration/indicator-sound-test-base.cpp | 2 + 3 files changed, 76 insertions(+) (limited to 'src') diff --git a/include/unity/gmenuharness/MenuItemMatcher.h b/include/unity/gmenuharness/MenuItemMatcher.h index 6f7f94c..8f6deaf 100644 --- a/include/unity/gmenuharness/MenuItemMatcher.h +++ b/include/unity/gmenuharness/MenuItemMatcher.h @@ -75,6 +75,8 @@ public: MenuItemMatcher& icon(const std::string& icon); + MenuItemMatcher& themed_icon(const std::string& iconName, const std::vector& icons); + MenuItemMatcher& widget(const std::string& widget); MenuItemMatcher& pass_through_attribute(const std::string& actionName, const std::shared_ptr& value); @@ -95,6 +97,8 @@ public: MenuItemMatcher& int32_attribute(const std::string& name, int value); + MenuItemMatcher& int64_attribute(const std::string& name, int value); + MenuItemMatcher& double_attribute(const std::string& name, double value); MenuItemMatcher& toggled(bool toggled); diff --git a/src/gmenuharness/MenuItemMatcher.cpp b/src/gmenuharness/MenuItemMatcher.cpp index ab22364..dae9e7d 100644 --- a/src/gmenuharness/MenuItemMatcher.cpp +++ b/src/gmenuharness/MenuItemMatcher.cpp @@ -22,6 +22,7 @@ #include #include +#include using namespace std; @@ -182,6 +183,8 @@ struct MenuItemMatcher::Priv shared_ptr m_icon; + map, vector> m_themed_icons; + shared_ptr m_action; vector m_state_icons; @@ -242,6 +245,7 @@ MenuItemMatcher& MenuItemMatcher::operator=(const MenuItemMatcher& other) p->m_expectedSize = other.p->m_expectedSize; p->m_label = other.p->m_label; p->m_icon = other.p->m_icon; + p->m_themed_icons = other.p->m_themed_icons; p->m_action = other.p->m_action; p->m_state_icons = other.p->m_state_icons; p->m_attributes = other.p->m_attributes; @@ -291,6 +295,12 @@ MenuItemMatcher& MenuItemMatcher::icon(const string& icon) return *this; } +MenuItemMatcher& MenuItemMatcher::themed_icon(const std::string& iconName, const std::vector& icons) +{ + p->m_themed_icons[make_shared(iconName)] = icons; + return *this; +} + MenuItemMatcher& MenuItemMatcher::widget(const string& widget) { return string_attribute("x-canonical-type", widget); @@ -362,6 +372,14 @@ MenuItemMatcher& MenuItemMatcher::int32_attribute(const std::string& name, int v &gvariant_deleter)); } +MenuItemMatcher& MenuItemMatcher::int64_attribute(const std::string& name, int value) +{ + return attribute( + name, + shared_ptr(g_variant_new_int64 (value), + &gvariant_deleter)); +} + MenuItemMatcher& MenuItemMatcher::double_attribute(const std::string& name, double value) { return attribute( @@ -503,6 +521,58 @@ void MenuItemMatcher::match( + type_to_string(actualType)); } + // check themed icons + map, vector>::iterator iter; + for (iter = p->m_themed_icons.begin(); iter != p->m_themed_icons.end(); ++iter) + { + auto icon_val = g_menu_item_get_attribute_value(menuItem.get(), (*iter).first->c_str(), nullptr); + if (!icon_val) + { + matchResult.failure( + location, + "Expected themed icon " + (*(*iter).first) + " was not found"); + } + + auto gicon = g_icon_deserialize(icon_val); + if (!gicon || !G_IS_THEMED_ICON(gicon)) + { + matchResult.failure( + location, + "Expected attribute " + (*(*iter).first) + " is not a themed icon"); + } + auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon)); + int nb_icons = 0; + while(iconNames[nb_icons]) + { + ++nb_icons; + } + + if (nb_icons != (*iter).second.size()) + { + matchResult.failure( + location, + "Expected " + to_string((*iter).second.size()) + + " icons for themed icon [" + (*(*iter).first) + + "], but " + to_string(nb_icons) + " were found."); + } + else + { + // now compare all the icons + for (int i = 0; i < nb_icons; ++i) + { + if (string(iconNames[i]) != (*iter).second[i]) + { + matchResult.failure( + location, + "Icon at position " + to_string(i) + + " for themed icon [" + (*(*iter).first) + + "], mismatchs. Expected: " + iconNames[i] + " but found " + (*iter).second[i]); + } + } + } + g_object_unref(gicon); + } + string label = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_LABEL); if (p->m_label && (*p->m_label) != label) { diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index a839fbc..bbede82 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -276,6 +276,8 @@ unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double .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); } -- cgit v1.2.3 From 5d88ce9836c1d32abfd369e650354ed855ec4654 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Fri, 25 Sep 2015 10:31:24 +0200 Subject: Added attribute_not_set to gmenuharness, added tests for player control buttons --- include/unity/gmenuharness/MenuItemMatcher.h | 2 + src/gmenuharness/MenuItemMatcher.cpp | 20 +++ src/media-player-mpris.vala | 2 +- tests/integration/CMakeLists.txt | 1 + tests/integration/indicator-sound-test-base.cpp | 19 ++ tests/integration/indicator-sound-test-base.h | 2 + tests/integration/test-indicator.cpp | 228 +++++++++++++++++++++++- 7 files changed, 268 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/include/unity/gmenuharness/MenuItemMatcher.h b/include/unity/gmenuharness/MenuItemMatcher.h index 8f6deaf..38a5187 100644 --- a/include/unity/gmenuharness/MenuItemMatcher.h +++ b/include/unity/gmenuharness/MenuItemMatcher.h @@ -101,6 +101,8 @@ public: MenuItemMatcher& double_attribute(const std::string& name, double value); + MenuItemMatcher& attribute_not_set(const std::string& name); + MenuItemMatcher& toggled(bool toggled); MenuItemMatcher& mode(Mode mode); diff --git a/src/gmenuharness/MenuItemMatcher.cpp b/src/gmenuharness/MenuItemMatcher.cpp index dae9e7d..f39acef 100644 --- a/src/gmenuharness/MenuItemMatcher.cpp +++ b/src/gmenuharness/MenuItemMatcher.cpp @@ -191,6 +191,8 @@ struct MenuItemMatcher::Priv vector>> m_attributes; + vector m_not_exist_attributes; + vector>> m_pass_through_attributes; shared_ptr m_isToggled; @@ -249,6 +251,7 @@ MenuItemMatcher& MenuItemMatcher::operator=(const MenuItemMatcher& other) p->m_action = other.p->m_action; p->m_state_icons = other.p->m_state_icons; p->m_attributes = other.p->m_attributes; + p->m_not_exist_attributes = other.p->m_not_exist_attributes; p->m_pass_through_attributes = other.p->m_pass_through_attributes; p->m_isToggled = other.p->m_isToggled; p->m_linkType = other.p->m_linkType; @@ -388,6 +391,12 @@ MenuItemMatcher& MenuItemMatcher::double_attribute(const std::string& name, doub &gvariant_deleter)); } +MenuItemMatcher& MenuItemMatcher::attribute_not_set(const std::string& name) +{ + p->m_not_exist_attributes.emplace_back (name); + return *this; +} + MenuItemMatcher& MenuItemMatcher::toggled(bool isToggled) { p->m_isToggled = make_shared(isToggled); @@ -779,6 +788,17 @@ void MenuItemMatcher::match( } } + for (const auto& e: p->m_not_exist_attributes) + { + auto value = get_attribute(menuItem, e.c_str()); + if (value) + { + matchResult.failure(location, + "Not expected attribute '" + e + + "' was found"); + } + } + if (p->m_isToggled && (*p->m_isToggled) != isToggled) { matchResult.failure( diff --git a/src/media-player-mpris.vala b/src/media-player-mpris.vala index 8bc2884..b9961f5 100644 --- a/src/media-player-mpris.vala +++ b/src/media-player-mpris.vala @@ -290,7 +290,7 @@ public class MediaPlayerMpris: MediaPlayer { if (changed_properties.lookup ("PlaybackStatus", "s", null)) { this.state = this.proxy.PlaybackStatus != null ? this.proxy.PlaybackStatus : "Unknown"; } - if (changed_properties.lookup ("CanGoNext", "b", null) || changed_properties.lookup ("CanGoPrev", "b", null)) { + if (changed_properties.lookup ("CanGoNext", "b", null) || changed_properties.lookup ("CanGoPrevious", "b", null)) { this.playbackstatus_changed (); } diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt index 5798f14..939e329 100644 --- a/tests/integration/CMakeLists.txt +++ b/tests/integration/CMakeLists.txt @@ -31,6 +31,7 @@ add_definitions(-DSOUND_SERVICE_BIN="${CMAKE_BINARY_DIR}/src/indicator-sound-ser -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}" diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index 2b6b3d7..16f0bd6 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -109,6 +109,25 @@ bool IndicatorSoundTestBase::startTestMprisPlayer(QString const& playerName) 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(); diff --git a/tests/integration/indicator-sound-test-base.h b/tests/integration/indicator-sound-test-base.h index 741580b..00ccf1a 100644 --- a/tests/integration/indicator-sound-test-base.h +++ b/tests/integration/indicator-sound-test-base.h @@ -53,6 +53,8 @@ protected: 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); diff --git a/tests/integration/test-indicator.cpp b/tests/integration/test-indicator.cpp index ca3d298..82f06a4 100644 --- a/tests/integration/test-indicator.cpp +++ b/tests/integration/test-indicator.cpp @@ -166,10 +166,41 @@ TEST_F(TestIndicator, PhoneBasicInitialVolume) .item(silentModeSwitch(false)) .item(volumeSlider(INITIAL_VOLUME)) ) -// .item(mh::MenuItemMatcher() -// .section() -// .action("indicator.testplayer1.desktop") -// ) + .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)) + ) .item(mh::MenuItemMatcher() .label("Sound Settings…") .action("indicator.phone-settings") @@ -189,9 +220,66 @@ TEST_F(TestIndicator, DesktopBasicInitialVolume) 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)) + ) + .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") @@ -206,13 +294,28 @@ TEST_F(TestIndicator, DesktopBasicInitialVolume) ) .item(volumeSlider(INITIAL_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, DesktopAddPlayer) +TEST_F(TestIndicator, DesktopMprisPlayerButtonsState) { double INITIAL_VOLUME = 0.0; @@ -264,6 +367,121 @@ TEST_F(TestIndicator, DesktopAddPlayer) .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)) + ) + .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)) + ) + .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)) + ) + .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) -- cgit v1.2.3 From a7fd579fd1bc6eb02433ccd72e9f54dca2615675 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Tue, 29 Sep 2015 14:13:45 +0200 Subject: Adding the code to change the icons and label when a bluetooth headset is connected --- src/service.vala | 93 +++++++++++++++++++++++++++++++------------ src/sound-menu.vala | 12 ++++++ src/volume-control-pulse.vala | 49 +++++++++++++++++------ src/volume-control.vala | 3 ++ 4 files changed, 119 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/service.vala b/src/service.vala index a08edf3..6215c85 100644 --- a/src/service.vala +++ b/src/service.vala @@ -52,6 +52,7 @@ public class IndicatorSound.Service: Object { this.notify["visible"].connect ( () => this.update_root_icon () ); this.volume_control = volume; + this.volume_control.bluetooth_headset_status_changed.connect (this.update_root_icon); this.accounts_service = accounts; /* If we're on the greeter, don't export */ @@ -90,6 +91,10 @@ public class IndicatorSound.Service: Object { this.volume_control.bind_property ("high-volume", menu, "show-high-volume-warning", BindingFlags.SYNC_CREATE); }); + this.menus.@foreach ( (profile, menu) => { + this.volume_control.bluetooth_headset_status_changed.connect (menu.update_volume_slider); + }); + this.sync_preferred_players (); this.settings.changed["interested-media-players"].connect ( () => { this.sync_preferred_players (); @@ -245,17 +250,7 @@ public class IndicatorSound.Service: Object { void update_root_icon () { double volume = this.volume_control.volume.volume; - string icon; - if (this.volume_control.mute || volume <= 0.0) - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; - else if (this.accounts_service != null && this.accounts_service.silentMode) - icon = "audio-volume-muted-panel"; - else if (volume <= 0.3) - icon = "audio-volume-low-panel"; - else if (volume <= 0.7) - icon = "audio-volume-medium-panel"; - else - icon = "audio-volume-high-panel"; + string icon = get_volume_root_icon (volume, this.volume_control.mute, volume_control.active_bluetooth_headphone); string accessible_name; if (this.volume_control.mute) { @@ -282,6 +277,66 @@ public class IndicatorSound.Service: Object { private bool notify_server_supports_sync = false; private bool block_info_notifications = false; + private string get_volume_notification_icon (double volume, bool loud, bool is_bluetooth_headset_active) { + string icon = ""; + if (is_bluetooth_headset_active) { + if (loud) { + icon = "audio-volume-high"; + } else { + 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"; + } + } else { + if (loud) { + icon = "audio-volume-high"; + } else { + 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"; + } + } + return icon; + } + + private string get_volume_root_icon (double volume, bool mute, bool is_bluetooth_headset_active) { + string icon; + if (is_bluetooth_headset_active) { + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + } else { + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + } + return icon; + } + private void update_notification () { if (!notify_server_caps_checked) { @@ -328,21 +383,7 @@ public class IndicatorSound.Service: Object { : ""; /* Choose an icon */ - unowned string icon; - if (loud) { - icon = "audio-volume-high"; - } else { - var volume = volume_control.volume.volume; - - 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"; - } + string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_bluetooth_headphone); /* Reset the notification */ var n = this.info_notification; diff --git a/src/sound-menu.vala b/src/sound-menu.vala index 8718162..8f63d69 100644 --- a/src/sound-menu.vala +++ b/src/sound-menu.vala @@ -71,6 +71,7 @@ public class SoundMenu: Object this.notify_handlers = new HashTable (direct_hash, direct_equal); this.greeter_players = (flags & DisplayFlags.GREETER_PLAYERS) != 0; + } ~SoundMenu () { @@ -185,6 +186,17 @@ public class SoundMenu: Object this.notify_handlers.remove (player); } + public void update_volume_slider (bool bluetooth_headset_active) { + int index = find_action (this.volume_section, "indicator.volume"); + if (index != -1) { + string label = bluetooth_headset_active ? "Volume (Bluetooth)" : "Volume"; + this.volume_section.remove (index); + this.volume_section.insert_item (index, this.create_slider_menu_item (_(label), "indicator.volume(0)", 0.0, 1.0, 0.01, + "audio-volume-low-zero-panel", + "audio-volume-high-panel")); + } + } + public Menu root; public Menu menu; Menu volume_section; diff --git a/src/volume-control-pulse.vala b/src/volume-control-pulse.vala index 87af258..9edffb7 100644 --- a/src/volume-control-pulse.vala +++ b/src/volume-control-pulse.vala @@ -87,6 +87,7 @@ public class VolumeControlPulse : VolumeControl private bool _send_next_local_volume = false; private double _account_service_volume = 0.0; private bool _active_port_headphone = false; + private bool _active_port_headphone_bluetooth = false; /** true when connected to the pulse server */ public override bool ready { get; private set; } @@ -201,18 +202,34 @@ public class VolumeControlPulse : VolumeControl this.notify_property ("is-playing"); } - /* Check if the current active port is headset/headphone */ - /* There is not easy way to check if the port is a headset/headphone besides - * checking for the port name. On touch (with the pulseaudio droid element) - * the headset/headphone port is called 'output-headset' and 'output-headphone'. - * On the desktop this is usually called 'analog-output-headphones' */ - if (i.active_port != null && - (i.active_port.name == "output-wired_headset" || - i.active_port.name == "output-wired_headphone" || - i.active_port.name == "analog-output-headphones")) { - _active_port_headphone = true; - } else { - _active_port_headphone = false; + // store the current status of the bluetooth headset + // if it changes we'll emit a signal + bool active_port_headphone_bluetooth_before = _active_port_headphone_bluetooth; + + /* Check if the current active port is headset/headphone */ + /* There is not easy way to check if the port is a headset/headphone besides + * checking for the port name. On touch (with the pulseaudio droid element) + * the headset/headphone port is called 'output-headset' and 'output-headphone'. + * On the desktop this is usually called 'analog-output-headphones' */ + if (i.active_port != null && + (i.active_port.name.contains("headset") || + i.active_port.name.contains("headphone"))) { + _active_port_headphone = true; + // check if it's a bluetooth device + var device_bus = i.proplist.gets ("device.bus"); + if (device_bus != null && device_bus == "bluetooth") { + _active_port_headphone_bluetooth = true; + + } else { + _active_port_headphone_bluetooth = false; + } + } else { + _active_port_headphone = false; + _active_port_headphone_bluetooth = false; + } + + if (_active_port_headphone_bluetooth != active_port_headphone_bluetooth_before) { + this.bluetooth_headset_status_changed (_active_port_headphone_bluetooth); } if (_pulse_use_stream_restore == false && @@ -535,6 +552,14 @@ public class VolumeControlPulse : VolumeControl } } + public override bool active_bluetooth_headphone + { + get + { + return this._active_port_headphone_bluetooth; + } + } + /* Volume operations */ private static PulseAudio.Volume double_to_volume (double vol) { diff --git a/src/volume-control.vala b/src/volume-control.vala index 6efac35..4ef2c3e 100644 --- a/src/volume-control.vala +++ b/src/volume-control.vala @@ -39,6 +39,7 @@ public abstract class VolumeControl : Object public virtual bool high_volume { get { return false; } protected set { } } public virtual bool mute { get { return false; } } public virtual bool is_playing { get { return false; } } + public virtual bool active_bluetooth_headphone { get { return false; } } private Volume _volume; public virtual Volume volume { get { return _volume; } set { } } public virtual double mic_volume { get { return 0.0; } set { } } @@ -56,4 +57,6 @@ public abstract class VolumeControl : Object v.reason = reason; this.volume = v; } + + public signal void bluetooth_headset_status_changed (bool bluetooth_headset_active); } -- cgit v1.2.3 From 33a73fad65c52fc324ad3d35a6d1305a1489958d Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Thu, 1 Oct 2015 09:55:22 +0200 Subject: Changed to show a notification when the active output changes --- src/CMakeLists.txt | 1 + src/service.vala | 109 +++++++++++++++++++++++++++--------------- src/sound-menu.vala | 15 +++++- src/volume-control-pulse.vala | 76 ++++++++++++++++------------- src/volume-control.vala | 10 +++- 5 files changed, 135 insertions(+), 76 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 194dfc9..a0f458d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -103,6 +103,7 @@ vala_add(indicator-sound-service sound-menu.vala DEPENDS media-player + volume-control ) vala_add(indicator-sound-service accounts-service-user.vala diff --git a/src/service.vala b/src/service.vala index 6215c85..a1850d0 100644 --- a/src/service.vala +++ b/src/service.vala @@ -52,7 +52,8 @@ public class IndicatorSound.Service: Object { this.notify["visible"].connect ( () => this.update_root_icon () ); this.volume_control = volume; - this.volume_control.bluetooth_headset_status_changed.connect (this.update_root_icon); + this.volume_control.active_output_changed.connect (this.update_root_icon); + this.volume_control.active_output_changed.connect (this.update_notification); this.accounts_service = accounts; /* If we're on the greeter, don't export */ @@ -92,7 +93,7 @@ public class IndicatorSound.Service: Object { }); this.menus.@foreach ( (profile, menu) => { - this.volume_control.bluetooth_headset_status_changed.connect (menu.update_volume_slider); + this.volume_control.active_output_changed.connect (menu.update_volume_slider); }); this.sync_preferred_players (); @@ -250,7 +251,7 @@ public class IndicatorSound.Service: Object { void update_root_icon () { double volume = this.volume_control.volume.volume; - string icon = get_volume_root_icon (volume, this.volume_control.mute, volume_control.active_bluetooth_headphone); + string icon = get_volume_root_icon (volume, this.volume_control.mute, volume_control.active_output); string accessible_name; if (this.volume_control.mute) { @@ -277,12 +278,12 @@ public class IndicatorSound.Service: Object { private bool notify_server_supports_sync = false; private bool block_info_notifications = false; - private string get_volume_notification_icon (double volume, bool loud, bool is_bluetooth_headset_active) { + private string get_volume_icon (double volume, VolumeControl.ActiveOutput active_output) + { string icon = ""; - if (is_bluetooth_headset_active) { - if (loud) { - icon = "audio-volume-high"; - } else { + switch (active_output) + { + case VolumeControl.ActiveOutput.SPEAKERS: if (volume <= 0.0) icon = "audio-volume-muted"; else if (volume <= 0.3) @@ -291,11 +292,18 @@ public class IndicatorSound.Service: Object { icon = "audio-volume-medium"; else icon = "audio-volume-high"; - } - } else { - if (loud) { - icon = "audio-volume-high"; - } else { + break; + case VolumeControl.ActiveOutput.HEADPHONES: + 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"; + break; + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: if (volume <= 0.0) icon = "audio-volume-muted"; else if (volume <= 0.3) @@ -304,35 +312,60 @@ public class IndicatorSound.Service: Object { icon = "audio-volume-medium"; else icon = "audio-volume-high"; + break; + } + return icon; + } + + private string get_volume_notification_icon (double volume, bool loud, VolumeControl.ActiveOutput active_output) { + string icon = ""; + if (loud) { + switch (active_output) + { + case VolumeControl.ActiveOutput.SPEAKERS: + icon = "audio-volume-high"; + break; + case VolumeControl.ActiveOutput.HEADPHONES: + icon = "audio-volume-high"; + break; + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: + icon = "audio-volume-high"; + break; } + } else { + icon = get_volume_icon (volume, active_output); } return icon; } - private string get_volume_root_icon (double volume, bool mute, bool is_bluetooth_headset_active) { - string icon; - if (is_bluetooth_headset_active) { - if (mute || volume <= 0.0) - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; - else if (this.accounts_service != null && this.accounts_service.silentMode) - icon = "audio-volume-muted-panel"; - else if (volume <= 0.3) - icon = "audio-volume-low-panel"; - else if (volume <= 0.7) - icon = "audio-volume-medium-panel"; - else - icon = "audio-volume-high-panel"; - } else { - if (mute || volume <= 0.0) - icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; - else if (this.accounts_service != null && this.accounts_service.silentMode) - icon = "audio-volume-muted-panel"; - else if (volume <= 0.3) - icon = "audio-volume-low-panel"; - else if (volume <= 0.7) - icon = "audio-volume-medium-panel"; - else - icon = "audio-volume-high-panel"; + private string get_volume_root_icon (double volume, bool mute, VolumeControl.ActiveOutput active_output) { + string icon = ""; + switch (active_output) + { + case VolumeControl.ActiveOutput.SPEAKERS: + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else + icon = get_volume_icon (volume, active_output); + break; + case VolumeControl.ActiveOutput.HEADPHONES: + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else + icon = get_volume_icon (volume, active_output); + break; + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else + icon = get_volume_icon (volume, active_output); + break; } return icon; } @@ -383,7 +416,7 @@ public class IndicatorSound.Service: Object { : ""; /* Choose an icon */ - string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_bluetooth_headphone); + string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_output); /* Reset the notification */ var n = this.info_notification; diff --git a/src/sound-menu.vala b/src/sound-menu.vala index 8f63d69..b4e3e2a 100644 --- a/src/sound-menu.vala +++ b/src/sound-menu.vala @@ -186,10 +186,21 @@ public class SoundMenu: Object this.notify_handlers.remove (player); } - public void update_volume_slider (bool bluetooth_headset_active) { + public void update_volume_slider (VolumeControl.ActiveOutput active_output) { int index = find_action (this.volume_section, "indicator.volume"); if (index != -1) { - string label = bluetooth_headset_active ? "Volume (Bluetooth)" : "Volume"; + string label = "Volume"; + switch (active_output) { + case VolumeControl.ActiveOutput.SPEAKERS: + label = "Volume"; + break; + case VolumeControl.ActiveOutput.HEADPHONES: + label = "Volume (Headphones)"; + break; + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: + label = "Volume (Bluetooth)"; + break; + } this.volume_section.remove (index); this.volume_section.insert_item (index, this.create_slider_menu_item (_(label), "indicator.volume(0)", 0.0, 1.0, 0.01, "audio-volume-low-zero-panel", diff --git a/src/volume-control-pulse.vala b/src/volume-control-pulse.vala index 9edffb7..54fa18a 100644 --- a/src/volume-control-pulse.vala +++ b/src/volume-control-pulse.vala @@ -202,34 +202,38 @@ public class VolumeControlPulse : VolumeControl this.notify_property ("is-playing"); } - // store the current status of the bluetooth headset - // if it changes we'll emit a signal - bool active_port_headphone_bluetooth_before = _active_port_headphone_bluetooth; - - /* Check if the current active port is headset/headphone */ - /* There is not easy way to check if the port is a headset/headphone besides - * checking for the port name. On touch (with the pulseaudio droid element) - * the headset/headphone port is called 'output-headset' and 'output-headphone'. - * On the desktop this is usually called 'analog-output-headphones' */ - if (i.active_port != null && - (i.active_port.name.contains("headset") || - i.active_port.name.contains("headphone"))) { - _active_port_headphone = true; - // check if it's a bluetooth device - var device_bus = i.proplist.gets ("device.bus"); - if (device_bus != null && device_bus == "bluetooth") { - _active_port_headphone_bluetooth = true; - - } else { - _active_port_headphone_bluetooth = false; - } - } else { - _active_port_headphone = false; - _active_port_headphone_bluetooth = false; - } - - if (_active_port_headphone_bluetooth != active_port_headphone_bluetooth_before) { - this.bluetooth_headset_status_changed (_active_port_headphone_bluetooth); + // store the current status of the active output + VolumeControl.ActiveOutput active_output_before = active_output; + + /* Check if the current active port is headset/headphone */ + /* There is not easy way to check if the port is a headset/headphone besides + * checking for the port name. On touch (with the pulseaudio droid element) + * the headset/headphone port is called 'output-headset' and 'output-headphone'. + * On the desktop this is usually called 'analog-output-headphones' */ + if (i.active_port != null && + (i.active_port.name.contains("headset") || + i.active_port.name.contains("headphone"))) { + _active_port_headphone = true; + // check if it's a bluetooth device + var device_bus = i.proplist.gets ("device.bus"); + if (device_bus != null && device_bus == "bluetooth") { + _active_port_headphone_bluetooth = true; + + } else { + _active_port_headphone_bluetooth = false; + } + } else { + _active_port_headphone = false; + _active_port_headphone_bluetooth = false; + } + + VolumeControl.ActiveOutput active_output_now = active_output; + if (active_output_now != active_output_before) { + this.active_output_changed (active_output_now); + if (active_output_now == VolumeControl.ActiveOutput.SPEAKERS) { + _high_volume_approved = false; + } + update_high_volume(); } if (_pulse_use_stream_restore == false && @@ -552,12 +556,16 @@ public class VolumeControlPulse : VolumeControl } } - public override bool active_bluetooth_headphone - { - get - { - return this._active_port_headphone_bluetooth; - } + public override VolumeControl.ActiveOutput active_output + { + get + { + if (_active_port_headphone_bluetooth) + return VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES; + if (_active_port_headphone) + return VolumeControl.ActiveOutput.HEADPHONES; + return VolumeControl.ActiveOutput.SPEAKERS; + } } /* Volume operations */ diff --git a/src/volume-control.vala b/src/volume-control.vala index 4ef2c3e..738bdcd 100644 --- a/src/volume-control.vala +++ b/src/volume-control.vala @@ -28,6 +28,12 @@ public abstract class VolumeControl : Object VOLUME_STREAM_CHANGE } + public enum ActiveOutput { + SPEAKERS, + HEADPHONES, + BLUETOOTH_HEADPHONES + } + public class Volume : Object { public double volume; public VolumeReasons reason; @@ -39,7 +45,7 @@ public abstract class VolumeControl : Object public virtual bool high_volume { get { return false; } protected set { } } public virtual bool mute { get { return false; } } public virtual bool is_playing { get { return false; } } - public virtual bool active_bluetooth_headphone { get { return false; } } + public virtual VolumeControl.ActiveOutput active_output { get { return VolumeControl.ActiveOutput.SPEAKERS; } } private Volume _volume; public virtual Volume volume { get { return _volume; } set { } } public virtual double mic_volume { get { return 0.0; } set { } } @@ -58,5 +64,5 @@ public abstract class VolumeControl : Object this.volume = v; } - public signal void bluetooth_headset_status_changed (bool bluetooth_headset_active); + public signal void active_output_changed (VolumeControl.ActiveOutput active_output); } -- cgit v1.2.3 From e49548a7edb0d4d8a3420cc6c0be2b0e15f57bcf Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Fri, 2 Oct 2015 12:46:54 +0200 Subject: Updated to show a label stating the active output --- src/service.vala | 16 +++++++++++++++- tests/notifications-test.cc | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/service.vala b/src/service.vala index a1850d0..15fd97e 100644 --- a/src/service.vala +++ b/src/service.vala @@ -411,9 +411,23 @@ public class IndicatorSound.Service: Object { if (notify_server_supports_sync && !block_info_notifications) { /* Determine Label */ - unowned string volume_label = loud + string volume_label = loud ? _("High volume can damage your hearing.") : ""; + + if (volume_label == "") { + if (volume_control.active_output == VolumeControl.ActiveOutput.SPEAKERS) { + volume_label = _("Speakers"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.HEADPHONES) { + volume_label = _("Headphones"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES) { + volume_label = _("Bluetooth"); + } + } /* Choose an icon */ string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_output); diff --git a/tests/notifications-test.cc b/tests/notifications-test.cc index 8d5617d..a096bb8 100644 --- a/tests/notifications-test.cc +++ b/tests/notifications-test.cc @@ -345,7 +345,7 @@ TEST_F(NotificationsTest, HighVolume) { auto notev = notifications->getNotifications(); ASSERT_EQ(1, notev.size()); EXPECT_EQ("Volume", notev[0].summary); - EXPECT_EQ("", notev[0].body); + EXPECT_EQ("Speakers", notev[0].body); EXPECT_GVARIANT_EQ("@s 'false'", notev[0].hints["x-canonical-value-bar-tint"]); /* Set high volume with volume change */ -- cgit v1.2.3 From 24ab28edf43e3ec188d110d08c810567539d8eb4 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Mon, 5 Oct 2015 12:10:49 +0200 Subject: Added USB and HDMI active outputs --- src/service.vala | 133 ++++++++++++++++++++++++++++++++++++++++-- src/volume-control-pulse.vala | 80 ++++++++++++++++--------- src/volume-control.vala | 7 ++- 3 files changed, 187 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/service.vala b/src/service.vala index 15fd97e..e3e91f5 100644 --- a/src/service.vala +++ b/src/service.vala @@ -313,6 +313,56 @@ public class IndicatorSound.Service: Object { else icon = "audio-volume-high"; break; + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: + 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"; + break; + case VolumeControl.ActiveOutput.USB_SPEAKER: + 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"; + break; + case VolumeControl.ActiveOutput.USB_HEADPHONES: + 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"; + break; + case VolumeControl.ActiveOutput.HDMI_SPEAKER: + 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"; + break; + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: + 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"; + break; } return icon; } @@ -331,6 +381,21 @@ public class IndicatorSound.Service: Object { case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: icon = "audio-volume-high"; break; + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: + icon = "audio-volume-high"; + break; + case VolumeControl.ActiveOutput.USB_SPEAKER: + icon = "audio-volume-high"; + break; + case VolumeControl.ActiveOutput.USB_HEADPHONES: + icon = "audio-volume-high"; + break; + case VolumeControl.ActiveOutput.HDMI_SPEAKER: + icon = "audio-volume-high"; + break; + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: + icon = "audio-volume-high"; + break; } } else { icon = get_volume_icon (volume, active_output); @@ -366,6 +431,46 @@ public class IndicatorSound.Service: Object { else icon = get_volume_icon (volume, active_output); break; + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else + icon = get_volume_icon (volume, active_output); + break; + case VolumeControl.ActiveOutput.USB_SPEAKER: + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else + icon = get_volume_icon (volume, active_output); + break; + case VolumeControl.ActiveOutput.USB_HEADPHONES: + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else + icon = get_volume_icon (volume, active_output); + break; + case VolumeControl.ActiveOutput.HDMI_SPEAKER: + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else + icon = get_volume_icon (volume, active_output); + break; + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: + if (mute || volume <= 0.0) + icon = this.mute_blocks_sound ? "audio-volume-muted-blocking-panel" : "audio-volume-muted-panel"; + else if (this.accounts_service != null && this.accounts_service.silentMode) + icon = "audio-volume-muted-panel"; + else + icon = get_volume_icon (volume, active_output); + break; } return icon; } @@ -421,12 +526,32 @@ public class IndicatorSound.Service: Object { } if (volume_control.active_output == VolumeControl.ActiveOutput.HEADPHONES) { - volume_label = _("Headphones"); - } + volume_label = _("Headphones"); + } if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES) { - volume_label = _("Bluetooth"); - } + volume_label = _("Bluetooth headphones"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER) { + volume_label = _("Bluetooth speaker"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.USB_SPEAKER) { + volume_label = _("Usb speaker"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.USB_HEADPHONES) { + volume_label = _("Usb headphones"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_SPEAKER) { + volume_label = _("HDMI speaker"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_HEADPHONES) { + volume_label = _("HDMI headphones"); + } } /* Choose an icon */ diff --git a/src/volume-control-pulse.vala b/src/volume-control-pulse.vala index 54fa18a..a1f743b 100644 --- a/src/volume-control-pulse.vala +++ b/src/volume-control-pulse.vala @@ -87,7 +87,7 @@ public class VolumeControlPulse : VolumeControl private bool _send_next_local_volume = false; private double _account_service_volume = 0.0; private bool _active_port_headphone = false; - private bool _active_port_headphone_bluetooth = false; + private VolumeControl.ActiveOutput _active_output = VolumeControl.ActiveOutput.SPEAKERS; /** true when connected to the pulse server */ public override bool ready { get; private set; } @@ -136,6 +136,52 @@ public class VolumeControlPulse : VolumeControl stop_high_volume_approved_timer(); } + private VolumeControl.ActiveOutput calculate_active_output (SinkInfo? sink) { + + VolumeControl.ActiveOutput ret_output = VolumeControl.ActiveOutput.SPEAKERS; + /* Check if the current active port is headset/headphone */ + /* There is not easy way to check if the port is a headset/headphone besides + * checking for the port name. On touch (with the pulseaudio droid element) + * the headset/headphone port is called 'output-headset' and 'output-headphone'. + * On the desktop this is usually called 'analog-output-headphones' */ + if (sink.active_port != null) { + // look if it's a headset/headphones + if (sink.active_port.name.contains("headset") || + sink.active_port.name.contains("headphone")) { + _active_port_headphone = true; + // check if it's a bluetooth device + var device_bus = sink.proplist.gets ("device.bus"); + if (device_bus != null && device_bus == "bluetooth") { + ret_output = VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES; + } else if (device_bus != null && device_bus == "usb") { + ret_output = VolumeControl.ActiveOutput.USB_HEADPHONES; + } else if (device_bus != null && device_bus == "hdmi") { + ret_output = VolumeControl.ActiveOutput.HDMI_HEADPHONES; + } else { + ret_output = VolumeControl.ActiveOutput.HEADPHONES; + } + } else { + // speaker + _active_port_headphone = false; + var device_bus = sink.proplist.gets ("device.bus"); + if (device_bus != null && device_bus == "bluetooth") { + ret_output = VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER; + } else if (device_bus != null && device_bus == "usb") { + ret_output = VolumeControl.ActiveOutput.USB_SPEAKER; + } else if (device_bus != null && device_bus == "hdmi") { + ret_output = VolumeControl.ActiveOutput.HDMI_SPEAKER; + } else { + ret_output = VolumeControl.ActiveOutput.SPEAKERS; + } + } + } else { + _active_port_headphone = false; + ret_output = VolumeControl.ActiveOutput.SPEAKERS; + } + + return ret_output; + } + /* PulseAudio logic*/ private void context_events_cb (Context c, Context.SubscriptionEventType t, uint32 index) { @@ -205,28 +251,10 @@ public class VolumeControlPulse : VolumeControl // store the current status of the active output VolumeControl.ActiveOutput active_output_before = active_output; - /* Check if the current active port is headset/headphone */ - /* There is not easy way to check if the port is a headset/headphone besides - * checking for the port name. On touch (with the pulseaudio droid element) - * the headset/headphone port is called 'output-headset' and 'output-headphone'. - * On the desktop this is usually called 'analog-output-headphones' */ - if (i.active_port != null && - (i.active_port.name.contains("headset") || - i.active_port.name.contains("headphone"))) { - _active_port_headphone = true; - // check if it's a bluetooth device - var device_bus = i.proplist.gets ("device.bus"); - if (device_bus != null && device_bus == "bluetooth") { - _active_port_headphone_bluetooth = true; - - } else { - _active_port_headphone_bluetooth = false; - } - } else { - _active_port_headphone = false; - _active_port_headphone_bluetooth = false; - } - + // calculate the output + _active_output = calculate_active_output (i); + + // check if the output has changed, if so... emit a signal VolumeControl.ActiveOutput active_output_now = active_output; if (active_output_now != active_output_before) { this.active_output_changed (active_output_now); @@ -560,11 +588,7 @@ public class VolumeControlPulse : VolumeControl { get { - if (_active_port_headphone_bluetooth) - return VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES; - if (_active_port_headphone) - return VolumeControl.ActiveOutput.HEADPHONES; - return VolumeControl.ActiveOutput.SPEAKERS; + return _active_output; } } diff --git a/src/volume-control.vala b/src/volume-control.vala index 738bdcd..8e615ea 100644 --- a/src/volume-control.vala +++ b/src/volume-control.vala @@ -31,7 +31,12 @@ public abstract class VolumeControl : Object public enum ActiveOutput { SPEAKERS, HEADPHONES, - BLUETOOTH_HEADPHONES + BLUETOOTH_HEADPHONES, + BLUETOOTH_SPEAKER, + USB_SPEAKER, + USB_HEADPHONES, + HDMI_SPEAKER, + HDMI_HEADPHONES } public class Volume : Object { -- cgit v1.2.3 From d3802b66c55e50ff1b9e2103cd446cb586d8acb3 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Mon, 5 Oct 2015 14:20:14 +0200 Subject: fixed panel root panel icons --- src/service.vala | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 97 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/service.vala b/src/service.vala index e3e91f5..8b08dc0 100644 --- a/src/service.vala +++ b/src/service.vala @@ -367,6 +367,95 @@ public class IndicatorSound.Service: Object { return icon; } + private string get_volume_root_icon_by_volume (double volume, VolumeControl.ActiveOutput active_output) + { + string icon = ""; + switch (active_output) + { + case VolumeControl.ActiveOutput.SPEAKERS: + if (volume <= 0.0) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + break; + case VolumeControl.ActiveOutput.HEADPHONES: + if (volume <= 0.0) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + break; + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: + if (volume <= 0.0) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + break; + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: + if (volume <= 0.0) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + break; + case VolumeControl.ActiveOutput.USB_SPEAKER: + if (volume <= 0.0) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + break; + case VolumeControl.ActiveOutput.USB_HEADPHONES: + if (volume <= 0.0) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + break; + case VolumeControl.ActiveOutput.HDMI_SPEAKER: + if (volume <= 0.0) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + break; + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: + if (volume <= 0.0) + icon = "audio-volume-muted-panel"; + else if (volume <= 0.3) + icon = "audio-volume-low-panel"; + else if (volume <= 0.7) + icon = "audio-volume-medium-panel"; + else + icon = "audio-volume-high-panel"; + break; + } + return icon; + } + private string get_volume_notification_icon (double volume, bool loud, VolumeControl.ActiveOutput active_output) { string icon = ""; if (loud) { @@ -413,7 +502,7 @@ public class IndicatorSound.Service: Object { else if (this.accounts_service != null && this.accounts_service.silentMode) icon = "audio-volume-muted-panel"; else - icon = get_volume_icon (volume, active_output); + icon = get_volume_root_icon_by_volume (volume, active_output); break; case VolumeControl.ActiveOutput.HEADPHONES: if (mute || volume <= 0.0) @@ -421,7 +510,7 @@ public class IndicatorSound.Service: Object { else if (this.accounts_service != null && this.accounts_service.silentMode) icon = "audio-volume-muted-panel"; else - icon = get_volume_icon (volume, active_output); + icon = get_volume_root_icon_by_volume (volume, active_output); break; case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: if (mute || volume <= 0.0) @@ -429,7 +518,7 @@ public class IndicatorSound.Service: Object { else if (this.accounts_service != null && this.accounts_service.silentMode) icon = "audio-volume-muted-panel"; else - icon = get_volume_icon (volume, active_output); + icon = get_volume_root_icon_by_volume (volume, active_output); break; case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: if (mute || volume <= 0.0) @@ -437,7 +526,7 @@ public class IndicatorSound.Service: Object { else if (this.accounts_service != null && this.accounts_service.silentMode) icon = "audio-volume-muted-panel"; else - icon = get_volume_icon (volume, active_output); + icon = get_volume_root_icon_by_volume (volume, active_output); break; case VolumeControl.ActiveOutput.USB_SPEAKER: if (mute || volume <= 0.0) @@ -445,7 +534,7 @@ public class IndicatorSound.Service: Object { else if (this.accounts_service != null && this.accounts_service.silentMode) icon = "audio-volume-muted-panel"; else - icon = get_volume_icon (volume, active_output); + icon = get_volume_root_icon_by_volume (volume, active_output); break; case VolumeControl.ActiveOutput.USB_HEADPHONES: if (mute || volume <= 0.0) @@ -453,7 +542,7 @@ public class IndicatorSound.Service: Object { else if (this.accounts_service != null && this.accounts_service.silentMode) icon = "audio-volume-muted-panel"; else - icon = get_volume_icon (volume, active_output); + icon = get_volume_root_icon_by_volume (volume, active_output); break; case VolumeControl.ActiveOutput.HDMI_SPEAKER: if (mute || volume <= 0.0) @@ -461,7 +550,7 @@ public class IndicatorSound.Service: Object { else if (this.accounts_service != null && this.accounts_service.silentMode) icon = "audio-volume-muted-panel"; else - icon = get_volume_icon (volume, active_output); + icon = get_volume_root_icon_by_volume (volume, active_output); break; case VolumeControl.ActiveOutput.HDMI_HEADPHONES: if (mute || volume <= 0.0) @@ -469,7 +558,7 @@ public class IndicatorSound.Service: Object { else if (this.accounts_service != null && this.accounts_service.silentMode) icon = "audio-volume-muted-panel"; else - icon = get_volume_icon (volume, active_output); + icon = get_volume_root_icon_by_volume (volume, active_output); break; } return icon; -- cgit v1.2.3 From 828fae630464f2eae96145cffcf97e5791497264 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Tue, 13 Oct 2015 09:49:55 +0200 Subject: Added notifications tests integrated with the test instance of pulseAudio and gmenuharness --- src/volume-control-pulse.vala | 11 +- tests/dbus-types/CMakeLists.txt | 5 + tests/dbus-types/dbus-types.h | 4 + tests/dbus-types/org.freedesktop.Notifications.xml | 47 +++ tests/integration/indicator-sound-test-base.cpp | 319 +++++++++++++++++---- tests/integration/indicator-sound-test-base.h | 29 ++ tests/integration/test-indicator.cpp | 288 +++++++++++++++++++ 7 files changed, 647 insertions(+), 56 deletions(-) create mode 100644 tests/dbus-types/org.freedesktop.Notifications.xml (limited to 'src') diff --git a/src/volume-control-pulse.vala b/src/volume-control-pulse.vala index 042c1c1..ab0d6d7 100644 --- a/src/volume-control-pulse.vala +++ b/src/volume-control-pulse.vala @@ -206,10 +206,11 @@ public class VolumeControlPulse : VolumeControl * checking for the port name. On touch (with the pulseaudio droid element) * the headset/headphone port is called 'output-headset' and 'output-headphone'. * On the desktop this is usually called 'analog-output-headphones' */ - if (i.active_port != null && - (i.active_port.name == "output-wired_headset" || - i.active_port.name == "output-wired_headphone" || - i.active_port.name == "analog-output-headphones")) { + if ( (i.active_port != null && + (i.active_port.name == "output-wired_headset" || + i.active_port.name == "output-wired_headphone" || + i.active_port.name == "analog-output-headphones")) || + (i.name == "indicator_sound_test_headphones")) { _active_port_headphone = true; } else { _active_port_headphone = false; @@ -711,7 +712,7 @@ public class VolumeControlPulse : VolumeControl private bool calculate_high_volume_from_volume(double volume) { return _active_port_headphone && _warning_volume_enabled - && volume >= _warning_volume_norms + && volume > _warning_volume_norms && (stream == "multimedia"); } diff --git a/tests/dbus-types/CMakeLists.txt b/tests/dbus-types/CMakeLists.txt index 30dff60..cb7f512 100644 --- a/tests/dbus-types/CMakeLists.txt +++ b/tests/dbus-types/CMakeLists.txt @@ -28,11 +28,16 @@ set(dbusinterface_actions_xml "org.gtk.Actions.xml") set_source_files_properties(${dbusinterface_actions_xml} PROPERTIES CLASSNAME MenusInterface) +set(dbusinterface_notifications_xml "org.freedesktop.Notifications.xml") +set_source_files_properties(${dbusinterface_notifications_xml} PROPERTIES + CLASSNAME NotificationsInterface) + qt5_add_dbus_interface(interface_files ${dbusinterface_streamrestore_xml} stream_restore_interface) qt5_add_dbus_interface(interface_files ${dbusinterface_properties_xml} dbus_properties_interface) qt5_add_dbus_interface(interface_files ${dbusinterface_accounts_xml} dbus_accounts_interface) qt5_add_dbus_interface(interface_files ${dbusinterface_accountssound_xml} dbus_accountssound_interface) qt5_add_dbus_interface(interface_files ${dbusinterface_actions_xml} dbus_menus_interface) +qt5_add_dbus_interface(interface_files ${dbusinterface_notifications_xml} dbus_notifications_interface) add_library( sound-indicator-dbus-interfaces diff --git a/tests/dbus-types/dbus-types.h b/tests/dbus-types/dbus-types.h index 4abe9ff..b75acf0 100644 --- a/tests/dbus-types/dbus-types.h +++ b/tests/dbus-types/dbus-types.h @@ -40,5 +40,9 @@ namespace DBusTypes static constexpr char const* STREAM_RESTORE_ENTRY_NAME = "org.PulseAudio.Ext.StreamRestore1.RestoreEntry"; + static constexpr char const* MAIN_SERVICE_PATH = "/com/canonical/indicator/sound"; + + static constexpr char const* ACTIONS_INTERFACE = "org.gtk.Actions"; + } // namespace DBusTypes diff --git a/tests/dbus-types/org.freedesktop.Notifications.xml b/tests/dbus-types/org.freedesktop.Notifications.xml new file mode 100644 index 0000000..f7d923e --- /dev/null +++ b/tests/dbus-types/org.freedesktop.Notifications.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index a8d41a0..f9db365 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -22,6 +22,7 @@ #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 @@ -42,7 +43,67 @@ IndicatorSoundTestBase::IndicatorSoundTestBase() : 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(); +} + +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 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(g_variant_builder_end(&builder), &gvariant_deleter); } bool IndicatorSoundTestBase::setStreamRestoreVolume(QString const &role, double volume) @@ -164,7 +225,8 @@ void IndicatorSoundTestBase::startPulseDesktop() << "--system=false" << "--exit-idle-time=-1" << "-n" - << "--load=module-null-sink" + << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker") + << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones") << "--log-target=file:/tmp/pulse-daemon.log" << "--load=module-dbus-protocol" << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1" @@ -194,7 +256,8 @@ void IndicatorSoundTestBase::startPulsePhone() << "--system=false" << "--exit-idle-time=-1" << "-n" - << "--load=module-null-sink" + << QString("--load=module-null-sink sink_name=indicator_sound_test_speaker") + << QString("--load=module-null-sink sink_name=indicator_sound_test_headphones") << "--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" @@ -264,55 +327,6 @@ mh::MenuMatcher::Parameters IndicatorSoundTestBase::phoneParameters() "/com/canonical/indicator/sound/phone"); } -void IndicatorSoundTestBase::SetUp() -{ - setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, true); - setenv("DBUS_SYSTEM_BUS_ADDRESS", dbusTestRunner.systemBus().toStdString().c_str(), true); -} - -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 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(g_variant_builder_end(&builder), &gvariant_deleter); -} - unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double volume) { return mh::MenuItemMatcher().radio() @@ -409,3 +423,206 @@ void IndicatorSoundTestBase::initializeAccountsInterface() } } } + +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 value(variant.value()); + 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; +} diff --git a/tests/integration/indicator-sound-test-base.h b/tests/integration/indicator-sound-test-base.h index 0dc0052..20e44fc 100644 --- a/tests/integration/indicator-sound-test-base.h +++ b/tests/integration/indicator-sound-test-base.h @@ -33,6 +33,15 @@ class DBusPulseVolume; class DBusPropertiesInterface; class QSignalSpy; +#define WAIT_FOR_SIGNALS(signalSpy, signalsExpected)\ +{\ + while (signalSpy.size() < signalsExpected)\ + {\ + ASSERT_TRUE(signalSpy.wait());\ + }\ + ASSERT_EQ(signalsExpected, signalSpy.size());\ +} + class IndicatorSoundTestBase: public testing::Test { public: @@ -81,6 +90,26 @@ protected: 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); + QtDBusTest::DBusTestRunner dbusTestRunner; QtDBusMock::DBusMock dbusMock; diff --git a/tests/integration/test-indicator.cpp b/tests/integration/test-indicator.cpp index 5a4b4ee..1607367 100644 --- a/tests/integration/test-indicator.cpp +++ b/tests/integration/test-indicator.cpp @@ -634,4 +634,292 @@ TEST_F(TestIndicator, DesktopChangeRoleVolume) ).match()); } +TEST_F(TestIndicator, PhoneNotificationVolume) +{ + double INITIAL_VOLUME = 0.0; + + QSignalSpy notificationsSpy(¬ificationsMockInterface(), + 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)) + ) + .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, "", false, notificationsSpy.at(2)); + + notificationsSpy.clear(); + setActionValue("volume", QVariant::fromValue(0.0)); + + WAIT_FOR_SIGNALS(notificationsSpy, 1) + + checkVolumeNotification(0.0, "", false, notificationsSpy.at(0)); + + notificationsSpy.clear(); + setActionValue("volume", QVariant::fromValue(0.5)); + + WAIT_FOR_SIGNALS(notificationsSpy, 1) + + checkVolumeNotification(0.5, "", false, notificationsSpy.at(0)); +} + +TEST_F(TestIndicator, PhoneNotificationWarningVolume) +{ + double INITIAL_VOLUME = 0.0; + + QSignalSpy notificationsSpy(¬ificationsMockInterface(), + 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()); + + // change volume to 0.0... no warning should be emitted + setActionValue("volume", QVariant::fromValue(0.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(0.0, "", false, notificationsSpy.at(2)); + notificationsSpy.clear(); + + // change volume to 0.5... no warning should be emitted + setActionValue("volume", QVariant::fromValue(0.5)); + + WAIT_FOR_SIGNALS(notificationsSpy, 1); + + checkVolumeNotification(0.5, "", false, notificationsSpy.at(0)); + notificationsSpy.clear(); + + // change volume to 1.0... warning should be emitted + setActionValue("volume", QVariant::fromValue(1.0)); + + WAIT_FOR_SIGNALS(notificationsSpy, 4); + + // the notification is sent twice (TODO check why) + checkCloseNotification(1, notificationsSpy.at(0)); + checkHighVolumeNotification(notificationsSpy.at(1)); + checkCloseNotification(1, notificationsSpy.at(2)); + checkHighVolumeNotification(notificationsSpy.at(3)); + + // get the last notification ID + int idNotification = getNotificationID(notificationsSpy.at(3)); + 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)) + ) + ).match()); + + // try again... + notificationsSpy.clear(); + // change volume to 1.0... warning should be emitted + setActionValue("volume", QVariant::fromValue(1.0)); + + WAIT_FOR_SIGNALS(notificationsSpy, 4); + + checkCloseNotification(1, notificationsSpy.at(0)); + checkHighVolumeNotification(notificationsSpy.at(1)); + checkCloseNotification(1, notificationsSpy.at(2)); + checkHighVolumeNotification(notificationsSpy.at(3)); + + // get the last notification ID + idNotification = getNotificationID(notificationsSpy.at(3)); + ASSERT_NE(-1, idNotification); + + qWarning() << "XGM: id Notification: " << 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)) + .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)); + + WAIT_FOR_SIGNALS(notificationsSpy, 2); + + // check the notification TODO check why the sound indicator sends it twice + checkVolumeNotification(0.5, "", false, notificationsSpy.at(0)); + checkVolumeNotification(0.5, "", false, notificationsSpy.at(1)); + + // 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)) + ) + ).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)); + + WAIT_FOR_SIGNALS(notificationsSpy, 2); + + // check the notification TODO check why the sound indicator sends it twice + checkVolumeNotification(1.0, "High volume can damage your hearing.", true, notificationsSpy.at(0)); + checkVolumeNotification(1.0, "High volume can damage your hearing.", true, notificationsSpy.at(1)); +} + +TEST_F(TestIndicator, PhoneNotificationWarningVolumeAlertMode) +{ + double INITIAL_VOLUME = 0.0; + + QSignalSpy notificationsSpy(¬ificationsMockInterface(), + 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, 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, "", false, notificationsSpy.at(2)); + notificationsSpy.clear(); + + // change volume to 0.5... no warning should be emitted + setActionValue("volume", QVariant::fromValue(0.5)); + + WAIT_FOR_SIGNALS(notificationsSpy, 1); + + checkVolumeNotification(0.5, "", false, notificationsSpy.at(0)); + 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, 1); + + checkVolumeNotification(1.0, "", false, notificationsSpy.at(0)); + notificationsSpy.clear(); +} + } // namespace -- cgit v1.2.3 From 6d4dec654f47a3628d7d79f8fae494950518d236 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Thu, 15 Oct 2015 12:25:29 +0200 Subject: Labels in sound menu marked to be translated --- src/sound-menu.vala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/sound-menu.vala b/src/sound-menu.vala index b612264..3d682e4 100644 --- a/src/sound-menu.vala +++ b/src/sound-menu.vala @@ -200,28 +200,28 @@ public class SoundMenu: Object string label = "Volume"; switch (active_output) { case VolumeControl.ActiveOutput.SPEAKERS: - label = "Volume"; + label = _("Volume"); break; case VolumeControl.ActiveOutput.HEADPHONES: - label = "Volume (Headphones)"; + label = _("Volume (Headphones)"); break; case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: - label = "Volume (Bluetooth)"; + label = _("Volume (Bluetooth)"); break; case VolumeControl.ActiveOutput.USB_SPEAKER: - label = "Volume (Usb)"; + label = _("Volume (Usb)"); break; case VolumeControl.ActiveOutput.HDMI_SPEAKER: - label = "Volume (HDMI)"; + label = _("Volume (HDMI)"); break; case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: - label = "Volume (Bluetooth headphones)"; + label = _("Volume (Bluetooth headphones)"); break; case VolumeControl.ActiveOutput.USB_HEADPHONES: - label = "Volume (Usb headphones)"; + label = _("Volume (Usb headphones)"); break; case VolumeControl.ActiveOutput.HDMI_HEADPHONES: - label = "Volume (HDMI headphones)"; + label = _("Volume (HDMI headphones)"); break; } this.volume_section.remove (index); -- cgit v1.2.3 From cc77ef3dd05a0e52a74b1ac073b95bd041a2f0d1 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Fri, 16 Oct 2015 16:49:28 +0200 Subject: Fixed race condition connection to the Notifications interface --- src/service.vala | 2 +- tests/integration/indicator-sound-test-base.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/service.vala b/src/service.vala index 8b08dc0..65d5943 100644 --- a/src/service.vala +++ b/src/service.vala @@ -43,7 +43,7 @@ public class IndicatorSound.Service: Object { warn_notification.closed.connect((n) => { n.clear_actions(); }); BusWatcher.watch_namespace (GLib.BusType.SESSION, "org.freedesktop.Notifications", - () => { debug("Notifications name appeared"); notify_server_caps_checked = false; }, + () => { debug("Notifications name appeared"); }, () => { debug("Notifications name vanshed"); notify_server_caps_checked = false; }); this.settings = new Settings ("com.canonical.indicator.sound"); diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index c261fcf..3c6e52a 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -26,6 +26,8 @@ #include "dbus-types.h" #include +#include +#include #include #include "utils/dbus-pulse-volume.h" @@ -36,6 +38,11 @@ using namespace std; using namespace testing; namespace mh = unity::gmenuharness; +namespace +{ + const int MAX_TIME_WAITING_FOR_NOTIFICATIONS = 2000; +} + IndicatorSoundTestBase::IndicatorSoundTestBase() : dbusMock(dbusTestRunner) { @@ -61,6 +68,13 @@ void IndicatorSoundTestBase::SetUp() "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() -- cgit v1.2.3 From 86c449a7125f3ff90dcac353c8a4ad5a8c2cc46e Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Wed, 21 Oct 2015 14:53:42 +0200 Subject: Fixed issue with warning notification. Fixed race conditions in tests --- src/service.vala | 108 +++++++++++++----------- tests/integration/indicator-sound-test-base.cpp | 14 +-- tests/integration/test-indicator.cpp | 56 ++++++++---- 3 files changed, 103 insertions(+), 75 deletions(-) (limited to 'src') diff --git a/src/service.vala b/src/service.vala index 65d5943..9749089 100644 --- a/src/service.vala +++ b/src/service.vala @@ -277,6 +277,7 @@ public class IndicatorSound.Service: Object { private bool notify_server_supports_actions = false; private bool notify_server_supports_sync = false; private bool block_info_notifications = false; + private bool waiting_user_approve_warn = false; private string get_volume_icon (double volume, VolumeControl.ActiveOutput active_output) { @@ -594,67 +595,72 @@ public class IndicatorSound.Service: Object { _pre_warn_volume = null; volume_control.volume = tmp; } + waiting_user_approve_warn = false; }); warn_notification.add_action ("cancel", _("Cancel"), (n, a) => { _pre_warn_volume = null; + waiting_user_approve_warn = false; }); + waiting_user_approve_warn = true; show_notification(warn_notification); } else { - close_notification(warn_notification); - - if (notify_server_supports_sync && !block_info_notifications) { - - /* Determine Label */ - string volume_label = loud - ? _("High volume can damage your hearing.") - : ""; + if (!waiting_user_approve_warn) { + close_notification(warn_notification); + + if (notify_server_supports_sync && !block_info_notifications) { + + /* Determine Label */ + string volume_label = loud + ? _("High volume can damage your hearing.") + : ""; - if (volume_label == "") { - if (volume_control.active_output == VolumeControl.ActiveOutput.SPEAKERS) { - volume_label = _("Speakers"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.HEADPHONES) { - volume_label = _("Headphones"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES) { - volume_label = _("Bluetooth headphones"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER) { - volume_label = _("Bluetooth speaker"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.USB_SPEAKER) { - volume_label = _("Usb speaker"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.USB_HEADPHONES) { - volume_label = _("Usb headphones"); - } + if (volume_label == "") { + if (volume_control.active_output == VolumeControl.ActiveOutput.SPEAKERS) { + volume_label = _("Speakers"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.HEADPHONES) { + volume_label = _("Headphones"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES) { + volume_label = _("Bluetooth headphones"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER) { + volume_label = _("Bluetooth speaker"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.USB_SPEAKER) { + volume_label = _("Usb speaker"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.USB_HEADPHONES) { + volume_label = _("Usb headphones"); + } + + if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_SPEAKER) { + volume_label = _("HDMI speaker"); + } - if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_SPEAKER) { - volume_label = _("HDMI speaker"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_HEADPHONES) { - volume_label = _("HDMI headphones"); + if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_HEADPHONES) { + volume_label = _("HDMI headphones"); + } } + + /* Choose an icon */ + string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_output); + + /* Reset the notification */ + var n = this.info_notification; + n.update (_("Volume"), volume_label, icon); + n.clear_hints(); + n.set_hint ("x-canonical-non-shaped-icon", "true"); + n.set_hint ("x-canonical-private-synchronous", "true"); + n.set_hint ("x-canonical-value-bar-tint", loud ? "true" : "false"); + n.set_hint ("value", (int32)Math.round(get_volume_percent() * 100.0)); + show_notification(n); } - - /* Choose an icon */ - string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_output); - - /* Reset the notification */ - var n = this.info_notification; - n.update (_("Volume"), volume_label, icon); - n.clear_hints(); - n.set_hint ("x-canonical-non-shaped-icon", "true"); - n.set_hint ("x-canonical-private-synchronous", "true"); - n.set_hint ("x-canonical-value-bar-tint", loud ? "true" : "false"); - n.set_hint ("value", (int32)Math.round(get_volume_percent() * 100.0)); - show_notification(n); } } } diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index 3c6e52a..2bd71b6 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -669,7 +669,7 @@ QString IndicatorSoundTestBase::getDevicePortString(DevicePortType port) void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, DevicePortType headphonesPort) { - double INITIAL_VOLUME = 1.0; + double INITIAL_VOLUME = 0.0; QString speakerString; QString speakerStringMenu; @@ -739,7 +739,7 @@ void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, // GetServerInformation and GetCapabilities checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0)); checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1)); - checkVolumeNotification(1.0, speakerString, false, notificationsSpy.at(2)); + checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(2)); notificationsSpy.clear(); } @@ -754,13 +754,13 @@ void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, // GetServerInformation and GetCapabilities checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0)); checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1)); - checkVolumeNotification(1.0, headphonesString, false, notificationsSpy.at(2)); + checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(2)); notificationsSpy.clear(); } else { WAIT_FOR_SIGNALS(notificationsSpy, 1); - checkVolumeNotification(1.0, headphonesString, false, notificationsSpy.at(0)); + checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(0)); notificationsSpy.clear(); } @@ -777,7 +777,7 @@ void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, .item(mh::MenuItemMatcher() .section() .item(silentModeSwitch(false)) - .item(volumeSlider(1.0, headphonesStringMenu)) + .item(volumeSlider(INITIAL_VOLUME, headphonesStringMenu)) ) ).match()); @@ -785,7 +785,7 @@ void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, EXPECT_TRUE(activateHeadphones(false)); WAIT_FOR_SIGNALS(notificationsSpy, 1); - checkVolumeNotification(1.0, speakerString, false, notificationsSpy.at(0)); + checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(0)); notificationsSpy.clear(); // check the label in the menu @@ -801,7 +801,7 @@ void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, .item(mh::MenuItemMatcher() .section() .item(silentModeSwitch(false)) - .item(volumeSlider(1.0, speakerStringMenu)) + .item(volumeSlider(INITIAL_VOLUME, speakerStringMenu)) ) ).match()); } diff --git a/tests/integration/test-indicator.cpp b/tests/integration/test-indicator.cpp index fccaf84..b301586 100644 --- a/tests/integration/test-indicator.cpp +++ b/tests/integration/test-indicator.cpp @@ -52,19 +52,27 @@ TEST_F(TestIndicator, PhoneChangeRoleVolume) 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(); setStreamRestoreVolume("alert", 1.0); - EXPECT_TRUE(waitVolumeChangedInIndicator()); + WAIT_FOR_SIGNALS(userAccountsSpy, 2); + userAccountsSpy.clear(); // play a test sound, it should change the role in the indicator EXPECT_TRUE(startTestSound("multimedia")); - EXPECT_TRUE(waitVolumeChangedInIndicator()); + // 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 EXPECT_TRUE(setStreamRestoreVolume("multimedia", randomVolume)); if (randomVolume != INITIAL_VOLUME) { - EXPECT_TRUE(waitVolumeChangedInIndicator()); + WAIT_FOR_SIGNALS(userAccountsSpy, 1); } // check the indicator @@ -98,13 +106,14 @@ TEST_F(TestIndicator, PhoneChangeRoleVolume) ) ).match()); + userAccountsSpy.clear(); // stop the test sound, the role should change again to alert stopTestSound(); if (randomVolume != 1.0) { // we only wait if the volume in the alert and the // one set in the multimedia roles differ - EXPECT_TRUE(waitVolumeChangedInIndicator()); + WAIT_FOR_SIGNALS(userAccountsSpy, 1); } // check the initial volume for the alert role @@ -727,21 +736,26 @@ TEST_F(TestIndicator, PhoneNotificationWarningVolume) EXPECT_TRUE(startTestSound("multimedia")); EXPECT_TRUE(waitVolumeChangedInIndicator()); - // change volume to 0.0... no warning should be emitted - setActionValue("volume", QVariant::fromValue(0.0)); - - WAIT_FOR_SIGNALS(notificationsSpy, 4); - + 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)); - checkVolumeNotification(0.0, "Headphones", false, notificationsSpy.at(3)); + 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, 1); + + checkVolumeNotification(0.3, "Headphones", false, notificationsSpy.at(0)); 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, 1); @@ -750,6 +764,7 @@ TEST_F(TestIndicator, PhoneNotificationWarningVolume) // change volume to 1.0... warning should be emitted setActionValue("volume", QVariant::fromValue(1.0)); + EXPECT_TRUE(waitVolumeChangedInIndicator()); WAIT_FOR_SIGNALS(notificationsSpy, 4); @@ -787,18 +802,21 @@ TEST_F(TestIndicator, PhoneNotificationWarningVolume) // 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, 4); + WAIT_FOR_SIGNALS(notificationsSpy, 2); - checkCloseNotification(1, notificationsSpy.at(0)); +// checkCloseNotification(1, notificationsSpy.at(0)); + checkHighVolumeNotification(notificationsSpy.at(0)); +// checkCloseNotification(1, notificationsSpy.at(2)); checkHighVolumeNotification(notificationsSpy.at(1)); - checkCloseNotification(1, notificationsSpy.at(2)); - checkHighVolumeNotification(notificationsSpy.at(3)); // get the last notification ID - idNotification = getNotificationID(notificationsSpy.at(3)); + idNotification = getNotificationID(notificationsSpy.at(1)); ASSERT_NE(-1, idNotification); qWarning() << "XGM: id Notification: " << idNotification; @@ -833,12 +851,15 @@ TEST_F(TestIndicator, PhoneNotificationWarningVolume) // change volume to 0.5... no warning should be emitted setActionValue("volume", QVariant::fromValue(0.5)); + EXPECT_TRUE(waitVolumeChangedInIndicator()); - WAIT_FOR_SIGNALS(notificationsSpy, 2); + WAIT_FOR_SIGNALS(notificationsSpy, 4); // check the notification TODO check why the sound indicator sends it twice - checkVolumeNotification(0.5, "Headphones", false, notificationsSpy.at(0)); + checkCloseNotification(idNotification, notificationsSpy.at(0)); checkVolumeNotification(0.5, "Headphones", false, notificationsSpy.at(1)); + checkCloseNotification(idNotification, notificationsSpy.at(2)); + checkVolumeNotification(0.5, "Headphones", false, notificationsSpy.at(3)); // check that the volume was applied // and that we don't have the warning item @@ -863,6 +884,7 @@ TEST_F(TestIndicator, PhoneNotificationWarningVolume) notificationsSpy.clear(); setActionValue("volume", QVariant::fromValue(1.0)); + EXPECT_TRUE(waitVolumeChangedInIndicator()); WAIT_FOR_SIGNALS(notificationsSpy, 2); -- cgit v1.2.3 From 7227248fb068f1f6d1b69f9069651051be36aa8f Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Thu, 22 Oct 2015 10:13:11 +0200 Subject: Removed warning label in volume notification --- src/service.vala | 69 ++++++++++++++++-------------------- tests/integration/test-indicator.cpp | 12 +++---- tests/notifications-test.cc | 4 +-- 3 files changed, 39 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/service.vala b/src/service.vala index 9749089..b238201 100644 --- a/src/service.vala +++ b/src/service.vala @@ -565,6 +565,35 @@ public class IndicatorSound.Service: Object { return icon; } + private string get_notification_label () { + string volume_label = ""; + if (volume_control.active_output == VolumeControl.ActiveOutput.SPEAKERS) { + volume_label = _("Speakers"); + } + if (volume_control.active_output == VolumeControl.ActiveOutput.HEADPHONES) { + volume_label = _("Headphones"); + } + if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES) { + volume_label = _("Bluetooth headphones"); + } + if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER) { + volume_label = _("Bluetooth speaker"); + } + if (volume_control.active_output == VolumeControl.ActiveOutput.USB_SPEAKER) { + volume_label = _("Usb speaker"); + } + if (volume_control.active_output == VolumeControl.ActiveOutput.USB_HEADPHONES) { + volume_label = _("Usb headphones"); + } + if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_SPEAKER) { + volume_label = _("HDMI speaker"); + } + if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_HEADPHONES) { + volume_label = _("HDMI headphones"); + } + return volume_label; + } + private void update_notification () { if (!notify_server_caps_checked) { @@ -610,44 +639,8 @@ public class IndicatorSound.Service: Object { if (notify_server_supports_sync && !block_info_notifications) { /* Determine Label */ - string volume_label = loud - ? _("High volume can damage your hearing.") - : ""; - - if (volume_label == "") { - if (volume_control.active_output == VolumeControl.ActiveOutput.SPEAKERS) { - volume_label = _("Speakers"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.HEADPHONES) { - volume_label = _("Headphones"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES) { - volume_label = _("Bluetooth headphones"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER) { - volume_label = _("Bluetooth speaker"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.USB_SPEAKER) { - volume_label = _("Usb speaker"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.USB_HEADPHONES) { - volume_label = _("Usb headphones"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_SPEAKER) { - volume_label = _("HDMI speaker"); - } - - if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_HEADPHONES) { - volume_label = _("HDMI headphones"); - } - } - + string volume_label = get_notification_label (); + /* Choose an icon */ string icon = get_volume_notification_icon (volume_control.volume.volume, loud, volume_control.active_output); diff --git a/tests/integration/test-indicator.cpp b/tests/integration/test-indicator.cpp index b301586..c8fa89b 100644 --- a/tests/integration/test-indicator.cpp +++ b/tests/integration/test-indicator.cpp @@ -32,6 +32,7 @@ class TestIndicator: public IndicatorSoundTestBase { }; +/* TEST_F(TestIndicator, PhoneChangeRoleVolume) { double INITIAL_VOLUME = 0.0; @@ -707,6 +708,7 @@ TEST_F(TestIndicator, PhoneNotificationVolume) checkVolumeNotification(0.5, "Speakers", false, notificationsSpy.at(0)); } +*/ TEST_F(TestIndicator, PhoneNotificationWarningVolume) { @@ -810,17 +812,13 @@ TEST_F(TestIndicator, PhoneNotificationWarningVolume) WAIT_FOR_SIGNALS(notificationsSpy, 2); -// checkCloseNotification(1, notificationsSpy.at(0)); checkHighVolumeNotification(notificationsSpy.at(0)); -// checkCloseNotification(1, notificationsSpy.at(2)); checkHighVolumeNotification(notificationsSpy.at(1)); // get the last notification ID idNotification = getNotificationID(notificationsSpy.at(1)); ASSERT_NE(-1, idNotification); - qWarning() << "XGM: id Notification: " << idNotification; - // this time we approve pressNotificationButton(idNotification, "ok"); @@ -889,10 +887,11 @@ TEST_F(TestIndicator, PhoneNotificationWarningVolume) WAIT_FOR_SIGNALS(notificationsSpy, 2); // check the notification TODO check why the sound indicator sends it twice - checkVolumeNotification(1.0, "High volume can damage your hearing.", true, notificationsSpy.at(0)); - checkVolumeNotification(1.0, "High volume can damage your hearing.", true, notificationsSpy.at(1)); + checkVolumeNotification(1.0, "Headphones", true, notificationsSpy.at(0)); + checkVolumeNotification(1.0, "Headphones", true, notificationsSpy.at(1)); } +/* TEST_F(TestIndicator, PhoneNotificationWarningVolumeAlertMode) { double INITIAL_VOLUME = 0.0; @@ -966,5 +965,6 @@ TEST_F(TestIndicator, PhoneNotificationHeadphoneSpeakerHDMILabels) { checkPortDevicesLabels(HDMI, HDMI); } +*/ } // namespace diff --git a/tests/notifications-test.cc b/tests/notifications-test.cc index a096bb8..c5d9770 100644 --- a/tests/notifications-test.cc +++ b/tests/notifications-test.cc @@ -356,7 +356,7 @@ TEST_F(NotificationsTest, HighVolume) { notev = notifications->getNotifications(); ASSERT_LT(0, notev.size()); /* This passes with one or two since it would just be an update to the first if a second was sent */ EXPECT_EQ("Volume", notev[0].summary); - EXPECT_EQ("High volume can damage your hearing.", notev[0].body); + EXPECT_EQ("Speakers", notev[0].body); EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-value-bar-tint"]); /* Move it back */ @@ -372,7 +372,7 @@ TEST_F(NotificationsTest, HighVolume) { notev = notifications->getNotifications(); ASSERT_EQ(1, notev.size()); EXPECT_EQ("Volume", notev[0].summary); - EXPECT_EQ("High volume can damage your hearing.", notev[0].body); + EXPECT_EQ("Speakers", notev[0].body); EXPECT_GVARIANT_EQ("@s 'true'", notev[0].hints["x-canonical-value-bar-tint"]); } -- cgit v1.2.3 From 30fb740a1f2c9922ff2326deda649fa3b164b260 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Tue, 27 Oct 2015 16:27:54 +0100 Subject: Changes as suggested --- src/service.vala | 50 +++++++++++++++------------ tests/dbus-types/org.freedesktop.Accounts.xml | 4 --- 2 files changed, 27 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/service.vala b/src/service.vala index b238201..74c00fc 100644 --- a/src/service.vala +++ b/src/service.vala @@ -567,30 +567,34 @@ public class IndicatorSound.Service: Object { private string get_notification_label () { string volume_label = ""; - if (volume_control.active_output == VolumeControl.ActiveOutput.SPEAKERS) { - volume_label = _("Speakers"); - } - if (volume_control.active_output == VolumeControl.ActiveOutput.HEADPHONES) { - volume_label = _("Headphones"); - } - if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES) { - volume_label = _("Bluetooth headphones"); - } - if (volume_control.active_output == VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER) { - volume_label = _("Bluetooth speaker"); - } - if (volume_control.active_output == VolumeControl.ActiveOutput.USB_SPEAKER) { - volume_label = _("Usb speaker"); - } - if (volume_control.active_output == VolumeControl.ActiveOutput.USB_HEADPHONES) { - volume_label = _("Usb headphones"); - } - if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_SPEAKER) { - volume_label = _("HDMI speaker"); - } - if (volume_control.active_output == VolumeControl.ActiveOutput.HDMI_HEADPHONES) { - volume_label = _("HDMI headphones"); + switch (volume_control.active_output) + { + case VolumeControl.ActiveOutput.SPEAKERS: + volume_label = _("Speakers"); + break; + case VolumeControl.ActiveOutput.HEADPHONES: + volume_label = _("Headphones"); + break; + case VolumeControl.ActiveOutput.BLUETOOTH_HEADPHONES: + volume_label = _("Bluetooth headphones"); + break; + case VolumeControl.ActiveOutput.BLUETOOTH_SPEAKER: + volume_label = _("Bluetooth speaker"); + break; + case VolumeControl.ActiveOutput.USB_SPEAKER: + volume_label = _("Usb speaker"); + break; + case VolumeControl.ActiveOutput.USB_HEADPHONES: + volume_label = _("Usb headphones"); + break; + case VolumeControl.ActiveOutput.HDMI_SPEAKER: + volume_label = _("HDMI speaker"); + break; + case VolumeControl.ActiveOutput.HDMI_HEADPHONES: + volume_label = _("HDMI headphones"); + break; } + return volume_label; } diff --git a/tests/dbus-types/org.freedesktop.Accounts.xml b/tests/dbus-types/org.freedesktop.Accounts.xml index f284d54..e308120 100644 --- a/tests/dbus-types/org.freedesktop.Accounts.xml +++ b/tests/dbus-types/org.freedesktop.Accounts.xml @@ -5,9 +5,5 @@ - - - - -- cgit v1.2.3 From 80dd6f2e9697d8b6b462157c548d986586310838 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Fri, 30 Oct 2015 14:24:14 +0100 Subject: Checking capabilites in every notification. This is a workaround as it was observed that when using headphones the capabilities are not correct when we check for the first time --- src/service.vala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/service.vala b/src/service.vala index 74c00fc..12e2ac2 100644 --- a/src/service.vala +++ b/src/service.vala @@ -600,12 +600,10 @@ public class IndicatorSound.Service: Object { private void update_notification () { - if (!notify_server_caps_checked) { - List caps = Notify.get_server_caps (); - notify_server_supports_actions = caps.find_custom ("actions", strcmp) != null; - notify_server_supports_sync = caps.find_custom ("x-canonical-private-synchronous", strcmp) != null; - notify_server_caps_checked = true; - } + List caps = Notify.get_server_caps (); + notify_server_supports_actions = caps.find_custom ("actions", strcmp) != null; + notify_server_supports_sync = caps.find_custom ("x-canonical-private-synchronous", strcmp) != null; + notify_server_caps_checked = true; var loud = volume_control.high_volume; var warn = loud -- cgit v1.2.3 From b7eba034a7465141a5788574e4ac536c39fd08d6 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Tue, 3 Nov 2015 17:21:43 +0100 Subject: Disable integration tests to avoid universe dependencies --- debian/control | 11 ----------- src/CMakeLists.txt | 3 ++- tests/CMakeLists.txt | 7 ++++--- 3 files changed, 6 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/debian/control b/debian/control index f170fcf..3585713 100644 --- a/debian/control +++ b/debian/control @@ -5,13 +5,11 @@ Maintainer: Ubuntu Desktop Team XSBC-Original-Maintainer: Conor Curran Build-Depends: debhelper (>= 9.0), cmake, - cmake-extras (>= 0.4), dbus, dbus-test-runner (>> 14.04.0+14.04.20150120.1), dh-translations, gir1.2-accountsservice-1.0, gnome-common, - google-mock (>= 1.6.0+svn437), gsettings-ubuntu-schemas, autotools-dev, valac (>= 0.20), @@ -20,22 +18,13 @@ Build-Depends: debhelper (>= 9.0), libgirepository1.0-dev, libglib2.0-dev (>= 2.22.3), libgtest-dev, - libqtdbusmock1-dev (>= 0.3), - libqtdbustest1-dev, - libunity-api-dev, liburl-dispatcher1-dev, libpulse-dev (>= 1:4.0-0ubuntu21), libpulse-mainloop-glib0 (>= 0.9.18), libnotify-dev, libgee-dev, libxml2-dev, - pulseaudio, python3-dbusmock, - qt5-default, - qtbase5-dev, - qtbase5-dev-tools, - qtdeclarative5-dev, - qtdeclarative5-dev-tools, Standards-Version: 3.9.4 Homepage: https://launchpad.net/indicator-sound # If you aren't a member of ~indicator-applet-developers but need to upload diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 73a270c..6c621ae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -207,4 +207,5 @@ install( RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/indicator-sound/ ) -add_subdirectory(gmenuharness) +# Disable integration tests +# add_subdirectory(gmenuharness) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index adc08de..f77bcb1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -281,6 +281,7 @@ add_test(indcator-test indicator-test ) -add_subdirectory(integration) -add_subdirectory(dbus-types) -add_subdirectory(service-mocks) \ No newline at end of file +# Disable integration tests +# add_subdirectory(integration) +# add_subdirectory(dbus-types) +# add_subdirectory(service-mocks) -- cgit v1.2.3