unity-system-compositor-0.4.3+16.04.20160323/0000755000015600001650000000000012674474477020773 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/cmake/0000755000015600001650000000000012674474477022053 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/cmake/FindGLESv2.cmake0000644000015600001650000000157312674474272024657 0ustar pbuserpbgroup00000000000000# - Try to find GLESv2 # Once done this will define # GLESv2_FOUND - System has GLESv2 # GLESv2_INCLUDE_DIRS - The GLESv2 include directories # GLESv2_LIBRARIES - The libraries needed to use GLESv2 find_package(PkgConfig) pkg_check_modules(PC_GLESv2 QUIET glesv2) find_path(GLESv2_INCLUDE_DIR GLES2/gl2.h HINTS ${PC_GLESv2_INCLUDEDIR} ${PC_GLESv2_INCLUDE_DIRS}) find_library(GLESv2_LIBRARY GLESv2 HINTS ${PC_GLESv2_LIBDIR} ${PC_GLESv2_LIBRARY_DIRS}) set(GLESv2_LIBRARIES ${GLESv2_LIBRARY}) set(GLESv2_INCLUDE_DIRS ${GLESv2_INCLUDE_DIR}) include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set GLESv2_FOUND to TRUE # if all listed variables are TRUE find_package_handle_standard_args(GLESv2 DEFAULT_MSG GLESv2_LIBRARY GLESv2_INCLUDE_DIR) mark_as_advanced(GLESv2_INCLUDE_DIR GLESv2_LIBRARY) unity-system-compositor-0.4.3+16.04.20160323/cmake/FindGtest.cmake0000644000015600001650000000337012674474272024740 0ustar pbuserpbgroup00000000000000include(ExternalProject) include(FindPackageHandleStandardArgs) #gtest set(GTEST_INSTALL_DIR /usr/src/gmock/gtest/include) find_path(GTEST_INCLUDE_DIR gtest/gtest.h HINTS ${GTEST_INSTALL_DIR}) #gmock find_path(GMOCK_INSTALL_DIR gmock/CMakeLists.txt HINTS /usr/src) if(${GMOCK_INSTALL_DIR} STREQUAL "GMOCK_INSTALL_DIR-NOTFOUND") message(FATAL_ERROR "google-mock package not found") endif() set(GMOCK_INSTALL_DIR ${GMOCK_INSTALL_DIR}/gmock) find_path(GMOCK_INCLUDE_DIR gmock/gmock.h) set(GMOCK_PREFIX gmock) set(GMOCK_BINARY_DIR ${CMAKE_BINARY_DIR}/${GMOCK_PREFIX}/libs) set(GTEST_BINARY_DIR ${GMOCK_BINARY_DIR}/gtest) set(GTEST_CMAKE_ARGS "") if (${CMAKE_CROSSCOMPILING}) set(GTEST_CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_MODULE_PATH}/LinuxCrossCompile.cmake) endif() ExternalProject_Add( GMock #where to build in source tree PREFIX ${GMOCK_PREFIX} #where the source is external to the project SOURCE_DIR ${GMOCK_INSTALL_DIR} #forward the compilers to the subproject so cross-arch builds work CMAKE_ARGS ${GTEST_CMAKE_ARGS} BINARY_DIR ${GMOCK_BINARY_DIR} #we don't need to install, so skip INSTALL_COMMAND "" ) set(GMOCK_LIBRARY ${GMOCK_BINARY_DIR}/libgmock.a) set(GMOCK_MAIN_LIBRARY ${GMOCK_BINARY_DIR}/libgmock_main.a) set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARY} ${GMOCK_MAIN_LIBRARY}) set(GTEST_LIBRARY ${GTEST_BINARY_DIR}/libgtest.a) set(GTEST_MAIN_LIBRARY ${GTEST_BINARY_DIR}/libgtest_main.a) set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY}) set(GTEST_ALL_LIBRARIES ${GTEST_BOTH_LIBRARIES} ${GMOCK_BOTH_LIBRARIES}) find_package_handle_standard_args(GTest DEFAULT_MSG GMOCK_INCLUDE_DIR GTEST_INCLUDE_DIR) unity-system-compositor-0.4.3+16.04.20160323/cmake/FindPIL.cmake0000644000015600001650000000030012674474272024264 0ustar pbuserpbgroup00000000000000execute_process( COMMAND python -c "from PIL import Image" RESULT_VARIABLE HAVE_PIL ) if (NOT ${HAVE_PIL} EQUAL 0) message(FATAL_ERROR "Python Imaging Library (PIL) not found") endif() unity-system-compositor-0.4.3+16.04.20160323/tests/0000755000015600001650000000000012674474477022135 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/tests/unit-tests/0000755000015600001650000000000012674474477024254 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/tests/unit-tests/test_mir_input_configuration.cpp0000644000015600001650000000746012674474272032754 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #include "src/mir_input_configuration.h" #include "mir/input/input_device_hub.h" #include "mir/input/device_capability.h" #include "mir/input/input_device_observer.h" #include "mir/input/device.h" #include #include namespace mi = mir::input; using namespace ::testing; namespace { struct MockDevice : mi::Device { MOCK_CONST_METHOD0(id, MirInputDeviceId()); MOCK_CONST_METHOD0(capabilities, mi::DeviceCapabilities()); MOCK_CONST_METHOD0(name, std::string()); MOCK_CONST_METHOD0(unique_id, std::string()); MOCK_CONST_METHOD0(pointer_configuration, mir::optional_value()); MOCK_METHOD1(apply_pointer_configuration, void(mi::PointerConfiguration const&)); MOCK_CONST_METHOD0(touchpad_configuration, mir::optional_value ()); MOCK_METHOD1(apply_touchpad_configuration, void(mi::TouchpadConfiguration const&)); MockDevice(mi::DeviceCapabilities caps) : caps(caps) { ON_CALL(*this, capabilities()).WillByDefault(Return(this->caps)); } mi::DeviceCapabilities caps; }; struct MockInputDeviceHub : mi::InputDeviceHub { MOCK_METHOD1(add_observer,void(std::shared_ptr const&)); MOCK_METHOD1(remove_observer,void(std::weak_ptr const&)); }; struct MirInputConfiguration : ::testing::Test { template using shared_mock = std::shared_ptr<::testing::NiceMock>; shared_mock mock_hub{std::make_shared<::testing::NiceMock>()}; shared_mock mock_mouse{ std::make_shared<::testing::NiceMock>(mi::DeviceCapability::pointer)}; shared_mock mock_touchpad{std::make_shared<::testing::NiceMock>( mi::DeviceCapability::pointer | mi::DeviceCapability::touchpad)}; shared_mock mock_keyboard{std::make_shared<::testing::NiceMock>( mi::DeviceCapability::keyboard | mi::DeviceCapability::alpha_numeric)}; std::shared_ptr obs; MirInputConfiguration() { ON_CALL(*mock_hub, add_observer(_)).WillByDefault(SaveArg<0>(&obs)); } }; } TEST_F(MirInputConfiguration, registers_something_as_device_observer) { EXPECT_CALL(*mock_hub, add_observer(_)); usc::MirInputConfiguration config(mock_hub); } TEST_F(MirInputConfiguration, configures_device_on_add) { usc::MirInputConfiguration config(mock_hub); EXPECT_CALL(*mock_touchpad, apply_pointer_configuration(_)); EXPECT_CALL(*mock_touchpad, apply_touchpad_configuration(_)); obs->device_added(mock_touchpad); } TEST_F(MirInputConfiguration, configures_mouse_on_add) { usc::MirInputConfiguration config(mock_hub); EXPECT_CALL(*mock_mouse, apply_pointer_configuration(_)); obs->device_added(mock_mouse); } TEST_F(MirInputConfiguration, ignores_keyboard_when_added) { usc::MirInputConfiguration config(mock_hub); EXPECT_CALL(*mock_keyboard, apply_touchpad_configuration(_)).Times(0); EXPECT_CALL(*mock_keyboard, apply_pointer_configuration(_)).Times(0); obs->device_added(mock_keyboard); } unity-system-compositor-0.4.3+16.04.20160323/tests/unit-tests/test_mir_screen.cpp0000644000015600001650000010135712674474277030152 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "src/mir_screen.h" #include "src/performance_booster.h" #include "src/screen_hardware.h" #include "src/power_state_change_reason.h" #include "advanceable_timer.h" #include "usc/test/mock_display.h" #include #include #include #include #include #include #include namespace mg = mir::graphics; namespace ut = usc::test; namespace { struct MockCompositor : mir::compositor::Compositor { MOCK_METHOD0(start, void()); MOCK_METHOD0(stop, void()); }; struct MockPerformanceBooster : usc::PerformanceBooster { MOCK_METHOD0(enable_performance_boost_during_user_interaction, void()); MOCK_METHOD0(disable_performance_boost_during_user_interaction, void()); }; struct MockScreenHardware : usc::ScreenHardware { MOCK_METHOD0(set_dim_backlight, void()); MOCK_METHOD0(set_normal_backlight, void()); MOCK_METHOD0(turn_off_backlight, void()); void change_backlight_values(int dim_brightness, int normal_brightness) override {} MOCK_METHOD0(allow_suspend, void()); MOCK_METHOD0(disable_suspend, void()); MOCK_METHOD1(enable_auto_brightness, void(bool)); bool auto_brightness_supported() override { return true; } MOCK_METHOD1(set_brightness, void(int)); int min_brightness() override { return 100; } int max_brightness() override { return 0; } enum class Proximity {near, far}; void enable_proximity(bool enable) override { proximity_enabled = enable; set_proximity(current_proximity); } void set_proximity(Proximity proximity) { current_proximity = proximity; if (proximity_enabled) on_proximity_changed(current_proximity); } std::function on_proximity_changed = [](Proximity){}; Proximity current_proximity{Proximity::far}; bool proximity_enabled{false}; }; struct MockTouchVisualizer : mir::input::TouchVisualizer { MOCK_METHOD0(enable, void()); MOCK_METHOD0(disable, void()); // Visualize a given set of touches statelessly. void visualize_touches(std::vector const& touches) override {} }; struct AMirScreen : testing::Test { AMirScreen() { using namespace testing; screen_hardware->on_proximity_changed = [this] (MockScreenHardware::Proximity p) { defer_proximity_event(p); }; } void defer_proximity_event(MockScreenHardware::Proximity proximity) { deferred_actions.push_back( [this, proximity] { mir_screen.set_screen_power_mode( proximity == MockScreenHardware::Proximity::far ? MirPowerMode::mir_power_mode_on : MirPowerMode::mir_power_mode_off, PowerStateChangeReason::proximity); }); } void process_deferred_actions() { for (auto const& a : deferred_actions) a(); deferred_actions.clear(); } std::vector> deferred_actions; std::chrono::seconds const power_off_timeout{60}; std::chrono::seconds const dimmer_timeout{50}; std::chrono::seconds const notification_power_off_timeout{15}; std::chrono::seconds const notification_dimmer_timeout{12}; std::chrono::seconds const call_power_off_timeout{30}; std::chrono::seconds const call_dimmer_timeout{25}; std::chrono::seconds const five_seconds{5}; std::chrono::seconds const ten_seconds{10}; std::chrono::seconds const fifteen_seconds{15}; std::chrono::seconds const thirty_seconds{30}; std::chrono::seconds const fourty_seconds{40}; std::chrono::seconds const fifty_seconds{50}; void expect_performance_boost_is_enabled() { using namespace testing; EXPECT_CALL(*performance_booster, enable_performance_boost_during_user_interaction()); } void expect_performance_boost_is_disabled() { using namespace testing; EXPECT_CALL(*performance_booster, disable_performance_boost_during_user_interaction()); } void expect_screen_is_turned_off() { using namespace testing; EXPECT_CALL(*screen_hardware, turn_off_backlight()); EXPECT_CALL(*screen_hardware, allow_suspend()); InSequence s; EXPECT_CALL(*compositor, stop()); EXPECT_CALL(*display, configure(_)); } void expect_screen_is_turned_on() { using namespace testing; EXPECT_CALL(*screen_hardware, disable_suspend()); EXPECT_CALL(*screen_hardware, set_normal_backlight()); InSequence s; EXPECT_CALL(*compositor, stop()); EXPECT_CALL(*display, configure(_)); EXPECT_CALL(*compositor, start()); } void expect_screen_is_turned_dim() { using namespace testing; EXPECT_CALL(*screen_hardware, set_dim_backlight()); } void expect_no_reconfiguration() { using namespace testing; EXPECT_CALL(*display, configure(_)).Times(0); EXPECT_CALL(*screen_hardware, set_dim_backlight()).Times(0); } void verify_proximity_enabled() { verify_and_clear_expectations(); if (screen_hardware->current_proximity == MockScreenHardware::Proximity::far) { expect_screen_is_turned_off(); cover_screen(); verify_and_clear_expectations(); expect_screen_is_turned_on(); uncover_screen(); verify_and_clear_expectations(); } if (screen_hardware->current_proximity == MockScreenHardware::Proximity::near) { expect_screen_is_turned_on(); uncover_screen(); verify_and_clear_expectations(); expect_screen_is_turned_off(); cover_screen(); verify_and_clear_expectations(); } } void verify_proximity_disabled() { verify_and_clear_expectations(); expect_no_reconfiguration(); uncover_screen(); cover_screen(); uncover_screen(); verify_and_clear_expectations(); } void turn_screen_off() { using namespace testing; mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key); verify_and_clear_expectations(); } void turn_screen_on() { using namespace testing; mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); verify_and_clear_expectations(); } void receive_notification() { mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::notification); process_deferred_actions(); } void receive_call() { mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::snap_decision); process_deferred_actions(); } void receive_call_done() { mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::call_done); process_deferred_actions(); } void cover_screen() { screen_hardware->set_proximity(MockScreenHardware::Proximity::near); process_deferred_actions(); } void uncover_screen() { screen_hardware->set_proximity(MockScreenHardware::Proximity::far); process_deferred_actions(); } void verify_and_clear_expectations() { using namespace testing; Mock::VerifyAndClearExpectations(screen_hardware.get()); Mock::VerifyAndClearExpectations(display.get()); Mock::VerifyAndClearExpectations(compositor.get()); } std::shared_ptr performance_booster{ std::make_shared>()}; std::shared_ptr screen_hardware{ std::make_shared>()}; std::shared_ptr compositor{ std::make_shared>()}; std::shared_ptr display{ std::make_shared>()}; std::shared_ptr touch_visualizer{ std::make_shared>()}; std::shared_ptr timer{ std::make_shared()}; usc::MirScreen mir_screen{ performance_booster, screen_hardware, compositor, display, touch_visualizer, timer, timer, {power_off_timeout, dimmer_timeout}, {notification_power_off_timeout, notification_dimmer_timeout}, {call_power_off_timeout, call_dimmer_timeout}}; }; struct AParameterizedMirScreen : public AMirScreen, public ::testing::WithParamInterface {}; struct ImmediatePowerOnMirScreen : public AParameterizedMirScreen {}; struct DeferredPowerOnMirScreen : public AParameterizedMirScreen {}; } TEST_P(ImmediatePowerOnMirScreen, enables_performance_boost_for_screen_on) { turn_screen_off(); expect_performance_boost_is_enabled(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, GetParam()); } TEST_P(DeferredPowerOnMirScreen, enables_performance_boost_for_screen_on_with_reason_proximity) { turn_screen_off(); expect_performance_boost_is_enabled(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, GetParam()); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); } TEST_P(AParameterizedMirScreen, disables_performance_boost_for_screen_off) { turn_screen_on(); expect_performance_boost_is_disabled(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_off, GetParam()); } INSTANTIATE_TEST_CASE_P( AParameterizedMirScreen, AParameterizedMirScreen, ::testing::Values(PowerStateChangeReason::unknown, PowerStateChangeReason::inactivity, PowerStateChangeReason::power_key, PowerStateChangeReason::proximity, PowerStateChangeReason::notification, PowerStateChangeReason::snap_decision, PowerStateChangeReason::call_done)); INSTANTIATE_TEST_CASE_P( ImmediatePowerOnMirScreen, ImmediatePowerOnMirScreen, ::testing::Values(PowerStateChangeReason::unknown, PowerStateChangeReason::inactivity, PowerStateChangeReason::power_key)); INSTANTIATE_TEST_CASE_P( DeferredPowerOnMirScreen, DeferredPowerOnMirScreen, ::testing::Values(PowerStateChangeReason::notification, PowerStateChangeReason::snap_decision, PowerStateChangeReason::call_done)); TEST_F(AMirScreen, turns_screen_off_after_power_off_timeout) { expect_screen_is_turned_off(); timer->advance_by(power_off_timeout); } TEST_F(AMirScreen, dims_screen_after_dim_timeout) { expect_screen_is_turned_dim(); timer->advance_by(dimmer_timeout); } TEST_F(AMirScreen, keeps_display_on_while_already_on) { using namespace testing; expect_no_reconfiguration(); mir_screen.keep_display_on(true); timer->advance_by(power_off_timeout); } TEST_F(AMirScreen, turns_and_keeps_display_on_while_off) { using namespace testing; turn_screen_off(); expect_screen_is_turned_on(); mir_screen.keep_display_on(true); verify_and_clear_expectations(); expect_no_reconfiguration(); timer->advance_by(power_off_timeout); } TEST_F(AMirScreen, does_not_turn_on_screen_temporarily_when_off) { using namespace testing; turn_screen_off(); expect_no_reconfiguration(); mir_screen.keep_display_on_temporarily(); } TEST_F(AMirScreen, keeps_screen_on_temporarily_when_already_on) { using namespace testing; std::chrono::seconds const fourty_seconds{40}; std::chrono::seconds const ten_seconds{10}; expect_no_reconfiguration(); // After keep_display_on_temporarily the timeouts should // be reset... timer->advance_by(fourty_seconds); mir_screen.keep_display_on_temporarily(); // ... so 40 seconds after the reset (total 80 from start) // should trigger neither dim nor power off timer->advance_by(fourty_seconds); verify_and_clear_expectations(); // Tens seconds more, 50 seconds from reset, the screen should dim expect_screen_is_turned_dim(); timer->advance_by(ten_seconds); verify_and_clear_expectations(); // Ten seconds second more, 60 seconds from reset, the screen should power off expect_screen_is_turned_off(); timer->advance_by(ten_seconds); verify_and_clear_expectations(); } TEST_F(AMirScreen, disabling_inactivity_timers_disables_dim_and_power_off) { using namespace testing; expect_no_reconfiguration(); mir_screen.enable_inactivity_timers(false); timer->advance_by(power_off_timeout); } TEST_F(AMirScreen, set_screen_power_mode_from_on_to_off) { expect_screen_is_turned_off(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key); } TEST_F(AMirScreen, set_screen_power_mode_from_off_to_on) { turn_screen_off(); expect_screen_is_turned_on(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); } TEST_F(AMirScreen, sets_hardware_brightness) { int const brightness{10}; EXPECT_CALL(*screen_hardware, set_brightness(brightness)); mir_screen.set_brightness(brightness); } TEST_F(AMirScreen, sets_hardware_auto_brightness) { bool const enable{true}; EXPECT_CALL(*screen_hardware, enable_auto_brightness(enable)); mir_screen.enable_auto_brightness(enable); } TEST_F(AMirScreen, forwards_touch_visualizations_requests_to_touch_visualizer) { using namespace testing; InSequence s; EXPECT_CALL(*touch_visualizer, enable()); EXPECT_CALL(*touch_visualizer, disable()); mir_screen.set_touch_visualization_enabled(true); mir_screen.set_touch_visualization_enabled(false); } TEST_F(AMirScreen, invokes_handler_when_power_state_changes) { using namespace testing; auto handler_reason = PowerStateChangeReason::unknown; auto handler_mode = mir_power_mode_standby; auto handler = [&] (MirPowerMode mode, PowerStateChangeReason reason) { handler_mode = mode; handler_reason = reason; }; mir_screen.register_power_state_change_handler(handler); auto const toggle_reason = PowerStateChangeReason::power_key; mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key); EXPECT_THAT(handler_reason, Eq(toggle_reason)); EXPECT_THAT(handler_mode, Eq(MirPowerMode::mir_power_mode_off)); } TEST_F(AMirScreen, turns_screen_off_after_notification_timeout) { turn_screen_off(); expect_screen_is_turned_on(); receive_notification(); verify_and_clear_expectations(); expect_screen_is_turned_off(); timer->advance_by(notification_power_off_timeout); } TEST_F(AMirScreen, keep_display_on_temporarily_overrides_notification_timeout) { turn_screen_off(); expect_screen_is_turned_on(); receive_notification(); verify_and_clear_expectations(); // At T=10 we request a temporary keep display on (e.g. user has touched // the screen) timer->advance_by(ten_seconds); mir_screen.keep_display_on_temporarily(); // At T=20 nothing should happen since keep display on temporarily // has reset the timers (so the notification timeout of 15s is overriden). expect_no_reconfiguration(); timer->advance_by(ten_seconds); verify_and_clear_expectations(); // At T=70 (10 + 60) the screen should turn off due to the normal // inactivity timeout expect_screen_is_turned_off(); timer->advance_by(fifty_seconds); } TEST_F(AMirScreen, notification_timeout_extends_active_timeout) { // At T=0 we turn the screen on, and normal inactivity timeouts // are reset mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); // At T=50 we get a notification timer->advance_by(fifty_seconds); receive_notification(); verify_and_clear_expectations(); // At T=60 the screen should still be active because the notification // has extended the timeout. expect_no_reconfiguration(); timer->advance_by(ten_seconds); verify_and_clear_expectations(); // At T=65 (50 + 15) the screen should turn off due to the notification // inactivity timeout expect_screen_is_turned_off(); timer->advance_by(five_seconds); } TEST_F(AMirScreen, notification_timeout_does_not_reduce_active_timeout) { // At T=0 we turn the screen on, and normal inactivity timeouts // are reset mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); // At T=30 we get a notification timer->advance_by(thirty_seconds); receive_notification(); verify_and_clear_expectations(); // At T=45 the screen should still be active because the notification // has not reduced the active timeout. expect_no_reconfiguration(); timer->advance_by(fifteen_seconds); verify_and_clear_expectations(); // At T=50 the screen should be dimmed expect_screen_is_turned_dim(); timer->advance_by(five_seconds); verify_and_clear_expectations(); // At T=60 the screen should turn off due to the normal // inactivity timeout expect_screen_is_turned_off(); timer->advance_by(ten_seconds); } TEST_F(AMirScreen, notification_timeout_can_extend_only_dimming) { std::chrono::seconds const two_seconds{2}; std::chrono::seconds const eight_seconds{8}; // At T=0 we turn the screen on, and normal inactivity timeouts // are reset mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); // At T=40 we get a notification timer->advance_by(fourty_seconds); receive_notification(); verify_and_clear_expectations(); // At T=50 nothing should happen since the notification has // extended the dimming timeout expect_no_reconfiguration(); timer->advance_by(ten_seconds); verify_and_clear_expectations(); // At T=52 (40 + 12) screen should be dimmed due to the notification // dimming timeout expect_screen_is_turned_dim(); timer->advance_by(two_seconds); verify_and_clear_expectations(); // At T=60 the screen should turn off due to the normal // inactivity timeout expect_screen_is_turned_off(); timer->advance_by(eight_seconds); } TEST_F(AMirScreen, proximity_requests_affect_screen_state) { expect_screen_is_turned_off(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::proximity); verify_and_clear_expectations(); expect_screen_is_turned_on(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); verify_and_clear_expectations(); } TEST_F(AMirScreen, proximity_requests_use_short_timeouts) { // At T=0 we turn the screen on, and normal inactivity timeouts // are reset mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); // At T=30 we get a screen off request due to proximity timer->advance_by(thirty_seconds); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::proximity); // At T=40 we get a screen on request due to proximity timer->advance_by(ten_seconds); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); verify_and_clear_expectations(); // At T=52 screen should be dimmed due to the short inactivity // dimming timeout expect_screen_is_turned_dim(); timer->advance_by(notification_dimmer_timeout); verify_and_clear_expectations(); // At T=55 the screen should turn off due to the short // inactivity timeout expect_screen_is_turned_off(); timer->advance_by(std::chrono::seconds{3}); } TEST_F(AMirScreen, does_not_turn_on_screen_when_notification_arrives_with_phone_covered) { turn_screen_off(); cover_screen(); expect_no_reconfiguration(); receive_notification(); } TEST_F(AMirScreen, turns_screen_on_when_phone_is_uncovered_after_notification_arrives) { turn_screen_off(); cover_screen(); expect_no_reconfiguration(); receive_notification(); verify_and_clear_expectations(); expect_screen_is_turned_on(); uncover_screen(); } TEST_F(AMirScreen, cancels_proximity_handling_when_phone_is_turned_off_after_notification) { turn_screen_off(); cover_screen(); receive_notification(); timer->advance_by(notification_power_off_timeout); verify_and_clear_expectations(); expect_no_reconfiguration(); uncover_screen(); cover_screen(); } TEST_F(AMirScreen, cancels_proximity_handling_when_screen_is_touched_after_notification) { turn_screen_off(); receive_notification(); mir_screen.keep_display_on_temporarily(); verify_and_clear_expectations(); expect_no_reconfiguration(); cover_screen(); uncover_screen(); } TEST_F(AMirScreen, does_not_enable_proximity_handling_for_notification_when_screen_is_already_on) { expect_no_reconfiguration(); receive_notification(); verify_and_clear_expectations(); expect_no_reconfiguration(); cover_screen(); uncover_screen(); } TEST_F(AMirScreen, does_not_allow_proximity_to_turn_on_screen_not_turned_off_by_proximity) { turn_screen_off(); expect_no_reconfiguration(); mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); verify_and_clear_expectations(); expect_no_reconfiguration(); mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_off, PowerStateChangeReason::proximity); verify_and_clear_expectations(); expect_no_reconfiguration(); mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); } TEST_F(AMirScreen, does_not_allow_proximity_to_turn_on_screen_not_turned_off_by_proximity_2) { mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_off, PowerStateChangeReason::proximity); mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key); verify_and_clear_expectations(); expect_no_reconfiguration(); mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); } TEST_F(AMirScreen, proximity_can_affect_screen_after_keep_display_on) { mir_screen.keep_display_on(true); expect_screen_is_turned_off(); mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_off, PowerStateChangeReason::proximity); verify_and_clear_expectations(); expect_screen_is_turned_on(); mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); } TEST_F(AMirScreen, disables_active_timeout_when_setting_zero_inactivity_timeouts) { std::chrono::hours const ten_hours{10}; expect_no_reconfiguration(); mir_screen.set_inactivity_timeouts(0, 0); timer->advance_by(ten_hours); verify_and_clear_expectations(); } TEST_F(AMirScreen, notification_timeout_is_ignored_if_inactivity_timeouts_are_zero) { std::chrono::hours const ten_hours{10}; expect_no_reconfiguration(); mir_screen.set_inactivity_timeouts(0, 0); receive_notification(); timer->advance_by(ten_hours); verify_and_clear_expectations(); } TEST_F(AMirScreen, notification_timeout_is_respected_when_screen_is_off_if_inactivity_timeouts_are_zero) { mir_screen.set_inactivity_timeouts(0, 0); turn_screen_off(); receive_notification(); verify_and_clear_expectations(); expect_screen_is_turned_off(); timer->advance_by(notification_power_off_timeout); verify_and_clear_expectations(); } TEST_F(AMirScreen, keep_display_on_temporarily_after_notification_keeps_display_on_forever_if_inactivity_timeouts_are_zero) { std::chrono::hours const ten_hours{10}; mir_screen.set_inactivity_timeouts(0, 0); turn_screen_off(); receive_notification(); verify_and_clear_expectations(); expect_no_reconfiguration(); mir_screen.keep_display_on_temporarily(); timer->advance_by(ten_hours); verify_and_clear_expectations(); } TEST_F(AMirScreen, proximity_can_turn_on_screen_after_power_off_timeout_occurs_when_screen_is_off) { mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::proximity); timer->advance_by(power_off_timeout); expect_screen_is_turned_on(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); verify_and_clear_expectations(); expect_screen_is_turned_off(); timer->advance_by(power_off_timeout); } TEST_F(AMirScreen, proximity_can_turn_on_screen_after_power_off_timeout_occurs_when_screen_is_on) { mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); expect_no_reconfiguration(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); verify_and_clear_expectations(); expect_screen_is_turned_off(); timer->advance_by(power_off_timeout); verify_and_clear_expectations(); expect_screen_is_turned_on(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); verify_and_clear_expectations(); expect_screen_is_turned_off(); timer->advance_by(notification_power_off_timeout); } TEST_F(AMirScreen, proximity_cannot_turn_on_screen_if_power_key_turned_it_off) { mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key); expect_no_reconfiguration(); mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); verify_and_clear_expectations(); expect_no_reconfiguration(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::proximity); verify_and_clear_expectations(); expect_no_reconfiguration(); mir_screen.set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::proximity); } TEST_F(AMirScreen, turns_screen_off_after_call_timeout) { turn_screen_off(); expect_screen_is_turned_on(); receive_call(); verify_and_clear_expectations(); expect_screen_is_turned_off(); timer->advance_by(call_power_off_timeout); } TEST_F(AMirScreen, keep_display_on_temporarily_overrides_call_timeout) { turn_screen_off(); expect_screen_is_turned_on(); receive_call(); verify_and_clear_expectations(); // At T=20 we request a temporary keep display on (e.g. user has touched // the screen) timer->advance_by(ten_seconds); timer->advance_by(ten_seconds); mir_screen.keep_display_on_temporarily(); // At T=30 nothing should happen since keep display on temporarily // has reset the timers (so the call timeout is overriden). expect_no_reconfiguration(); timer->advance_by(ten_seconds); verify_and_clear_expectations(); // At T=110 (50 + 60) the screen should turn off due to the normal // inactivity timeout expect_screen_is_turned_off(); timer->advance_by(fifty_seconds); } TEST_F(AMirScreen, does_not_turn_on_screen_when_call_arrives_with_phone_covered) { turn_screen_off(); cover_screen(); expect_no_reconfiguration(); receive_call(); } TEST_F(AMirScreen, enables_proximity_when_call_arrives) { turn_screen_off(); cover_screen(); expect_no_reconfiguration(); receive_call(); verify_and_clear_expectations(); verify_proximity_enabled(); } TEST_F(AMirScreen, cancels_proximity_handling_when_screen_is_turned_off_by_call_timeout) { turn_screen_off(); cover_screen(); receive_call(); timer->advance_by(call_power_off_timeout); verify_proximity_disabled(); } TEST_F(AMirScreen, cancels_proximity_handling_when_screen_is_touched_after_call) { turn_screen_off(); receive_call(); mir_screen.keep_display_on_temporarily(); verify_proximity_disabled(); } TEST_F(AMirScreen, does_not_enable_proximity_handling_for_call_when_screen_is_already_on) { expect_no_reconfiguration(); receive_call(); verify_and_clear_expectations(); verify_proximity_disabled(); } TEST_F(AMirScreen, retains_proximity_handling_when_call_done_arrives_when_screen_is_off_after_call) { turn_screen_off(); cover_screen(); receive_call(); receive_call_done(); verify_proximity_enabled(); } TEST_F(AMirScreen, turns_off_screen_and_proximity_soon_after_call_when_screen_not_covered) { turn_screen_off(); // Call receive_call(); timer->advance_by(ten_seconds); receive_call_done(); verify_and_clear_expectations(); // After the notification timeout the screen should be off // and proximity should be disabled expect_screen_is_turned_off(); timer->advance_by(notification_power_off_timeout); verify_proximity_disabled(); } TEST_F(AMirScreen, turns_proximity_off_soon_after_call_when_screen_covered) { turn_screen_off(); cover_screen(); expect_no_reconfiguration(); // call receive_call(); timer->advance_by(ten_seconds); receive_call_done(); timer->advance_by(notification_power_off_timeout); verify_and_clear_expectations(); verify_proximity_disabled(); } TEST_F(AMirScreen, turns_off_screen_and_proximity_soon_after_call_when_screen_uncovered_after_call_is_received) { turn_screen_off(); cover_screen(); // Uncover screen while in call receive_call(); timer->advance_by(five_seconds); uncover_screen(); timer->advance_by(five_seconds); receive_call_done(); verify_and_clear_expectations(); // After the notification timeout the screen should be off // and proximity should be disabled expect_screen_is_turned_off(); timer->advance_by(notification_power_off_timeout); verify_proximity_disabled(); } TEST_F(AMirScreen, keeps_screen_on_for_full_timeout_after_call_received_when_screen_was_turned_on_manually) { // Turn screen on manually (i.e. with power key) turn_screen_off(); turn_screen_on(); expect_no_reconfiguration(); // call receive_call(); timer->advance_by(ten_seconds); receive_call_done(); // Call done notification timeout should be ignored since // user had turned on the screen manually timer->advance_by(notification_power_off_timeout); verify_and_clear_expectations(); verify_proximity_disabled(); } TEST_F(AMirScreen, sets_normal_backlight_when_notification_arrives_while_screen_already_on) { turn_screen_on(); EXPECT_CALL(*screen_hardware, set_normal_backlight()); receive_notification(); } TEST_F(AMirScreen, sets_normal_backlight_when_call_arrives_while_screen_already_on) { turn_screen_on(); EXPECT_CALL(*screen_hardware, set_normal_backlight()); receive_call(); } unity-system-compositor-0.4.3+16.04.20160323/tests/unit-tests/fake_shared.h0000644000015600001650000000162512674474272026656 0ustar pbuserpbgroup00000000000000/* * Copyright © 2013-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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alan Griffiths */ #ifndef USC_TEST_FAKE_SHARED_H_ #define USC_TEST_FAKE_SHARED_H_ #include namespace usc { namespace test { template std::shared_ptr fake_shared(Type& t) { return {&t, [](void*){}}; } } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/unit-tests/test_screen_event_handler.cpp0000644000015600001650000002436712674474272032201 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "src/screen_event_handler.h" #include "src/power_state_change_reason.h" #include "src/screen.h" #include "advanceable_timer.h" #include "fake_shared.h" #include #include #include #include "linux/input.h" #include namespace { struct MockScreen : usc::Screen { MOCK_METHOD1(enable_inactivity_timers, void(bool enable)); MOCK_METHOD0(keep_display_on_temporarily, void()); MirPowerMode get_screen_power_mode() override {return mock_mode;} MOCK_METHOD2(set_screen_power_mode, void(MirPowerMode mode, PowerStateChangeReason reason)); MOCK_METHOD1(keep_display_on, void(bool on)); MOCK_METHOD1(set_brightness, void(int brightness)); MOCK_METHOD1(enable_auto_brightness, void(bool enable)); MOCK_METHOD2(set_inactivity_timeouts, void(int power_off_timeout, int dimmer_timeout)); MOCK_METHOD1(set_touch_visualization_enabled, void(bool enabled)); MOCK_METHOD1(register_power_state_change_handler, void( usc::PowerStateChangeHandler const& handler)); MirPowerMode mock_mode = MirPowerMode::mir_power_mode_off; }; struct AScreenEventHandler : testing::Test { void press_power_key() { screen_event_handler.handle(*power_key_down_event); } void release_power_key() { screen_event_handler.handle(*power_key_up_event); } void press_a_key() { screen_event_handler.handle(*another_key_down_event); } void press_volume_keys() { screen_event_handler.handle(*vol_plus_key_down_event); screen_event_handler.handle(*vol_plus_key_up_event); screen_event_handler.handle(*vol_minus_key_down_event); screen_event_handler.handle(*vol_minus_key_up_event); } void touch_screen() { screen_event_handler.handle(*touch_event); } void move_pointer() { screen_event_handler.handle(*pointer_event); } mir::EventUPtr power_key_down_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_keyboard_action_down, 0, KEY_POWER, mir_input_event_modifier_none); mir::EventUPtr power_key_up_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_keyboard_action_up, 0, KEY_POWER, mir_input_event_modifier_none); mir::EventUPtr vol_plus_key_down_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_keyboard_action_down, 0, KEY_VOLUMEUP, mir_input_event_modifier_none); mir::EventUPtr vol_plus_key_up_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_keyboard_action_up, 0, KEY_VOLUMEUP, mir_input_event_modifier_none); mir::EventUPtr vol_minus_key_down_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_keyboard_action_down, 0, KEY_VOLUMEDOWN, mir_input_event_modifier_none); mir::EventUPtr vol_minus_key_up_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_keyboard_action_up, 0, KEY_VOLUMEDOWN, mir_input_event_modifier_none); mir::EventUPtr another_key_down_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_keyboard_action_down, 0, KEY_A, mir_input_event_modifier_none); mir::EventUPtr another_key_up_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_keyboard_action_up, 0, KEY_A, mir_input_event_modifier_none); mir::EventUPtr touch_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_input_event_modifier_none); mir::EventUPtr pointer_event = mir::events::make_event( MirInputDeviceId{1}, std::chrono::nanoseconds(0), std::vector{}, mir_input_event_modifier_none, mir_pointer_action_motion, {}, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); AdvanceableTimer timer; std::chrono::milliseconds const power_key_ignore_timeout{5000}; std::chrono::milliseconds const shutdown_timeout{10000}; std::chrono::milliseconds const normal_press_duration{100}; testing::NiceMock mock_screen; std::atomic shutdown_called{false}; usc::ScreenEventHandler screen_event_handler{ usc::test::fake_shared(mock_screen), usc::test::fake_shared(timer), power_key_ignore_timeout, shutdown_timeout, [&] { shutdown_called = true; }}; }; } TEST_F(AScreenEventHandler, turns_screen_on_immediately_on_press) { auto const long_press_duration = power_key_ignore_timeout; EXPECT_CALL(mock_screen, set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key)); press_power_key(); } TEST_F(AScreenEventHandler, shuts_down_system_when_power_key_pressed_for_long_enough) { using namespace testing; std::chrono::milliseconds const one_ms{1}; press_power_key(); timer.advance_by(shutdown_timeout - one_ms); ASSERT_FALSE(shutdown_called); timer.advance_by(one_ms); ASSERT_TRUE(shutdown_called); } TEST_F(AScreenEventHandler, turns_screen_off_when_shutting_down) { press_power_key(); testing::InSequence s; // First, screen turns on from long press EXPECT_CALL(mock_screen, set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key)); EXPECT_CALL(mock_screen, set_screen_power_mode(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key)); timer.advance_by(shutdown_timeout); } TEST_F(AScreenEventHandler, keeps_display_on_temporarily_on_touch_event) { EXPECT_CALL(mock_screen, keep_display_on_temporarily()); touch_screen(); } TEST_F(AScreenEventHandler, turns_on_screen_and_filters_first_pointer_event_when_screen_is_off) { mock_screen.mock_mode = MirPowerMode::mir_power_mode_off; EXPECT_CALL(mock_screen, set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::unknown)); auto const event_filtered = screen_event_handler.handle(*pointer_event); EXPECT_TRUE(event_filtered); } TEST_F(AScreenEventHandler, turns_on_screen_and_propagates_keys_when_screen_is_off) { mock_screen.mock_mode = MirPowerMode::mir_power_mode_off; EXPECT_CALL(mock_screen, set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::unknown)); auto const event_filtered = screen_event_handler.handle(*another_key_down_event); EXPECT_FALSE(event_filtered); } TEST_F(AScreenEventHandler, keeps_display_on_temporarily_for_pointer_event_when_screen_is_on) { mock_screen.mock_mode = MirPowerMode::mir_power_mode_on; EXPECT_CALL(mock_screen, keep_display_on_temporarily()); move_pointer(); } TEST_F(AScreenEventHandler, keeps_display_on_temporarily_key_event_when_screen_is_on) { mock_screen.mock_mode = MirPowerMode::mir_power_mode_on; EXPECT_CALL(mock_screen, keep_display_on_temporarily()); press_a_key(); } TEST_F(AScreenEventHandler, does_not_affect_screen_state_for_volume_keys) { using namespace testing; EXPECT_CALL(mock_screen, keep_display_on_temporarily()).Times(0); EXPECT_CALL(mock_screen, set_screen_power_mode(_,_)).Times(0); press_volume_keys(); } TEST_F(AScreenEventHandler, sets_screen_mode_off_normal_press_release) { EXPECT_CALL(mock_screen, set_screen_power_mode(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key)); mock_screen.mock_mode = MirPowerMode::mir_power_mode_on; press_power_key(); timer.advance_by(normal_press_duration); release_power_key(); } TEST_F(AScreenEventHandler, does_not_set_screen_mode_off_long_press_release) { using namespace testing; auto const long_press_duration = power_key_ignore_timeout; EXPECT_CALL(mock_screen, set_screen_power_mode(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key)); EXPECT_CALL(mock_screen, set_screen_power_mode(MirPowerMode::mir_power_mode_off, _)) .Times(0); mock_screen.mock_mode = MirPowerMode::mir_power_mode_on; press_power_key(); timer.advance_by(long_press_duration); release_power_key(); } TEST_F(AScreenEventHandler, passes_through_all_handled_events_when_screen_is_on) { using namespace testing; mock_screen.mock_mode = MirPowerMode::mir_power_mode_on; EXPECT_FALSE(screen_event_handler.handle(*power_key_down_event)); EXPECT_FALSE(screen_event_handler.handle(*power_key_up_event)); EXPECT_FALSE(screen_event_handler.handle(*touch_event)); EXPECT_FALSE(screen_event_handler.handle(*pointer_event)); } TEST_F(AScreenEventHandler, disables_inactivity_timers_on_power_key_down) { EXPECT_CALL(mock_screen, enable_inactivity_timers(false)); press_power_key(); } TEST_F(AScreenEventHandler, enables_inactivity_timers_on_power_key_up_when_turning_screen_on) { press_power_key(); timer.advance_by(normal_press_duration); EXPECT_CALL(mock_screen, enable_inactivity_timers(true)); release_power_key(); } unity-system-compositor-0.4.3+16.04.20160323/tests/unit-tests/advanceable_timer.h0000644000015600001650000000325312674474272030046 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_TESTS_ADVANCEABLE_TIMER_H_ #define USC_TESTS_ADVANCEABLE_TIMER_H_ #include #include "src/clock.h" #include #include #include namespace detail { class AdvanceableAlarm; } class AdvanceableTimer : public mir::time::AlarmFactory, public usc::Clock { public: // mir::time::AlarmFactory std::unique_ptr create_alarm( std::function const& callback) override; std::unique_ptr create_alarm( std::shared_ptr const& callback) override; // usc::Clock mir::time::Timestamp now() const override; void advance_by(std::chrono::milliseconds advance); private: void register_alarm(std::shared_ptr const& alarm); void trigger_alarms(); mutable std::mutex now_mutex; mir::time::Timestamp now_{}; std::mutex alarms_mutex; std::vector> alarms; }; #endif unity-system-compositor-0.4.3+16.04.20160323/tests/unit-tests/advanceable_timer.cpp0000644000015600001650000001333312674474272030401 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "advanceable_timer.h" #include #include namespace { struct AlarmWrapper : mir::time::Alarm { AlarmWrapper( std::shared_ptr const& alarm) : wrapped_alarm{alarm} { } bool cancel() override { return wrapped_alarm->cancel(); } mir::time::Alarm::State state() const override { return wrapped_alarm->state(); } bool reschedule_in(std::chrono::milliseconds delay) override { return wrapped_alarm->reschedule_in(delay); } bool reschedule_for(mir::time::Timestamp t) override { return wrapped_alarm->reschedule_for(t); } std::shared_ptr const wrapped_alarm; }; }; struct detail::AdvanceableAlarm : mir::time::Alarm { AdvanceableAlarm( mir::time::Timestamp now, std::shared_ptr const& callback) : now{now}, callback{callback} { } bool cancel() override { std::lock_guard lock{mutex}; state_ = mir::time::Alarm::cancelled; return true; } mir::time::Alarm::State state() const override { std::lock_guard lock{mutex}; return state_; } bool reschedule_in(std::chrono::milliseconds delay) override { std::lock_guard lock{mutex}; next_trigger_ = now + delay; state_ = mir::time::Alarm::pending; return true; } bool reschedule_for(mir::time::Timestamp t) override { std::lock_guard lock{mutex}; next_trigger_ = t; state_ = mir::time::Alarm::pending; return true; } void update_now(mir::time::Timestamp t) { std::unique_lock lock{mutex}; now = t; if (state_ == mir::time::Alarm::pending && now >= next_trigger_) { state_ = mir::time::Alarm::triggered; lock.unlock(); callback->lock(); (*callback)(); callback->unlock(); } } mir::time::Timestamp next_trigger() { std::lock_guard lock{mutex}; return next_trigger_; } private: mir::time::Timestamp now; std::shared_ptr const callback; mir::time::Timestamp next_trigger_; mutable std::mutex mutex; mir::time::Alarm::State state_ = mir::time::Alarm::triggered; }; std::unique_ptr AdvanceableTimer::create_alarm( std::function const& callback) { struct SimpleLockableCallback : public mir::LockableCallback { SimpleLockableCallback(std::function const& callback) : callback{callback} { } void operator()() override { callback(); } void lock() override {} void unlock() override {} std::function const callback; }; return create_alarm(std::make_shared(callback)); } std::unique_ptr AdvanceableTimer::create_alarm( std::shared_ptr const& callback) { auto const adv_alarm = std::make_shared(now(), callback); register_alarm(adv_alarm); return std::unique_ptr(new AlarmWrapper{adv_alarm}); } void AdvanceableTimer::advance_by(std::chrono::milliseconds advance) { { std::lock_guard lock{now_mutex}; now_ += advance; } trigger_alarms(); } void AdvanceableTimer::register_alarm( std::shared_ptr const& alarm) { std::lock_guard lock{alarms_mutex}; alarms.push_back(alarm); } void AdvanceableTimer::trigger_alarms() { std::unique_lock lock{alarms_mutex}; // sort by trigger time std::sort(begin(alarms), end(alarms), [] (std::weak_ptr const& weak_a1, std::weak_ptr const& weak_a2) { auto a1 = weak_a1.lock(); auto a2 = weak_a2.lock(); if (a1 && a2) { if (a1->next_trigger() == a2->next_trigger()) return weak_a1.owner_before(weak_a2); else return a1->next_trigger() < a2->next_trigger(); } else return weak_a1.owner_before(weak_a2); }); for (auto& weak_alarm : alarms) { auto const alarm = weak_alarm.lock(); if (alarm) { lock.unlock(); alarm->update_now(now()); lock.lock(); } } alarms.erase( std::remove_if( begin(alarms), end(alarms), [](std::weak_ptr const& alarm) { return alarm.expired(); }), end(alarms)); } mir::time::Timestamp AdvanceableTimer::now() const { std::lock_guard lock{now_mutex}; return now_; } unity-system-compositor-0.4.3+16.04.20160323/tests/unit-tests/CMakeLists.txt0000644000015600001650000000221212674474272027002 0ustar pbuserpbgroup00000000000000# Copyright © 2014 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 warranty of # MERCHANTABILITY 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 . # # Authored by: Alexandros Frantzis include_directories( ${CMAKE_SOURCE_DIR} ${MIRSERVER_INCLUDE_DIRS} ) add_executable( usc_unit_tests test_session_switcher.cpp test_screen_event_handler.cpp test_mir_screen.cpp test_mir_input_configuration.cpp advanceable_timer.cpp ) target_link_libraries( usc_unit_tests usc ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARY} ${GMOCK_MAIN_LIBRARY} ) add_test(usc_unit_tests ${EXECUTABLE_OUTPUT_PATH}/usc_unit_tests) add_dependencies(usc_unit_tests GMock) unity-system-compositor-0.4.3+16.04.20160323/tests/unit-tests/test_session_switcher.cpp0000644000015600001650000005036212674474272031411 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014-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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "src/session_switcher.h" #include "src/spinner.h" #include "mir/frontend/session.h" #include #include #include #include #include namespace { class FakeScene { public: void add(usc::Session* session) { sessions.emplace_back(session, false); } void remove(usc::Session* session) { sessions.erase(find(session)); } void show(usc::Session* session) { find(session)->second = true; } void hide(usc::Session* session) { find(session)->second = false; } void raise(usc::Session* session) { auto const iter = find(session); auto const pair = *iter; sessions.erase(iter); sessions.push_back(pair); } std::vector displayed_sessions() { std::vector ret; for (auto const& pair : sessions) { bool session_visible = pair.second; if (session_visible) ret.push_back(pair.first->name()); } return ret; } private: std::vector> sessions; decltype(sessions)::iterator find(usc::Session* session) { return std::find_if( sessions.begin(), sessions.end(), [session] (decltype(sessions)::value_type const& p) { return p.first == session; }); } }; class StubMirSession : public mir::frontend::Session { public: StubMirSession(std::string const& name) : name_{name} {} std::shared_ptr get_surface(mir::frontend::SurfaceId surface) const override { return nullptr; } mir::frontend::BufferStreamId create_buffer_stream(mir::graphics::BufferProperties const& /*props*/) override { return {}; } std::shared_ptr get_buffer_stream(mir::frontend::BufferStreamId /*stream*/) const override { return nullptr; } void destroy_buffer_stream(mir::frontend::BufferStreamId /*stream*/) override {} std::string name() const override { return name_; } private: std::string const name_; }; class StubSession : public usc::Session { public: StubSession(FakeScene& fake_scene, std::string const& name) : mir_stub_session{std::make_shared(name)}, fake_scene(fake_scene) { fake_scene.add(this); } ~StubSession() { fake_scene.remove(this); } std::string name() override { return mir_stub_session->name(); } void show() override { fake_scene.show(this); } void hide() override { fake_scene.hide(this); } void raise_and_focus() override { fake_scene.raise(this); } bool corresponds_to(mir::frontend::Session const* s) override { return s == mir_stub_session.get(); } std::shared_ptr corresponding_session() { return mir_stub_session; } private: std::shared_ptr const mir_stub_session; FakeScene& fake_scene; }; struct StubSpinner : usc::Spinner { void ensure_running() override { is_running_ = true; } void kill() override { is_running_ = false; } pid_t pid() override { return pid_; } void set_pid(pid_t new_pid) { pid_ = new_pid; } bool is_running() { return is_running_; } private: bool is_running_ = false; pid_t pid_ = 666; }; struct ASessionSwitcher : testing::Test { std::shared_ptr create_stub_session(std::string const& name) { return std::make_shared(fake_scene, name); } std::tuple,std::shared_ptr> boot() { std::string const boot_active_name{"boot_active"}; std::string const boot_next_name{"boot_next"}; auto const boot_active = create_stub_session(boot_active_name); auto const boot_next = create_stub_session(boot_next_name); switcher.add(boot_active, active_pid); switcher.add(boot_next, next_pid); switcher.set_next_session(boot_next_name); switcher.set_active_session(boot_active_name); switcher.mark_ready(boot_active->corresponding_session().get()); switcher.mark_ready(boot_next->corresponding_session().get()); return std::make_tuple(boot_active, boot_next); } FakeScene fake_scene; std::shared_ptr const stub_spinner{std::make_shared()}; usc::SessionSwitcher switcher{stub_spinner}; std::string const active_name{"active"}; std::string const next_name{"next"}; std::string const spinner_name{"spinner"}; pid_t const invalid_pid{0}; pid_t const active_pid{1000}; pid_t const next_pid{1001}; pid_t const other_pid{1002}; }; } TEST_F(ASessionSwitcher, does_not_display_any_session_if_active_and_next_not_set) { using namespace testing; switcher.add(create_stub_session("s1"), invalid_pid); switcher.add(create_stub_session("s2"), invalid_pid); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, does_not_display_not_ready_active_session) { using namespace testing; auto const active = create_stub_session(active_name); switcher.add(active, active_pid); switcher.set_active_session(active_name); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, does_not_display_not_ready_next_session) { using namespace testing; auto const next = create_stub_session(next_name); switcher.add(next, next_pid); switcher.set_next_session(next_name); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, does_not_display_ready_next_session_without_ready_active_session) { using namespace testing; auto const next = create_stub_session(next_name); switcher.add(next, next_pid); switcher.set_next_session(next_name); switcher.mark_ready(next->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, does_not_display_any_session_on_boot_if_not_both_active_and_next_are_ready) { using namespace testing; auto const active = create_stub_session(active_name); auto const next = create_stub_session(next_name); switcher.add(active, active_pid); switcher.add(next, next_pid); switcher.set_active_session(active_name); switcher.set_next_session(next_name); switcher.mark_ready(active->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, displays_the_active_session_on_boot_if_it_is_ready_and_there_is_no_next_session) { using namespace testing; auto const active = create_stub_session(active_name); auto const spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); switcher.mark_ready(active->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(active_name)); } TEST_F(ASessionSwitcher, displays_the_active_session_after_boot_if_it_is_ready) { using namespace testing; boot(); auto const active = create_stub_session(active_name); auto const next = create_stub_session(next_name); switcher.add(active, active_pid); switcher.add(next, next_pid); switcher.set_next_session(next_name); switcher.set_active_session(active_name); switcher.mark_ready(active->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(active_name)); } TEST_F(ASessionSwitcher, displays_active_over_next_if_both_are_ready) { using namespace testing; auto const active = create_stub_session(active_name); auto const next = create_stub_session(next_name); switcher.add(active, active_pid); switcher.add(next, next_pid); switcher.set_next_session(next_name); switcher.set_active_session(active_name); switcher.mark_ready(active->corresponding_session().get()); switcher.mark_ready(next->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(next_name, active_name)); } TEST_F(ASessionSwitcher, displays_only_active_if_next_equals_active) { using namespace testing; auto const active = create_stub_session(active_name); switcher.add(active, active_pid); switcher.set_next_session(active_name); switcher.set_active_session(active_name); switcher.mark_ready(active->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(active_name)); } TEST_F(ASessionSwitcher, displays_only_active_and_next_sessions) { using namespace testing; auto const active = create_stub_session(active_name); auto const next = create_stub_session(next_name); auto const other = create_stub_session("other"); switcher.add(active, active_pid); switcher.add(next, next_pid); switcher.add(other, other_pid); switcher.set_next_session(next_name); switcher.set_active_session(active_name); switcher.mark_ready(active->corresponding_session().get()); switcher.mark_ready(next->corresponding_session().get()); switcher.mark_ready(other->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(next_name, active_name)); } TEST_F(ASessionSwitcher, displays_spinner_if_active_is_not_ready) { using namespace testing; auto const active = create_stub_session(active_name); auto const spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name)); } TEST_F(ASessionSwitcher, does_not_display_spinner_if_next_is_not_ready) { using namespace testing; auto const next = create_stub_session(next_name); auto const spinner = create_stub_session(spinner_name); switcher.add(next, next_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_next_session(next_name); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, displays_only_spinner_when_active_is_not_ready_but_next_is_ready) { using namespace testing; auto const active = create_stub_session(active_name); auto const next = create_stub_session(next_name); auto const spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(next, next_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); switcher.set_next_session(next_name); switcher.mark_ready(next->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name)); } TEST_F(ASessionSwitcher, displays_only_spinner_when_booting_if_not_both_active_and_next_are_ready) { using namespace testing; auto const active = create_stub_session(active_name); auto const next = create_stub_session(next_name); auto const spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(next, next_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); switcher.set_next_session(next_name); switcher.mark_ready(active->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name)); } TEST_F(ASessionSwitcher, displays_spinner_behind_active_after_boot_if_active_is_ready_but_next_is_not_ready) { using namespace testing; boot(); auto const active = create_stub_session(active_name); auto const next = create_stub_session(next_name); auto const spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(next, next_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); switcher.set_next_session(next_name); switcher.mark_ready(active->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name, active_name)); } TEST_F(ASessionSwitcher, starts_and_stops_spinner_as_needed) { using namespace testing; auto const active = create_stub_session(active_name); switcher.add(active, active_pid); switcher.set_active_session(active_name); EXPECT_TRUE(stub_spinner->is_running()); switcher.mark_ready(active->corresponding_session().get()); EXPECT_FALSE(stub_spinner->is_running()); } TEST_F(ASessionSwitcher, does_not_display_next_when_active_is_removed) { using namespace testing; std::string const no_session_name; std::shared_ptr boot_active; std::shared_ptr boot_next; std::tie(boot_active, boot_next) = boot(); auto const spinner = create_stub_session(spinner_name); switcher.add(spinner, stub_spinner->pid()); switcher.remove(boot_active->corresponding_session()); switcher.set_active_session(no_session_name); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, displays_only_active_not_spinner_when_next_is_removed) { using namespace testing; std::string const no_session_name; std::shared_ptr boot_active; std::shared_ptr boot_next; std::tie(boot_active, boot_next) = boot(); auto const spinner = create_stub_session(spinner_name); switcher.add(spinner, stub_spinner->pid()); switcher.remove(boot_next->corresponding_session()); switcher.set_next_session(no_session_name); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(boot_active->name())); } TEST_F(ASessionSwitcher, displays_spinner_when_active_is_removed_unexpectedly) { using namespace testing; std::string const no_session_name; std::shared_ptr boot_active; std::shared_ptr boot_next; std::tie(boot_active, boot_next) = boot(); auto const spinner = create_stub_session(spinner_name); switcher.add(spinner, stub_spinner->pid()); switcher.remove(boot_active->corresponding_session()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name)); } TEST_F(ASessionSwitcher, displays_spinner_under_active_if_next_is_removed_unexpectedly) { using namespace testing; std::string const no_session_name; std::shared_ptr boot_active; std::shared_ptr boot_next; std::tie(boot_active, boot_next) = boot(); auto const spinner = create_stub_session(spinner_name); switcher.add(spinner, stub_spinner->pid()); switcher.remove(boot_next->corresponding_session()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name, boot_active->name())); } TEST_F(ASessionSwitcher, does_not_display_any_session_when_spinner_is_removed_and_no_sessions_are_ready) { using namespace testing; auto const active = create_stub_session(active_name); auto spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name)); switcher.remove(spinner->corresponding_session()); spinner.reset(); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, can_handle_spinner_resurrection_under_different_name) { using namespace testing; auto const active = create_stub_session(active_name); auto spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name)); switcher.remove(spinner->corresponding_session()); spinner.reset(); std::string const new_spinner_name{"new_spinner_name"}; spinner = create_stub_session(new_spinner_name); switcher.add(spinner, stub_spinner->pid()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(new_spinner_name)); } TEST_F(ASessionSwitcher, can_handle_spinner_resurrection_under_different_pid) { using namespace testing; auto const active = create_stub_session(active_name); auto spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name)); switcher.remove(spinner->corresponding_session()); spinner.reset(); pid_t const new_pid{1234}; stub_spinner->set_pid(new_pid); spinner = create_stub_session(spinner_name); switcher.add(spinner, stub_spinner->pid()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name)); } TEST_F(ASessionSwitcher, is_not_confused_by_other_session_with_name_of_dead_spinner) { using namespace testing; auto const active = create_stub_session(active_name); auto spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); switcher.remove(spinner->corresponding_session()); spinner.reset(); stub_spinner->set_pid(invalid_pid); auto const other = create_stub_session(spinner_name); switcher.add(other, other_pid); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, is_not_confused_by_other_session_with_pid_of_dead_spinner) { using namespace testing; auto const active = create_stub_session(active_name); auto spinner = create_stub_session(spinner_name); switcher.add(active, active_pid); switcher.add(spinner, stub_spinner->pid()); switcher.set_active_session(active_name); auto const old_spinner_pid = stub_spinner->pid(); stub_spinner->set_pid(invalid_pid); switcher.remove(spinner->corresponding_session()); spinner.reset(); auto const other = create_stub_session(spinner_name); switcher.add(other, old_spinner_pid); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); } TEST_F(ASessionSwitcher, replaces_tracked_session_if_same_session_name_is_used) { using namespace testing; auto const active = create_stub_session(active_name); auto const other = create_stub_session(active_name); pid_t const other_pid{2000}; EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); switcher.add(active, active_pid); switcher.set_active_session(active_name); switcher.mark_ready(active->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(active_name)); switcher.add(other, other_pid); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(active_name)); } TEST_F(ASessionSwitcher, ignores_removal_of_untracked_session) { using namespace testing; auto const active = create_stub_session(active_name); EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty()); switcher.add(active, active_pid); switcher.set_active_session(active_name); switcher.mark_ready(active->corresponding_session().get()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(active_name)); auto const other = create_stub_session(active_name); switcher.remove(other->corresponding_session()); EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(active_name)); } unity-system-compositor-0.4.3+16.04.20160323/tests/include/0000755000015600001650000000000012674474477023560 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/tests/include/usc/0000755000015600001650000000000012674474477024352 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/tests/include/usc/test/0000755000015600001650000000000012674474477025331 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/tests/include/usc/test/mock_screen.h0000644000015600001650000000331712674474272027767 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #ifndef USC_TEST_MOCK_SCREEN_H_ #define USC_TEST_MOCK_SCREEN_H_ #include "src/screen.h" #include #include namespace usc { namespace test { struct MockScreen : usc::Screen { MOCK_METHOD1(enable_inactivity_timers, void(bool enable)); MOCK_METHOD0(keep_display_on_temporarily, void()); MOCK_METHOD0(get_screen_power_mode, MirPowerMode()); MOCK_METHOD2(set_screen_power_mode, void(MirPowerMode mode, PowerStateChangeReason reason)); MOCK_METHOD1(keep_display_on, void(bool on)); MOCK_METHOD1(set_brightness, void(int brightness)); MOCK_METHOD1(enable_auto_brightness, void(bool enable)); MOCK_METHOD2(set_inactivity_timeouts, void(int power_off_timeout, int dimmer_timeout)); MOCK_METHOD1(set_touch_visualization_enabled, void(bool enabled)); void register_power_state_change_handler( usc::PowerStateChangeHandler const& handler) { power_state_change_handler = handler; } usc::PowerStateChangeHandler power_state_change_handler; }; } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/include/usc/test/mock_input_configuration.h0000644000015600001650000000304112674474272032570 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #ifndef USC_TEST_MOCK_INPUT_CONFIGURATION_H_ #define USC_TEST_MOCK_INPUT_CONFIGURATION_H_ #include "src/input_configuration.h" #include #include namespace usc { namespace test { struct MockInputConfiguration : usc::InputConfiguration { public: MOCK_METHOD1(set_mouse_primary_button, void(int32_t)); MOCK_METHOD1(set_mouse_cursor_speed, void(double)); MOCK_METHOD1(set_mouse_scroll_speed, void(double)); MOCK_METHOD1(set_touchpad_primary_button, void(int32_t)); MOCK_METHOD1(set_touchpad_cursor_speed, void(double)); MOCK_METHOD1(set_touchpad_scroll_speed, void(double)); MOCK_METHOD1(set_two_finger_scroll, void(bool)); MOCK_METHOD1(set_tap_to_click, void(bool)); MOCK_METHOD1(set_disable_touchpad_with_mouse, void(bool)); MOCK_METHOD1(set_disable_touchpad_while_typing, void(bool)); }; } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/include/usc/test/mock_display.h0000644000015600001650000000446712674474272030164 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_TEST_MOCK_DISPLAY_H #define USC_TEST_MOCK_DISPLAY_H #include "usc/test/stub_display_configuration.h" #include #include #if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 20, 0) #include #endif #include namespace usc { namespace test { struct MockDisplay : mir::graphics::Display { void for_each_display_sync_group(std::function const& f) override { } std::unique_ptr configuration() const override { return std::make_unique(); } MOCK_METHOD1(configure, void(mir::graphics::DisplayConfiguration const& conf)); void register_configuration_change_handler( mir::graphics::EventHandlerRegister& , mir::graphics::DisplayConfigurationChangeHandler const& ) override {}; void register_pause_resume_handlers( mir::graphics::EventHandlerRegister&, mir::graphics::DisplayPauseHandler const&, mir::graphics::DisplayResumeHandler const&) override { } void pause() override {}; void resume() override {}; std::shared_ptr create_hardware_cursor( std::shared_ptr const&) override { return {}; }; std::unique_ptr create_gl_context() override { return std::unique_ptr{};}; #if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 20, 0) std::unique_ptr create_virtual_output(int, int) override { return std::unique_ptr{}; } #endif }; } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/include/usc/test/stub_display_configuration.h0000644000015600001650000000344212674474272033127 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_TEST_STUB_DISPLAY_CONFIGURATION_H_ #define USC_TEST_STUB_DISPLAY_CONFIGURATION_H_ #include namespace usc { namespace test { struct StubDisplayConfiguration : mir::graphics::DisplayConfiguration { StubDisplayConfiguration() { conf_output.power_mode = MirPowerMode::mir_power_mode_on; } StubDisplayConfiguration(mir::graphics::DisplayConfigurationOutput const& output) : conf_output(output) { } void for_each_card(std::function) const override { } void for_each_output(std::function f) const override { f(conf_output); } void for_each_output(std::function f) { mir::graphics::UserDisplayConfigurationOutput user{conf_output}; f(user); } std::unique_ptr clone() const override { return std::make_unique(conf_output); } mir::graphics::DisplayConfigurationOutput conf_output; }; } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/0000755000015600001650000000000012674474477025620 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/test_unity_services.cpp0000644000015600001650000000541412674474272032433 0ustar pbuserpbgroup00000000000000/* * Copyright © 2016 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #include "src/unity_input_service.h" #include "src/dbus_connection_handle.h" #include "src/dbus_connection_thread.h" #include "src/dbus_event_loop.h" #include "src/dbus_message_handle.h" #include "src/unity_screen_service.h" #include "src/unity_input_service_introspection.h" #include "src/unity_screen_service_introspection.h" #include "wait_condition.h" #include "dbus_bus.h" #include "dbus_client.h" #include "unity_input_dbus_client.h" #include "unity_screen_dbus_client.h" #include "usc/test/mock_input_configuration.h" #include "usc/test/mock_screen.h" namespace ut = usc::test; using namespace testing; namespace { struct UnityServices : testing::Test { std::chrono::seconds const default_timeout{3}; ut::DBusBus bus; ut::UnityScreenDBusClient screen_client{bus.address()}; ut::UnityInputDBusClient input_client{bus.address()}; std::shared_ptr const mock_screen = std::make_shared>(); std::shared_ptr const mock_input_configuration = std::make_shared>(); std::shared_ptr const dbus_loop= std::make_shared(); usc::UnityScreenService screen_service{dbus_loop, bus.address(), mock_screen}; usc::UnityInputService input_service{dbus_loop, bus.address(), mock_input_configuration}; std::shared_ptr const dbus_thread = std::make_shared(dbus_loop); }; } TEST_F(UnityServices, offer_screen_introspection) { auto reply = screen_client.request_introspection(); EXPECT_THAT(reply.get(), Eq(unity_screen_service_introspection)); } TEST_F(UnityServices, offer_input_introspection) { auto reply = input_client.request_introspection(); EXPECT_THAT(reply.get(), Eq(unity_input_service_introspection)); } TEST_F(UnityServices, provides_access_to_input_methods) { double const speed = 8.0; EXPECT_CALL(*mock_input_configuration, set_mouse_scroll_speed(speed)); input_client.request_set_mouse_scroll_speed(speed); } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/dbus_bus.cpp0000644000015600001650000000252212674474272030124 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "dbus_bus.h" #include "run_command.h" #include #include #include usc::test::DBusBus::DBusBus() : pid{0} { auto launch = usc::test::run_command( "dbus-daemon --session --print-address=1 --print-pid=1 --fork"); std::stringstream ss{launch}; std::getline(ss, address_); ss >> pid; if (address_.empty()) throw std::runtime_error("Failed to get dbus bus address"); if (pid == 0) throw std::runtime_error("Failed to get dbus bus pid"); } usc::test::DBusBus::~DBusBus() { kill(pid, SIGTERM); } std::string usc::test::DBusBus::address() { return address_; } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/run_command.cpp0000644000015600001650000000231312674474272030616 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "run_command.h" #include #include #include std::string usc::test::run_command(std::string const& cmd) { auto fp = ::popen(cmd.c_str(), "r"); if (!fp) BOOST_THROW_EXCEPTION(std::runtime_error("Failed to execute command: " + cmd)); std::string output; char buffer[64]; while (!std::feof(fp)) { auto nread = std::fread(buffer, 1, 64, fp); output.append(buffer, nread); } ::pclose(fp); return output; } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/dbus_client.h0000644000015600001650000000501512674474272030256 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_TEST_DBUS_CLIENT_H_ #define USC_TEST_DBUS_CLIENT_H_ #include "src/dbus_connection_handle.h" #include #include namespace usc { class DBusMessageHandle; namespace test { class DBusAsyncReply { public: DBusAsyncReply(DBusAsyncReply const&) = delete; DBusAsyncReply& operator=(DBusAsyncReply const&) = delete; DBusAsyncReply(DBusPendingCall* pending_reply); DBusAsyncReply(DBusAsyncReply&& other); ~DBusAsyncReply(); usc::DBusMessageHandle get(); protected: void throw_on_error_reply(::DBusMessage* reply); void throw_on_invalid_reply(::DBusMessage* reply); private: DBusPendingCall* pending_reply = nullptr; }; class DBusAsyncReplyVoid : DBusAsyncReply { public: using DBusAsyncReply::DBusAsyncReply; void get(); }; class DBusAsyncReplyInt : DBusAsyncReply { public: using DBusAsyncReply::DBusAsyncReply; int get(); }; class DBusAsyncReplyBool : DBusAsyncReply { public: using DBusAsyncReply::DBusAsyncReply; bool get(); }; class DBusAsyncReplyString : DBusAsyncReply { public: using DBusAsyncReply::DBusAsyncReply; std::string get(); }; class DBusClient { public: DBusClient( std::string const& bus_address, std::string const& destination, std::string const& path); void disconnect(); template T invoke_with_reply( char const* interface, char const* method, int first_arg_type, Args... args) { return T{invoke_with_pending(interface, method, first_arg_type, args...)}; } protected: DBusPendingCall* invoke_with_pending( char const* interface, char const* method, int first_arg_type, ...); usc::DBusConnectionHandle connection; std::string const destination; std::string const path; std::string const interface; }; } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/test_powerd_mediator.cpp0000644000015600001650000005667312674474272032561 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "src/powerd_mediator.h" #include "src/dbus_connection_handle.h" #include "src/dbus_message_handle.h" #include "src/dbus_event_loop.h" #include "wait_condition.h" #include "dbus_bus.h" #include #include namespace ut = usc::test; namespace { class PowerdService { public: enum class StartNow { no, yes }; PowerdService( std::string const& address, dbus_bool_t auto_brightness_supported, StartNow start_now) : auto_brightness_supported{auto_brightness_supported}, connection{std::make_shared(address)}, dbus_event_loop{} { dbus_event_loop.add_connection(connection); if (start_now == StartNow::yes) start(); } ~PowerdService() { dbus_event_loop.stop(); if (dbus_loop_thread.joinable()) dbus_loop_thread.join(); } void start() { ON_CALL(*this, dbus_setUserBrightness(normal_brightness)) .WillByDefault(WakeUp(&initial_setup_done)); connection->request_name(powerd_service_name); connection->add_filter(handle_dbus_message_thunk, this); std::promise event_loop_started; auto event_loop_started_future = event_loop_started.get_future(); dbus_loop_thread = std::thread( [this,&event_loop_started] { dbus_event_loop.run(event_loop_started); }); event_loop_started_future.wait(); } void wait_for_initial_setup() { initial_setup_done.wait_for(std::chrono::seconds{5}); } MOCK_METHOD1(dbus_setUserBrightness, void(int brightness)); MOCK_METHOD1(dbus_userAutobrightnessEnable, void(dbus_bool_t enable)); MOCK_METHOD2(dbus_requestSysState, std::string(char const* name, int state)); MOCK_METHOD1(dbus_clearSysState, void(char const* cookie)); MOCK_METHOD1(dbus_enableProximityHandling, void(char const* name)); MOCK_METHOD1(dbus_disableProximityHandling, void(char const* name)); int32_t const dim_brightness = 13; int32_t const min_brightness = 5; int32_t const max_brightness = 42; int32_t const normal_brightness = 24; dbus_bool_t const auto_brightness_supported; const char* const default_cookie = "monster"; void emit_SysPowerStateChange(int32_t state) { usc::DBusMessageHandle signal{ dbus_message_new_signal( powerd_service_path, powerd_service_interface, "SysPowerStateChange"), DBUS_TYPE_INT32, &state, DBUS_TYPE_INVALID}; dbus_connection_send(*connection, signal, nullptr); } private: static ::DBusHandlerResult handle_dbus_message_thunk( ::DBusConnection* connection, DBusMessage* message, void* user_data) { auto const powerd_service = static_cast(user_data); return powerd_service->handle_dbus_message(connection, message, user_data); } ::DBusHandlerResult handle_dbus_message( ::DBusConnection* connection, DBusMessage* message, void* user_data) { if (dbus_message_is_method_call(message, powerd_service_name, "getBrightnessParams")) { usc::DBusMessageHandle reply{dbus_message_new_method_return(message)}; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); DBusMessageIter iter_struct; dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, nullptr, &iter_struct); dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_INT32, &dim_brightness); dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_INT32, &min_brightness); dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_INT32, &max_brightness); dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_INT32, &normal_brightness); dbus_message_iter_append_basic(&iter_struct, DBUS_TYPE_BOOLEAN, &auto_brightness_supported); dbus_message_iter_close_container(&iter, &iter_struct); dbus_connection_send(connection, reply, nullptr); } else if (dbus_message_is_method_call(message, powerd_service_name, "setUserBrightness")) { int32_t brightness{0}; dbus_message_get_args( message, nullptr, DBUS_TYPE_INT32, &brightness, DBUS_TYPE_INVALID); dbus_setUserBrightness(brightness); usc::DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); } else if (dbus_message_is_method_call(message, powerd_service_name, "userAutobrightnessEnable")) { dbus_bool_t enable{FALSE}; dbus_message_get_args( message, nullptr, DBUS_TYPE_BOOLEAN, &enable, DBUS_TYPE_INVALID); dbus_userAutobrightnessEnable(enable); usc::DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); } else if (dbus_message_is_method_call(message, powerd_service_name, "requestSysState")) { char const* name{nullptr}; int32_t state{-1}; dbus_message_get_args( message, nullptr, DBUS_TYPE_STRING, &name, DBUS_TYPE_INT32, &state, DBUS_TYPE_INVALID); auto cookie = dbus_requestSysState(name, state); if (cookie.empty()) cookie = default_cookie; auto cookie_cstr = cookie.c_str(); usc::DBusMessageHandle reply{ dbus_message_new_method_return(message), DBUS_TYPE_STRING, &cookie_cstr, DBUS_TYPE_INVALID}; dbus_connection_send(connection, reply, nullptr); emit_SysPowerStateChange(state); } else if (dbus_message_is_method_call(message, powerd_service_name, "clearSysState")) { char const* cookie{nullptr}; dbus_message_get_args( message, nullptr, DBUS_TYPE_STRING, &cookie, DBUS_TYPE_INVALID); dbus_clearSysState(cookie); usc::DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); emit_SysPowerStateChange(0); } else if (dbus_message_is_method_call(message, powerd_service_name, "enableProximityHandling")) { char const* name{nullptr}; dbus_message_get_args( message, nullptr, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); dbus_enableProximityHandling(name); usc::DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); } else if (dbus_message_is_method_call(message, powerd_service_name, "disableProximityHandling")) { char const* name{nullptr}; dbus_message_get_args( message, nullptr, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); dbus_disableProximityHandling(name); usc::DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); } return DBUS_HANDLER_RESULT_HANDLED; } char const* const powerd_service_name = "com.canonical.powerd"; char const* const powerd_service_path = "/com/canonical/powerd"; char const* const powerd_service_interface = "com.canonical.powerd"; ut::WaitCondition initial_setup_done; std::shared_ptr connection; usc::DBusEventLoop dbus_event_loop; std::thread dbus_loop_thread; }; struct APowerdMediatorBaseFixture : testing::Test { std::chrono::seconds const default_timeout{3}; APowerdMediatorBaseFixture( dbus_bool_t auto_brightness_supported, PowerdService::StartNow start_powerd_service_now) : powerd_service{bus.address(), auto_brightness_supported, start_powerd_service_now} { } ~APowerdMediatorBaseFixture() { expect_shutdown_requests(); } void expect_shutdown_requests() { using namespace testing; Mock::VerifyAndClearExpectations(&powerd_service); EXPECT_CALL(powerd_service, dbus_setUserBrightness(0)).Times(AtMost(1)); EXPECT_CALL(powerd_service, dbus_clearSysState(_)).Times(AtMost(1)); } static dbus_bool_t const auto_brightness_is_supported{TRUE}; static dbus_bool_t const auto_brightness_not_supported{FALSE}; ut::DBusBus bus; testing::NiceMock powerd_service; std::shared_ptr mediator{ std::make_shared(bus.address())}; }; } namespace { struct APowerdMediator : APowerdMediatorBaseFixture { APowerdMediator() : APowerdMediatorBaseFixture{ auto_brightness_is_supported, PowerdService::StartNow::yes} { } }; } TEST_F(APowerdMediator, gets_initial_brightness_values_from_service) { using namespace testing; EXPECT_THAT(mediator->min_brightness(), Eq(powerd_service.min_brightness)); EXPECT_THAT(mediator->max_brightness(), Eq(powerd_service.max_brightness)); EXPECT_THAT(mediator->auto_brightness_supported(), Eq(powerd_service.auto_brightness_supported == TRUE)); } TEST_F(APowerdMediator, sets_dim_brightness_for_dim_backlight) { EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.dim_brightness)); mediator->set_dim_backlight(); } TEST_F(APowerdMediator, does_not_set_dim_brightness_if_not_needed) { EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.dim_brightness)); mediator->set_dim_backlight(); mediator->set_dim_backlight(); } TEST_F(APowerdMediator, sets_normal_brightness_for_normal_backlight) { using namespace testing; InSequence s; EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.dim_brightness)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.normal_brightness)); // Start-up brightness is normal, so we need to change to some other value // first before resetting normal brightness. mediator->set_dim_backlight(); mediator->set_normal_backlight(); } TEST_F(APowerdMediator, does_not_set_normal_brightness_if_not_needed) { using namespace testing; EXPECT_CALL(powerd_service, dbus_setUserBrightness(_)).Times(0); mediator->set_normal_backlight(); } TEST_F(APowerdMediator, sets_zero_brightness_for_off_backlight) { EXPECT_CALL(powerd_service, dbus_setUserBrightness(0)); mediator->turn_off_backlight(); } TEST_F(APowerdMediator, uses_user_specified_brightness_values_if_set) { using namespace testing; auto const dim_brightness = powerd_service.dim_brightness + 1; auto const normal_brightness = powerd_service.normal_brightness + 1; InSequence s; EXPECT_CALL(powerd_service, dbus_setUserBrightness(dim_brightness)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(normal_brightness)); mediator->change_backlight_values(dim_brightness, normal_brightness); mediator->set_dim_backlight(); mediator->set_normal_backlight(); } TEST_F(APowerdMediator, ignores_out_of_range_user_specified_brightness_values) { using namespace testing; InSequence s; EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.dim_brightness)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.normal_brightness)); mediator->change_backlight_values(mediator->max_brightness() + 1, mediator->max_brightness() + 1); mediator->change_backlight_values(mediator->min_brightness() - 1, mediator->min_brightness() - 1); mediator->set_dim_backlight(); mediator->set_normal_backlight(); } TEST_F(APowerdMediator, enables_auto_brightness_on_request) { EXPECT_CALL(powerd_service, dbus_userAutobrightnessEnable(TRUE)); mediator->enable_auto_brightness(true); } TEST_F(APowerdMediator, sets_normal_brightness_when_disabling_auto_brightness) { using namespace testing; InSequence s; EXPECT_CALL(powerd_service, dbus_userAutobrightnessEnable(TRUE)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.normal_brightness)); mediator->enable_auto_brightness(true); mediator->enable_auto_brightness(false); } TEST_F(APowerdMediator, retains_auto_brightness_mode_when_setting_normal_backlight) { using namespace testing; EXPECT_CALL(powerd_service, dbus_userAutobrightnessEnable(TRUE)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(_)).Times(0); mediator->enable_auto_brightness(true); mediator->set_normal_backlight(); } TEST_F(APowerdMediator, retains_auto_brightness_mode_when_setting_normal_backlight_after_off) { using namespace testing; InSequence s; EXPECT_CALL(powerd_service, dbus_userAutobrightnessEnable(TRUE)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(0)); EXPECT_CALL(powerd_service, dbus_userAutobrightnessEnable(TRUE)); mediator->enable_auto_brightness(true); mediator->turn_off_backlight(); mediator->set_normal_backlight(); } TEST_F(APowerdMediator, sets_explicit_brightness_value) { ut::WaitCondition request_processed; int const brightness{30}; EXPECT_CALL(powerd_service, dbus_setUserBrightness(brightness)); mediator->set_brightness(brightness); } TEST_F(APowerdMediator, changes_normal_brightness_when_explicitly_setting_brightness) { using namespace testing; int const brightness{30}; InSequence s; EXPECT_CALL(powerd_service, dbus_setUserBrightness(brightness)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.dim_brightness)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(brightness)); mediator->set_brightness(brightness); mediator->set_dim_backlight(); // Normal backlight should use value set by explicit set_brightness() call mediator->set_normal_backlight(); } TEST_F(APowerdMediator, retains_auto_brightness_mode_when_explicitly_setting_brightness) { using namespace testing; int const brightness{30}; InSequence s; EXPECT_CALL(powerd_service, dbus_userAutobrightnessEnable(TRUE)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(brightness)).Times(0); mediator->enable_auto_brightness(true); // set_brightness() shouldn't change mode since auto brightness is enabled // (i.e. it shouldn't contact powerd) mediator->set_brightness(brightness); } TEST_F(APowerdMediator, ignores_disable_suspend_request_if_suspend_already_disabled) { using namespace testing; EXPECT_CALL(powerd_service, dbus_requestSysState(_, _)).Times(0); mediator->disable_suspend(); } TEST_F(APowerdMediator, waits_for_state_change_when_disabling_suspend) { using namespace testing; mediator->allow_suspend(); while (!mediator->is_system_suspended()) std::this_thread::sleep_for(std::chrono::milliseconds{1}); mediator->disable_suspend(); EXPECT_TRUE(!mediator->is_system_suspended()); } TEST_F(APowerdMediator, uses_correct_cookie_when_allowing_suspend) { using namespace testing; EXPECT_CALL(powerd_service, dbus_clearSysState(StrEq(powerd_service.default_cookie))); mediator->allow_suspend(); } TEST_F(APowerdMediator, enables_and_disables_proximity_handling) { using namespace testing; auto const proximity_name = "unity-system-compositor"; InSequence s; EXPECT_CALL(powerd_service, dbus_enableProximityHandling(StrEq(proximity_name))); EXPECT_CALL(powerd_service, dbus_disableProximityHandling(StrEq(proximity_name))); mediator->enable_proximity(true); mediator->enable_proximity(false); } TEST_F(APowerdMediator, ignores_requests_for_redundant_proximity_handling) { using namespace testing; auto const proximity_name = "unity-system-compositor"; InSequence s; EXPECT_CALL(powerd_service, dbus_enableProximityHandling(StrEq(proximity_name))); EXPECT_CALL(powerd_service, dbus_disableProximityHandling(StrEq(proximity_name))); // Should be ignore, proximity already disabled mediator->enable_proximity(false); // Should be handled only once mediator->enable_proximity(true); mediator->enable_proximity(true); // Should be handled only once mediator->enable_proximity(false); mediator->enable_proximity(false); } namespace { struct APowerdMediatorWithoutAutoBrightnessSupport : APowerdMediatorBaseFixture { APowerdMediatorWithoutAutoBrightnessSupport() : APowerdMediatorBaseFixture{ auto_brightness_not_supported, PowerdService::StartNow::yes} { } }; } TEST_F(APowerdMediatorWithoutAutoBrightnessSupport, sets_normal_brightness_for_enable_auto_brightness) { using namespace testing; InSequence s; // (1) EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.dim_brightness)); // (2) Since auto brightness is not supported, when trying to enable it we should see // we should just get normal brightness. EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.normal_brightness)); mediator->set_dim_backlight(); // (1) mediator->enable_auto_brightness(true); // (2) } TEST_F(APowerdMediatorWithoutAutoBrightnessSupport, sets_normal_brightness_for_normal_backlight_when_auto_brightness_not_supported) { using namespace testing; InSequence s; // (1) EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.dim_brightness)); // (2) Since auto brightness is not supported, when setting normal backlight we should // get normal brightness, even though the user has previously tried to enable // auto brightness (call marked (*) below). EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.normal_brightness)); // This call should have no effect, since we are already in normal backlight state mediator->enable_auto_brightness(true); // (*) mediator->set_dim_backlight(); // (1) mediator->set_normal_backlight(); // (2) } namespace { struct APowerdMediatorWithoutInitialService : APowerdMediatorBaseFixture { APowerdMediatorWithoutInitialService() : APowerdMediatorBaseFixture{ auto_brightness_is_supported, PowerdService::StartNow::no} { } }; } TEST_F(APowerdMediatorWithoutInitialService, has_sensible_brightness_values) { using namespace testing; EXPECT_THAT(mediator->min_brightness(), Ge(0)); EXPECT_THAT(mediator->max_brightness(), Ge(mediator->min_brightness())); EXPECT_THAT(mediator->auto_brightness_supported(), Eq(false)); } TEST_F(APowerdMediatorWithoutInitialService, updates_brightness_values_when_service_connects) { using namespace testing; powerd_service.start(); powerd_service.wait_for_initial_setup(); EXPECT_THAT(mediator->min_brightness(), Eq(powerd_service.min_brightness)); EXPECT_THAT(mediator->max_brightness(), Eq(powerd_service.max_brightness)); EXPECT_THAT(mediator->auto_brightness_supported(), Eq(powerd_service.auto_brightness_supported == TRUE)); } TEST_F(APowerdMediatorWithoutInitialService, disables_suspend_if_suspend_not_allowed_when_service_connects) { using namespace testing; int const sys_state_active{1}; ut::WaitCondition request_processed; EXPECT_CALL(powerd_service, dbus_requestSysState(_, sys_state_active)) .WillOnce(DoAll(WakeUp(&request_processed), Return(""))); powerd_service.start(); powerd_service.wait_for_initial_setup(); request_processed.wait_for(default_timeout); EXPECT_TRUE(request_processed.woken()); } TEST_F(APowerdMediatorWithoutInitialService, does_not_disable_suspend_if_suspend_is_allowed_when_service_connects) { using namespace testing; mediator->allow_suspend(); ut::WaitCondition request_processed; EXPECT_CALL(powerd_service, dbus_requestSysState(_, _)).Times(0); powerd_service.start(); powerd_service.wait_for_initial_setup(); } TEST_F(APowerdMediatorWithoutInitialService, reenables_auto_brightness_if_it_is_enabled_when_service_connects) { using namespace testing; // Connect powerd service, enable auto brightness, disconnect service { NiceMock another_powerd_service{ bus.address(), TRUE, PowerdService::StartNow::yes}; another_powerd_service.wait_for_initial_setup(); EXPECT_CALL(another_powerd_service, dbus_userAutobrightnessEnable(TRUE)); mediator->enable_auto_brightness(true); } // Connect service again ut::WaitCondition request_processed; EXPECT_CALL(powerd_service, dbus_userAutobrightnessEnable(TRUE)) .WillOnce(WakeUp(&request_processed)); powerd_service.start(); // We don't wait for initial default setup, since it won't happen // in this case. request_processed.wait_for(default_timeout); EXPECT_TRUE(request_processed.woken()); } TEST(APowerdMediatorAtStartup, turns_on_backlight_and_disables_suspend) { using namespace testing; const char* const name{"com.canonical.Unity.Screen"}; int const sys_state_active{1}; dbus_bool_t const auto_brightness_supported{TRUE}; ut::DBusBus bus; NiceMock powerd_service{ bus.address(), auto_brightness_supported, PowerdService::StartNow::yes}; EXPECT_CALL(powerd_service, dbus_setUserBrightness(powerd_service.normal_brightness)); EXPECT_CALL(powerd_service, dbus_requestSysState(StrEq(name), sys_state_active)); usc::PowerdMediator mediator{bus.address()}; powerd_service.wait_for_initial_setup(); Mock::VerifyAndClearExpectations(&powerd_service); EXPECT_CALL(powerd_service, dbus_clearSysState(_)).Times(AtMost(1)); EXPECT_CALL(powerd_service, dbus_setUserBrightness(0)).Times(AtMost(1)); } TEST(APowerdMediatorAtStartup, disables_proximity_handling) { using namespace testing; dbus_bool_t const auto_brightness_supported{TRUE}; ut::DBusBus bus; NiceMock powerd_service{ bus.address(), auto_brightness_supported, PowerdService::StartNow::yes}; EXPECT_CALL(powerd_service, dbus_disableProximityHandling(_)); usc::PowerdMediator mediator{bus.address()}; powerd_service.wait_for_initial_setup(); Mock::VerifyAndClearExpectations(&powerd_service); } TEST(APowerdMediatorAtShutdown, turns_off_backlight) { using namespace testing; ut::DBusBus bus; dbus_bool_t const auto_brightness_supported{TRUE}; NiceMock powerd_service{ bus.address(), auto_brightness_supported, PowerdService::StartNow::yes}; usc::PowerdMediator mediator{bus.address()}; powerd_service.wait_for_initial_setup(); Mock::VerifyAndClearExpectations(&powerd_service); EXPECT_CALL(powerd_service, dbus_setUserBrightness(0)); } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/unity_screen_dbus_client.h0000644000015600001650000000344612674474272033053 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #ifndef USC_TEST_UNITY_SCREEN_DBUS_CLIENT_H_ #define USC_TEST_UNITY_SCREEN_DBUS_CLIENT_H_ #include "dbus_client.h" namespace usc { namespace test { class UnityScreenDBusClient : public DBusClient { public: UnityScreenDBusClient(std::string const& address); DBusAsyncReplyString request_introspection(); DBusAsyncReplyVoid request_set_user_brightness(int32_t brightness); DBusAsyncReplyVoid request_user_auto_brightness_enable(bool enabled); DBusAsyncReplyVoid request_set_inactivity_timeouts( int32_t poweroff_timeout, int32_t dimmer_timeout); DBusAsyncReplyVoid request_set_touch_visualization_enabled(bool enabled); DBusAsyncReplyBool request_set_screen_power_mode( std::string const& mode, int reason); DBusAsyncReplyInt request_keep_display_on(); DBusAsyncReplyVoid request_remove_display_on_request(int id); DBusAsyncReply request_invalid_method(); DBusAsyncReply request_method_with_invalid_arguments(); usc::DBusMessageHandle listen_for_power_state_change_signal(); char const* const unity_screen_interface = "com.canonical.Unity.Screen"; }; } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/dbus_bus.h0000644000015600001650000000173412674474272027575 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_TEST_DBUS_BUS_H_ #define USC_TEST_DBUS_BUS_H_ #include #include namespace usc { namespace test { class DBusBus { public: DBusBus(); ~DBusBus(); std::string address(); private: std::string address_; pid_t pid; }; } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/unity_input_dbus_client.cpp0000644000015600001650000000677712674474272033300 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #include "unity_input_dbus_client.h" namespace ut = usc::test; ut::UnityInputDBusClient::UnityInputDBusClient(std::string const& address) : ut::DBusClient{ address, "com.canonical.Unity.Input", "/com/canonical/Unity/Input"} { } ut::DBusAsyncReplyString ut::UnityInputDBusClient::request_introspection() { return invoke_with_reply( "org.freedesktop.DBus.Introspectable", "Introspect", DBUS_TYPE_INVALID); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request(char const* requestName, int32_t value) { return invoke_with_reply( unity_input_interface, requestName, DBUS_TYPE_INT32, &value, DBUS_TYPE_INVALID); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_mouse_primary_button(int32_t button) { return request("setMousePrimaryButton", button); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_touchpad_primary_button(int32_t button) { return request("setTouchpadPrimaryButton", button); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request(char const* requestName, double value) { return invoke_with_reply( unity_input_interface, requestName, DBUS_TYPE_DOUBLE, &value, DBUS_TYPE_INVALID); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_mouse_cursor_speed(double speed) { return request("setMouseCursorSpeed", speed); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_mouse_scroll_speed(double speed) { return request("setMouseScrollSpeed", speed); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_touchpad_cursor_speed(double speed) { return request("setTouchpadCursorSpeed", speed); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_touchpad_scroll_speed(double speed) { return request("setTouchpadScrollSpeed", speed); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request(char const* requestName, bool value) { dbus_bool_t copied = value; return invoke_with_reply( unity_input_interface, requestName, DBUS_TYPE_BOOLEAN, &copied, DBUS_TYPE_INVALID); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_touchpad_two_finger_scroll(bool enabled) { return request("setTouchpadTwoFingerScroll", enabled); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_touchpad_tap_to_click(bool enabled) { return request("setTouchpadTapToClick", enabled); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_touchpad_disable_with_mouse(bool enabled) { return request("setTouchpadDisableWithMouse", enabled); } ut::DBusAsyncReplyVoid ut::UnityInputDBusClient::request_set_touchpad_disable_while_typing(bool enabled) { return request("setTouchpadDisableWhileTyping", enabled); } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/dbus_client.cpp0000644000015600001650000000753012674474272030615 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "dbus_client.h" #include "src/dbus_message_handle.h" #include namespace ut = usc::test; ut::DBusAsyncReply::DBusAsyncReply(DBusPendingCall* pending_reply) : pending_reply{pending_reply} { } ut::DBusAsyncReply::DBusAsyncReply(DBusAsyncReply&& other) : pending_reply{other.pending_reply} { other.pending_reply = nullptr; } ut::DBusAsyncReply::~DBusAsyncReply() { throw_on_error_reply(get()); } usc::DBusMessageHandle ut::DBusAsyncReply::get() { if (pending_reply) { dbus_pending_call_block(pending_reply); auto reply = usc::DBusMessageHandle{ dbus_pending_call_steal_reply(pending_reply)}; dbus_pending_call_unref(pending_reply); pending_reply = nullptr; return reply; } else { return usc::DBusMessageHandle{nullptr}; } } void ut::DBusAsyncReply::throw_on_error_reply(::DBusMessage* reply) { if (reply && dbus_message_get_error_name(reply) != nullptr) throw std::runtime_error("Got an error reply"); } void ut::DBusAsyncReply::throw_on_invalid_reply(::DBusMessage* reply) { if (!reply) throw std::runtime_error("Async reply is invalid"); } void ut::DBusAsyncReplyVoid::get() { auto reply = ut::DBusAsyncReply::get(); throw_on_invalid_reply(reply); throw_on_error_reply(reply); } int ut::DBusAsyncReplyInt::get() { auto reply = ut::DBusAsyncReply::get(); throw_on_invalid_reply(reply); throw_on_error_reply(reply); int32_t val{-1}; dbus_message_get_args(reply, nullptr, DBUS_TYPE_INT32, &val, DBUS_TYPE_INVALID); return val; } bool ut::DBusAsyncReplyBool::get() { auto reply = ut::DBusAsyncReply::get(); throw_on_invalid_reply(reply); throw_on_error_reply(reply); dbus_bool_t val{FALSE}; dbus_message_get_args(reply, nullptr, DBUS_TYPE_BOOLEAN, &val, DBUS_TYPE_INVALID); return val == TRUE; } std::string ut::DBusAsyncReplyString::get() { auto reply = ut::DBusAsyncReply::get(); throw_on_invalid_reply(reply); throw_on_error_reply(reply); char* val{nullptr}; dbus_message_get_args(reply, nullptr, DBUS_TYPE_STRING, &val, DBUS_TYPE_INVALID); return std::string{val}; } ut::DBusClient::DBusClient( std::string const& bus_address, std::string const& destination, std::string const& path) : connection{bus_address.c_str()}, destination{destination}, path{path} { } void ut::DBusClient::DBusClient::disconnect() { if (dbus_connection_get_is_connected(connection)) dbus_connection_close(connection); } ::DBusPendingCall* ut::DBusClient::invoke_with_pending( char const* interface, char const* method, int first_arg_type, ...) { static int const timeout_ms = 5000; va_list args; va_start(args, first_arg_type); usc::DBusMessageHandle msg{ dbus_message_new_method_call( destination.c_str(), path.c_str(), interface, method), first_arg_type, args}; va_end(args); DBusPendingCall* pending_reply; dbus_connection_send_with_reply( connection, msg, &pending_reply, timeout_ms); dbus_connection_flush(connection); return pending_reply; } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/test_unity_screen_service.cpp0000644000015600001650000002243112674474272033605 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "src/unity_screen_service.h" #include "src/dbus_connection_handle.h" #include "src/dbus_connection_thread.h" #include "src/dbus_event_loop.h" #include "src/dbus_message_handle.h" #include "src/screen.h" #include "src/power_state_change_reason.h" #include "src/unity_screen_service_introspection.h" #include "wait_condition.h" #include "dbus_bus.h" #include "unity_screen_dbus_client.h" #include "usc/test/mock_screen.h" #include #include #include #include namespace ut = usc::test; namespace { struct AUnityScreenService : testing::Test { std::chrono::seconds const default_timeout{3}; ut::DBusBus bus; std::shared_ptr const mock_screen = std::make_shared>(); ut::UnityScreenDBusClient client{bus.address()}; std::shared_ptr const dbus_loop= std::make_shared(); usc::UnityScreenService service{dbus_loop, bus.address(), mock_screen}; std::shared_ptr const dbus_thread = std::make_shared(dbus_loop); }; } TEST_F(AUnityScreenService, replies_to_introspection_request) { using namespace testing; auto reply = client.request_introspection(); EXPECT_THAT(reply.get(), Eq(unity_screen_service_introspection)); } TEST_F(AUnityScreenService, forwards_set_user_brightness_request) { int const brightness = 10; EXPECT_CALL(*mock_screen, set_brightness(brightness)); client.request_set_user_brightness(brightness); } TEST_F(AUnityScreenService, forwards_user_auto_brightness_enable_request) { bool const enable = true; EXPECT_CALL(*mock_screen, enable_auto_brightness(enable)); client.request_user_auto_brightness_enable(enable); } TEST_F(AUnityScreenService, forwards_set_inactivity_timeouts_request) { int const poweroff_timeout = 1000; int const dimmer_timeout = 500; EXPECT_CALL(*mock_screen, set_inactivity_timeouts(poweroff_timeout, dimmer_timeout)); client.request_set_inactivity_timeouts(poweroff_timeout, dimmer_timeout); } TEST_F(AUnityScreenService, forwards_set_touch_visualization_enabled_request) { bool const enabled = true; EXPECT_CALL(*mock_screen, set_touch_visualization_enabled(enabled)); client.request_set_touch_visualization_enabled(enabled); } TEST_F(AUnityScreenService, forwards_set_screen_power_mode_request) { auto const mode = MirPowerMode::mir_power_mode_standby; std::string const mode_str = "standby"; auto const reason = PowerStateChangeReason::proximity; auto const reason_int = static_cast(reason); EXPECT_CALL(*mock_screen, set_screen_power_mode(mode, reason)); client.request_set_screen_power_mode(mode_str, reason_int); } TEST_F(AUnityScreenService, replies_to_set_screen_power_mode_request) { using namespace testing; std::string const mode_str = "standby"; auto const reason_int = static_cast(PowerStateChangeReason::proximity); auto reply = client.request_set_screen_power_mode(mode_str, reason_int); EXPECT_THAT(reply.get(), Eq(true)); } TEST_F(AUnityScreenService, forwards_keep_display_on_request) { EXPECT_CALL(*mock_screen, keep_display_on(true)); client.request_keep_display_on(); } TEST_F(AUnityScreenService, replies_with_different_ids_to_keep_display_on_requests) { using namespace testing; auto reply1 = client.request_keep_display_on(); auto reply2 = client.request_keep_display_on(); auto const id1 = reply1.get(); auto const id2 = reply2.get(); EXPECT_THAT(id1, Ne(id2)); } TEST_F(AUnityScreenService, disables_keep_display_on_when_single_request_is_removed) { using namespace testing; InSequence s; EXPECT_CALL(*mock_screen, keep_display_on(true)); EXPECT_CALL(*mock_screen, keep_display_on(false)); auto reply1 = client.request_keep_display_on(); client.request_remove_display_on_request(reply1.get()); } TEST_F(AUnityScreenService, disables_keep_display_on_when_all_requests_are_removed) { using namespace testing; EXPECT_CALL(*mock_screen, keep_display_on(true)).Times(3); EXPECT_CALL(*mock_screen, keep_display_on(false)).Times(0); auto reply1 = client.request_keep_display_on(); auto reply2 = client.request_keep_display_on(); auto reply3 = client.request_keep_display_on(); client.request_remove_display_on_request(reply1.get()); client.request_remove_display_on_request(reply2.get()); auto id3 = reply3.get(); // Display should still be kept on at this point Mock::VerifyAndClearExpectations(mock_screen.get()); // keep_display_on should be disable only when the last request is removed EXPECT_CALL(*mock_screen, keep_display_on(false)); client.request_remove_display_on_request(id3); } TEST_F(AUnityScreenService, disables_keep_display_on_when_single_client_disconnects) { ut::WaitCondition request_processed; EXPECT_CALL(*mock_screen, keep_display_on(true)).Times(3); EXPECT_CALL(*mock_screen, keep_display_on(false)) .WillOnce(WakeUp(&request_processed)); client.request_keep_display_on(); client.request_keep_display_on(); client.request_keep_display_on(); client.disconnect(); request_processed.wait_for(default_timeout); EXPECT_TRUE(request_processed.woken()); } TEST_F(AUnityScreenService, disables_keep_display_on_when_all_clients_disconnect_or_remove_requests) { using namespace testing; ut::UnityScreenDBusClient other_client{bus.address()}; EXPECT_CALL(*mock_screen, keep_display_on(true)).Times(4); EXPECT_CALL(*mock_screen, keep_display_on(false)).Times(0); auto reply1 = client.request_keep_display_on(); auto reply2 = client.request_keep_display_on(); other_client.request_keep_display_on(); other_client.request_keep_display_on(); other_client.disconnect(); client.request_remove_display_on_request(reply1.get()); auto id2 = reply2.get(); // Display should still be kept on at this point Mock::VerifyAndClearExpectations(mock_screen.get()); // keep_display_on should be disabled only when the last request is removed ut::WaitCondition request_processed; EXPECT_CALL(*mock_screen, keep_display_on(false)) .WillOnce(WakeUp(&request_processed)); client.request_remove_display_on_request(id2); request_processed.wait_for(default_timeout); EXPECT_TRUE(request_processed.woken()); } TEST_F(AUnityScreenService, ignores_invalid_display_on_removal_request) { ut::WaitCondition request_processed; int32_t const invalid_id{-1}; EXPECT_CALL(*mock_screen, keep_display_on(false)).Times(0); client.request_remove_display_on_request(invalid_id); client.disconnect(); // Allow some time for dbus calls to reach UnityScreenService std::this_thread::sleep_for(std::chrono::milliseconds(100)); } TEST_F(AUnityScreenService, ignores_disconnects_from_clients_without_display_on_request) { ut::WaitCondition request_processed; EXPECT_CALL(*mock_screen, keep_display_on(false)).Times(0); client.disconnect(); // Allow some time for disconnect notification to reach UnityScreenService std::this_thread::sleep_for(std::chrono::milliseconds(100)); } TEST_F(AUnityScreenService, emits_power_state_change_signal) { using namespace testing; auto async_message = std::async(std::launch::async, [&] { return client.listen_for_power_state_change_signal(); }); mock_screen->power_state_change_handler( MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key); auto message = async_message.get(); int32_t state{-1}; int32_t reason{-1}; dbus_message_get_args(message, nullptr, DBUS_TYPE_INT32, &state, DBUS_TYPE_INT32, &reason, DBUS_TYPE_INVALID); int32_t const off_state{0}; EXPECT_THAT(state, Eq(off_state)); EXPECT_THAT(reason, Eq(static_cast(PowerStateChangeReason::power_key))); } TEST_F(AUnityScreenService, returns_error_reply_for_unsupported_method) { using namespace testing; auto reply = client.request_invalid_method(); auto reply_msg = reply.get(); EXPECT_THAT(dbus_message_get_type(reply_msg), Eq(DBUS_MESSAGE_TYPE_ERROR)); EXPECT_THAT(dbus_message_get_error_name(reply_msg), StrEq(DBUS_ERROR_FAILED)); } TEST_F(AUnityScreenService, returns_error_reply_for_method_with_invalid_arguments) { using namespace testing; auto reply = client.request_method_with_invalid_arguments(); auto reply_msg = reply.get(); EXPECT_THAT(dbus_message_get_type(reply_msg), Eq(DBUS_MESSAGE_TYPE_ERROR)); EXPECT_THAT(dbus_message_get_error_name(reply_msg), StrEq(DBUS_ERROR_FAILED)); } ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/usc_test_helper_wait_for_signal.cunity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/usc_test_helper_wait_for_signal0000644000015600001650000000136012674474272034153 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include int main() { pause(); } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/test_unity_input_service.cpp0000644000015600001650000001057012674474272033466 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #include "src/unity_input_service.h" #include "src/dbus_connection_handle.h" #include "src/dbus_connection_thread.h" #include "src/dbus_event_loop.h" #include "src/dbus_message_handle.h" #include "src/unity_input_service_introspection.h" #include "wait_condition.h" #include "dbus_bus.h" #include "dbus_client.h" #include "unity_input_dbus_client.h" #include "usc/test/mock_input_configuration.h" #include #include namespace ut = usc::test; namespace { struct AUnityInputService : testing::Test { std::chrono::seconds const default_timeout{3}; ut::DBusBus bus; std::shared_ptr const mock_input_configuration = std::make_shared>(); ut::UnityInputDBusClient client{bus.address()}; std::shared_ptr const dbus_loop= std::make_shared(); usc::UnityInputService service{dbus_loop, bus.address(), mock_input_configuration}; std::shared_ptr const dbus_thread = std::make_shared(dbus_loop); }; } TEST_F(AUnityInputService, replies_to_introspection_request) { using namespace testing; auto reply = client.request_introspection(); EXPECT_THAT(reply.get(), Eq(unity_input_service_introspection)); } TEST_F(AUnityInputService, forwards_set_mouse_primary_button) { int32_t const primary_button = 2; EXPECT_CALL(*mock_input_configuration, set_mouse_primary_button(primary_button)); client.request_set_mouse_primary_button(primary_button); } TEST_F(AUnityInputService, forwards_set_mouse_cursor_speed) { double const speed = 10.0; EXPECT_CALL(*mock_input_configuration, set_mouse_cursor_speed(speed)); client.request_set_mouse_cursor_speed(speed); } TEST_F(AUnityInputService, forwards_set_mouse_scroll_speed) { double const speed = 8.0; EXPECT_CALL(*mock_input_configuration, set_mouse_scroll_speed(speed)); client.request_set_mouse_scroll_speed(speed); } TEST_F(AUnityInputService, forwards_set_touchpad_primary_button) { int32_t const primary_button = 1; EXPECT_CALL(*mock_input_configuration, set_touchpad_primary_button(primary_button)); client.request_set_touchpad_primary_button(primary_button); } TEST_F(AUnityInputService, forwards_set_touchpad_cursor_speed) { double const speed = 10.0; EXPECT_CALL(*mock_input_configuration, set_touchpad_cursor_speed(speed)); client.request_set_touchpad_cursor_speed(speed); } TEST_F(AUnityInputService, forwards_set_touchpad_scroll_speed) { double const speed = 8.0; EXPECT_CALL(*mock_input_configuration, set_touchpad_scroll_speed(speed)); client.request_set_touchpad_scroll_speed(speed); } TEST_F(AUnityInputService, forwards_set_disable_touchpad_while_typing) { bool const disable_it = false; EXPECT_CALL(*mock_input_configuration, set_disable_touchpad_while_typing(disable_it)); client.request_set_touchpad_disable_while_typing(disable_it); } TEST_F(AUnityInputService, forwards_set_disable_touchpad_with_mouse) { bool const enable_it = true; EXPECT_CALL(*mock_input_configuration, set_disable_touchpad_with_mouse(enable_it)); client.request_set_touchpad_disable_with_mouse(enable_it); } TEST_F(AUnityInputService, forwards_set_two_finger_scroll) { bool const enable_it = true; EXPECT_CALL(*mock_input_configuration, set_two_finger_scroll(enable_it)); client.request_set_touchpad_two_finger_scroll(enable_it); } TEST_F(AUnityInputService, forwards_set_touchpad_tap_to_click) { bool const enable_it = true; EXPECT_CALL(*mock_input_configuration, set_tap_to_click(enable_it)); client.request_set_touchpad_tap_to_click(enable_it); } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/wait_condition.h0000644000015600001650000000306312674474272030776 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_WAIT_CONDITION_H_ #define USC_WAIT_CONDITION_H_ #include #include #include #include namespace usc { namespace test { struct WaitCondition { void wait_for(std::chrono::milliseconds msec) { std::unique_lock ul(guard); condition.wait_for(ul, msec, [this] { return woken_; }); } void wake_up() { std::lock_guard ul(guard); woken_ = true; condition.notify_all(); } bool woken() { std::lock_guard ul(guard); return woken_; } private: std::mutex guard; std::condition_variable condition; bool woken_ = false; }; ACTION_P(WakeUp, wait_condition) { wait_condition->wake_up(); } ACTION_P2(WaitFor, wait_condition, delay) { wait_condition->wait_for(delay); } } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/spin_wait.h0000644000015600001650000000202412674474272027755 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_SPIN_WAIT_H_ #define USC_SPIN_WAIT_H_ #include #include namespace usc { namespace test { bool spin_wait_for_condition_or_timeout( std::function const& condition, std::chrono::milliseconds timeout, std::chrono::milliseconds spin_period = std::chrono::milliseconds{10}); } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/unity_screen_dbus_client.cpp0000644000015600001650000001116712674474272033405 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #include "unity_screen_dbus_client.h" #include "src/dbus_message_handle.h" namespace ut = usc::test; ut::UnityScreenDBusClient::UnityScreenDBusClient(std::string const& address) : ut::DBusClient{ address, "com.canonical.Unity.Screen", "/com/canonical/Unity/Screen"} { connection.add_match( "type='signal'," "interface='com.canonical.Unity.Screen'," "member='DisplayPowerStateChange'"); } ut::DBusAsyncReplyString ut::UnityScreenDBusClient::request_introspection() { return invoke_with_reply("org.freedesktop.DBus.Introspectable", "Introspect", DBUS_TYPE_INVALID); } ut::DBusAsyncReplyVoid ut::UnityScreenDBusClient::request_set_user_brightness(int32_t brightness) { return invoke_with_reply(unity_screen_interface, "setUserBrightness", DBUS_TYPE_INT32, &brightness, DBUS_TYPE_INVALID); } ut::DBusAsyncReplyVoid ut::UnityScreenDBusClient::request_user_auto_brightness_enable(bool enabled) { dbus_bool_t const e = enabled ? TRUE : FALSE; return invoke_with_reply(unity_screen_interface, "userAutobrightnessEnable", DBUS_TYPE_BOOLEAN, &e, DBUS_TYPE_INVALID); } ut::DBusAsyncReplyVoid ut::UnityScreenDBusClient::request_set_inactivity_timeouts(int32_t poweroff_timeout, int32_t dimmer_timeout) { return invoke_with_reply(unity_screen_interface, "setInactivityTimeouts", DBUS_TYPE_INT32, &poweroff_timeout, DBUS_TYPE_INT32, &dimmer_timeout, DBUS_TYPE_INVALID); } ut::DBusAsyncReplyVoid ut::UnityScreenDBusClient::request_set_touch_visualization_enabled(bool enabled) { dbus_bool_t const e = enabled ? TRUE : FALSE; return invoke_with_reply(unity_screen_interface, "setTouchVisualizationEnabled", DBUS_TYPE_BOOLEAN, &e, DBUS_TYPE_INVALID); } ut::DBusAsyncReplyBool ut::UnityScreenDBusClient::request_set_screen_power_mode(std::string const& mode, int reason) { auto mode_cstr = mode.c_str(); return invoke_with_reply(unity_screen_interface, "setScreenPowerMode", DBUS_TYPE_STRING, &mode_cstr, DBUS_TYPE_INT32, &reason, DBUS_TYPE_INVALID); } ut::DBusAsyncReplyInt ut::UnityScreenDBusClient::request_keep_display_on() { return invoke_with_reply(unity_screen_interface, "keepDisplayOn", DBUS_TYPE_INVALID); } ut::DBusAsyncReplyVoid ut::UnityScreenDBusClient::request_remove_display_on_request(int id) { return invoke_with_reply(unity_screen_interface, "removeDisplayOnRequest", DBUS_TYPE_INT32, &id, DBUS_TYPE_INVALID); } ut::DBusAsyncReply ut::UnityScreenDBusClient::request_invalid_method() { return invoke_with_reply(unity_screen_interface, "invalidMethod", DBUS_TYPE_INVALID); } ut::DBusAsyncReply ut::UnityScreenDBusClient::request_method_with_invalid_arguments() { char const* const str = "abcd"; return invoke_with_reply(unity_screen_interface, "setUserBrightness", DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID); } usc::DBusMessageHandle ut::UnityScreenDBusClient::listen_for_power_state_change_signal() { while (true) { dbus_connection_read_write(connection, 1); auto msg = usc::DBusMessageHandle{dbus_connection_pop_message(connection)}; if (msg && dbus_message_is_signal(msg, "com.canonical.Unity.Screen", "DisplayPowerStateChange")) { return msg; } } } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/test_external_spinner.cpp0000644000015600001650000001153212674474272032736 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "src/external_spinner.h" #include "run_command.h" #include "spin_wait.h" #include #include #include #include #include #include #include namespace { std::string executable_path() { std::vector link_vec(64, 0); ssize_t nread{0}; do { link_vec.resize(link_vec.size() * 2); nread = readlink("/proc/self/exe", link_vec.data(), link_vec.size()); } while (nread == static_cast(link_vec.size())); std::string link{link_vec.begin(), link_vec.begin() + nread}; return link.substr(0, link.rfind("/")); } std::vector pidof(std::string const& process_name) { auto cmd_string = "pidof " + process_name; auto const pid_str = usc::test::run_command(cmd_string); std::vector pids; std::stringstream ss{pid_str}; while (ss) { pid_t pid{0}; ss >> pid; if (pid > 0) pids.push_back(pid); } return pids; } bool is_zombie(pid_t pid) { std::ifstream stat("/proc/" + std::to_string(pid) + "/stat"); std::stringstream ss; ss << stat.rdbuf(); return ss.str().find(" Z ") != std::string::npos; } struct AnExternalSpinner : testing::Test { std::vector spinner_pids() { std::vector pids; usc::test::spin_wait_for_condition_or_timeout( [&pids, this] { pids = pidof(spinner_cmd); return !pids.empty(); }, timeout); if (pids.empty()) BOOST_THROW_EXCEPTION(std::runtime_error("spinner_pids timed out")); return pids; } std::vector environment_of_spinner() { auto const pids = spinner_pids(); if (pids.size() > 1) BOOST_THROW_EXCEPTION(std::runtime_error("Detected multiple spinner processes")); std::vector env; std::string const proc_path{"/proc/" + std::to_string(pids[0]) + "/environ"}; std::ifstream env_file{proc_path}; std::string val; while (std::getline(env_file, val, '\0')) env.push_back(val); return env; } void wait_for_spinner_to_terminate() { usc::test::spin_wait_for_condition_or_timeout( [this] { return pidof(spinner_cmd).empty(); }, timeout); } std::string const spinner_cmd{executable_path() + "/usc_test_helper_wait_for_signal"}; std::string const mir_socket{"usc_mir_socket"}; std::chrono::milliseconds const timeout{3000}; usc::ExternalSpinner spinner{spinner_cmd, mir_socket}; }; } TEST_F(AnExternalSpinner, starts_spinner_process) { using namespace testing; spinner.ensure_running(); EXPECT_THAT(spinner_pids(), SizeIs(1)); } TEST_F(AnExternalSpinner, kills_spinner_process_on_destruction) { using namespace testing; { usc::ExternalSpinner another_spinner{spinner_cmd, "bla"}; another_spinner.ensure_running(); EXPECT_THAT(spinner_pids(), SizeIs(1)); } wait_for_spinner_to_terminate(); } TEST_F(AnExternalSpinner, kills_spinner_process_on_request) { using namespace testing; spinner.ensure_running(); EXPECT_THAT(spinner_pids(), SizeIs(1)); spinner.kill(); wait_for_spinner_to_terminate(); } TEST_F(AnExternalSpinner, starts_spinner_process_only_once) { using namespace testing; spinner.ensure_running(); spinner.ensure_running(); EXPECT_THAT(spinner_pids(), SizeIs(1)); } TEST_F(AnExternalSpinner, sets_mir_socket_in_spinner_process_environment) { using namespace testing; spinner.ensure_running(); EXPECT_THAT(environment_of_spinner(), Contains("MIR_SOCKET=" + mir_socket)); } TEST_F(AnExternalSpinner, does_not_leave_zombie_process) { using namespace testing; spinner.ensure_running(); auto const spinner_pid = spinner_pids()[0]; spinner.kill(); wait_for_spinner_to_terminate(); // Wait a bit for zombie to be reaped by parent bool const spinner_is_not_zombie = usc::test::spin_wait_for_condition_or_timeout( [spinner_pid] { return !is_zombie(spinner_pid); }, timeout); EXPECT_TRUE(spinner_is_not_zombie); } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/test_dbus_event_loop.cpp0000644000015600001650000001531212674474272032545 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "src/dbus_event_loop.h" #include "src/dbus_connection_handle.h" #include "src/dbus_message_handle.h" #include "src/scoped_dbus_error.h" #include "dbus_bus.h" #include "dbus_client.h" #include #include namespace ut = usc::test; namespace { char const* const test_service_name = "com.TestService"; char const* const test_service_path = "/com/TestService"; char const* const test_service_interface = "com.TestService"; class TestDBusClient : public ut::DBusClient { public: TestDBusClient(std::string const& address) : ut::DBusClient{ address, test_service_name, test_service_path} { std::string const match = std::string{"type='signal',interface='"} + std::string{test_service_interface} + std::string{"',member='signal'"}; connection.add_match(match.c_str()); } ut::DBusAsyncReplyInt request_add(int32_t a, int32_t b) { return invoke_with_reply( test_service_interface, "add", DBUS_TYPE_INT32, &a, DBUS_TYPE_INT32, &b, DBUS_TYPE_INVALID); } usc::DBusMessageHandle listen_for_signal() { while (true) { dbus_connection_read_write(connection, 1); auto msg = usc::DBusMessageHandle{dbus_connection_pop_message(connection)}; if (msg && dbus_message_is_signal(msg, test_service_interface, "signal")) { return msg; } } } }; struct ADBusEventLoop : testing::Test { ADBusEventLoop() { dbus_event_loop.add_connection(connection); connection->request_name(test_service_name); connection->add_filter(handle_dbus_message_thunk, this); std::promise event_loop_started; auto event_loop_started_future = event_loop_started.get_future(); dbus_loop_thread = std::thread( [this,&event_loop_started] { dbus_event_loop.run(event_loop_started); }); event_loop_started_future.wait(); } ~ADBusEventLoop() { dbus_event_loop.stop(); if (dbus_loop_thread.joinable()) dbus_loop_thread.join(); } static ::DBusHandlerResult handle_dbus_message_thunk( ::DBusConnection* connection, DBusMessage* message, void* user_data) { auto const a_dbus_event_loop = static_cast(user_data); return a_dbus_event_loop->handle_dbus_message(connection, message, user_data); } DBusHandlerResult handle_dbus_message( ::DBusConnection* connection, DBusMessage* message, void* user_data) { usc::ScopedDBusError args_error; if (dbus_message_is_method_call(message, test_service_interface, "add")) { int32_t a{0}; int32_t b{0}; dbus_message_get_args( message, &args_error, DBUS_TYPE_INT32, &a, DBUS_TYPE_INT32, &b, DBUS_TYPE_INVALID); if (!args_error) { int32_t const result{a + b}; usc::DBusMessageHandle reply{ dbus_message_new_method_return(message), DBUS_TYPE_INT32, &result, DBUS_TYPE_INVALID}; dbus_connection_send(connection, reply, nullptr); } } return DBUS_HANDLER_RESULT_HANDLED; } std::chrono::seconds const default_timeout{3}; ut::DBusBus bus; std::shared_ptr connection{std::make_shared(bus.address())}; usc::DBusEventLoop dbus_event_loop{}; std::thread dbus_loop_thread; TestDBusClient client{bus.address()}; }; } TEST_F(ADBusEventLoop, dispatches_received_message) { using namespace testing; int32_t const a = 11; int32_t const b = 13; int32_t const expected = a + b; auto reply = client.request_add(a, b); EXPECT_THAT(reply.get(), expected); } TEST_F(ADBusEventLoop, enqueues_send_requests) { dbus_event_loop.enqueue( [this] { usc::DBusMessageHandle msg{ dbus_message_new_signal( test_service_path, test_service_interface, "signal")}; dbus_connection_send(*connection, msg, nullptr); }); auto const reply = client.listen_for_signal(); EXPECT_TRUE(dbus_message_is_signal(reply, test_service_interface, "signal")); } namespace { void pending_complete(DBusPendingCall* pending, void* user_data) { auto const pending_promise = static_cast*>(user_data); pending_promise->set_value(pending); } } TEST_F(ADBusEventLoop, handles_reply_timeouts) { using namespace testing; std::promise pending_promise; auto pending_future = pending_promise.get_future(); static int const timeout_ms = 100; auto const start = std::chrono::steady_clock::now(); dbus_event_loop.enqueue( [this,&pending_promise] { usc::DBusMessageHandle msg{ dbus_message_new_signal( test_service_path, test_service_interface, "signal")}; DBusPendingCall* pending{nullptr}; dbus_connection_send_with_reply( *connection, msg, &pending, timeout_ms); dbus_pending_call_set_notify( pending, &pending_complete, &pending_promise, nullptr); }); // No one is going to reply to the signal, so the notification should time out auto pending = pending_future.get(); auto const end = std::chrono::steady_clock::now(); auto const delay = end - start; ASSERT_THAT(pending, NotNull()); dbus_pending_call_unref(pending); // Use a high upper bound for valgrind runs to succeed EXPECT_THAT(delay, Lt(std::chrono::milliseconds{timeout_ms * 10})); EXPECT_THAT(delay, Ge(std::chrono::milliseconds{timeout_ms})); } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/run_command.h0000644000015600001650000000156312674474272030271 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_TEST_RUN_COMMAND_H_ #define USC_TEST_RUN_COMMAND_H_ #include namespace usc { namespace test { std::string run_command(std::string const& cmd); } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/CMakeLists.txt0000644000015600001650000000306412674474272030354 0ustar pbuserpbgroup00000000000000# Copyright © 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 warranty of # MERCHANTABILITY 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 . # # Authored by: Alexandros Frantzis include_directories( ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${MIRSERVER_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS} ) add_executable( usc_test_helper_wait_for_signal usc_test_helper_wait_for_signal.c ) add_executable( usc_integration_tests run_command.cpp dbus_bus.cpp dbus_client.cpp spin_wait.cpp unity_screen_dbus_client.cpp unity_input_dbus_client.cpp test_dbus_event_loop.cpp test_unity_screen_service.cpp test_unity_input_service.cpp test_unity_services.cpp test_external_spinner.cpp test_powerd_mediator.cpp test_deadlock_lp1491566.cpp ) target_link_libraries( usc_integration_tests usc ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARY} ${GMOCK_MAIN_LIBRARY} ) add_test(usc_integration_tests ${EXECUTABLE_OUTPUT_PATH}/usc_integration_tests) add_dependencies(usc_integration_tests GMock) add_dependencies(usc_integration_tests usc_test_helper_wait_for_signal) unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/test_deadlock_lp1491566.cpp0000644000015600001650000001551612674474277032412 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "src/server.h" #include "src/mir_screen.h" #include "src/performance_booster.h" #include "src/screen_hardware.h" #include "src/power_state_change_reason.h" #include "spin_wait.h" #include "usc/test/stub_display_configuration.h" #include "usc/test/mock_display.h" #include #include #include #include #include #include #include #include namespace mg = mir::graphics; namespace ut = usc::test; namespace { struct NullCompositor : mir::compositor::Compositor { void start() override {} void stop() override {} }; struct StubPerformanceBooster : usc::PerformanceBooster { void enable_performance_boost_during_user_interaction() override {} void disable_performance_boost_during_user_interaction() override {} }; struct StubScreenHardware : usc::ScreenHardware { void set_dim_backlight() override {} void set_normal_backlight() override {} void turn_off_backlight() override {} void change_backlight_values(int dim_brightness, int normal_brightness) override {} void allow_suspend() override {} void disable_suspend() override {} void enable_auto_brightness(bool) override {} bool auto_brightness_supported() override { return true; } void set_brightness(int) override {} int min_brightness() override { return 100; } int max_brightness() override { return 0; } void enable_proximity(bool) override { } }; struct NullTouchVisualizer : mir::input::TouchVisualizer { void enable() override {} void disable() override {} void visualize_touches(std::vector const& touches) override {} }; class TestMirScreen : public usc::MirScreen { public: using usc::MirScreen::MirScreen; void dimmer_alarm_notification_l() override { before_dimmer_alarm_func(); usc::MirScreen::dimmer_alarm_notification_l(); } void power_off_alarm_notification_l() override { before_power_off_alarm_func(); usc::MirScreen::power_off_alarm_notification_l(); } std::function before_dimmer_alarm_func = []{}; std::function before_power_off_alarm_func = []{}; }; struct DeadlockLP1491566 : public testing::Test { DeadlockLP1491566() { main_loop_thread = std::thread{[this] { main_loop->run(); }}; } ~DeadlockLP1491566() { main_loop->stop(); main_loop_thread.join(); } std::future async_set_screen_power_mode() { return std::async( std::launch::async, [this] { mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); }); } void schedule_inactivity_handlers() { // We set the screen power mode to on, which will // schedule inactivity handlers mir_screen.set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); } void wait_for_async_operation(std::future& future) { if (!usc::test::spin_wait_for_condition_or_timeout( [&future] { return future.valid(); }, std::chrono::seconds{3})) { throw std::runtime_error{"Future is not valid!"}; } if (future.wait_for(std::chrono::seconds{3}) != std::future_status::ready) { std::cerr << "Deadlock detected. Aborting." << std::endl; abort(); } } char *argv[1] = {nullptr}; usc::Server server{0, argv}; std::shared_ptr const main_loop{server.the_main_loop()}; std::chrono::milliseconds const power_off_timeout{200}; std::chrono::milliseconds const dimmer_timeout{100}; TestMirScreen mir_screen{ std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared<::testing::NiceMock>(), std::make_shared(), main_loop, server.the_clock(), {power_off_timeout, dimmer_timeout}, {power_off_timeout, dimmer_timeout}, {power_off_timeout, dimmer_timeout}}; std::thread main_loop_thread; }; } // The following tests reproduce the situation we are seeing in bug 1491566, // where a set_screen_power_mode request tries to run concurrently with either // the dimmer or power-off timeout handlers. The deadlock is caused by two // threads trying to acquire the internal alarm lock and the MirScreen lock in // opposite orders. // // To reproduce the race in these tests, we need to run the // set_screen_power_mode request just before the timeout handlers are invoked, // while the thread invoking the timeout handlers holds the internal alarm // lock, but before it has acquired the MirScreen lock. The thread calling // set_screen_power_mode will then first acquire the MirScreen lock and then // try to acquire the internal alarm clock and block. The timeout handler // thread will eventually resume and try to get the MirScreen lock, leading to // a deadlock. TEST_F(DeadlockLP1491566, between_dimmer_handler_and_screen_power_mode_request_is_averted) { std::future future; mir_screen.before_dimmer_alarm_func = [this, &future] { future = async_set_screen_power_mode(); // Wait a bit for async operation to get processed std::this_thread::sleep_for( std::chrono::milliseconds{500}); }; schedule_inactivity_handlers(); wait_for_async_operation(future); } TEST_F(DeadlockLP1491566, between_power_off_handler_and_screen_power_mode_request_is_averted) { std::future future; mir_screen.before_power_off_alarm_func = [this, &future] { future = async_set_screen_power_mode(); // Wait a bit for async operation to get processed std::this_thread::sleep_for( std::chrono::milliseconds{500}); }; schedule_inactivity_handlers(); wait_for_async_operation(future); } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/spin_wait.cpp0000644000015600001650000000232612674474272030315 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "spin_wait.h" #include bool usc::test::spin_wait_for_condition_or_timeout( std::function const& condition, std::chrono::milliseconds timeout, std::chrono::milliseconds spin_period) { auto const end = std::chrono::steady_clock::now() + timeout; bool condition_fulfilled = false; while (std::chrono::steady_clock::now() < end && !(condition_fulfilled = condition())) { std::this_thread::sleep_for(spin_period); } return condition_fulfilled; } unity-system-compositor-0.4.3+16.04.20160323/tests/integration-tests/unity_input_dbus_client.h0000644000015600001650000000377512674474272032740 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #ifndef USC_TEST_UNITY_INPUT_DBUS_CLIENT_H_ #define USC_TEST_UNITY_INPUT_DBUS_CLIENT_H_ #include "dbus_client.h" namespace usc { namespace test { class UnityInputDBusClient : public DBusClient { public: UnityInputDBusClient(std::string const& address); DBusAsyncReplyString request_introspection(); DBusAsyncReplyVoid request(char const* requestName, int32_t value); DBusAsyncReplyVoid request_set_mouse_primary_button(int32_t button); DBusAsyncReplyVoid request_set_touchpad_primary_button(int32_t button); DBusAsyncReplyVoid request(char const* requestName, double value); DBusAsyncReplyVoid request_set_mouse_cursor_speed(double speed); DBusAsyncReplyVoid request_set_mouse_scroll_speed(double speed); DBusAsyncReplyVoid request_set_touchpad_cursor_speed(double speed); DBusAsyncReplyVoid request_set_touchpad_scroll_speed(double speed); DBusAsyncReplyVoid request(char const* requestName, bool value); DBusAsyncReplyVoid request_set_touchpad_two_finger_scroll(bool enabled); DBusAsyncReplyVoid request_set_touchpad_tap_to_click(bool enabled); DBusAsyncReplyVoid request_set_touchpad_disable_with_mouse(bool enabled); DBusAsyncReplyVoid request_set_touchpad_disable_while_typing(bool enabled); char const* const unity_input_interface = "com.canonical.Unity.Input"; }; } } #endif unity-system-compositor-0.4.3+16.04.20160323/tests/autopilot/0000755000015600001650000000000012674474477024155 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/tests/autopilot/setup.py0000644000015600001650000000112512674474272025657 0ustar pbuserpbgroup00000000000000#!/usr/bin/python # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # Copyright 2013 Canonical # # 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. from distutils.core import setup from setuptools import find_packages setup( name='unity-system-compositor', version='1.0', description='Unity 8 autopilot tests.', url='https://launchpad.net/unity-system-compositor', license='GPLv3', packages=find_packages(), ) unity-system-compositor-0.4.3+16.04.20160323/tests/autopilot/unity_system_compositor/0000755000015600001650000000000012674474477031207 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/tests/autopilot/unity_system_compositor/__init__.py0000644000015600001650000000150712674474272033314 0ustar pbuserpbgroup00000000000000# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Autopilot Functional Test Tool # Copyright (C) 2012-2013 Canonical # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # 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 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 . # """Test package for unity-system-compositor tests.""" unity-system-compositor-0.4.3+16.04.20160323/tests/autopilot/unity_system_compositor/test_runtime.py0000644000015600001650000000726012674474272034301 0ustar pbuserpbgroup00000000000000# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Unity System Compositor # Copyright (C) 2013 Canonical # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # 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 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 . # """Runtime Environment Tests for unity-system-compositor.""" from autopilot.testcase import AutopilotTestCase import logging import re from subprocess import check_output, CalledProcessError from testtools.matchers import Equals logger = logging.getLogger(__name__) # this maps device manufacturers to their open source drivers. supported_hardware = { 'Intel': 'i915', 'NVidia': 'nouveau', 'Advanced Micro Devices': 'radeon', } class RuntimeEnvironmentTests(AutopilotTestCase): """Tests for the unity system compositor runtime environment.""" def test_running_hardware_check(self): """Checks whether u-s-c is running if we're on supported hardware, and checks the inverse if we're not on supported hardware. """ should_be_running = True devices = _get_video_devices() self.assertThat(len(devices), Equals(1)) device = devices[0] try: manufacturer = _get_supported_device_manufacturer(device) logger.info("Video card manufacturer is supported.") except RuntimeError: logger.info("Video card manufacturer is not supported.") should_be_running = False driver = _get_kernel_driver_in_use(device) if _is_running_oss_driver(manufacturer, driver): logger.info("Running OSS driver (%s).", driver) else: logger.info("Running proprietary driver (%s).", driver) should_be_running = False logger.info( "Unity system compositor %s be running!", "should" if should_be_running else "should not" ) self.assertThat( _is_system_compositor_running(), Equals(should_be_running) ) def _get_video_devices(): lspci_output = check_output(["lspci", "-vvv"]).strip() devices = [s.strip() for s in re.split("\n\n", lspci_output, flags=re.M)] video_devices = [dev for dev in devices if _is_video_device(dev)] return video_devices def _is_video_device(device_section): return 'VGA' in device_section.split('\n')[0] def _get_supported_device_manufacturer(device_section): global supported_hardware dev_line = device_section.split('\n')[0] for manufacturer in supported_hardware: if manufacturer.lower() in dev_line.lower(): return manufacturer raise RuntimeError("Unknown or unsupported device: " + dev_line) def _get_kernel_driver_in_use(device_section): for line in reversed(device_section.split('\n')): k,v = line.strip().split(':') if k == 'Kernel driver in use': return v.strip() return "unlisted driver" def _is_running_oss_driver(manufacturer, driver): global supported_hardware return supported_hardware.get(manufacturer, "") == driver def _is_system_compositor_running(): try: check_output(["pgrep", "unity-system"]) return True except CalledProcessError: return False unity-system-compositor-0.4.3+16.04.20160323/tests/CMakeLists.txt0000644000015600001650000000141112674474272024663 0ustar pbuserpbgroup00000000000000# Copyright © 2014 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 warranty of # MERCHANTABILITY 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 . # # Authored by: Alexandros Frantzis include_directories(include) add_subdirectory(unit-tests/) add_subdirectory(integration-tests/) unity-system-compositor-0.4.3+16.04.20160323/src/0000755000015600001650000000000012674474477021562 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/src/external_spinner.h0000644000015600001650000000236412674474272025311 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_EXTERNAL_SPINNER_H_ #define USC_EXTERNAL_SPINNER_H_ #include "spinner.h" #include #include #include namespace usc { class ExternalSpinner : public Spinner { public: ExternalSpinner(std::string const& executable, std::string const& mir_socket); ~ExternalSpinner(); void ensure_running() override; void kill() override; pid_t pid() override; private: std::string const executable; std::string const mir_socket; std::mutex mutex; pid_t spinner_pid; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/server.cpp0000644000015600001650000003274212674474277023602 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014-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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #define MIR_LOG_COMPONENT "UnitySystemCompositor" #include "server.h" #include "external_spinner.h" #include "asio_dm_connection.h" #include "session_switcher.h" #include "window_manager.h" #include "mir_screen.h" #include "mir_input_configuration.h" #include "screen_event_handler.h" #include "performance_booster.h" #include "powerd_mediator.h" #include "unity_screen_service.h" #include "unity_input_service.h" #include "dbus_connection_thread.h" #include "dbus_event_loop.h" #include "display_configuration_policy.h" #include "steady_clock.h" #include #include #include #include #include #include #include #include #include #include namespace msh = mir::shell; namespace ms = mir::scene; namespace mf = mir::frontend; namespace mi = mir::input; namespace { // FIXME For reasons that are not currently clear lightdm sometimes passes "--vt" on android void ignore_unknown_arguments(int argc, char const* const* argv) { std::cout << "Warning: ignoring unrecognised arguments:"; for (auto arg = argv; arg != argv+argc; ++arg) std::cout << " " << *arg; std::cout << std::endl; } struct NullCursorListener : public mi::CursorListener { void cursor_moved_to(float, float) override { } }; struct ServerStatusListener : public mir::ServerStatusListener { explicit ServerStatusListener( std::shared_ptr const& focus_controller) : focus_controller{focus_controller} { } void paused() override { std::cerr << "pause" << std::endl; if (auto active_session = weak_active_session().lock()) active_session->set_lifecycle_state(mir_lifecycle_state_will_suspend); } void resumed() override { std::cerr << "resume" << std::endl; if (auto active_session = weak_active_session().lock()) active_session->set_lifecycle_state(mir_lifecycle_state_resumed); } void started() override { } std::weak_ptr weak_active_session() { return focus_controller->focused_session(); } std::shared_ptr const focus_controller; }; struct StubCookie : public mir::cookie::Cookie { uint64_t timestamp() const override { return 0; } std::vector serialize() const override { // FIXME Workaround until mir fixes its internal message bliting // This size needs to equal mir/src/include/cookie/mir/cookie/blob.h::default_blob_size return std::vector(29); } }; struct StubCookieAuthority : public mir::cookie::Authority { std::unique_ptr make_cookie(uint64_t const& timestamp) override { return std::unique_ptr(new StubCookie()); } std::unique_ptr make_cookie(std::vector const& raw_cookie) override { return std::unique_ptr(new StubCookie()); } }; const char* const dm_from_fd = "from-dm-fd"; const char* const dm_to_fd = "to-dm-fd"; const char* const dm_stub = "debug-without-dm"; const char* const dm_stub_active = "debug-active-session-name"; } usc::Server::Server(int argc, char** argv) { add_configuration_option(dm_from_fd, "File descriptor of read end of pipe from display manager [int]", mir::OptionType::integer); add_configuration_option(dm_to_fd, "File descriptor of write end of pipe to display manager [int]", mir::OptionType::integer); add_configuration_option(dm_stub, "Run without a display manager (only useful when debugging)", mir::OptionType::null); add_configuration_option(dm_stub_active, "Expected connection when run without a display manager (only useful when debugging)", "nested-mir@:/run/user/1000/mir_socket"); add_configuration_option("blacklist", "Video blacklist regex to use", mir::OptionType::string); add_configuration_option("version", "Show version of Unity System Compositor", mir::OptionType::null); add_configuration_option("spinner", "Path to spinner executable", mir::OptionType::string); add_configuration_option("public-socket", "Make the socket file publicly writable", mir::OptionType::boolean); add_configuration_option("enable-hardware-cursor", "Enable the hardware cursor (disabled by default)", mir::OptionType::boolean); add_configuration_option("inactivity-display-off-timeout", "The time in seconds before the screen is turned off when there are no active sessions", mir::OptionType::integer); add_configuration_option("inactivity-display-dim-timeout", "The time in seconds before the screen is dimmed when there are no active sessions", mir::OptionType::integer); add_configuration_option("shutdown-timeout", "The time in milli-seconds the power key must be held to initiate a clean system shutdown", mir::OptionType::integer); add_configuration_option("power-key-ignore-timeout", "The time in milli-seconds the power key must be held to ignore - must be less than shutdown-timeout", mir::OptionType::integer); add_configuration_option("disable-inactivity-policy", "Disables handling user inactivity and power key", mir::OptionType::boolean); add_display_configuration_options_to(*this); add_configuration_option("notification-display-off-timeout", "The time in seconds before the screen is turned off after a notification arrives", mir::OptionType::integer); add_configuration_option("notification-display-dim-timeout", "The time in seconds before the screen is dimmed after a notification arrives", mir::OptionType::integer); add_configuration_option("snap-decision-display-off-timeout", "The time in seconds before the screen is turned off after snap decision arrives", mir::OptionType::integer); add_configuration_option("snap-decision-display-dim-timeout", "The time in seconds before the screen is dimmed after a snap decision arrives", mir::OptionType::integer); set_command_line(argc, const_cast(argv)); set_command_line_handler(&ignore_unknown_arguments); wrap_cursor_listener([this](std::shared_ptr const& default_) -> std::shared_ptr { // This is a workaround for u8 desktop preview in 14.04 for the lack of client cursor API. // We need to disable the cursor for XMir but leave it on for the desktop preview. // Luckily as it stands they run inside seperate instances of USC. ~racarr if (enable_hardware_cursor()) return default_; else return std::make_shared(); }); override_the_server_status_listener([this]() -> std::shared_ptr { return std::make_shared(the_focus_controller()); }); override_the_window_manager_builder([this](msh::FocusController* focus_controller) { return std::make_shared( focus_controller, the_shell_display_layout(), the_session_coordinator(), the_session_switcher()); }); override_the_cookie_authority([this]() -> std::shared_ptr { return std::make_unique(); }); set_config_filename("unity-system-compositor.conf"); apply_settings(); } std::shared_ptr usc::Server::the_performance_booster() { return platform_default_performance_booster(); } std::shared_ptr usc::Server::the_spinner() { return spinner( [this] { return std::make_shared( spinner_executable(), get_socket_file()); }); } std::shared_ptr usc::Server::the_session_switcher() { return session_switcher( [this] { return std::make_shared( the_spinner()); }); } std::shared_ptr usc::Server::the_dm_message_handler() { return the_session_switcher(); } namespace { struct NullDMMessageHandler : usc::DMConnection { explicit NullDMMessageHandler( std::shared_ptr const& dm_message_handler, std::string const& client_name) : dm_message_handler{dm_message_handler}, client_name{client_name} {} ~NullDMMessageHandler() = default; void start() override { dm_message_handler->set_active_session(client_name); }; std::shared_ptr const dm_message_handler; std::string const client_name; }; } std::shared_ptr usc::Server::the_dm_connection() { return dm_connection( [this]() -> std::shared_ptr { if (the_options()->is_set(dm_from_fd) && the_options()->is_set(dm_to_fd)) { return std::make_shared( the_options()->get(dm_from_fd, -1), the_options()->get(dm_to_fd, -1), the_dm_message_handler()); } else if (the_options()->is_set(dm_stub)) { return std::make_shared( the_dm_message_handler(), the_options()->get(dm_stub_active)); } BOOST_THROW_EXCEPTION(mir::AbnormalExit("to and from FDs are required for display manager")); }); } std::shared_ptr usc::Server::the_input_configuration() { return input_configuration( [this] { return std::make_shared(the_input_device_hub()); }); } std::shared_ptr usc::Server::the_screen() { return screen( [this] { return std::make_shared( the_performance_booster(), the_screen_hardware(), the_compositor(), the_display(), the_touch_visualizer(), the_main_loop(), the_clock(), MirScreen::Timeouts{ inactivity_display_off_timeout(), inactivity_display_dim_timeout()}, MirScreen::Timeouts{ notification_display_off_timeout(), notification_display_dim_timeout()}, MirScreen::Timeouts{ snap_decision_display_off_timeout(), snap_decision_display_dim_timeout()}); }); } std::shared_ptr usc::Server::the_screen_event_handler() { return screen_event_handler( [this] { return std::make_shared( the_screen(), the_main_loop(), power_key_ignore_timeout(), shutdown_timeout(), [] { if (system("shutdown -P now")); }); // ignore warning }); } std::shared_ptr usc::Server::the_screen_hardware() { return screen_hardware( [this] { return std::make_shared(dbus_bus_address()); }); } std::shared_ptr usc::Server::the_dbus_event_loop() { return dbus_loop( [this] { return std::make_shared(); }); } std::shared_ptr usc::Server::the_dbus_connection_thread() { return dbus_thread( [this] { return std::make_shared(the_dbus_event_loop()); }); } std::shared_ptr usc::Server::the_unity_screen_service() { return unity_screen_service( [this] { return std::make_shared( the_dbus_event_loop(), dbus_bus_address(), the_screen()); }); } std::shared_ptr usc::Server::the_unity_input_service() { return unity_input_service( [this] { return std::make_shared( the_dbus_event_loop(), dbus_bus_address(), the_input_configuration()); }); } std::shared_ptr usc::Server::the_clock() { return clock( [this] { return std::make_shared(); }); } std::string usc::Server::dbus_bus_address() { static char const* const default_bus_address{"unix:path=/var/run/dbus/system_bus_socket"}; char const* bus = getenv("DBUS_SYSTEM_BUS_ADDRESS"); if (!bus) bus = default_bus_address; return std::string{bus}; } unity-system-compositor-0.4.3+16.04.20160323/src/mir_input_configuration.cpp0000644000015600001650000001207212674474272027216 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #include "mir_input_configuration.h" #include "mir/input/input_device_observer.h" #include "mir/input/input_device_hub.h" #include "mir/input/pointer_configuration.h" #include "mir/input/touchpad_configuration.h" #include "mir/input/device.h" namespace mi = mir::input; namespace { struct DeviceObserver : mi::InputDeviceObserver { usc::MirInputConfiguration* conf; DeviceObserver(usc::MirInputConfiguration* conf) : conf{conf} { } void device_added(std::shared_ptr const& device) override { conf->device_added(device); } void device_changed(std::shared_ptr const&) override { } void device_removed(std::shared_ptr const&device) override { conf->device_removed(device); } void changes_complete() override { } }; } usc::MirInputConfiguration::MirInputConfiguration(std::shared_ptr const& device_hub) : observer{std::make_shared(this)} { device_hub->add_observer(observer); } void usc::MirInputConfiguration::device_removed(std::shared_ptr const& dev) { std::lock_guard lock(devices_lock); touchpads.erase(dev); mice.erase(dev); } void usc::MirInputConfiguration::configure_mouse(mi::Device& dev) { dev.apply_pointer_configuration(mouse_pointer_config); } void usc::MirInputConfiguration::configure_touchpad(mi::Device& dev) { dev.apply_pointer_configuration(touchpad_pointer_config); dev.apply_touchpad_configuration(touchpad_config); } void usc::MirInputConfiguration::device_added(std::shared_ptr const& dev) { std::lock_guard lock(devices_lock); if (contains(dev->capabilities(), mi::DeviceCapability::touchpad)) { touchpads.insert(dev); configure_touchpad(*dev); } else if (contains(dev->capabilities(), mi::DeviceCapability::pointer)) { mice.insert(dev); configure_mouse(*dev); } } void usc::MirInputConfiguration::update_touchpads() { for (auto const& touchpad : touchpads) configure_touchpad(*touchpad); } void usc::MirInputConfiguration::update_mice() { for (auto const& mouse : mice) configure_mouse(*mouse); } void usc::MirInputConfiguration::set_mouse_primary_button(int32_t button) { mouse_pointer_config.handedness = button == 0 ? mir_pointer_handedness_right : mir_pointer_handedness_left; update_mice(); } void usc::MirInputConfiguration::set_mouse_cursor_speed(double speed) { double clamped = speed; if (clamped < 0.0) clamped = 0.0; if (clamped > 1.0) clamped = 1.0; mouse_pointer_config.cursor_acceleration_bias = clamped * 2.0 - 1.0; update_mice(); } void usc::MirInputConfiguration::set_mouse_scroll_speed(double speed) { mouse_pointer_config.horizontal_scroll_scale = speed; mouse_pointer_config.vertical_scroll_scale = speed; update_mice(); } void usc::MirInputConfiguration::set_touchpad_primary_button(int32_t button) { touchpad_pointer_config.handedness = button == 0?mir_pointer_handedness_right:mir_pointer_handedness_left; update_touchpads(); } void usc::MirInputConfiguration::set_touchpad_cursor_speed(double speed) { double clamped = speed; if (clamped < 0.0) clamped = 0.0; if (clamped > 1.0) clamped = 1.0; touchpad_pointer_config.cursor_acceleration_bias = clamped * 2.0 - 1.0; update_touchpads(); } void usc::MirInputConfiguration::set_touchpad_scroll_speed(double speed) { touchpad_pointer_config.horizontal_scroll_scale = speed; touchpad_pointer_config.vertical_scroll_scale = speed; update_touchpads(); } void usc::MirInputConfiguration::set_two_finger_scroll(bool enable) { MirTouchpadScrollModes current = touchpad_config.scroll_mode; if (enable) current |= mir_touchpad_scroll_mode_two_finger_scroll; else current &= ~mir_touchpad_scroll_mode_two_finger_scroll; touchpad_config.scroll_mode = current; update_touchpads(); } void usc::MirInputConfiguration::set_tap_to_click(bool enable) { touchpad_config.tap_to_click = enable; update_touchpads(); } void usc::MirInputConfiguration::set_disable_touchpad_while_typing(bool enable) { touchpad_config.disable_while_typing = enable; update_touchpads(); } void usc::MirInputConfiguration::set_disable_touchpad_with_mouse(bool enable) { touchpad_config.disable_with_mouse = enable; update_touchpads(); } unity-system-compositor-0.4.3+16.04.20160323/src/server.h0000644000015600001650000001375012674474277023245 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_SERVER_H_ #define USC_SERVER_H_ #include #include #include #include namespace mir { namespace input { class EventFilter; } } namespace usc { class Spinner; class SessionSwitcher; class DMMessageHandler; class DMConnection; class PerformanceBooster; class Screen; class ScreenHardware; class UnityScreenService; class InputConfiguration; class UnityInputService; class DBusConnectionThread; class DBusEventLoop; class Clock; class Server : private mir::Server { public: explicit Server(int argc, char** argv); using mir::Server::add_init_callback; using mir::Server::run; using mir::Server::the_main_loop; using mir::Server::the_composite_event_filter; using mir::Server::the_display; using mir::Server::the_compositor; using mir::Server::the_touch_visualizer; virtual std::shared_ptr the_performance_booster(); virtual std::shared_ptr the_spinner(); virtual std::shared_ptr the_dm_message_handler(); virtual std::shared_ptr the_dm_connection(); virtual std::shared_ptr the_screen(); virtual std::shared_ptr the_input_configuration(); virtual std::shared_ptr the_screen_event_handler(); virtual std::shared_ptr the_screen_hardware(); virtual std::shared_ptr the_unity_screen_service(); virtual std::shared_ptr the_unity_input_service(); virtual std::shared_ptr the_dbus_event_loop(); virtual std::shared_ptr the_dbus_connection_thread(); virtual std::shared_ptr the_clock(); bool show_version() { return the_options()->is_set("version"); } bool disable_inactivity_policy() { return the_options()->get("disable-inactivity-policy", false); } std::string blacklist() { auto x = the_options()->get("blacklist", ""); return x; } bool public_socket() { return !the_options()->is_set("no-file") && the_options()->get("public-socket", true); } std::string get_socket_file() { // the_socket_file is private, so we have to re-implement it here return the_options()->get("file", "/tmp/mir_socket"); } private: inline auto the_options() -> decltype(mir::Server::get_options()) { return mir::Server::get_options(); } std::chrono::milliseconds inactivity_display_off_timeout() { using namespace std::chrono; return duration_cast( seconds{the_options()->get("inactivity-display-off-timeout", 60)}); } std::chrono::milliseconds inactivity_display_dim_timeout() { using namespace std::chrono; return duration_cast( seconds{the_options()->get("inactivity-display-dim-timeout", 45)}); } std::chrono::milliseconds notification_display_off_timeout() { using namespace std::chrono; return duration_cast( seconds{the_options()->get("notification-display-off-timeout", 15)}); } std::chrono::milliseconds notification_display_dim_timeout() { using namespace std::chrono; return duration_cast( seconds{the_options()->get("notification-display-dim-timeout", 12)}); } std::chrono::milliseconds snap_decision_display_off_timeout() { using namespace std::chrono; return duration_cast( seconds{the_options()->get("snap-decision-display-off-timeout", 60)}); } std::chrono::milliseconds snap_decision_display_dim_timeout() { using namespace std::chrono; return duration_cast( seconds{the_options()->get("snap-decision-display-dim-timeout", 50)}); } std::chrono::milliseconds shutdown_timeout() { return std::chrono::milliseconds{ the_options()->get("shutdown-timeout", 5000)}; } std::chrono::milliseconds power_key_ignore_timeout() { return std::chrono::milliseconds{ the_options()->get("power-key-ignore-timeout", 2000)}; } bool enable_hardware_cursor() { return the_options()->get("enable-hardware-cursor", false); } std::string spinner_executable() { // TODO: once our default spinner is ready for use everywhere, replace // default value with DEFAULT_SPINNER instead of the empty string. auto x = the_options()->get("spinner", ""); return x; } virtual std::shared_ptr the_session_switcher(); std::string dbus_bus_address(); mir::CachedPtr spinner; mir::CachedPtr dm_connection; mir::CachedPtr session_switcher; mir::CachedPtr screen; mir::CachedPtr input_configuration; mir::CachedPtr screen_event_handler; mir::CachedPtr screen_hardware; mir::CachedPtr dbus_thread; mir::CachedPtr dbus_loop; mir::CachedPtr unity_screen_service; mir::CachedPtr unity_input_service; mir::CachedPtr clock; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/com.canonical.Unity.Screen.xml0000644000015600001650000000212612674474272027327 0ustar pbuserpbgroup00000000000000 unity-system-compositor-0.4.3+16.04.20160323/src/powerd_mediator.cpp0000644000015600001650000003426712674474272025457 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #include "powerd_mediator.h" #include "thread_name.h" #include "scoped_dbus_error.h" #include "dbus_message_handle.h" #include #include namespace { char const* const powerd_service_name = "com.canonical.powerd"; char const* const powerd_service_interface = "com.canonical.powerd"; char const* const powerd_service_path = "/com/canonical/powerd"; usc::DBusMessageHandle make_powerd_method_call_message( char const* method, int first_arg_type, va_list args) { return usc::DBusMessageHandle{ dbus_message_new_method_call( powerd_service_name, powerd_service_path, powerd_service_interface, method), first_arg_type, args}; } } usc::PowerdMediator::PowerdMediator(std::string const& bus_addr) : connection{std::make_shared(bus_addr.c_str())}, pending_suspend_block_request{false}, dim_brightness_{10}, min_brightness_{0}, max_brightness_{102}, normal_brightness_{102}, current_brightness{0}, backlight_state{BacklightState::normal}, auto_brightness_supported_{false}, auto_brightness_requested{false}, proximity_enabled{false}, sys_state{SysState::unknown} { dbus_event_loop.add_connection(connection); connection->add_match( "type='signal'," "sender='com.canonical.powerd'," "interface='com.canonical.powerd'," "member='SysPowerStateChange'"); connection->add_match( "type='signal'," "sender='org.freedesktop.DBus'," "interface='org.freedesktop.DBus'," "member='NameOwnerChanged'"); connection->add_filter(handle_dbus_message_thunk, this); std::promise event_loop_started; auto event_loop_started_future = event_loop_started.get_future(); dbus_loop_thread = std::thread( [this,&event_loop_started] { usc::set_thread_name("USC/DBusPowerd"); dbus_event_loop.run(event_loop_started); }); event_loop_started_future.wait(); init_powerd_state(ForceDisableSuspend::yes); } usc::PowerdMediator::~PowerdMediator() { turn_off_backlight(); dbus_event_loop.stop(); dbus_loop_thread.join(); } void usc::PowerdMediator::set_dim_backlight() { std::lock_guard lock{mutex}; change_backlight_state(BacklightState::dim, ForceBacklightState::no); } void usc::PowerdMediator::set_normal_backlight() { std::lock_guard lock{mutex}; if (auto_brightness_supported_ && auto_brightness_requested) change_backlight_state(BacklightState::automatic, ForceBacklightState::no); else change_backlight_state(BacklightState::normal, ForceBacklightState::no); } void usc::PowerdMediator::turn_off_backlight() { std::lock_guard lock{mutex}; change_backlight_state(BacklightState::off, ForceBacklightState::no); } void usc::PowerdMediator::change_backlight_values(int dim, int normal) { std::lock_guard lock{mutex}; if (is_valid_brightness(dim)) dim_brightness_ = dim; if (is_valid_brightness(normal)) normal_brightness_ = normal; } void usc::PowerdMediator::allow_suspend() { std::lock_guard lock{mutex}; if (!suspend_block_cookie.empty()) { auto const cstr = suspend_block_cookie.c_str(); invoke_with_reply("clearSysState", DBUS_TYPE_STRING, &cstr, DBUS_TYPE_INVALID); suspend_block_cookie.clear(); } pending_suspend_block_request = false; } void usc::PowerdMediator::disable_suspend() { std::unique_lock lock{mutex}; if (request_suspend_block()) { lock.unlock(); wait_for_sys_state(SysState::active); } } void usc::PowerdMediator::enable_auto_brightness(bool enable) { std::lock_guard lock{mutex}; auto_brightness_requested = enable; if (auto_brightness_supported_ && enable) change_backlight_state(BacklightState::automatic, ForceBacklightState::no); else change_backlight_state(BacklightState::normal, ForceBacklightState::no); } bool usc::PowerdMediator::auto_brightness_supported() { std::lock_guard lock{mutex}; return auto_brightness_supported_; } void usc::PowerdMediator::set_brightness(int brightness) { std::lock_guard lock{mutex}; normal_brightness_ = brightness; if (backlight_state != BacklightState::automatic) change_backlight_state(BacklightState::normal, ForceBacklightState::yes); } int usc::PowerdMediator::min_brightness() { std::lock_guard lock{mutex}; return min_brightness_; } int usc::PowerdMediator::max_brightness() { std::lock_guard lock{mutex}; return max_brightness_; } void usc::PowerdMediator::enable_proximity(bool enable) { std::lock_guard lock{mutex}; enable_proximity_l(enable, ForceProximity::no); } bool usc::PowerdMediator::is_system_suspended() { std::lock_guard lock{mutex}; return sys_state == SysState::suspend; } ::DBusHandlerResult usc::PowerdMediator::handle_dbus_message_thunk( ::DBusConnection* connection, DBusMessage* message, void* user_data) { auto const powerd_mediator = static_cast(user_data); return powerd_mediator->handle_dbus_message(connection, message, user_data); } ::DBusHandlerResult usc::PowerdMediator::handle_dbus_message( ::DBusConnection* connection, ::DBusMessage* message, void* user_data) { ScopedDBusError error; if (dbus_message_is_signal(message, powerd_service_interface, "SysPowerStateChange")) { int32_t state{-1}; dbus_message_get_args( message, &error, DBUS_TYPE_INT32, &state, DBUS_TYPE_INVALID); if (!error) { update_sys_state(state == static_cast(SysState::suspend) ? SysState::suspend : SysState::active); } } else if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) { char const* name = ""; char const* old_owner = ""; char const* new_owner = ""; dbus_message_get_args( message, &error, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID); if (!error && std::string{powerd_service_name} == name && *old_owner == '\0' && *new_owner != '\0') { init_powerd_state(ForceDisableSuspend::no); } } return DBUS_HANDLER_RESULT_HANDLED; } void usc::PowerdMediator::init_powerd_state(ForceDisableSuspend force_disable_suspend) { std::lock_guard lock{mutex}; init_brightness_params(); /* * A suspend block request needs to be issued here on the following scenarios: * 1. powerd has restarted and PowerdMediator had already issued a request * to the previous powerd instance * 2. When booting up the screen is assumed to be on and consequently we need to also issue * a system suspend block request. * 3. If powerd interface is not available yet, but the screen had been turned on * then now is the time to issue the request */ if (!suspend_block_cookie.empty() || pending_suspend_block_request || force_disable_suspend == ForceDisableSuspend::yes) { // Clear the previous cookie as powerd has restarted anyway suspend_block_cookie.clear(); if (request_suspend_block()) { /* * If powerd is already up it may already be in the active state * and the SysPowerStateChange signal could have already been broadcasted * before we got a chance to register a listener for it. * We will assume that if the active request succeeds that the system state * will become active at some point in the future - this is only a workaround * for the lack of a system state query api in powerd */ update_sys_state(SysState::active); } } // Powerd may have restarted, re-apply backlight settings change_backlight_state(backlight_state, ForceBacklightState::yes); enable_proximity_l(false, ForceProximity::yes); } void usc::PowerdMediator::init_brightness_params() { auto reply = invoke_with_reply("getBrightnessParams", DBUS_TYPE_INVALID); if (reply) { DBusMessageIter msg_iter; dbus_message_iter_init(reply, &msg_iter); if (dbus_message_iter_get_arg_type(&msg_iter) != DBUS_TYPE_STRUCT) return; DBusMessageIter struct_iter; dbus_message_iter_recurse(&msg_iter, &struct_iter); int32_t dim, min, max, normal; dbus_bool_t auto_b; struct { int type; void* address; } args[]{ { DBUS_TYPE_INT32, &dim }, { DBUS_TYPE_INT32, &min }, { DBUS_TYPE_INT32, &max }, { DBUS_TYPE_INT32, &normal }, { DBUS_TYPE_BOOLEAN, &auto_b } }; for (auto const& arg : args) { if (dbus_message_iter_get_arg_type(&struct_iter) == arg.type) dbus_message_iter_get_basic(&struct_iter, arg.address); else return; dbus_message_iter_next(&struct_iter); } dim_brightness_ = dim; min_brightness_ = min; max_brightness_ = max; normal_brightness_ = normal; auto_brightness_supported_ = (auto_b == TRUE); } } void usc::PowerdMediator::change_backlight_state( BacklightState new_state, ForceBacklightState force_backlight_state) { if (backlight_state == new_state && force_backlight_state == ForceBacklightState::no) { return; } update_current_brightness_for_state(new_state); backlight_state = new_state; if (new_state == BacklightState::automatic) { dbus_bool_t const enable{TRUE}; invoke_with_reply("userAutobrightnessEnable", DBUS_TYPE_BOOLEAN, &enable, DBUS_TYPE_INVALID); } else { int32_t const b{current_brightness}; invoke_with_reply("setUserBrightness", DBUS_TYPE_INT32, &b, DBUS_TYPE_INVALID); } } void usc::PowerdMediator::enable_proximity_l( bool enable, ForceProximity force_proximity) { if (proximity_enabled == enable && force_proximity == ForceProximity::no) return; auto const name = "unity-system-compositor"; auto const request = enable ? "enableProximityHandling" : "disableProximityHandling"; invoke_with_reply(request, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); proximity_enabled = enable; } bool usc::PowerdMediator::is_valid_brightness(int brightness) { return brightness >= min_brightness_ && brightness <= max_brightness_; } bool usc::PowerdMediator::request_suspend_block() { if (suspend_block_cookie.empty()) { char const* const name{"com.canonical.Unity.Screen"}; int const sys_state_active{static_cast(SysState::active)}; auto reply = invoke_with_reply( "requestSysState", DBUS_TYPE_STRING, &name, DBUS_TYPE_INT32, &sys_state_active, DBUS_TYPE_INVALID); if (reply) { char const* cookie{nullptr}; dbus_message_get_args( reply, nullptr, DBUS_TYPE_STRING, &cookie, DBUS_TYPE_INVALID); if (cookie) suspend_block_cookie = cookie; return true; } else { pending_suspend_block_request = true; } } return false; } void usc::PowerdMediator::update_sys_state(SysState state) { { std::lock_guard lock{sys_state_mutex}; sys_state = state; } sys_state_changed.notify_one(); } void usc::PowerdMediator::wait_for_sys_state(SysState state) { std::unique_lock lock{sys_state_mutex}; sys_state_changed.wait(lock, [this, state]{ return sys_state == state; }); } void usc::PowerdMediator::update_current_brightness_for_state(BacklightState state) { switch (state) { case BacklightState::normal: current_brightness = normal_brightness_; break; case BacklightState::off: current_brightness = 0; break; case BacklightState::dim: current_brightness = dim_brightness_; break; default: break; } } usc::DBusMessageHandle usc::PowerdMediator::invoke_with_reply( char const* method, int first_arg_type, ...) { va_list args; va_start(args, first_arg_type); auto msg = make_powerd_method_call_message(method, first_arg_type, args); va_end(args); std::promise reply_promise; auto reply_future = reply_promise.get_future(); auto const send_message_with_reply = [this, &msg, &reply_promise] { auto const reply = dbus_connection_send_with_reply_and_block( *connection, msg, DBUS_TIMEOUT_USE_DEFAULT, nullptr); reply_promise.set_value(reply); }; // Run in the context of the dbus event loop to avoid // strange dbus behaviors. if (std::this_thread::get_id() == dbus_loop_thread.get_id()) send_message_with_reply(); else dbus_event_loop.enqueue(send_message_with_reply); return usc::DBusMessageHandle{reply_future.get()}; } unity-system-compositor-0.4.3+16.04.20160323/src/asio_dm_connection.cpp0000644000015600001650000001127212674474272026114 0ustar pbuserpbgroup00000000000000/* * Copyright © 2013-2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Robert Ancell * Alexandros Frantzis */ #include "asio_dm_connection.h" #include #include namespace ba = boost::asio; namespace bs = boost::system; usc::AsioDMConnection::AsioDMConnection( int from_dm_fd, int to_dm_fd, std::shared_ptr const& dm_message_handler) : from_dm_pipe{io_service, from_dm_fd}, to_dm_pipe{io_service, to_dm_fd}, dm_message_handler{dm_message_handler} { } usc::AsioDMConnection::~AsioDMConnection() { io_service.stop(); if (io_thread.joinable()) io_thread.join(); } void usc::AsioDMConnection::start() { std::cerr << "dm_connection_start" << std::endl; send_ready(); read_header(); io_thread = std::thread{ [this] { io_service.run(); }}; } void usc::AsioDMConnection::send_ready() { send(USCMessageID::ready, ""); } void usc::AsioDMConnection::read_header() { ba::async_read(from_dm_pipe, ba::buffer(message_header_bytes), std::bind(&AsioDMConnection::on_read_header, this, std::placeholders::_1)); } void usc::AsioDMConnection::on_read_header(bs::error_code const& ec) { if (!ec) { size_t const payload_length = message_header_bytes[2] << 8 | message_header_bytes[3]; ba::async_read(from_dm_pipe, message_payload_buffer, ba::transfer_exactly(payload_length), std::bind(&AsioDMConnection::on_read_payload, this, std::placeholders::_1)); } else std::cerr << "Failed to read header" << std::endl; } void usc::AsioDMConnection::on_read_payload(const bs::error_code& ec) { if (!ec) { auto message_id = (USCMessageID) (message_header_bytes[0] << 8 | message_header_bytes[1]); size_t const payload_length = message_header_bytes[2] << 8 | message_header_bytes[3]; switch (message_id) { case USCMessageID::ping: { std::cerr << "ping" << std::endl; send(USCMessageID::pong, ""); break; } case USCMessageID::pong: { std::cerr << "pong" << std::endl; break; } case USCMessageID::set_active_session: { std::ostringstream ss; ss << &message_payload_buffer; auto client_name = ss.str(); std::cerr << "set_active_session '" << client_name << "'" << std::endl; dm_message_handler->set_active_session(client_name); break; } case USCMessageID::set_next_session: { std::ostringstream ss; ss << &message_payload_buffer; auto client_name = ss.str(); std::cerr << "set_next_session '" << client_name << "'" << std::endl; dm_message_handler->set_next_session(client_name); break; } default: std::cerr << "Ignoring unknown message " << (uint16_t) message_id << " with " << payload_length << " octets" << std::endl; break; } } else std::cerr << "Failed to read payload" << std::endl; read_header(); } void usc::AsioDMConnection::send(USCMessageID id, std::string const& body) { const size_t size = body.size(); const uint16_t _id = (uint16_t) id; const unsigned char header_bytes[4] = { static_cast((_id >> 8) & 0xFF), static_cast((_id >> 0) & 0xFF), static_cast((size >> 8) & 0xFF), static_cast((size >> 0) & 0xFF) }; write_buffer.resize(sizeof header_bytes + size); std::copy(header_bytes, header_bytes + sizeof header_bytes, write_buffer.begin()); std::copy(body.begin(), body.end(), write_buffer.begin() + sizeof header_bytes); // FIXME: Make asynchronous ba::write(to_dm_pipe, ba::buffer(write_buffer)); } unity-system-compositor-0.4.3+16.04.20160323/src/mir_screen.cpp0000644000015600001650000003276712674474277024431 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014-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 warranty of * MERCHANTABILITY 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 . */ #include "mir_screen.h" #include "clock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "performance_booster.h" #include "screen_hardware.h" #include "power_state_change_reason.h" #include "server.h" namespace mi = mir::input; namespace mg = mir::graphics; namespace { void log_exception_in(char const* const func) { static char const* const warning_format = "%s failed: %s"; std::stringstream buffer; mir::report_exception(buffer); mir::log(::mir::logging::Severity::warning, "usc::MirScreen", warning_format, func, buffer.str().c_str()); } } class usc::MirScreen::LockableCallback : public mir::LockableCallback { public: LockableCallback(MirScreen* mir_screen) : mir_screen{mir_screen} { } void lock() override { guard_lock = std::unique_lock{mir_screen->guard}; } void unlock() override { guard_lock.unlock(); } protected: MirScreen* const mir_screen; std::unique_lock guard_lock; }; class usc::MirScreen::PowerOffLockableCallback : public usc::MirScreen::LockableCallback { using usc::MirScreen::LockableCallback::LockableCallback; void operator()() override { // We need to hold the lock before calling power_off_alarm_notification_l() // (and not acquire it in that function) as we override the function to // test for deadlock conditions. C.f. test_deadlock_lp1491566.cpp assert(guard_lock.owns_lock()); mir_screen->power_off_alarm_notification_l(); } }; class usc::MirScreen::DimmerLockableCallback : public usc::MirScreen::LockableCallback { using usc::MirScreen::LockableCallback::LockableCallback; void operator()() override { // We need to hold the lock before calling dimmer_alarm_notification_l() // (and not acquire it in that function) as we override the function to // test for deadlock conditions. C.f. test_deadlock_lp1491566.cpp assert(guard_lock.owns_lock()); mir_screen->dimmer_alarm_notification_l(); } }; usc::MirScreen::MirScreen( std::shared_ptr const& perf_booster, std::shared_ptr const& screen_hardware, std::shared_ptr const& compositor, std::shared_ptr const& display, std::shared_ptr const& touch_visualizer, std::shared_ptr const& alarm_factory, std::shared_ptr const& clock, Timeouts inactivity_timeouts, Timeouts notification_timeouts, Timeouts snap_decision_timeouts) : perf_booster{perf_booster}, screen_hardware{screen_hardware}, compositor{compositor}, display{display}, touch_visualizer{touch_visualizer}, alarm_factory{alarm_factory}, clock{clock}, power_off_alarm{alarm_factory->create_alarm( std::make_shared(this))}, dimmer_alarm{alarm_factory->create_alarm( std::make_shared(this))}, inactivity_timeouts(inactivity_timeouts), notification_timeouts(notification_timeouts), snap_decision_timeouts(snap_decision_timeouts), current_power_mode{MirPowerMode::mir_power_mode_on}, restart_timers{true}, power_state_change_handler{[](MirPowerMode,PowerStateChangeReason){}}, allow_proximity_to_turn_on_screen{false}, turned_on_by_user{true} { try { /* * Make sure the compositor is running as certain conditions can * cause Mir to tear down the compositor threads before we get * to this point. See bug #1410381. */ compositor->start(); reset_timers_l(PowerStateChangeReason::inactivity); } catch (...) { log_exception_in(__func__); throw; } } usc::MirScreen::~MirScreen() = default; void usc::MirScreen::keep_display_on_temporarily() { std::lock_guard lock{guard}; reset_timers_l(PowerStateChangeReason::inactivity); if (current_power_mode == MirPowerMode::mir_power_mode_on) { screen_hardware->set_normal_backlight(); screen_hardware->enable_proximity(false); } } void usc::MirScreen::enable_inactivity_timers(bool enable) { std::lock_guard lock{guard}; enable_inactivity_timers_l(enable); } MirPowerMode usc::MirScreen::get_screen_power_mode() { std::lock_guard lock{guard}; return current_power_mode; } void usc::MirScreen::set_screen_power_mode(MirPowerMode mode, PowerStateChangeReason reason) { std::lock_guard lock{guard}; set_screen_power_mode_l(mode, reason); } void usc::MirScreen::keep_display_on(bool on) { std::lock_guard lock{guard}; restart_timers = !on; enable_inactivity_timers_l(!on); if (on) set_screen_power_mode_l(MirPowerMode::mir_power_mode_on, PowerStateChangeReason::unknown); } void usc::MirScreen::set_brightness(int brightness) { std::lock_guard lock{guard}; screen_hardware->set_brightness(brightness); } void usc::MirScreen::enable_auto_brightness(bool enable) { std::lock_guard lock{guard}; screen_hardware->enable_auto_brightness(enable); } void usc::MirScreen::set_inactivity_timeouts(int raw_poweroff_timeout, int raw_dimmer_timeout) { std::lock_guard lock{guard}; std::chrono::seconds the_power_off_timeout{raw_poweroff_timeout}; std::chrono::seconds the_dimming_timeout{raw_dimmer_timeout}; if (raw_poweroff_timeout >= 0) inactivity_timeouts.power_off_timeout = std::chrono::duration_cast(the_power_off_timeout); if (raw_dimmer_timeout >= 0) inactivity_timeouts.dimming_timeout = std::chrono::duration_cast(the_dimming_timeout); cancel_timers_l(PowerStateChangeReason::inactivity); reset_timers_l(PowerStateChangeReason::inactivity); } void usc::MirScreen::set_screen_power_mode_l(MirPowerMode mode, PowerStateChangeReason reason) try { if (!is_screen_change_allowed_l(mode, reason)) return; // Notifications don't turn on the screen directly, they rely on proximity events if (mode == MirPowerMode::mir_power_mode_on && (reason == PowerStateChangeReason::notification || reason == PowerStateChangeReason::snap_decision || reason == PowerStateChangeReason::call_done)) { if (current_power_mode != MirPowerMode::mir_power_mode_on) { allow_proximity_to_turn_on_screen = true; screen_hardware->enable_proximity(true); } else { screen_hardware->set_normal_backlight(); } if (reason == PowerStateChangeReason::call_done && !turned_on_by_user) { reset_timers_ignoring_power_mode_l(reason, ForceResetTimers::yes); } else { reset_timers_ignoring_power_mode_l(reason, ForceResetTimers::no); } return; } if (mode == MirPowerMode::mir_power_mode_on) { /* The screen may be dim, but on - make sure to reset backlight */ if (current_power_mode == MirPowerMode::mir_power_mode_on) screen_hardware->set_normal_backlight(); configure_display_l(mode, reason); reset_timers_l(reason); } else { cancel_timers_l(reason); configure_display_l(mode, reason); } } catch (std::exception const&) { log_exception_in(__func__); } void usc::MirScreen::configure_display_l(MirPowerMode mode, PowerStateChangeReason reason) try { if (reason != PowerStateChangeReason::proximity) { screen_hardware->enable_proximity(false); allow_proximity_to_turn_on_screen = reason != PowerStateChangeReason::power_key; } if (current_power_mode == mode) return; allow_proximity_to_turn_on_screen = mode == mir_power_mode_off && reason != PowerStateChangeReason::power_key; if (mode == mir_power_mode_on) { turned_on_by_user = reason == PowerStateChangeReason::power_key; } std::shared_ptr displayConfig = display->configuration(); displayConfig->for_each_output( [&](const mg::UserDisplayConfigurationOutput displayConfigOutput) { displayConfigOutput.power_mode = mode; } ); compositor->stop(); bool const power_on = mode == MirPowerMode::mir_power_mode_on; if (power_on) { perf_booster->enable_performance_boost_during_user_interaction(); //Some devices do not turn screen on properly from suspend mode screen_hardware->disable_suspend(); } else { perf_booster->disable_performance_boost_during_user_interaction(); screen_hardware->turn_off_backlight(); } display->configure(*displayConfig.get()); if (power_on) { compositor->start(); screen_hardware->set_normal_backlight(); } current_power_mode = mode; // TODO: Don't call this under lock power_state_change_handler(mode, reason); if (!power_on) screen_hardware->allow_suspend(); } catch (std::exception const&) { log_exception_in(__func__); } void usc::MirScreen::cancel_timers_l(PowerStateChangeReason reason) { if (reason == PowerStateChangeReason::proximity) { next_power_off = {}; next_dimming = {}; return; } power_off_alarm->cancel(); dimmer_alarm->cancel(); next_power_off = {}; next_dimming = {}; } void usc::MirScreen::reset_timers_l(PowerStateChangeReason reason) { if (current_power_mode != MirPowerMode::mir_power_mode_off) reset_timers_ignoring_power_mode_l(reason, ForceResetTimers::no); } void usc::MirScreen::reset_timers_ignoring_power_mode_l( PowerStateChangeReason reason, ForceResetTimers force) { if (!restart_timers) return; auto const timeouts = timeouts_for_l(reason); auto const now = clock->now(); if (timeouts.power_off_timeout.count() > 0) { auto const new_next_power_off = now + timeouts.power_off_timeout; if (new_next_power_off > next_power_off || force == ForceResetTimers::yes) { power_off_alarm->reschedule_in(timeouts.power_off_timeout); next_power_off = new_next_power_off; } } else { power_off_alarm->cancel(); next_power_off = mir::time::Timestamp::max(); } if (timeouts.dimming_timeout.count() > 0) { auto const new_next_dimming = now + timeouts.dimming_timeout; if (new_next_dimming > next_dimming || force == ForceResetTimers::yes) { dimmer_alarm->reschedule_in(timeouts.dimming_timeout); next_dimming = new_next_dimming; } } else { dimmer_alarm->cancel(); next_dimming = mir::time::Timestamp::max(); } } void usc::MirScreen::enable_inactivity_timers_l(bool enable) { if (enable) reset_timers_l(PowerStateChangeReason::inactivity); else cancel_timers_l(PowerStateChangeReason::inactivity); } usc::MirScreen::Timeouts usc::MirScreen::timeouts_for_l(PowerStateChangeReason reason) { if (reason == PowerStateChangeReason::notification || reason == PowerStateChangeReason::proximity || reason == PowerStateChangeReason::call_done) { return notification_timeouts; } else if (reason == PowerStateChangeReason::snap_decision) { return snap_decision_timeouts; } else { return inactivity_timeouts; } } bool usc::MirScreen::is_screen_change_allowed_l(MirPowerMode mode, PowerStateChangeReason reason) { if (mode == MirPowerMode::mir_power_mode_on && reason == PowerStateChangeReason::proximity && !allow_proximity_to_turn_on_screen) { return false; } return true; } void usc::MirScreen::power_off_alarm_notification_l() { configure_display_l(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::inactivity); next_power_off = {}; } void usc::MirScreen::dimmer_alarm_notification_l() { if (current_power_mode != MirPowerMode::mir_power_mode_off) screen_hardware->set_dim_backlight(); next_dimming = {}; } void usc::MirScreen::set_touch_visualization_enabled(bool enabled) { std::lock_guard lock{guard}; if (enabled) touch_visualizer->enable(); else touch_visualizer->disable(); } void usc::MirScreen::register_power_state_change_handler( PowerStateChangeHandler const& handler) { std::lock_guard lock{guard}; power_state_change_handler = handler; } unity-system-compositor-0.4.3+16.04.20160323/src/mir_input_configuration.h0000644000015600001650000000471512674474272026670 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_MIR_INPUT_CONFIGRATION_H_ #define USC_MIR_INPUT_CONFIGRATION_H_ #include "input_configuration.h" #include "mir/input/touchpad_configuration.h" #include "mir/input/pointer_configuration.h" #include #include #include #include namespace mir { namespace input { class InputDeviceHub; class Device; class InputDeviceObserver; } } namespace usc { struct MirInputConfiguration : InputConfiguration { public: MirInputConfiguration(std::shared_ptr const& device_hub); void set_mouse_primary_button(int32_t button) override; void set_mouse_cursor_speed(double speed) override; void set_mouse_scroll_speed(double speed) override; void set_touchpad_primary_button(int32_t button) override; void set_touchpad_cursor_speed(double speed) override; void set_touchpad_scroll_speed(double speed) override; void set_two_finger_scroll(bool enable) override; void set_tap_to_click(bool enable) override; void set_disable_touchpad_while_typing(bool enable) override; void set_disable_touchpad_with_mouse(bool enable) override; void device_added(std::shared_ptr const& device); void device_removed(std::shared_ptr const& device); private: void configure_mouse(mir::input::Device& dev); void configure_touchpad(mir::input::Device& dev); void update_touchpads(); void update_mice(); std::shared_ptr const observer; std::mutex devices_lock; std::unordered_set> touchpads; std::unordered_set> mice; mir::input::PointerConfiguration mouse_pointer_config; mir::input::PointerConfiguration touchpad_pointer_config; mir::input::TouchpadConfiguration touchpad_config; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/thread_name.h0000644000015600001650000000155012674474272024174 0ustar pbuserpbgroup00000000000000/* * Copyright © 2015 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: Alexandros Frantzis */ #ifndef USC_THREAD_NAME_H_ #define USC_THREAD_NAME_H_ #include namespace usc { void set_thread_name(std::string const& name); } #endif unity-system-compositor-0.4.3+16.04.20160323/src/unity_screen_service.cpp0000644000015600001650000002632612674474272026517 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "unity_screen_service.h" #include "screen.h" #include "dbus_message_handle.h" #include "dbus_event_loop.h" #include "dbus_connection_handle.h" #include "scoped_dbus_error.h" #include "unity_screen_service_introspection.h" // autogenerated namespace { char const* const dbus_screen_interface = "com.canonical.Unity.Screen"; char const* const dbus_screen_path = "/com/canonical/Unity/Screen"; char const* const dbus_screen_service_name = "com.canonical.Unity.Screen"; } usc::UnityScreenService::UnityScreenService( std::shared_ptr const& loop, std::string const& address, std::shared_ptr const& screen) : screen{screen}, loop{loop}, connection{std::make_shared(address.c_str())}, request_id{0} { loop->add_connection(connection); connection->request_name(dbus_screen_service_name); connection->add_match( "type='signal'," "sender='org.freedesktop.DBus'," "interface='org.freedesktop.DBus'," "member='NameOwnerChanged'"); connection->add_filter(handle_dbus_message_thunk, this); screen->register_power_state_change_handler( [this](MirPowerMode mode, PowerStateChangeReason reason) { dbus_emit_DisplayPowerStateChange(mode, reason); }); } ::DBusHandlerResult usc::UnityScreenService::handle_dbus_message_thunk( ::DBusConnection* connection, DBusMessage* message, void* user_data) { auto const dbus_screen_service = static_cast(user_data); return dbus_screen_service->handle_dbus_message(connection, message, user_data); } DBusHandlerResult usc::UnityScreenService::handle_dbus_message( ::DBusConnection* connection, DBusMessage* message, void* user_data) { auto const sender = dbus_message_get_sender(message); ScopedDBusError args_error; if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { DBusMessageHandle reply{ dbus_message_new_method_return(message), DBUS_TYPE_STRING, &unity_screen_service_introspection, DBUS_TYPE_INVALID}; dbus_connection_send(connection, reply, nullptr); } else if (dbus_message_is_method_call(message, dbus_screen_interface, "setUserBrightness")) { int32_t brightness{0}; dbus_message_get_args( message, &args_error, DBUS_TYPE_INT32, &brightness, DBUS_TYPE_INVALID); if (!args_error) { dbus_setUserBrightness(brightness); DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); } } else if (dbus_message_is_method_call(message, dbus_screen_interface, "userAutobrightnessEnable")) { dbus_bool_t enable{false}; dbus_message_get_args( message, &args_error, DBUS_TYPE_BOOLEAN, &enable, DBUS_TYPE_INVALID); if (!args_error) { dbus_userAutobrightnessEnable(enable); DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); } } else if (dbus_message_is_method_call(message, dbus_screen_interface, "setInactivityTimeouts")) { int32_t poweroff_timeout{-1}; int32_t dimmer_timeout{-1}; dbus_message_get_args( message, &args_error, DBUS_TYPE_INT32, &poweroff_timeout, DBUS_TYPE_INT32, &dimmer_timeout, DBUS_TYPE_INVALID); if (!args_error) { dbus_setInactivityTimeouts(poweroff_timeout, dimmer_timeout); DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); } } else if (dbus_message_is_method_call(message, dbus_screen_interface, "setTouchVisualizationEnabled")) { dbus_bool_t enable{false}; dbus_message_get_args( message, &args_error, DBUS_TYPE_BOOLEAN, &enable, DBUS_TYPE_INVALID); if (!args_error) { dbus_setTouchVisualizationEnabled(enable); DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); } } else if (dbus_message_is_method_call(message, dbus_screen_interface, "setScreenPowerMode")) { char const* mode = nullptr; int32_t reason{-1}; dbus_message_get_args( message, &args_error, DBUS_TYPE_STRING, &mode, DBUS_TYPE_INT32, &reason, DBUS_TYPE_INVALID); if (!args_error) { auto const result = dbus_setScreenPowerMode(mode, reason); dbus_bool_t bresult = (result == true); DBusMessageHandle reply{ dbus_message_new_method_return(message), DBUS_TYPE_BOOLEAN, &bresult, DBUS_TYPE_INVALID}; dbus_connection_send(connection, reply, nullptr); } } else if (dbus_message_is_method_call(message, dbus_screen_interface, "keepDisplayOn")) { auto const id = dbus_keepDisplayOn(sender); DBusMessageHandle reply{ dbus_message_new_method_return(message), DBUS_TYPE_INT32, &id, DBUS_TYPE_INVALID}; dbus_connection_send(connection, reply, nullptr); } else if (dbus_message_is_method_call(message, dbus_screen_interface, "removeDisplayOnRequest")) { int32_t id{-1}; dbus_message_get_args( message, &args_error, DBUS_TYPE_INT32, &id, DBUS_TYPE_INVALID); if (!args_error) { dbus_removeDisplayOnRequest(sender, id); DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(connection, reply, nullptr); } } else if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) { char const* name = nullptr; char const* old_owner = nullptr; char const* new_owner = nullptr; dbus_message_get_args( message, &args_error, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID); if (!args_error) dbus_NameOwnerChanged(name, old_owner, new_owner); } else if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL) { DBusMessageHandle reply{ dbus_message_new_error(message, DBUS_ERROR_FAILED, "Not supported")}; dbus_connection_send(connection, reply, nullptr); } if (args_error) { DBusMessageHandle reply{ dbus_message_new_error(message, DBUS_ERROR_FAILED, "Invalid arguments")}; dbus_connection_send(connection, reply, nullptr); } return DBUS_HANDLER_RESULT_HANDLED; } void usc::UnityScreenService::dbus_setUserBrightness(int32_t brightness) { screen->set_brightness(brightness); } void usc::UnityScreenService::dbus_userAutobrightnessEnable(dbus_bool_t enable) { screen->enable_auto_brightness(enable == TRUE); } void usc::UnityScreenService::dbus_setInactivityTimeouts( int32_t poweroff_timeout, int32_t dimmer_timeout) { screen->set_inactivity_timeouts(poweroff_timeout, dimmer_timeout); } void usc::UnityScreenService::dbus_setTouchVisualizationEnabled(dbus_bool_t enable) { screen->set_touch_visualization_enabled(enable == TRUE); } bool usc::UnityScreenService::dbus_setScreenPowerMode( std::string const& mode, int32_t reason) { MirPowerMode new_power_mode; // Note: the "standby" and "suspend" modes are mostly unused if (mode == "on") new_power_mode = MirPowerMode::mir_power_mode_on; else if (mode == "standby") // higher power "off" mode (fastest resume) new_power_mode = MirPowerMode::mir_power_mode_standby; else if (mode == "suspend") // medium power "off" mode new_power_mode = MirPowerMode::mir_power_mode_suspend; else if (mode == "off") // lowest power "off" mode (slowest resume) new_power_mode = MirPowerMode::mir_power_mode_off; else return false; screen->set_screen_power_mode( new_power_mode, static_cast(reason)); return true; } int32_t usc::UnityScreenService::dbus_keepDisplayOn( std::string const& sender) { std::lock_guard lock{keep_display_on_mutex}; auto const id = ++request_id; keep_display_on_ids.emplace(sender, id); screen->keep_display_on(true); return id; } void usc::UnityScreenService::dbus_removeDisplayOnRequest( std::string const& sender, int32_t id) { std::lock_guard lock{keep_display_on_mutex}; bool id_removed{false}; auto range = keep_display_on_ids.equal_range(sender); for (auto iter = range.first; iter != range.second; ++iter) { if (iter->second == id) { keep_display_on_ids.erase(iter); id_removed = true; break; } } if (id_removed && keep_display_on_ids.empty()) screen->keep_display_on(false); } void usc::UnityScreenService::dbus_NameOwnerChanged( std::string const& name, std::string const& old_owner, std::string const& new_owner) { if (new_owner.empty() && old_owner == name) { std::lock_guard lock{keep_display_on_mutex}; // If the disconnected client had issued keepDisplayOn requests // and after removing them there are now no more requests left, // tell the screen we don't need to keep the display on. if (keep_display_on_ids.erase(name) > 0 && keep_display_on_ids.empty()) { screen->keep_display_on(false); } } } void usc::UnityScreenService::dbus_emit_DisplayPowerStateChange( MirPowerMode power_mode, PowerStateChangeReason reason) { int32_t const power_state = (power_mode == MirPowerMode::mir_power_mode_off) ? 0 : 1; int32_t const reason_int = static_cast(reason); loop->enqueue( [this, power_state, reason_int] { DBusMessageHandle signal{ dbus_message_new_signal( dbus_screen_path, dbus_screen_interface, "DisplayPowerStateChange"), DBUS_TYPE_INT32, &power_state, DBUS_TYPE_INT32, &reason_int, DBUS_TYPE_INVALID}; dbus_connection_send(*connection, signal, nullptr); }); } unity-system-compositor-0.4.3+16.04.20160323/src/dbus_connection_handle.cpp0000644000015600001650000000547612674474272026762 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "dbus_connection_handle.h" #include "scoped_dbus_error.h" #include #include usc::DBusConnectionHandle::DBusConnectionHandle(std::string const& address) { dbus_threads_init_default(); ScopedDBusError error; connection = dbus_connection_open_private(address.c_str(), &error); if (!connection || error) { BOOST_THROW_EXCEPTION( std::runtime_error("dbus_connection_open: " + error.message_str())); } if (!dbus_bus_register(connection, &error)) { BOOST_THROW_EXCEPTION( std::runtime_error("dbus_bus_register: " + error.message_str())); } } usc::DBusConnectionHandle::~DBusConnectionHandle() { if (dbus_connection_get_is_connected(connection)) dbus_connection_close(connection); dbus_connection_unref(connection); } void usc::DBusConnectionHandle::request_name(char const* name) const { ScopedDBusError error; auto const request_result = dbus_bus_request_name( connection, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error); if (error) { BOOST_THROW_EXCEPTION( std::runtime_error("dbus_request_name: " + error.message_str())); } if (request_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { BOOST_THROW_EXCEPTION( std::runtime_error("dbus_request_name: Failed to become primary owner")); } } void usc::DBusConnectionHandle::add_match(char const* match) const { ScopedDBusError error; dbus_bus_add_match(connection, match, &error); if (error) { BOOST_THROW_EXCEPTION( std::runtime_error("dbus_add_match: " + error.message_str())); } } void usc::DBusConnectionHandle::add_filter( DBusHandleMessageFunction filter_func, void* user_data) const { auto const added = dbus_connection_add_filter( connection, filter_func, user_data, nullptr); if (!added) { BOOST_THROW_EXCEPTION( std::runtime_error("dbus_connection_add_filter: Failed to add filter")); } } usc::DBusConnectionHandle::operator ::DBusConnection*() const { return connection; } unity-system-compositor-0.4.3+16.04.20160323/src/steady_clock.h0000644000015600001650000000155112674474272024372 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_STEADY_CLOCK_H_ #define USC_STEADY_CLOCK_H_ #include "clock.h" namespace usc { class SteadyClock : public Clock { public: mir::time::Timestamp now() const override; private: std::chrono::steady_clock clock; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/mir_screen.h0000644000015600001650000001072512674474277024064 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014-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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_MIR_SCREEN_H_ #define USC_MIR_SCREEN_H_ #include "screen.h" #include #include #include #include enum class PowerStateChangeReason; namespace mir { namespace compositor { class Compositor; } namespace graphics {class Display;} namespace input { class TouchVisualizer; } namespace time { class AlarmFactory; class Alarm; } } namespace usc { class PerformanceBooster; class Server; class ScreenHardware; class Clock; class MirScreen: public Screen { public: struct Timeouts { std::chrono::milliseconds power_off_timeout; std::chrono::milliseconds dimming_timeout; }; MirScreen(std::shared_ptr const& perf_booster, std::shared_ptr const& screen_hardware, std::shared_ptr const& compositor, std::shared_ptr const& display, std::shared_ptr const& touch_visualizer, std::shared_ptr const& alarm_factory, std::shared_ptr const& clock, Timeouts inactivity_timeouts, Timeouts notification_timeouts, Timeouts call_timeouts); ~MirScreen(); void enable_inactivity_timers(bool enable) override; void keep_display_on_temporarily() override; MirPowerMode get_screen_power_mode() override; void set_screen_power_mode(MirPowerMode mode, PowerStateChangeReason reason) override; void keep_display_on(bool on) override; void set_brightness(int brightness) override; void enable_auto_brightness(bool enable) override; void set_inactivity_timeouts(int power_off_timeout, int dimmer_timeout) override; void set_touch_visualization_enabled(bool enabled) override; void register_power_state_change_handler( PowerStateChangeHandler const& power_state_change_handler) override; protected: // These are protected virtual because we need to override them in tests virtual void power_off_alarm_notification_l(); virtual void dimmer_alarm_notification_l(); private: enum class ForceResetTimers { no, yes }; class LockableCallback; class PowerOffLockableCallback; class DimmerLockableCallback; void set_screen_power_mode_l(MirPowerMode mode, PowerStateChangeReason reason); void configure_display_l(MirPowerMode mode, PowerStateChangeReason reason); void cancel_timers_l(PowerStateChangeReason reason); void reset_timers_l(PowerStateChangeReason reason); void reset_timers_ignoring_power_mode_l(PowerStateChangeReason reason, ForceResetTimers force); void enable_inactivity_timers_l(bool flag); Timeouts timeouts_for_l(PowerStateChangeReason reason); bool is_screen_change_allowed_l(MirPowerMode mode, PowerStateChangeReason reason); void long_press_alarm_notification(); std::shared_ptr const perf_booster; std::shared_ptr const screen_hardware; std::shared_ptr const compositor; std::shared_ptr const display; std::shared_ptr const touch_visualizer; std::shared_ptr const alarm_factory; std::shared_ptr const clock; std::unique_ptr const power_off_alarm; std::unique_ptr const dimmer_alarm; std::mutex guard; Timeouts inactivity_timeouts; Timeouts notification_timeouts; Timeouts snap_decision_timeouts; mir::time::Timestamp next_power_off{}; mir::time::Timestamp next_dimming{}; MirPowerMode current_power_mode; bool restart_timers; PowerStateChangeHandler power_state_change_handler; bool allow_proximity_to_turn_on_screen; bool turned_on_by_user; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/asio_dm_connection.h0000644000015600001650000000402712674474272025561 0ustar pbuserpbgroup00000000000000/* * Copyright © 2013-2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Robert Ancell * Alexandros Frantzis */ #ifndef USC_ASIO_DM_CONNECTION_H_ #define USC_ASIO_DM_CONNECTION_H_ #include "dm_connection.h" #include #include namespace usc { class AsioDMConnection : public DMConnection { public: AsioDMConnection( int from_dm_fd, int to_dm_fd, std::shared_ptr const& dm_message_handler); ~AsioDMConnection(); void start() override; private: enum class USCMessageID { ping = 0, pong = 1, ready = 2, session_connected = 3, set_active_session = 4, set_next_session = 5, }; void send_ready(); void read_header(); void on_read_header(const boost::system::error_code& ec); void on_read_payload(const boost::system::error_code& ec); void send(USCMessageID id, std::string const& body); boost::asio::io_service io_service; boost::asio::posix::stream_descriptor from_dm_pipe; boost::asio::posix::stream_descriptor to_dm_pipe; std::thread io_thread; std::shared_ptr const dm_message_handler; static size_t const size_of_header = 4; unsigned char message_header_bytes[size_of_header]; boost::asio::streambuf message_payload_buffer; std::vector write_buffer; }; } #endif /* USC_ASIO_DM_CONNECTION_H_ */ unity-system-compositor-0.4.3+16.04.20160323/src/thread_name.cpp0000644000015600001650000000202412674474272024524 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014-2015 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: Alexandros Frantzis */ #include "thread_name.h" #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include void usc::set_thread_name(std::string const& name) { static size_t const max_name_len = 15; auto const proper_name = name.substr(0, max_name_len); pthread_setname_np(pthread_self(), proper_name.c_str()); } unity-system-compositor-0.4.3+16.04.20160323/src/main.cpp0000644000015600001650000000217512674474272023210 0ustar pbuserpbgroup00000000000000/* * Copyright © 2013-2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Robert Ancell * Alexandros Frantzis */ #include "system_compositor.h" #include "server.h" #include #include int main(int argc, char *argv[]) try { auto const config = std::make_shared(argc, argv); usc::SystemCompositor system_compositor{config}; system_compositor.run(); return 0; } catch (...) { mir::report_exception(std::cerr); return 1; } unity-system-compositor-0.4.3+16.04.20160323/src/screen.h0000644000015600001650000000333412674474272023206 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014-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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_SCREEN_H_ #define USC_SCREEN_H_ #include #include enum class PowerStateChangeReason; namespace usc { using PowerStateChangeHandler = std::function; class Screen { public: virtual ~Screen() = default; virtual void enable_inactivity_timers(bool enable) = 0; virtual void keep_display_on_temporarily() = 0; virtual MirPowerMode get_screen_power_mode() = 0; virtual void set_screen_power_mode(MirPowerMode mode, PowerStateChangeReason reason) = 0; virtual void keep_display_on(bool on) = 0; virtual void set_brightness(int brightness) = 0; virtual void enable_auto_brightness(bool enable) = 0; virtual void set_inactivity_timeouts(int power_off_timeout, int dimmer_timeout) = 0; virtual void set_touch_visualization_enabled(bool enabled) = 0; virtual void register_power_state_change_handler( PowerStateChangeHandler const& handler) = 0; protected: Screen() = default; Screen(Screen const&) = delete; Screen& operator=(Screen const&) = delete; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/null_performance_booster.cpp0000644000015600001650000000161212674474277027354 0ustar pbuserpbgroup00000000000000/* * Copyright © 2016 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Thomas Voß */ #include "null_performance_booster.h" void usc::NullPerformanceBooster::enable_performance_boost_during_user_interaction() { } void usc::NullPerformanceBooster::disable_performance_boost_during_user_interaction() { } unity-system-compositor-0.4.3+16.04.20160323/src/window_manager.cpp0000644000015600001650000000641612674474272025267 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored By: Alan Griffiths */ #include "window_manager.h" #include "session_monitor.h" #include "mir/geometry/rectangle.h" #include "mir/shell/surface_ready_observer.h" #include "mir/scene/session.h" #include "mir/scene/session_coordinator.h" #include "mir/scene/surface.h" #include "mir/scene/surface_creation_parameters.h" #include "mir/shell/display_layout.h" #include "mir/shell/focus_controller.h" #include "mir/shell/surface_specification.h" #include "mir_toolkit/client_types.h" #include namespace mf = mir::frontend; namespace ms = mir::scene; namespace msh = mir::shell; namespace { class UscSession : public usc::Session { public: UscSession( std::shared_ptr const& scene_session, msh::FocusController& focus_controller) : scene_session{scene_session}, focus_controller(focus_controller) { } std::string name() { return scene_session->name(); } void show() override { scene_session->show(); } void hide() override { scene_session->hide(); } void raise_and_focus() override { auto const surface = scene_session->default_surface(); focus_controller.raise({surface}); focus_controller.set_focus_to(scene_session, surface); } bool corresponds_to(mir::frontend::Session const* s) override { return scene_session.get() == s; } std::shared_ptr const scene_session; msh::FocusController& focus_controller; }; } usc::WindowManager::WindowManager( mir::shell::FocusController* focus_controller, std::shared_ptr const& display_layout, std::shared_ptr const& session_coordinator, std::shared_ptr const& session_monitor) : mir::shell::SystemCompositorWindowManager{ focus_controller, display_layout, session_coordinator}, session_monitor{session_monitor} { } void usc::WindowManager::on_session_added(std::shared_ptr const& session) const { std::cerr << "Opening session " << session->name() << std::endl; auto const usc_session = std::make_shared(session, *focus_controller); session_monitor->add(usc_session, session->process_id()); } void usc::WindowManager::on_session_removed(std::shared_ptr const& session) const { std::cerr << "Closing session " << session->name() << std::endl; session_monitor->remove(session); } void usc::WindowManager::on_session_ready(std::shared_ptr const& session) const { session_monitor->mark_ready(session.get()); } unity-system-compositor-0.4.3+16.04.20160323/src/screen_event_handler.h0000644000015600001650000000377612674474272026116 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014-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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_SCREEN_EVENT_HANDLER_H_ #define USC_SCREEN_EVENT_HANDLER_H_ #include "mir/input/event_filter.h" #include #include #include #include namespace mir { namespace time { class Alarm; class AlarmFactory; } } namespace usc { class Screen; class ScreenEventHandler : public mir::input::EventFilter { public: ScreenEventHandler( std::shared_ptr const& screen, std::shared_ptr const& alarm_factory, std::chrono::milliseconds power_key_ignore_timeout, std::chrono::milliseconds shutdown_timeout, std::function const& shutdown); ~ScreenEventHandler(); bool handle(MirEvent const& event) override; private: void power_key_up(); void power_key_down(); void shutdown_alarm_notification(); void long_press_notification(); void keep_or_turn_screen_on(); std::mutex guard; std::shared_ptr const screen; std::shared_ptr const alarm_factory; std::chrono::milliseconds const power_key_ignore_timeout; std::chrono::milliseconds const shutdown_timeout; std::function const shutdown; std::atomic long_press_detected; std::atomic mode_at_press_start; std::unique_ptr shutdown_alarm; std::unique_ptr long_press_alarm; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/external_spinner.cpp0000644000015600001650000000366012674474272025644 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "external_spinner.h" #include #include #include namespace { void wait_for_child(int) { while (waitpid(-1, nullptr, WNOHANG) > 0) continue; } } usc::ExternalSpinner::ExternalSpinner( std::string const& executable, std::string const& mir_socket) : executable{executable}, mir_socket{mir_socket}, spinner_pid{0} { struct sigaction sa; sigfillset(&sa.sa_mask); sa.sa_handler = wait_for_child; sa.sa_flags = 0; sigaction(SIGCHLD, &sa, nullptr); } usc::ExternalSpinner::~ExternalSpinner() { kill(); } void usc::ExternalSpinner::ensure_running() { std::lock_guard lock{mutex}; if (executable.empty() || spinner_pid) return; auto pid = fork(); if (!pid) { setenv("MIR_SOCKET", mir_socket.c_str(), 1); execlp(executable.c_str(), executable.c_str(), nullptr); } else { spinner_pid = pid; } } void usc::ExternalSpinner::kill() { std::lock_guard lock{mutex}; if (spinner_pid) { ::kill(spinner_pid, SIGTERM); spinner_pid = 0; } } pid_t usc::ExternalSpinner::pid() { std::lock_guard lock{mutex}; return spinner_pid; } unity-system-compositor-0.4.3+16.04.20160323/src/dbus_connection_thread.cpp0000644000015600001650000000265712674474272026774 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #include "dbus_connection_thread.h" #include "dbus_event_loop.h" #include "thread_name.h" usc::DBusConnectionThread::DBusConnectionThread(std::shared_ptr const& loop) : dbus_event_loop(loop) { std::promise event_loop_started; auto event_loop_started_future = event_loop_started.get_future(); dbus_loop_thread = std::thread( [this,&event_loop_started] { usc::set_thread_name("USC/DBus"); dbus_event_loop->run(event_loop_started); }); event_loop_started_future.wait(); } usc::DBusConnectionThread::~DBusConnectionThread() { dbus_event_loop->stop(); dbus_loop_thread.join(); } usc::DBusEventLoop & usc::DBusConnectionThread::loop() { return *dbus_event_loop; } unity-system-compositor-0.4.3+16.04.20160323/src/generate_header_with_string_from_file.sh0000644000015600001650000000161512674474272031657 0ustar pbuserpbgroup00000000000000# Copyright © 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 warranty of # MERCHANTABILITY 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 . # # Authored by: Alexandros Frantzis header=$1 varname=$2 filename=$3 header_guard=$(echo "USC_$(basename $header)_" | tr '[a-z].' '[A-Z]_') echo "#ifndef $header_guard #define $header_guard const char* const $varname = R\"($(cat $filename))\"; #endif" > $header unity-system-compositor-0.4.3+16.04.20160323/src/clock.h0000644000015600001650000000171012674474272023016 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_CLOCK_H_ #define USC_CLOCK_H_ #include #include namespace usc { class Clock { public: virtual ~Clock() = default; virtual mir::time::Timestamp now() const = 0; protected: Clock() = default; Clock(Clock const&) = delete; Clock& operator=(Clock const&) = delete; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/unity_screen_service.h0000644000015600001650000000473512674474272026164 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_UNITY_SCREEN_SERVICE_H_ #define USC_UNITY_SCREEN_SERVICE_H_ #include "dbus_connection_handle.h" #include #include #include #include #include #include enum class PowerStateChangeReason; namespace usc { class Screen; class DBusEventLoop; class UnityScreenService { public: UnityScreenService( std::shared_ptr const& loop, std::string const& address, std::shared_ptr const& screen); private: static ::DBusHandlerResult handle_dbus_message_thunk( DBusConnection* connection, DBusMessage* message, void* user_data); ::DBusHandlerResult handle_dbus_message( DBusConnection* connection, DBusMessage* message, void* user_data); void dbus_setUserBrightness(int32_t brightness); void dbus_userAutobrightnessEnable(dbus_bool_t enable); void dbus_setInactivityTimeouts(int32_t poweroff_timeout, int32_t dimmer_timeout); void dbus_setTouchVisualizationEnabled(dbus_bool_t enable); bool dbus_setScreenPowerMode(std::string const& mode, int32_t reason); int32_t dbus_keepDisplayOn(std::string const& sender); void dbus_removeDisplayOnRequest(std::string const& sender, int32_t id); void dbus_NameOwnerChanged( std::string const& name, std::string const& old_owner, std::string const& new_owner); void dbus_emit_DisplayPowerStateChange( MirPowerMode power_mode, PowerStateChangeReason reason); std::shared_ptr const screen; std::shared_ptr const loop; std::shared_ptr connection; std::mutex keep_display_on_mutex; std::unordered_multimap keep_display_on_ids; int32_t request_id; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/system_compositor.cpp0000644000015600001650000001004412674474272026060 0ustar pbuserpbgroup00000000000000/* * Copyright © 2013-2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Robert Ancell * Alexandros Frantzis */ #include "system_compositor.h" #include "server.h" #include "dm_connection.h" #include #include #include #include #include #include #include #include namespace { bool check_blacklist( std::string const& blacklist, const char *vendor, const char *renderer, const char *version) { if (blacklist.empty()) return true; std::cerr << "Using blacklist \"" << blacklist << "\"" << std::endl; regex_t re; auto result = regcomp (&re, blacklist.c_str(), REG_EXTENDED); if (result == 0) { char driver_string[1024]; snprintf (driver_string, 1024, "%s\n%s\n%s", vendor ? vendor : "", renderer ? renderer : "", version ? version : ""); auto result = regexec (&re, driver_string, 0, nullptr, 0); regfree (&re); if (result == 0) return false; } else { char error_string[1024]; regerror (result, &re, error_string, 1024); std::cerr << "Failed to compile blacklist regex: " << error_string << std::endl; } return true; } } usc::SystemCompositor::SystemCompositor( std::shared_ptr const& server) : server{server}, spinner{server->the_spinner()} { } void usc::SystemCompositor::run() { if (server->show_version()) { std::cerr << "unity-system-compositor " << USC_VERSION << std::endl; return; } server->add_init_callback([&] { auto vendor = (char *) glGetString(GL_VENDOR); auto renderer = (char *) glGetString (GL_RENDERER); auto version = (char *) glGetString (GL_VERSION); std::cerr << "GL_VENDOR = " << vendor << std::endl; std::cerr << "GL_RENDERER = " << renderer << std::endl; std::cerr << "GL_VERSION = " << version << std::endl; if (!check_blacklist(server->blacklist(), vendor, renderer, version)) { BOOST_THROW_EXCEPTION( mir::AbnormalExit("Video driver is blacklisted, exiting")); } dm_connection = server->the_dm_connection(); // Make socket world-writable, since users need to talk to us. No worries // about race condition, since we are adding permissions, not restricting // them. if (server->public_socket() && chmod(server->get_socket_file().c_str(), 0777) == -1) std::cerr << "Unable to chmod socket file " << server->get_socket_file() << ": " << strerror(errno) << std::endl; dm_connection->start(); if (!server->disable_inactivity_policy()) { screen = server->the_screen(); screen_event_handler = server->the_screen_event_handler(); auto composite_filter = server->the_composite_event_filter(); composite_filter->append(screen_event_handler); unity_screen_service = server->the_unity_screen_service(); unity_input_service = server->the_unity_input_service(); dbus_service_thread = server->the_dbus_connection_thread(); } }); server->run(); } unity-system-compositor-0.4.3+16.04.20160323/src/dbus_connection_thread.h0000644000015600001650000000212112674474272026423 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #ifndef USC_DBUS_CONNECTION_THREAD_H_ #define USC_DBUS_CONNECTION_THREAD_H_ #include namespace usc { class DBusEventLoop; class DBusConnectionThread { public: DBusConnectionThread(std::shared_ptr const& thread); ~DBusConnectionThread(); DBusEventLoop & loop(); private: std::shared_ptr dbus_event_loop; std::thread dbus_loop_thread; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/screen_event_handler.cpp0000644000015600001650000001221612674474272026436 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014-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 warranty of * MERCHANTABILITY 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 . */ #include "screen_event_handler.h" #include "screen.h" #include "power_state_change_reason.h" #include #include #include "linux/input.h" #include usc::ScreenEventHandler::ScreenEventHandler( std::shared_ptr const& screen, std::shared_ptr const& alarm_factory, std::chrono::milliseconds power_key_ignore_timeout, std::chrono::milliseconds shutdown_timeout, std::function const& shutdown) : screen{screen}, alarm_factory{alarm_factory}, power_key_ignore_timeout{power_key_ignore_timeout}, shutdown_timeout{shutdown_timeout}, shutdown{shutdown}, long_press_detected{false}, mode_at_press_start{MirPowerMode::mir_power_mode_off}, shutdown_alarm{alarm_factory->create_alarm([this]{ shutdown_alarm_notification(); })}, long_press_alarm{alarm_factory->create_alarm([this]{ long_press_notification(); })} { } usc::ScreenEventHandler::~ScreenEventHandler() = default; bool usc::ScreenEventHandler::handle(MirEvent const& event) { if (mir_event_get_type(&event) != mir_event_type_input) return false; auto const input_event = mir_event_get_input_event(&event); auto const input_event_type = mir_input_event_get_type(input_event); if (input_event_type == mir_input_event_type_key) { auto const kev = mir_input_event_get_keyboard_event(input_event); if (mir_keyboard_event_scan_code(kev) == KEY_POWER) { auto const action = mir_keyboard_event_action(kev); if (action == mir_keyboard_action_down) power_key_down(); else if (action == mir_keyboard_action_up) power_key_up(); } // we might want to come up with a whole range of media player related keys else if (mir_keyboard_event_scan_code(kev) == KEY_VOLUMEDOWN|| mir_keyboard_event_scan_code(kev) == KEY_VOLUMEUP) { // do not keep display on when interacting with media player } else { keep_or_turn_screen_on(); } } else if (input_event_type == mir_input_event_type_touch) { std::lock_guard lock{guard}; screen->keep_display_on_temporarily(); } else if (input_event_type == mir_input_event_type_pointer) { bool const filter_out_event = screen->get_screen_power_mode() != mir_power_mode_on; keep_or_turn_screen_on(); return filter_out_event; } return false; } void usc::ScreenEventHandler::power_key_down() { std::lock_guard lock{guard}; mode_at_press_start = screen->get_screen_power_mode(); if (mode_at_press_start != MirPowerMode::mir_power_mode_on) { screen->set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); } screen->enable_inactivity_timers(false); long_press_detected = false; long_press_alarm->reschedule_in(power_key_ignore_timeout); shutdown_alarm->reschedule_in(shutdown_timeout); } void usc::ScreenEventHandler::power_key_up() { std::lock_guard lock{guard}; shutdown_alarm->cancel(); long_press_alarm->cancel(); if (!long_press_detected) { if (mode_at_press_start == MirPowerMode::mir_power_mode_on) { screen->set_screen_power_mode( MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key); } else { screen->enable_inactivity_timers(true); } } } void usc::ScreenEventHandler::shutdown_alarm_notification() { screen->set_screen_power_mode( MirPowerMode::mir_power_mode_off, PowerStateChangeReason::power_key); shutdown(); } void usc::ScreenEventHandler::long_press_notification() { // We know the screen is already on after power_key_down(), but we turn the // screen on here to ensure that it is also at full brightness for the // presumed system power dialog that will appear. screen->set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::power_key); long_press_detected = true; } void usc::ScreenEventHandler::keep_or_turn_screen_on() { std::lock_guard lock{guard}; if (screen->get_screen_power_mode() == mir_power_mode_off) { screen->set_screen_power_mode( MirPowerMode::mir_power_mode_on, PowerStateChangeReason::unknown); } else { screen->keep_display_on_temporarily(); } } unity-system-compositor-0.4.3+16.04.20160323/src/display_configuration_policy.h0000644000015600001650000000166112674474272027703 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored By: Alan Griffiths */ #ifndef MIR_EXAMPLE_DISPLAY_CONFIGURATION_POLICY_H_ #define MIR_EXAMPLE_DISPLAY_CONFIGURATION_POLICY_H_ namespace mir { class Server; } void add_display_configuration_options_to(mir::Server& server); #endif /* MIR_EXAMPLE_DISPLAY_CONFIGURATION_POLICY_H_ */ unity-system-compositor-0.4.3+16.04.20160323/src/screen_hardware.h0000644000015600001650000000300412674474272025055 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_SCREEN_HARDWARE_H_ #define USC_SCREEN_HARDWARE_H_ namespace usc { class ScreenHardware { public: virtual ~ScreenHardware() = default; virtual void set_dim_backlight() = 0; virtual void set_normal_backlight() = 0; virtual void turn_off_backlight() = 0; virtual void change_backlight_values(int dim_brightness, int normal_brightness) = 0; virtual void allow_suspend() = 0; virtual void disable_suspend() = 0; virtual void enable_auto_brightness(bool flag) = 0; virtual bool auto_brightness_supported() = 0; virtual void set_brightness(int brightness) = 0; virtual int min_brightness() = 0; virtual int max_brightness() = 0; virtual void enable_proximity(bool enable) = 0; protected: ScreenHardware() = default; ScreenHardware(ScreenHardware const&) = delete; ScreenHardware& operator=(ScreenHardware const&) = delete; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/dbus_message_handle.cpp0000644000015600001650000000442612674474272026241 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "dbus_message_handle.h" #include #include usc::DBusMessageHandle::DBusMessageHandle(DBusMessage* message) : message{message} { } usc::DBusMessageHandle::DBusMessageHandle(::DBusMessage* message, int first_arg_type, ...) : message{message} { if (!message) BOOST_THROW_EXCEPTION(std::runtime_error("Invalid dbus message")); va_list args; va_start(args, first_arg_type); auto appended = dbus_message_append_args_valist(message, first_arg_type, args); va_end(args); if (!appended) { dbus_message_unref(message); BOOST_THROW_EXCEPTION( std::runtime_error("dbus_message_append_args_valist: Failed to append args")); } } usc::DBusMessageHandle::DBusMessageHandle( ::DBusMessage* message, int first_arg_type, va_list args) : message{message} { if (!message) BOOST_THROW_EXCEPTION(std::runtime_error("Invalid dbus message")); if (!dbus_message_append_args_valist(message, first_arg_type, args)) { dbus_message_unref(message); BOOST_THROW_EXCEPTION( std::runtime_error("dbus_message_append_args_valist: Failed to append args")); } } usc::DBusMessageHandle::DBusMessageHandle(DBusMessageHandle&& other) noexcept : message{other.message} { other.message = nullptr; } usc::DBusMessageHandle::~DBusMessageHandle() { if (message) dbus_message_unref(message); } usc::DBusMessageHandle::operator ::DBusMessage*() const { return message; } usc::DBusMessageHandle::operator bool() const { return message != nullptr; } unity-system-compositor-0.4.3+16.04.20160323/src/unity_input_service.cpp0000644000015600001650000001430312674474272026367 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #include "unity_input_service.h" #include "input_configuration.h" #include "dbus_message_handle.h" #include "dbus_event_loop.h" #include "scoped_dbus_error.h" #include "unity_input_service_introspection.h" // autogenerated namespace { char const* const dbus_input_interface = "com.canonical.Unity.Input"; char const* const dbus_input_path = "/com/canonical/Unity/Input"; char const* const dbus_input_service_name = "com.canonical.Unity.Input"; } usc::UnityInputService::UnityInputService(std::shared_ptr const& loop, std::string const& address, std::shared_ptr const& input_config) : loop{loop}, connection{std::make_shared(address.c_str())}, input_config{input_config} { loop->add_connection(connection); connection->request_name(dbus_input_service_name); connection->add_filter(handle_dbus_message_thunk, this); } ::DBusHandlerResult usc::UnityInputService::handle_dbus_message_thunk( ::DBusConnection* connection, DBusMessage* message, void* user_data) { auto const dbus_input_service = static_cast(user_data); return dbus_input_service->handle_dbus_message(connection, message, user_data); } void usc::UnityInputService::handle_message(DBusMessage* message, void (usc::InputConfiguration::* method)(bool)) { ScopedDBusError args_error; dbus_bool_t flag{true}; dbus_message_get_args( message, &args_error, DBUS_TYPE_BOOLEAN, &flag, DBUS_TYPE_INVALID); if (!args_error) { (input_config.get()->*method)(flag); DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(*connection, reply, nullptr); } } void usc::UnityInputService::handle_message(DBusMessage* message, void (usc::InputConfiguration::* method)(double)) { ScopedDBusError args_error; double value{true}; dbus_message_get_args( message, &args_error, DBUS_TYPE_DOUBLE, &value, DBUS_TYPE_INVALID); if (!args_error) { (input_config.get()->*method)(value); DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(*connection, reply, nullptr); } } void usc::UnityInputService::handle_message(DBusMessage* message, void (usc::InputConfiguration::* method)(int32_t)) { ScopedDBusError args_error; int32_t value{true}; dbus_message_get_args( message, &args_error, DBUS_TYPE_INT32, &value, DBUS_TYPE_INVALID); if (!args_error) { (input_config.get()->*method)(value); DBusMessageHandle reply{dbus_message_new_method_return(message)}; dbus_connection_send(*connection, reply, nullptr); } } DBusHandlerResult usc::UnityInputService::handle_dbus_message( ::DBusConnection* connection, DBusMessage* message, void* user_data) { ScopedDBusError args_error; if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) { DBusMessageHandle reply{ dbus_message_new_method_return(message), DBUS_TYPE_STRING, &unity_input_service_introspection, DBUS_TYPE_INVALID}; dbus_connection_send(connection, reply, nullptr); } else if (dbus_message_is_method_call(message, dbus_input_interface, "setMousePrimaryButton")) handle_message(message, &InputConfiguration::set_mouse_primary_button); else if (dbus_message_is_method_call(message, dbus_input_interface, "setMouseCursorSpeed")) handle_message(message, &InputConfiguration::set_mouse_cursor_speed); else if (dbus_message_is_method_call(message, dbus_input_interface, "setMouseScrollSpeed")) handle_message(message, &InputConfiguration::set_mouse_scroll_speed); else if (dbus_message_is_method_call(message, dbus_input_interface, "setTouchpadPrimaryButton")) handle_message(message, &InputConfiguration::set_touchpad_primary_button); else if (dbus_message_is_method_call(message, dbus_input_interface, "setTouchpadCursorSpeed")) handle_message(message, &InputConfiguration::set_touchpad_cursor_speed); else if (dbus_message_is_method_call(message, dbus_input_interface, "setTouchpadScrollSpeed")) handle_message(message, &InputConfiguration::set_touchpad_scroll_speed); else if (dbus_message_is_method_call(message, dbus_input_interface, "setTouchpadDisableWhileTyping")) handle_message(message, &InputConfiguration::set_disable_touchpad_while_typing); else if (dbus_message_is_method_call(message, dbus_input_interface, "setTouchpadTapToClick")) handle_message(message, &InputConfiguration::set_tap_to_click); else if (dbus_message_is_method_call(message, dbus_input_interface, "setTouchpadTwoFingerScroll")) handle_message(message, &InputConfiguration::set_two_finger_scroll); else if (dbus_message_is_method_call(message, dbus_input_interface, "setTouchpadDisableWithMouse")) handle_message(message, &InputConfiguration::set_disable_touchpad_with_mouse); else if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL) { DBusMessageHandle reply{ dbus_message_new_error(message, DBUS_ERROR_FAILED, "Not supported")}; dbus_connection_send(connection, reply, nullptr); } if (args_error) { DBusMessageHandle reply{ dbus_message_new_error(message, DBUS_ERROR_FAILED, "Invalid arguments")}; dbus_connection_send(connection, reply, nullptr); } return DBUS_HANDLER_RESULT_HANDLED; } unity-system-compositor-0.4.3+16.04.20160323/src/steady_clock.cpp0000644000015600001650000000133612674474272024726 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #include "steady_clock.h" mir::time::Timestamp usc::SteadyClock::now() const { return clock.now(); } unity-system-compositor-0.4.3+16.04.20160323/src/dbus_event_loop.h0000644000015600001650000000524212674474272025116 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_DBUS_EVENT_LOOP_H_ #define USC_DBUS_EVENT_LOOP_H_ #include #include #include #include #include #include namespace usc { class DBusConnectionHandle; class DBusEventLoop { public: DBusEventLoop(); ~DBusEventLoop(); void add_connection(std::shared_ptr const& connection); void run(std::promise& started); void stop(); void enqueue(std::function const& action); private: std::vector enabled_watches_for(int fd); DBusTimeout* enabled_timeout_for(int fd); dbus_bool_t add_watch(DBusWatch* watch); void remove_watch(DBusWatch* watch); void toggle_watch(DBusWatch* watch); void update_events_for_watch_fd(int watch_fd); bool is_watched(int watch_fd); uint32_t epoll_events_for_watch_fd(int watch_fd); dbus_bool_t add_timeout(DBusTimeout* timeout); void remove_timeout(DBusTimeout* timeout); void toggle_timeout(DBusTimeout* timeout); dbus_bool_t update_timer_fd_for(DBusTimeout* timeout); int timer_fd_for(DBusTimeout* timeout); void wake_up_loop(); void dispatch_actions(); static dbus_bool_t static_add_watch(DBusWatch* watch, void* data); static void static_remove_watch(DBusWatch* watch, void* data); static void static_toggle_watch(DBusWatch* watch, void* data); static dbus_bool_t static_add_timeout(DBusTimeout* timeout, void* data); static void static_remove_timeout(DBusTimeout* timeout, void* data); static void static_toggle_timeout(DBusTimeout* timeout, void* data); static void static_wake_up_loop(void* data); std::atomic running; std::mutex mutex; std::vector> connections; std::vector watches; std::vector> timeouts; std::vector> actions; mir::Fd epoll_fd; mir::Fd wake_up_fd_r; mir::Fd wake_up_fd_w; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/window_manager.h0000644000015600001650000000342012674474272024724 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored By: Alan Griffiths */ #ifndef USC_WINDOW_MANAGER_H_ #define USC_WINDOW_MANAGER_H_ #include #include "session_monitor.h" namespace mir { namespace scene { class PlacementStrategy; class SessionCoordinator; } namespace shell { class FocusController; class DisplayLayout; } } namespace usc { class SessionMonitor; class WindowManager : public mir::shell::SystemCompositorWindowManager { public: explicit WindowManager( mir::shell::FocusController* focus_controller, std::shared_ptr const& display_layout, std::shared_ptr const& session_coordinator, std::shared_ptr const& session_switcher); private: std::shared_ptr const session_monitor; virtual void on_session_added(std::shared_ptr const& session) const; virtual void on_session_removed(std::shared_ptr const& session) const; virtual void on_session_ready(std::shared_ptr const& session) const; }; } #endif /* USC_WINDOW_MANAGER_H_ */ unity-system-compositor-0.4.3+16.04.20160323/src/null_performance_booster.h0000644000015600001650000000206112674474277027020 0ustar pbuserpbgroup00000000000000/* * Copyright © 2016 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Thomas Voß */ #ifndef USC_NULL_PERFORMANCE_BOOSTER_H_ #define USC_NULL_PERFORMANCE_BOOSTER_H_ #include "performance_booster.h" namespace usc { class NullPerformanceBooster : public PerformanceBooster { public: void enable_performance_boost_during_user_interaction() override; void disable_performance_boost_during_user_interaction() override; }; } #endif // USC_NULL_PERFORMANCE_BOOSTER_H_ unity-system-compositor-0.4.3+16.04.20160323/src/dbus_message_handle.h0000644000015600001650000000255112674474272025703 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_DBUS_MESSAGE_HANDLE_H_ #define USC_DBUS_MESSAGE_HANDLE_H_ #include #include namespace usc { class DBusMessageHandle { public: DBusMessageHandle(DBusMessage* message); DBusMessageHandle(DBusMessage* message, int first_arg_type, ...); DBusMessageHandle(DBusMessage* message, int first_arg_type, va_list args); DBusMessageHandle(DBusMessageHandle&&) noexcept; ~DBusMessageHandle(); operator ::DBusMessage*() const; operator bool() const; private: DBusMessageHandle(DBusMessageHandle const&) = delete; DBusMessageHandle& operator=(DBusMessageHandle const&) = delete; ::DBusMessage* message; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/power_state_change_reason.h0000644000015600001650000000157012674474272027137 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014 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 warranty of * MERCHANTABILITY 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 . */ #ifndef POWER_STATE_CHANGE_REASON_H_ #define POWER_STATE_CHANGE_REASON_H_ enum class PowerStateChangeReason { unknown = 0, inactivity = 1, power_key = 2, proximity = 3, notification = 4, snap_decision = 5, call_done = 6 }; #endif unity-system-compositor-0.4.3+16.04.20160323/src/powerd_mediator.h0000644000015600001650000000660012674474272025112 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_POWERD_MEDIATOR_H_ #define USC_POWERD_MEDIATOR_H_ #include "screen_hardware.h" #include "dbus_connection_handle.h" #include "dbus_event_loop.h" namespace usc { class DBusMessageHandle; class PowerdMediator : public ScreenHardware { public: PowerdMediator(std::string const& bus_addr); ~PowerdMediator(); void set_dim_backlight() override; void set_normal_backlight() override; void turn_off_backlight() override; void change_backlight_values(int dim_brightness, int normal_brightness) override; void allow_suspend() override; void disable_suspend() override; void enable_auto_brightness(bool flag) override; bool auto_brightness_supported() override; void set_brightness(int brightness) override; int min_brightness() override; int max_brightness() override; void enable_proximity(bool enable) override; bool is_system_suspended(); private: enum class BacklightState { off, dim, normal, automatic }; enum class SysState { unknown = -1, suspend = 0, active, }; enum class ForceDisableSuspend { no, yes }; enum class ForceBacklightState { no, yes }; enum class ForceProximity { no, yes }; static ::DBusHandlerResult handle_dbus_message_thunk( ::DBusConnection* connection, ::DBusMessage* message, void* user_data); ::DBusHandlerResult handle_dbus_message( ::DBusConnection* connection, ::DBusMessage* message, void* user_data); void init_powerd_state(ForceDisableSuspend force_disable_suspend); void init_brightness_params(); void change_backlight_state( BacklightState new_state, ForceBacklightState force_backlight_state); void enable_proximity_l(bool enable, ForceProximity force_proximity); bool is_valid_brightness(int brightness); bool request_suspend_block(); void wait_for_sys_state(SysState state); void update_sys_state(SysState state); void update_current_brightness_for_state(BacklightState state); void invoke(char const* method, int first_arg_type, ...); usc::DBusMessageHandle invoke_with_reply(char const* method, int first_arg_type, ...); std::shared_ptr connection; usc::DBusEventLoop dbus_event_loop; std::thread dbus_loop_thread; std::mutex mutex; bool pending_suspend_block_request; int dim_brightness_; int min_brightness_; int max_brightness_; int normal_brightness_; int current_brightness; BacklightState backlight_state; bool auto_brightness_supported_; bool auto_brightness_requested; bool proximity_enabled; std::string suspend_block_cookie; std::mutex sys_state_mutex; SysState sys_state; std::condition_variable sys_state_changed; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/performance_booster.cpp0000644000015600001650000000326412674474277026327 0ustar pbuserpbgroup00000000000000/* * Copyright © 2016 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Thomas Voß */ #define MIR_LOG_COMPONENT "UnitySystemCompositor" #include "performance_booster.h" #include "null_performance_booster.h" #include #include #if defined(USC_HAVE_UBUNTU_PLATFORM_HARDWARE_API) #include "hw_performance_booster.h" std::shared_ptr usc::platform_default_performance_booster() { // We are treating access to a functional implementation of PerformanceBooster as optional. // With that, we gracefully fall back to a NullImplementation if we cannot gain access // to hw-provided booster capabilities. try { return std::make_shared(); } catch (boost::exception const& e) { mir::log_warning(boost::diagnostic_information(e)); } return std::make_shared(); } #else std::shared_ptr usc::platform_default_performance_booster() { return std::make_shared(); } #endif // USC_HAVE_UBUNTU_PLATFORM_HARDWARE_API unity-system-compositor-0.4.3+16.04.20160323/src/hw_performance_booster.h0000644000015600001650000000242112674474277026464 0ustar pbuserpbgroup00000000000000/* * Copyright © 2016 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Thomas Voß */ #ifndef USC_HW_PERFORMANCE_BOOSTER_H_ #define USC_HW_PERFORMANCE_BOOSTER_H_ #include "performance_booster.h" #include #include namespace usc { class HwPerformanceBooster : public PerformanceBooster { public: // Throws std::runtime_error if we fail to gain access to the hw booster. HwPerformanceBooster(); void enable_performance_boost_during_user_interaction() override; void disable_performance_boost_during_user_interaction() override; protected: const std::shared_ptr hw_booster; }; } #endif // USC_HW_PERFORMANCE_BOOSTER_H_ unity-system-compositor-0.4.3+16.04.20160323/src/unity_input_service.h0000644000015600001650000000352412674474272026037 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Andreas Pokorny */ #ifndef USC_UNITY_INPUT_SERVICE_H_ #define USC_UNITY_INPUT_SERVICE_H_ #include #include "dbus_connection_handle.h" #include namespace usc { class DBusEventLoop; class InputConfiguration; class UnityInputService { public: UnityInputService( std::shared_ptr const& loop, std::string const& address, std::shared_ptr const& input_config); private: static ::DBusHandlerResult handle_dbus_message_thunk( DBusConnection* connection, DBusMessage* message, void* user_data); ::DBusHandlerResult handle_dbus_message( DBusConnection* connection, DBusMessage* message, void* user_data); void handle_message(DBusMessage* message, void (usc::InputConfiguration::* method)(bool)); void handle_message(DBusMessage* message, void (usc::InputConfiguration::* method)(int32_t)); void handle_message(DBusMessage* message, void (usc::InputConfiguration::* method)(double)); std::shared_ptr const loop; std::shared_ptr connection; std::shared_ptr const input_config; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/display_configuration_policy.cpp0000644000015600001650000000416512674474272030240 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014-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 warranty of * MERCHANTABILITY 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 . * * Authored By: Alan Griffiths */ #include "display_configuration_policy.h" #include #include #include #include namespace mg = mir::graphics; void add_display_configuration_options_to(mir::Server& server) { static char const* const display_config_opt = "display-config"; static char const* const display_config_descr = "Display configuration [{clone,sidebyside,single}]"; static char const* const sidebyside_opt_val = "sidebyside"; static char const* const single_opt_val = "single"; // Add choice of monitor configuration server.add_configuration_option( display_config_opt, display_config_descr, sidebyside_opt_val); server.wrap_display_configuration_policy( [&](std::shared_ptr const& wrapped) -> std::shared_ptr { auto const options = server.get_options(); auto display_layout = options->get(display_config_opt); auto layout_selector = wrapped; if (display_layout == sidebyside_opt_val) layout_selector = std::make_shared(); else if (display_layout == single_opt_val) layout_selector = std::make_shared(); return layout_selector; }); } unity-system-compositor-0.4.3+16.04.20160323/src/dbus_connection_handle.h0000644000015600001650000000253712674474272026422 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_DBUS_CONNECTION_HANDLE_H_ #define USC_DBUS_CONNECTION_HANDLE_H_ #include #include namespace usc { class DBusConnectionHandle { public: DBusConnectionHandle(std::string const& address); ~DBusConnectionHandle(); void request_name(char const* name) const; void add_match(char const* match) const; void add_filter(DBusHandleMessageFunction filter_func, void* user_data) const; operator ::DBusConnection*() const; private: DBusConnectionHandle(DBusConnectionHandle const&) = delete; DBusConnectionHandle& operator=(DBusConnectionHandle const&) = delete; ::DBusConnection* connection; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/hw_performance_booster.cpp0000644000015600001650000000306412674474277027023 0ustar pbuserpbgroup00000000000000/* * Copyright © 2016 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Thomas Voß */ #include "hw_performance_booster.h" #include #include namespace { UHardwareBooster* create_hw_booster_or_throw() { if (auto result = u_hardware_booster_new()) return result; BOOST_THROW_EXCEPTION(std::runtime_error{"Failed to acquire a valid UHardwareBooster instance."}); } } usc::HwPerformanceBooster::HwPerformanceBooster() : hw_booster{create_hw_booster_or_throw(), [](UHardwareBooster* booster) { if (booster) u_hardware_booster_unref(booster); }} { } void usc::HwPerformanceBooster::enable_performance_boost_during_user_interaction() { u_hardware_booster_enable_scenario(hw_booster.get(), U_HARDWARE_BOOSTER_SCENARIO_USER_INTERACTION); } void usc::HwPerformanceBooster::disable_performance_boost_during_user_interaction() { u_hardware_booster_disable_scenario(hw_booster.get(), U_HARDWARE_BOOSTER_SCENARIO_USER_INTERACTION); } unity-system-compositor-0.4.3+16.04.20160323/src/session_switcher.cpp0000644000015600001650000001233312674474272025654 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #include "session_switcher.h" #include "spinner.h" #include usc::SessionSwitcher::SessionSwitcher(std::shared_ptr const& spinner) : spinner_process{spinner}, booting{true} { } void usc::SessionSwitcher::add(std::shared_ptr const& session, pid_t pid) { std::lock_guard lock{mutex}; if (pid == spinner_process->pid()) spinner_name = session->name(); sessions[session->name()] = SessionInfo(session); update_displayed_sessions(); } void usc::SessionSwitcher::remove(std::shared_ptr const& session) { std::lock_guard lock{mutex}; auto const& name = session->name(); auto const iter = sessions.find(name); if (iter == sessions.end()) return; if (iter->second.session == nullptr) return; if (!iter->second.session->corresponds_to(session.get())) return; if (name == spinner_name) spinner_name = ""; iter->second.session->hide(); sessions.erase(iter); update_displayed_sessions(); } void usc::SessionSwitcher::set_active_session(std::string const& name) { std::lock_guard lock{mutex}; active_name = name; update_displayed_sessions(); } void usc::SessionSwitcher::set_next_session(std::string const& name) { std::lock_guard lock{mutex}; next_name = name; update_displayed_sessions(); } void usc::SessionSwitcher::mark_ready(mir::frontend::Session const* session) { std::lock_guard lock{mutex}; for (auto& pair : sessions) { if (pair.second.session && pair.second.session->corresponds_to(session)) pair.second.ready = true; } update_displayed_sessions(); } void usc::SessionSwitcher::update_displayed_sessions() { hide_uninteresting_sessions(); bool show_spinner = false; ShowMode show_spinner_mode{ShowMode::as_next}; bool const allowed_to_display_active = is_session_ready_for_display(next_name) || !is_session_expected_to_become_ready(next_name) || !booting; bool show_active = false; if (allowed_to_display_active && is_session_ready_for_display(active_name)) { show_session(active_name, ShowMode::as_active); show_active = true; booting = false; } else if (is_session_expected_to_become_ready(active_name)) { show_spinner = true; show_spinner_mode = ShowMode::as_active; } bool const allowed_to_display_next = !show_spinner && show_active; if (allowed_to_display_next) { if (is_session_ready_for_display(next_name)) { show_session(next_name, ShowMode::as_next); } else if (is_session_expected_to_become_ready(next_name)) { show_spinner = true; show_spinner_mode = ShowMode::as_next; } } else if (is_session_ready_for_display(next_name)) { hide_session(next_name); } if (show_spinner) ensure_spinner_will_be_shown(show_spinner_mode); else ensure_spinner_is_not_running(); } void usc::SessionSwitcher::hide_uninteresting_sessions() { for (auto const& pair : sessions) { if (pair.second.session->name() != active_name && pair.second.session->name() != next_name) { pair.second.session->hide(); } } } bool usc::SessionSwitcher::is_session_ready_for_display(std::string const& name) { auto const iter = sessions.find(name); if (iter == sessions.end()) return false; return iter->second.session && iter->second.ready; } bool usc::SessionSwitcher::is_session_expected_to_become_ready(std::string const& name) { return !name.empty(); } void usc::SessionSwitcher::show_session( std::string const& name, ShowMode show_mode) { auto& session = sessions[name].session; if (show_mode == ShowMode::as_active) session->raise_and_focus(); session->show(); } void usc::SessionSwitcher::hide_session(std::string const& name) { auto& session = sessions[name].session; session->hide(); } void usc::SessionSwitcher::ensure_spinner_will_be_shown(ShowMode show_mode) { auto const iter = sessions.find(spinner_name); if (iter == sessions.end()) { spinner_process->ensure_running(); } else { if (show_mode == ShowMode::as_active) iter->second.session->raise_and_focus(); iter->second.session->show(); } } void usc::SessionSwitcher::ensure_spinner_is_not_running() { spinner_process->kill(); } unity-system-compositor-0.4.3+16.04.20160323/src/session_switcher.h0000644000015600001650000000461212674474272025322 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_SESSION_SWITCHER_H_ #define USC_SESSION_SWITCHER_H_ #include "dm_connection.h" #include "session_monitor.h" #include #include namespace usc { class Spinner; class SessionSwitcher : public DMMessageHandler, public SessionMonitor { public: explicit SessionSwitcher(std::shared_ptr const& spinner); /* From SessionMonitor */ void add(std::shared_ptr const& session, pid_t pid) override; void remove(std::shared_ptr const& session) override; void mark_ready(mir::frontend::Session const* session) override; /* From DMMessageHandler */ void set_active_session(std::string const& name) override; void set_next_session(std::string const& name) override; private: enum class ShowMode { as_active, as_next }; void update_displayed_sessions(); void hide_uninteresting_sessions(); bool is_session_ready_for_display(std::string const& name); bool is_session_expected_to_become_ready(std::string const& name); void show_session(std::string const& name, ShowMode show_mode); void hide_session(std::string const& name); void ensure_spinner_will_be_shown(ShowMode show_mode); void ensure_spinner_is_not_running(); struct SessionInfo { SessionInfo() = default; explicit SessionInfo(std::shared_ptr session) : session{session} { } std::shared_ptr session; bool ready = false; }; std::mutex mutex; std::shared_ptr const spinner_process; std::map sessions; std::string active_name; std::string next_name; std::string spinner_name; bool booting; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/system_compositor.h0000644000015600001650000000323712674474272025533 0ustar pbuserpbgroup00000000000000/* * Copyright © 2013-2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Robert Ancell * Alexandros Frantzis */ #ifndef USC_SYSTEM_COMPOSITOR_H_ #define USC_SYSTEM_COMPOSITOR_H_ #include namespace mir { namespace input { class EventFilter; } } namespace usc { class Server; class DMConnection; class Spinner; class ScreenEventHandler; class Screen; class UnityScreenService; class UnityInputService; class DBusConnectionThread; class SystemCompositor { public: explicit SystemCompositor(std::shared_ptr const& server); void run(); private: std::shared_ptr const server; std::shared_ptr dm_connection; std::shared_ptr const spinner; std::shared_ptr screen; std::shared_ptr screen_event_handler; std::shared_ptr unity_screen_service; std::shared_ptr unity_input_service; std::shared_ptr dbus_service_thread; }; } #endif /* USC_SYSTEM_COMPOSITOR_H_ */ unity-system-compositor-0.4.3+16.04.20160323/src/spinner.h0000644000015600001650000000207512674474272023406 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_SPINNER_H_ #define USC_SPINNER_H_ #include namespace usc { class Spinner { public: virtual ~Spinner() = default; virtual void ensure_running() = 0; virtual void kill() = 0; virtual pid_t pid() = 0; protected: Spinner() = default; Spinner(Spinner const&) = delete; Spinner& operator=(Spinner const&) = delete; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/performance_booster.h0000644000015600001650000000243312674474277025771 0ustar pbuserpbgroup00000000000000/* * Copyright © 2016 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Thomas Voß */ #ifndef USC_PERFORMANCE_BOOSTER_H_ #define USC_PERFORMANCE_BOOSTER_H_ #include namespace usc { class PerformanceBooster { public: PerformanceBooster() = default; PerformanceBooster(const PerformanceBooster&) = delete; virtual ~PerformanceBooster() = default; PerformanceBooster& operator=(const PerformanceBooster&) = delete; virtual void enable_performance_boost_during_user_interaction() = 0; virtual void disable_performance_boost_during_user_interaction() = 0; }; std::shared_ptr platform_default_performance_booster(); } #endif // USC_PERFORMANCE_BOOSTER_H_ unity-system-compositor-0.4.3+16.04.20160323/src/scoped_dbus_error.h0000644000015600001650000000247712674474272025441 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_SCOPED_DBUS_ERROR_H_ #define USC_SCOPED_DBUS_ERROR_H_ #include #include namespace usc { struct ScopedDBusError : ::DBusError { ScopedDBusError() { dbus_error_init(this); } ~ScopedDBusError() { if (dbus_error_is_set(this) == TRUE) dbus_error_free(this); } std::string message_str() const { return message; } operator bool() const { return dbus_error_is_set(this) == TRUE; } ScopedDBusError(ScopedDBusError const&) = delete; ScopedDBusError& operator=(ScopedDBusError const&) = delete; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/CMakeLists.txt0000644000015600001650000000704312674474277024324 0ustar pbuserpbgroup00000000000000# Copyright © 2013 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 warranty of # MERCHANTABILITY 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 . # # Authored by: Robert Ancell if (UBUNTU_PLATFORM_HARDWARE_API_FOUND) set(PERFORMANCE_BOOSTER_IMPL_SRCS hw_performance_booster.cpp null_performance_booster.cpp) else() set(PERFORMANCE_BOOSTER_IMPL_SRCS null_performance_booster.cpp) endif() set(USC_SRCS asio_dm_connection.cpp dbus_connection_handle.cpp dbus_event_loop.cpp dbus_message_handle.cpp display_configuration_policy.cpp external_spinner.cpp mir_screen.cpp mir_input_configuration.cpp performance_booster.h performance_booster.cpp powerd_mediator.cpp screen_event_handler.cpp server.cpp session_switcher.cpp steady_clock.cpp system_compositor.cpp thread_name.cpp dbus_connection_thread.cpp unity_input_service.cpp unity_input_service_introspection.h unity_screen_service.cpp unity_screen_service_introspection.h window_manager.cpp ${PERFORMANCE_BOOSTER_IMPL_SRCS} ) # Generate unity_screen_service_introspection.h from the introspection XML file add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unity_screen_service_introspection.h COMMAND sh generate_header_with_string_from_file.sh ${CMAKE_CURRENT_BINARY_DIR}/unity_screen_service_introspection.h unity_screen_service_introspection com.canonical.Unity.Screen.xml DEPENDS com.canonical.Unity.Screen.xml generate_header_with_string_from_file.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} VERBATIM ) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unity_input_service_introspection.h COMMAND sh generate_header_with_string_from_file.sh ${CMAKE_CURRENT_BINARY_DIR}/unity_input_service_introspection.h unity_input_service_introspection com.canonical.Unity.Input.xml DEPENDS com.canonical.Unity.Input.xml generate_header_with_string_from_file.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} VERBATIM ) # Compile system compositor add_library( usc STATIC ${USC_SRCS} ) add_executable( unity-system-compositor main.cpp ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${Boost_INCLUDE_DIRS} ${GLESv2_INCLUDE_DIRS} ${MIRSERVER_INCLUDE_DIRS} ${MIRCLIENT_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS} ${UBUNTU_PLATFORM_HARDWARE_API_INCLUDE_DIRS} ) add_definitions( -DDEFAULT_SPINNER="${CMAKE_INSTALL_FULL_BINDIR}/unity-system-compositor-spinner" ) link_directories(${MIRSERVER_LIBRARY_DIRS}) target_link_libraries(usc ${MIRSERVER_STATIC_LDFLAGS} # Meaning usc is STATIC, _not_ libmirserver. pthread ${Boost_LIBRARIES} ${GLESv2_LIBRARIES} ${DBUS_LIBRARIES} ${UBUNTU_PLATFORM_HARDWARE_API_LIBRARIES} ) target_link_libraries(unity-system-compositor usc ) # Install into bin directory install(TARGETS unity-system-compositor RUNTIME DESTINATION sbin ) # Install data files install(FILES com.canonical.Unity.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/dbus-1/system.d ) install(FILES com.canonical.Unity.Screen.xml com.canonical.Unity.Input.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/interfaces ) unity-system-compositor-0.4.3+16.04.20160323/src/input_configuration.h0000644000015600001650000000313212674474272026011 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #ifndef USC_INPUT_CONFIGURATION_H_ #define USC_INPUT_CONFIGURATION_H_ #include namespace usc { class InputConfiguration { public: virtual ~InputConfiguration() = default; virtual void set_mouse_primary_button(int32_t button) = 0; virtual void set_mouse_cursor_speed(double speed) = 0; virtual void set_mouse_scroll_speed(double speed) = 0; virtual void set_touchpad_primary_button(int32_t button) = 0; virtual void set_touchpad_cursor_speed(double speed) = 0; virtual void set_touchpad_scroll_speed(double speed) = 0; virtual void set_two_finger_scroll(bool enable) = 0; virtual void set_tap_to_click(bool enable) = 0; virtual void set_disable_touchpad_while_typing(bool enable) = 0; virtual void set_disable_touchpad_with_mouse(bool enable) = 0; protected: InputConfiguration() = default; InputConfiguration(InputConfiguration const&) = delete; InputConfiguration& operator=(InputConfiguration const&) = delete; }; } #endif unity-system-compositor-0.4.3+16.04.20160323/src/session_monitor.h0000644000015600001650000000336412674474272025164 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alan Griffiths */ #ifndef USC_SESSION_MONITOR_H #define USC_SESSION_MONITOR_H #include #include #include namespace mir { namespace frontend { class Session; }}; namespace usc { class Session { public: virtual ~Session() = default; virtual std::string name() = 0; virtual void show() = 0; virtual void hide() = 0; virtual void raise_and_focus() = 0; virtual bool corresponds_to(mir::frontend::Session const*) = 0; protected: Session() = default; Session(Session const&) = delete; Session& operator=(Session const&) = delete; }; class SessionMonitor { public: virtual void add(std::shared_ptr const& session, pid_t pid) = 0; virtual void remove(std::shared_ptr const& session) = 0; virtual void mark_ready(mir::frontend::Session const* session) = 0; protected: SessionMonitor() = default; SessionMonitor(SessionMonitor const&) = delete; SessionMonitor& operator=(SessionMonitor const&) = delete; virtual ~SessionMonitor() = default; }; } #endif //USC_SESSION_MONITOR_H unity-system-compositor-0.4.3+16.04.20160323/src/dbus_event_loop.cpp0000644000015600001650000002621712674474272025456 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "dbus_event_loop.h" #include "dbus_connection_handle.h" #include #include #include #include #include #include #include namespace { uint32_t dbus_flags_to_epoll_events(DBusWatch* bus_watch) { unsigned int flags; uint32_t events{0}; if (!dbus_watch_get_enabled(bus_watch)) return 0; flags = dbus_watch_get_flags(bus_watch); if (flags & DBUS_WATCH_READABLE) events |= EPOLLIN; if (flags & DBUS_WATCH_WRITABLE) events |= EPOLLOUT; return events | EPOLLHUP | EPOLLERR; } unsigned int epoll_events_to_dbus_flags(uint32_t events) { unsigned int flags{0}; if (events & EPOLLIN) flags |= DBUS_WATCH_READABLE; if (events & EPOLLOUT) flags |= DBUS_WATCH_WRITABLE; if (events & EPOLLHUP) flags |= DBUS_WATCH_HANGUP; if (events & EPOLLERR) flags |= DBUS_WATCH_ERROR; return flags; } timespec msec_to_timespec(int msec) { static long const milli_to_nano = 1000000; time_t const sec = msec / 1000; long const nsec = (msec % 1000) * milli_to_nano; return timespec{sec, nsec}; } } usc::DBusEventLoop::DBusEventLoop() : running{false}, epoll_fd{epoll_create1(EPOLL_CLOEXEC)} { if (epoll_fd == -1) { BOOST_THROW_EXCEPTION( std::system_error(errno, std::system_category(), "epoll_create1")); } int pipefd[2]{}; if (pipe2(pipefd, O_CLOEXEC) == -1) { BOOST_THROW_EXCEPTION( std::system_error(errno, std::system_category(), "pipe2")); } wake_up_fd_r = mir::Fd{pipefd[0]}; wake_up_fd_w = mir::Fd{pipefd[1]}; epoll_event ev{}; ev.data.fd = wake_up_fd_r; ev.events = EPOLLIN; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1) { BOOST_THROW_EXCEPTION( std::system_error(errno, std::system_category(), "epoll_ctl")); } } void usc::DBusEventLoop::add_connection(std::shared_ptr const& connection) { if (running) BOOST_THROW_EXCEPTION(std::logic_error("Connection added after dbus event loop started")); connections.push_back(connection); dbus_connection_set_watch_functions( *connection, DBusEventLoop::static_add_watch, DBusEventLoop::static_remove_watch, DBusEventLoop::static_toggle_watch, this, nullptr); dbus_connection_set_timeout_functions( *connection, DBusEventLoop::static_add_timeout, DBusEventLoop::static_remove_timeout, DBusEventLoop::static_toggle_timeout, this, nullptr); dbus_connection_set_wakeup_main_function( *connection, DBusEventLoop::static_wake_up_loop, this, nullptr); } usc::DBusEventLoop::~DBusEventLoop() { stop(); for(auto connection : connections) { dbus_connection_set_watch_functions( *connection, nullptr, nullptr, nullptr, nullptr, nullptr); dbus_connection_set_timeout_functions( *connection, nullptr, nullptr, nullptr, nullptr, nullptr); dbus_connection_set_wakeup_main_function( *connection, nullptr, nullptr, nullptr); } } void usc::DBusEventLoop::run(std::promise& started) { running = true; started.set_value(); while (running) { epoll_event event{}; int n = epoll_wait(epoll_fd, &event, 1, -1); if (n == -1) { if (errno == EINTR) continue; BOOST_THROW_EXCEPTION( std::system_error(errno, std::system_category(), "epoll_wait")); } if (event.data.fd == wake_up_fd_r) { char c; if (read(event.data.fd, &c, 1)); } else { auto const& matching_watches = enabled_watches_for(event.data.fd); for (auto const& watch : matching_watches) { dbus_watch_handle(watch, epoll_events_to_dbus_flags(event.events)); } if (matching_watches.empty()) { auto timeout = enabled_timeout_for(event.data.fd); if (timeout) dbus_timeout_handle(timeout); } } dispatch_actions(); for (auto connection : connections) { dbus_connection_flush(*connection); while (dbus_connection_dispatch(*connection) == DBUS_DISPATCH_DATA_REMAINS); } } // Flush any remaining outgoing messages for (auto connection : connections) dbus_connection_flush(*connection); } void usc::DBusEventLoop::stop() { running = false; wake_up_loop(); } std::vector usc::DBusEventLoop::enabled_watches_for(int fd) { std::lock_guard lock{mutex}; std::vector ret_watches; for (auto const& w : watches) { if (dbus_watch_get_unix_fd(w) == fd && dbus_watch_get_enabled(w)) { ret_watches.push_back(w); } } return ret_watches; } DBusTimeout* usc::DBusEventLoop::enabled_timeout_for(int fd) { std::lock_guard lock{mutex}; for (auto const& p : timeouts) { if (p.second == fd && dbus_timeout_get_enabled(p.first)) return p.first; } return nullptr; } dbus_bool_t usc::DBusEventLoop::add_watch(DBusWatch* watch) { std::lock_guard lock{mutex}; int const watch_fd = dbus_watch_get_unix_fd(watch); if (!is_watched(watch_fd)) { epoll_event ev{}; ev.data.fd = watch_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, watch_fd, &ev) == -1) return FALSE; } watches.push_back(watch); update_events_for_watch_fd(watch_fd); return TRUE; } void usc::DBusEventLoop::remove_watch(DBusWatch* watch) { std::lock_guard lock{mutex}; watches.erase(std::remove(begin(watches), end(watches), watch), end(watches)); int const watch_fd = dbus_watch_get_unix_fd(watch); if (!is_watched(watch_fd)) epoll_ctl(epoll_fd, EPOLL_CTL_DEL, watch_fd, nullptr); else update_events_for_watch_fd(watch_fd); } void usc::DBusEventLoop::toggle_watch(DBusWatch* watch) { std::lock_guard lock{mutex}; int const watch_fd = dbus_watch_get_unix_fd(watch); update_events_for_watch_fd(watch_fd); } void usc::DBusEventLoop::update_events_for_watch_fd(int watch_fd) { epoll_event ev{}; ev.events = epoll_events_for_watch_fd(watch_fd); ev.data.fd = watch_fd; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, watch_fd, &ev); } bool usc::DBusEventLoop::is_watched(int watch_fd) { auto const iter = std::find_if( begin(watches), end(watches), [watch_fd] (DBusWatch* w) { return dbus_watch_get_unix_fd(w) == watch_fd; }); return iter != end(watches); } uint32_t usc::DBusEventLoop::epoll_events_for_watch_fd(int fd) { uint32_t events{}; for (auto const& watch : watches) { if (dbus_watch_get_unix_fd(watch) == fd) events |= dbus_flags_to_epoll_events(watch); } return events; } dbus_bool_t usc::DBusEventLoop::add_timeout(DBusTimeout* timeout) { std::lock_guard lock{mutex}; auto tfd = mir::Fd{timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)}; epoll_event ev{}; ev.events = EPOLLIN; ev.data.fd = tfd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, tfd, &ev)) return FALSE; timeouts.emplace_back(timeout, std::move(tfd)); return update_timer_fd_for(timeout); } void usc::DBusEventLoop::remove_timeout(DBusTimeout* timeout) { std::lock_guard lock{mutex}; auto tfd = timer_fd_for(timeout); epoll_ctl(epoll_fd, EPOLL_CTL_DEL, tfd, nullptr); timeouts.erase( std::remove_if(begin(timeouts), end(timeouts), [timeout] (decltype(timeouts)::const_reference p) { return p.first == timeout; }), end(timeouts)); } void usc::DBusEventLoop::toggle_timeout(DBusTimeout* timeout) { std::lock_guard lock{mutex}; update_timer_fd_for(timeout); } dbus_bool_t usc::DBusEventLoop::update_timer_fd_for(DBusTimeout* timeout) { itimerspec spec{}; auto tfd = timer_fd_for(timeout); if (dbus_timeout_get_enabled(timeout)) { spec.it_interval = msec_to_timespec(dbus_timeout_get_interval(timeout)); spec.it_value = spec.it_interval; } if (timerfd_settime(tfd, 0, &spec, nullptr) == -1) return FALSE; return TRUE; } int usc::DBusEventLoop::timer_fd_for(DBusTimeout* timeout) { auto iter = std::find_if(begin(timeouts), end(timeouts), [timeout] (std::pair const& p) { return p.first == timeout; }); return (iter == end(timeouts) ? -1 : iter->second); } void usc::DBusEventLoop::wake_up_loop() { if (write(wake_up_fd_w, "a", 1) != 1) { BOOST_THROW_EXCEPTION( std::system_error(errno, std::system_category(), "write")); } } void usc::DBusEventLoop::enqueue(std::function const& action) { { std::lock_guard lock{mutex}; actions.push_back(action); } wake_up_loop(); } void usc::DBusEventLoop::dispatch_actions() { decltype(actions) actions_to_dispatch; { std::lock_guard lock{mutex}; actions.swap(actions_to_dispatch); } for (auto const& action : actions_to_dispatch) action(); } dbus_bool_t usc::DBusEventLoop::static_add_watch(DBusWatch* watch, void* data) { return static_cast(data)->add_watch(watch); } void usc::DBusEventLoop::static_remove_watch(DBusWatch* watch, void* data) { static_cast(data)->remove_watch(watch); } void usc::DBusEventLoop::static_toggle_watch(DBusWatch* watch, void* data) { static_cast(data)->toggle_watch(watch); } dbus_bool_t usc::DBusEventLoop::static_add_timeout(DBusTimeout* timeout, void* data) { return static_cast(data)->add_timeout(timeout); } void usc::DBusEventLoop::static_remove_timeout(DBusTimeout* timeout, void* data) { static_cast(data)->remove_timeout(timeout); } void usc::DBusEventLoop::static_toggle_timeout(DBusTimeout* timeout, void* data) { static_cast(data)->toggle_timeout(timeout); } void usc::DBusEventLoop::static_wake_up_loop(void* data) { static_cast(data)->wake_up_loop(); } unity-system-compositor-0.4.3+16.04.20160323/src/dm_connection.h0000644000015600001650000000236412674474272024550 0ustar pbuserpbgroup00000000000000/* * Copyright © 2014 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 warranty of * MERCHANTABILITY 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 . * * Authored by: Alexandros Frantzis */ #ifndef USC_DM_CONNECTION_H_ #define USC_DM_CONNECTION_H_ #include namespace usc { class DMMessageHandler { public: virtual void set_active_session(std::string const& client_name) = 0; virtual void set_next_session(std::string const& client_name) = 0; }; class DMConnection { public: virtual ~DMConnection() = default; virtual void start() = 0; protected: DMConnection() = default; DMConnection(DMConnection const&) = delete; DMConnection& operator=(DMConnection const&) = delete; }; } #endif /* USC_DM_CONNECTION_H_ */ unity-system-compositor-0.4.3+16.04.20160323/src/com.canonical.Unity.Input.xml0000644000015600001650000000243212674474272027207 0ustar pbuserpbgroup00000000000000 unity-system-compositor-0.4.3+16.04.20160323/src/com.canonical.Unity.conf0000644000015600001650000000165512674474272026244 0ustar pbuserpbgroup00000000000000 unity-system-compositor-0.4.3+16.04.20160323/spinner/0000755000015600001650000000000012674474477022451 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/spinner/miregl.h0000644000015600001650000000327112674474272024075 0ustar pbuserpbgroup00000000000000/* * Copyright © 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 warranty of * MERCHANTABILITY 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 . */ #ifndef UNITYSYSTEMCOMPOSITOR_MIREGL_H #define UNITYSYSTEMCOMPOSITOR_MIREGL_H #include #include #include "mir_toolkit/mir_client_library.h" #include #include class MirEglApp; class MirEglSurface; std::shared_ptr make_mir_eglapp( MirConnection* const connection, MirPixelFormat const& pixel_format); class MirEglSurface { public: MirEglSurface( std::shared_ptr const& mir_egl_app, MirSurfaceParameters const& surfaceparm, int swapinterval); ~MirEglSurface(); template void paint(Painter const& functor) { egl_make_current(); functor(width(), height()); swap_buffers(); } private: void egl_make_current(); void swap_buffers(); unsigned int width() const; unsigned int height() const; std::shared_ptr const mir_egl_app; MirSurface* const surface; EGLSurface const eglsurface; int width_; int height_; }; #endif //UNITYSYSTEMCOMPOSITOR_MIREGL_H unity-system-compositor-0.4.3+16.04.20160323/spinner/orange-dot.png0000644000015600001650000000216512674474272025213 0ustar pbuserpbgroup00000000000000PNG  IHDR;mGtEXtSoftwareAdobe ImageReadyqe<(iTXtXML:com.adobe.xmp 47IDATxbd@T*,3S sw2EÈHM8`  {bԀ@l@@ނ8LP$:@<ha ] 4w\)0_@xLMHOM䂽K!@r\Ի9ŦC2.CD@3o&2lzcb M$y0/ @0:4{dQIENDB`unity-system-compositor-0.4.3+16.04.20160323/spinner/white-dot.png0000644000015600001650000000214212674474272025053 0ustar pbuserpbgroup00000000000000PNG  IHDR;mGtEXtSoftwareAdobe ImageReadyqe<(iTXtXML:com.adobe.xmp 4t&IDATxڤ0E_Ip_`tt7\$a4$ FF4MQlor $-d@ 0u'"%ND'3HmIggJrL$-?K|'HDdRM.cK!"$gsL_ϩ7`^Jհ~wcIgš !8O|rHpyĪwJZ#.T}y)t#c76˼F1-; ?F <@Ƒ &Lؒ5XU (vX_?Nd_4(^?7zZ! g?!~zBD Qyl<]'ŴxVPXrqo ٜXR7+./B'4,p۱QP%D8lYO(kF{aRKd 1JSz3&0;r]~-υoD{CE,5^O(X)VAnx^&.([/hKB̕뭽w.KCE8& XfS_0Ab BY^p%zQU"ح]/,YAPwd*+vɽV뛇}W3Pp/m*ICPX^_lixۿ',Yy>dސ5bxY=f0WTX9~=?_>2{u gIDE~N ߗusM̞K? b̼={,Q9M][0<~^$B]]am`}=g5cQ~By{T.6.78a:Q(2RS֙3g3^&v٩ 9As i*H{RB:ݔaa4nS+OdzLZ`rtu/[zK{|hnFjւ=)Wt>A1KrB[H*iC l)7{u2h(xU'f}MSĴ% ,ASfT|Xbcʗ~u-oV6hQqCt |pz=;AKFϔ ljH(}NU}8vj9*.a1a4+XhNC!6 2uXnRVD~ш"wJYU|F߼CEKQ pjO2eg^()d 4:"!|ܮU_؉"I0,Y7<"l\͏CE r:Kƈ+"elm D Y0@gC BY TJR*Db)E\C YG<o`7FiC-Fz4'*CD4_lD: uh RվA4]BhWKٕ18~ְP =\ ϋwGEB[y(: BO5F]eV ;a/\Oe~~wFޘ 1YZ_f6pF_1a o1ڋsQ&#DMZ D{3W=ӊJ$ ,YA{嬔%ѫ(k4B8ɬ?GnVιޓdihp8z[ 81 < HDEV!xJ-s<_Y5m\Iube!ԘiE#*#ɓLFYbȒuV_/}QsJ>|չ Q=B0FسN.W,K!ga"æP~iDy"owp D Bm#l;4@ǙWW\h;H^zqOqjܫv Phc-4E6oL簍~T4*WЂ$yxUx&2?ob^˒%p֬(JјiȺNqT ?I%KxM\s  zpGVAaHy ONMdɒY/wlLgff'F eICYyLI@z{=67] `}Kv5D_EM&˟ϋ0_B l;.ߗ?DMF1'=~Ծ7,W!P_Dm~wHv6o=4ue ̀P9'}0aڞhd;vù!9:I` 6ROƋw.d&l jԁ-Xn li$1.iHx@G 7`zU1(ssbAŷU`s3!*D%K{W̮>Rj+h}XVN\4/K6|9q9e)hYMB_ YoR"iDՅGrbL埪*&k9ci#0du5x]Ub,Xgp ܚk+n$WZ]ڄ ܼU_*i2=qQB0"-*eyfQ#S7`}w\F$=L{rGdZN뗬7x/Q5n6 t{mI֣p:8\yQY- Պ1Jb$/z^'k*r r>?Y)[le+hy  $NY`5%f,YRwT_C T+kɥg{zfDl+0Pѫ.|7'T#rO ?dգa _$ZFeWRG:ߛq^ KN1do{CA}e0QKQ6x[fy鱐&#;OSkT# َbZvW|x)xzVj"Gs|*߰bJݡf2DG sV@ D_q \P Ϝ Ǽ;(6½#heߎO0:OV6EmΝ,lVUr Bv.IGa02֥y2'z UӦddCbsCvN]7y6Ҙ\hFEVeo,79]teY/1SNK2 }e7>UC|3'h {D+Gyܐ?1zL/NvyQIbtWdyj3O1+-6/@i1"d3򢳎-ctd}H;1ՒH eш0MXU˖"K%5mYVzQ*2\s687ę''= [u~Op̯* ɨ|n"Vih1kqnˎ֮V.mCzI򏈀j;hݎЫJy:8,9ji'BR!dD^|0뀠1hhuR׼9]]>/y>9Mygsi3hyu2W@ H`)掓y)I% "+㺁5SU\L}V8F3KK,k"B$1Tu+dreő/`w+Ԅջ-'m-fǰ*H4XDoGVSJMRKC.$r9TH#~-yF^uj2H10(b^)zI>.ѹɞPu|6B1CJW>.ţǴ+6Mbx4_mGfZkpك_CVhv:狁y4#+lOVPRR|y~HٶGFx|\w쉮" ˹v=A Tye6KFݸZPH񄏢_k;7(4 j~:C~*}B֛BRnIHnv+`Wg(p![Y,.["ȳCNNICO!{rEIj g?@?FfQ$W "n(be,У Ȑ=(n}VsBY®sHG! %8br%h`x|*4Iw9ͅ[ J49ɜ-JaFl:b~9{n|2;ks*{" >ϕӍyO#VTv@;R6 iGhV>?CJLuĐ> hT2!(cNg&NB?nPEG/' re1/O%?"GH'ڶ@|_aK!zah1!E~LVqbidh@c|U@sKSI A}5= y79[Î+ 3wk!~mG?$"=Gg%a3mwó2|iĈ!_Һ,֮_|Y}Z:R3,]BC(_0^scA`&S2("J{O#~ YTVp0h\6h]1r)Jq)/'s<*Swh?&<͢kT)[+(S1h&uA)/ŀa{P8<*l)KO'cʎӥCn`'4fl]Wְ{i bv2ЕŹV{a* i{ْb+6gii:g\0dz@ܛc^dlF:MYvʩڜaVowGYL4R~4"~ȸ(F-UJAQm u=yηs'*hх<6O&"h>ohCET6E}U,J34Vt []{dB6qd¸%,Tu4a23tUI'm uYCYbȒcD`Wv+ |Y/t4]]R"ze5}tH7oIo1Td(p< r=2'B] 9gտj|P{|qi5VADk~\ h<",kæ fmSF\q^4]Η2+BIH2;b}&0JY3p+$-;OrռKYQf ЎBg:(N9|/v ] "W6 ̜opd-:Е;8XS>+dK֠&BOfhx&=Lu=-9YNS+IDu|&cl` y>?9-X $nN1f5O/:9hґ;| /=oL)oNAꘐȆ)GxN.Ue) Z0VyUxĘߋ}~LzW/ǝHjT+ܒ&QvMΧ`r|"^T>r^xl{%M#G WUaʟs=-G掴DF؅U};uFZ65asZP+e\ذԞ?0[&Jl=_9ZfU 12VzJ0#n(BY[w#Am\m&A.#S1sZPy{9b)t$M1!4nέ){E/Odz8-yRX M74{HP~=uJ-b̷sZr0k1Td'7y36s,Fr[)qww5r[۞*|':`{s %.l?:8%װ_zN*%oܐ߭A0Ab) C]tR!R_cGrrp"MY9W:eu'-hYnNtzg")bZ)lK,/xҒf{7D1(evdLRV zW ySVu{bD=pXiзVr8tRsZF0><6H8_ت7/e)w3׉qYpvy"DQag|Ns]pd+)F@ SYԁ,XV2BD 2o?yUC?J'w5ޯFz1ȟLf! i,|^|ج2ޚ#ic;%1'B!\7{gJuPN@r5W|msA?7[s}=$Xݷ<׮Y5L?~J%:nyD]T< 9coIe/GIRd-i"ID>x()w&CYfx^*O[˾s<.=]|_f? 3M>B*FNaԽwL Vg3MFֺ,$ӋPbWk3%Nx&_/~`ЭHv7_Gzs٩*ц[ҩ!DL,TUi6Kvfj?@K )CŸB6v5'JeJlJROZmv~:R*e .d~r1ťڽb"@Dá4gkIwvt.V;*b/5"kCYA.C-"w01{$e=^XQi"x4#<`1=b.xbb*i_h#81TdQ>Nw[<[yáߦus;zOGvCHw@y\$$3\B3CESxWy;ψXu Hފ@1w2գM?@ˬ ͓m(R[pY|߽/vU|m( CC 6xJF "uuo_TD>#LN AțIvi-s ,{0_(]1lީV٬)"+\#o^ g^~6.|7'$RZQ5+g3/@"Ji@(mV)fmab9-y)3/%f/3P2:_%BG~'Qdȝ6+t?0 B @EUPey/9+ErWbCEL^gzمyP[`N29}hm :RumH:{8V<_C11nDn~PnȻJ}VzMI$a"Bu##TUͼ/3K8g|<;!P^j89qbf%]XYVePnL}e,TFmޤqK ;~=928j"6V7LѨ?*%q:ˏ ӂew1ofgE~|&!+*=o \z+2; ǰ4"InEPtDā^޲(n]Y)P#" 6TfGԯ-bb5'vhǠ|ҵ{s)a{ۣjz1 + {頼92  g˚{'|Vf02^T B8F5yZB7f,u}. [*d7m53?**֔1Gfuvxniߩs5Sr r㽪ץry"- XhKI Y[MT%ey\L@| ڴz| J]n~Ѯ!#&oRtqGw.ʊ d*»oK?@Oܐ3Y)pgu]XJ,OEvƥJ.hSJs"o *T qNQe-mAH`mpڒO?g]Jw~C>֨5V)h֊-褭"Ҝ&\t%+]Ųʔ**Vc)<\][%~򹚌_i yNⶥJmb\MաO{<~2GEVe )>0YM8#?2Z8q7(!lY`N>_9mg*;Kx$Odnf QZ@}kz_3>K!F߀,HR~xJ=_?\R-lSQ!SFڜa2hp9\#5u)\nVxڪ8jD?48Ff3=#%.hH+1#(B"A/FN5?w"p\;!!~UށAw 052xN(wBmp- DՇ]PԗMhob1%?7cUaBԽCf}4ԸOj/Yb{-] {"󏟍/c-6BkeܑZ֡S0 ņ5m~.@ȼI"b_H58_5X>uzZhxf@L<$9atHiZ E!vƉǮX&tpmo~f&%L4^tBҨeOp}\LHLn&$#R rFp$T0gCE*X)`-f(Nc z{PEzFP]{7oˍqo$w(̺p G"ig>It@iKda/4nIw~)\yu+e60nîMfde\jCEI[1O_h۹͝םrV!zťqZkgU-(^x%uw1:h ]翔~Gp78(Qe* xۉ4}P(' 0wk0vڭ*2^afy`zXD;Nk&=- ʚtv)(Rܽ-Ԟ]K߯)FFn:K#s.Jkx;QND '=hD9ri^dcF8xfz3ddIG3 b"g$4]Zޭ̎~hH@ԡU[ݨ-HDtz4>40}i "ǛeDYoQ#@m2n4 U%\C(R(-CnH,Tklp܎ b`QE)"tJTLX4z|9/v=bND}a?k' Jw5 =C͊IGt1BxZ S4ڗgMcqd|&2¢>1E--LbWd0mROwd`w$ކs{#ǿ/_o<WtctF~ɝ@mKaywPv0Z~) knuH#V+\4dnQLk"1Td湸Bm,Tm.(7@fXef2O:I[Y=(Ci> O@~A(l l%& E br߳٤oۜ7*9Ԕqx| ]4dlVxk9_Pv%~xm04H*xeUy*X[ksfS QFD6p[IANQ{R/~ZǮlh3e(R4^!Ura\Ds֜1T[iZNH{rTͺGˬ2D^AS\O|*BĘ ,jgH\dCEIQgyByMbz;dA(S/&&(KM d+&%.ݽQ&M3ɵhSN߽qmG5?* 09"(uѾjjQИWѼScUDZDAdWETAm6Gh7_%7})(>hpkH#R޹wD CAPDRROv!5!+""ǒ VXcP>TNg;>x&`tW5tG@IѤw|"?G?otU20OܰTj7\|)~%^r.'\s9Fɔ{u;[4]4L{^< fk9@jT*ȅ9-M Yѷ uَiԸ]crV#/<1WVՔt9Ee bfiSQeX 4|Z.<2fkI<}ǰa;)PoJPp;y'+y_]Aܩ ,Dn俶bny-GۘfĴ%ʧ0(,5tptĵZ0 KI_FD vo-hx,(V}]CnjQ&CJ+WB=NmJPRDt±'i m89[a1NO2 GY6F@ p/p'ePxuͱ uُӿ~Sf$T@3%Kq>7U~R?ltQ6mfh gACbwM4%Zd$B 5s !~2!',ެ];Wĩ}|sv#8m m=6_χ^w_D׉sǼdh^ ;'<Ͼ}o AdCywtlR9Qs`(O-0d?f-ASHfFp P"*]k5{]_bz.WXVMZ1q+e&{3 i~}Nt>f[ᏬHNՑ~6P5y(ߔ݆ygD)uDƘ,hu6ջ (YZ 5J0ZAo:j늷K`~@S#99`ޮOLߑ{&jд_똮C-#,+մUis{ b]8<!\U n)Ԡ.]hdu IDAT︹򙆇_Ēll1bR] v_0Џ3/Tx[/k?=7Ȋ8.>bz9[4ݟl`$jĐ&`>x9>Ʊ~:D/Tn]ǣg&B_ 5RO!lڽWqNCDsdJt??צGT½\@;'¨+f ʊ')KDMUƟ uENM2"Ud[>/r6!nKɗr߉I!wl394$v#޲r(.i`" )]XS[ObSr2ya̳ב7b_>= -rZ]lW8lI Pj֕q/Utl]hKR"H,fs&kmXfc3wᄎ&2 Wq;pwGy|kdHa xsK-3xJ^Au8#.Q[OnG4_Pu \G{l/ȓW/xQ=ѡMu uWM+ yh?h &_e[۫Jȸe9xr?ӆJÑqVhG6.0!W`:(jvj $qNeʓzktzm⿹3yadyK$pGu PyO0tswJy!G~we3z8_7/Vi^W^T] JA܆+'Sofp]1Ŀk[e*i:'F*2;CZ x }eV XZ. Tg:I#K1EKYE-葲h?<jK)M"K]\`Mh>I\IɚրUO^nIA ?lxbNB3bNӠbݲKb)幙+}Ev"?7/?=>+:rVqk B@mCrW Ӫλ?*Rz(5uBESYon mV/Mw]Mˍzm"ĚNf#/u)~BͻTb@F/ؙg}tGH*mAamM@7 DmTc NHI-0a}#/^Xɸ[@~ގtr,͉OYm;AH`zc^C\=Liؔ5R߿/B$_k<~KČEk!jgSCR[FҘv=9ksIy$_sr ͱ»icY]ui0x\*r5&Rg3} u({@KC[e U~ޞK_:Br/72?[<*зpC5$2_^9b61,;sc=Ս iQwEsz+w+#qzhTs"r1$V. h7>]/(]We-wUF"*(rJ= 7:z"[@e&t7a{8yJhE~ qA^1jV݈!VL̪? ZS 8*rR7Aj[m~B}.;eۉ"Ž[(ˁ:7l$Ua~u5W#Mx|@\=r|׻U|q%ʻgyEi%{xω_%"#fn@yEaն)h7͸e(㩄ܤ1;0PKQ; k@V1++}窯x9mnm(xb%k%I%7JIVE1r}hyϓYDF"D/^mD1PǐÓ 8*AWS5\ ~ i22#ԧS &'gϸ/򦑺ٵ̉摒't r$X;OEdM?R:f7$LeBiCE0vF$ A,3LjUcK\`ϻLx'w\1Z?'Lb̴Mh♘}v:Z!"_V7<8iˑ^av>uh*TCEV-ϧ1 vc5`l x23%-/d;Ad#Rԗ+j/Q }‘~Wd*Cy޸Ce9&9cipP#w&@+"jC(/Z4@ )8as0[GˊOzx>[bIմ֞Z>Ԧ*|y9KIW2QQ”P-VVLC|]MW#WP}̿.,zóU c"ӪHc>r3DiRߚ7rب*>\t w1e:#KS."Jk㓨n{ xAc&{o,+3m*J\hPsnќ)s1&%FפQ̣M5DB30ai2u^=U,Wc犉r3sn6Z.λK "*_J`25h;v:-$"sw|^wSP_ ;0ᕰ5oob20+&%Pdb%m?haoE[W~GFUo] Ξ)4?f6(|q}nih ʼMߌ.5'Vt [D}T&uTB8lFR>Fʉ 7S>K"qZLDhsHxRd-ɗXTt8s`X=ɝ{C;kFPOB3C?0 y,'w9Bg8sKBib@g>Lt )$>eґiZ-׎?z/}9TO?n5N/zvtyR x6Z\QWGb%kP+bXIZ1! փM"&%WB?t\q%~g8iCpmϩ[X0G+VeV̾9]|Fb=]mLg Df`n dCjƋgj>wcr-A2yj:Z}Lץ8j=xlj 85`WV7aY g ӄ%3#v)@ySDfX1dPh0-  : aT nBY|ƶ5zA|q,Qӭi?pp]"- 񈨾dD{8Һ`|=$6*!E1ȺLbptœ.U~soHf!N0?w€0&&r0W_ϷU" i,&/Ns g<nk <]:\֫))6t'gvnۮэin123t},L5KA9Ͽڶƌzø-+z3fU,@Q}0 *R6RkE٥+33 y=jhG>aD;.y,Yďyy v€m &I,^-X*x3 [mMn$w7eџm5ZD`}ud%hJǣ‚gRrOE7 JH83J͔bH$&dUߖ!p)9;%1 N(Y`AjOyEɯ$|ِ#uM{օ_c=@N/Fxq5'0=husqo|~26 -x++ ?,#W\`^ՂvvƏ # \Ⱥd{'x4BL8dU"%vu۫189>Q=?$'(O/,L̹`6I&L'1F/?"DMw{p9"ܡӜG"^ .K6?zLTULbQ%<#ī~k^0a߻,5*CfbݲF1\3惷GWS챷PX#S3P}$PһS.$W48qV-uZbA(Z0$kYV+KoD ,"nC9jϫ̬[`7ݭwt@3,9i^&P^DED>p$.o(#>L91 zkRV%mӤnz(#1+rp W'ny7-\a}2Ig.axQ^UX}{:̟hyԦSzkh={&8.aVPU{Y ց d"fCnF{Y;C#VԳvԚm`RxC3"Sp8'dź]sg?™pqX4tF,d>JX -T]Ksk)SrE/#-^ Q{78!m3q~ՌŸ֋>ԉma'N 03 9{[y, &Rf8$vژph,t/1GEwD{J+۰K6snJ^W"Gop8_G$*E./G̦L=h-ˬʇDjH=YM@e@|z,'d@ikRgjaZ΢%:b)Jp,3 L$2t:t.5oȐ$%C!?IC $5*卬gm[o\aTc†z]p7-xuf`Yj/vZ)S.U#Hq>S=suPf9IC ݊Jh{xb`3֪}bpm->: MOb{ @#5 /V!m\~rՃ$jz%-#Z kZRn^-]B%%ۇ!Z`yMho]NDIsۓP_hs☠}+rxR -Xdqz 'G8y5ކ ǐBd gMGc}lbFhʘrt"Qh;j?nDukj*W6` W ZI_9 YR4ZYgN;7 8߷4Wue{.O'˄z'cvS z満1m 7ȭpڑ+n8H$){Eu t7NapeNYJ$(7Ez~'y)[kM& (Cˁʴ@I%gT'?48qmQjgx\hjm66'}ٹRDt[[Y-P{shۯ 4 4*$*wHKW!y>.iï *!>0-1mX^Ʒ"}Pb,{"urʍ`X]ڰh0n,Pd11m[:rS)S^($>&{ƄiYdݮBi7 0Z7;dZAoԱ,&kBb +"eӰfL =՝GH + !S1Td=M?;1#3-B[Ǭ,{b۠Hݒ>*CHLΰ7gqpݑvd5/p7:'9"zhl0~*X.y]v[]&%YmD[f d)4=b)$WnX:Y+1^e೥]QPrz./CN1VS4a|3ZIɒ5 ~*k5i 5mВ{ťF9q|gQx]YR蓐oozH IDAT@8^5`X $Viaȁdn2r|qރE#"'Iܩa&M=3*ҕr,v]t6uN∫E.}&2rBav=P1Jt? 9\E䓛S q=pD_R~ϥ!||>z u1u ʺPDN3&WHlcZ8?]4!1wI'q̟k?U0~aYzm܊Vݳ/5?\h ]+8^=bSEWd꜆ty>rLܠ&)Y:柄OOeY3ŕ{V!2'4MDF:C݌̹vxLxRI ]dE1HI!i DQQ %ܭ9Q&=45yhY!Jzh@Qַu6P!jX\'qSVHLP͐ӌװyMw)?,Z/5T<a;\JŕMG7Qt;}F ̹}{U^8]g\ ,<_෥R"k{gӚHߺH"b[MKsQ)k7?cFAp#-ҶaHg-演a)[oEPܚ(n,+zX pd:tF%lD%&Vŕ`YKf,e|Ijn=V2519oTk܀;G۴BNby"DEwVǡI,b>f;:`׍R2ӁP Y2lKz(q҃b:V~6jVklPRickunVd+&M1!0u Sz´If43 d5|5i#MeP/t;侟;!&Rom4ybh6/`l~*[xεԉ%!u4H9N|B<+b4a+9bDcl(.Ċx~$[T?uV(ӄi8!5FAu>Y%n#Bb]s] {K:bJD;p@+?m:Rg^v: 1MEG_lR@J_G@8[2Hۚg>^k{]Eo?|W<~MD~NgC2DP8!3뤰 V)y9uNjҴʺ4L!~\l,S9~S"`؈5zφCt^g朗k(=q#[oR(APZ"-Tp~JE`Y&s26:% )Ag0#>fEqN#?2T*oY; u]\`&̠sgL??\S)%+'A2ڱPЍH2P>TaQ7L& n"FUr zp?_^_tb͞HɬKM񽆊wɥGJ[Uxd˵( '(Uzf|`'èBw<)tj>i ?L #qzku}EtAy_ϱKaGtS59]*#d8+*L+[2- !ࡻfvҏՄRWk|g/ V28Ur =Lt!č0XlS {Vy!8=d&d>S/nŽlXnf]%y 7p3rhzO;j6Gދ. " 69Ǝ^eJ!T:m^5nqI妲 ZV$?PIĬɰy|IzC'bVD,`9t~Ђ2F%roT>[5zIgCJfE7OviWMDSow c#g<1ң\^;XOB; ٜH^v{qg4 M6ϕ<=߈W[$i>caC3c"  _@~PsRP5E>DJN1vVhʮ߫ߞ.1G3IxљJZ&,]OS*}^ZP >}̌ U+we͋aQvq!pzZ? ?(+#| tLB2Cx8gK_s'%eX<ntWaH̎g׋v7yv]-TªT•5EžJ캽vFC'zf+#̢۔{q{Xދ`uCܮF(ogNζ}+<r.hHDՊG&)!yuCaStDb~ In1pzOJ5Sef(/ת$Y: oԗ 2\aDYi-= sΫ"6ɤ4Öubrj.1M&&!nEUyNdFRezw,, :w{y^Xhga1kA>瓷"ŮPke4?0'}Y,G0z\?..֞8Z,NB S=FK}u"zwnЄѓ%uoiWRYbX5Qz -/is>G$Y UlA9nr2ݼ,퐓0jNFZH:)ENQь6+Ӥ_oWnЬeAFL\o)7#̀`ZNjϮܹF#}rBl)kW{NF{|`o"&̞ZDMM_F֪ePeq 285|3Ôo3P %l~ݚSISMS ,֠0P`#I$<ŵOLy Df& SmEh]FD>G^u(hkmn%=_T}BkfSыR4Q>3R+Cl^X4W1v-cz/٣bIfa/bī=qNՈ1ișzb?9KsJIJ}k[{O-cX#FF|xqۥc}gUFbJNHf+[-HK ^砜Z9#zX9Msh""(X=6 dɗn !'z9y_c8qlWe!Yl•#2Q' ޴X)qwǘb׺P䣓&bB4[0ObzY ew{b#ߘU|5#=Jmnj*%m#h6cw(S,gKPp7tX$;nl@ I 松^n|)Sg`!@DKTx>] ϟnڔX"KPbn ΨFCTQFi[Dŗ< dR39"ogH#*Jq+uUirG&D܄M!Uf9|+6f:az2?xk=Dp\R9fvL@#m -g<*&1mG6mDYvt73[5s5|q(JtĈ.W|5S"C1 zh&}x0Y3ЌCr?!cFr]8xBg&@d MF~\_>l#Nr"H]|LW/\↮#!u )J߯>]Ŷ[[Y 9۱x:E̎/&</W#Y6Y_)nz"pǔr\do SY:FFe<1|H1kQMF\_@HYgܤy//"i7Dz%[Kֽ$-c;Vg#4_ɰ>߬nW)K{n6257jQ4 =(4fҏgj".pb$7Θ*Wlpd~篪=lqNEo" [˝Z'27kV5[)KN2 [+_+&R'RAIB^s ~Y5*"d\i 92s3gM?"P2 'H5YL&屓X0[6~r RjV5`i\t¨yQf!+яCrԾι%mEf[eb.հaVz߄8Deɺ{!kP;Ex#u{ VNV90{TV1!&SFK '\uhpzYK#_p(: LVsNO[.T!ҥ]oߌo84J)5*}: IkCz93/0DʿIbΉdɚ!^q}ZV^ej| PD%A?x"F7vB/Ka^Ch;=$W%836;Cj0"m^",W/I\݌Ld{AP3^!P0,.Qj䞓PSx~W6%xΡ9iHd@@P)d%kBc,R\J.*P;z7Jhc ,#$&^e7gnq}:ej`^qѻ/}-u|]S1 WgO]bgJrٽWJCo*#jp x-G4F#pHN3oInrdcfo=jE=oV5*7(e»*|ڈb]l~d,7'M&I"&ǥ`pܺ${~cGWg}otT.KV'_Q}Fe3-l"J(oTp\-gdh + a'=ܜ$R^ VwWu5,c}}{͕kbZEp#jtmę`+hVj~6A޷qz r|tH̬ֆqV5*WR l& XBuj*fe$*r峰5}5Gu2et!G3-:Tn5ʲdɒzxkd l5-#5nTgp=ܮHftYXmFM[_ɝg=5e|1D=א.t(|q/߲frYuih[_p/_Rp)^K ' KyK?;?GO% <;)w=_p,M@7l' ץtp׬~Pݽ IDATe녢ZD1.q/Ԕu*+w?1bFRKIFr?KYM?+Q=-+CtYjs~^OޙA3IVBD2R~L5$_ae8 ljbȚ0?l)#XYsK3NrDݼ*Vʌ$gGz|V " p%YGK)k%z4awREp{Pus%0tjZCEC+hk?z;ƴՌo2]!+ U)‹- UyeLUnLE'qj5[kxPWN8)}m|B06aӧ#i4mʥ*dG)-2'(\aշr(4f4NZ){ *['!W7*P9I*eQ)ev=7}IfÑK΍)~˨PݬKCEh|X)3χKV(Q_=FH ͸\0a~_wƶkvom}<Ϙqv J_, 5:et.m]֋@`O3Y?'`Y_agVN/%"#xGB{dKY]= G2g̊&Y\{2@"Bğh dy%A;.ʪB^xoʵB:1r ]F{!*ZeWsW\+^M];Gűoҹz!6Qdp[n.ǽWW#U9cI6~ʯ󈘞wK}3r}%N<%χ7FQZoM`5j!y淲1Jtk%wnqUDH{wdI wŖǼSFXCozѓ;ìHrI(@-ln]FIHsք-TC} [3@ T`Fe$4f 왂o {=*V[~gFVC`}sT|81fu7[c,tNjP׍&WyX&àl$p3S8 uIj>G֬DfoO>sC)>Iug=ݰڃ ~6QxN_N&\Y5ߤ)r I>. 14\&zOgpeZa-/6T2!2#H w O趟dkIl(| my)yy64k%S@O!-5Q}mLx8HKŰk&)Aڍ 0gj{l*߮ i G%ƗBNJlsscBULjKNqw *Z2zo͆aTQͯXI恢1X%X%nD$؍B0XI,5lK$䢠 =nfq4#z^dVkn|fNj:d$rxi/a74 lA%rglr3|ըW|};m刌1^Nҹf z$kEw> LYnҾjhhWo$W0tt).2P_.!Lۏ(Y(q"-rO&v(+~dQv> NqGsDt)TZ ۣϙ<$W]}7옅P ]L{#$s[:Tʳё@/9Z| Fo϶#z'LJ5L@&miCnMnr쵲7F<͈MUF\ȔQz9yHR YyiJ[i4[]=4L]\ lĭ*IE˸G$6ȃVc3U>cKmE'KLWHsFRekQ4ZJ% "R5tX0;hg[\KvT&sD6k}7%T D%0JD@[W"\XbuuLRof`[ǛQ- ʡC7zJ ۩kcdO6jSF b v݋}wcJPl; Ga6 W5W+sNP-GTy/a8Y.#$c3Zfކy)#fDuoe}PN Yw>7D!s?u|Exȫ7K]/tݾn"NLSa*|_]Tj?W:' ig]YlhZ9W9uvO<(\sv ;nbH!dylz>QwF{]F:y7;ZMj+x]h 7Q7zijVGla.{6/ h}zc(klV |ZJh0B~竗 G%vh+~'b#f_1z9v-Κm=Nʖ61(͆+pW[0B8wڟpe A"өzi~WQUv=+s_ 6?|ȓ8Cޣ;ёU;oyޫ}ĢVU^g6))oG!jd?Dcym.e&}C<cCoȈP'B’9T?9U\9U3:\)l]<ݖv0drannZ(Ֆ+m8VJf^t8ּ{*oo#a螐^ ~.-_HՈ' zC;+CoN,,dgܓ4bPX"굂X|$CA(I@/ Hࢇ iC,,mTPɛyrPs~E}&[ Cj?! U~g:2fJQkKdnҬؤ3+ijX:ާںCRpâMc9b!:Z<*M=ЊC#[3t ^ }ǭ|yH:<7?p*F&?qAD-􈠆OQԔW$'M1pmLA Q]C3\bTNA>}חu‡NB2(f+fMCT$ӢjL*dj1-fM5<#-TvmXWvޔIAhi#hʽ&Ih;趛gO]+I58M뜨-:K)۲1i}0ScI{5#>'^b4_ ^2w@ r&XOEQLvLm_[w)=:YnG$ߌ9| Ůd{HG-{Cm URԓE ~@':k'V<7ժZ;%A{aή6M5]qx`C-d쎶RL9F^AcT|-JBйRq01ӷ:: 9#>;_Uǜa@аt'RPP'ES[OkPogR՜h;~$9^cH16o Ok\UO𢸫IcfMR:l#bGI"Y?KT8GVV/0nDY] 7 Xr,X+!+6p9_~ekk,|DəZ Gq P5)VqVٰ$) t˾MI$A5S6j}yq-ù6e(=́!jGU )a}W~jm֋yNXQ-=|+AÙ"ji7T2>}Ru1:&ado<PW+wR5O9cU 7,B${N/EV[^RVQ`\NH>hW>J$d- Q>t{g8]6ςvdm Gtmي rqפeg=DSU`&~KyC.epifPPlYxg% +k;Ge]N&ƿUwX|SOL}j%[j o%*YFp!$y3Oh)TaJʡWџ 7oSː_&i=ho-l^A)=Y=0r5?txY523`niѕ]߆&~#FjX\Gv]Ѯr+d8~ȕixSqm~Z<+__Mj1vfR41( &O3:uRWr!* }[@s!3&ܲ䄴Tn$XYM H\Q)~4i9rCJTp([VP@R?E CGThQ٢@QYf*R *J``X8n]0MГzMHy)Q <>\뇺~ [^Y; vlWoo$>LV\ މ~܊ܫV T?)'ki2 ̣ڑ=C\M~DڧIhţ==.A݈SB h/ ɒoޑn(4DH`xJy=gQyEz.^L:wÉ ?,Jv'Ln[~Oy.cL|O,#>a_Q*`D9';Y RnLjA7<D95~HGEyYUgD3^ R/9Ji[_er#TͶ3BP1GW[ڊ$"kI2hE|zx;UgH-A2o7??陜-/b$=cDG+Aj5娬ڧVfH Q>{klXx/EZ5j5"F&tQypՉs$ [Fmz;rޝǰQXGr2x;Ջ{b ^Jni,+yJL~ ~_;~Nk^-,nsOpDi}䢡lt2ܱaDd=uƴ!$gfq%YZsn<T]0Yyn<Ǽ74*zhcCUAD&J7OȤx#Zh0!WeKQ ~Tmmk& @nYËP"S(?H˘ <`ؖoKٞn8JU8yN67[qTV!&v/"ţ(sE| v`(;d/u"ab6 ˦iC&;z~AJ[1P֨L^-g}xGf]-l +]둪r9*~x/x^D}W.~D]RsOʥޜ2^JM,5]EĎB#;Cc}ҩ-p\hk"7a( U)d#߸z*bfR?Py0XeV. Y ʚDo3U+ߦsK6ҟU(r :e#6P$ Т|"!iOϮ{dw~i9*}w)!9YԀYd[yJO d\EAZ|?`TO0%#ѕR7 'BK4QG=%B%I%pşVx{{>En딘9ҟ_jR4cV2S7>L}ae1Dnp 1$',/H' E:|uzOKC{[سLCe7$t))rTV$k&Ka)ᎄU'I4WQK\#EbZnwer7EY|_}ry*mON?W`|Ge>S&&K!M#)Eo81C~i ֶSטJkᑾJM\ k%9/7E;a3` 6%OiUFG?LHw~HӨp'H ~Ylg$hRd$YgZwrp1IB1 ?.Fos9ނ1w0?7$pۓh{SI:^^`` 1&6԰h=Qg s35t1a9;:W?D ,@D/c.JNEsn2iQ!qf9({G[Z _"vL ](,lqTH%VLdy*HVmY5;rO<K^O k~cR^ESR}`6L. U LnNPgoJ=EK}˽ˁ/=/jP()IJ`olLb[ 09gpfى#1rXH: k+oJ* u3^`Nif#NI8tBd!V:T8!KLAU}ud,ellPyܵ|+nL˸/($H[֤ŴJ*vj0v+gTJΙٴ>y:S'`hKՐREQ8a\!XryK:5t/r0 b,!<ŞZ8J M.$^ DG-olϛ|_?nΚ\T^BbIu0!TS0KY{5pK1jhw]Zi!~ےޓ";4vRžE%?܉@j) P ' m3706M߲X}0,M+ԑ'VFYGV~0eٺ "6-߭ 5B$>8[m6#l>S^-v8kAqnN~WҶi[_čtk+C?Jn=Qpt_!N5|Tvӛe^3o+گ'm͂VOk*#X_w{W/٭:+=*+8.M&kn<ϚBq.$q7ykCt_ld14 @DpGցz[/{mHM?WbH!P: xuՖZs,;8&rg\/˅d.(ןbg{bRGrg?u7H-j$@43cņ  s FŷUsbQ\d5;}ɮmN!Qɼ;<͈PKaˍ<&0{DaJqwh]J)6=0#ܳh] '${>z;2󔼵ݗ(jS@D&3hDx3˻\j}eLSs . ܰpW:_{%P *:-Ljٶ9] 4*\f!ل0 +n1?Xje|0 I++hn̘3B,j< =sc(# 4a9\A5g}Z$s*qQ ~]ӄDvNQ+GG8q&ZϺ("v`zb#Dr\yY\XJX1:Vj)#Mzr-nGܳri'xOr=Fe?Q(,4ؽzPV[fVζhFteJ !W=԰cN$ RZh-RyX?vsw GrJŅaAxģ͋q1[)Vfb3r4evOTk49rq+&%eeJ!jMıfkLb5 16IvVEήPi"er7pN*U͖7q+ߡ6D IV2y'ʪNYm5x3;`rY\n`'׵lTFTtQgzރ@^)@/oʨs*0_1͵Xoa //?#@bB)gwk[1}.h=ۮ'xIAa[(SϠm+%`tcd˘$B%avMmDYg}N: |,~>?h[:+ [*v?qvmw" Z״cekvDd,1 kӯ?G+5;T~I]Ͷփ;bbq*u"8r,W7PQ5,UwYP[k>!b+Дf_(QYW\b`UgC҉'t෶AǠ+J,!ص'*ţ~_.f5q"D)_ȧw>_϶~ie5%dɺ.EdcܭalWaQYm?HL5WuzWe=? IIt0U2f^SyQ4_s^yzaw1EcgPX٢0_= t#ا mO$hK.qDkbإϭ/RܧɨngT4Bgغ{{Ȉxq³8_&*Undu O02Tj+Wj}Cp@@NjLb5jRǎҳͧ!bh<ĺٵK YV_0j&k+O e՟QPb,MLJjB?}dWbqM6}J+O~x|?&%m<}c.\wrM~oJ|@ YmdG(pŏjAYJρ~ZnDaSPqODE99tPN! FB)vK :8I썫č1M=1]< ֋l0ڃ;H/ =ya)Lc,Y7d5cmssk.Do;޼ " {ىtUpPNR!5Xk@leonV7&6(F3ip2\sDyf~NK3&{\tl7+l:d:RR+EV! \$oQV%UK UN"-E;:BcC6Ѹ7-}ݛЀ@)϶y. d$SiC&Nbz)cP)͹G)d#o+Q R  Z|XRmYkvGW,yxqYW2AYsÜRvԫd\"IhW/6eϴnêߞա~BgrLJf^/Ysʫ,>^|QFf=ErHVmQ HB5IYsnae 갋IRb"kEs/Ta雥Qs!RC*L`|:rG;M"Kc|]%ꑅ@504Cz/(@ÿf*ȵ&klCڨ*RBŊX[.%\TF{%}$9"W@|*en(-/<TP6ups<~Ej){TL G3^כaeNuDwTRl RV_X>ESg!7M'ƃ~iw,"-HʣcP+|vv ={J iIz3 =u8 KT#AVpxWyb;8 e0f8u7I%͸ܽ6Ufs s$T 1~(70^6kñaG4^9l&;7>l C*;,DקjTDdaۻijOwo3Z d&841{(Ԃ7|2 \,cxsb?ڑy3pnraW` Y9~V;MI4vK*n#BuYc,+O c)+,zM%FQtB ? etvv= -w2M&z8FG͠3N*biW1Fb@tzZ[4egȂgG ,Gjե EHuGlaO-kZu=1gVsV]JJt4~K9 q|e\sBB)I򙩄mWˇQ] IDATN!䥟jtJj<6'Y>*e|]Pִ_#G-yU@)qӈr\y՞U։5xV'N; mV'̏Z7bǨX smcr`;:0R)XaPm<>9tW] ]XpJ" c|CPIXߚ>d;%6i񘑴b KӃ]#蕽!ћuʵu (udص6`g ]d2L#VFDt:)p: #ՠsvR}#6f9im\3=ETbUh qAjS;7@H}'/ z8v_Ĝ@1oFx,y[ =7]u' }7uYedAeBƲE_ [G=q3v*+kBc-HQYm0C=n'ŭ_֬$.<ħw#"dpkxNåI"Xrلת,!L 3iěHZ"G)#mPӜs$5\akenN3 pG|Bʗy#Ř$42RmZ݊ HuU^E05t3SU,S*&ߥ4} v"_f&gs+ NoܞlKh+e^|(pI?dNĵqV.Gr9%ߵF\b{á"lqwkTs8NtߕE ۩{5hZNB QR5G cxX#&L> S\*w~SnuRM/nWLC̻F¯a$%U9oݒcz[ʲ((htԐ`pN /hD< %>j쫬N':5vskmC 擃:; reIqp뙭 tyZuŢZ*:c:|Jت2N (Vj؍pfLFhe Y0soR5 XV:G]Sϩ .+aϗV B3,B(~Igf4#C313jÑH6Xl|6v)WYGq|=EQpN߼sQ(o""Hk奊n(oBGŧZ =u(am鴝 b#MՂL!ZkB;´s!#wyy޻|++ ,LwTq[݇?les*c@&:<~~~pmLAfOL٢=Ά'cU=DdGctRs~ŝe), [\wo4knlĞEMD<1ӮăݧFTcMqOsIX?AKC BƒI>4COpRB>\- QVjlBݞ/"IxlN1V c wWn'I1}uY9׽+Xvb72D>}Y7.SaΥ>紆Bͣn*OB $VhH}u:Xv%)n&mxo?4w0\|1Jw.tUx S=)G_Iz_[^&r^Oh~h!U 1^Ii1%devU'`nG;&Av>}>cyPljNp#5|ak &$#ࡘwyy½^OW]p_E&dR/ ;0>Uci'fl6;AGR45nK, JʒD\suM&F8*B!Ą/D9㷇pfkW;[_N6wO~8RGabdȮ撓 A!C|:WĎZD`L) b;@D@@cm)O”4yaΈI5'&p-dؿ6<!(<ӅP\p g4)+Bf[:> lC1<$(B&Mȣkgeg !G5#Q'pZ *-!\4/oB3OH"c\ Ixs!<>@deqB،&?;-z$4O[ >! #p xxN GQ"w>0؈{2x~lpysE\ YhYL׽j=ژC≸Z5,4Y=t ]-OaRV8- F\jF]F=jU([lX"&{wA4]c*.AHSv,y LS`|6|d /{$+FՐ[߆ֲdSae^7\tD1iJg4=Ż"T-^6']} YMƔ̛>{2ny\d5#58~bdI5@,/몰bS;nm0>ky? U.J(xkK5bk/h6N[PY:Lz`RA/E3{V lM\dZWv>0>W?IyeoJV[Yk싽Ŀg~R~D-wV淰2TCel|4%uW"8]>nR$0Y B,QM?BY-ib~1Wl5 8j?k9\(C ~mޯcޠo瓱{7f4>,y(vo T榸0:kݤh*qG-$o䉭 HUWl7TOOƌm[j=_@Y3ڋI&^ HSnTirO*c uS? 8W4>"eyeBB8gKj<[(m~/ Gr_!>A%)pJNRO\OޕEY+0rIJy.X dCE6yyo_6җ&$LUw)T_Tr[mk~bI+ћ,"*OVo2 7y~Ru$%Fi"q߈4V>I{Q6/ԫ6qq$#eB`\"gn҈b tz:e,sYZCfJǀ[EY; \C##Ǝj*VMp׍NI 3m(̣>Q=;և*J!P]{cyv$܋,pPU\[E(>\#4FMл=)_g1.qW/#V3P"`xje>&pR D5с`GyƢqZD-+cUO{|%82m1Z>!|311{n.* >A@X\՞]{;v0ڝ)C"irP#fir!=I{"ƽV9O[Bc9֕[}Jmm KikF48bd~M=n;rj\AY? *7AA>Tc^vT 52fzP%Yyʾt/怙m?Ԟ| Oބ:u/w">k5u9 Zc qj2 &_$<\"%VSz>r (HN\^jb]I3ǍaDG̑,Z[$ b=İ\@4? .h*C^c.Inkpwl3&,thOmxჭS'Ďl5M!9FObe{zLk Ո1vhIEt)LӁy>>E1Xɏ9*ov! † j_aB%2<Xix#M3bSᄑ~lRBbOY"J7p>eRo*mZJc&óe" j8W?QڦAl8. ̴SW[-G;|)Y_E2*O A M 5XS [, /X?9@c gXطf7PFԨAD"> J١[zK bX0S74=EӋN:**0;?܋bsc!&~ dx|),64( H%˒ 9tCG7uY-n-^\QR SYeA$[`T47 &G!'D~+ by~6 2Pnhs{E4$J6=Z\`yD_y,b!ܯw(p\;$BAf)s{z17 oߡ% o$FsKYxrVKkk{GF) \R3(̒waV&9*;!l(ppdH"|5*BFYf՞B=+ ,Ւ͹5dG,ax${:l14Y~(. aҙj/HA~Un4u,Ghգ0>/-RQ=vb{GML<O9ZYP`ȴzܞ.$0Ǘ,) 6VX]]ܷaq]xoZ"}}+'T0Tȁld禳85oJ'VNaj7 N&uT́V֢6FD""pŦ:^.J땡8UY=C8q0ݝu9T~6 XGy yqK㑻=z0NKBs̞MnRGk. IDAT2=M72I-c{8Tpa70TM8r'|=. N׵j."fڭཁ/?W'w)ƣ9n.-'~q`EZԃB~yB}Q 5 TRld BBgS9T '+k?Cq{FT\qeaE-B+gy C=%BhkqUF>N0(7JNDlsfldKqn3y6vLWnǴA|#2bwL7nâ-!A\A ꣙;,"GDߐc.͡Xe_,&Kva ?_)+WTQzM!<*xi޸nݩȧW6.7j8=͓4Ϣ}J`mbC ,9r\}B OC6  ɩ-.>QIPDΆR4T϶rFr"<>9]<+v8\ 0}~؃։+*PY@_9EʣB?b~AҊ}Ky_ȁT Y/jy:^|N#9,@ )gacnn 0\cIvT^ogGWH<^4˪j{V ٵb*{\8#gKՅq伹j,`!dq'Ͳa]* zY^aX ^dB@As3o#=#E5g'7;@xsW>Q$Ȅ}3L<$ek?ZcwJ*pbGέKkbC$˰LAOoO-Ddԑ`*. - s#h::caYeRUH@U%Э03:0<>y姊ٛ9 1M;ZtǞTy pK ($> 9‰pO+e8;Q2p$!Qe=&Fsbqi#7V;wtfָ:W=L)0iUI|߶Ę٪HG<p\Qe̽Coڇ-%v" U.Jo`@GQ`o~(jefe;bd@?ۡw 7D!O{*?WSEqB{^f_w+/&\.K:,|VDW;F8] $AǞdugKS7a_cW׸h&dlި W\imGñqٞQ($_=|'<\O\d6vC"<۽I`3</C.j\Vʐ@45x6ŋU*ج f}yK BsQSG9? Qϳ <.dcΪĸ%PesϏ]ʬ>*Z*^|5 @CļRZIYɽj)*,ݽ)EE풓ܑsoìI:wy)kq6 ubNZ[MWPKIγYʷ3e*r`*)LlcR nmVU >>yɖl3ZIp:{lLion):k.p(F}ӻ4߃u&0,EO.XN1Vo %K{f2tlJOnů.\`]kirsIKrgm7JLJHLw!fwù3AW"΅+R8BclCx5݀%1AgFUm;?Di~W.w>Qܾ?q;!qr))4|yf{zgXF/0"P-:vSGCǮ1ήswz;~t1{=v&ID5*?DC!R?)^>۷19oq/!Xk y2}(n)KYPHEhT ú^!<DbHWI;wMnL]G)fyF5Z=ύds i Э^'.s=Lx|`ӕ*irJf׹Lkh9ց:bd4v{>|`M}֭?+t;@|aWhn bkݍvV}47Xin( FܤZ!MF?txMW]TrBT'}B7#! {z<o7w# [Jj<%/%%xj@`lrc "V*G[}t?^KDD/n jbnM鬣+SB"~ɝ/>t,j6-9fAA fzޓ)Si2@<аexf׽fwwR2<x]0,YuFBxh9ԯy&(tû0pkPSQ4= }Zk5|r+ׇnK~G5zTƲwDwkȅɊ>tMR˝WAn2eQ˕uQl\C+.3 B},oq1bã47B΢t?H2w:X01%d7 ?x&s7+Y(ƙNTZ"H"LOn=_QLv*M_8Kl( ?wɟAL]A#HwF3%'h]d)f\Fe%gx@~] i-oxdVR^N koJOJE􉞧I-TJ,׀_bӔ0C[DHSRR%U|/-dӬgn" Y)q/yT$rw0G.Df6zz,Yi#ۄ]ZqĂ\-wZђqhw7{)JnAnƱj}Q]@,UZV4sLR#;e~QAD aPJY /ˬ.7ʬitX9LB]9bX˻;Rb$Õ&v:M7QygaZSK! =~d ~D)=k<`zQW&<V*7gbGoviw>jKXૄ5`ˌ(0_G>f`ܯwWڧ͝`_:U݊of8̚* "B|m!bY?܆I7@789Ze) *Ek'6htuDN2d:hhN*S}C^@- 0;:Ix)%>!iŻ%qIf<Wa&c:P -MD F:bζ23(覆|5ӌrz)>~SB+%LNQn_/AjegK 莫y{"K9KS&?L>3R8<:-22{~*=B{XVaHha{s+H\,FwxҌ] =DvՖS%6-1Xޫٕa^*4YtNH(r 疒5d7ƞi//D/X %v7>jqmwE /?CH%獪& 捀/0ч&LygX# kBSc8mZ"b9"ʖ,)5q_L,0Z.=uXWPʊǫG re~K P#79sD1$qmd `JI/}sN}y ?!lH1癯9evUFNsn?KOsxeYKD(DY?̦FDcȒj?Yy$U';v)Z? fnDYLi:mxTݴw^*ܶ n-9| Or ̺|i&lEBx aCec4:4'fBKرdyw恜7m W0Dٜ-GC '{-S9 *JCDf%53hX;`Lv(ɋdG/e$ɣғ30Eǻp2+ UJ*qOMP#TkP,G-$)-vRʦ#R\c (1cUTmKe.yRֿX7쥴2PZ8ʻ;=KC>x ؜aaQ1m4;Q#e:09|7m;2 šԯ^8-oem/Wj+(F.L-(Kq~vn@E7@43zqYF=Uh |fbTҌ4 )7#5@2[=pc1EMOߨ2~*WLVx4CݐOZ$ӎHwq/7t6<កIaE crSܘ+L<5?T>Lj$37`]0TLmO_EaD 2N6O?ǣlee~ڭugeΆ@u#xc~΀ݫ]:y,:=Ӱ\L>ngVOMݙΟܚ8QIksbʛnSټJl~XGe, Ds>"DG6v uv$Zrr燐MiźV/]WF rq!.BF:H;07!ʟmI݄+rλ*6EZ8d/ޕ /K1lgoO}p;S&^N8ߌ7Z8P jfp9ђhXz yJS6 :z/N+)Qa |#伏sSF\PHt#+ᡧZ%r@DT?q8̀6=^`]tAf?";\ yQB}(޼޻hpo92sDo\@7xׯϥ;4G~nopH@zm62 j5~4챈)g$x "hW1.pHɵjͶgpV0O8޳:]އٜX؂OG{ aT>3YT\z)#G+eHl-b +YV[-)XG14RvoG]O -`Y(X_17'bJEHT2!@|y2pxpMsrud`6+Ll}9*(#v(V[#{[X>Y0i7w-oOwF(/|BoҼd]YzI^f&}xnI~ݾ'9 hfX,l=MpL< nO j_erTC7T+]՚+p)l}*!e˪Κa>_[ߒ-T{fSXrE4 &P2#IX g-H2ԟ[^"oIV;' ҘHܽ>u;gVÄ},vfnπ75scBv^JD W8MK9JDY늳^9kƤ.j7H@jJFc| pF/G$jLz78ͦt PR>G&kW>DCN&>yrڭF'[˝莒8Te39L/EUOH8&z充?ܞؕnjrV3I̞SY&Q@2dJpHsp4ybrƲ 6@gaI?d (z  c eta?f4e|@<E"|3 쁘v)&\ּWk }p62O:*dYo=psFz"i=F$f\2E"BDqJbx6A BB?ʊ}n!u 揶Z5*-kEXv n= },dHF9W#N7"3b2 osh{Bb8Fi, {}(z~YXfBJ;߭ص ߒ<d&~Z+'bYϓszbfh\'|#BACN/:Ć?i5k=|j@}QrR)'aJyUfI9kfR۞k|\iC)-?.KVM&uZw"$%M7`N_і1aNfKp^ľ+t]bL2rQ .I[J%#nY`-yIc]Hx8X{ \QV;[oR.JhXх(. tC#~Up/6$c^p<"'?¿.G W]j˶.V7,0ӓz),Wœ_g.p9JD9rEOfB3 V )WDMʟ˃oM;9_[.GQ*jk$\GObOi" [+c%LyB4MnBɓ(&,ɞXk$ӥ^ 䰱Ǻ\9CPV6-˝8ӧ~"/#HMmǗ¦X aUUؗM`^9Ɏ,!*fAQse("\ lIG4U㙮x;蘕@<^9$LU`WY #LYscEo*`F聶fhD]DC 阇Sѿ n=a8x^%0ΛD KD6’d -VoϟcNkA[GˮLj2 uO~CTnuA_c|Q_DH|Tb}:J4M4nBpߝ>^o/oR:zܿ{=V5>{SLs>ֺU.͘)}T2v\߫aկoV$oڞ! >;AUOD\Q uzz|KT?)oeS{3FB n i'/)UpW\CB?‘;19}wjBA4˞\V27e) J#7,Gdچq­3֛=+Q oVY.uhsrZTE~ c\onsKĆw=W<ѾFbLK{?^.^8Ig˒dYu-%"Y fQ5y>pi7vܙހjJIiMacEf1Sh?IXv.1DЧgM".G=lhhHÚ5tp9+g ţ@/v~wgXrGʽAErRNz9@ GjVWs+99 0kT|S k5~\6nߓ`%wEokՊ=;ErP\"6Z q Pwwy f;:UASnwr@%ϹvOʴMc9*Cb`\c90Oxg/A*Kxm^/A^zq[GBoˆ=MnWa9* ՠ`du)*' hť٠% {.Xx@Lj2!wׄnXwW\˦$Q9K'J!O=-5A2(#}?j={QOxf5cp`cݡYSW]}t*[{L撧?0gJ͒nqS.G]DZhI&"M7{G/q.5έPV8yN^U=/k ==(Z܃^&IlEe`z^ISHqeci}*TZ g14͏o-Bô#!>ׁ草YίoHaɯ K@q]z1e+/%}q;&+me4+?xlMę@,2rT-%"y?H6Raed-dNxe*M Zn}N|RrO3ns~@ S?.Ʋ _Ɋofe޼؉X$?sSTo\;Ѓ\⻱ȂƿBN!(*%r}ce?r)?c—:34ᒌo0rMVGR.T%OVl"7܏ 2oYnf~]4bN \>eW軃|?;}KƌUm+H5lQ) .{`jFJ/ڻo ;qwϝle]cRwr;8\9=)jEɕQWʧ7 V ʕB gf~_ |zmaMpiXSxvi:5geTw=PWB(:o=2B}H}G=(^Lm,GTWOekI΂".(QHSr C*κp(~u,)<^J(?5e{}u>}싽&`Mvɴ,֬Liy1k(Y|~G^]CxF|d~jS!c/96U6OV[kKb֌Y}39=6msSJ$Aqu̯dogL V Wή/ϰZ&ޛgG-hRi{1f/8(?G&ڋJ/Ks{2 Dha6Y 74Y;r,=U;qӮPEz5=qjvbо<Ķ!JZZSJCEbA DCrl/S; DIz03R2Nb<:tvnPД>c~\FM@)8""v@PjĦs /F0:ww2 s<ae`edyF q! c=@Ͽx!țds[LP @D6瘽64M{3IJ dod ؞ ~:9Dm]J'n+%WmDQ^ jl^OL1䓉!w dBSOҰ̓1mVM1qRsXkg@6x'K:($8(m"}D8:E_њŒG:J0 ۢAJ4 MW b}C(՚R8萫^tkƾNk=W w9*OEY7Q'WnJ^Gmfz+qJD>t}޽U>,.3}J 0!MZH1_]_fWA v擽VTOsR% yϹg5CDr΁5.0rxeBO޷JV]od`#b)x<̗."~yҍ򺲉|iJ^ʺX 4*`4vNW#X>L"TZY(4Wix[:| -G.?X)5dU֤uK{-:o{ A̚]N25.%èEEBщM)Un7(x)^ae%@8-yH^#Zt`kQ.ݲʓW<ڙJfĔ[Vuz=K\to2KEM=bL[س@02ē٘ x"!jX,&_%xO>,>iJcI;)yhܚ @=H}֩C&XX9n5Mypǹu@%8`$q{exR`Q-:@esMޱ yG3|}DPS%13 j5 n8ohDseo3RiM90'<m#߆? *  b O0}6Mbɵ+kG_oIE%P>gμR~ql2>n]v'G@m0o]K ] og#`gD?_8gV-\bѮkG@VuL<[IdJ^DoW~2*G[u.~SƬkt5VPdB`Z,u9a|,{^']u-k)UpN\~34l+ `LUĂݠ/C1{H,_)Q@Hfk4<0|fQ`Xۙ2Q vMr݄wD3ņCh5ZtBuZxXɹ7|da{%YGYFMJCӢ6V$_?_*d9*a8Sk%+GU*qsFm@,P HŢ bPĴwʍ$ O< |PGcR:wӣJԹ4霎5!˚ze;,Iيv9ڔWu엣xZe*k|ۿLC`^3:vw[oΘ͚)q2j5Ҳ#2S_2L" Dtٗv'' OmU*#Kk9*`Vg\s*_cACg+6|m"dtNTzORZ2PdH&w Kboϓ ǁԉۄb)?ﷶ\}9ԨYPXvܗf o6pW*k3$g(Q9E nnw1k56$DD%쾪~wP?gש{==0z?J5]B_^nb -fOj֨تKvDWSC!5\<g/I>x]Ruذ%n%4WHӣDtYkFš:P2'.=m]_o>EH7t<(״"j;?gNd$'zLtye׎]2*6ē&9>(UY(/fwWv'7M("PA>fgɉoc IDAT1\EDqsLJ0G'.P f]Ϙ$r(![kCD` WO KswDm#(l;k=V}ṑ!±'/y& K f s׌d51\RSֺC ;iJ*b Efw1_ 1EWf)n t(w+?w R&wvzw=ȥ)h(d6kˑU'66ڠh&'r?͟pV&!?N7\Tc#J'H`)?x&3;{CsFD 4aQ I殠1kJ`B3`XvD1&u1C)2/iZFܴ Wr$)@8VplBUG^ھ _dX_9cO17WlX BY -zW 0 ,bdn9)Hޭ6J]7-GY4?L߱AʥZ!ȟZ`YOp6}UcL4cr9麕OrsufuR@yP`/eLTVh'd.DA\>_)X#ګK)̣vP+r5>Kѯ!8O.Ïzex>3ţfoOEM(m%Q/۽@aaR?U=j`\] s?w#qJ ,Q?J&K i>Et` >+ՊG^ g)Fս^|JAL@SMln VNBfhê@0:{яu73V_^F2}&!Q)ҶlQBc|aosPx%'E6!ѺQR'C&H_)FCbdCeJ/nKc=zϞ(CBGAN!*Iϛ<^10S@D. s3mWVͳ(4줲wZ2%K9E?7!#4@ 2[S-[#fy%6DJ 0/4l-tSA6c93=LFQS^Ah+ 1Nқ}BNy Ņ `3%}zT({DKh7RUuYJ|H ;k:3?[3)7:cǯ1pg̜;gqxSMmzYqi`Jv[o"ԫ!<?7jKi;EmҙK1f[k;5.'BJu >8΂-*QNG)fyƗ-߷RH>#wSX7?koޅ=HANq48vgXhp?,X (yӳ1r+/Jw^<txٝ d`3zF ' IA\Z8Y۳i/fo\Q_e*_< nϿI0w?sAÀxMsc4Sשls ETz*Vq%"GȘ#oO6瞛L)!ߺtQq{ {|BSx{һTBAQYdM81 nav8~WuV]WJ 6Ib#DO"cU-.6? d/%ZLahJ=qC]- "P80~'2Pꮩ徥o Gb@$g`Imٌ}B=O wםŌ:zG qt񬪗R͓';5^w(9mw)|XzIQ.o7F*'=HÊd#J y `sX$Ϗ|MKY3lW{ߩ0*iPmGm"6- VkGwb)7*/ 'ʜmn9 HXCL'gn7[CBvǴu(2'M>Z-zkq>=nIC5VVQO+":Z!(~d6fn t!BJn]k{s6lifJ&yrT~hJqjSkApZ@/q4.[]-I8J0hJ!)3T =/baHnxT~Ţ@kR_&:Qn$|gL/v}󷱅ᬛa?/3,|p9ND!Mab4U \ߘS5xG-V" R8͚| 7s8Krb WRgTZp-AJ'B^"cOIVK\V3_2$H8% `dJu֝D.&],$ka-Gi 躶WHMڀ,`EČ=I/Ng5}(TDQ{sA-{{/Ar}Zir%jݟ*uqۿ,Z Hxخz;^Y7z_ւO9sX#̖y]z UŘhXfw5&JXcVfG`BKnLeэ֨y~hS2 EӾ_ %L_XUsXqKr3SQT>K,g;'5\c馮VzAQOa,faJ#T!b=#WýJUO_Z5t؟RmzV 3Zʬ/74k15u+#6z^}-B-| oƔf󟴚XOLv)Դ0]sR]dEyv^RIgu`K1yFE_Oɨ]0賭-}&U0Xcy)9{pM7{Zծq)AE?jv658]bdYmM։3}F*l-Ժ+lYCPs9* k}@ܯFnXc失O84=:2} ǹ G+uq!x!Rmڀ(`9*whsjhS-[1\_Ѵ5MS @٨>deحnϬFJoD8N&'ш\ ,sQMiDBIrgz}_J$ +78XK9ޭLJdrTSwQUMzdN`KUݕ[5X>11 >뼿w[N=qWR ޠ9ڤwYUn1D@|+a2])-ں];ng\cWHwB iN*)J@_Z9Ve@"% ,:X;Za\a R+{yH9י{Ja<^g ^o"pOʧ >>*J[ք:WɦnQgM~qY߫32 Q~\=|`K"؟+_/>հ rR]Qj(1}Z{'V]}6D$keENA$4uoauԜҾ[l֛vvsЅ#dlܠ}rTֈ ˘j\2tJ013 ai)i6D0b% ^=9Or$= M\)L'vr ,A^z:.\z̄ ,ѕ{MAaa(ZH(Kj-xTրDw}WfW'91CVo]5įtp%﷧?VM$),Ϡ^S|:Qj~/v־;uʂ9kl\@<> B7z9ߚk? @澓ǔ~uARĽEώ #I jx ^T&OCƏ[veT' H&t  7JO{w0"cP ;e1yC͗I^)d9*Ī,D꺜pih7i7h' Rl͟,p8BۊD=+q%7 : _^1k~ٍ)`M<dv0  D@hס|Uv_wB'e~1X' cAuyfd |hUB}eu;9۾qJ '&Wޅ˨"xGO3td)jr0ȏԯ7l2!I5D%̞Z[s%=F~p~ X`:p Tc6CvPLdCaɅZ;-_W][ʀkA +nMά.jW D 狙XM z\|;wYy=D~9&̟m90puXSpO<Tږ)-|L\zOjtK7]y)?9rWmkV&l+jW8\y4ˬ]mjO3cw,޴}K#ucT78ϧ 'lɟ?O/>KMGՕzoycS=yݙU/_͹@ Rf,cAfה{+* N =44ݖgxwP>xrS|QlpE5XəɼkOkhf0&wy<) Aʙ_eX;VgC.Ge+x&_#~ݕ֠j]9g}^Pj58E;[MP瑖"M_g 9\IDC6Qoٳ'% k.mmP=^ʯ;$ՠN&0\ICqzpQMYArw|%Y5Ȗ߲VQpr$z=EQ|{R6ctJG[ jþ rKDy^2+IuR~|,7F?9M/as&uyċ΍omnU Q{Gc.cѐ&n4"Oe/R^6[ZY}jEB=*x֥ErߖʪǻZ"Bxz7]ţ|65嫬I5Imݭz$#e)%=7 I),D.W*LqB IDAT)}``1>_3M Fam*BoMi #3G.pݲCn%+ߞнj!j4DZ{F @@HD{N < 53du#7 M&9YV`[HTx@?.XK *o'ʉzZgC IyJaĬ/mGʁ]d&4؆SW_W}7!G"WD٠~ꓻl+7L(gcֶ_)JadK>)dA7;npWg O&NۖCy<$Ӣ8r>eaEMZ )+D| L' g(C]3Yc EF5hXgTH%"rsFwd{0"XCPwڢ`"qk/Sq%yJK B)oz? Ck1!pdsXZh;] :'tIUzkM)<jX}[Gd$7 AP쎆8:f3"[}MYԇtFMw:QYs} Y.eЀS.=94\]6ӊo{sRHxlsSA4l\gKy ݀5>W:a mwtז5UDfG`p5@Ҏ8{12`rz6we9*kH,4RuNΐbp䒖LH^l}b Zgm2pPn24L> W?RRm3`upU킳թ>?o}$`J5QYw[BDCt&k7tk J{Y/%JU5uNgN5Ugu{/ _4J 7P)AD )\e@,dsr]+pa;)4q_n9*k4[^Z/I{7=kύ|@d䨫"]޾ֳJ: x1Yl)%O" k(Ny[:Tdxh;ԜKGa0O}Pp"G r˰Ňs q^8 M}e\QYEiҀ sʗ!sކk^Dg (/[͘OU5H*ꓷ9Bϭݰq-9שX.RnuIZSAb; q^|w͒X"KQ.ђrç{TsަV%E:ŢH 2t⮬p))"|$chC +BC3!y~fTXT7̑3v{o2[Uڧu6PCŕ4hQr\Al]~G"^SL-IȑY^0?m;&R !d7,KohjubJt"Q/pA6j{#%[[''y8uGmA![*Nku_3O:vAqDHfpNX PLv# +CZە"S 0{?٫PMaa}3y pH(M׶siH[@Džh:֑v)R؋T|%iPZh_ pBbd[ccتWcp %jj0p3Q|*Oa3"U#tfJ K9q4!YMLc b:6 ld `qt KSܩM6ʦ9JE $H~d~n ˦n-=+pr%%|ۊD?-Qwzeh >/⚫tqP3O QӘLֆwL8'Ym*8c#(ŒJlqj ώ^P$P<&eRJYh߱Ao;zT`BSRgU5Us<Tʩq/e5\Ldu,MV5ft-#g#^QKXs.%mzСjݏ| e\r;bRx⸢8.|BbMSF`M^/2/ ny!MgS@LyFldyx7R:}k5w? Í0Ǫ6ចA7Efh9*k\wfo\ܕ5?/S qY0Tez:hE|1N$!Ⓨ>xEυ<꘨VJCnopl}@vkeF cNI?}˕3ڡ*0eui>Wϕ8KLk<ƥR *5㸛CU?Hw*ʺ[s6d' 3 zdZU.=[v5H#X&wB"@|Tzߘ?Wc[ GJJȮPnN,@`nQD |_eؠ󞭏9wse?(3OS0=O M(8a&0d+lɛ:K lml|6k!L&柦 ZS7qQw8.̺U:I 7]CkߵR4GI됁)8KMP[ТhTr}]'wXtYo{ݴwZ_٘|Ϧ WmUnb_` sňs7W ?/famGsK.R2T@[o@crOb"2JihC J{iRM5ὁct>3Rw)d0H&|-tTRSDG +5#!̎]s=P0`!KJv4Kܔ+9 p4HX| E;dޭ&ŊETDNn|%܅$xwGԀJr7{}V,0`P⅙ ZvDR-%Da2j"?kίm9ilFi~̾ly`U5\|b>CB@菠Aݺ؃O)q1qYA)"_ʨ|X<őBcR 41te>}F-Go3%k%QHb'y7*)]RXALtm&/gZ&BGx_M6 )ڙzv~5XӀ EWOgZ!,,saSN(/'mE ?wcIMڇSzK#|cZԳQyMO k_h)Yj2nōI @5`qlZ2ĺRz1.6]Y!2*7Dw~'ƈ/z$l[4Nwe_rCBn2 IBo68M{^,'~Hu2:Glǫ?^՝$X sQJe 沥'Ќ6qosZ5kf.?_-gZO+!h`B!>׋Wр+Mt/SnM|e-yI漗/ VhkeeT>2w.k>nJ@Ut%pDW>aP*!PAp~“̊\J3K MXd2;;q3+ΪvM9 H׉rk"ŜNpq}zI%ujL[ԓ JW cB3dBy}/0w9ĕz4v;굘y¾s8Z{~4f MR /TB;M*<1=d%=!۞m{S`n} 1WzJAu$hM6pP,5{)%Y>Vy[^`/zT mE}*֜iْecY" 65c}^5:Z/`Č9Nj!I_d$Ȉ Tc|gTc[Cd8(&Nh+YB{tYDzB&p|/>PTDPrD($hzGgn(JI [ 6_tH~<KA~MHYs" />"@ۡA xcڈzRq @ڙЂp7K"WҖMбS1>1t!0o"f|ӷ7ZX=*k-OIgW.@]kԩ (y7S֫oLXe&9Oze="5u4X8wY|* J^6{,/jC_ eL(ӢS6T@V!N Xy;=v tZE.ўYVF{ύڋ|k'xqߎ)pxnG-1eI Zk;%RW[[s|)D-y5 kZ,Cq/b/£'A[ɤz~XQ@>^#Y?_ɨXx ocJT ܡEk6bH a9ڊg[ ]t [J&.;:QEݔJ0X/}i,\R hJ1È ŒRp,G@DB<+o/׃ #  .쯒{@Lxp* xX"ՒmZ8$Q) [:e*%l~K \[qdHcWXܡfРb@hp'Sq&MK(o۠(@`^-XWM]43jTܢ%9(|xGpPiNKn;*5l`?Q(Os':P-嬔9SH7EI5C6R+HӨ@Z*>4d&⦗- Q'2pެ:>h V/9d+%vV|nM7+;gSR s{3]nS^;uĵHb Hp^Tēz}Y>E6ZK*Mv&Y85%e1|qx!U!^O5|뗼n&,kDlW*DӧЄ_{Q#IlMX?]6l}yv5nx;(Y0|)rd"׳ %p$qw-!+QY3tz$[ w2 =*k魶=X>i7]Lsdvz kow@?)'}qjGeN 0j4;&QISUij_lt>N8Yr^bB*_ٺkd9|K]#2"ĴYj?e&S:Ct[Tu,Ν5!ϼ_㚵X+tuپQWLr̵B^_;?ψyNxiA464K @Zg=`}d=`D "76ٻ;J)rp7 *쪥fyV|BB'y95XlPe*/9mHcg;27k{ډ~=|Νtcȶ>Q9 "!Q٤R j m9Ln|Pg3@c r9?xavPj;̊+r(9~i|a6T!bó}Ŋ7mA(SK!Z&dS]PAz3? kgzUu@)e 4}<4gutwX=qUdK%aY;5R J}JI*9aDx~p'5DDr$rT D˦aA""WU |t_.&U.V.7E.ݱ+12Q+CK\%[V>`ESHvqToXvdۺ+kM?z*jC> >r^kWѧ}ǩLPbm0g-kn饬az~ڋ) zAUM1ih0h@oV7Rf>߳^m]p @ԔU$04L.[Dt9I6JUBOc!|$jze ԯ\' l,/ɺhM+B}`+dSQzYAZB>B'bYƔcOIOrg|uW>P !; }"_$DTpI+5Z29_Ů.><|z{⇿U['/mQ׻},:WB%ωX$"WA:RP70 #ޮdvd,5~93'D)|G)<]/&]goAa=|&/Y9!X3(&K~gOܺ`Yj *®udV_zwNWITZ.GHʨ=в;>z2eCjRUlY"šMW2y4yFEX7Jtuef_l wؚ2n8jk|I'Ry??";V2M^lǖֱd+&/VX8}J5fyfy&*i%PW Kfό:]y}bTԴ,%s/Cj#=*1Yu3!R!]6%M>YЕoOB-Z :R䭎 ȁD&b JW^)t܄X {ӟ;!`͋/Zg6v!<2uDɕoFRZn2)3d+-۳鼭#flHG-l:s+LE X\ofm2M YQYC;"x^ 5+Uָ )M5?5*NY"3d>NaA}ntU܅XYs }J~h2ǯL)֔y(EQY v"嫇16b%9&Z'0)G])Ra9i 轒sW9sTADx}@ƎhU A7YVKQ@)>YLx>rT~o˞u?/jGp4]z٥QfX3 ݚ˻|!<;֞nݙOi ޻H0l5o*]gqe9*k߂h;x}VT3]婶,#c|K'$@@*rORLٍj}xt $wZoGW Ir/o:4/(#%4娬vIT U1sn~?͏k3!3azM,T}/U#`c5 GN#,;SR?'|#W#ϣ2? m\$[!M:bvg1֠&9òHu8)IS1ZI5J %wϏx-Eu}!43l˭3'Sޏ-Kp"!AO1Zbn[LيJyٷEB־WCk\6;rVA#WV >k)XcNq:/mòE+GsBSO gsf` BZ0+g]ƁSMo\q&Fb7vp+X2wvzk~\ ;LxYW 7E C¶Ov  $-U~ `%YC]UVqA4T0\1>>wg9ޭ&L|2]eyHx%G'S2%S_,-z`׫kb#[}5K@}KZt&ܚbtM5A4Wm4꾯<@bv;5"i"Xlwca5$RtBs|BW#QY&Țs@KٶEpmf/˱2덏ZYQѾWZ~޽=8/_G3'EI@Q5TBT}M "JS켔SWµiҞg/"Rݺ1yiPd7 "_Q9Ju9*k!ƾH!u)15@nlhZg|ٱj%)ɠ ,;9'J5qQ(dUP){C2lA}i^PK'B觏Rv9*qx/#3\ךY*_epՏ3֘2+.SwDP{;Դ2ntΥtHSp48Qv9k2`$pBP6|E/5eR>\UaE RKn9*=׸22tp@%ݺY%6^޷s=;[aMF M{'q_ELUBL4M!usC@:͔88O4쫙̇2XI\%jG_067BP$֤×r[lۗ`=kZDGFxAk?қBlAɈ$)JiǟOejƊ΍~SLyљ*]O:Cޤ.f6!╷,ͩ?#ھ}4x灃3/]/hpDT t! Ĉ_wHRq$5EB3O9.6?e.VíuCױJ%ԋ-Gn @HpG֑\J/$%@4o$^ZvXyR VQ}v04[Ao siǫfF]9L%~AF p!G'$Z!!K5e`_]kq6\]FStM0d%WF{ uDsw,V{Ɵ.ڑ׶e靜-1%m80q{u5I3y{_"sYXp֜L /Geڡud,sTA8(ݞACQ%r:$^զjcIu|e`7˱'a3G._&99X؃#k2_JLo_G<[[էXPr_ly5#7 $ |۞% _,(U~LFOU3d;ҳs| kA:BX$z)w4<#Y/1q1wM ug-SK3*["iYlCNlC2ZՈYGcT{{: C+R~OCgAz6wDhڕ X0=.(')͢$5,R_䄥_%4-y Xhټ)RS5/{V?D0eE:]a-x5@ 6rb[fI<8oy)Wk{Y.WZӕ>1|_J[i)`-E`^(yf,6m=̍2a3;FDӶ0KjBh&ǝyVGڈ#kkTaf^3wmϒi㒁 ѥCc{|tC^aQJʛ"԰%ӛ~?c.xr*pB0K $!yjeKvlqʵ)_̨b*"T~PK{2R%8}IX?H|Ӣ6,OtB`j8d-lT+J|)>e1u 2M)OU|/o>C6H"^DqW;(ր{f ]Q.TP f1)i3g_[D_pӇ 0";fb{T6ϸМMeEeSzKɄc!T LD3מC!|$NmDz\( <*5lg9ٍ̡ q,N$y(уVD"XITo@4Ї6+$m*~([_ew33۱q,&*i+HJ%c@#F7EaY $0$1xٷAŮү5ИtlaX#,|FB^|Ջd98Ԭ@l^q?<.PRc)ި/1YM#du^:Bq`Vz娬jѬU s Yq(Tt?FJ%.jR\b/+Zy'(a0 IDATwKW!,dҕR zyH4nxԍ(M‚Y B1&I+Ș ]@h= 2٥*$`^>*h9*k ne`Dv5F̅nNJgj{KcYr#?$7Ekk)IiYYVcIE h5p3T m5h^#9^|5%J:Qj|)׹MG}&izTPX#5^1m3\M'uC'qO !nt%,c+rS^ʦ p_0m, DapZh'4-(Q)yQFG EVD@ oߴ`im隬KSYb$ɔ wll28K*hN-Q]V_sN_(_֘01!?!J}m!n6Lhۯ* 9,_j,+}0陳a& vU:d.j lN}4®ǹ;jX _Y L[Ho/E7;TcUd4uY*.G#Dfb $<"[Tב?-Dd#1U_KSxORtQPRâeMjEZ3s2ޢ$<%y̌$@w_}WM^S3BFKC`3-GE"5%)9k y),A4Po\5#ﭼne'Jk;g>_(5Cd rA}:k ^D8qSATo @JK\K-ۭp@/NMhN`Dtޥ4Bi'"$% 8(1Gya5"KPzRЅO~`YE1쌙!KEf/ͨO_i,;9w ;To׊/Yq!OM%"-+ D{-p xqh)z;D:r-@`1rC? [ՌK=8LqN>҃- g4[)}g䆚I STBU̗@Ԡx(Fb'GCaKVlJpjIu>}%$b77-v+@\Urv|67IŤ'$?ր+E8XSkmLR k 0#ˋ)­s.Hx֯=Rm%&H@|å=s#-sRe^@/7?Z_91]"Bh,)\>NYĭ; vMxJsRnsT֓0҇5L >R\%GΒᾔ6W.Qee10BUV)iůiajRq%~z}5C7]r/`Y/g*^VQUz|@k#՚Mt5O t|eJV $~;W2a;h.|"pb*\" aT>JEvb((1"x@Yv*%ufjL⚋>v8p~!,}$$Sn1 `}*`q [""ڧCpmrqgv;zD$%CLMVakEC&,9^6l^GƭGVR/De^D90fZ#cϵ--F!į4QuS IRD|֦ۜm]aX#hyEˎMr?`3kN bz$5 _4Kp2`>Sy@RP#"!ﯗ`R`Wڀág=X=ߓx x!sP.R\1V ݕG8nPmnR4D/jۘ,llpQj| X8 "1 0IM<isν>]E.0-#6'v{ D4y?*P5nv^rꈵ3pm:f!LYÝV&B[I"2gNzBNӟ;9ߑLt[Qޠ7/w Dj[-mgۓ-m,2u59SH9Ř߁\,JS';}M/"1[` "l' ]چoc m6kN=C9 Oe Hk(qf0{>/CD"R~#OlRO9/"ڽ2}b(poL>>]8( rH ^HwJh !lB]^ռ vo/׾2^E_7} QSEּ6L;,ܣZds84bqh*ˉSGQI&QbĀg^Zй㠱T%NvHHrUvkk3X@yc^cEW&hfUC!Uj-GG.=J~@_Ok?AΘ9V{ b<'4#Su;9yZ;%VM:bIWj51e_?]T4 2/\/MEܳ!ҟr\$!HՌh\rowC锟qκ,ёe"M5+zVwn^@kqMzDz[%2Zk#jdJDg!P&Sx;SDYN<r;`L$KP#(zl])d"mҥ daD"'K#oUЇD/@BIE<2@c|Nlxf$#h6̬rŠ@zkogd3't;&E"t,I2%h%q|0 j[8%Ҧy6GţhV&!gNEc̐-wH^Q6zwk)~&W¥]`ک5ѿ B=MCIM&U˄v/?Kg`#9}V B[D,-]5*M\OoI-=>h~ o}_^i`m׌d zxcZowl< F*ɶKYWJo0K[sZMaM^rIs]*Y㊕GBZ)hhb?Dv:KY姫}G(s}]kS}o_h 1DouáFӌ\K ߻f;}Pˌh QtP[)% "BS' m&Ԥ}xt]GCmUi}+"m'ߺs ܜz`G|ȈUw_]y5U,QF, C^S E-{(Bt?DEg8o`;'6/ bƂ%,eHWz!N[KAz{/_8 JfQˑWB}6riq AÐhd1l- lQC -=r>p-D]k7%%<2haBV䃈'e-(:ͩܝTb#Fi{nI8tv fp9\" `,#D%Pm~Zz{$5(g@jy0Nk ԅ9+gŜSQ󛋐s1sÀ6Dް- W[qG]w>7M7E5NWM9m!\M1;$o/WmP# GBywl)d3{G? x9wƈ;`r8bB*jSv6QYms8MWkDU7f6mSKi&>߶<-rG-%Rшowc/\&-6`Z,x>'w Tr=ڿ3fiMLJ "g4dw挓}p]%z\yF DΕTq\#r:A娬# XZm{jWϚacQ-u'  ]J3cWoq5G|>]:ܳ2Ե7CC]&)[dezAQ!|%PӉbnbIk:DF3j=W d6~bU,`;!~9 gJsXDn[Z֟(I⅌){5g$xJ"Δ,SrTV[I w` +rM?J4=އ7C Z_ƃYMo*& vT_3!w׬@5d+b/FA@: tc9*Pq C9><(l">W!"j0Ytt3X41t؃  JlHTlOA*)cJC$C%eQf!726* Z4)"؜{xVOv넥w>4| ~H΃3gwz0`Fzd0X9YYiF DD6`h-j.pL(݋ɿu43=mP0|Jvv4ChC_疎_\`psVo\;$ !j!*ުջ]X[b2> "2jF١3n:R(Ixft8f!I&d$AQ 1 J^/ވxٲK6;ڞK`Z`Z:xP*ȴCP(ɰg(gNF  _—u,:k(g$~x H&UvMz1 b![ IDAT o@xayy0`qTVDf19ޕt; @w2@@xA5q˄"$u.[?=XDe}s>}\AbRt'pRʓI ^}ez)K:g~qj<;lj +} 1!㻉^K(~1_˃tg)wD%_ԫX1!Z:M\?#+kQǘ?ئ }q\,Jء9) ;HΓ'^m-n!!~hy& ϟ&z{wwhA ?ӟLB8O.WT5YSXrV &[PT4OvQMnԔ*GglaW|K H!wT%abh^+Ce-7•6V{X]ݱz)xؗsCM>IvzTQJ|W*ySJIb/ o,{ -xrKlJ6)bw&Ҵ0C+C%S~e~+`Z9sї'jeק=6G ?XTVGbTi%DV H5\6)NQ3(H@ˬfvZ[`ZCf8ĦխA*Z,wQĭz\W>%_th7F gV^Iv[gZ+ly. ZV/aQX [/˽3!,~іQD=xrIG܋S$6XUGtxk?5i#wZmO1I?]5bmU߲_\@V=k"<qA%X0*l L‚!`"`ۍb;˦sV#\80BKwiX^ V L8C8KսDIAw9Y*_{WSc_-=.ޤGi2JJN48Ún7XsӍy*J A,KBfmL5fDJ]c'H9| $$n;R; mztmUY qGJa"RJo Od]<ݡSmbvk<ګY|ڢk+JVQ1?S1ࢯ:)8>z> u&2F6zEG-*]8Ea?!lSeUʵZ_ɱG*LJ_YN :q_]D{qJF.QXL;ߍ32;%Q}x8_35q Sъc_3];'Tj@׉j}D, [12ii3c,#PDr׻W>ѷ?#~^4'?4=9;>n'y9*W.HV;oDe(B9,9wqJQYmOwKK=;Zõe GfLgn\30J,G%)wf"cHY>79tiC90m .rfh A&0pDfte?GyEkըwK,?6Jlr_2N 4sg\k92)kz+1f~,93"ݞ+xκJZjʟ(?jr: tЇ Q,VtXgDpd 2،A#S9v4yPL',9ō_4|; 8w%K-.Z'ǀz\dэ#9V! LKp_xCbUaU' R6&)y< ZNR5Ri$fpmLM|^) 7\ d@ZSVQM0@ZγB{onc%>Qnc1ceF{B?hTxr ąR"-RnniXn(޻}H{dJx\km,GX"+EDW@$k? aʠ ,6QA p:KBCJV%{=ϐ5A;7x$tOr [Ӱ0^GkvK*jim5H~j; }SZ(z܍h!BBnRr-Lw "_RV[mgl e`5Y~4*fQࠜ2py"¸nk yKiUWī-E6qLCʄQy{!-$yHDP:֗*m!•um.|1?,9 I]2V B:^ f_g2mpu/rI&w և$Zv8H%6JoUY-]mIJ'-Ng&)g\^`Us8.xNne1ul 5/D9Ѹ`Br4m=<6?R6uH&,ܨ(Ԏympe. :1=B;\å3xY&)3WyۂOy3䛔`yZ/A!h"wa]&#E)Tˀ{ypj7|ɋ"(ZdHF;CC~B8vPs^\a "ռ 8~4Oi]QYmop(_Ъ4P_ia\6ryOݥا5M޳Sq4cc#L0[t'565;΁j3Gfcٚ pu?avm((JPWQYmonId}f2|9UW@M!ެhޔ9"zX6=5R9+vɞt}&=&&7.}] \5DcY"IC%H!哎)a)Ҕ0saN %hA4nFZؘw089IZK"%, *^6lxcC,'n%E?Y$|k/*UbK!gr[˛l bWe3"&Rqǭ rO-'f_"JPXk=aKrAR+/3_d#61å;),C@rG8V(g՛>|3.mƾ2d ^]:HD'm>ъ"ҫUmHA(mxSV߽wP-%Zqaۋ?^;X6j: @DnW[$ZJ;7$;Fܣ}>_㉸M> T*3 ¦nAqMYKG-~_?O5gUE ?ds/ԯ޾›O[_쌼,K;Z2bQc=j5EL:2Nީ\j3OQU'*Zjm˛_^{)&/eyuNdk , ~G9J>BF6?<ϣ:Fs |ZA%9^ kZ@7 |8ZKf^ ]vnNmջñ ^,)eI׀LRZp4lĽV[Պ/[L1}/3 L{wXMw?esVERYKW''.XhbGt@kCo\;ֿfoZ}zl0[-Jǃb}f11B/ߪ-J+ g#"-BUϛ5V0V; J *A2t|k-L7Xb]ZkyTTe2 EWg.nikˬE70KVJn܍CHSžO1BjyyC<Gdydbq'-?>/Db%_JIDdZ ILeg'$2>X6:@Dι@\X=[q S$N0jKОt"龘y'9б1$ovf>`*4$2Q|%N8np^JNoxLhGd[đVlQ$Dk٫[o$D]?Hhp )xii+!w6ȒG$B@@bDQ r~S957yGC_m yΐ<&-x^興7?Y&+jB<5ˀZ,3&7ґGvGij$7k|e6)p̭᠌e7J-((5%:*;Y jTV[w] 52OϏ݅gh2C;/^Ti.{^7yְX OP=Mᰵ=}e"ČgdIYP<++:Ɋ(9M"_{Gey)Qov ^/_̵)?IfL#Ն?)rlFWS]!Ւ";9=cy:dt\\繩_`˨jiddt8<LIEIh27|(9BLLՎ}:6LȝoD(^hW ;ґN]z-!cLٽ(v dk8Rt` !ƼZ-ў~8d%8MjWd+1D6}"&k-wD!l.-, $7X/ơ6g`!?2+n%e;gB>4\"C2d a{d~*0VCB}nlkDv'mH@'[~/t4h ` Qɕjr$[f_1'ohWǿ~ H}'1[]JyMbVYc3[1F!Oq#<# 5dbg3LW":EYxe80]ƢCҙCkj^'JO4GBXr׎cWm1*ˤ.޾z'vHYx:JJo>>xҫ\tM' J%~btohF̮ De?]8W m*bc'~Wi,XҖ/ЯwTV[} b 9WP=oN񸹐a>FgwYxs|Z ^jDŽa!>`EŲK5UV'Ps 1G?iɗVޜI]'*l~~y;ܚK6/7cSW#I ޘVDk[M:Z܊Ǫv􁚎N{FH>k1/Z `^Ir]g*_E8SvaK$[h(ݹe9*;en-|i ^i ڳ.Ra: 4eȢwZQN'-YW)_g`"]sl /'d~@,n@{a>M:7qTgHT' c4eV[B8нZy1_I-N b#}*u*z))k27QzAeEBIw{ȷ4 x75;!3Mh  h k!$'WATj5>ed> !:=nmVu*jձḭڟsE%JHtk~  `?wi&~ִʪIGMHn€J%A12t1w-J_ ;C!e uЦCn3;Y_O_)lHU] d,$"}Y0-T#ۜ- ̣\TyDL|7[}/SR$:wjl~;qTZ]UO\k$ʟ) wDZ~Brdv->{b; ux)ĵP[jLw]]*ǥ+Sgԙ%Z~sĸBVȀ2V Ĺi|I1}%kEx V1ؓdz[9ן2˾Ev/55ceF:`,2Cc%u|,F r9fYAR4/J)g(J.At-QkUqVɗ8*^C^ʞn GIr/Ox/Ɓ\X%2QGy_k#HBwEQQd.]WJC+o/1rBD}h PX9{m%A?K|e֡ Һu`<^0g+~g'8/.qx[Y$5v5h!J8얕TɡrPc5/@ H%N㊰_3bζsaV>NTz0J_m8 eI)>YSdo@QNc>r"5KϦ#+Ub1k%2[ N8rUX^MAxv.0)O>I*YhXS={a!j6%>h"$:%x#(Ycbœkr -W-LVE7^7¿nb ɵ)$AapGfǖAf{_YbCϟ+p"zQ{~y6=ԫ>hJ)ʧR84#m3mW]9ථvEi+&{ t;אHP=ˬ>}߁T;-`g @5ۀ_hl2ҟ$0j8 1e^"`ۄ]xZkzޭC@ɖDm|80`Hlc*t 7-V,zw[]پr7!ޯwlikF@:ђnY?*.1>>.?huqH{F//f11k]T1־edN狊W[m-Ǖ&:>U8ɾUzyy`^B=Gz38A8"8'vI"Q*qi3f9lؐ Ԣ %9 }ӡrQta!M4sQ_覚ш.pP@ \ ^7QQl/nPl"2vOz/%J0'lChs{u*nd|h^Jm}# 4A'Pa`j@W jD" ^i@ݑ~G{$ gVpmPѦs"O?&8;\`rSUķĢV[)FUFުqǹeHb $QC"2$y5;U@@]\x"%e$O֤7oZÎ|bG;hpCb%?|m;,9x7_9~NR*3QT։j}uy# /~m\2n}x EēVw)zCDrx w")Sңث c &ImX$^\0L[U 9wZZW\٘D LF^.*7 #;j0?4;?Dh8"ێP1V{,S~#JY2tք Xdn*rl\>ӳGM+ oE_M*fhp]_DD"0A] ?SUNao^՚Yoa5*KgMjm SYU0 =h6cs=ak+&&!ػ哒#Z,JUfw+Ɉ*}<ôKPI-ԉkAPwO3R&lN BjvϪTr$&L8*\h t=;ϓ܎aW37Yaqc `ε2]{ ]h s{jo;aL3x=\PV[&ji= ?|[.x]&4qW$p@d]kB=:n[*Ƙ+cs}{*?N'%HYR[q,aw>PRp$I~NZ)d:>˃H<ߕ*Uf)Q4+tS#`U*⮼"s+=^rL)zPYN`PjC5.q]mI( $XrK::ET|f_:+ V,ܚJ'ˆc*ۏ6VpY,)a)Մ_sWV./lw))Ф~+gQ5ɤ+G5" @[>Y"꼁)Chh[x)'-OLf wBc=`uG c!i+ZEuܴ&jRlnG|~1Y8~RAQ*JFF)qzk<1.2m$2L>wEl0IY"@&D c'oIS-I3_*O0߹)fLEn BCH^,u4؞H+ ֪J<>Ta,C9h,,IB A8oh#ċ{Wfu%°ƸY;?Bv@6$A# 8-'fɵ ~!/mWSJL>"wv()؈ !ݔ<ŬGH;sH܍Y_YCG(iҷ(Exk {na6H -s`'c]5gpOzc~L^5x[qњ6IP&. dK7~$Š@pF@G>\}YQ8*=O"1יR$XBy}$w7;~l_KX69T.zA5YAvueM7+j^ge+?e걵*)w|=OSuIp~F_iAXDrl j\m.ކ n{_1O~9Aorјd8[gsyiÕ%q׈9=d!&iJ u餟,MJ(}GKZ.fUJrϊp+9a[j]dt"3S ;|esqL T[J.S&瀱Zr1'FUZ+oQɞ.u^J8@VeI*f!yQTE 81$ރB iMJW_3,1RjRϻ̽W&PN|+5[V7cW +[nc!?w.3tCJugX&xŜrc.Ӎ3ZR.Ɛ;·dۄ?.|>&xU~W>RniLFT)r YtH  #WSN=/tȘm-IGJ>$@P_[+ {\:? ۔"~~/ eR( rxSDo!K-Q#4)ʋO$&%;q,fGNdM} }xVvX|J SM$H`TA䍒ŵӀ}hpۑAjMXE ,%dzPP*^C؉wkߨղo !6H:L1w,ݢO4`PLyA{;$ܤciT_ad6 ml5+z? Za]^j]al'^X/WYiª~P06yNuZ-~Pңzjֽ{) +_<)LyFi!|ک/]_LzZW KĬP&SV 3:KYm9 =#t~hǪR+Է3ۜ1[MJgnGJ~_4YoC6yp&}d$[b(4Q&?9)ݧ^{/` o'*z+uYmSحW9{ {(bޮwF/О}B*j,J)9ɡ vs^K-_W̓,\_D4JlUwZ?Ϙ# kJSz&/8нHܞQiӴj*oq^VVj<>:]F哷BAxbգ,{)y?49cF~e 5֠cݭ` sڗ6dq$$VG~|ޅ]@9W.j-/gϤU r4-奬րj~?K4i=%Q)oխmTƠZlе>VJZV#xL &" PC>&I$ݬuy^NhB3k?9TAeUR,qoըt@,enXzzrw |AAfI>{Qf!uKk)f7j .ዄBP.ǒ~%~J'͟XX7mOo(Nґ b  9"K!h9ha*0n*[]о^/Qf"Ш9DNNmhb[ЄvID41H{$^쌳ɜ:rc:YQ lPQOk~W{X6cVҧDuG2 D[f(@ĝќr{J<41b+1mRQ=M'awF5~cF:Vm |M%9e~;UXV 5N+f ! O 18W>M#lռTny_cϔ,@`7-TYS~qpj߱>g|6V&%LB&[ qMuVQE2hhU9Dl>bQc`G_njJ=XxOA=Q=/I`iPp:_j&]&ţjBuY2N}TcOrcfm~Z>'lw(R\$X*{|`[HOě9h)ćHevPZmՍѿlM)׾W K}ӥN^%q(U !cxoZyɪY\:)!epQYmg&9a:51`5O̠qnޓgyǭ^5I ޮWָ Fߜ՘ IDATέB,'9rGgM Os})2 F5Y3"*hJpDPѓ6j;aF\?xhtYk/SS 5crZs[KTF$@#"4iy!<}vD,XyÑ >w"#x#%9Em8 YAh;]kITCmm42l]i%m> zD4nt8)lKqiSX *Xk>7E4uR.\?Tpc A/KĿQ_ׄHRFlf;CN1d/YJg1A!B 1 2B#vcB_& A$1GL,c@צ4L8?$]AJH341YDa?Uv#co7,Cˌj0'#Md؛L+]Gr M|'ť:Q>p4&`\Y3I$w|1vl?S=bdS E`bVrTBlש>Ý3Wjl"K=!AJ& 4L)LV^DJ@@Dyo)A$l'Ts[Vmq j`S~ْGXҚ?am_y0bJ5IRSrg_)Z;t\!̏hRf)sg7V rڥY/yUGqty։jth*< [`l3|벑t/]]7wAxfF5k_<\'EmUi}dɇ=1'vm>>V=E848tf=qЪ&"{BWKVE !|_hM<܏p 3@$*KUD! U:Q]/"]̬j{f7 wB[4"y^pΧ<@s9@|dQ4Y/7*U@R]ݛid˴.nwͣŦZ!*X4eʖ.rlBF/%x9*f%5K7T\#CnIyCHbS33IwhCN(Ѓ˟F*zO|T 0ROgsZɾz~4y&ۮ6sgnp0I]bi*[~0|phJx?!1\AFzX6Y;z_8X+u!`r*qHŒvRY}<\# ?z*:[̠a.Z>s<*`93}C"K.<ǎmN[$k^%^aRZ DJ/AJT}|\V vUxu>gUJ4z-찃s vOe 2,͗^y1^!ݥ2|_K&ZRsK=X;\*xr*jC)ѡjap(֙!5n`bô2,x*|bݺ,XE!䟪9's'o4c@&0 LǷ ;ag ǨrM!*WA;d $s]b7`rg~O\^I_-`ӹ㭽S8"2v4HJ2 At}W+# =ߒx3%695HlTʻ3+!q_؎%g)`!va<.t%T+UJԮ.pIť+Ջ&ΓY'D&@s6nwx[Gì_ : ްA)TZٙkm9* ]}#L[7'ȼmi.  `F5:@@KVdc&N=>(TήSɺ K̩JlԆW|q6&dª *M7@Q5א TY*>O @qcFZE~j1øKVBݿU\䘵eIش4a8%CeB՚`ggy_NpLRCd)5Is6yu\_ų F Uo drᕈ%BQ!Eh88R,KTT52}3mh1PD nSd UΗ%o*?:NK9C-0:-+$A JCglcD^*=&p_2*dK/mYX#G.3de_?GSN:^mZAM]Tf*W&=9 :n M?ѿ T "aD%X.w]b y[oRn^JՔ*1&.[ %eS1Wj&AY^3a+*&t9]YIL5BN8-;x4WҎZ5[JEvdj=?LSix|c.5Y:ꠎQ9;n>wNRRxlޑi>WJi@`<ұLi6G6MI2kCጥ>Ob/]aW[mg-g(q$r4$g%O ힼΟ"w 4˸{z{ohXg?Nt/۽` '=<ĐUzA׻j%x4` 0*BLUFxoT,d[UMp!oIX^q"LY|?7)tx7Ei*PEI6pđK)o*<ǁiE{Bd "e_mo l`I? iYAܱy <|f4l*Ke1ܫpaИ|)m((GHTE0@v @3~h[f Ъ8Ȃ1/aW^}ڍ" 9w s`o_J? {"lueݳ*J+i<^bmLf{~ҷd SËqr`6g"Ա67Apc@v@h Sf T-%Ƹq#"ZxZ87D6^@V;TtbD]~$)-lې芄)#W6[ KdZ2%SS%>ƍϤ}Cۄ,Z g:SyɝObѓ pAh_A\<*.j穂m[ųb)ojþTV~V߆'*5" O\MIBm7 od2|v zd| # ldipf01nJPO-?]n]jU9doYw؀}'=mEX l2HN]xN^g´SF@>J< M_8˰>K+mqCӇMn 儸հZˌ%ޏ< uiWJ>N1롄xUCJ+j=>Si"ϝxU<.A`VIר6M\99K;]QOډ΃͇쳦),D?lA/H$@T[m1ȇ4:/K)?Y(6LNTߘ^aV+.UowO&.)F* ړu?c4EEO&JѕQP+KID4 U|4u}qE㣪,u߫jHT(tn^;Qy^Gf|8-CdѽDT)]?jOIv$!w5{eQh)Gs7m6}/ ((i~Q|"nR3&뀱2 Z㼑wW(nDeytv_m@9ʕe<sH/k{X(DRd|aVX:BZE5ע3P*M#yHGu8(dem+ HXiy-J4{#wNgwR~< E}!(ʛ]^ɐu6 WrN:iȁ(:tFeW{VYS$xy U/kkt]HEVICz̚iZ <(Ӹ%.EJ bs/E27K?ֳkv+dO?6\uZ+6u/RCaM0uG sKMSϔQNfJ IN͌ 6r?R [5`VA@:4 "BZgt/y/~Cr L  ;&͐Cr [':R" vUםLI؞6zyeߵfPs D,>ԗV.)S8xÞ4&ߏ 7yI]!ׂAr"}^R=*NٔEwÍSTtTVw4@hY>b[Cl(XBW,@U'4jg6yP^>IwP!c)2MFݹMpݻfnHpVNW1'qdtБVi06e#))͔2 A TuTS?SnTy`,/C= %WZN;R\<BW<] :ƆY,x}EEĄ!VbPoU_2<0Øc;HU,Q_ }Gc^!^9FĈ sdޘ[f).CEE_FmvM7 1@B71y!8k p8,Y@xæ7ӭ[X tE+% &"̨!?WAnC LҀrueq[HmrVbױ`a{ɩ\1 Fp6"}~M+NiT*ةHA?ѠAxM_학XoMz/qKc:zk!zBrzj42]c)iB4qyz6 f ݡ^dD 9#ʦivt! ;xD"G֗,-ՆnՔÛvK/rP:o#1$T%z!g|! @fK"9)Avj`e6uɷ/$ė+_J{NeĚf^㯿Qy(j=ŋ.y^HWZ.! KS(澗 !95Pa"vR$f^=b+ bY-¸+L9azRRy}Qɷ*|ԯ u37qCJ_hzٷzyYƔnóW$; ,Utc ½kvv:Q; sA:)`Oxj~Oqk]J1_߃DQɉ0H(m!⸼o2lβxHrӉyVt˫oUgXTlun,Q;K!B_KAfG᰷͍'6JTVn8Le0!S]6/HdOPR&¢[<*5oH{ KŰՒ*#@`r{Ѡ9o \O5p7|opr {21'f@Wz6Ŷֻ IOd)PR;J+27w,g M FWʇ(ŖgO& яyٽ d@bY$Pyx"Fb2bl~J,]fs9'b7 0Cdjh0 /5~w˰ Jz/%_5G\ R~G4:`f'ydUYэ`F 'v# m<9d8q az371D %%cSN&aT[Z/dd{ѫz@Os|nT0LJb@݄2cۜ1-eܸw#` ]6d0˃l(FS8<)aXg%H== %6A6ۿ_Ʊɰ ET,HxGx|_,a $5$<pv͒x(O@D?MJjg;e^TbnS+nkK/>RrU2YY+JBG)f[Iyh{lbh7( #b4_VkTbN`>Z}VkJ9(XMǸ;OelRIIJM5KRk0H(q%{,R% lgDO9dd>T뮙9SjJSd-ԯ2Is.$|.^\,w-R,=!k33ŚnQH. 2T.sql?O"7`1>c' ֘}K!k.dED ."N/^K9 ڹXʗ4`aAE-ynww 2,x"],~;wZZ&}Y :Ko߮N+{j}\h"gXT0hd/B˂VY*&P 4<@k9*_,ggےdufw{mOmGx3; P(">U ZW"ZEiïXѡrqv4BM =v}ugN;/125*CsجEaNuCV~|)Lfˉz; ,PP*ԑGBE RrWnCUi0|ʂ}sjmbD.9^΂Xı_+WDEpCt$:/%.zP]t಼-4'3bˡ)R+<y{`d jJ[P!H~dv\^!{h8icD!AX_fjBRjq u7>𼯕6ȉiP<*+FR!R3ӼK ᔿyv,CCC5?D/9A3o'g.SHyc8$@EcA B&CV <2)r?Q?uv;[;Gj)+ҤTRnD BESέ3nϱ/" $% v_yIZp8T+U+WBPxO܃DD6\B.+.96h  rS\/~dVI/į)Vv̷7/!^Zh'oִЬ+@/Of]hȣU1W3w0ᇶî,+s )sVdS"GTi2`jbcʜ㹵~kpxc࿌_Bcdj]`>dx5 ubW:ƣSi}+oTԨo4"Wi ҹ5n\rZm`Kk"ԱȠQYm'X\c_"vC.UTV%HPKfo6&osi\|rޜJȨ7^ Gq PNMZܩr4Fr7SW6Pb3\&sl2X7x>/j=4 3\Sp%ug2kyuf(/_Kpc[mK$cUh%em$24[a`m7XpZbvx9RN#2f}hO3\G^XWO,ҀmvϨF +V.~cZ[ظsIy}_/-JpSJڼ :p,UaL@ǞmŦ3Ma4:Quh%TEsYm^;-sh .* k|LƢ2娬ڗ'˹2VoX8G32p)6j[3^ `mf^"jT|_ּ_؅>"ņf{7 ] צVPs&v,;P*xbnɔ :T6בM yR{~IQ܎ ΰ&Y2,!1l동$f.}ђADU"4{/b! ƛ .t>޼*`ai^@'%}3/ bFKd_)) ̎وHHv2/[QXG復ݵRKϪ/Xˆm'r|O=Oi+l25!L9QT{.K&i ݘI8S#{GĆc)epƐQŔkBi>jV/?EZolpyЍxCFUf䲴弅Q#=Ld(͓LeFDŽh5'U9[B眕ɎV=^}o 3򽲝by Qǣ HKKS6.;F(40 jTV{<->by@ѬmX|\6{ԜR$iNYݛ%+.0pT5W?/_ Gq/3UVDw)l1Ⴌ"Ub'(=x1ѿ1sQBɳLa ץ娬m˾4"ڕME3:y!DoS^m.7=/ -10rx5ΘvڋIف)3P[*sSGDa YLjQYme2 >&ˆ:t~ܩ[,R܆Nw 0ط h17{( {uf-CƚE͡5&APU((& zQr% ,Kf6ZK Ebf.'[R`e 9Br1^=Ƿ6Ãq.PE7)i~tިSKm{6,YkBQ^M_džeVEZAC9'SRF6yDK*H3.5i}zdi?AmQ"X32!z64O4jM^},Z2߀?s*\ٙRE&8/wFG}K XN |mW/\(O/?OyK"5|W*_E}#ŪC< o m-gFǿ [~6L]&יi]~cZIS4r=!(=$^d~_e['sI95ڇPR:96ANTemT=M2a0m2fZua/Q\}طbBr;M{)9|W.i+]"<  Y ) L-EngH񉬝hN}栰: PQ4{-/u.4 ީJB-#8i󱙫&ȽTt#sқ9Sy(TRɩzJ4Š q2=}SŃ56H^b0u^I(ַ`G}0$DXox,VlO&U¹xf^٭UōA 6<,AF@Y5*-n wMCfH6^楙l= +!G!|}Uwz30;vtj׶PGqx(AUL-IyIUDcs`9ECS;XLVWw6Y׼R5݋U<%Njʉz}sz?ml4OHuxˌu)N\ܨЊczK`u(ۓ$m5 9O/%ܨl-!E?dž/Mҁ<d'B@@7PXGmHשrT>)-.k8݄C`W*5hIKz&{J RH6bstF^**xh9yd0a+Q{>W 5,\P &[Mڭ/x,6q}^1$hatVaQ 4۸_ ̿Y R|az a{mGh֯ D76A5Nm$;{6sڼ_*{>a^m d+1h-6 H4&vA1ʊ)D])ɓUSb. %+ Q4l^\p%,C&PYe{0FT%\'#,D |nTK~wy)1^ҧoq,bo08 fJj73PpYk+KߝNV\W/ r3W7)8U1Nx׀'-]dTtUH3-Jx_G=Nڒ?rO2ڳ]vb%%Ҁ@*Hƪ2yX9 Anϋv2$a&4B@Z{&gۅ{Ã=6?o ^-5UAh%]ZyZ!x,l9#"Hை)w$ y YBlBB7Tqd,[" n6:ݫX"L$|%vm~7ǛMZ IDATn 2?ͮϓ0]ㅙre.{qi,]խK #C_!Bh.vٛ< f*5xAt "!FV ߼t BxċrA `ݨoǙhQ6Qse c= $J%?πlsO̱\p/߉~ś2O~ʤ&o@{(a+լxP4"kϺy2ۜB(jV'WY2HzX%7*9Ejj7JX9x\צީ-)ʥ$%zæVB]9gl?gLC ç򓻲,8f̛P6DEgP絶`IF3!U'k T{\ Y( VR/ erZɻm0ĽP:ܖnc"2PZZ촇.}q܈~$y,e*{i ,Z%_6ʢrAxhE^l) 7lVJS/܀ޖ=U^k{b=>s8ENty)}۶/43E>źlfN)߹KIN @ՇEu3ɻu,2V=f'R#cַϳ~(z+f}9L.eQE}SڼX~"jXF-Ktd%V>̱|Jo&6 "7hi2 x|m:tΕޣ_-Mpsk 91E{iwv/pZ 'zHg(ɟ vp:V=;Pd#h|"΀i5.hҊ䔚'n*GI rMl(i_G)IO؇&!KYNb:fޮl=5fOf?óm{ qad9#OKYZUUL?0&DIX5T%UJI&*WGc<7GWC`E743~bfծ9k?ėS~皾0fwᦿny,ӌ@mUwE"j j(j}Q=S!cCh*-( XL:?ߙS13]cqyE~iNײL o|"$Y>D,DT?^[|cL7QU%FHR">X' dDvG吓9* FHTD^3\7{6#i^Af []7:^[2KP2fx1&mJKE(`XRp !<& @LБMR8\$_6 6:4pyA.Ǧ-7a4@^V3.AҴ)iC5IJ8_C3EcEs&7UZӺ ߐ͋/kϏ}??Y|&hj lPX{aPdb.<( /0!_EzY(= CԚ b= X5*>E }1,V[my`t9fib.SgJtqrZk5w^Z m~0ǯO9wOuN2ŚKs,Oo'9+?| jߪNYkوl6>]'C j_T~z 2D@au ),KnVkm܍\{5,$ O;)^R d-cRI{+bUiU3)WjZ_:XL_⫄!WiKaPkmI5;_ZYY@?Ÿ=S[jF-&gOLKEC69* OTO% "KN<%/*)"*C.=k1ܻWj:z5xJ' WrÏvSx'OU)O{pṞ+p P6OsG["5F~Ul:Ӆve~+u`sg 2%K+@dOmM0xDF]?i߾KHa; 'y.EUMa'`0}!ma;+sʎMF4b;j4));Dv_}{:}[lE]gpZ(*>29==L"Tq`qv<])fJAKƠ"1)]+(AjO㨔P:![\G 4qf QJ {kbbaSjW}¨4+NZhaR!"I{{Yx?An\X99U7Ť1G "a`MvE㏬Qiz׼pkf>ȷTr<ݲהOl>J2wۼc1U!}da<(-SEɵĉ~6e8,[ȶgϔ] DzԦf-ĄWעѱlg>GA) O٭qiAQlNQyG6lShԯPŋeMI*ޚyuN*Ƣ)4Ԥ~!Z[t6wAbĝcu~x^A~j4JW|Ա/m4@;|)w8GEC*e (!w"'RuwiIJrvF6'~ͮsZGu "Z O@;JqҊ&HqX;F%8~RV!Wz(#]#o06 7ùƒ7X:O鋅DR%/`eeC7պ0 x)1j&pemM03ZmATQbUD t^`p ZsrPV^:OąC[R]m-jc}N;eΞK{XM#=y -y=s[\&MKW;|"E?Xgvne]AqGFDl %O9*]EH epԢ)ܫAx(ԏX5z'Jmd[U>HɣP^Tًcp| ^E'A'yxm { ]uZLC3j_;V[ bZ4r W7&y)]mTH9>©v>]srrk[UIG&57Q<]XJ'?u?,cw"z 턾i)tvZ+Q0}~2](,ݼK-aibCcCgup҃EFI84Wμ Vx31(Apbt~)En dV#,VT!G#K; $0NMfXݟY@/j!@6_ʒM "DwN0rDt.پeߤVLsL/2L(;1}Xٿ[ {.톞#j뽱T4 ЮCB  (o`р^ƍp yR#Hr1N l /c_¢H_+h]67Kr\,hҭ/:ukˊ=l fwq w'\Ox<ujZ7ŠPq@ŒNqE|^}Bvtr aDgׂH7z+[> )<wOLzq:FrZW#"_DrHWEZo{YpYI }G9{V'K*GLڍw͔ @PN ?MzHaWQ,Lb)2Qpyl0pg[mWZ Gd1_)2D*OZ4loyq+H|W[m+řq0 O%XSRcÔ_ΐRUũQ\AǔS9Od2Ÿ<_VP-v$~ₔ7q9G~N#`=6R{jO#Ciy="q$xoI$Iέo|V@dqbg 2Jll,9xL 0Ks)ٝ/Q&Bv iW'zBhRtZp2ye <|%,KDp+:Ϙ' n.ɇczISR<0&ߜg>̉Fa2EA C'#Cfh͛Z;1y͋79֒˘OeFU }&GX& J4)!˓_ " Y56. oLdȳZg}4pagR{F0ozX0?т%Fe^@h_W !DRvhijylhn9H )/ű[Xri!Q/Lz֛1ҹp M\2X ֐ 5g(3Ǝ74*.wʶA)v cNKq)?e$-#N ]0N9|7 9c`)m0Ә mG:Qi<t2&b=MJO{3p!LIu"exsNiZVc!?Yɬ)* _*+5g9M1,t s);}%1^&.5zz?0nT!A@A Gҏѥl b9D ,ϣ|r}*'UgiV coDc:e"^YqϦLKp8`3?ь Ӓ3/U?Y^m:[ "5;yu9r45);+AW-z)Ny<5CQ B7l.@fX L(3K IDAT' o6o_AB_adێM=mPBc}Yî E>w0,k2$s,,+Ngb9W> ~X;"4e-ިT|&i5~@pgdJ>paBLyEywCw)X*;Kuj$K119r*.BCVs9܂" Efa'1YOa+FUVeaqTf[UzBQ d5d?L+{bU܅cwk."Os\DnM8jVEŽ(*=R3tX= Kd R%JFn LXOS %|GnD@de]$@l :=1>ּ<:rB_Nfvt 3`0oo_ۉziq>L&z8GNpwWs?W[!螆:)WU"JuӸf@x/B !zX|J5pޝg#zkn˪,dȡRQ׷jT:M⯣u?/iSI>9cՍP$Çڑl暐 oC)Tp) IS@1MT+WԴRí{+k_*ʢ }jmIƵ%3bWNKiGN਒ZeB1L6?6W~ CJ`&pC 䢏k0*s}_!lN/@iŭ|YSMztiDQeUr\''*6EaW*-;NCrtj{5OrЄ6Zm4' ޼2@ofoڜ.ܕe>j-Rj7R=ivX*+ĵC*ެxBls$_2?nmkp>G_)n녿9l_a?);Q3 hG7qU:R/d^O3VЄ˲_|~dҼIެOc\"<+T.xhŢ< ғ/6 `qX#\MљTOMl&k}N='փe+*@"0En _ ,6=ow!E@öV&g:8"Vs[/J=ۏYNޑ!tUGBp*,|2 zCȣ'/e%S xrq1X~t >Om(9YwF(] E0 AK3`,R~gDooDǸ蚄sV9 o;]Pe= pWך̵Q_7K@&[p}x.e"I`U)[Ef :KP?t, ѽ15%Ǟ¾++% C9诂7Y,ZԍOWk'7idM~@`S Raߨ(fW1û^!~1@բ\9߲~l^NEDa h`W_ aSsޡTu_e#E`[k~T ɫf?-:xɛs]\.y ]|V=7}q_,# FP?in꜇sehA`vLԻ+ m\ʻbҧهsR~!fj!++D UCE'cS!}F${)2y5L pg(W:9(vƘ w?${8^#8~cScI<=s;|oX"{_><@4tın\ڨo y_2.G-̀ MH3/$hKΕ\wk) GqkXU"IB) Ɋ,nQNF֟GTe[RFhǂlL <*zsjfunm=;yf"7|K;~2U!69x;`̱xCBhM@yR%H8zn$Yu%NP1gvD(f@u02uʏp#YmpDH&Bcਟv>^k1% /7 *QHdLe߰DA׮NސZcuQ^3D~on]zoۉocH y4ty:&hrJ77T ê0(_mkf9CëfG%G~fTm$1٣xt@ oEm4|wIES{[ '%@GXkD x=7ki%m )dܞkV[hn ~B-EwT[P'&DDKsio=VB#:LKI/;X%Ntg!M1);r8S2gJΊ%J5i~e'8=^>M:;_v֧˧GYVCe^!,mj3×(aMt~.!\> \B"֚4 |uDgfs ",ÜEE&5B{MlW!'䱞S}ԙU$92Z-)}o'syC){1isUW#L6U4hBޱ4#LpsUo)QYFUW_jE;dWOtDhugjn'8@9ԯjszݵmjkD0<&jar.(/cmm(ac-R JN.m-Қp`%D t_+OPr&*dXv_u@v5hYLzMLDDqԢu+.0IŸFrjk'2ܐoLN[mI'"lNUObPX7a 0KtmW7l+JۥES[Z/z6SsbFmEJ/%C;g$@RSm W+5V<2T,`*C{ :$ELѨ\(!"'֨Nj 7_dݕe*99*oKa $3 VCQjPaHڔ>Rc=\1L?R^6l' Zƕ"P. I EU"Td|n;<r"{Jx<K~SJGeLG Y <<*b2r@vn!L%+OJ}Ŗ{s9k&#$/u> O@u,9pSL1kg%V0.2O\f"1 _bD.4 gehcu 8jjsL&/7ٜ!NR*"< $qH7̒:u9~wT3[}}Et+ 5{YUrA 'շ7"]SۈIeL&AmŢ/`V*4iiHoR'*c=ڪw#zq~w 6 ƺxn@-CpM͐}Q*X<,hV=j IRz|ul#z具i˷yyc`n6.Ya[ՠ߹>?*QмtԚh'۱f$ ΘWVfξDS"hZ(k@kOdeN3R#_jT6pw}f#,nGxx_??o7R@5v uz}?OZy9tę%6_7p=@VB3e'^c#B|ŧ0ժnoJnbzC.֪:n[3"9C(_c腙>|Z&8Ag)^ *_:ˬж[}""GEtӲo]pW[ *gh2rp޶̱S+ L!7[Õ Cf\u `*Hhyk~ږrF.P2Ԏ}Q\VKtY!J0?x==$S$]J&D>v`Ic8a6PE} ϐ4,JwR/<*Du~c:؈_z~f@s!./2`9 U2v6 6.xt}~[m͌뼍ᒣ`m!{ǎؿY d?"X"7捌~f<>"lʳ#褬O1DN8=l2kۃ[? a}ڼ+LޱOBcxyK_63ex _B<<W.6`䁄O' b7 /GnVUMz !qF@T<`d`*Uh.4{d  ! Y$?<* `??=Fe݁V'ƀ@- "-Ͱ?l۽UDUc԰)7,J?sQdղFv'ZZAl,M\Yڱ9T*h4*Hs) j>RB4Ttj$}Te]}RU"JMEw꨹iO֔CGнٺ5joc{1kՑ*KX͕ kc) j 6FYWER&(݅[OMUK>EYڽ糀9QT)jؠ^xdz"|0f6_f֡ZA{8ߌ5R/Szbřc(o2D\ CGn N)=nBK""d6Ϣ҇(؂GQ:vEM9>˨~:Yj*_e$*}s0]`/jޚ'(< i\UlkE@>HO,s󹠅+}"}xM, rrpBɸrAGDmN%+r5+O6L-^UzwInmw&Fst7LSnW"n:7Ge6jO Sݕ%/U_foE8",.NCHSD4?h1ѽJ̤9LՉCG6Aba7KGHRfʬ83\]MlUVINinh9N@5`}QУU+Qy!jTMj٥dIx=-S|mzklB?#;HC3$'19Єd\]il#CkYvtdxV[m7ѕm2NrmrwziVJw:PsG1v=/9:Xx^8_taƴ[kI~Zo&wf8|jt"3͝$;QkS#QL+|a3wlXOr_C¤8i4-ySp@|8 bcnې 3LweLvw$_eE c!+bz.il* ޸CT";5IVҷxJtR&*Kr`uўFg<*TOs}@:nuek_xG"ԅgn5629Ɉڏ1SUÓ*OO(cXgLNhcˢAyV'>l^=~(N!Ucb5t.XzV??%L1K9ֿj99%vt8ȥ8_E|ߢڤȥ+G7U!tEjJH.ko;.2p]a{[-hamVPi8mCϡ^YyA;D~y*M) պ#fzCGֺUK1[ٜYDDVJlnq$~BD膵6q߇й<8/OB_Eh>Xy)uʤ6w7X_%+K>H/e~X; v:缰mg|YrS+B:]@ȇ~kx:]7gqc9ז6"G5PiRf4I Ë :H~vVCqT o;|#<9Acj4Ȧ%P5 PQ35aNj,1:= pˉg&Oȃ{syРaRZJ\s-VZlU5ǣYu06p3ZZV(+OOGx; _{Q*r{Qt?Vy; ~ۓs,c͢&Rs9Y'E2Ӥ[ ;ٓ+,O?H(s$W׹wJfY]"ARL!-M?s7IMVDm. sAt=CW'(&_g"*YeoэmĖ+ݮ(MI%Qz!,RaJStPDj.`ްj-;hQsYd?R^jmi}M)G6EP]{ M0=U6ȋ'2<#@C6mNAxl?tVUoDO|Co-}N Bs7y{n),I/ No !7? hFp6\󳼔gY-*jz̸ l6k/95,ͮd!˱ lSJrv؅:1܄ QSڋ6wZ7(5f_эT6&ǨQB *-_S~r&i3gvݿCgd[^F&|;=5*`\.W6L춅gg'57PUCWm뎒E 1hJ5scZ[*`/Dܰiњ֓7|رgn; XeeO-ldC,!{$U{@D--w0S+1BSׄ4a7!@׏|GvOF$<^- ww`E{'Yi5Vi"_c6yXr`b:4Js"Ám,0T),[|)b6KKa dJ.Gr`sax$ |㴽z2k׌p겍,B᩸|Yқ[(4+07=76Iok IɲE^J -yt u۾M,b ?(:ݧGT) v7 &RZD!b@EDD@aWB/Ŀ`{ec!k?=a_"]|A_LVaT#13 <׶̥4YHI$Ҧ~2ljpeY4өa]Y$8&MxW2$wuvbUP1o6@LBHA80Gp+-y[j+} V*^Vq~P16`-p֫4s${㸂e@wG{JF΢:Uz2RO%弝h:r/̏Q`5V$bkoTDtTakQgr;ಶWlidZre\w0>wkʨ7=/ػUeG A+I7z*w'jE8Ɍ6AKo`sMR?]njaew֬G;mܼ};4)3jd*_(v_1{BqϤUY,v\k^ )l{.*\]ZCHyhtF;WYXjhfR󱌃d".Ij{5=eBkZ!]1EEٕrQK`k+nPcKl `FӢ,׍7w r72Vyq)?Zr1s͔ζtrb%鋚(!5 Z:9R* \gxͲ#,Su9k+[KK66}Hɩ:9ץGWy#0g㐬j=6Pyr*G{̾}M(۸ւl[-9W|PQm*wӥO=J)w \1V]rnIQs%?6m{lvy/b+{oT&U,t6tM{Xc dB?cB!/+,)}*8$0NP~?rAƼ#6SxTV[*} 3JثG _(=G/CJûZB{d{6? *aa\}2 |5B6;&,‰<."Ϸ OTY]HR!$fAYA#/ XzK Vdsy&۠>4I"vocm2lɑmV O|`ŏnɋ/۽QGI' 䱙 w{vu/蓐  ^U7c $cJVX÷t*!_\l@s"b޳m};V L򜟶<5/:h)hr<!>9R)bf\A>֞6|\N#pad/Ya*gn  $ ;}lc/9p^\"z9/NGy}HPuU:uV=G>F|Ҵ˄+C)j2 #F-_N3Ztí*1?"m:g3d-يNoJYǒ+*mt~ 7"t̀ǔ3c0H_ bs&E *pͬD'yEtF+:6U UޗFmA/MIdӰWyf./]%M 8]MqN'nI(~Hzfڤ1Q6J46Q RFgG?]QevwZFUk6,OPnuwRgi~6iHsėdwTLB7dNPp` VFZQRz5<:6M) +lDgsx'+f>EQ~*҅xTmNSoȄ/-VkKD[p?[TGNVh)^XEr7 (U`H`oq ²F8־zR9bz"bJlz!r*S,)!l($hOп1iW1 ٮJ_fCz2ps /m 5|Qm~>szmI-i(Tʗ>Sح0dD@& {?8BOKemwu<65kU-*fܡ'*Ab_l3| 6X ZU,E>,bAs& Cgl1u' >&ߢX(%agX118M*?o~g25iˢ\Z^ue{1}^CZh IDAT 2zJ#X({>inh$2xQuި"8k_NbɔF F[[CIH:v4@{.\8Bk-"Z-=5=dkz[zCۏ+!JʡOBϹ%&%$&>gQٕ:7Ez)vCXQ${!SO<*cdƫ]4ט7.*XJaH8O 8}~Te!?cA{Lg<.\('(6@sVBVIadF)CX0!p 6w59UkcZ;X|[]2̘扝qY,d R/9I]}'ڏ+#.S^{(54G!d{=:tdc5H&6 vnU4F@Q2<'Gg 1/y2>Vr tlW4#_Ci>mնO#|ue6V[ CXtV5K=d5#Q -wWD_\¼w'.qsm˨k 0ն185IYqԘMt-.uYmզj灥Xuz@՞=עRj 80s*z8Ĩ) )k|?rr5jEJ.o;J:=/c HRCfj9-SpҜMdzIx΄3̬?- k ^I_$~5 7E[tE9@f D,5@˰VJQYmzxXEq_4y@xpEדֻL[\ĪezHtMɦ$5 4n0;w,1r)'ЧcM|Nu~|7EU2Ruj]ch^If̖4; lL)aU.rJO8(SXN*: ,#(,m*QpB;6;EcMQB^oРx[ `^TL_ h>WxҸ3Qr ~C p7oYStG5I0fCm6'!kij}g/oÎ =Ggαoo SR:үH@QSy0ɚCV3pr5SŪ9JP4iUZ6s~DQ#eۼGy*R۫gC"2pkT'jל=j*rr %q`= {]갉|ߦYJņ~lՖ4O)?4zLUc=3C MaJJ_s?öeFŒ/%b` w9>E7k3C|z/aXR_ 4BmOp~Y~wɨOUpxC>^Uڑ*ao',&x4߰W3| :UFA抒YTY!9rLBD:IN,zY _#VȐpTN :W2v@I)}[O@/l+aȡ;L<tɦ SJډUp7nOn+ac.rivY !pE!; 8jMrrn9j9D[nWq,+|%!kY 5IzȪ T`QfL1?E }7]8hy3q< ]HqR:O#OUN,\U [};5'xo|c*wf͇XI+t ІU'eTLM%Rb5 /?>|!L'b ..ƞ"re'6@=ot`M` 9~/DD'镦_x39y<\F]SMPS)Q>ײ+lArr,q;靚{"fJ gCţ\ FoX75奰x@bJ;iZNl"E`&U1,2$hp_N11O_%/Z 8g+<֠n²9yH4t'gB4!zݕyYX#/+[c_e RWj)NzԄeC4L2t'mD$9a XB*wtHQD,b^ . s|xEK?&`4n{\Ha|0Seuu9Ic\W5}:kvsqi~-1Z@EJeQO1~t1T>e^;evR7x [Cy0`!wtJW;;v}.#蜍OcaЄ$4y/=ԷT_ٙ}QAy˵./"nmI]ǣQ߲@r{ tK7UU?Tz*|$.? !BpzMҜ_wS^9JEp3#ˆ)k Wb9T[m&>KϷn΁$<ȺBX Ͽ&o v|M)`N8y1E~̝UY$~b6$F^lBl6nԣ~ /)a ]9P}؜.hPTW˃E6!,迸㑫M]Uüg6I%ip#uۓNaKL,{ $^sޙǟ6Cɷ YyV!*r9}?U" ],*x ZH3JlR{PCp3l/,@=}r Dz T@Dؕa zUJ5!@C6f&m,֎Л^HhE_!.J>m u2QEA><0?8Awĺ&˖@[NN a6l_ gR闅F^GQIxy]cu~ڥW5grC-9BvUUg[;GyKc@-u/ s&Z c X2VC7*;WyV^2EFW,xjrMH6+5u^ ض(2 O:=շ]aϴǞU{6aT. L.3Cy'LG d}>Ї|6Di?CLwR:8l1?ȶָg4[Rț \bSHGzV¥zdYHLAcj~A#aVtR:T)ߒ9"sQw ;*f Z#:6NtTW KOJb0ڔl uoOcS Tѥ,3_NS;"}#i}G8k&pU@]~M1+~liWDφTjr=Jٰ}KY.4)2LLG;Oi [&$o.*C.@+S6z34 37Nt^2UE/rx>'uދb6NvVjۆx>, ^%{Ļ@mQqiy7d:*!|2onjKDڃϵ ƽRYod&" Y*h(shQd&U Ю |V)zKQ@-wGYY@EȅNr:a#̮tTf{78hhikcf o;KKes*ߢNyĦ&5ǨU|&nȻ;-JcHY"~A`}: V1P TC,%6fo/0JăQ;fqj\߳j;<~){&H_%(fWJVWh߳lNo0ݶX>SAϹYb:ҕJ6z!UVC݊ a!ٛ8F9qǾB+(~C|(ӳ<-%ps0GRf,+pO~3l7l$\~YaftiHKC},q]`RD>r*ZӼp{$&˼ϣPyxO9Ѥo<9Q ֒>OhD|QR:;8m l,E&%!6}™q:\og"=]:q"_} 0 Y?r~BS,"ݧ0 v!Ǵrfu_+}TV&}E'VNuu?) s|'XcIʩB@hq]|-@Ʀji_e1vk-"3>G>pN`#tpμ w~ fc`f{ȷl]I"TLE_=7Pl5A`*dDSkoP%Pc \r5G/=C 1IGTRK6-乽:ۡSr|Q ~dgñV+"bbџpTfl/R$|BDvfn'A#zˣʜp}T_9GXLJ=dƶGe=>] bV1^$*ILE)1{L, ۏch#j8i\9vϑrts_-ӝjxvͳzjٽY2lw%K:ύl(AUþ]k哋.^~n)+#^ 7*Ƙ Z8A$c)9:,Pn,q=K"Wr6PBR,K9-3qm̃qlFxa\H2edSՓ&g=tCť#eۼNm?vۅCb-*8-ٚ @{IaY YZ sMIj7"gYY49hc肟KѽR{ޚ5Mch^&W\(V 7nڃ<[)^.4c.(/wT2wг=Rulِ^)ѽPB>I-f((5{Py]t3yd\A"HY_<~<23MhM^Jt:f0XoQەE)ؘiY$\$flxOKqғV]Jy¢:p1m \~<:1ϹqN5IJ%M]&Ɩ?Âgm8&Sw} ZurgSRzl\#b`iJ8z %J(=Ʋ(rzV"բhR2\ Oz לIU9ޱhVfY: H{1i\$C|A0ȢaԱ6srz,8~BV"2d[>.c@?|lӕe!b_+ N5uBkZ2J^hzd&J5smL?mپx[8u`JR2߇OI T\ @Y4`jݻGUO&?}] Ke B}+y)b-o?\R"P'.i6T%ɈI!_.Cy6{.q^ZV{)9;s]Rl,\S+8JBF8b`Lup̝>a#=ɐX0|Qh[P52;J@p \"p-fPSAE) N{D?T(ɳ;y*Џ$@ *sD.~[9| BZ= +Ki^$e(3y;.@]c4{ΝTrwDCI>xR{N+}Π!K )E!Y_e.b]釖h] pAPL?!f՘6Son7\*Qmˈ>fML"+o6{NnDu(j%%"%G'*c3Z+do1o#gSU!JM"B':ps]@fSY^qwO]J;fzP̕zi,N7Y+q,A ʜ~TOC n,5F͐],zu7X+ս$_߹ي%frI5:V[?Ulp(#],ji we&h,Ԡj\礚+꒿3FJ|"_yaq6CKY_N"1vY_PekFw|+_ێ6d^F۫;Z+ҋSE]hf,[Dd0*\(YIMicqL?l7_y'-8Yq,۫] %{VLD ͂l/;q^$te/"}-M7(E$P=hd~2~Z @a]n'"O uYIL1 Sꯕ4~VJ7/ީi*%; 3ef lRwrC?iV\/x?:4,:9pv*pH^)6cqSmդkDuP>IJGCy<Ѣ¶C@%%d…;h[4VNe;J~ ՁxFsFS!Y>谘U"\XAIN.>\z{ԉ;7 %@b "Z 2;[afp=6VpR*J*.U*48@j X=$ #D_a)aKA%+El<Sւ'ilZQ즣`Q0""Npj3è u\ zH 3נ)vw'~vykܼ$|L~_qiitQ4A* WYܓ%kP V`5XK8uAX'ZfyUӳ-r$m.1]d+ ॠ]&6BhԊS6YyVʏKP~, u%ݢ,mqe@#(vଽ 9l\4?@zS{K#'6 V4lK 5BJ7{ea,4 ơc} ]_|y'Үpp-rvh*RN޷\Tf$d 4&xJ r5~Z,3#%yuK7*Lzۖw {4tt[n'ԃ]*JJTtt{Q^r| cm{t |i7eJتzĄ|)E3YPC?Ip7 !;6/XoZ}NB޼ qkn8zJd:W1P:qg@pi'U<6TT1 lWZqM5,1ʙg%r+A D!9cx4{C7^bz[sdg6KnumeJApoH,ʠu&;~!Tpe$sܿ ,eaK''VlY+t%v!bXVĹlPO(k%U1T+MY-g3,W2Mz/{qc*XlAyًaYGHxkg.8m8hh_$8循YA?xT:m=<]a4æel):ţb_Ep(ͣbٙ$vU@2)j}+);IȲ8ճ̓g] <*_Sre'V.*zDfR[]2F* ޟ:epȴW֦^$[fcXޘw`AXВ12we4=*xkM~eEpgdf. WHdc~} r'c AH`Z4cN{s@ ϻ{r D7NdKDrD9zp{bCQ|ڰYU֦5y -sֺT\=9\o$syNGev}Ta:rZfw;f,esBo]<@cσb@ȸzWlۺ"M w: JYe;T]x~Q=by_ ,1CPZ΃_mqzCW%Gjfctm @Xr cUUdLto!d)Z -?@b~q4P5af,h7qM%+C< :`b)Ke90˽OL诙h+mJ5fb,}E ekAꁎ9T1۩ D&QۼvOitNve[tTPY-K \qڲdȟ*G9R' &WX"vfӖq}RDc[0/ kgHoF#nLG_NEYX)<.e rfA,Ksxϭo+8z鏀cǭA^LsDoB? 0͂8Q~Pl}W݅I>RC:6[;2:vR ^ uqv*`Qy֒7 qY3Zm L2KLQ",B|)PAcPD3mD!J'3^^v{{]]__ʷj2EynQ PPOBr\ 9Lh.G;WT?zHRP{-z*bDT ɑmM,ER|CR[2mpgom3+UNхh%!1JBl xg̲vD,5@‹ogDdb :ɤcUv l ߅bg+5,b\| X܅;?F̏3)'M,b7o`*BV\Qo]?7y[`!@:md^: dE"$Ķ}%9*ޒfig]"\&QD?? ]aϾQ nJ+$gSm0'DQoݨA"GkFP {~({mX}w*Ge1:ޡ Ur@%>\GU\ٻ)$EfFRLc/L{l*FW|e(!*]{g3Qi-~J20TJE[1L0V$ev[3,`Pzڤ^ j͏C3pz)HOlj4wW_U5-g(J4! j[@fMP{aŠ,8"jpdmZ8K ԵOWXp}hfs=k?Y[DUZ%7UFi#~!*eϕ}|vRg r "KÕXrvr^]i62^% ,v@ٹTqGP!6Zm}\ߧ Jx?}ʼ{%5W1e7ޏ;0@:^  #&,4eȁ$;E"RT;`7G]| 7qXe70Zhf Ѿ1%Lx1/}8AW* ,c s-۬WY{Cgo* rۼ*b5kXN{ds0u+=3l*W#\AEQ):}>W-cM] , IDAT2S)^ػ?vQ߫  <Σ"setR@!CD8먉X|)E%$Xv۔fOrLo1E֞ՕmcNkLI*$]pZ sWn2娵ZoYm~!:b`W>@8%9_~k bOt LEyw< "]{Ac!2$ a;oBQ@D!]X/I"rgMD N=M̗S%c~%e;, ث@'Ac=6}u0@) Awd3 )0 m?;:7w&6PT鍉b Xv,+98j tTxf{N)-A)߶A&IZD|=KL,)iFB4! EǦo`|zPdwn&wQ*/L5qӦ"s4{.SJN"+XRs" (bR6ۛu ƍLn_ [|5mEB?-Ei, ]J_9Z1NbeWu7մmE\qGDl:V_#zWRzހ @|Y2l Pw5RSh6ykG~ABc!}U(gwR%.[ziT$<]LݸFRAI #} H@Pҽz?9Xf_;[| %E TlhVxM02ΊGn *r\/Fv*{WE~iܭq sOwy GjH]b1,$G iz12JO&iQ6V UϷg/p#=-4OBgSpAf!1q. " zPT:U2P2ɊA]\[;N,;m'YWk ʯ!W51⸱' :AɼT0(w5*/?G̺f-uUFR} ܫ}ocb{c":BHb~"(- iyԥϥu~~4V*YH78N0YSg<*q8+(CDW7߽̳ྊot P6A=Q pRo w[:lѻb:Z1'1%Ƈ"/Do]Y,0S}Ƽ(W 9H V&a<>FyTι5H$Db_)-!w^,˒"FJ ƿ4Khgpd'g !%$ ) c?Y!.;X%Z}S H# !8Hl a-~[-Ru cT')p)R*ŸpOyԼVlӇ wڰW Nx,\k#d/M.C("e B˸3Z㫮{)F=5afmLAvՈڱdSYEe1V5+>> cc".]^Sس=B7& mG6@Wyx-Dž%8]djRb{fY.?%W|틏&0;Am P:k@'V04bbawܩi.6CN ~xP1{h:p' ^ m+kPV> 0;Fa Ѹ>2rحTBvh+.8iπB[Bdf5Y=V~X{?e8D!= #y~[B4BG<H)O2{oZ[?LDK|+!^\T;]9yG'\av)D\,#x<&θ]OL&CM<10\ zVvzFDU̾LIs D+b F=8t:{d)(!! IDsGjD=DHe7 +SJ %@GDB]1 KᓢI"V8'O_!$ ,Nhײ @֟މiOw.ey96!D$U,3j;ќؐVDlsBWC%zT0hZ_gƆq~i H)a%Zn"7lڳ(:բsZ[#RaUL1i3" L=PH'+}tyr5{I`4сƙDD{aub:3SWVJ~ {U etKg[3# ^G=`pl.$  My!Odu\BtaL9!o2Hqi4h^xvܜ(D$qY>N50*Ó+8!cW뱊E kHX\wlNn]K*ygvK_Oш |)t@ KGy{tͨt1'"]F4)HyP] GF`CGԫZo-~ ;KySSi+듎PK3Y>f6r( L(F8rGO4Q}݃d]jH:5\:vFpXy6?ڪ#^@:VհeG Yʉ/bbd_Iji09d}]p=h|-N^o҉hDlZ{YZ~v~oYkQEC5 JUW6(G<ʯ[跔ˇ<B#{H[̿K9@_4g:饅8bBɝ[ +]xpT*z)Ոl}Ae^gҭ(_ KS*N_N V3W|rqYjyhIxvB$3Zz6ھ{.TVuk7 B-j$}?Kg^MB^429/mSh8lz"SJ_5EhR-rv_%+QGKJ#{"_[MQPT|Z~B`p؄(zU42d@NP13E_a׼Y|>5)V+YxMK yFCC}9x9^b6yo9M-9Un]z7C= B!$0ģx ڝOyP=B\hʈwJ`-c+78Ȋ$jAlU }1 R2zi(9e)&cfOWrP71QH[5R[h兩LKIy*]VAj\v-I-GU "trVREmmql}z{y#.w&hddž\U_jtYwpʆeX(`D!LW=-\RyIyrNc`sh(GII2^Tlh)c8coT?]lݿvLl f%bdw  ,Dt8`; Pgwwd`0g)El!udJ;)3]Te_ԟGE*;6gY>˨T!V@6}~&anwO9gݐ~z b_AQ@$DAWg|Q|]nѺ= B BUtKLJN;cF,Q3ql#%V]g9lh|j9P}كuj.T7bbDS'v5Fh5fup) Еb Q:Vjq&E UG;~eEnS Эh(z+>Pdm? sz7p*]3Ϛ%脬ڏYE BFQ2&y>7X 9,jːIZ{.@M޽pj}"+kKO` "ϓ9xƞ:24@ŇlfmDu6:sN/Enuy!.{Ҟ$ əl! dD)DvZm %@UKBz-DwִpIXd-ݒ,38pj661̉e@FGE>7>6[s;p`3f~r^\ Až-4)C?](V/e/ppنT<:Lx56)L ȞӼ(l7kN܀ ad|3סIiOVa lO  "Zx~p\ wK#wa& i3=fAޑI MHҪ.FC2Cl}Kp'6":6=߽݇:j`81ODn,%>X 퍞WY:W lmh8xc ۰M FMW?geeZ So; QNY"af '6֜zbuM S7*śsZ*֬ Sw0$^&L KlY j^06]P 2/Z=+"2-,mP]ȗ8|¨,D,Ώud7<EMp6߲_q؃HdPseu.]PB:}"G?PRoXE+KRa'.k35qU:PY:a1W40Ľ6T).E**bzmX gkUX丵O+vY%uLj_B 7*6eHky>cDzzK~S/?ePډ|G36^ώ=4ދ% zvfˠv9;?s?~ 妽>N]yUjE*]7)_JЗdQюM R#Mmd}45sJoT5ԗ'%[~7*'ܕO'>uN7)X;$|ȃ1&Mi81Bh`kU4BQkÐƞ&+bWRT>H|^ \Qd򢜰YABv]~L[2!R@5*Qm/l/p3e_J_R0E?|f]eUT>^aJumz\e)Hۿ\r [LlmseBKPTH/fYPCeb] 뉔 (#ƿ!x~o~D^uDI*yӬK]x;"2#"2ƬnւQRC̠ a@Zg 8 Gl<@3#Y.B{ЮA.Y1yqD )?T!bj~xt1ۯSPhfB˛ZCKf < "\h?|g p_0fYZ>l Eh Y n8:3r|$: Z*Ɠ%b:-% gTo1e}r ? e^hXBD8opt?*RBZIeE\т# udR%'7nKψ Rx$s _wAf#K|OUjPH)گ\4,?GkT9sݼ.38A(wn2?8~rS{zŬD4XKTyeWq} Z.1s&̺+84.5k;(Մf]7Z;$[2 26TDC@itSk05u%BC X F$,U@_UۗIH/^!%MXxׯKRU E! ҃`2FlonK竐AӇC Ȓ}5Gtيy-eu~1nƶ40 @+ybo? zC p_M[dFR@M5H]uR wW-53eS'E8%wb@xsTfWGqm\TF83+oNȜ^AS荼ZwRBܕɗqAR&_ܪXRݵӋ}UtPJ@39h!r#peXtz"i6[TJ-AFKR xTB#}'eNv钽'"/.u"d`J]m%\msߠvbx"'A#z4p)̨lewG=>~~'{H۫)^Tr.8!idC9~cV!xkV!Eo{4%=R?GJV;I2g͛b|C(3q?Llf+=ٔ?}b=)* y<{oXNx}tW`:XPS ;oJѯtfg6U)%&\lwv)_ZP>EwwF(J)?k7xQ* U/ʮ|0 r7 tEEhȁsC#1fw9DnbyVnZFNCΎ;˞` +g {\,Ѻ2ޞL\}x,G"36|p#G$Q|l, KJtmc;K L+nZ|{+%\;9!V{ĺXzAWt7f /R^Ysthi~r,E;Dk,-C5Nvط)n?Ha4l @B 4)PiQ_e18-T#TEUؠ]{)u -8Uaؒ^G`:j¥b(V!cŃIE3e?R#:;Y\ZƬQ;z ԯ٦#*c Dvi=hޙgEWG ׈}TՎ0 63\sGN997piB̸TJaoϖ0,(V ]ͦ )">fnZRIbL54tTf[ wOSߛT{?ư#XI,}J (O}-ڡ>{czVQfWzBV@leWg#s+.+(N<+JQQiDT5< 67Κj& |4KJ5Y{d!J_f_D#Q;*xq@Ȋ!BuJ?ιFo]NTz_* hXS j&!wmި&Q q溠Nlr𗉜GXEwW%Vv8JS~ndnv9.EVD 'a*B^C+2Ɵo>RzQsK4J4vµ?*p)a$m ZϮ -[ȈA;󬙓jǕ[*AZ?Kw(M).uG$m|> d/̎~nӁЛ.J%keW6*Wf!$VJ4HƑ;Y:HCO]͗;*6mJ_Q8QSrvِw R$~CD\(\-pg7˗fc`?d S8P"bJS95O]ќ#)='À}rTp?>nͽb]7@d1JRR+J?p~#K=j满7JpRrkg%#4?YK=Cƒ~љ,Xsn鸸 ϲo~6-GK!HҤ=0[[F|IK'| :3K#Ֆxf"Yn.`4Ɯ zk"W:!".x ٽɒA4V$ dܰtlV2W-ⲿb<7ӭRC8!sCyk0W d!ˈs(Β9{~7$>oKEu,HKΡekY"3cq3Kxٕ'*T~}Ḁ1rBD@T1ro,; Wm-Zc/xTl}ns^7~FLaf!j=U0{tb3;1=]{t.@L|C c3VᶥfzwD S~Zj6ۻ-3P'7(ѯ)&o̐'wݯ#`X,6ޯDWk]Xf3?ShJnĎ_/Dz`! -/7LmhAݣd%>oi&u7`HJ*g/_~B1)W.is`-bߞ%؉ӮX~IEe=o)!LxfK>B..0k0~O}Dx{ѳw ht_Se!1@(>!ahQmo}ʒ{@Ϫ˧[rτ֐"a,ȚYC>eųlt7-5*h'SKJ ﱉfoX07fLFxtA5o|Dh 3p#6p0A R?%(Υ `7gz1 tΕ/=bje{ BxQFef셴w﷧Jak4H٥+)1 % "3rP%Qc0YlR>Nn-V*fZ}vvELrzE+UIaG?k%| HO~Dl @(c'|!Q7lHc(2XOGr^*fD!N?I/1ȱ~d"XY{<`,Hg)CnI0ڔḕx;GWXAih0k\-],n~|3&qBѫ=.%i '!ED4$k9"0K8,mݫ$D"Z •\1\}pg(^>Bm5WF:%.侲m@lψVT@t " <`}A5r_֒!@@Ky%;gN| m„h !NZ3T/Tfeu!eިMɕwaquG~YyP#&-]ʆl_+8qCQrǮHELzq+"R4醓M45.?]DN HuS727* fleTΘaCfeϿ1B=A]Q*ښ#=Tz4 u9kƷjC]F"}_E%B'sK`ê%QlAVHၑQo7e"m CY, t;,~O5;螘Bcm7oK|3NO )rQGzk)n^ 27x)GDO?giT\{t wkuˋZFxH߫C, RM@*K!|܂XKķOJX@CG0b>\?T}W7 {&䫴(?]oAw9ţİn-&We^14vˣ#B>\H5+~OJWV:Ԙ1U-<~l`3kW7LN|8g+mӒd.`w/&~?ս_ݫcQ*%x 5>֖)/B=ǖB DEQh*BWCɻ#u^AQ{|ۢ5G_t}Vy*Y3;̎SMsrG "ReoQPtutjQrgcQ ۾-ٽ1ԙh<1h6/_a<*B"pAmĘ)foM(,;Gи7G*]$m9~]~hlhsʎMi=aCL[LrXH󅄓wo%DP&ViQ\x틱@ķLEXݡĠ"˸*4 main8 uA%?dHҕ{k5ccj+֢1@^X1h9"Պ<)WDڍMć9*i/l:Qnϥ:ƴ "`8}IIښf!0E]P;LPnXZSLaHĬN.r4B0ZҾN@ э2xStjg_ֈ[ cu >Iwyu+Cg'b-hƖs7-~kΔP[kdU8.0Gt*A샽!c. -"D NŘUb͐s㶵V0k'йO`'#{jTfe^Ĩ˭׮r $sOY8w(B:$tvxZ#P`h1{=ThJAj0e)XƑopNaY%YvF\V>U#8sKsTfm "|tm:0/BҥXY݉:!Q>JChqU75X[f@_SqϮӁ:E+kP݊v3@.-)yAEFgf7}Wm HSo7e94Ԛ ˻'ޮ=rk8jl+9Bdty|=Tor Ndb{_x~| ^tƶOQ$٦ORg.l9ҁ_Ϡ,Uw(PȎ%==84~Xo ,?BF*!0D~0VKL#N~sH&c@@TX^ucu-O.R]Βw./%Z.LNeV'Pu>"42橞_yFƌ޳coEf ba.rxg4OJkZY"Tg [W\qlOd5@2RjMC*+"W&Uԯ WTg"5}WWnQ1t鴑,п!ƿ'▀kEx z3=?}o (yI<_HYxWyi8@9A9QCpyRZEͣp4p`i2v%HH>/l;?1$̰E||N Dh\ˌ*BZXys,%#@Kr5K!/ؗ&dT7&>܄!\E{=n"F KJ{,z dIVd,BD?hl7N_.Ǵ(qM'qxk< ǔ ٫hxL3 mh֧d  F2-A$0;CxB0!x`c:<.F%c@` `Xa pИs 䘊#G.uq0\U\.>WYב"6 SLtg#QvuDg@m6ŀ[Z?>c?^:/TUK~oW[*o3)E, ZʥT Byǀ=W~i5]*VR6%\telǙ0۠Scw!MdqFSiWD\U[LMh?\<͗J5&2u'inc=x *j|K8Oa:7WrRЃ?WwJH1=T%L3h7W[kRf =4(znNJ}cڷ!#= +qRFY`"{Pk d_("{f@"N[L AnQR݊{wQ1 IDAT 0JcSEHF RAn,+=-R)Z 0dxwZ|]WYjpVd ,HN Y=z7$05De#A-\cvT2[< jK,+`R<8@Oe~wˠ!+c>6PףӲ#od ΒvRJҋR:WSjPs_5˥*l4WST(%&2Oa?{KAk+YEOsT2[w9W^Y]G*`Reґm~vUWT+?Yg5:;l4wztr۱.H?V\q)u.+jڱR-EWH8H b&f ۔}讒Gx0Gez)S#샳ԑE+{k*22D6{;r)[F(^5 (Eif[o"/(N\"dsW+K+:+]| i8AC8JTχ0M)o2$X^0C>_AT-v|Gźu_(O{MYlpcC!} /G$UaNQ?ŧ` o1c6l'Q#%B`Ƞv'(}gnU8:F8b- H8=t|ZߎEd ,Fdy*! c%@v љsveѳ8ż2Z Bپԉv2b"e[* ɻm 0t%-W!k`!ȔZ~D>d0h^f lexi|FLD gfܓɶX\gc. ?2z kfOv/}˾ҁؾ-v{vDgrm&% N[b{$P]Y<*0`|N|j0 * ~RA$|QKte{l4f|u/:2Q)k"V` 'yѷZ2@H px~I N3TnoX~<3'@.He 6,J~lZQ\c{:*d}UUn˷>gՄ kY2]#1puEjHc:率'GbZ|6U7+w]hH SlKwS[֛<:f{଎6ݕkYsBFg#ܧTH+b=C|p%gJ;d\QL W{x_8K͛qp?#|LY.>/`ہ:lR߳~*|aWxQ0oq4f{$3v*T LRb^6=jQs 2|iRd.C,2_vaiG\UQDޚU0:?qk &ɂZj Kl8tyQwK~`a8H<8Z (˜C|S_\'ȁ2^(n2kvE1PUQl?;N)#.\DfH =1,^z  ^}f{R_&y=;.³jDz"FMtĝm@t!1Q탒ODd/AuJ!!م3$f?P}:-vMB\WCdhEo>l,JZ]Q=t<>nZƺ?n˞ړ|>=j]^i "8&"JȖd zǻWȠcTN5] l+720$6>-Z<荶1+J}X_T֊A-Q F@bfH3FswkxZA|@wKr,ɮ|)~(-K N+ EStB  .wA Zݒ"Y M(cDtR.6es`9v;sO {iM[OS.V_bw5K3 ;l.Xc=k?>*Ԡ& o]I_lqA6vGs{"rdGY]P:ٯ]2l[ 2yި(o|^ˀ*g#Hqr*JRE(A, xӢŞ,@EdF/ ϋ/! y$e6ۭw[A y.`}vE:Z@Zʫ!u!B'tѓ \>! A *ؐzPZ&RԶhB 5HJ֖Uj-0B͒O}80qV?fr/86 bSwmI|7\Nm<ϚdDp&\|߷K `ʹsr N*K%\SՙUYYcкsAs x !aBRuV8{1J_,>m{%|)xϞO$㇗8A?cӠPO$1+*oS19cPWgmGumaI>q6'c){WWe|!V Q*D˜;lO躽N{*]P~1B>$}sQZksD{P6V-ÂN46W\tyT ܋exv)<Ub>nH?KQF\"kOb_;H (^J xD2tdCʡk+aB"+'3.XnA Os$Yfhytܛ ڟl`w/3&#KUd)1+ѳ$oqh5&J|w&ePȚQ7. R)_b"բ翉nTL~Eӈtπ,LR?nJGU{_Vn_?haK6\yM<Ҟ!Yx?/oSz .sJ3߾#%o$b &&WLau)CSnqc1yD?7x6JӐ|QO`K?SyMսM~~.|7KaUsG xEq p%vn@s}JގVJ5AeHidS+-cJ,b1y͏qsU7oZ:8VdtSʟ)t:jܼ}BGJwG򕎜%M d70OgH^囇Ӻsqu70 ˙-sGrʒo MQJ7<'>X+rl^\?qfnȵ^%4j:bW@\tѱObmgxRL>SV NAø>%LԅxTܦ`MǽԵfh(L=J,(JJH/GduT1 ""&DȂBB,jb:gaV7nX5O}u_ڪQ,5Y࣪V4>swTtD/mũ"I[%FH8`Xa-# z^$.Kwəy E{e̾ECPbz3*3O80E䗱èY8z rsi[)+S V4/! l%P#- yK[UvbfQ-ϋM>Ll.: U0lj;|7wsRX xazwXᜫ_&_* /sJ7BuV? uTE(X3r;)Ƙ ~j$D"3~ZIXF\G|U8 "Y:IH6zn ӿ"֯#'5!wTOCtۣI:nɛܬR2mBnLyx5Ϫ xX}˞|]RK=[)1{9g|o@#yƵLDj'J6T̔[{}n^9X.ѭ̘풒v"<,gv]HmO[X5ɡ$si-<}&Z>>όJmj(,GʎgoW|,HZ?̭?-#^Ur) o:KbΤIr1C%&@~ b? ӄy9E{lI.eܣod_igL,ZuOhf'AZʫ?P|T(,=V^d*h]_0iIn$ĭ(*&& ÏxXubBw&XeKwr~5Q]"Qj3LTZ9ݤǖXax2;gv,Ȥ2qc&@+4k:G=.K}R{RbINlϜȨ<FV/;6Ss-iZ7\,*->>;hRVǔW9qJswW0!Ă0;.$1\&*&&7Q~Yqvƕ +t恭|Ml$w.8TC UcԁBzB#\=f8՝UsWVHI;S7_ ]ww8tB^,PP 2K94Х?v5'+ckG L:Wر7Q0|N+?6&s1 uN<$ZRu{cE,?=ι={ M׏_ՁQ6KHJ%M0IluAv-}Vʦg{ t<#)D~93;`@sq5A zKtQY(MD YLJx }EAۀx$V}K= 2dž&@vffvZ}MxQJُp Xyo8ρn3-()F%dpo'a? -dɲ_ef@i[GOюPɓ"2>o+ayZo%-%Qռy&"`,0 "zXYi_;C^1^qۘ6D2ODŸ߻S8︿"31yΪǼD)NvLq6n[tW*آb$>Z?u-{ߕShE[x(^{m~|(WKrj؊k壉ɓ^ݔї3vxoV9+8O]WQ蝒dcQuhW:QM92Ѕbye)%U @:b~QyrZS_O# tɅmkexrb"9= p|6PJ0MB >PY!zyQk~QtAJXyxib+~練UFzeH֪2r2|WǡBƙftAr]Li}JE׃M}xS9{%i=(GsJO<̹g%:qPCmh2/k l}R󋎣htuM# IDAT^N&'FɗdnbRIik.+K\~ACEuiTIyY|v4j<颕\ j|s~EWƜx w0!< P^^4Tqԝr W)%PGBED|ݳ+}Cms/+W wϸ~hev=a2E{iDJ)T=UBʌCK&x◂}IB ߓF"GWȦC@L[xeP&S9e}fȐ^ 99h6-`  h孇}Izx}3L[Y:kd^0 J41RRMTA kc7ۖ2.Wk]$TՊ2YP#:o1|qg1c`pՊC=}(k/SO 4iΑ: j=EvVcU)I^Wblj/Irx3edZJR?tRqtKmTvnCvv[y]<`[kǸ &sR^:_[\Î v}=-bxX:.\Ԭv~off.v &v59Ȫ7V@4 ts,!2ev2M.+^}ifx,ɇiB=K|xl0}wƟ$i#< _ImqkC4!)!CӷE1Nɺ[Z3@r̉u#>Fv!q/XXل_G˭:oFŭV`f 2q T"?k͆M@s)Tc=$ _ pVT ϝڴT^bd\U@ic1ذKꞇYJ*>g9 < ?3[8oaryX-^5Z<"+'J%?x6+cj[+{%A@|[O1ڴ@Ģ^^4bE]5y);!4)):LŖ#Ð,M弣 K/<)x uݷ H9>}q,(,P11#K11GOnHOԀ [{~LW` XQf~ _#BĤд <)U%ԁ`J7fgG#oGZ*%Qq*o \N@'GJ:TLN %#cɩ/Cre+ȷMhIb((i` NJM̥31y{/+fPf;LS5дz/!t`~ jS< a"ISBÒp44?J+gIGU2i[ZgH˰["ƕ/)32x*Mb )} ks؅SFs Oph0:dx.iUX9e7$qc_N@>2G_pHUߢ$8$ޯJGE/WJ_( I<5@1* ֺU]k1 `8=JfWf@BgMlSFH_?a<myTˆ{/hǜ\ś BD*dс@ [eHzRO<hEYq !"Gt1/<$KߔYj+vg,[7h ؆CZz~*{]^:uw5#wGtytC@DV.,pGCrurδ;h?(X~`-\(PTs6C ]̭g(đOߋI K<Ȁ"hq9ȱ#f~MbVM)UL< k>ڤXŰÙN{ +LPTIXKx>J ^^DO +Ⅳ RUrFÛ l<6&yCGhS!>I9glL^5 s1?Sb/5alwxeMI\t)u<8]ptui bO:ra{AfeGM;bjBDvc ' a@MEjj ;FDA f\+9Ki(D%ͅviS 9R=*1#nLJr+#V9mH 3nv)'/F@* lt,,|GRXhw@r"@c^Ub0]i.!D59硷Ľ`y&"Bq@M`o9wK˭Yqww>{y19sm=ݱð3ꩀa:2Xy?y M>oTۮ8ΧDtFV⹻v R^F"Fׇb+xۡugpϋvULM_=W:SG}^pS79%`FLiumѨ zGM>d^ENƜ (ɓq\ZHꈋ ``CVHOgdgԼ=-WHͳ@6%nNJ&&:qo])I&C{?k-[? (=JeqOIׅ9 }v8~/.)En%0W39yv{1a,Mչ|;@[ێ&K˧~^n]󫗁e|v3M,\1ɼ" 3{lEo{w:|9 +ZE !Z\@u4SEmHX%kNiCfB5sӀWh^p-0o-n̊O[o)HoPydZ2h^f`ZZ/wf^DZ w>2LQĿqx0˽Ln k,' 7U` D;CEa&ΞK9 C#t'wqZ /Dl5.9ĝaDę'dB κIWOf`2BΥZQK/䙏vʐyG$H^%"{vˀWq`B)&fĔ7W_4tIp_q'͊4y0\LG9T¤O"lb$ ZWW!rlQ96',-GLez׌{AiݥNr&E@ _)G $R(v tϨFϐ֋OZCFX, M}FJ^*^߃_fv'UE?OsZQ}9$tዮcʇ_Q(rrxHDQ%UXޠ&|Tq6\ϕwA|GÞy>+i7*?-ot-\zmo+3_'Խ!RZve(>Q:wm:R !C¾@QUR"q\gs X%#&bfhӇoaA;}R6I񔔠Lv?yp"<V/ iKҾT}ҺΧ[,#+( ♘&ZFږ5:C3k?}QPi*u&G2` Ƶj)TYN  #hO*֧*2R\qO&P9̎51)b+5㢄`5<:VЙꎪ0-$(wҹs|=5U"BDT@&]l79+!E5D\y@z qgxT4(/E)AVWJ,|op>%01x]B@oli:Z0kXB bj#_+/Iyn"7YkhԟV()u+vSh$㐤RQK D!FՉ.\pJjzaZP+ԩ6IΦ:u S4<<.ϗptgb.VR6yjʯE/qߵ.&X@TW"S®/A!Eȵ[o^mvST:;SS6=nTm?МR:ɸwCrLML^}>u F[i~G)9^K9?s6A~-J`:x4{' =&miؚj= xR@J*) XE J)o8?-PyV^Q h\W6,&u ۷åԞB8`M+і*0O;73#"Oe|(ܼJC@/8R2bGw_g+%q=`MFX> X+r@uRR|eӥfg {"H饥";%C8xzDIt" ­7>AYP⌉<:J7':H'>t&N*# -|HŨ٣eMD[~U_sJ8$9[m'u4?Z_\z%!M=c;b )g5l)=&wzNpE:(4V] o}Q,o F'i0 cKQ IDATgW3B:%%vJ%ܡM} S$ZP \9GҐ=Xb@GxQV6|!޶Q`%XM2Es ..j BHbU5b)$d ;Q?%;#ڱ؛tbYIÀR^_86/q\xOpfBn3H; H@ Ź.Sm x sŪkY1@`Q6H\QMY^:Da:p'͞s(wLXE-0n_/?Quiɂv'hPt-|Irbu-!.նȴ^©"-38@H[/ahrɥ@^kʬDYHКFG*_w5M(W xnےPT0Gi51/BS<\!$*&&&ph>^h!)I tzcOjA:)]vXnj&nM bw%!!P0O';nHD/\b*Q]~ ^O.4 0L d~`q~%t.sggG9-Dtj@ʇ۳f. ÓT3o6.[B 1D}ӽo+mb:aVf$alVJֈx]ҍݰ@+(cYDv.+V BO-a-RVEN2pED+u622"s,$*f'ce'~R"D_YX#uMIO^4BoD1mbQ+9sr1^dZ)Vs +2!Duؓfg&`RgNᔥ%XkUHD3/C|ׅPIc t~:U$s%ixpS (ɝ߂fYD7BYX~ bEJ0ڝ@Ԍ`?7$}*y(2d7ۄ20;`"fVXǓer_b`36RqQ^=z%iʻ LbÁCR˃pDəގb} Oną" @ntT?ahp1t9GrT1 Ti؂{;]oy[fݎ~<ļG@ x|^^oh{zi kKxi _d=>ib=YᓫhZ(nn8OK[⽫u 4XC2%اyv@DZٞ-w)N󄼐ɥ,daf VLorf->pjw +{W&3RIqkbͺ'ד ?p9fG|J\78.RLLL>bvf5{&/[2/Tv+Kj%Մxv{k'cgP>(rֳe .AIn Љ *Tki4&nP*IXbQ/8 `&&&0osC @.,zyTQqXJv? "9d v:nnI TI EڇWGB\uphZJuZ`xY$[%5\J<,)~k|/ ' :(IE[3)DQ =[sy:G`à%'9𙅐5<" p_ᵋ1-K"9|6NK˦A'X%)fJorlPSZ\(Cw|!_؎8]kZwh͜Gv?sYNdxM 'if=Z!;fR*|7@(1|L-"J晑),+fm]J̙Iܗ7ʈgvX.?qnsy-irɠC<;R.߲`w%aygWc -訙! -bj{nʘW xy@]ž)[\2Y@0! `cJnC5C r֖reO*;$ȼ(@Qw %˭b9-ώCГ,M]9Wjs3]ۄ8#xzcl:9ARBʍh(3-bhϪ!Aؙ\tԭW9}Xgix}Z }hZ<1vӃƕầ}Ӈ5#R4r 7X?΁i7R <δRLLަiWIOK+ipA€3LP?ԣ<18ư PgƤtzX3D }݌o(D`0ʉMVG[!DdRLL+fL,{!y6j붼VV @{9?8mG13?DėVHBGgJAҢGӠxf( 񨘌qlL~V-2XeyHbjnUU.x+DhWa71c#+;`Yd;x#!P| 4V&JiaM2XWIuØޒzZrHΣ$G#mILerfo01y&WvҴGͱu_1H@KO^J @Ѓ\sYV _z81T8oJRH>^JcX |hoZ-,O\Kg\yɱ՚ YEJxU5e=q(n}iQgL#7LqѱFuAz!hmR@fA5irj7~%'{NGcn#|7 KH *7v`f CvJlꕹ5gq:q՟L+ɒ~9H" Sg'*[#6~5edssOmL!8nm.k'IABZqߏ 1 $6f|?دk7Az&#1QJbn#nE L 9L -g}T-)nOZfGS ^v 癭靉YCTA@Th;ٌ(MLc{7Ǩj}cyד5B8t(t>jbbb(~7~LĪ>*ϩ. ;' p%Mʥ38gza@B"&t&hde,[=L D_1Υ_ Щru{|U?9f` =i=yZ3fוpv\4Ե]^RŘ.qUJMebm~ Rj; _6Y2*SP9Q /Lɗ31BdT~Fǫ=RS6 Ӻcm숋,P1{7111nxO'$WhA 0>6>#:EI2U.I䠙&C-aD|s2_o=hbbQKx=0[A7y%RtH-њ;+VrG9í l%ݕU$ '.JczSS)EF*Cl5*&&&f ϻ&p\JBa\=(Ⱦm4Q=NC/W6M֯+ĵZA[ q۴j+tTEM*=wRU?8ωs>fT4rTlJP>!і`~ZIxz|ffL1F^x($"ҹ]^'*l,תc]fFgiy"m!K~Xy,U8r7= IBBD"K DG;:{9Z5zF}xJ A __.~fg c"u >-_`gXq}f-(=w:{2xxb_1O`r@ȌR5,IŴ4a2nu#Sa#pIDBp>oMar Ϋ=,J"y"8g{.Rf:ږ8瀊m`8$b"Qc-+Ŗ#(:9|^Z ON+q*\F,ʘ|aFEɥA5r/xE@ގ' M A>6fX(SxqU}\;Q̧EiHy.R8H ^$ԯNʩJTXvܝ_n0|кETB8v=Dtǀd0d;jHOw^3x $dUU/!EB"L!EC+62*+[V4JY(&Nh#+&iӑ{󾂅`Wҥ/78-_J' i?|6HHR_A ubbkΟ}<n "l΋fm,m%'_ +)cTܒť' R΍Cďoۆ+nΜ(Jp[bGcu<{̧3Ĥ'_=n·[:\DL~v[߫㸷#^~q* zڕm\!(K;`"lltPc$ev-ԯW}k/ćWKܹݓ?6L3 :r~*$îtRy,"|z_z"r)SF0{>d;v }{@ 7QEfK$K*O0Z~:݅\%sY*GOs%'&WNPbVtY /K>$|Ӕ1ax/i$5ӞׂP[b8x @K9Ҩoޜ /=@xB6Z@uʐKx{EADzRp1[˱OFDdR2ڈni /<$N !"}y5ү=̑1DJsq0l խC9| * /F@@Z@(GiixFb33ƶe^sÒޒxW`{;42R.a<`\a\8[D*螖o!Q.ТiP%8_+Uateߩ:yg 3*!uK2Q1J93|Σ||rWca/ wr❷N ;CzӋ졟MEK5If(|<9+JwY I}SF-Y';IjXh PqwǨiO]7/oYbv֙R2zJ\09\dV0ֆƍ Z7]te9y@=dK61YB_,e;=m@aᓍ|ޅ^D}vT'd0r+UNg}^JH)0>V )? \B?^3$\i.qn`6W|zaGJv؊i/Y |)"`旮l3 jRfF8杌Sz6?hq_n(A+721ye8Ɏ~B 1^ނUJwrD zc>n[y**D)A5hzx֒C̥8(&YtRMLLv+[o W>qP/ *-E\{-]/|RBO ofa8L Hqa}VBzNcʭXC,^)&x}Z?s#ɝ9 @Yۚjb򳦠e~HD)K# ;gne=$:a=*SB8ܛFe9P΋ī/cE5mW)gjTBOkz~K/م/ӕ y {ƶ돛 }>h/7o&Kq:ߧhx%i FbsŅ a֮G+3oX O0T͢ nah-se7-lDqB^i=l.-j2ԎD00t|sLZLV2 q 9qDVoxaSV| Gɰ,f^U ֐)S0fIcRvifƉוblOCUyhYJᓓx&*)NT^DŽcJ&s-J11Edbb'ۣ|,VebЧgD;&Wʥ /ЀI &yA{(!Nk! )o6IfX/rsLLFh41:ȏ\]IM'hX@c/U|YLzeu%P$+I8CtJ[IW`mQ#Y/"LNC!""8`rkm=󂚠R)]ȥ])iZC8IoWClޕI21Qa'tJ7:Hfi$&dh^O0h+GOlg6wT<"PA^L#%Ģ}GmOŐJJN,䀱C'/rD CQ{(|_Ze"Ĵ;'Q zF)Ibbbb>㐁-@ IDATulھ +X(9pC/*#JSsOؓ8T65`6֨\z$v>*$ +K 4g ݿn='Th q:YtŠC=f?ېDr3d"Qp3g,Nߣc`Bt!DIN" oi;@ilj߳Ĺݸq2D&1t{]}ATy:.?h"^fO@ҝ\ 31Ŝ:v4Z* bY'ppig~0111 7WDAƧHIFyfo?'mnRD"C+Qa<4I[k'ıE)&&&&mQ‰^E>J̡̝w3,!\䪭ojѾF4}/*KJdr/Q)@&ܳB*_Ӱ/n&&&&&Gڼ>{g1 QxOWAQz6[|52u̎q~LE R^Zebbb[J\F Op8#A$*_S981L*дѾϦˬkM.b3M~7 ;X39|6$Itae;hOvG%sNQ*cC$I3\攑Je<^V*@ia֛dO{r?@"zi n~&zH7)xq.%ŖL4*49$e枾_x/*Qk (8O 5111yMXa4'?%'gaxLU#N͇Ρt4pWCz10bs%9e5:n eE@8o?N'w)c’S)"zYӵR˓|".&eT - ZHHRϗ{ʹFè$Wz%v1EV*=a~ɧy=kY4)鉛2=.q;܄1pC\Gnd/GY"33|GUN? O@Y'iT[f4uGV{k7zm!qؙq(G m}lbEʜt'>ê&d>2{*%)<| vM]W cw %[(M"qwvAY0 vKmcȆ!)'V OŌOV:) 4q(cC RؙU-KF݇άwa&&&-3祉ad\F67NO{ؐw[i{VV)O<12ry7r^4ԣ&1AÅ+F6GϮ2LLL4q);=TGw3ǧ:pkiW?_Jo;P˥Jr4k"EOǰ+cɼE;2111yM`e~>ٹ<&n(&GBO:'F!mc^}I0>SbP}~—Lz ~d^v!|jJa.H\*x;yXԭH0n4<'}Ü @)髊m/71111>ךj}BڳI=2떃OC"*%r !z'PEޕS]{>Ik(GSG-D?3 =c~1jc%m yunTLLLLϭ6߼VތH#{ =+F,v-4o93%(g0:WĽ@'>v߿/z Ga/Zz)[cC*OG;}rgJN7XTo];9#oɇܣkzW+սk7:Ώ:}zr˴\^'SVeBz L!bz}Nu];qAo\ xF 'ʸLrCˇt״+#>5J-\$PZq{G!-bGhЭ9]]H~cw3!&pŎluDⅵce( vG+?O~TX C\gk!v(IyqI{O{2!0=NSIkŀMO!eƽilznbbbbqԧqomw5a=G}:Uk :DCD/H FUB!#PWh(Evoo|,a2G TD@G|,xءUɁڽʦ(`vA>έRMd;svMULLLL&]v6qJ_4a/e(\IBqDcCXW~Hܞ%)td:1qH#Ώ~XޯmNpkX^_#CZY?sxmZ|'sV60;`"Ȓ\t ץ419&cr8>K[Í8uAl2|/q!Y_e o!}D,F'R% > I| ߥJg's9w|4~b58 <16=Mx>2*!S}KJX+oJkZw (.P.ퟹ͖v!8VcNQ?n-2^X(ٽ}ka^'qZt-9Oa: 3-M@Z95pXm&@c^3pi^ɔ>"ߌh ]hxeD?AJœٷ< bK" d~Sb !Ns.'o0#M?̈#ÃO&ȷ9iYm0yjE;“r:0%h=Wh/e`>6s+׊sMGɣ+_4dIB'+MUw^~Д<$G7{ɕf _19Rg͟<,_]5+R)~=u 79ff`JY1Q07,6x|*@zŁĢ*EMpty}F^JX>4% K\xz2e&2;fFJs5rK~O@Χ[϶f్*ֆ:%(y]N&e {MΗ]E49]Cr3&&&dV.T oɇLJ1dM}uM P[%T,&|+.qhr_i"ҎuS{]n8Nٌ5aOF)'[rORc}z {@2C㶡[gWX%I;>Z>00+&@wP X9g{5%? T@~fbb;ڌ{)?1Go}P.hpshV&( :~mQ&BLW<̕n$vJV? t[-ۢ> [Uj].ˤ~s=| u˧€:\Vkr_xK.Xbn:Br3RE&aj-;x:=ńܠ>$M6\zQa[S=%Mky)|h.+ ʍ3GE"HGҕWC5OP6ryM,_20Ow(c3:ֶz~G}?/Mؠ?$ޅYNޘ P)+*LJ29zM!{LLLL^|,# `4'Mt?_+{h6臫alW: dkr$kR6VW:R|XYJ^M]%#U8{N fbbb⿺/g0>Ϋ؍(}[/">8jQ}FQ2~&dĿC,]ΉC4JV_Cg811111y`5s<mgxt|TJFuS$Є}hQ(eQ#.^VZUB!%(|y~D޸à+<(t9׎>ϱiuѷ:5+A+pt@Uf[IKfsM^]Uv9Rza2A ^ѧb.vGL, <$;`4˱wсԇ98p ۃt,)Rv(kaLM.f- Zടۋ[^9BĄ0́c ψ/SDf&FjN?$ιۆz".LcF4_"|ٱs(cD.es?"!;`fOELĹeeypH^AuA\ ܭlح8Nخs+IXRPii%^2'9HY&m%bW1&&&&&OtC{5JOP]:/˄!7u HksiK gU i|Η&}O^.7RVbbbb#rszįR'{]J&○E (j)ǰT_\qG IDATD.0L J'!ʀŬ57*oRLLLL~0\)$z@Q[)_A㿻Ҍ=½FcP/m9|J 'd7o]S-g`!ܯ/+")ILH"_H,IǸ:mOD"&¸ Ve';R/[%攼FJ1Jk>t`Yў'sI~N'sKzTacX*AM+1_F\9ct=cXقk+#JUXiFJ`iKE߹CZ_;m:LLLLLLn#~ΞW,G}^t?O|3111nRz)KB 6 dN1{QxJ\j s)M2ЀDb~vabbr-z5'䋦̤E!hN"v~sIZ}#J{@bAz$@aF ؝-P!wbFѷX0PȀw:$3111",\yVEӊ܁S9نĖ7RSw).sY1T;}fK^JC7Q3(}_鶕ȘTc^5q!նl=}{}M~=8)NGR`bN*8v8uY'w=t褐?t7"-0 ' `OףYZ\ƀQ w[H{W1~G*XU"*(0PY~r:! &yi"9hàp1ߥun i\GD3db<z1Bɭ$)38-[xD~~0%o\4 nD_W)oA*y>-ab3uF nLx&mQ[eWUs3urN~h`e#==ÑO;JN$;rTtcP( ϮSRTblq2t csF]@%u *d1nKޜRڐ(;'~Qi Bagӓ -/GG,ۚޓja)2mTQ92lBP|sKUl޳*1j\#6&'] C[x-=WUF-xlI^=FȁDʭP(-"K{Q]O{PkWq8g4dH"+ E?y)g´v{ DKӄvŕv\BLBPr?Ml+v`Q6RcƧ)%OEGCR/(}H]N:*-6ݠOQQ[=g֔|ƚX9fl#)<5#,] kkRgSsoܪZi"nK7dɐ .TFݽ*ZEn(&Ht3cAdN D-AuDF^wcgdM6`͞ qAuDLT]5%}?" !n:A*,vk@xu.,k]]Xx3D.$럸pLn>"BIg5==l/0,qP˜'`]#" hoZ|9xSmcݺؾĮ~fm! Fǟ#$^ڻ$K{s&E3P B1OKT{G`;mGWڰ9E}BP 4#@ H6qRFy;|jAPWKoU쥊cNHB{Jq9Q( &:07{sz{˗~eh|xbnX^AU/EP('=?+a`:>8zr V:\6lh8[~V>2Ib}^QpXZ6P2N,7DR꼔#V( 8%J5ThӺRw7%?!t|lՌ6V]nJRʨOv~.=F"VwEP(LLi}pI*ۀJ:KL^zeu?rTuu/u=|)y?ߟSM|3s <:C;ր;/['M5qH`-Ap ¾Wd_ K#UC}Ls+J`@.G≅Bva'ZWSV%Ne6.;-5f,⺯EG݂_$,{H"|)EWۡf{RIO+@JHܐG\QN|R,<!Wyo S\oOpwZ}+ˏŒ?ۣqAlf?3l?Bf DAA^wwT{%H9*;U{bAC苲[ҰN%`1sadzȥP(-0"yЋg[:?9VL\mmKsfԦQoNC$ DB1꧞ lU_Ika;?_Kui=wkb7掯PjLX%t%/;"fs$S-~XS:*6m%CH}dP(w./؏پsl)e B.U;_WvhS=%-NvCIQ)P%WIU*HQ1sC27N+9{89Uͭ~eIJj#o"JZv.edR̜aBm&'^$t/Z=WSx{W8Jq>yP}\.3]Cy "&j JG.GԖ1+tD(3Eu,v0| J5b9ǚ`~F[7)$a+ 1>!E %6ۉV'50m-=^" V+-3Dd% A )t3͍GD `2 ӡO`Sl5@ƕ8s2@DJDaDشVeQA)-ݼ>)h9'6p*F, yWek.,k'&u!@4e/:0FEP[7Hr"ѧxwAO\PL 5ِ8çIUd~gwS7en>,xr!"%3K' TH~ J${/ B1=SL{'._;r )AI fU9(ߝP%P9vlRv^5ZF3-5{y fsAs-Vݸ*-!tQ-](C{nKѭNP(/2TF/6o#TbPrR"-oeL@LJVyς܍ΖK$ ?R 7_;V|rt-Ǻj0yifgd8PDxEQVםO&`L@yqv3RƼ1[X51 W^S:8+u{e"7ܲ[tu~g ( n=V%Aq 7mRb(?Pw]$J2kͳ|VWR=B}w´*LϜ ΰi7O ݼ93AB;0;"gC[.OĨdr;h(_~UKoJwE7|[:9S4&?'v##9m&2G3y6kd+v/p#N.+rXZc" {a$=QٝKx]c]}WTwm⼇|Id.4P{(1Jk<_ܩA9jVY9 ߋ/<$ Wb9F$ߛ.i"ral|wXF,2&UI,$}@ Kr;7}tl[{54dDKaY]F![RcH)`!Fjy&B*@ʕFT_9ݡMbnVRR\?Ɣ"FDZLb -zKkl/7n <_  ;w[`vj23#B)8+G4Ѽ<H(DdA0)D$ǵ+Rl4mfݬ WAEmK`6FƓ[X,٩) BMUC4*7Q6iO2w%d,:1nۜR[D3B¬\=s[mQz,uHjtKW( ^^޵R٢!e%vޫ_I[Rra[xNB6zLJD67YY]ccʗP(XwXrk,/?rV!SSp_괣꤄9Yo5Z\h(9~2>޷rR PP YJs;ߺ¾Ϛ"X#:X2z6o|>#FSL4|o| fqyP(l*}&Br&z5SKB˳ve׬ݺڣIRY 弗rXΧ*=dcL/MW(/jCr(2_öϖZ ߚ&Y5k+]"h AlӯܣBP(_dfAoT;) :U5]||u ffc^Kv/Op}#WIdvƩ^Wej!3e*-.kv|)JOos.c)Q g.Ǡ1?O! x> ǶzS ac v'=khAQ9Z #_g Ɗ&0VO 36y&X7*zn+T$A;?G&/D甂hK?m}D;Jk`@C73fn d25 IDAT'$L2WH eM7ۣK5DqzHmY!K8E@$LL%aP̓3W!ZN@׍Bv&bg%q)Ð,^hl^nBd`' BQd1 ᜒb%o[?d>V 9 }_+XHQ( 7LI4#tnHZ{ViR\zGEG}|fhTg,OQkLv]a ŏ-,K:¥&c|Çd\;_, +O$j#XPrI~{1Ȥmy_l( z)W| qmy?^4> e oXtśES) X/}ȘNDha m ^BP(KQO{hf+̖xK G|]*kDYc}a,+guX7 ]Ñ~查ĿfR(tcξBGQzg۩<ҦvU=S :9Ls~6U]lz#U}wP *.g[oTR*|P(pEֳˋ2{&Wަ6a9D֣UR)<.UYхبv)g$F15+ ;w %6Q>=F4fZ_ຟ/5[V7 5_m:K OQrN~Xy*øa+/]֯6SD^#HvjW=JZ <-Kٙ Wة(*RR[deXʼ&7ʯC$4(b`fN|\\7a@Yye "EͮkR:OB6K1#fHq_06 )[ƕUy>%b!۶q〻n"Xό .hD2Ί7Y. #?Qb1g 4۰ֆ5R! "ႆ֎ #^y9*r]-GBߨvMBEP(_@Kz{l|V[KWzTj(Uyl 2EP܆|;d~9.L߃f62uV( W/%kz#,H!pdIܳ7i!80mv("%|/Ad GըEV.#ǞU o?5qQΘԑeKe kf߳ʗP(%Nq92C~U*cjZX+qҺ&]ZF/^B[$ aث!VwCe-!*P(ڭBGdrl4=f¸dXXCfl+ؽW{+RS(]֫ CS_U Ͱ!R4] Bh4,5JiyAa+ݯg*k]1I'pA]+_G +tA(/vF$ ZU$gQw&Zʹ)97]sW((QM9li~7OOStR2JWd֫Wݙ%Zٵ]R%}+^xe~JU4(X><˫,r~$6P|dZC~ ?I/HT4QKQ~}!{"0w9"~pi`RL*FZp؏ `anE?|>^dBAG^-$ų xrJ#n T-i%8ڙΧ]d ւrVJgZu$d`ђڄ=͛ DBA+J9)}ЌXAziy!!!R($B{$0]J#~X΋JݤF![tOm=Gjr?*vD$ɥf^8x1Yw6yj"(/5]I8Ip΄U u~ڒ^rKw{ʣٕTyކ/.U fD23MYܕ0 Z;4K?y_)F̺<؁ EB1Mp .G/5͇FbBPߣwԫփQ9I͆!ntSYR~a(8 ʜM5~k>[B(mt>|=|a]J%5[Tt|~䳯t2N"$*`Fl%3~p= {iuKhHBqv[C~P[?e3L ބuɳ,h]50aa-| tʌ q3/6pW={G%~3{Hj"(0c%-թct`O.& ltRR 0;L;|eq$v0 t>}oA ] zٺfn4n0:&4[$hJɖڈpH񂈰 Į?@">fɐj+!ȇ!.^ >yQ\S:QVXj%}3qUL?=b_}.kWRV"za؇ SzՠH8l;>/@"Hx?(N HZ4P_ IDk ’fF$$GyT B8{+"Ȼ5pۑ0|&Mq>r%;ț2uVaO񫈀9{Eq>[g4.Q%acPfWl]XeK+Ke T!(Ut-)#u|lD !ͣtR9)EKgV锹N'# `FC(sf\o=XͷP(f:TQeM*bxƊg{ƴ ץ/F\=Koy<[5Sy/ƭy 9'v^nx Bqۂm*y&WD]8Yx 'j̵^R?a44:J Q-K&^@|Pw0+BP(MUJxA铲[+bM~iw}Sz֗j+2--6P_{#+E q&Rj5!wTҪdQusQ(@gv^(y\Rji䅧Oj.fs*@Ü^ܪؚ)b˲Yz `]w$BP(fi|E 3ogb6|+E'5q_4v.`Q֊ߣ O{;*ϪXيoZ`}_,!, A|]ōx z7&s>&5C2qFX KXS$Yg0r2&^g-:{XȾsa\b1j5~L?ͪQҽxV^b5{H@+!*g~\$P+a[ޕGa mkhp<0Rd-p( V]= V Ifts8\L۸ :*d"SL[(~X̭~^JMRm"cfAb !2 0wr2tu^ڗl 'P(?.fW#\%[fMdI!AY-dyn=:m1=I9۲w;GqYUEBP(1v_hzt0-5'W.G(Il\R-uVnKS in;Y {죲bÄ6F;zV@p:/EU ⽨ػSqQV<V#ۮn`~˝I>~IGa8{K[ D--+9!E;BP(t W\B#δ0$zIXK.Jo-+BP?RZn1 kbẂ@/7ueo9UJ5}HѪ's[,*A_쫤Ήzn Bx_?/g841{̈́ܤoG:ʍE jEhnY(p]ıd^P&v3Ay  -> u>E,AJ^ =aUb;xԾrmYڈ 5l-sٮXxėB\ X'J*YXD=t=ꥸe( dug@ei+ѳ4ɽؔ#\Z%d3F5G{? `pbJ"+du`!@c?;{_QЫNv*o+5l ^WAA"5^Q' Y:"EСy7",Nr0̚(kQ/+OU1-RP Z3z ~'t3Zoa!@A-P$8:G/ LJ!Rgo BqI뻐=-ԵߕmsWI5ZֳHȐN>^T=9uw~xC'󠀌.BPåGkT"k=┦퍇rIPgWLкYP3p_b$cjltrs9~܉ޓM,^o_Y]I2`!P( =9 ǽMvqͽNsKS]ô-o?Lbw\F3O@U8OjK4sB5g9B?<@=fg]dM~E4k]uyt~EK^G vF^*lcP(!{._?%,HWجT{R|6= $Kר]ٲWLEAJ8,@$5W/2'ZxW9B1豈Ѽ%`W k*åz^k%H_Ⱦ\qxMzB}eWYo.^BP(KQggod1]y~u3IŸum(_CU.mTkrO3>"Ԯ'Y֒/*v%a+,b\4szy0*]L,0[4*˦|x r~EC 4W OzB01 Tb>;KI󌫤10CFY5!hp*V2l H]#10ŰZx|;5C:֐(Ȭc @W*ĒYpn睷0RHckQyT\|4:ORT_W]WUpqmc2Ȉ q݀D'%cz)uG<AxӿS BнG{}jk>Iײ~lz ?I 3;G̏Y*E1Y3-UnJ%+>eL Bma'_cSbg+ES%#iq'ciٓY 6E6URBP(~ܢUA}}SҺ׵aHߴRwϏ=ebv$q=qWY: ڧrLU. BxRQida"yB^]eRm +}׏~&K)OouXnZtf87b+DžP1/SnFo򥨗P( lVIj] .CRċȭN5,8Zϳwt*DBb9% !R?/nd)/e705eJt6p)úUt|QKGq>֞Zo]r6&c@)WEfk@D5oLxVz¨'P(kH*KQAhӞ0| GW Y+V#MSHRD fj- T MEQ"#n&#|0*T%-84'p0A5{%+!Q ۉ)lJip K޲*,NӹRJrg"F9@̏/먅b]+ 7B늠*AzCF'c23:K3I{\;QՄ{:ɳJ Bn!4(Jm:;}@aZ¬UM!3bݢ}t)C E )@=碌,#B @af|3.[(z۾:@HS<+a`gG+p_5Cc$'17PNKloAU>+^=WNYqo^dHHDb ~_L?B@& GWTx!O`A%Vʛq6؜VV(߭kd:wdH>I'/fQ(sIV} ?hv"guK5[8u :'f62XDy8JV|“UFlMB","d; !,A? ?u93S( E,+;[7Iy^7׋L ;͎Obtc%nxuK_,@DD]d bR/L8Y#Ϯ=}N3ߥsRݷDcR]=۝CtۜP;%>Y_enᎈcUJ !Pܾ(BBu'0JEzk7ʗR_46f.T4 jh5;' ' sAt"ʗoQ Bˣw뼜FScOeMsQ8?墑BBObS( %62U1w.Qv^z}xNB wui+ϧA"ഩ/սݕ':"DDQQEP4٣*ѭᶧek "&;~Q\^7WQ[g"Zm_> ^q9xw+,UkoUHoT B1G=5 !x02>KQ"CGٳ%4<uiR*#^,3Iע j̼ )4X+\s</8]Z&"eJ dL)"B`*H,mV!sj7JD!DRx_3' ž.HF|-{eY}/P$[7b-Syٳu_rVWZ ,w"bNDH6kX> Zh7RAK8DdOV]7׷0VZD1"biYq! tEXqY@@ߑBP(f9;Rq)Vr|{~ଽwNJ|Z&+_~isqv͏꨼o}T(J|GKQ2W&p"_(Kvuz-ے/QOkorZltDqq[?aְ%P9+Y=8  ؙևVQ:O|ntV8:'c3b9`VtՓJ]Pe#XFC`v(4 [X^uS(,}hribkm*l`#3.&H/P+ + o34g0sZ nr.D %{]>*XLiU% =:` ("h8@P$*s`}?ֿ ‚' +8ݚK/-5#0[QiZ 'zgP(Wa4 jG-YsJ8*-H땟O::&h 3exmQsW|EBe/Rw6i5_?-*)vm)^12LP(wY.?u%tC$ȮLP(Q[(G͈?54Q{]=3ϯ_(Koxv~oFVoX[_cmpUBP!byP}RJDzMk> {BvVeonحkEQ?uWꨴqP( R(;aޢx /֕!ڬui/\ZQyS+CT%ܭyφ~QAJ0|2Q+8={kONU+b/ 6.cWux4&pŷ8I@ZYnY;➘vb*^kH~7Mm\wt/ -uNמpDPypkDƟy UmzZ;}o {KcZJ%+ʢ.C%D3 ZpS!d(dhVQO8_Bsk. ՟$`XwZ $DZpE1!1XHzeTΟ@%%m9Bd:Z zX϶UX[wS ?k)h0 + bA4GG B^xg/1GqnIPQ\mծT0[>t8H- ΀~vUU~%I(TԊ0 qg +BQ0 ]Y6ۼW0w_9עp~S*ë{Z2m^w{nI0M2z,w]?M7˶S( BFАw>_9 mN`b^)UuT B[.fHTD[`Ww&o!M8ec6~ƩP(X M"-2}uZI)/lDݝU"0"S( S7QA ˜UKPȮ 0ï~ΪAF 6)C| [Ta%&+ b EY nU4f=>]eׁy*7NW!l(P( BNKq3>@rF&ߨ /S# ?LK"PKvQ8$B = Hfzm""ɵk)᳓K j)b$dȌ9{Z-٠OHE"* =;6/uSfH\vZEE0I+)  ^j84NXHEdAdO;f>ox~<6W3OmYv3B,"f%NA#EoT b̑ m~^b%k6ػ["$s@)ۏhfixyܴ簐"+fQQ(n$ EVUTat޼xֻ~p%phEѵU1w=8L?#iuWϔIw%|]qRg  $&ue˯c/:R(zٽ'v.Jt6vuȒYrGIuo_g&\*k:"q@aTGEP/EvA/>z }um.3HB^j^0$[|oٴ_XһQx C+t#W̿d<7];(ټ_&j2.:ҋf 3-ɘ+P7)b1?օz=(]VLpUÍBsi!E)tgvxpCRV)+){Sϔ+@kI}ȦBe4KP(:rԯݙp4 o h9S Qސ|_W\3ڳ5'|B=I"iH قcy=<6a\\UoLJfu!)D/@ EUVVj2k(+ @6T$ oI%%F2aI)llgi1"l%jtۋh\Ѡ\_#鱙X<隡aϞGYi9v3Vge&0T UBٛiDzi3F"BkKrtGw5uAr 9ޕzjit| ԹM>>u8b1k%:#}%hR{z$U$Y]Ǘr+nwҡZ aհU}]$XׅsWyWmި}"-^$T4M쳆IDP>(@Oy(`l B18mӵ/vo+RHvьnFeFE(%Ȓ9V^ҥ2<X4 a?upY_*x2u.zBP(tyMkKf_c(Wo|:+]úeN3! I+bpo?EP(o4goΌ(Pޓq0ɝX֟vx9"Lz[^olGh !wG| CdzBPqI`Bdi3.˱9 \Փ5e)|:uTxBGP(7pd'kK*z|yf'g|XivuI)ß|lhIcת~Oۡz v0Ñje߳&0$f~9ABҝIP(z8p1=lQeIF}mO);|mLߵ+K'ITMQʄ(-Y[2ic.v.%@LJ~1n BqƄjS|vU|UI!U|q)3k+,ߘ.;88ΌڐOL789; .gGviݹ%Yf _u3^4"PcPk!^I׷=húЛÔ}tŢLj<汫Xbf(y{ v?"`^޾ 3΍ȇ6q9A7%w\lcdN5 Ɂ.?0[kٝ܉دX(@n#Ϫ$'h>1j3Րy0,3tTvOOn1ewvo3Rfzzb7DE>XNTa@\J8kofn_pҍ8bQqo HD).,KT#a BYIvlt~ ]"f4;%lFF3(*Qыx[_,!lK.ʖ7`\9ޤTeG1^Ȋ10E 䃚LP( tu/E0C=|ND%dܨӽIz1#JRSiP B񃸍՞CKrp;2Oը3ct}~ kG6W?*aPM1ɚ5&tIUD>͙WCVMS(G)G%sFtzW2g5\m֌5,œT d,wuN nȨKu .#'&oDC B1r^ť鱿^1+!u&~H]YecyN+ wC=K ш=GEˤ4Z6#0Z>KJ@(,? _cA  ;/k6M^oʰ%+e=.LOg?geJ*Ͳ~=@6nĘ4Q~pY8뻈Bڔn-ܹCknpf߉TŪU,rc!pvx>hiߍDڄcxъ"nXms3Jy+T(qaAÍb9RRGҐw!M!^=yP~S )Cqs$zLADAG>9NswؑOR;)~sUҞ {&~dWB 6#5|ߑTʬx ę?Ҳ Ei@y,KIN1[#'" k|r@!Di{)! ~I}6) -퇵TB~GQHv[u*C\̳(nI Wg]0FQ UGEP(0|ux[/nMT=n߽)'KiV hy@_wHX%vϒ/6u;}:li2<'K|Ct ykK7D|Gk7 "J/I@8={#k/Ng  ׇeK^Ցs9y?fbћq!GJ;_y}@e{R 3) oYU'6xXhg3l|.S 1ؑ%(Gy~u[P T#~=)RjL ]$7o]r!2:"SLtCQJneSvшf3DLBP#"RDkȽqܽdGmyl?I)}JZ@DD("ϿKdӒItA&`"BPu=)F?N3RFxx =̺!G KDJ{*KX+T'YyDq oGQQ( ?VX+oen }o4wo9Vyc-?@JQ <]Y8_d.8sxgعw?&OFBtPŦq鿛|Ҟ#$"~pZVFUzK~DfD-tHZkpdBD;%+EāE![﯉ }k+X:+EBP(Ƅ7KÛT{A'qK_erFtd$x?9m)ErahCNKS3+-ZJN_Ddߞ޼ekz),("l2 ۺQQ( ŋ*1ou'H 9' GL ;/U|WhIhdM-V)aWi?׷kn sh\I_]UpZآ!9 "KU-SBP\iQKY\'OGZQxVE u0[k.slpJ!v'-{@៖BG&Ϯt@К4__jGEP(^`"VSݓՆcc&s_4/MdX؜a"T d:IPLcϹ?fABha&soT:ٺ8hLxAT@h^!!/Vx26E-O:Sa֠]~o5kbKFzng SF+F'd23tDǴ=54 *QAP% Fa.,^ ֯|J^%޵|-ѕ#ܖ/^!D,, otDUK_YNz) ův SWoջDؘqw2w+J4^KJ9w&LC(y-~Ww_&i Lzzn%tj^>򏽺xґѩP/E=JVi/fS^wՐ?(rn1/ͰNqԔBs GyOq{& #VJC(6-jUL]:s1 z^+ k/i9ͭ:9u'_܅W{|lfd`un xW"(-SD9'5DDBm>:۵ @1aꛉ]ӶdĐԝ{V>Ww |݋B.LjYp*ᯔcegH:\+S$Sx}nB`f ެ 7RSQm5QFslg` " I3Qw7ߣiNѺʰ@J2we=\O9IIuu'rvE+L?()|gXjg-ش^}x*R'݀Y'vvKSXB}4FK]~^,"d%lH3Q^?ֽ┪HD`1ncL"BƆJ5KP([8~|3J_c;;pR6kr>pX}x#\N|YZTλBFTTuT B1:e ]`ަ- r^:MJoVo E~Eyݝp!-2ɒ&$1QQ( D=n:Z$Nt}.zZ*k1U̵u*F &t_T绬w)ؑYD8Ɩ~&>7* BPLEJA=].zG]XZJ1$9덊BP(^izߣ2VRzst}ݥޱ?>?z{y'|V K.%)QNuuT B-Hm>]Jꆵ9])ƝZQA[^=1:;5džnԻF7oȆuUR( }xtQujVdi1iu|Ɉڋ-gw40zE*]\}~iDQQcRj߯ь^(?zJ _mD5:ާZ4a4f\`Ӄg 6BD#sYW[+r˫S_fʙ"q!Z-7ImT D-Ҁ4Y)S['7 %qf#YPr-_KXuL ZT59[Er.^,yg}8DdޥC{Vcnu[rlD>qH~dIlmS/a[gψc쥒u?yx:s 螐 ""FKRU)`˟O: x 4f!jẾ" &+ u k4`zy_'sxYG)BP(Tϑ$`=^=[ꖔfU9<+q"*u݋>3۔P pC[@TݽTGEP(SYy>w*_<_jGz{ƃJE&狤R7zCZD\";'#\FޕeMXTxcz گBP(^l>es qE>AxJ04Wi[pVZE*.uT BѺ`-/pmU%p|z/o"_woP+u h#;Fx|q0HL_h]u6jHT{:* Bhtw_diFLήVdْ}W3bk%l"q>R[e:BP(^,; (?na_4-,)DcsDʼhۉ,Ggd=zqWFf'An/,@D? (`?=\ZdW^UKE{'c2rm!LL(fr@9_)ٜPِ!cEDvpa7n@7C,) zj*^0,MxnyxȼEL@8K`rf=7zӣ2_eE'3"i]4L v?FY"$EЅA4o Y}|@ >@ (BP(&(VԪA' ZvEgjbָțI5Up7yI- 7%''I}=jBPze#2=fa 6nɝɱ_ёVFxX1.Rn7!}s=y)k%b(:\@Ԯ?}:* B1SfimlƱٌ3+4ķ\To4=xfV|%ץIFS?KgaBא 0K:* kqѩ>ɥ{do֮KuRhZwr0˟`>DP(ֺJӦoZ=_-{k;  ֚d>}ogUW;$qox?Vn½D5ŜRPn B14,a j:gŒ}{zbv-T7*{|8c{%>uOq#csnf( 5v/z V&y\һi}㰏:@޲~ۡVGuEJ3Kq_p:&4gEW,XguLLxfPLv"v鑑C1ȁ$9 Si%5-9dvGCr p%xgWAG)mz^ 3*hX%iTS ,Dtя`2" D.z W[@ĤE?"=W$q_ݰ@l~,t ~Q5aK=(GT͜3 i%f&_ %_b^$$,)-NE7%JlCho#J#yڟCaqzҤ3.u:D d~ჰ}yPHt z "_B"V1YoT Bq\-O}@C.̯;'$syip"BR4`K$ B{ H02kіL}LIo*/zcD?k)Hc/Z29X6ḒHvʵۍeyu7QQ(cE=w{|%tА{=w W9vR!vln);j Jb΀ňr1Xb|(Kbfb]Y*1J{-lj!@2#T5~oE(on @cC _zf{wӾb=3~Ƴ͐쯮rrsQrKDKu4ߊQQ( xnj\j:ţnx,sʰ!Ei8[ZήEDH%?ʰː|5A9$+ժxm،婎BP(2ۋ?r'y&(3RKo7p8XI9 E Y}xR-),`}>yqF= U< R`߂d묏= &TSzaå*/oKIãNQN"VpY3\uѺʅw-UJ.[2nUbC-"QZ" Z% "WQXjx0?iEQ Kwa?˖wd fm}aΘe\Yܦ> ?3 i\Cnp'/z\ٕt~`c6NWwU*:o `B[fLtj(zǡs'D@rpO]wƯsҪmecdL#ypY6<_n@Fm wrT/.!?&J !IaR "I4 і nzFzcA#Yǚ}@+))) M;Y7//)♾*m #G(>8Iֹ K <-S GQB"/#Xƛg.ޖֶɆky~@.MRRRR|͙T]WϧD]XnY\kآmW7LĢ[g=3O2~ 6 *_e6[,KPQPIi4]ydQ6N5~r㳳J81ӎ[phꗒ(JJJ *TPcSSyaT'>>VlGsREQQOIir$lDx|=n'3%Ӈ9^MT^y'u] ڰn"B郜[2P% ֕gZKx'+U!y'QQRRRjfoLBDP ka|U1!l=VʨIqf`MOcY8¯u @=[sr\}!;d+&(!PY^())))\G&xiE?h,AU AtB:XKRo=T< INk$5P>JjU.9a%f_ Z8OӃ;!}B IDATם"]o]11뎜MOfnop5q>Ig"!Krr`a[׺A s!}IV胆RZU]Lad96qEx+ *dž+-01dݲ,&!lM0Yz!c ]}i]+XuC`c })ĕoD?G$.B։15y,-Vjv39&fp0Ms!iȞaD"aa A$=@ cK?D@֍o Q ։ a"hm8."ʇ>aY$Z\љ`.?P8 "jDEIIII'Z|3%nΒۡ &Lű,})OdñZAl7nތl/'Mܗ?yisQ5*JJJJgՔnPԆ+v MXiGrT)Wx_ e`htW'Z_W.FE)IkũPO=S7wc?u.tBHY\""y)ԃ곈֞^wVB7j())))1V/V=~:Ite[;0ʵKLn"r,G %%%TS"'} [MJS..p"'>[5JF'2+X.R>@ma d٥p_kT")4[)#N[bs|W_]?(G8ŗ̷Cmjz6Eo !E`ՂzGN 6o0A^X8L_η?+ĸ4,/tY 2yZH5%"C~0KGu\PL``@i}?ı"DΪ!> ـ; "ſ@WN BD; Uc O| hOz CB[|9\HZ! |BcY?MRRRR4;޷=wFIPg,cp7a?t2UUlH>&xȆÄؤ0Io=*ZpN;aL+oh$M=yݡFp oCl"+:0Uucpj()))͢v+6+ga #nLrOG1QeΜי.3IRӽw, O OrM-Y:4KIIIII8(,<Ξg$Jȴ\l\DKSD&π?6CJJJz#`)u}*;гdXP;x/|lqV 8:VdLᓁ ,+8_WRRR+GN}$":c1B05Ț4r2h.ùsΘ@@~_#.} @/uVqDkvL}BgkI UIIIII5 ͭ M/Iv튈S)6|j;!$êj*Ix0&s׾xޱUw`>y, vh.l6'L XgEB0Ywh0Qk)Uv?4<'—m U#90>`E%;IK=R#t@`XL}ۆ\JNa1O S3{}o>vLsL֛)1 $X.U)ʪJRĜclP6FO#΂V _Av9BDݺ"eĸpR"%Wk^̚m>9ukK,~v$rY-*$4.uVCqk-7 I\?@`† '"yfMA`xX3aWZ[2k;%&"aX~~ޅyǹ^:u#ǿ ₓ4c)%h!|ae)5Jbjo z7u%z4}T0ݧɥC[PZƵ%^j)?BV,%|,2aPN`f2nU: FTڙa֢ዟ5 .⍤H˼gu~Y%aEDMp>-1J;f1$8)1**JJJJsi䉙q&n0\{^Y|Zc{`ň^j5o)(pcذcLb&>p>\`hFQaw)!Q4  K΍JJJJJA xJ7ZT] iYvLZ63_*[]Xy!K"cL7"`h9?N6$^b Hin'x*JJJJ*YZ%Нu;&u$|4܌9sZ+E|gꧬn@"=xpj IMH^U: ]] o*lVW4:iV)ي h,.Jxi1W̋loQp(/T uT穚"/|ylPZvZ_dXk?+{'T)=o_X+KȹNPQRRE [u<S۩u߹|$ȰrKWXQ]Qgx,v(%dF9H?vPrqJe4KIIIwQ_syge"fsdnYMgV(fIK&VnR2b)dobFH %%%ƾQB7+O}KwV"L5N~D 7IYP:|=$-oKT#撥cfIʡ-OnHNkէK*TolӷhecTxYYTyBtZm+)qF8>DJXiˬ$KkT~SV57h_qyh^33~ť,ݡ,*)AKWr1[z(033̒I@_ xvk*&.o9mhͧb]xIPqR<. q2{o>\z"mT5m-J n'sWa) p%Ǣc뎟v,, >_23@e B=-:z!"B ,@h",eY\L %R`')Еqz$Hy6Q(Y\dBLܔ2u|io?,G)`¸ه 4H>9}{N>`UW9n\kVXbH!03a4}3!C@ T%Z l A|4^ >IKP 1ӝT䖰OL|q[ {cCHhFEIIIv~MEU9.3$mLۼJ΅qŒ2—j:,M,Cȳ dtL/AپZ2sqF^-5TfW.J9BjF ).*噪&U, ?Mcˣ@@Wp~硒+^Jy*JJJ(MUzj0㓽yD8n {F"'9}K?Dͯo"/8GuPQRRR* yt%%"ΰwsR}QTbD#y.C4`"'Oը҇}\e !zsNMT0/6-RkUEqwVCEIII&=~wK]̈́1iϔdbC z<]`N^ZNena`{@ &x9N`GEIII5]FzάF(2  s ޣ9eӐ":z=˛.V%J`@䭮تq!)IPJ[na;fw౔zv#TUbos] qԝoME>Aqa"Ư&܍KoD_:Ke3OoJo@VcHSdy/-Xe9[-IBY^ΦU~*n5,~Ѷ@ WG[T.Tz!kR8Xcm8`ŅP.%83Q?A^Z[T&TȟPU1!'_1odzfv _8d`3dX am>`J֢q@`<ɚ1ڂZ\JIg+Iܯqkpq\< ADtl`{|çنo@`%CuG墡jn+BN FTx[Li.||ý,LPhW I%[}Q#=~\35<%X&~PQRy":q~1ਫ਼&yqxڱ=1=h /<ᯌ3+?j()/~S3TA/;S\R֭#u%/S?|o Jo;Y1B#*JJJj(MaAt4CޑLX~ȷ@KCsEQ%fqA~sYKS Rms1N==O^̾mkt` RM)ltr.*U*/d8\eQZ<\Sq.T c#/lfxC:&dcV 74 (];a3\ | ,vX1|B ?)]OUfB=${~S(?Aq{'ѳtLQ4`3!kX?le~ch!^t6_m #s(\=.*cx^nePۀ%>]kt"W\>%;Μ6v>D RD,[O$ejGj?KeahQ1G\ob ,٤J,y p_P@,+3 GWb _x oj[Ewо/ WMI#-{^B6ѶX~NA%D\^%hQ%/ ׬e+myM. => IWi9T͓[2&AY!4êݍ:f0C65TT镔T#T[EXABU %w]\W,=}{фuDeS\XiKĔKOHSρVE"kF03T$j1ΨEE8|˦t'f|w^F{žN$HqlYY)gvM89R)ץnTqsZL4ZHUR2~d͢sϨԇkQ)>ր/e{&-מUJRmG_QY4aRZ;\:և [Hή'*A!z^VRRiEMw.͠;ORџh#MJJ|B/5*3?n ܋<'F{뫓y݆4jj.c}IedžE $I l+œ6*2͆'JJJJ?nY|=*XyyѢ|aYTC?bSK&؍?'=m 7Cæ{ +6_[t+c w{<(g%Y{| Bj,L~h(b1)8fՙ^4fc-ܠ!D.aC(=]O{9v!@N܄IWY*c&$ѻK%EUveqLy3 lIdDLjDKl8 ?dEc@UT ef \"N>,pӿ҆ԀՆ+ zPU~nbD i^ X#,.sk~G30߶ْ>tB'm I b9ޛ؁594-,◈(K%y-8(M,o̘@$Z!T^QR{L%pL:!.Rv2W4`$D־L\u.3' 1[Oj$Y"6ts%9+OrҔuaٟl ܠ.8󑫡:ҭz܏Sf$)XobRlRCTXs7Q3pRsn%c}2&\DQR⸽ط~ CozKx򼣩Nk eF s)y'n{yv,.A)%/J5\w8\|3 6K%୤kJ_Lk)ESy=wcb9_<V<5xaE9iX3Q"摊}*V GD%0B1&LCDPQR[E`>cQ >mͣ{{1<"$>MU$fq IDAT?fSb9s*yxZĥn󥞋$@ )v0*Ew:ۈIJISDe\*o.5c|Z{'zˣK[/[=~X'x]ONA-{7l+0FT~TW3l !VrB߼¢ɂ[-]z5?P,N5] /](66S(wv1h:W6~f8j#g3k<}h!49ӡSHuKGf`E,x0% }F|%,E>i:$˜6EƘׅ%hw_>k?}<̂f쀡Z5!?q([rTv; ^eo_e# [cڗ $:wu#/m1m CCbdVx /Hd`-YHkc7*fJXDFŶ}~"?Ȋ5D O1D, lM]2 8dСgJkViLKGSԓt u%Lx +i%jEaT?$7uU5`IzťMmں|!q`Y#gPck.K,CC":]В,H2k_n\*))MpH9y^$Ӆ.¢q.zVumvB`cA 4dgWIu&^$Yw>6{% m{zR^yL-㫊ōn-߫QQ+EIieAĴVJMg.ȟ,yKrMi_6λjFg?rPB+Ac&Ot:a/L2I^* 8 H!VNu_$PW&s{ؘV6ٝ ',a}V;m役JR^5yӃP>''l7lc=gXf_t4^DsK ?+lm0+>|lYz j.e>pYέb d)bw ,v@KURt -`h >h"m ^{[p\ Mȫ0%o?=?n "r bD+qG+OD_䍼Jǧd-lT"SP[uIO<–*+M!Ed eejR`ҍu1ۉN~L$"Dzf6%D yȉ CvB\ޠiѢk̆zp.sAwqg;~ '_Pmx)!&9!"d>";VAl3?C?ES^R@OK;%cjॺt ^Yh+O=IB*Rd ggE&JN+9qVPTv، 5sIGkcߒ}PkIcSn)) ?, {"(݇cxc~K JJJJJ_N:(+:V^4<#w8Uqy d aEĈ›1)FlYc%p՛80}zK[()]-ͿFUu 7۝s`TffvؙJNpYY1m/rn~H B{hb+Ѯ_JJz)= y ?F2gu [&?NHQ|Eyo\fF{3f0Y; _ژ J>PQRRRzNjczmIO,kK8e Q\߲"rR=}JJJ?{7IS7^cuF`Z =bSÀ~D:Zڤ{8`FG~a׬nw^r^5!Lg]QGW:JJJJytۮ3~Ǣև;%+Y+Pa_χU+hJjúsJ@W())J5, 5fuHM!!yUs{};h- .6R\|}* ,[Ӛٗ/rZQQQRR9Ȯ+D,gߖ=+)3pN sC0ew.E߀R۴ 2d~X[r~~`jlfc[]?/6g80 P#*JJJVQ$i8C{𗲷Ʒ]چk9 a|P^QYYڞqP*4Y7њ_bp;j٬IoNsڴK |'@;ѷ~PO鵸,ϩgYL18|$KEi& !!9a7>w>rdG=;ld J<&p v_)3Ƙ[iGPș &]qlC&l#_ZNS% #64c YiXN0,&;0 d$}vI!P/yYgY 胸flYr\&SZ<0E>m ZQWXdi]MZ Xb3–De`2Tcz?j%fy)u:ښhR{rhT;v `@] (O,9,Es:j0X*܎RWi/yJ#Wfs#^G(?Aa|)`gs5a L}9fpa0f?~[F[a 8ȔDBJͥk`q5*j()}㬑3;[(Gv1@t;5~rФQ\ar+nC5t̮&/XGʺX!^꿩UIII5~+[TSkUWVɪ+%aN٢jŤkR%UM=C{Br49G 0^JJaqq{CS~JNyʊE {MVBN!aCVWzVCEx~L"Y"b?'Q\~)$w\ݜ60@HḼЌ (-9oG5u<|/m}'m.Z%%1]vp/gp&}s.V[1ɒ:8)(~3Iqe*9]0a+Qa$ʏ.-o\8g+))MZ:7]Y{t/L9d7j da2o܂Z7&Jxsj_L/W;`&|R  722V?^p67 c̑'5o$e 7gǾmwl"I{;=4DC,RWB%/jVK2z5S}6|q9@'0nMi\[RGU[[50J8V6n r쉘&\,X6$νv }";>Ib͆JL~/hy*6`:D@ PJ ;>ɓRShXc@KS`1*M].I2oi=Rm UcmI6BϡTX8@Žeid O.׊LmLqGb1|V#O,ao@eZZjCDn8y 'W ~Ax@ owч.@TzuĽ&;3ESR}IcC'}F:n$w<ٚ6H:/tep+-.kP@IIIz[¹կFz:뚼 Vw#9yޔ?j$y[ `z.Bȳ&򴺓+890*K5c>Eř^Η<~F#*JJHp,2 =3FBX)W;v:QP*fsTD/~6 W-ӗ \0CBle5T\9_R4*Ur(V,LJ6j{ty{ ˮCQ>*U?gW!Hɇ'L3;(/A 1VwVi`?WJW/vH9{rK"~<%d9`ٚD1Lk-3V({`w9Ćɗ _[mP9dA#$()*ʫ_#Vc`š>tF)We+K uC )+H.O&:@ػzC1'7Z 9>GU^?S[CmCO4He}loi\SӸ9s5waFn-c:X0pd8'Y*22UjE4D߷Qrd>M5R8D~[%ito be٠;)5؇l^.vFa_CW>D==m  ennHsv۲b q6SDB&cYBuA$Jcs7 rD=Vzc-!%0}@1d3vrULg&Ohb "B NhNW$Tf/5dkQkL|Om|hݮ|,|m x=YG=T"Bml"lIf0؂ADK@f ^ )XE^iVꀞ jꗒHS]FK㙤\sѓ%. yܧ ョX D ؑThk.hс% me1 gr©uȨyŲ8FGc>!/XiJJJ&'ONJV?(Oz7Y)aR#yE7`}Vd賣=~(*dD/ٗ;C2ю\j<}"®_5דZ0SRRR[EmΙUV=0=eRB3P譈=pBj% !3OCjV)yE_-Pǯr ec J)"1$*t4Fɜjt-pwjEr,{/蟊!j0^ňQ/.+ȝ 0{Uߘa5{yct"HS?c?A ֣SRRRՎ ڛTruL!/LSX~N IDAT^EPV:,_ܬ'Y}%J[ #2~(C73N&DVMI7aSpP,: |p$9Ҫf+!=}Ms[^ݶ5)ULü#l%}>2:%hkr߂w|xZ/95: ER, klݥsGEIII'wږZ&  οu2K鑁4W6P"fP%MKɡWBItۄû?|0MVJkg H~Uw,GCT\&|_Q}׷Hv¸lL_܏ؓ Vlc 9e{EEf Eib %ߟ^͌'H`(h %ص(kTNiUvmgk_x26c%|UI[ %MK"Z@ {cۋFID;/N9ڎd \a2D[ # Ҋscl$'¾ؿ^-fNYV86M8Tq$Na \T̥xn "X"@Z'Vk _ǒy)qzrؖDޕ`4if1u%$ yH*ьpʁpSr+2&Fm?$qhxWkDEII2{&mFI96bwOXmXwP|yl;2@9kK3u*Yqc74a[1"]ݱ-%[㤤4Q(8wN5])`J|yV.w bG!/2XA᫲l*ɼfz ~sIob))}ʖҳV=ӹ(~2p '6.֗ ׅVJT~o>+.-aSJ'Wޓ96j)i7[Z)tcq4WmXׄ=a%H?swa2\H}j$$-}E\8VW3pDtޒ5*AIL]30f@R߅^Y$;7x5#\]1b;S%w_V+-uS]-PVA|_hinHnp:y\$F&g 3oC'(HS~nH>M*,~?m9Ş%? jVč1FRG4Ju M߄BTMo uHrҵ 朳=tW+ܗG#xo:SaU1hY)蒦iW"iAr 0s!_dݭxAy񠱚/4i^b 3',*)+y񺞿_-?<7@#3UO2ȺgqZXe*@o%0-BIAaa8K~)))P>p~sr=矼(vZ}||ksq4&<k,%'zʓ6e-՚οoRLAGG> ]`j\ @ WsEd%\wB+*[œ2I]Q9}m(_Ϫ[禃1d0]x),U˚*^қdn0+ٺ0‡$ɽ\k3ESҲヌH*2CڑIH ˴JrCJEM M"b lTk2K ͪ]D Ai6p|$~Fƒ1AC,Aj@#ܿCym%" b_Z|`RF69Y}69Z'* sP>Vhh L ӽm+]gfR̰BLaW\_Q[<@1[|Q!B\U%cAH14盛Y'y1?|x$  2z#æ 9MufZʁ]&i]{{7"ƆyӜnCZwXd0j})%y*DzXNda:)i@xzR@< K11())5~eiKa;kd/"NSJ8 .i2ZTc @ʶ,1CJ9eR+7Fmp;,ëI %%aqatRs#SXݫ}'9pgwUJy6WoS}ώZ?1hhh2}xAұ܅MUehW$ <LR2 Oq'IIIt'~~yuu~.W$/6׆tN-Z Loö4aUrYKJwnixЇos4ޚ(`#4qj״Z 6 =cْyRe2Uff kU( .OV6C%7rܕÍBңl>5^N&] mB|A諃ب P/_Bt MRak⹰^!l`锒a% F+ACG( tOϷ$u |\=b{U1rS-rmRdv@6`PLIlVܻ"<(McYe4 c6- Vl86Տ |YR h(ًC[[ڀMRiDڞj -+Zv•[8>FQo.x ֱJK&Ɠ7n,;[;6dJ!bpuF2yNR:28i i}8I9#CEe`Y2,0͏93g%祔g+dI(ˈ|K80r=ϐ;-ϧ~JJJ]yq2GT^DpE{I(3+KٴmgZL{T ꎒHԃHa "- /LJIIiz0j$đt~1_+5A]{ݢMTypI6>8VY8JCE]*ai%pN Ciy5(<ңjZB] }y0Wr2Rr*YKmm)plh+sgKnDo2/O )dEN,öI=l=\QR]TLRp?꣪w B(zSB^E rs]|geMb~VQg.ƪ$ ڔΖ1,^ʝ%Y%lj{½8?!%z-$PውӍce4 MXkNz\0k6?_0G>I?]݈Tٍ^fE-F "߀?c{ :DO߈k uArs=5R)rH`ׅd8N! jL5{f*$7"+L֮5KV~k3]Z)'=X240XmDCNŅ64P|Y7out*G^b 9QQ)-&ކ_$^#JJ(M';tX+[-{=ybeοc5źFĹJ9ݕr:KE} i`C/k%Poǻ*q$>C>V&;1/0T:^JJc%i[Qw::}R4IϪft/&è6MYnZ n;kol{)I~*sm_+@%CJ}IfDb5Ĉ%3dнb@IIIQ~A8Mߧ 8ӍMkT6APc EhgE_J&;;sJC]  gײȼYR9tZyjuky԰gק)U;KvșB a""b Jޘҫ<~_ YїY"GH6,,7M[B?k%)&dy?x R+w3k%֛. DJp6yW2%)7'#aa&-;!ĭȡ#q&u_|iWB! #l̆fyKg-", QyLGir}c{D^&.@qo˝#׵J/#ެm)XEb\!W[@̠GU=d`F$l ^TM {XyV#A3N 0Z, 1ބ0T ;-c{c-u5:ׯI$>!,M)HG3'/RgMf!dR AR&jE7* )+6Ҿn}igX47q#Y riWع{ɾWg{tr zaiSp_^~dlTe'tUePREV&y65} 70@W`!H/J@ڜ7m?pBb,? -1IGB/g]BƌGĿ뽧P:"T-?iQz6û2FJD$^t'E115{#/G'QseuL\ü9Ѻt_QR~V/ "2P>ȇGiBȋ3'ݻ"**@|`= CƱ\OCG[ҭof; Js6(7+o GDCW;JJ:}cJq, O` 폚/{k)%0 doynfP}͟q +䟄L~dUKtZP-e#gI.. bw:Y/_goyT-EA*Ŭu*#ZaImaKI/dᑴ(:D I.cx߲ @Cscq"6 `0\3)^dW> v}eŨFDMo)E`BEDC@cR(̥^DiYSes|m0{&?D233S.1,~^Q\>hm>GC~F[/fE^nvT$%j2lObEYXOIObD&RJU|An$&xϑ@rU'J墆gW=ĢfNL_A;.~eV8NQ[mh7 v+bk*(yfxޯ&ywƒJXᆯӱkTg^ܤ`>J/8NVlrs,$Kr8Ŭ=G IDAT%zn"y)Rc.gUÆJI F+)))U#9[8?W1bSђO*Xnp+.ilC'U"ek2Vv;H\ZMƓT^$UW$s$ *m y͆%(.=\COr 퓤@\VrN 5lnu`n6TӗwDR^gZ&'(|]Aqo]Q;3B[Z䴽sY[~H衐 b)*¸XC;뽛J~Ze1hGx,TgʡW~J!"ԄN>/uWdd'Y&Lw0w;HԳR+=+Mvʌ+7,ҁ%[o mش0IJL`/3 R;' 1@du eBΠfw)Pmʛ@45H%}soop>3h |+QRRRTFبRX.)ae r츍 (oXQ4>I\;D xWR6 }4,D:MlM}E=`3u3뻅&O $,9 ̝?ٌ݋6*RJa(Sw"GTWR[Eδ&JM-2/FΜmxZ{*ʒ>2岰A#uGE<%N_R;(g:>"L-hE]qg]?PRRRRRRzRev Ka'~4q1N=aIObZ͕S(7c1d%%CЄb>VH(/t.Aܻ"{CgMt4+،Yh WPJ$]xF>9u#:> |CJ_˟ZЈ%-lig(WpN;/-,b5!"_ƺɭmyCqʭ^aZhC[ !~3Y&[,VhDEIII)dF REaȥ.Ҵ&2=zEdM GPu}w~`@;VkTث)pxE7rEԃ@$,<тݜ,,.`P܀iqٗŸl036*YheZ0ؕ+\KݾtY,J$^@GUJ}s["0[dXn l0cίsv+M_ج*QFsW@v>]`\!K#:'xF.ϋB˦w}=,Rv=^^7Gf JV D-WF{,nG4AAhKRRJy@(}rowKL0H"]%}j%&TcJR%%-&?wo{aGD8j,a'M#rVw3.JJJҝ#^wZ)a v-ٍvXyM3+EQ-Vۿ5*2X"īc)DܧԻ.CSj+JI>pYR~t(u/Q 9л p_n݂)a]JW*MX:}4ٖ3yaC¶ާ{KvMMэlj !u=SQCIir+etڕsK _YeJ:Bߌ}H<F16C`*2ڙB`ڔݟFMyfeix@'ʆrZ".au.͎'2G[u;֥ѫݶ( J" ݀(`+'ğyP"q0}ml p/WRl[,Zºo[LNécSW)yz< &Z|rujϞp/icY[s}Hk;;P*;) 3/d dgBX9_OL+khʧ,^'K$vIn縹=ϊk\D TXDA \8i|Æ]B*Ic7tle??,M΢aV6ݱ :-dZșY8xD6Y'*~CԔK.Jz5AS\淺B:<Xᷱ%ʊKH<:ߌB ӆ ;<$]-z0UOw>Jফ(>'-OʮHiZ&" 4Zm E$?kTT!b n nQnG&'`%J/J(w$b#$z%Ճ|=2Z vѿJMWJ y"aȦOC&\y y.F%U g'O%wf\}ӕ( -r(뙔7B-5Hؒuud ܒT8<˨1+LHu5K1m \}y4᪻5ubodI󲞈U{J]bˎM,QOYl@!m|L}Bw3Jku(hҥ>kՠvȵٲ){b Ǹ+uGtG$QrX=k'7a!':7%ɽ|%~"$mU#TD4кSZo_~K]4)2__}y$/+Q;1 ?vwGșbN%OQ뫍aߓizGLB%- ʜ /7@m9#74z@4cY}P娸'g5_3.iniLfG9jkR2|D,=51 W$:Jr8VT!-U:6S,QNx=h%]'&Ub2-[B^7V", ?QQRRRz~{RtL'y/ÐRKr%R(GIw7T~~Ebz˖e6gV OTbK__^5TһCHV4܁"Vn2w8KvbHa.%)u ~0_` AؖYl.ۡ1&X"S؂DGb#gyM6'9_` T7wO;7YX>YS41FIbN54(u;3]],p%nu^]`׫HNJU !NKP3-(s\Kȯ;o pƣ&r?ώҺZX|]O= uY)HoSbӗ+_&0_k@gWZVMȡ DÆQvB>aUO!pB~I6ɗUI]XPϝh)N_*o' XI*"Jz-ۍIc9-K@᩹bJANW3j'Z V[eGǵ7堁o#(UgxuJkZ)p/uw`KI=Tu=N1 \DuNH'JJJJ[֛[NВxpKzTG ۘ_a,D`L*l:RNhuQWoPa׸!ʘ+ڡEyVRR\FHz)hT*;&r;m\c]wgt_tXZmݨ믇] 5*,^1c*fY)‡rl%zaċH#4Q#*JJKݐ Vj_霛>[J2^b[k5McW~M$a:oZ]GA0y>ab15k>X'K$6'&֯6[0ǜuljP1oR2BsOGn6}m>s6V+]r#"eGL:yo )ϓ8QwGxMd/&ʋ]A!G lԒ19&q$%ZMY^Aَ]uRI*hpP$Z~ FolY۶LH y@̌yQ:Z+>"*Uƌ$kC6:acKЎ@obuyD6kKAč, g~i$U3EX%j>U?]SV& ##aȼզWuwOoCr0+2.^Akj{x]-:t{iOzרM=W}Q29܆I,*;yt ^j"ۺ [_Qj#ytR@&3KS;yUJ9JGb/Z+ /^v]򼌤A)5*¤4:t4OOL65JԱ\&0ւ@D.*[P:3n@ug iMbnL9kr2ҡCnx2fa moGZ _0'44lr`&OT@zXc Mfic.$ViDXiQѡ1CQa|􆞜SXs_1z,K8(ހAN?#]-@H5RVϙMsNy[م8#/GEIX6V./AN˼5I2^ޙtZ]W5*s W{)pkسXOS6}̤zί?o $Ů }WPǏV:OPnwok?}nʤ_(q6wCsU6)%:mHr]PmcJM;}~h&; X "05">ҀՍ ok5*Mw:rZ*Vk2òYO1-XdU,Cn|WKȋzjUyؘ"z5Nh}q8A@"3UW8kMIDAT1JcI?Vj]whΜCUOs~3j}2‚i{$x>u%Wp2trwenB[O. */ #include "miregl.h" #include #include class MirEglApp { public: MirEglApp(MirConnection* const connection, MirPixelFormat pixel_format); EGLSurface create_surface(MirSurface* surface); void release_current(); void make_current(EGLSurface eglsurface) const; void swap_buffers(EGLSurface eglsurface) const; void destroy_surface(EGLSurface eglsurface) const; void get_surface_size(EGLSurface eglsurface, int* width, int* height) const; void set_swap_interval(EGLSurface eglsurface, int interval) const; bool supports_surfaceless_context(); ~MirEglApp(); MirConnection* const connection; private: EGLDisplay egldisplay; EGLContext eglctx; EGLConfig eglconfig; EGLint neglconfigs; EGLSurface dummy_surface; }; std::shared_ptr make_mir_eglapp( MirConnection* const connection, MirPixelFormat const& pixel_format) { return std::make_shared(connection, pixel_format); } namespace { MirSurface* create_surface(MirConnection* const connection, MirSurfaceParameters const& surfaceparm) { auto const spec = mir_connection_create_spec_for_normal_surface( connection, surfaceparm.width, surfaceparm.height, surfaceparm.pixel_format); mir_surface_spec_set_name(spec, surfaceparm.name); mir_surface_spec_set_buffer_usage(spec, surfaceparm.buffer_usage); mir_surface_spec_set_fullscreen_on_output(spec, surfaceparm.output_id); auto const surface = mir_surface_create_sync(spec); mir_surface_spec_release(spec); if (!mir_surface_is_valid(surface)) throw std::runtime_error(std::string("Can't create a surface ") + mir_surface_get_error_message(surface)); if (surfaceparm.output_id != mir_display_output_id_invalid) mir_surface_set_state(surface, mir_surface_state_fullscreen); return surface; } } MirEglSurface::MirEglSurface(std::shared_ptr const& mir_egl_app, MirSurfaceParameters const& surfaceparm, int swapinterval) : mir_egl_app{mir_egl_app}, surface{create_surface(mir_egl_app->connection, surfaceparm)}, eglsurface{mir_egl_app->create_surface(surface)}, width_{0}, height_{0} { mir_egl_app->set_swap_interval(eglsurface, swapinterval); } MirEglSurface::~MirEglSurface() { mir_egl_app->destroy_surface(eglsurface); mir_surface_release_sync(surface); } void MirEglSurface::egl_make_current() { mir_egl_app->get_surface_size(eglsurface, &width_, &height_); mir_egl_app->make_current(eglsurface); } void MirEglSurface::swap_buffers() { mir_egl_app->swap_buffers(eglsurface); } unsigned int MirEglSurface::width() const { return width_; } unsigned int MirEglSurface::height() const { return height_; } MirEglApp::MirEglApp(MirConnection* const connection, MirPixelFormat pixel_format) : connection{connection}, dummy_surface{EGL_NO_SURFACE} { unsigned int bpp = 8*MIR_BYTES_PER_PIXEL(pixel_format); EGLint attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_BUFFER_SIZE, (EGLint) bpp, EGL_NONE }; egldisplay = eglGetDisplay((EGLNativeDisplayType) mir_connection_get_egl_native_display(connection)); if (egldisplay == EGL_NO_DISPLAY) throw std::runtime_error("Can't eglGetDisplay"); EGLint major; EGLint minor; if (!eglInitialize(egldisplay, &major, &minor)) throw std::runtime_error("Can't eglInitialize"); if (major != 1 || minor != 4) throw std::runtime_error("EGL version is not 1.4"); if (!eglChooseConfig(egldisplay, attribs, &eglconfig, 1, &neglconfigs)) throw std::runtime_error("Could not eglChooseConfig"); if (neglconfigs == 0) throw std::runtime_error("No EGL config available"); EGLint ctxattribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; eglctx = eglCreateContext(egldisplay, eglconfig, EGL_NO_CONTEXT, ctxattribs); if (eglctx == EGL_NO_CONTEXT) throw std::runtime_error("eglCreateContext failed"); if (!supports_surfaceless_context()) { static EGLint const dummy_pbuffer_attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }; dummy_surface = eglCreatePbufferSurface(egldisplay, eglconfig, dummy_pbuffer_attribs); if (dummy_surface == EGL_NO_SURFACE) throw std::runtime_error("eglCreatePbufferSurface failed"); } make_current(dummy_surface); } EGLSurface MirEglApp::create_surface(MirSurface* surface) { auto const eglsurface = eglCreateWindowSurface( egldisplay, eglconfig, (EGLNativeWindowType) mir_buffer_stream_get_egl_native_window(mir_surface_get_buffer_stream(surface)), NULL); if (eglsurface == EGL_NO_SURFACE) throw std::runtime_error("eglCreateWindowSurface failed"); return eglsurface; } void MirEglApp::make_current(EGLSurface eglsurface) const { if (!eglMakeCurrent(egldisplay, eglsurface, eglsurface, eglctx)) throw std::runtime_error("Can't eglMakeCurrent"); } void MirEglApp::swap_buffers(EGLSurface eglsurface) const { eglSwapBuffers(egldisplay, eglsurface); } void MirEglApp::destroy_surface(EGLSurface eglsurface) const { eglDestroySurface(egldisplay, eglsurface); } void MirEglApp::get_surface_size(EGLSurface eglsurface, int* width, int* height) const { eglQuerySurface(egldisplay, eglsurface, EGL_WIDTH, width); eglQuerySurface(egldisplay, eglsurface, EGL_HEIGHT, height); } void MirEglApp::set_swap_interval(EGLSurface eglsurface, int interval) const { auto const previous_surface = eglGetCurrentSurface(EGL_DRAW); make_current(eglsurface); eglSwapInterval(egldisplay, interval); if (previous_surface != EGL_NO_SURFACE) make_current(previous_surface); } bool MirEglApp::supports_surfaceless_context() { auto const extensions = eglQueryString(egldisplay, EGL_EXTENSIONS); if (!extensions) return false; return std::strstr(extensions, "EGL_KHR_surfaceless_context") != nullptr; } MirEglApp::~MirEglApp() { eglMakeCurrent(egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (dummy_surface != EGL_NO_SURFACE) destroy_surface(dummy_surface); eglDestroyContext(egldisplay, eglctx); eglTerminate(egldisplay); mir_connection_release(connection); } unity-system-compositor-0.4.3+16.04.20160323/spinner/eglapp.cpp0000644000015600001650000002026212674474272024420 0ustar pbuserpbgroup00000000000000/* * Copyright © 2013, 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 warranty of * MERCHANTABILITY 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 . */ #include "eglapp.h" #include "miregl.h" float mir_eglapp_background_opacity = 1.0f; static const char appname[] = "eglspinner"; namespace { template void for_each_active_output( MirConnection* const connection, ActiveOutputHandler const& handler) { /* eglapps are interested in the screen size, so use mir_connection_create_display_config */ MirDisplayConfiguration* display_config = mir_connection_create_display_config(connection); for (MirDisplayOutput* output = display_config->outputs; output != display_config->outputs + display_config->num_outputs; ++output) { if (output->used && output->connected && output->num_modes && output->current_mode < output->num_modes) { handler(output); } } mir_display_config_destroy(display_config); } MirPixelFormat select_pixel_format(MirConnection* connection) { unsigned int format[mir_pixel_formats]; unsigned int nformats; mir_connection_get_available_surface_formats( connection, (MirPixelFormat*) format, mir_pixel_formats, &nformats); auto const pixel_format = (MirPixelFormat) format[0]; printf("Server supports %d of %d surface pixel formats. Using format: %d\n", nformats, mir_pixel_formats, pixel_format); return pixel_format; } } std::vector> mir_eglapp_init(int argc, char *argv[]) { MirSurfaceParameters surfaceparm = { "eglappsurface", 0, 0, mir_pixel_format_xbgr_8888, mir_buffer_usage_hardware, mir_display_output_id_invalid }; EGLint swapinterval = 1; char *mir_socket = NULL; if (argc > 1) { int i; for (i = 1; i < argc; i++) { bool help = 0; const char *arg = argv[i]; if (arg[0] == '-') { switch (arg[1]) { case 'b': { float alpha = 1.0f; arg += 2; if (!arg[0] && i < argc-1) { i++; arg = argv[i]; } if (sscanf(arg, "%f", &alpha) == 1) { mir_eglapp_background_opacity = alpha; } else { printf("Invalid opacity value: %s\n", arg); help = 1; } } break; case 'n': swapinterval = 0; break; case 'o': { unsigned int output_id = 0; arg += 2; if (!arg[0] && i < argc-1) { i++; arg = argv[i]; } if (sscanf(arg, "%u", &output_id) == 1) { surfaceparm.output_id = output_id; } else { printf("Invalid output ID: %s\n", arg); help = 1; } } break; case 's': { unsigned int w, h; arg += 2; if (!arg[0] && i < argc-1) { i++; arg = argv[i]; } if (sscanf(arg, "%ux%u", &w, &h) == 2) { surfaceparm.width = w; surfaceparm.height = h; } else { printf("Invalid size: %s\n", arg); help = 1; } } break; case 'm': mir_socket = argv[++i]; break; case 'q': { FILE *unused = freopen("/dev/null", "a", stdout); (void)unused; break; } case 'h': default: help = 1; break; } } else { help = 1; } if (help) { printf("Usage: %s []\n" " -b Background opacity (0.0 - 1.0)\n" " -h Show this help text\n" " -o ID Force placement on output monitor ID\n" " -n Don't sync to vblank\n" " -m socket Mir server socket\n" " -s WIDTHxHEIGHT Force surface size\n" " -q Quiet mode (no messages output)\n" , argv[0]); return {}; } } } MirConnection* const connection{mir_connect_sync(mir_socket, appname)}; if (!mir_connection_is_valid(connection)) throw std::runtime_error(mir_connection_get_error_message(connection)); auto const pixel_format = select_pixel_format(connection); surfaceparm.pixel_format = pixel_format; auto const mir_egl_app = make_mir_eglapp(connection, pixel_format); std::vector> result; // If a size has been specified just do that if (surfaceparm.width && surfaceparm.height) { result.push_back(std::make_shared(mir_egl_app, surfaceparm, swapinterval)); return result; } // If an output has been specified just do that if (surfaceparm.output_id != mir_display_output_id_invalid) { for_each_active_output(connection, [&](MirDisplayOutput const* output) { if (output->output_id == surfaceparm.output_id) { auto const& mode = output->modes[output->current_mode]; surfaceparm.width = mode.horizontal_resolution; surfaceparm.height = mode.vertical_resolution; } }); result.push_back(std::make_shared(mir_egl_app, surfaceparm, swapinterval)); return result; } // but normally, we're fullscreen on every active output for_each_active_output(connection, [&](MirDisplayOutput const* output) { auto const& mode = output->modes[output->current_mode]; printf("Active output [%u] at (%d, %d) is %dx%d\n", output->output_id, output->position_x, output->position_y, mode.horizontal_resolution, mode.vertical_resolution); surfaceparm.width = mode.horizontal_resolution; surfaceparm.height = mode.vertical_resolution; surfaceparm.output_id = output->output_id; result.push_back(std::make_shared(mir_egl_app, surfaceparm, swapinterval)); }); if (result.empty()) throw std::runtime_error("No active outputs found."); return result; } unity-system-compositor-0.4.3+16.04.20160323/spinner/eglapp.h0000644000015600001650000000164212674474272024066 0ustar pbuserpbgroup00000000000000/* * Copyright © 2013 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 warranty of * MERCHANTABILITY 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: Daniel van Vugt */ #ifndef __EGLAPP_H__ #define __EGLAPP_H__ #include #include class MirEglSurface; extern float mir_eglapp_background_opacity; std::vector> mir_eglapp_init(int argc, char *argv[]); #endif unity-system-compositor-0.4.3+16.04.20160323/spinner/eglspinner.cpp0000644000015600001650000004073312674474272025323 0ustar pbuserpbgroup00000000000000/* * Copyright © 2013-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 warranty of * MERCHANTABILITY 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 . * * Authors: Daniel van Vugt * Mirco Müller * Alan Griffiths * Kevin DuBois */ #include "eglapp.h" #include "miregl.h" #include #include #include #include #if HAVE_PROPS #include #endif #include #include #include #include #include #include #define GLM_FORCE_RADIANS #include #include #include "wallpaper.h" #include "logo.h" #include "white_dot.h" #include "orange_dot.h" enum TextureIds { WALLPAPER = 0, LOGO, WHITE_DOT, ORANGE_DOT, MAX_TEXTURES }; class SessionConfig { public: SessionConfig() { parse_session_conf_file(); } int get_int(std::string const& key, int default_value) { try { if (conf_map.find(key) != conf_map.end()) return std::stoi(conf_map[key]); } catch (...) { } return default_value; } std::string get_string(std::string const& key, std::string const& default_value) { return conf_map.find(key) != conf_map.end() ? conf_map[key] : default_value; } private: void parse_session_conf_file() { std::ifstream fs{default_file}; #ifdef HAVE_PROPS if (!fs.is_open()) { fs.clear(); char const* default_value = ""; char value[PROP_VALUE_MAX]; property_get(device_property_key, value, default_value); fs.open(file_base + value + file_extension); } #endif std::string line; while (std::getline(fs, line)) conf_map.insert(parse_key_value_pair(line)); } std::string trim(std::string const& s) { auto const wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { return std::isspace(c); }); auto const wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c) { return std::isspace(c); }).base(); return (wsback <= wsfront ? std::string() : std::string(wsfront, wsback)); } std::pair parse_key_value_pair(std::string kv) { auto const separator = kv.find("="); auto const key = kv.substr(0, separator); auto const value = separator != std::string::npos ? kv.substr(separator + 1, std::string::npos) : std::string{}; return {trim(key), trim(value)}; } std::string const default_file{"/etc/ubuntu-touch-session.d/android.conf"}; std::string const file_base{"/etc/ubuntu-touch-session.d/"}; std::string const file_extension{".conf"}; char const* device_property_key = "ro.product.device"; std::map conf_map; }; static GLuint load_shader(const char *src, GLenum type) { GLuint shader = glCreateShader(type); if (shader) { GLint compiled; glShaderSource(shader, 1, &src, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (!compiled) { GLchar log[1024]; glGetShaderInfoLog(shader, sizeof log - 1, NULL, log); log[sizeof log - 1] = '\0'; printf("load_shader compile failed: %s\n", log); glDeleteShader(shader); shader = 0; } } return shader; } // Colours from http://design.ubuntu.com/brand/colour-palette //#define MID_AUBERGINE 0.368627451f, 0.152941176f, 0.31372549f //#define ORANGE 0.866666667f, 0.282352941f, 0.141414141f //#define WARM_GREY 0.682352941f, 0.654901961f, 0.623529412f //#define COOL_GREY 0.2f, 0.2f, 0.2f //#define LIGHT_AUBERGINE 0.466666667f, 0.297297297f, 0.435294118f //#define DARK_AUBERGINE 0.17254902f, 0.0f, 0.117647059f #define BLACK 0.0f, 0.0f, 0.0f //#define WHITE 1.0f, 1.0f, 1.0f template void uploadTexture (GLuint id, Image& image) { glBindTexture(GL_TEXTURE_2D, id); GLint format = GL_RGBA; if (image.bytes_per_pixel == 3) format = GL_RGB; glTexImage2D(GL_TEXTURE_2D, 0, format, image.width, image.height, 0, format, GL_UNSIGNED_BYTE, image.pixel_data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } GLuint createShaderProgram(const char* vertexShaderSrc, const char* fragmentShaderSrc) { if (!vertexShaderSrc || !fragmentShaderSrc) return 0; GLuint vShaderId = 0; vShaderId = load_shader(vertexShaderSrc, GL_VERTEX_SHADER); assert(vShaderId); GLuint fShaderId = 0; fShaderId = load_shader(fragmentShaderSrc, GL_FRAGMENT_SHADER); assert(fShaderId); GLuint progId = 0; progId = glCreateProgram(); assert(progId); glAttachShader(progId, vShaderId); glAttachShader(progId, fShaderId); glLinkProgram(progId); GLint linked = 0; glGetProgramiv(progId, GL_LINK_STATUS, &linked); if (!linked) { GLchar log[1024]; glGetProgramInfoLog(progId, sizeof log - 1, NULL, log); log[sizeof log - 1] = '\0'; printf("Link failed: %s\n", log); return 0; } return progId; } typedef struct _AnimationValues { double lastTimeStamp; GLfloat fadeBackground; int dot_mask; } AnimationValues; void updateAnimation (GTimer* timer, AnimationValues* anim) { if (!timer || !anim) return; static const int sequence[] = {0, 1, 3, 7, 15, 31}; static int counter = 0; static double second = 0.0f; double elapsed = g_timer_elapsed (timer, NULL); if (second >= 1.0f) { second = 0.0f; counter++; } else { second += elapsed - anim->lastTimeStamp; } anim->dot_mask = sequence[counter%6]; anim->lastTimeStamp = elapsed; } namespace { const char vShaderSrcPlain[] = "attribute vec4 aPosition; \n" "attribute vec2 aTexCoords; \n" "uniform vec2 uOffset; \n" "varying vec2 vTexCoords; \n" "uniform mat4 uProjMat; \n" "void main() \n" "{ \n" " vTexCoords = aTexCoords + vec2 (0.5, 0.5); \n" " gl_Position = uProjMat * vec4(aPosition.xy + uOffset.xy, 0.0, 1.0);\n" "} \n"; const char fShaderSrcPlain[] = "precision mediump float; \n" "varying vec2 vTexCoords; \n" "uniform sampler2D uSampler; \n" "void main() \n" "{ \n" " gl_FragColor = texture2D(uSampler, vTexCoords);\n" "} \n"; static volatile sig_atomic_t running = 0; static void shutdown(int signum) { if (running) { running = 0; printf("Signal %d received. Good night.\n", signum); } } bool mir_eglapp_running() { return running; } } int main(int argc, char *argv[]) try { GLuint prog[3]; GLuint texture[MAX_TEXTURES]; GLint vpos[MAX_TEXTURES]; GLint aTexCoords[MAX_TEXTURES]; GLint sampler[MAX_TEXTURES]; GLint offset[MAX_TEXTURES]; GLint projMat[MAX_TEXTURES]; SessionConfig session_config; auto const surfaces = mir_eglapp_init(argc, argv); if (!surfaces.size()) { printf("No surfaces created\n"); return EXIT_SUCCESS; } running = 1; signal(SIGINT, shutdown); signal(SIGTERM, shutdown); const GLfloat texCoords[] = { 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f, 0.5f, }; prog[WALLPAPER] = createShaderProgram(vShaderSrcPlain, fShaderSrcPlain); prog[LOGO] = createShaderProgram(vShaderSrcPlain, fShaderSrcPlain); prog[WHITE_DOT] = createShaderProgram(vShaderSrcPlain, fShaderSrcPlain); // setup proper GL-blending glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); // get locations of shader-attributes/uniforms vpos[WALLPAPER] = glGetAttribLocation(prog[WALLPAPER], "aPosition"); aTexCoords[WALLPAPER] = glGetAttribLocation(prog[WALLPAPER], "aTexCoords"); sampler[WALLPAPER] = glGetUniformLocation(prog[WALLPAPER], "uSampler"); offset[WALLPAPER] = glGetUniformLocation(prog[WALLPAPER], "uOffset"); projMat[WALLPAPER] = glGetUniformLocation(prog[WALLPAPER], "uProjMat"); vpos[LOGO] = glGetAttribLocation(prog[LOGO], "aPosition"); aTexCoords[LOGO] = glGetAttribLocation(prog[LOGO], "aTexCoords"); sampler[LOGO] = glGetUniformLocation(prog[LOGO], "uSampler"); offset[LOGO] = glGetUniformLocation(prog[LOGO], "uOffset"); projMat[LOGO] = glGetUniformLocation(prog[LOGO], "uProjMat"); vpos[WHITE_DOT] = glGetAttribLocation(prog[WHITE_DOT], "aPosition"); aTexCoords[WHITE_DOT] = glGetAttribLocation(prog[WHITE_DOT], "aTexCoords"); sampler[WHITE_DOT] = glGetUniformLocation(prog[WHITE_DOT], "uSampler"); offset[WHITE_DOT] = glGetUniformLocation(prog[WHITE_DOT], "uOffset"); projMat[WHITE_DOT] = glGetUniformLocation(prog[WHITE_DOT], "uProjMat"); // create and upload spinner-artwork // note that the embedded image data has pre-multiplied alpha glGenTextures(MAX_TEXTURES, texture); uploadTexture(texture[WALLPAPER], wallpaper); uploadTexture(texture[LOGO], logo); uploadTexture(texture[WHITE_DOT], white_dot); uploadTexture(texture[ORANGE_DOT], orange_dot); // bunch of shader-attributes to enable glVertexAttribPointer(aTexCoords[WALLPAPER], 2, GL_FLOAT, GL_FALSE, 0, texCoords); glEnableVertexAttribArray(vpos[WALLPAPER]); glEnableVertexAttribArray(aTexCoords[WALLPAPER]); glVertexAttribPointer(aTexCoords[LOGO], 2, GL_FLOAT, GL_FALSE, 0, texCoords); glEnableVertexAttribArray(vpos[LOGO]); glEnableVertexAttribArray(aTexCoords[LOGO]); glVertexAttribPointer(aTexCoords[WHITE_DOT], 2, GL_FLOAT, GL_FALSE, 0, texCoords); glEnableVertexAttribArray(vpos[WHITE_DOT]); glEnableVertexAttribArray(aTexCoords[WHITE_DOT]); glActiveTexture(GL_TEXTURE0); AnimationValues anim = {0.0, 0.0, 0}; GTimer* timer = g_timer_new(); auto const pixels_per_gu = session_config.get_int("GRID_UNIT_PX", 13); auto const gu2px = [pixels_per_gu] (float gu) { return pixels_per_gu * gu; }; auto const native_orientation = session_config.get_string("NATIVE_ORIENTATION", ""); std::cout << "Spinner using pixels per grid unit: " << pixels_per_gu << std::endl; std::cout << "Spinner using native orientation: '" << native_orientation << "'" << std::endl; while (mir_eglapp_running()) { for (auto const& surface : surfaces) surface->paint([&](unsigned int width, unsigned int height) { bool const needs_rotation = (width < height && native_orientation == "landscape") || (width > height && native_orientation == "portrait"); GLfloat logoWidth = gu2px (14.5f); GLfloat logoHeight = gu2px (3.0f); GLfloat logoXOffset = gu2px (1.0f); GLfloat dotSize = gu2px (0.5f); GLfloat dotXGap = gu2px (2.5f); GLfloat dotYGap = gu2px (2.0f); auto render_width = width; auto render_height = height; if (needs_rotation) std::swap(render_width, render_height); const GLfloat fullscreen[] = { (GLfloat) render_width, 0.0f, (GLfloat) render_width, (GLfloat) render_height, 0.0f, 0.0f, 0.0f, (GLfloat) render_height }; const GLfloat logo[] = { logoWidth, 0.0f, logoWidth, logoHeight, 0.0f, 0.0f, 0.0f, logoHeight }; const GLfloat dot[] = { dotSize, 0.0f, dotSize, dotSize, 0.0f, 0.0f, 0.0f, dotSize }; auto mvpMatrix = glm::mat4(1.0f); if (needs_rotation) mvpMatrix = glm::rotate(mvpMatrix, glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); mvpMatrix = glm::translate(mvpMatrix, glm::vec3(-1.0, -1.0, 0.0f)); mvpMatrix = glm::scale(mvpMatrix, glm::vec3(2.0f / render_width, 2.0f / render_height, 1.0f)); auto const projMatrix = glm::ortho(-1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f); mvpMatrix = projMatrix * mvpMatrix; glViewport(0, 0, width, height); glClearColor(BLACK, anim.fadeBackground); glClear(GL_COLOR_BUFFER_BIT); // draw wallpaper backdrop glVertexAttribPointer(vpos[WALLPAPER], 2, GL_FLOAT, GL_FALSE, 0, fullscreen); glUseProgram(prog[WALLPAPER]); glBindTexture(GL_TEXTURE_2D, texture[WALLPAPER]); glUniform1i(sampler[WALLPAPER], 0); glUniform2f(offset[WALLPAPER], 0.0f, 0.0f); glUniformMatrix4fv(projMat[WALLPAPER], 1, GL_FALSE, glm::value_ptr(mvpMatrix)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // draw logo glVertexAttribPointer(vpos[LOGO], 2, GL_FLOAT, GL_FALSE, 0, logo); glUseProgram(prog[LOGO]); glBindTexture(GL_TEXTURE_2D, texture[LOGO]); glUniform1i(sampler[LOGO], 0); glUniform2f(offset[LOGO], render_width/2.0f - logoWidth / 2.0f + logoXOffset, render_height / 2.0f - logoHeight * 0.75f); glUniformMatrix4fv(projMat[LOGO], 1, GL_FALSE, glm::value_ptr(mvpMatrix)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // draw white/orange dots glVertexAttribPointer(vpos[WHITE_DOT], 2, GL_FLOAT, GL_FALSE, 0, dot); glUseProgram(prog[WHITE_DOT]); glUniform1i(sampler[WHITE_DOT], 0); glUniformMatrix4fv(projMat[WHITE_DOT], 1, GL_FALSE, glm::value_ptr(mvpMatrix)); for (int i = -2; i < 3; i++) { glBindTexture(GL_TEXTURE_2D, texture[anim.dot_mask >> (i + 2) ? ORANGE_DOT : WHITE_DOT]); glUniform2f(offset[WHITE_DOT], render_width/2.0f + i * dotXGap, render_height / 2.0f + logoHeight / 2.0f + dotYGap - logoHeight * 0.25f); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } }); // update animation variable updateAnimation(timer, &anim); } glDeleteTextures(MAX_TEXTURES, texture); g_timer_destroy (timer); return EXIT_SUCCESS; } catch (std::exception const& x) { printf("%s\n", x.what()); return EXIT_FAILURE; } unity-system-compositor-0.4.3+16.04.20160323/spinner/logo.png0000644000015600001650000001301612674474272024111 0ustar pbuserpbgroup00000000000000PNG  IHDRWWtEXtSoftwareAdobe ImageReadyqe<(iTXtXML:com.adobe.xmp ݔ|IDATxUϊbA. bCņh,|vGc/1h54FE16lFlQcƆ+AE BD6'.o}ηw{wfv̙s4577 .*=UV-V!̩[ʚ*+,~|ʧ*屮S*S*ypSUlo *v%r dz*{ TY(SURV>Tq2",!$+P9Ae*~[*\Qs-!TtUV `e*#U֡r |*;UQ9Q!'/PQe1t{%ljS9BH^&k-UUJ@!]xR*!Ij|2D*"m!d6gOWLz p%+lr*!AU}UJWDz Ae siX)^=d웬L8_s{1\B*|g !TI03Mr 1*R l G`-\V/_&s_Ia5LIЬDH27CqB{>T9־߻LQ*ܩW+ghr tY^e=F=x6h/5s3r'0A&ý;R9BH IbgQ.T *Ӭ,)|}r 4`EJHȷiF4RYW[X]fJ-:Q9BHz, DSxᐟ~%ױ1!G_GY]rP:>c^;T,6YB?!$=8#pYu6HZM{aW]G& |#dHT ώͬ6ێ[I0}g??rT/ǔMQRM͏F>Gfة9x{-͒i)XimV+;gq(2[FJgu6W؀\VKvKo`7٤`Fu<1 )*/)ڿR>?A>W7&!w|<* 醎+M" H|,M6x5eQ :$rjϨn{\1<lY Y[xoT*1r)n;qƬb_c׈|x!-YdDƁ/}PZXE;;x{FV3ZY;LZw#WJ8}ÌC)p--SABXz[vǷznπ2 .u3&0ʃW2z8a3 f,?ǻ<@fMw0~7r}me=l@cf#8s?at}ȋ:vfF6I$Ͼ)@ we/(KU[P8ujA9pPc`k h\A4&p@83})H#LwۘM0uSٖU.'WIp_aT YZ˾?h:~2Y)E8C>\]`U4<0jhH0`T\%!y'$"OTȾ侩c ?pϡ2^24>˩>1&xZM^I%`&bN`vXiV4srHWU9C$>h$~ !RLtޏnN7&0 >W !:e#Uf*"dp7V['8.f i+=$'[" :ʤ\|̉ZC`Gt>*8 г3b6@ ݻsEBac8DWO P9'CT`=DZYr6#pǤN?"[0Rm<:BT]\b7Ƭq{-*!ilD9&,yPe]por 9Q~,w||VYFFl6HHxq,븴KI=K` 6EPN{Q/6ksd:T,nwG{-҄S@90!,[~-uZ 6;{aUUvǦ geq>,Z=GK6mvxl;Ը35\?_|i7R pKe!E7\V>.r s6]`a :*Tt=e%89TBr^|  #3Qxl+uVG̪zL& [$ &_ſkRoM%x]Ra !Ri OB Ʉ$>Z@_[! JNkLJ||97,'$;J nL_ qq IQYe[6I Z8p; QoZVV${ U'J0yӡ&{R'V(w7Sftǡ`C֦Xyf+?Xڵ4+σ7٤*6wN06w9'_ OW݋cgv֕K*g[#%ռ4耽MjalpڔNTIxdzIe: =ת"!6 = FK+S%YB7bCpkVB1\U$Wd$ceN mUnH9l6Uސm\իE\H_?֯r( $I {_F>Ӗ$l6g JI^8MWsAZw2iOzzuU^a34$p<8R2+ŴIq=󑌔n?LL$8QHȐyTx{I $l_a}SKqJ4577ǖ ^ݵ3ogLQϚۖK0a^wRMXZdĞsֈyZrrD9=fΈgĚ00χ*B,6Z>ְ?]}h>f/r(XQMBZ4>Nߔxi/p]ERi|=.V (hgkoCkW+*@bݫUr3O~ {d--U'{]mrr/ƟђI.ʡz,HHV\awTd>)JVp[C -Xh36Y:)f jjafc$Dr>mJ#%aog{,eAfoRC;O6ouiI{E|]ѮkJHI 23x4k?8<|ִ/38b?N sozIENDB`unity-system-compositor-0.4.3+16.04.20160323/spinner/CMakeLists.txt0000644000015600001650000000414612674474272025207 0ustar pbuserpbgroup00000000000000# -*- Mode: CMake; indent-tabs-mode: nil; tab-width: 2 -*- # # Copyright © 2014 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 warranty of # MERCHANTABILITY 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 . function(png2header png header varname) add_custom_command( OUTPUT ${header} COMMAND python ${CMAKE_SOURCE_DIR}/tools/png2header.py ${png} ${varname} > ${header} DEPENDS ${png} ${CMAKE_SOURCE_DIR}/tools/png2header.py ) endfunction() png2header( ${CMAKE_CURRENT_SOURCE_DIR}/wallpaper.png ${CMAKE_CURRENT_BINARY_DIR}/wallpaper.h wallpaper ) png2header( ${CMAKE_CURRENT_SOURCE_DIR}/logo.png ${CMAKE_CURRENT_BINARY_DIR}/logo.h logo ) png2header( ${CMAKE_CURRENT_SOURCE_DIR}/white-dot.png ${CMAKE_CURRENT_BINARY_DIR}/white_dot.h white_dot ) png2header( ${CMAKE_CURRENT_SOURCE_DIR}/orange-dot.png ${CMAKE_CURRENT_BINARY_DIR}/orange_dot.h orange_dot ) include_directories( ${GLIB_INCLUDE_DIRS} ${ANDROIDPROPS_INCLUDE_DIRS} ${GLESv2_INCLUDE_DIRS} ${MIRCLIENT_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} ) if(ANDROIDPROPS_FOUND) add_definitions(-DHAVE_PROPS) endif() link_directories(${MIRCLIENT_LIBRARY_DIRS}) add_executable(unity-system-compositor-spinner eglapp.cpp eglapp.h eglspinner.cpp miregl.h miregl.cpp ${CMAKE_CURRENT_BINARY_DIR}/wallpaper.h ${CMAKE_CURRENT_BINARY_DIR}/logo.h ${CMAKE_CURRENT_BINARY_DIR}/white_dot.h ${CMAKE_CURRENT_BINARY_DIR}/orange_dot.h ) target_link_libraries(unity-system-compositor-spinner EGL ${GLIB_LDFLAGS} ${ANDROIDPROPS_LDFLAGS} ${GLESv2_LIBRARIES} ${MIRCLIENT_LDFLAGS} ) install(TARGETS unity-system-compositor-spinner RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) unity-system-compositor-0.4.3+16.04.20160323/tools/0000755000015600001650000000000012674474477022133 5ustar pbuserpbgroup00000000000000unity-system-compositor-0.4.3+16.04.20160323/tools/png2header.py0000755000015600001650000000470412674474272024525 0ustar pbuserpbgroup00000000000000#!/usr/bin/env python # coding: utf-8 # Copyright © 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 warranty of # MERCHANTABILITY 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 . # # Authored by: Alexandros Frantzis import sys from PIL import Image def premultiply(image): pixels = image.load() for i in range(image.size[0]): for j in range(image.size[1]): orig = pixels[i,j] m = orig[3] / 255.0 pixels[i,j] = (int(orig[0] * m) , int(orig[1] * m), int(orig[2] * m), orig[3]) def tocstring(data): result = '' line_chars = 0 line_limit = 80 for c in data: if line_chars == 0: result += ' "' s = '\\%o' % ord(c) result += s line_chars += len(s) if line_chars >= line_limit: result += '"\n' line_chars = 0 if line_chars != 0: result += '"' return result def bytes_per_pixel(image): if image.mode == 'RGBA': return 4 elif image.mode == 'RGB': return 3 else: raise "Unsupported image mode %s" % image.mode def export(image, variable_name): image_info = (image.size[0], image.size[1], bytes_per_pixel(image)) print "static const struct {" print " unsigned int width;" print " unsigned int height;" print " unsigned int bytes_per_pixel; /* 3:RGB, 4:RGBA */" print " unsigned char pixel_data[%d * %d * %d + 1];" % image_info print "} %s = {" % variable_name print " %d, %d, %d," % image_info print tocstring(image.tobytes()) print "};" def show_usage(): print >>sys.stderr, "Usage: ./png2header.py PNGFILE VARNAME > HEADER_FILE" print >>sys.stderr, "Convert a PNG image to an embeddable C/C++ header file" if len(sys.argv) < 3: show_usage() sys.exit(1) image_filename = sys.argv[1] variable_name = sys.argv[2] image = Image.open(image_filename) if image.mode == 'RGBA': premultiply(image) export(image, variable_name) unity-system-compositor-0.4.3+16.04.20160323/COPYING0000644000015600001650000010451312674474272022023 0ustar pbuserpbgroup00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 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 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . unity-system-compositor-0.4.3+16.04.20160323/CMakeLists.txt0000644000015600001650000000423612674474277023536 0ustar pbuserpbgroup00000000000000# Copyright © 2013 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 warranty of # MERCHANTABILITY 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 . # # Authored by: Robert Ancell project(UnitySystemCompositor) set(USC_VERSION_MAJOR 0) set(USC_VERSION_MINOR 4) set(USC_VERSION_PATCH 3) set(USC_VERSION "${USC_VERSION_MAJOR}.${USC_VERSION_MINOR}.${USC_VERSION_PATCH}") cmake_minimum_required(VERSION 2.8) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(GNUInstallDirs) # Set location of outputs set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib) add_definitions(-DUSC_VERSION="${USC_VERSION}") set(CMAKE_AUTOMOC ON) # Use C++11 and warnings set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -Wall") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall") find_package(PkgConfig) pkg_check_modules(ANDROIDPROPS libandroid-properties) pkg_check_modules(GLIB REQUIRED glib-2.0) pkg_check_modules(MIRCLIENT REQUIRED mirclient) pkg_check_modules(MIRSERVER REQUIRED mirserver) pkg_check_modules(DBUS REQUIRED dbus-1) pkg_check_modules(UBUNTU_PLATFORM_HARDWARE_API ubuntu-platform-hardware-api) find_package(Boost 1.48.0 COMPONENTS system REQUIRED) find_package(GLESv2 REQUIRED) find_package(PIL REQUIRED) if (UBUNTU_PLATFORM_HARDWARE_API_FOUND) add_definitions(-DUSC_HAVE_UBUNTU_PLATFORM_HARDWARE_API) endif() add_subdirectory(spinner/) add_subdirectory(src/) enable_testing() option(MIR_ENABLE_TESTS "Build tests" ON) if (MIR_ENABLE_TESTS) find_package(Gtest REQUIRED) include_directories(${MIRCLIENT_INCLUDE_DIRS} ) include_directories(${GMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIR}) add_subdirectory(tests/) endif () unity-system-compositor-0.4.3+16.04.20160323/HACKING.md0000644000015600001650000000120112674474272022344 0ustar pbuserpbgroup00000000000000unity-system-compositor versioning scheme ----------------------------------------- unity-system-compositor (USC) is versioned using a standard x.y.z versioning scheme, where x is the major version, y is the minor version and z is the patch version. The minor version should be increased at least every time USC is changed to use a newer mir server or mir client API/ABI. In other words, we want to guarantee that all patch releases x.y.\* will continue to work with the same mir library ABIs that x.y.0 was originally released against. This scheme makes it straightforward to manage updates/fixes for released versions as patch releases.