./0000755000015600001650000000000012677054331011105 5ustar jenkinsjenkins./cmake/0000755000015600001650000000000012677054330012164 5ustar jenkinsjenkins./cmake/modules/0000755000015600001650000000000012677054330013634 5ustar jenkinsjenkins./cmake/modules/QmlTest.cmake0000644000015600001650000001741412677054330016236 0ustar jenkinsjenkins# add_qml_test(path component_name [NO_ADD_TEST] [NO_TARGETS] # [TARGETS target1 [target2 [...]]] # [IMPORT_PATHS import_path1 [import_path2 [...]] # [PROPERTIES prop1 value1 [prop2 value2 [...]]]) # # NO_ADD_TEST will prevent adding the test to the "test" target # NO_TARGETS will prevent adding the test to any targets # TARGETS lists the targets the test should be added to # IMPORT_PATHS will pass those paths to qmltestrunner as "-import" arguments # PROPERTIES will be set on the target and test target. See CMake's set_target_properties() # # Two targets will be created: # - testComponentName - Runs the test with qmltestrunner # - tryComponentName - Runs the test with uqmlscene, for manual interaction # # To change/set a default value for the whole test suite, prior to calling add_qml_test, set: # qmltest_DEFAULT_NO_ADD_TEST (default: FALSE) # qmltest_DEFAULT_TARGETS # qmltest_DEFAULT_IMPORT_PATHS # qmltest_DEFAULT_PROPERTIES find_program(qmltestrunner_exe qmltestrunner) if(NOT qmltestrunner_exe) msg(FATAL_ERROR "Could not locate qmltestrunner.") endif() set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene) macro(add_manual_qml_test SUBPATH COMPONENT_NAME) set(options NO_ADD_TEST NO_TARGETS) set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT) cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN}) set(qmlscene_TARGET try${COMPONENT_NAME}) set(qmltest_FILE ${SUBPATH}/tst_${COMPONENT_NAME}) set(qmlscene_imports "") if(NOT "${qmltest_IMPORT_PATHS}" STREQUAL "") foreach(IMPORT_PATH ${qmltest_IMPORT_PATHS}) list(APPEND qmlscene_imports "-I") list(APPEND qmlscene_imports ${IMPORT_PATH}) endforeach(IMPORT_PATH) elseif(NOT "${qmltest_DEFAULT_IMPORT_PATHS}" STREQUAL "") foreach(IMPORT_PATH ${qmltest_DEFAULT_IMPORT_PATHS}) list(APPEND qmlscene_imports "-I") list(APPEND qmlscene_imports ${IMPORT_PATH}) endforeach(IMPORT_PATH) endif() set(qmlscene_command env ${qmltest_ENVIRONMENT} ${qmlscene_exe} ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml ${qmlscene_imports} ) add_custom_target(${qmlscene_TARGET} ${qmlscene_command}) endmacro(add_manual_qml_test) macro(add_qml_benchmark SUBPATH COMPONENT_NAME ITERATIONS) add_qml_test_internal(${SUBPATH} ${COMPONENT_NAME} ${ITERATIONS} ${ARGN}) endmacro(add_qml_benchmark) macro(add_qml_test SUBPATH COMPONENT_NAME) add_qml_test_internal(${SUBPATH} ${COMPONENT_NAME} 0 ${ARGN}) endmacro(add_qml_test) macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS) set(options NO_ADD_TEST NO_TARGETS) set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT) cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN}) set(qmltest_TARGET test${COMPONENT_NAME}) set(qmltest_xvfb_TARGET xvfbtest${COMPONENT_NAME}) set(qmltest_FILE ${SUBPATH}/tst_${COMPONENT_NAME}) set(qmltestrunner_imports "") if(NOT "${qmltest_IMPORT_PATHS}" STREQUAL "") foreach(IMPORT_PATH ${qmltest_IMPORT_PATHS}) list(APPEND qmltestrunner_imports "-import") list(APPEND qmltestrunner_imports ${IMPORT_PATH}) endforeach(IMPORT_PATH) elseif(NOT "${qmltest_DEFAULT_IMPORT_PATHS}" STREQUAL "") foreach(IMPORT_PATH ${qmltest_DEFAULT_IMPORT_PATHS}) list(APPEND qmltestrunner_imports "-import") list(APPEND qmltestrunner_imports ${IMPORT_PATH}) endforeach(IMPORT_PATH) endif() string(TOLOWER "${CMAKE_GENERATOR}" cmake_generator_lower) if(cmake_generator_lower STREQUAL "unix makefiles") set(function_ARGS $(FUNCTION)) else() set(function_ARGS "") endif() if (${ITERATIONS} GREATER 0) set(ITERATIONS_STRING "-iterations" ${ITERATIONS}) else() set(ITERATIONS_STRING "") endif() set(qmltest_command env ${qmltest_ENVIRONMENT} ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml ${qmltestrunner_imports} ${ITERATIONS_STRING} -o ${CMAKE_BINARY_DIR}/${qmltest_TARGET}.xml,xunitxml -o -,txt ${function_ARGS} ) find_program( HAVE_GCC gcc ) if (NOT ${HAVE_GCC} STREQUAL "") exec_program( gcc ARGS "-dumpmachine" OUTPUT_VARIABLE ARCH_TRIPLET ) set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1") endif() set(qmltest_xvfb_command env ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH} xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml ${qmltestrunner_imports} -o ${CMAKE_BINARY_DIR}/${qmltest_TARGET}.xml,xunitxml -o -,txt ${function_ARGS} ) add_qmltest_target(${qmltest_TARGET} "${qmltest_command}" TRUE ${qmltest_NO_ADD_TEST}) add_qmltest_target(${qmltest_xvfb_TARGET} "${qmltest_xvfb_command}" ${qmltest_NO_TARGETS} TRUE) add_manual_qml_test(${SUBPATH} ${COMPONENT_NAME} ${ARGN}) endmacro(add_qml_test_internal) macro(add_binary_qml_test CLASS_NAME LD_PATH DEPS) set(testCommand LD_LIBRARY_PATH=${LD_PATH} ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec -o ${CMAKE_BINARY_DIR}/${CLASSNAME}Test.xml,xunitxml -o -,txt) add_qmltest_target(test${CLASS_NAME} "${testCommand}" FALSE TRUE) add_dependencies(test${CLASS_NAME} ${CLASS_NAME}TestExec ${DEPS}) find_program( HAVE_GCC gcc ) if (NOT ${HAVE_GCC} STREQUAL "") exec_program( gcc ARGS "-dumpmachine" OUTPUT_VARIABLE ARCH_TRIPLET ) set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1") endif() set(xvfbtestCommand ${LD_PRELOAD_PATH} LD_LIBRARY_PATH=${LD_PATH} xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec -o ${CMAKE_BINARY_DIR}/${CLASS_NAME}Test.xml,xunitxml -o -,txt) add_qmltest_target(xvfbtest${CLASS_NAME} "${xvfbtestCommand}" FALSE TRUE) add_dependencies(qmluitests xvfbtest${CLASS_NAME}) endmacro(add_binary_qml_test) macro(add_qmltest_target qmltest_TARGET qmltest_command qmltest_NO_TARGETS qmltest_NO_ADD_TEST) add_custom_target(${qmltest_TARGET} ${qmltest_command}) if(NOT "${qmltest_PROPERTIES}" STREQUAL "") set_target_properties(${qmltest_TARGET} PROPERTIES ${qmltest_PROPERTIES}) elseif(NOT "${qmltest_DEFAULT_PROPERTIES}" STREQUAL "") set_target_properties(${qmltest_TARGET} PROPERTIES ${qmltest_DEFAULT_PROPERTIES}) endif() if("${qmltest_NO_ADD_TEST}" STREQUAL FALSE AND NOT "${qmltest_DEFAULT_NO_ADD_TEST}" STREQUAL "TRUE") add_test(${qmltest_TARGET} ${qmltest_command}) if(NOT "${qmltest_UNPARSED_ARGUMENTS}" STREQUAL "") set_tests_properties(${qmltest_TARGET} PROPERTIES ${qmltest_PROPERTIES}) elseif(NOT "${qmltest_DEFAULT_PROPERTIES}" STREQUAL "") set_tests_properties(${qmltest_TARGET} PROPERTIES ${qmltest_DEFAULT_PROPERTIES}) endif() endif("${qmltest_NO_ADD_TEST}" STREQUAL FALSE AND NOT "${qmltest_DEFAULT_NO_ADD_TEST}" STREQUAL "TRUE") if("${qmltest_NO_TARGETS}" STREQUAL "FALSE") if(NOT "${qmltest_TARGETS}" STREQUAL "") foreach(TARGET ${qmltest_TARGETS}) add_dependencies(${TARGET} ${qmltest_TARGET}) endforeach(TARGET) elseif(NOT "${qmltest_DEFAULT_TARGETS}" STREQUAL "") foreach(TARGET ${qmltest_DEFAULT_TARGETS}) add_dependencies(${TARGET} ${qmltest_TARGET}) endforeach(TARGET) endif() endif("${qmltest_NO_TARGETS}" STREQUAL "FALSE") endmacro(add_qmltest_target) ./cmake/modules/FindGLESv2.cmake0000644000015600001650000000157312677054330016447 0ustar jenkinsjenkins# - 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) ./cmake/modules/autopilot.cmake0000644000015600001650000000153512677054330016662 0ustar jenkinsjenkinsadd_custom_target(autopilot) function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR) add_custom_target(autopilot-${TEST_NAME} COMMAND LANG=C UBUNTU_ICON_THEME=ubuntu-mobile QML2_IMPORT_PATH=${QTMIR_INSTALL_QML}/mocks python3 -m autopilot.run run ${TEST_SUITE} WORKING_DIRECTORY ${WORKING_DIR} DEPENDS fake_install ) add_custom_target(fake_install COMMAND cmake --build ${CMAKE_BINARY_DIR} --target install ) add_dependencies(autopilot autopilot-${TEST_NAME}) add_custom_target(autopilot2-${TEST_NAME} COMMAND LANG=C UBUNTU_ICON_THEME=ubuntu-mobile QML2_IMPORT_PATH=${QTMIR_INSTALL_QML}/mocks python2 -m autopilot.run run ${TEST_SUITE} WORKING_DIRECTORY ${WORKING_DIR} DEPENDS fake_install ) add_dependencies(autopilot autopilot2-${TEST_NAME}) endfunction() ./cmake/modules/UseLttngGenTp.cmake0000644000015600001650000000131512677054330017341 0ustar jenkinsjenkinscmake_minimum_required(VERSION 2.6) if(POLICY CMP0011) cmake_policy(SET CMP0011 NEW) endif(POLICY CMP0011) find_program(LTTNG_GEN_TP NAMES lttng-gen-tp DOC "lttng-gen-tp executable") if(NOT LTTNG_GEN_TP) message(FATAL_ERROR "Excutable lttng-gen-top not found") endif() function(add_lttng_gen_tp) set(_one_value NAME) cmake_parse_arguments (arg "" "${_one_value}" "" ${ARGN}) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${arg_NAME}.h" "${CMAKE_CURRENT_BINARY_DIR}/${arg_NAME}.c" COMMAND "${LTTNG_GEN_TP}" -o "${arg_NAME}.h" -o "${arg_NAME}.c" "${CMAKE_CURRENT_SOURCE_DIR}/${arg_NAME}.tp" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS "${arg_NAME}.tp" ) endfunction(add_lttng_gen_tp)./cmake/modules/QmlPlugins.cmake0000644000015600001650000001265212677054330016737 0ustar jenkinsjenkins# If you need to override the qmlplugindump binary, create the qmlplugin executable # target before loading this plugin. if(NOT TARGET qmlplugindump) find_program(qmlplugindump_exe qmlplugindump) if(NOT qmlplugindump_exe) msg(FATAL_ERROR "Could not locate qmlplugindump.") endif() add_executable(qmlplugindump IMPORTED) set_target_properties(qmlplugindump PROPERTIES IMPORTED_LOCATION ${qmlplugindump_exe}) endif() # # A custom target for building the qmltypes files manually. # if (NOT TARGET qmltypes) add_custom_target(qmltypes) endif() # Creates a target for copying resource files into build dir and optionally installing them. # # Files will be copied into ${BINARY_DIR}/${path} or ${CMAKE_CURRENT_BINARY_DIR} and installed # into ${DESTINATION}/${path}. # # Resource file names are matched against {*.{qml,js,jpg,png,sci,svg},qmldir}. # # export_qmlfiles(plugin path # [SEARCH_PATH path] # Path to search for resources in (defaults to ${CMAKE_CURRENT_SOURCE_DIR}) # [BINARY_DIR path] # [DESTINATION path] # [TARGET_PREFIX string] # Will be prefixed to the target name # ) # # Created target: # - ${TARGET_PREFIX}${plugin}-qmlfiles - Copies resources into the binary dir. macro(export_qmlfiles PLUGIN PATH) set(single SEARCH_PATH BINARY_DIR DESTINATION TARGET_PREFIX) cmake_parse_arguments(QMLFILES "" "${single}" "" ${ARGN}) if(NOT QMLFILES_SEARCH_PATH) set(QMLFILES_SEARCH_PATH ${CMAKE_CURRENT_SOURCE_DIR}) endif() if(QMLFILES_BINARY_DIR) set(qmlfiles_dir ${QMLFILES_BINARY_DIR}/${PATH}) else() set(qmlfiles_dir ${CMAKE_CURRENT_BINARY_DIR}) endif() file(GLOB QMLFILES ${QMLFILES_SEARCH_PATH}/*.qml ${QMLFILES_SEARCH_PATH}/*.js ${QMLFILES_SEARCH_PATH}/*.jpg ${QMLFILES_SEARCH_PATH}/*.png ${QMLFILES_SEARCH_PATH}/*.sci ${QMLFILES_SEARCH_PATH}/*.svg ${QMLFILES_SEARCH_PATH}/*.qmltypes ${QMLFILES_SEARCH_PATH}/qmldir ) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${qmlfiles_dir}) # copy the files add_custom_target(${QMLFILES_TARGET_PREFIX}${PLUGIN}-qmlfiles ALL COMMAND cp ${QMLFILES} ${qmlfiles_dir} DEPENDS ${QMLFILES} SOURCES ${QMLFILES} ) if(QMLFILES_DESTINATION) # install the qmlfiles file. install(FILES ${QMLFILES} DESTINATION ${QMLFILES_DESTINATION}/${PATH} ) endif() endmacro() # Creates a target for generating the typeinfo file for a QML plugin and/or installs the plugin # targets. # # Files will be copied into ${BINARY_DIR}/${path} or ${CMAKE_CURRENT_BINARY_DIR} and installed # into ${DESTINATION}/${path}. If you don't pass BINARY_DIR, it's assumed that current source # path ends with ${path}. # # The generated file will be named after the last segment of the plugin name, e.g. Foo.qmltypes. # # export_qmlplugin(plugin version path # [BINARY_DIR path] # [DESTINATION path] # [TARGET_PREFIX string] # Will be prefixed to the target name # [ENVIRONMENT string] # Will be added to qmlplugindump's env # [TARGETS target1 [target2 ...]] # Targets to depend on and install (e.g. the plugin shared object) # [NO_TYPES] # Do not create the qmltypes target # ) # # Created target: # - ${TARGET_PREFIX}${plugin}-qmltypes - Generates the qmltypes file in the source dir. # It will be made a dependency of the "qmltypes" target. macro(export_qmlplugin PLUGIN VERSION PATH) set(options NO_TYPES) set(single BINARY_DIR DESTINATION TARGET_PREFIX ENVIRONMENT) set(multi TARGETS) cmake_parse_arguments(QMLPLUGIN "${options}" "${single}" "${multi}" ${ARGN}) get_target_property(qmlplugindump_executable qmlplugindump LOCATION) if(QMLPLUGIN_BINARY_DIR) set(qmlplugin_dir ${QMLPLUGIN_BINARY_DIR}/${PATH}) else() # Find import path to point qmlplugindump at string(REGEX REPLACE "/${PATH}$" "" QMLPLUGIN_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}") set(qmlplugin_dir ${CMAKE_CURRENT_BINARY_DIR}) endif() if(NOT QMLPLUGIN_NO_TYPES) # Relative path for the module string(REPLACE "${CMAKE_BINARY_DIR}/" "" QMLPLUGIN_MODULE_DIR "${QMLPLUGIN_BINARY_DIR}") # Find the last segment of the plugin name to use as qmltypes basename string(REGEX MATCH "[^.]+$" plugin_suffix ${PLUGIN}) set(target_prefix ${QMLPLUGIN_TARGET_PREFIX}${PLUGIN}) set(qmltypes_path ${CMAKE_CURRENT_SOURCE_DIR}/${plugin_suffix}.qmltypes) add_custom_target(${target_prefix}-qmltypes COMMAND env ${QMLPLUGIN_ENVIRONMENT} ${qmlplugindump_executable} -notrelocatable ${PLUGIN} ${VERSION} ${QMLPLUGIN_MODULE_DIR} > ${qmltypes_path} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) add_dependencies(${target_prefix}-qmltypes ${target_prefix}-qmlfiles ${QMLPLUGIN_TARGETS}) add_dependencies(qmltypes ${target_prefix}-qmltypes) endif() set_target_properties(${QMLPLUGIN_TARGETS} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${qmlplugin_dir} LIBRARY_OUTPUT_DIRECTORY ${qmlplugin_dir} RUNTIME_OUTPUT_DIRECTORY ${qmlplugin_dir} ) if (QMLPLUGIN_DESTINATION) # Install additional targets install(TARGETS ${QMLPLUGIN_TARGETS} DESTINATION ${QMLPLUGIN_DESTINATION}/${PATH} ) endif() endmacro() ./tests/0000755000015600001650000000000012677054330012246 5ustar jenkinsjenkins./tests/framework/0000755000015600001650000000000012677054330014243 5ustar jenkinsjenkins./tests/framework/mock_settings.h0000644000015600001650000000204012677054330017261 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_SETTINGS_H #define MOCK_SETTINGS_H #include #include #include namespace qtmir { struct MockSettings : public qtmir::SettingsInterface { MockSettings(); virtual ~MockSettings(); MOCK_CONST_METHOD1(get, QVariant(const QString &)); }; } // namespace qtmir #endif // MOCK_SETTINGS_H ./tests/framework/fake_desktopfilereader.cpp0000644000015600001650000000450512677054330021435 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "fake_desktopfilereader.h" #include namespace qtmir { FakeDesktopFileReader::FakeDesktopFileReader(const QString &appId) : DesktopFileReader() , m_appId(appId) { } FakeDesktopFileReader::FakeDesktopFileReader() : DesktopFileReader() , m_appId("foo-app") { } FakeDesktopFileReader::~FakeDesktopFileReader() { } QString FakeDesktopFileReader::file() const { return m_appId + ".desktop"; } QString FakeDesktopFileReader::appId() const { return m_appId; } QString FakeDesktopFileReader::name() const { return QString(); } QString FakeDesktopFileReader::comment() const { return QString(); } QString FakeDesktopFileReader::icon() const { return QString(); } QString FakeDesktopFileReader::exec() const { return QString(); } QString FakeDesktopFileReader::path() const { return QString(); } QString FakeDesktopFileReader::stageHint() const { return QString(); } QString FakeDesktopFileReader::splashTitle() const { return QString(); } QString FakeDesktopFileReader::splashImage() const { return QString(); } QString FakeDesktopFileReader::splashShowHeader() const { return QString(); } QString FakeDesktopFileReader::splashColor() const { return QString(); } QString FakeDesktopFileReader::splashColorHeader() const { return QString(); } QString FakeDesktopFileReader::splashColorFooter() const { return QString(); } Qt::ScreenOrientations FakeDesktopFileReader::supportedOrientations() const { return Qt::PortraitOrientation; } bool FakeDesktopFileReader::rotatesWindowContents() const { return false; } bool FakeDesktopFileReader::isTouchApp() const { return true; } bool FakeDesktopFileReader::loaded() const { return true; } } // namespace qtmir ./tests/framework/fake_displayconfigurationoutput.h0000644000015600001650000000377312677054330023132 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef FAKE_DISPLAYCONFIGURATIONOUTPUT_H #define FAKE_DISPLAYCONFIGURATIONOUTPUT_H #include namespace mg = mir::graphics; namespace geom = mir::geometry; const mg::DisplayConfigurationOutput fakeOutput1 { mg::DisplayConfigurationOutputId{3}, mg::DisplayConfigurationCardId{2}, mg::DisplayConfigurationOutputType::dvid, { mir_pixel_format_abgr_8888 }, { {geom::Size{100, 200}, 60.0}, {geom::Size{100, 200}, 59.0}, {geom::Size{150, 200}, 59.0} }, 0, geom::Size{1111, 2222}, true, true, geom::Point(), 2, mir_pixel_format_abgr_8888, mir_power_mode_on, mir_orientation_normal, 1.0f, mir_form_factor_unknown }; const mg::DisplayConfigurationOutput fakeOutput2 { mg::DisplayConfigurationOutputId{2}, mg::DisplayConfigurationCardId{4}, mg::DisplayConfigurationOutputType::lvds, { mir_pixel_format_xbgr_8888 }, { {geom::Size{800, 1200}, 90.0}, {geom::Size{1600, 2400}, 60.0}, {geom::Size{1500, 2000}, 75.0} }, 0, geom::Size{1000, 2000}, true, true, geom::Point(500, 600), 2, mir_pixel_format_xbgr_8888, mir_power_mode_on, mir_orientation_left, 1.0f, mir_form_factor_unknown }; #endif // FAKE_DISPLAYCONFIGURATIONOUTPUT_H ./tests/framework/stub_scene_surface.cpp0000644000015600001650000000625412677054330020620 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "stub_scene_surface.h" namespace mir { namespace test { namespace doubles { StubSceneSurface::StubSceneSurface(int fd) : channel(std::make_shared(fd)), fd(fd) { } StubSceneSurface::~StubSceneSurface() { } std::shared_ptr StubSceneSurface::input_channel() const { return channel; } mir::input::InputReceptionMode StubSceneSurface::reception_mode() const { return input_mode; } std::string StubSceneSurface::name() const { return {}; } mir::geometry::Point StubSceneSurface::top_left() const { return {}; } mir::geometry::Size StubSceneSurface::client_size() const { return {};} mir::geometry::Size StubSceneSurface::size() const { return {}; } mir::geometry::Rectangle StubSceneSurface::input_bounds() const { return {{},{}}; } bool StubSceneSurface::input_area_contains(mir::geometry::Point const&) const { return false; } mir::graphics::RenderableList StubSceneSurface::generate_renderables(mir::compositor::CompositorID) const { return {};} float StubSceneSurface::alpha() const { return 0.0f; } MirSurfaceType StubSceneSurface::type() const { return mir_surface_type_normal; } MirSurfaceState StubSceneSurface::state() const { return mir_surface_state_unknown; } void StubSceneSurface::hide() {} void StubSceneSurface::show() {} void StubSceneSurface::move_to(const mir::geometry::Point &) {} void StubSceneSurface::set_input_region(const std::vector &) {} void StubSceneSurface::resize(const mir::geometry::Size &) {} void StubSceneSurface::set_transformation(const glm::mat4 &) {} void StubSceneSurface::set_alpha(float) {} void StubSceneSurface::set_orientation(MirOrientation) {} void StubSceneSurface::add_observer(const std::shared_ptr &) {} void StubSceneSurface::remove_observer(const std::weak_ptr &) {} void StubSceneSurface::set_reception_mode(mir::input::InputReceptionMode mode) { input_mode = mode; } void StubSceneSurface::consume(const MirEvent *) {} void StubSceneSurface::set_cursor_image(const std::shared_ptr &) {} std::shared_ptr StubSceneSurface::cursor_image() const { return {}; } bool StubSceneSurface::supports_input() const { return true; } int StubSceneSurface::client_input_fd() const { return fd;} int StubSceneSurface::configure(MirSurfaceAttrib, int) { return 0; } int StubSceneSurface::query(MirSurfaceAttrib) const { return 0; } } // namespace doubles } // namespace test } // namespace mir ./tests/framework/mock_prompt_session_manager.cpp0000644000015600001650000000161712677054330022543 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_prompt_session_manager.h" namespace mir { namespace scene { MockPromptSessionManager::MockPromptSessionManager() { } MockPromptSessionManager::~MockPromptSessionManager() { } } // namespace scene } // namespace mir ./tests/framework/fake_mirsurface.cpp0000644000015600001650000001326112677054330020100 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "fake_mirsurface.h" namespace qtmir { FakeMirSurface::TouchEvent::TouchEvent(Qt::KeyboardModifiers mods, const QList &points, Qt::TouchPointStates states, ulong timestamp) : keyboardModifiers(mods) , touchPoints(points) , states(states) , timestamp(timestamp) { } FakeMirSurface::TouchEvent::~TouchEvent() { } FakeMirSurface::FakeMirSurface(QObject *parent) : MirSurfaceInterface(parent) , m_isFirstFrameDrawn(false) , m_isFrameDropperRunning(true) , m_live(true) , m_state(Mir::RestoredState) , m_orientationAngle(Mir::Angle0) , m_visible(true) , m_focused(false) { } FakeMirSurface::~FakeMirSurface() { } Mir::Type FakeMirSurface::type() const { return Mir::NormalType; } QString FakeMirSurface::name() const { return QString("Fake MirSurface"); } QSize FakeMirSurface::size() const { return m_size; } void FakeMirSurface::resize(int width, int height) { if (m_size.width() != width || m_size.height() != height) { m_size.setWidth(width); m_size.setHeight(height); Q_EMIT sizeChanged(m_size); } } void FakeMirSurface::resize(const QSize &size) { resize(size.width(), size.height()); } Mir::State FakeMirSurface::state() const { return m_state; } void FakeMirSurface::setState(Mir::State qmlState) { if (qmlState != m_state) { m_state = qmlState; Q_EMIT stateChanged(m_state); } } bool FakeMirSurface::live() const { return m_live; } bool FakeMirSurface::visible() const { return m_visible; } Mir::OrientationAngle FakeMirSurface::orientationAngle() const { return m_orientationAngle; } void FakeMirSurface::setOrientationAngle(Mir::OrientationAngle angle) { if (m_orientationAngle != angle) { m_orientationAngle = angle; Q_EMIT orientationAngleChanged(m_orientationAngle); } } bool FakeMirSurface::isFirstFrameDrawn() const { return m_isFirstFrameDrawn; } void FakeMirSurface::stopFrameDropper() { m_isFrameDropperRunning = false; } void FakeMirSurface::startFrameDropper() { m_isFrameDropperRunning = true; } void FakeMirSurface::setLive(bool value) { if (m_live != value) { m_live = value; Q_EMIT liveChanged(m_live); } } void FakeMirSurface::setViewVisibility(qintptr viewId, bool visible) { if (!m_views.contains(viewId)) return; m_views[viewId] = visible; updateVisibility(); } bool FakeMirSurface::isBeingDisplayed() const { return !m_views.isEmpty(); } void FakeMirSurface::registerView(qintptr viewId) { m_views.insert(viewId, false); if (m_views.count() == 1) { Q_EMIT isBeingDisplayedChanged(); } } void FakeMirSurface::unregisterView(qintptr viewId) { m_views.remove(viewId); if (m_views.count() == 0) { Q_EMIT isBeingDisplayedChanged(); } updateVisibility(); } QSharedPointer FakeMirSurface::texture() { return QSharedPointer(); } QSGTexture *FakeMirSurface::weakTexture() const { return nullptr; } bool FakeMirSurface::updateTexture() { return true; } unsigned int FakeMirSurface::currentFrameNumber() const { return 0; } bool FakeMirSurface::numBuffersReadyForCompositor() { return 0; } void FakeMirSurface::setFocus(bool focus) { m_focused = focus; } void FakeMirSurface::mousePressEvent(QMouseEvent *) {} void FakeMirSurface::mouseMoveEvent(QMouseEvent *) {} void FakeMirSurface::mouseReleaseEvent(QMouseEvent *) {} void FakeMirSurface::hoverEnterEvent(QHoverEvent *) {} void FakeMirSurface::hoverLeaveEvent(QHoverEvent *) {} void FakeMirSurface::hoverMoveEvent(QHoverEvent *) {} void FakeMirSurface::wheelEvent(QWheelEvent *) {} void FakeMirSurface::keyPressEvent(QKeyEvent *) {} void FakeMirSurface::keyReleaseEvent(QKeyEvent *) {} void FakeMirSurface::touchEvent(Qt::KeyboardModifiers mods, const QList &points, Qt::TouchPointStates states, ulong timestamp) { m_touchesReceived.append(TouchEvent(mods, points, states, timestamp)); } QString FakeMirSurface::appId() const { return "foo-app"; } void FakeMirSurface::setKeymap(const QString &layout, const QString &variant) { Q_EMIT keymapChanged(layout, variant); } void FakeMirSurface::onCompositorSwappedBuffers() {} void FakeMirSurface::drawFirstFrame() { if (!m_isFirstFrameDrawn) { m_isFirstFrameDrawn = true; Q_EMIT firstFrameDrawn(); } } bool FakeMirSurface::isFrameDropperRunning() const { return m_isFrameDropperRunning; } QList &FakeMirSurface::touchesReceived() { return m_touchesReceived; } void FakeMirSurface::updateVisibility() { bool newVisible = false; QHashIterator i(m_views); while (i.hasNext()) { i.next(); newVisible |= i.value(); } if (m_visible != newVisible) { m_visible = newVisible; Q_EMIT visibleChanged(newVisible); } } } // namespace qtmir ./tests/framework/mock_main_loop.h0000644000015600001650000000333012677054330017401 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCKMAINLOOP_H #define MOCKMAINLOOP_H #include #include "gmock_fixes.h" class MockMainLoop : public mir::MainLoop { public: MockMainLoop(); ~MockMainLoop() noexcept; void run() override; void stop() override; MOCK_METHOD2(register_signal_handler, void(std::initializer_list, std::function const&)); MOCK_METHOD3(register_fd_handler, void(std::initializer_list, void const*, std::function const&)); MOCK_METHOD1(unregister_fd_handler, void(void const*)); MOCK_METHOD2(enqueue, void(void const*, mir::ServerAction const&)); MOCK_METHOD1(pause_processing_for,void (void const*)); MOCK_METHOD1(resume_processing_for,void (void const*)); MOCK_METHOD1(create_alarm, std::unique_ptr(std::function const& callback)); MOCK_METHOD1(create_alarm, std::unique_ptr(std::shared_ptr const& callback)); }; #endif // MOCKMAINLOOP_H ./tests/framework/mock_display_configuration.cpp0000644000015600001650000000150212677054330022352 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_display_configuration.h" MockDisplayConfiguration::MockDisplayConfiguration() { } MockDisplayConfiguration::~MockDisplayConfiguration() { } ./tests/framework/mock_task_controller.cpp0000644000015600001650000000601612677054330021170 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_task_controller.h" namespace qtmir { MockTaskController::MockTaskController() { using namespace ::testing; ON_CALL(*this, primaryPidForAppId(_)) .WillByDefault( Invoke(this, &MockTaskController::doPrimaryPidForAppId)); ON_CALL(*this, appIdHasProcessId(_, _)) .WillByDefault( Invoke(this, &MockTaskController::doAppIdHasProcessId)); ON_CALL(*this, findDesktopFileForAppId(_)) .WillByDefault( Invoke(this, &MockTaskController::doFindDesktopFileForAppId)); ON_CALL(*this, stop(_)) .WillByDefault( Invoke(this, &MockTaskController::doStop)); ON_CALL(*this, start(_, _)) .WillByDefault( Invoke(this, &MockTaskController::doStart)); ON_CALL(*this, suspend(_)) .WillByDefault( Invoke(this, &MockTaskController::doSuspend)); ON_CALL(*this, resume(_)) .WillByDefault( Invoke(this, &MockTaskController::doResume)); } MockTaskController::~MockTaskController() { } pid_t MockTaskController::doPrimaryPidForAppId(const QString &appId) { auto it = children.find(appId); if (it == children.end()) return -1; return it->pid(); } bool MockTaskController::doAppIdHasProcessId(const QString &appId, pid_t pid) { auto primaryPid = primaryPidForAppId(appId); if (primaryPid == -1) return false; return primaryPid == pid; } QFileInfo MockTaskController::doFindDesktopFileForAppId(const QString &appId) const { QString path = QString("/usr/share/applications/%1.desktop").arg(appId); return QFileInfo(path); } bool MockTaskController::doStop(const QString &appId) { Q_UNUSED(appId); return false; } bool MockTaskController::doStart(const QString &appId, const QStringList &args) { Q_UNUSED(args); auto child = core::posix::fork([]() { while (true); return core::posix::exit::Status::success; }, core::posix::StandardStream::empty); if (child.pid() > 0) { children.insert(appId, child); return true; } return false; } bool MockTaskController::doSuspend(const QString &appId) { Q_UNUSED(appId); return false; } bool MockTaskController::doResume(const QString &appId) { Q_UNUSED(appId); return false; } } // namespace qtmir ./tests/framework/stub_input_channel.h0000644000015600001650000000223612677054330020303 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIR_TEST_DOUBLES_STUB_INPUT_CHANNEL_H_ #define MIR_TEST_DOUBLES_STUB_INPUT_CHANNEL_H_ #include "mir/input/input_channel.h" namespace mir { namespace test { namespace doubles { struct StubInputChannel : public input::InputChannel { StubInputChannel(int fd); StubInputChannel(); virtual ~StubInputChannel(); int client_fd() const override; int server_fd() const override; int input_fd; }; } } } // namespace mir #endif // MIR_TEST_DOUBLES_STUB_INPUT_CHANNEL_H_ ./tests/framework/mock_proc_info.h0000644000015600001650000000206612677054330017407 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_PROC_INFO_H #define MOCK_PROC_INFO_H #include #include namespace qtmir { struct MockProcInfo : public qtmir::ProcInfo { MockProcInfo(); virtual ~MockProcInfo(); MOCK_METHOD1(command_line, QByteArray(pid_t)); std::unique_ptr commandLine(pid_t pid); }; } // namespace qtmir #endif // MOCK_PROC_INFO_H ./tests/framework/mock_gl_display_buffer.cpp0000644000015600001650000000206312677054330021441 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_gl_display_buffer.h" MockGLDisplayBuffer::MockGLDisplayBuffer() { using namespace testing; ON_CALL(*this, view_area()) .WillByDefault(Return(mir::geometry::Rectangle{{0,0},{0,0}})); ON_CALL(*this, native_display_buffer()) .WillByDefault(Return(dynamic_cast(this))); } MockGLDisplayBuffer::~MockGLDisplayBuffer() { } ./tests/framework/fake_session.cpp0000644000015600001650000000633712677054330017431 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "fake_session.h" namespace qtmir { FakeSession::FakeSession() : SessionInterface(0) , m_application(nullptr) , m_state(Starting) { } FakeSession::~FakeSession() { } void FakeSession::release() {} QString FakeSession::name() const { return QString("foo-session"); } unity::shell::application::ApplicationInfoInterface *FakeSession::application() const { return m_application; } MirSurfaceInterface *FakeSession::lastSurface() const { return nullptr; } const ObjectListModel *FakeSession::surfaces() const { return nullptr; } SessionInterface *FakeSession::parentSession() const { return nullptr; } SessionModel *FakeSession::childSessions() const { return nullptr; } SessionInterface::State FakeSession::state() const { return m_state; } bool FakeSession::fullscreen() const { return false; } bool FakeSession::live() const { return true; } std::shared_ptr FakeSession::session() const { return nullptr; } void FakeSession::registerSurface(MirSurfaceInterface *) {} void FakeSession::removeSurface(MirSurfaceInterface *) {} void FakeSession::setApplication(unity::shell::application::ApplicationInfoInterface *app) { if (m_application != app) { m_application = app; Q_EMIT applicationChanged(m_application); } } void FakeSession::suspend() { if (m_state == Running) { setState(Suspending); } } void FakeSession::resume() { if (m_state == Suspending || m_state == Suspended) { setState(Running); } } void FakeSession::stop() { setState(Stopped); } void FakeSession::close() {} void FakeSession::addChildSession(SessionInterface *) {} void FakeSession::insertChildSession(uint, SessionInterface *) {} void FakeSession::removeChildSession(SessionInterface *) {} void FakeSession::foreachChildSession(std::function) const {} std::shared_ptr FakeSession::activePromptSession() const { return std::shared_ptr(); } void FakeSession::foreachPromptSession(std::function &)>) const {} void FakeSession::setFullscreen(bool) {} void FakeSession::setLive(const bool) {} void FakeSession::appendPromptSession(const std::shared_ptr &) {} void FakeSession::removePromptSession(const std::shared_ptr &) {} void FakeSession::setState(SessionInterface::State state) { if (m_state != state) { m_state = state; Q_EMIT stateChanged(m_state); } } } // namespace qtmir ./tests/framework/mock_mir_session.h0000644000015600001650000000616612677054330017770 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_MIR_SCENE_SESSION_H #define MOCK_MIR_SCENE_SESSION_H #include #include #include #include #include namespace mir { namespace scene { struct MockSession : public Session { MockSession(); MockSession(std::string const& sessionName, pid_t processId); virtual ~MockSession(); std::string name() const override; pid_t process_id() const override; MOCK_METHOD0(force_requests_to_complete, void()); MOCK_CONST_METHOD0(default_surface, std::shared_ptr()); MOCK_CONST_METHOD1(get_surface, std::shared_ptr(frontend::SurfaceId)); MOCK_CONST_METHOD1(surface, std::shared_ptr(frontend::SurfaceId)); MOCK_METHOD1(take_snapshot, void(SnapshotCallback const&)); MOCK_METHOD1(set_lifecycle_state, void(MirLifecycleState)); MOCK_METHOD2(create_surface, frontend::SurfaceId(SurfaceCreationParameters const&, std::shared_ptr const&)); MOCK_METHOD1(destroy_surface, void (frontend::SurfaceId)); MOCK_METHOD1(destroy_surface, void (std::weak_ptr const& surface)); MOCK_METHOD0(hide, void()); MOCK_METHOD0(show, void()); MOCK_METHOD1(send_display_config, void(graphics::DisplayConfiguration const&)); MOCK_METHOD3(configure_surface, int(frontend::SurfaceId, MirSurfaceAttrib, int)); void start_prompt_session() override; void stop_prompt_session() override; void suspend_prompt_session() override; void resume_prompt_session() override; std::shared_ptr surface_after(std::shared_ptr const&) const override; MOCK_CONST_METHOD1(get_buffer_stream, std::shared_ptr(frontend::BufferStreamId)); MOCK_METHOD1(destroy_buffer_stream, void(frontend::BufferStreamId)); MOCK_METHOD1(create_buffer_stream, frontend::BufferStreamId(graphics::BufferProperties const&)); void configure_streams(Surface&, std::vector const&) override;; MOCK_METHOD1(send_input_device_change, void(std::vector> const&)); //void send_input_device_change(std::vector> const& devices) = 0; private: std::string m_sessionName; pid_t m_sessionId; }; } // namespace scene } // namespace mir #endif // MOCK_MIR_SCENE_SESSION_H ./tests/framework/mock_proc_info.cpp0000644000015600001650000000207412677054330017741 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_proc_info.h" namespace qtmir { MockProcInfo::MockProcInfo() { using namespace ::testing; ON_CALL(*this, command_line(_)).WillByDefault(Return(QByteArray())); } MockProcInfo::~MockProcInfo() { } std::unique_ptr MockProcInfo::commandLine(pid_t pid) { return std::unique_ptr(new CommandLine{command_line(pid)}); } } // namespace qtmir ./tests/framework/mock_prompt_session.cpp0000644000015600001650000000155312677054330021050 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_prompt_session.h" namespace mir { namespace scene { MockPromptSession::MockPromptSession() { } MockPromptSession::~MockPromptSession() { } } // namespace scene } // namespace mir ./tests/framework/mock_surface.cpp0000644000015600001650000000230012677054330017403 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_surface.h" namespace mir { namespace scene { MockSurface::MockSurface() { } MockSurface::~MockSurface() { } void MockSurface::rename(const std::string &) {} void MockSurface::set_keymap(MirInputDeviceId, std::string const&, std::string const&, std::string const&, std::string const&) { } // void MockSurface::consume(const MirEvent &event) { consume(&event); } } // namespace scene } // namespace mir ./tests/framework/mock_display.h0000644000015600001650000000506512677054330017100 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCKDISPLAY_H #define MOCKDISPLAY_H #include #include #include #if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 20, 0) #include #endif #include #include "gmock_fixes.h" class MockDisplaySyncGroup : public mir::graphics::DisplaySyncGroup { public: MockDisplaySyncGroup(); virtual ~MockDisplaySyncGroup(); MOCK_METHOD1(for_each_display_buffer, void(std::function const& f)); MOCK_METHOD0(post, void()); }; struct MockDisplay : public mir::graphics::Display { public: MockDisplay(); virtual ~MockDisplay(); MOCK_METHOD1(for_each_display_sync_group, void(std::function const&)); MOCK_CONST_METHOD0(configuration, std::unique_ptr()); MOCK_METHOD1(configure, void(mir::graphics::DisplayConfiguration const&)); MOCK_METHOD2(register_configuration_change_handler, void(mir::graphics::EventHandlerRegister&, mir::graphics::DisplayConfigurationChangeHandler const&)); MOCK_METHOD3(register_pause_resume_handlers, void(mir::graphics::EventHandlerRegister&, mir::graphics::DisplayPauseHandler const&, mir::graphics::DisplayResumeHandler const&)); MOCK_METHOD0(pause, void()); MOCK_METHOD0(resume, void()); MOCK_METHOD1(create_hardware_cursor, std::shared_ptr(std::shared_ptr const&)); MOCK_METHOD0(create_gl_context, std::unique_ptr()); #if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 20, 0) MOCK_METHOD2(create_virtual_output, std::unique_ptr (int width, int height)); #endif }; #endif // MOCKDISPLAY_H ./tests/framework/mock_renderable.h0000644000015600001650000000255412677054330017536 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_MIR_GRAPHICS_RENDERABLE_H #define MOCK_MIR_GRAPHICS_RENDERABLE_H #include #include namespace mir { namespace graphics { struct MockRenderable : public Renderable { MockRenderable(); virtual ~MockRenderable(); MOCK_CONST_METHOD0(id, ID()); MOCK_CONST_METHOD0(buffer, std::shared_ptr()); MOCK_CONST_METHOD0(alpha_enabled, bool()); MOCK_CONST_METHOD0(screen_position, geometry::Rectangle()); MOCK_CONST_METHOD0(alpha, float() ); MOCK_CONST_METHOD0(transformation, glm::mat4()); MOCK_CONST_METHOD0(shaped, bool()); }; } // namespace graphics } // namespace mir #endif // MOCK_MIR_GRAPHICS_RENDERABLE_H ./tests/framework/mock_shared_wakelock.h0000644000015600001650000000254512677054330020561 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_SHARED_WAKELOCK_H #define MOCK_SHARED_WAKELOCK_H #include #include namespace qtmir { class MockSharedWakelock : public qtmir::SharedWakelock { public: MockSharedWakelock(const QDBusConnection& /*connection*/= QDBusConnection::systemBus()); virtual ~MockSharedWakelock(); MOCK_CONST_METHOD0(enabled, bool()); MOCK_METHOD1(acquire, void(const QObject *)); MOCK_METHOD1(release, void(const QObject *)); bool doEnabled(); void doAcquire(const QObject *object); void doRelease(const QObject *object); private: QSet m_owners; }; } // namespace qtmir #endif // MOCK_SHARED_WAKELOCK_H ./tests/framework/mock_shared_wakelock.cpp0000644000015600001650000000320612677054330021107 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_shared_wakelock.h" namespace qtmir { MockSharedWakelock::MockSharedWakelock(const QDBusConnection &) { using namespace ::testing; ON_CALL(*this, enabled()).WillByDefault(Invoke(this, &MockSharedWakelock::doEnabled)); ON_CALL(*this, acquire(_)).WillByDefault(Invoke(this, &MockSharedWakelock::doAcquire)); ON_CALL(*this, release(_)).WillByDefault(Invoke(this, &MockSharedWakelock::doRelease)); } MockSharedWakelock::~MockSharedWakelock() { } void MockSharedWakelock::doRelease(const QObject *object) { if (!m_owners.remove(object)) { return; } if (m_owners.isEmpty()) { Q_EMIT enabledChanged(false); } } void MockSharedWakelock::doAcquire(const QObject *object) { if (m_owners.contains(object)) { return; } m_owners.insert(object); if (m_owners.size() == 1) { Q_EMIT enabledChanged(true); } } bool MockSharedWakelock::doEnabled() { return !m_owners.isEmpty(); } } // namespace qtmir ./tests/framework/fake_session.h0000644000015600001650000000550412677054330017071 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #ifndef QTMIR_FAKE_SESSION_H #define QTMIR_FAKE_SESSION_H namespace qtmir { class FakeSession : public SessionInterface { Q_OBJECT public: FakeSession(); virtual ~FakeSession(); // For QML use void release() override; QString name() const override; unity::shell::application::ApplicationInfoInterface* application() const override; MirSurfaceInterface* lastSurface() const override; const ObjectListModel* surfaces() const override; SessionInterface* parentSession() const override; SessionModel* childSessions() const override; State state() const override; bool fullscreen() const override; bool live() const override; std::shared_ptr session() const override; // For MirSurfaceItem and MirSurfaceManager use void registerSurface(MirSurfaceInterface*) override; void removeSurface(MirSurfaceInterface*) override; // For Application use void setApplication(unity::shell::application::ApplicationInfoInterface* app) override; void suspend() override; void resume() override; void stop() override; void close() override; // For SessionManager use void addChildSession(SessionInterface*) override; void insertChildSession(uint, SessionInterface*) override; void removeChildSession(SessionInterface*) override; void foreachChildSession(std::function) const override; std::shared_ptr activePromptSession() const override; void foreachPromptSession(std::function&)>) const override; void setFullscreen(bool) override; void setLive(const bool) override; void appendPromptSession(const std::shared_ptr&) override; void removePromptSession(const std::shared_ptr&) override; // For tests void setState(State state); private: unity::shell::application::ApplicationInfoInterface* m_application; State m_state; }; } // namespace qtmi #endif // QTMIR_FAKE_SESSION_H ./tests/framework/stub_input_channel.cpp0000644000015600001650000000216012677054330020632 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "stub_input_channel.h" namespace mir { namespace test { namespace doubles { StubInputChannel::StubInputChannel(int fd) : input_fd(fd) { } StubInputChannel::StubInputChannel() : StubInputChannel(0) { } StubInputChannel::~StubInputChannel() { } int StubInputChannel::client_fd() const { return input_fd; } int StubInputChannel::server_fd() const { return input_fd; } } // namespace doubles } // namespace test } // namespace mir ./tests/framework/mock_desktop_file_reader.h0000644000015600001650000000420012677054330021413 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_DESKTOP_FILE_READER_H #define MOCK_DESKTOP_FILE_READER_H #include #include namespace qtmir { struct MockDesktopFileReader : public qtmir::DesktopFileReader { MockDesktopFileReader(const QString &appId, const QFileInfo& fileInfo); virtual ~MockDesktopFileReader(); MOCK_CONST_METHOD0(file, QString()); MOCK_CONST_METHOD0(appId, QString ()); MOCK_CONST_METHOD0(name, QString()); MOCK_CONST_METHOD0(comment, QString()); MOCK_CONST_METHOD0(icon, QString()); MOCK_CONST_METHOD0(exec, QString()); MOCK_CONST_METHOD0(path, QString()); MOCK_CONST_METHOD0(stageHint, QString()); MOCK_CONST_METHOD0(isTouchApp, bool()); MOCK_CONST_METHOD0(loaded, bool()); QString doFile() const; QString doAppId() const; QString doName() const; QString doComment() const; QString doIcon() const; QString doExec() const; QString doPath() const; QString doStageHint() const; bool doIsTouchApp() const; bool doLoaded() const; }; struct MockDesktopFileReaderFactory : public qtmir::DesktopFileReader::Factory { MockDesktopFileReaderFactory(); virtual ~MockDesktopFileReaderFactory(); virtual qtmir::DesktopFileReader* doCreateInstance(const QString &appId, const QFileInfo &fi); MOCK_METHOD2(createInstance, qtmir::DesktopFileReader*(const QString &appId, const QFileInfo &fi)); }; } // namespace qtmir #endif // MOCK_DESKTOP_FILE_READER_H ./tests/framework/mock_session.h0000644000015600001650000000570312677054330017115 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_QTMIR_SESSION_H #define MOCK_QTMIR_SESSION_H #include #include namespace qtmir { class MockSession : public SessionInterface { public: MockSession(); virtual ~MockSession(); MOCK_METHOD0(release, void()); MOCK_CONST_METHOD0(name, QString()); MOCK_CONST_METHOD0(application, unity::shell::application::ApplicationInfoInterface*()); MOCK_CONST_METHOD0(lastSurface, MirSurfaceInterface*()); MOCK_CONST_METHOD0(surfaces, const ObjectListModel*()); MOCK_CONST_METHOD0(parentSession, SessionInterface*()); MOCK_CONST_METHOD0(childSessions, SessionModel*()); MOCK_CONST_METHOD0(state, State()); MOCK_CONST_METHOD0(fullscreen, bool()); MOCK_CONST_METHOD0(live, bool()); MOCK_CONST_METHOD0(session, std::shared_ptr()); MOCK_METHOD1(registerSurface, void(MirSurfaceInterface* surface)); MOCK_METHOD1(removeSurface, void(MirSurfaceInterface* surface)); MOCK_METHOD1(setApplication, void(unity::shell::application::ApplicationInfoInterface* item)); MOCK_METHOD0(suspend, void()); MOCK_METHOD0(resume, void()); MOCK_METHOD0(close, void()); MOCK_METHOD0(stop, void()); MOCK_METHOD1(addChildSession, void(SessionInterface* session)); MOCK_METHOD2(insertChildSession, void(uint index, SessionInterface* session)); MOCK_METHOD1(removeChildSession, void(SessionInterface* session)); MOCK_CONST_METHOD1(foreachChildSession, void(std::function f)); MOCK_CONST_METHOD0(activePromptSession, std::shared_ptr()); MOCK_CONST_METHOD1(foreachPromptSession, void(std::function&)> f)); void setState(State state); void doSuspend(); void doResume(); void doStop(); State doState() const; protected: MOCK_METHOD1(setFullscreen, void(bool fullscreen)); MOCK_METHOD1(setLive, void(const bool)); MOCK_METHOD1(appendPromptSession, void(const std::shared_ptr& session)); MOCK_METHOD1(removePromptSession, void(const std::shared_ptr& session)); private: State m_state; }; } // namespace qtmir #endif // MOCK_QTMIR_SESSION_H ./tests/framework/mock_surface.h0000644000015600001650000001033512677054330017057 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_MIR_SCENE_SURFACE_H #define MOCK_MIR_SCENE_SURFACE_H #include #include #include #include "mock_renderable.h" namespace mir { namespace scene { struct MockSurface : public mir::scene::Surface { MockSurface(); virtual ~MockSurface(); MOCK_CONST_METHOD0(name, std::string()); MOCK_CONST_METHOD0(client_size, geometry::Size()); MOCK_CONST_METHOD0(input_bounds, geometry::Rectangle()); MOCK_CONST_METHOD0(top_left, geometry::Point()); MOCK_CONST_METHOD0(size, geometry::Size()); MOCK_CONST_METHOD1(generate_renderables,graphics::RenderableList(compositor::CompositorID id)); MOCK_CONST_METHOD0(parent, std::shared_ptr()); MOCK_CONST_METHOD0(alpha, float()); MOCK_CONST_METHOD0(type, MirSurfaceType()); MOCK_CONST_METHOD0(state, MirSurfaceState()); MOCK_METHOD0(hide, void()); MOCK_METHOD0(show, void()); MOCK_CONST_METHOD0(visible, bool()); MOCK_METHOD1(move_to, void(geometry::Point const& top_left)); MOCK_METHOD1(take_input_focus, void(std::shared_ptr const& targeter)); MOCK_METHOD1(set_input_region, void(std::vector const& region)); MOCK_METHOD1(allow_framedropping, void(bool)); MOCK_METHOD1(resize, void(geometry::Size const& size)); MOCK_METHOD1(set_transformation, void(glm::mat4 const& t)); MOCK_METHOD1(set_alpha, void(float alpha)); MOCK_METHOD1(set_orientation, void(MirOrientation orientation)); MOCK_METHOD0(force_requests_to_complete, void()); MOCK_METHOD1(set_cursor_image, void(std::shared_ptr const& image)); MOCK_CONST_METHOD0(cursor_image, std::shared_ptr()); MOCK_METHOD1(add_observer, void(std::shared_ptr const& observer)); MOCK_METHOD1(remove_observer, void(std::weak_ptr const& observer)); MOCK_CONST_METHOD0(input_channel, std::shared_ptr()); MOCK_METHOD1(set_reception_mode, void(input::InputReceptionMode mode)); MOCK_METHOD0(request_client_surface_close, void()); MOCK_CONST_METHOD1(buffers_ready_for_compositor, int(void const*)); void set_keymap(MirInputDeviceId, std::string const&, std::string const&, std::string const&, std::string const&) override; void rename(std::string const&) override; MOCK_METHOD1(set_streams, void(std::list const&)); // from mir::input::surface MOCK_CONST_METHOD1(input_area_contains, bool(geometry::Point const& point)); MOCK_CONST_METHOD0(reception_mode, input::InputReceptionMode()); MOCK_METHOD1(consume, void(MirEvent const* event)); //void consume(MirEvent const* event) override; // from mir::frontend::surface MOCK_CONST_METHOD0(pixel_format, MirPixelFormat()); MOCK_METHOD2(swap_buffers, void(graphics::Buffer* old_buffer, std::function complete)); MOCK_CONST_METHOD0(supports_input, bool()); MOCK_CONST_METHOD0(client_input_fd, int()); MOCK_METHOD2(configure, int(MirSurfaceAttrib attrib, int value)); MOCK_CONST_METHOD1(query, int(MirSurfaceAttrib attrib)); MOCK_CONST_METHOD0(primary_buffer_stream, std::shared_ptr()); // from mir::scene::SurfaceBufferAccess MOCK_METHOD1(with_most_recent_buffer_do, void(std::function const& exec)); MOCK_METHOD2(set_cursor_stream, void(std::shared_ptr const&, geometry::Displacement const&)); }; } // namespace scene } // namespace mir #endif // MOCK_MIR_SCENE_SURFACE_H ./tests/framework/stub_scene_surface.h0000644000015600001650000000553112677054330020262 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIR_TEST_DOUBLES_STUB_SCENE_SURFACE_H_ #define MIR_TEST_DOUBLES_STUB_SCENE_SURFACE_H_ #include "mir/scene/surface.h" #include "stub_input_channel.h" #include #include namespace mir { namespace test { namespace doubles { class StubSceneSurface : public mir::scene::Surface { public: std::shared_ptr channel; int fd; mir::input::InputReceptionMode input_mode{mir::input::InputReceptionMode::normal}; StubSceneSurface(int fd); virtual ~StubSceneSurface(); std::shared_ptr input_channel() const override; mir::input::InputReceptionMode reception_mode() const override; std::string name() const override; geometry::Point top_left() const override; geometry::Size client_size() const override; geometry::Size size() const override; geometry::Rectangle input_bounds() const override; bool input_area_contains(mir::geometry::Point const&) const override; graphics::RenderableList generate_renderables(compositor::CompositorID) const override; float alpha() const override; MirSurfaceType type() const override; MirSurfaceState state() const override; void hide() override; void show() override; void move_to(geometry::Point const&) override; void set_input_region(std::vector const&) override; void resize(geometry::Size const&) override; void set_transformation(glm::mat4 const&) override; void set_alpha(float) override; void set_orientation(MirOrientation) override; void add_observer(std::shared_ptr const&) override; void remove_observer(std::weak_ptr const&) override; void set_reception_mode(input::InputReceptionMode mode) override; void consume(MirEvent const*) override; void set_cursor_image(std::shared_ptr const& /* image */) override; std::shared_ptr cursor_image() const override; bool supports_input() const override; int client_input_fd() const override; int configure(MirSurfaceAttrib, int) override; int query(MirSurfaceAttrib) const override; }; } } } #endif ./tests/framework/qtmir_test.cpp0000644000015600001650000001264412677054330017151 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "qtmir_test.h" namespace qtmir { void PrintTo(const Application::InternalState& state, ::std::ostream* os) { switch (state) { case Application::InternalState::Starting: *os << "Starting"; break; case Application::InternalState::Running: *os << "Running"; break; case Application::InternalState::RunningInBackground: *os << "RunningInBackground"; break; case Application::InternalState::SuspendingWaitSession: *os << "SuspendingWaitSession"; break; case Application::InternalState::SuspendingWaitProcess: *os << "SuspendingWaitProcess"; break; case Application::InternalState::Suspended: *os << "Suspended"; break; case Application::InternalState::StoppedResumable: *os << "StoppedResumable"; break; case Application::InternalState::Closing: *os << "Closing"; break; case Application::InternalState::Stopped: *os << "Stopped"; break; default: *os << "???"; break; } } void PrintTo(const Session::State& state, ::std::ostream* os) { switch (state) { case SessionInterface::Starting: *os << "Starting"; break; case SessionInterface::Running: *os << "Running"; break; case SessionInterface::Suspending: *os << "Suspending"; break; case SessionInterface::Suspended: *os << "Suspended"; break; case SessionInterface::Stopped: *os << "Stopped"; break; default: *os << "???"; break; } } // Initialization of mir::Server needed for by tests class TestMirServerInit : public virtual mir::Server { public: TestMirServerInit(std::shared_ptr const& promptSessionManager) : mock_prompt_session_manager(promptSessionManager) { override_the_prompt_session_manager( [this]{ return the_mock_prompt_session_manager(); }); } std::shared_ptr the_mock_prompt_session_manager() { return mock_prompt_session_manager; } private: std::shared_ptr const mock_prompt_session_manager; }; namespace { int argc = 0; char* argv[] = { nullptr }; } class FakeMirServer: private TestMirServerInit, public MirServer { public: FakeMirServer(std::shared_ptr const& promptSessionManager) : TestMirServerInit(promptSessionManager), MirServer(argc, argv, QSharedPointer()) { } using TestMirServerInit::the_mock_prompt_session_manager; }; } // namespace qtmir namespace testing { QtMirTest::QtMirTest() : promptSessionManager(std::make_shared()) , mirServer(QSharedPointer(new FakeMirServer(promptSessionManager))) , applicationManager(mirServer, taskControllerSharedPointer, QSharedPointer(&sharedWakelock, [](MockSharedWakelock *){}), QSharedPointer(&desktopFileReaderFactory,[](DesktopFileReader::Factory*){}), QSharedPointer(&procInfo,[](ProcInfo *){}), QSharedPointer(&settings,[](MockSettings *){})) , sessionManager(mirServer, &applicationManager) , surfaceManager(mirServer, mirShell, &sessionManager) { } QtMirTest::~QtMirTest() { } Application *QtMirTest::startApplication(pid_t procId, const QString &appId) { using namespace testing; ON_CALL(*taskController,appIdHasProcessId(appId, procId)).WillByDefault(Return(true)); // Set up Mocks & signal watcher auto mockDesktopFileReader = new NiceMock(appId, QFileInfo()); ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true)); ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId)); EXPECT_CALL(desktopFileReaderFactory, createInstance(appId, _)) .Times(1) .WillOnce(Return(mockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); auto application = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); bool authed = false; applicationManager.authorizeSession(procId, authed); EXPECT_EQ(authed, true); auto appSession = std::make_shared(appId.toStdString(), procId); applicationManager.onSessionStarting(appSession); sessionManager.onSessionStarting(appSession); Mock::VerifyAndClearExpectations(taskController); Mock::VerifyAndClearExpectations(&desktopFileReaderFactory); return application; } } // namespace testing ./tests/framework/mock_gl_display_buffer.h0000644000015600001650000000306412677054330021110 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_GL_DISPLAY_BUFFER_H #define MOCK_GL_DISPLAY_BUFFER_H #include #include #include class MockGLDisplayBuffer : public mir::graphics::DisplayBuffer, public mir::renderer::gl::RenderTarget, public mir::graphics::NativeDisplayBuffer { public: MockGLDisplayBuffer(); virtual ~MockGLDisplayBuffer(); MOCK_CONST_METHOD0(view_area, mir::geometry::Rectangle()); MOCK_METHOD1(post_renderables_if_optimizable, bool(mir::graphics::RenderableList const&)); MOCK_CONST_METHOD0(orientation, MirOrientation()); MOCK_METHOD0(native_display_buffer, mir::graphics::NativeDisplayBuffer*()); MOCK_METHOD0(make_current, void()); MOCK_METHOD0(release_current, void()); MOCK_METHOD0(swap_buffers, void()); }; #endif // MOCK_GL_DISPLAY_BUFFER_H ./tests/framework/mock_main_loop.cpp0000644000015600001650000000147712677054330017746 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_main_loop.h" MockMainLoop::MockMainLoop() { } MockMainLoop::~MockMainLoop() { } void MockMainLoop::run() {} void MockMainLoop::stop() {}./tests/framework/mock_display.cpp0000644000015600001650000000170612677054330017431 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_display.h" #include #include MockDisplaySyncGroup::MockDisplaySyncGroup() { } MockDisplaySyncGroup::~MockDisplaySyncGroup() { } MockDisplay::MockDisplay() { } MockDisplay::~MockDisplay() { } ./tests/framework/mock_settings.cpp0000644000015600001650000000202112677054330017613 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_settings.h" namespace qtmir { MockSettings::MockSettings() { using namespace ::testing; QVariantList lifecycleExemptAppIds; lifecycleExemptAppIds << "com.ubuntu.music"; ON_CALL(*this, get(_)) .WillByDefault( Return(lifecycleExemptAppIds)); } MockSettings::~MockSettings() { } } // namespace qtmir ./tests/framework/mock_task_controller.h0000644000015600001650000000361512677054330020637 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_TASK_CONTROLLER_H #define MOCK_TASK_CONTROLLER_H #include #include #include #include namespace qtmir { struct MockTaskController : public qtmir::TaskController { MockTaskController(); virtual ~MockTaskController(); MOCK_METHOD1(primaryPidForAppId, pid_t(const QString& appId)); MOCK_METHOD2(appIdHasProcessId, bool(const QString&, pid_t)); MOCK_CONST_METHOD1(findDesktopFileForAppId, QFileInfo(const QString &appId)); MOCK_METHOD1(stop, bool(const QString&)); MOCK_METHOD2(start, bool(const QString&, const QStringList&)); MOCK_METHOD1(suspend, bool(const QString&)); MOCK_METHOD1(resume, bool(const QString&)); pid_t doPrimaryPidForAppId(const QString& appId); bool doAppIdHasProcessId(const QString& appId, pid_t pid); QFileInfo doFindDesktopFileForAppId(const QString& appId) const; bool doStop(const QString& appId); bool doStart(const QString& appId, const QStringList& args); bool doSuspend(const QString& appId); bool doResume(const QString& appId); private: QMap children; }; } // namespace qtmir #endif // MOCK_TASK_CONTROLLER_H ./tests/framework/mock_renderable.cpp0000644000015600001650000000154112677054330020064 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_renderable.h" namespace mir { namespace graphics { MockRenderable::MockRenderable() { } MockRenderable::~MockRenderable() { } } // namespace graphics } // namespace mir ./tests/framework/CMakeLists.txt0000644000015600001650000000201612677054330017002 0ustar jenkinsjenkinsinclude_directories( ${APPLICATION_API_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${CMAKE_SOURCE_DIR}/src/modules ${MIRSERVER_INCLUDE_DIRS} ${MIRRENDERERGLDEV_INCLUDE_DIRS} ${Qt5Quick_INCLUDE_DIRS} ${Qt5DBus_INCLUDE_DIRS} ) set(QTMIR_TEST_PRIVATE_SRC fake_desktopfilereader.cpp fake_mirsurface.cpp fake_session.cpp mock_task_controller.cpp mock_desktop_file_reader.cpp mock_display.cpp mock_display_configuration.cpp mock_gl_display_buffer.cpp mock_main_loop.cpp mock_mir_session.cpp mock_proc_info.cpp mock_prompt_session.cpp mock_prompt_session_manager.cpp mock_renderable.cpp mock_session.cpp mock_settings.cpp mock_shared_wakelock.cpp mock_surface.cpp stub_input_channel.cpp stub_scene_surface.cpp qtmir_test.cpp ) add_library(qtmir-test-framework-static STATIC ${QTMIR_TEST_PRIVATE_SRC} ) target_link_libraries( qtmir-test-framework-static ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) ./tests/framework/fake_mirsurface.h0000644000015600001650000001202512677054330017542 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef FAKE_MIRSURFACEINTERFACE_H #define FAKE_MIRSURFACEINTERFACE_H #include #include #include #include namespace qtmir { class FakeMirSurface : public MirSurfaceInterface { Q_OBJECT public: class TouchEvent { public: TouchEvent(Qt::KeyboardModifiers mods, const QList &points, Qt::TouchPointStates states, ulong timestamp); virtual ~TouchEvent(); Qt::KeyboardModifiers keyboardModifiers; QList touchPoints; Qt::TouchPointStates states; ulong timestamp; }; FakeMirSurface(QObject *parent = nullptr); virtual ~FakeMirSurface(); //// // unity.shell.application.MirSurfaceInterface Mir::Type type() const override; QString name() const override; QSize size() const override; void resize(int width, int height) override; void resize(const QSize &size) override; Mir::State state() const override; void setState(Mir::State qmlState) override; bool live() const override; bool visible() const override; Mir::OrientationAngle orientationAngle() const override; void setOrientationAngle(Mir::OrientationAngle angle) override; int minimumWidth() const override { return 0; } int minimumHeight() const override { return 0; } int maximumWidth() const override { return 0; } int maximumHeight() const override { return 0; } int widthIncrement() const override { return 0; } int heightIncrement() const override { return 0; } //// // qtmir.MirSurfaceInterface bool isFirstFrameDrawn() const override; void stopFrameDropper() override; void startFrameDropper() override; void setLive(bool value) override; void setViewVisibility(qintptr viewId, bool visible) override; bool isBeingDisplayed() const override; void registerView(qintptr viewId) override; void unregisterView(qintptr viewId) override; // methods called from the rendering (scene graph) thread: QSharedPointer texture() override; QSGTexture *weakTexture() const override; bool updateTexture() override; unsigned int currentFrameNumber() const override; bool numBuffersReadyForCompositor() override; // end of methods called from the rendering (scene graph) thread void setFocus(bool focus) override; void mousePressEvent(QMouseEvent *) override; void mouseMoveEvent(QMouseEvent *) override; void mouseReleaseEvent(QMouseEvent *) override; void hoverEnterEvent(QHoverEvent *) override; void hoverLeaveEvent(QHoverEvent *) override; void hoverMoveEvent(QHoverEvent *) override; void wheelEvent(QWheelEvent *) override; void keyPressEvent(QKeyEvent *) override; void keyReleaseEvent(QKeyEvent *) override; void touchEvent(Qt::KeyboardModifiers mods, const QList &points, Qt::TouchPointStates states, ulong timestamp) override; QString appId() const override; QCursor cursor() const override { return QCursor(); } Mir::ShellChrome shellChrome() const override { return Mir::NormalChrome; } void setShellChrome(Mir::ShellChrome) override {} void close() override { Q_EMIT closeRequested(); } QString keymapLayout() const override { return QString(); } QString keymapVariant() const override { return QString(); } void setKeymap(const QString &layout, const QString &variant) override; Q_SIGNALS: void closeRequested(); public Q_SLOTS: void onCompositorSwappedBuffers() override; void setMinimumWidth(int) {} void setMinimumHeight(int) {} void setMaximumWidth(int) {} void setMaximumHeight(int) {} void setWidthIncrement(int) {} void setHeightIncrement(int) {} //// // Test API from now on public: void drawFirstFrame(); bool isFrameDropperRunning() const; QList &touchesReceived(); private: void updateVisibility(); bool m_isFirstFrameDrawn; bool m_isFrameDropperRunning; bool m_live; Mir::State m_state; Mir::OrientationAngle m_orientationAngle; bool m_visible; QSize m_size; QHash m_views; bool m_focused; QList m_touchesReceived; }; } // namespace qtmir #endif // FAKE_MIRSURFACEINTERFACE_H ./tests/framework/qtmir_test.h0000644000015600001650000000543512677054330016616 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QT_MIR_TEST_FRAMEWORK_H #define QT_MIR_TEST_FRAMEWORK_H #include #include #include #include #include #include #include #include #include #include #include #include "mock_desktop_file_reader.h" #include "mock_proc_info.h" #include "mock_mir_session.h" #include "mock_prompt_session_manager.h" #include "mock_prompt_session.h" #include "mock_shared_wakelock.h" #include "mock_settings.h" #include "mock_task_controller.h" namespace ms = mir::scene; using namespace qtmir; namespace qtmir { typedef testing::NiceMock StubPromptSessionManager; // For better output in ASSERT_* and EXPECT_* error messages void PrintTo(const Application::InternalState& state, ::std::ostream* os); void PrintTo(const SessionInterface::State& state, ::std::ostream* os); } // namespace qtmir namespace testing { class QtMirTest : public ::testing::Test { public: QtMirTest(); virtual ~QtMirTest(); Application* startApplication(pid_t procId, QString const& appId); QSharedPointer taskControllerSharedPointer{new testing::NiceMock}; testing::NiceMock *taskController{static_cast*>(taskControllerSharedPointer.data())}; testing::NiceMock procInfo; testing::NiceMock desktopFileReaderFactory; testing::NiceMock sharedWakelock; testing::NiceMock settings; std::shared_ptr promptSessionManager; QSharedPointer mirServer; MirShell *mirShell{nullptr}; ApplicationManager applicationManager; SessionManager sessionManager; MirSurfaceManager surfaceManager; }; } // namespace testing #endif // QT_MIR_TEST_FRAMEWORK_H ./tests/framework/mock_prompt_session.h0000644000015600001650000000243312677054330020513 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_MIR_PROMPT_SESSION_H #define MOCK_MIR_PROMPT_SESSION_H #include #include namespace mir { namespace scene { struct MockPromptSession : public PromptSession { public: MockPromptSession(); virtual ~MockPromptSession(); MOCK_METHOD1(start, void(std::shared_ptr const&)); MOCK_METHOD1(stop, void(std::shared_ptr const&)); MOCK_METHOD1(suspend, void(std::shared_ptr const&)); MOCK_METHOD1(resume, void(std::shared_ptr const&)); }; } // namespace scene } // namespace mir #endif // MOCK_MIR_PROMPT_SESSION_H ./tests/framework/mock_desktop_file_reader.cpp0000644000015600001650000000662012677054330021756 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_desktop_file_reader.h" namespace qtmir { MockDesktopFileReader::MockDesktopFileReader(const QString &appId, const QFileInfo &fileInfo) : DesktopFileReader(appId, fileInfo) { using namespace ::testing; ON_CALL(*this, file()).WillByDefault(Invoke(this, &MockDesktopFileReader::doFile)); ON_CALL(*this, appId()).WillByDefault(Invoke(this, &MockDesktopFileReader::doAppId)); ON_CALL(*this, name()).WillByDefault(Invoke(this, &MockDesktopFileReader::doName)); ON_CALL(*this, comment()).WillByDefault(Invoke(this, &MockDesktopFileReader::doComment)); ON_CALL(*this, icon()).WillByDefault(Invoke(this, &MockDesktopFileReader::doIcon)); ON_CALL(*this, exec()).WillByDefault(Invoke(this, &MockDesktopFileReader::doExec)); ON_CALL(*this, path()).WillByDefault(Invoke(this, &MockDesktopFileReader::doPath)); ON_CALL(*this, stageHint()).WillByDefault(Invoke(this, &MockDesktopFileReader::doStageHint)); ON_CALL(*this, isTouchApp()).WillByDefault(Invoke(this, &MockDesktopFileReader::doIsTouchApp)); ON_CALL(*this, loaded()).WillByDefault(Invoke(this, &MockDesktopFileReader::doLoaded)); } MockDesktopFileReader::~MockDesktopFileReader() { } QString MockDesktopFileReader::doFile() const { return DesktopFileReader::file(); } QString MockDesktopFileReader::doAppId() const { return DesktopFileReader::appId(); } QString MockDesktopFileReader::doName() const { return DesktopFileReader::name(); } QString MockDesktopFileReader::doComment() const { return DesktopFileReader::comment(); } QString MockDesktopFileReader::doIcon() const { return DesktopFileReader::icon(); } QString MockDesktopFileReader::doExec() const { return DesktopFileReader::exec(); } QString MockDesktopFileReader::doPath() const { return DesktopFileReader::path(); } QString MockDesktopFileReader::doStageHint() const { return DesktopFileReader::stageHint(); } bool MockDesktopFileReader::doIsTouchApp() const { return DesktopFileReader::isTouchApp(); } bool MockDesktopFileReader::doLoaded() const { return DesktopFileReader::loaded(); } MockDesktopFileReaderFactory::MockDesktopFileReaderFactory() { using namespace ::testing; ON_CALL(*this, createInstance(_, _)) .WillByDefault( Invoke( this, &MockDesktopFileReaderFactory::doCreateInstance)); } MockDesktopFileReaderFactory::~MockDesktopFileReaderFactory() { } qtmir::DesktopFileReader *MockDesktopFileReaderFactory::doCreateInstance(const QString &appId, const QFileInfo &fi) { using namespace ::testing; auto instance = new NiceMock(appId, fi); ON_CALL(*instance, loaded()).WillByDefault(Return(true)); return instance; } } // namespace qtmir ./tests/framework/gmock_fixes.h0000644000015600001650000001027012677054330016712 0ustar jenkinsjenkins// // Copyright © 2012 Canonical Ltd. Copyright 2007, Google Inc. // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Author: wan@google.com (Zhanyong Wan) // Authored by: Alan Griffiths #ifndef MIR_TEST_GMOCK_FIXES_H_ #define MIR_TEST_GMOCK_FIXES_H_ #include #include namespace testing { namespace internal { template class ActionResultHolder> : public UntypedActionResultHolderBase { public: explicit ActionResultHolder(std::unique_ptr&& a_value) : value_(std::move(a_value)) {} // The compiler-generated copy constructor and assignment operator // are exactly what we need, so we don't need to define them. // Returns the held value and deletes this object. std::unique_ptr GetValueAndDelete() const { std::unique_ptr retval(std::move(value_)); delete this; return retval; } // Prints the held value as an action's result to os. virtual void PrintAsActionResult(::std::ostream* os) const { *os << "\n Returns: "; // T may be a reference type, so we don't use UniversalPrint(). UniversalPrinter>::Print(value_, os); } // Performs the given mock function's default action and returns the // result in a new-ed ActionResultHolder. template static ActionResultHolder* PerformDefaultAction( const FunctionMockerBase* func_mocker, const typename Function::ArgumentTuple& args, const string& call_description) { return new ActionResultHolder( func_mocker->PerformDefaultAction(args, call_description)); } // Performs the given action and returns the result in a new-ed // ActionResultHolder. template static ActionResultHolder* PerformAction(const Action& action, const typename Function::ArgumentTuple& args) { return new ActionResultHolder(action.Perform(args)); } private: std::unique_ptr mutable value_; // T could be a reference type, so = isn't supported. GTEST_DISALLOW_ASSIGN_(ActionResultHolder); }; } template class DefaultValue> { public: // Unsets the default value for type T. static void Clear() {} // Returns true iff the user has set the default value for type T. static bool IsSet() { return false; } // Returns true if T has a default return value set by the user or there // exists a built-in default value. static bool Exists() { return true; } // Returns the default value for type T if the user has set one; // otherwise returns the built-in default value if there is one; // otherwise aborts the process. static std::unique_ptr Get() { return std::unique_ptr(); } }; } #endif /* MIR_TEST_GMOCK_FIXES_H_ */ ./tests/framework/fake_desktopfilereader.h0000644000015600001650000000344712677054330021106 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef FAKE_DESKTOPFILEREADER_H #define FAKE_DESKTOPFILEREADER_H #include namespace qtmir { class FakeDesktopFileReader : public qtmir::DesktopFileReader { public: FakeDesktopFileReader(const QString &appId); FakeDesktopFileReader(); virtual ~FakeDesktopFileReader(); QString file() const override; QString appId() const override; QString name() const override; QString comment() const override; QString icon() const override; QString exec() const override; QString path() const override; QString stageHint() const override; QString splashTitle() const override; QString splashImage() const override; QString splashShowHeader() const override; QString splashColor() const override; QString splashColorHeader() const override; QString splashColorFooter() const override; Qt::ScreenOrientations supportedOrientations() const override; bool rotatesWindowContents() const override; bool isTouchApp() const override; bool loaded() const override; QString m_appId; }; } // namespace qtmir #endif // FAKE_DESKTOPFILEREADER_H ./tests/framework/mock_prompt_session_manager.h0000644000015600001650000000443412677054330022210 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_MIR_SCENE_PROMPT_SESSION_MANAGER_H_ #define MOCK_MIR_SCENE_PROMPT_SESSION_MANAGER_H_ #include "mir/scene/prompt_session_manager.h" #include "mir/scene/prompt_session_creation_parameters.h" #include namespace mir { namespace scene { class MockPromptSessionManager: public PromptSessionManager { public: MockPromptSessionManager(); virtual ~MockPromptSessionManager(); MOCK_CONST_METHOD2(start_prompt_session_for, std::shared_ptr(std::shared_ptr const&, mir::scene::PromptSessionCreationParameters const&)); MOCK_CONST_METHOD1(stop_prompt_session, void(std::shared_ptr const&)); MOCK_CONST_METHOD1(suspend_prompt_session, void(std::shared_ptr const&)); MOCK_CONST_METHOD1(resume_prompt_session, void(std::shared_ptr const&)); MOCK_CONST_METHOD2(add_prompt_provider, void(std::shared_ptr const&, std::shared_ptr const&)); MOCK_CONST_METHOD1(add_expected_session, void(std::shared_ptr const&)); MOCK_CONST_METHOD1(remove_session, void(std::shared_ptr const&)); MOCK_CONST_METHOD1(application_for, std::shared_ptr(std::shared_ptr const&)); MOCK_CONST_METHOD1(helper_for, std::shared_ptr(std::shared_ptr const&)); MOCK_CONST_METHOD2(for_each_provider_in, void(std::shared_ptr const&, std::function const&)> const&)); }; } // namespace scene } // namespace mir #endif // MOCK_MIR_SCENE_PROMPT_SESSION_MANAGER_H_ ./tests/framework/mock_session.cpp0000644000015600001650000000343212677054330017445 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_session.h" namespace qtmir { MockSession::MockSession() : SessionInterface(0) { m_state = Starting; ON_CALL(*this, name()).WillByDefault(::testing::Return(QString())); ON_CALL(*this, suspend()).WillByDefault(::testing::Invoke(this, &MockSession::doSuspend)); ON_CALL(*this, resume()).WillByDefault(::testing::Invoke(this, &MockSession::doResume)); ON_CALL(*this, stop()).WillByDefault(::testing::Invoke(this, &MockSession::doStop)); ON_CALL(*this, state()).WillByDefault(::testing::Invoke(this, &MockSession::doState)); } MockSession::~MockSession() { } SessionInterface::State MockSession::doState() const { return m_state; } void MockSession::doStop() { setState(Stopped); } void MockSession::doResume() { if (m_state == Suspending || m_state == Suspended) { setState(Running); } } void MockSession::doSuspend() { if (m_state == Running) { setState(Suspending); } } void MockSession::setState(SessionInterface::State state) { if (m_state != state) { m_state = state; Q_EMIT stateChanged(m_state); } } } // namespace qtmir ./tests/framework/mock_display_configuration.h0000644000015600001650000000275712677054330022034 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MOCK_DISPLAY_CONFIGURATION_H #define MOCK_DISPLAY_CONFIGURATION_H #include #include #include "gmock_fixes.h" class MockDisplayConfiguration : public mir::graphics::DisplayConfiguration { public: MockDisplayConfiguration(); virtual ~MockDisplayConfiguration(); MOCK_CONST_METHOD1(for_each_card, void(std::function)); MOCK_CONST_METHOD1(for_each_output, void(std::function)); MOCK_METHOD1(for_each_output, void(std::function)); MOCK_CONST_METHOD0(clone, std::unique_ptr()); MOCK_CONST_METHOD0(valid, bool()); }; #endif // MOCK_DISPLAY_CONFIGURATION_H ./tests/framework/mock_mir_session.cpp0000644000015600001650000000300412677054330020307 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mock_mir_session.h" namespace mir { namespace scene { MockSession::MockSession() { } MockSession::MockSession(const std::string &sessionName, pid_t processId) : m_sessionName(sessionName), m_sessionId(processId) { } MockSession::~MockSession() { } std::string MockSession::name() const { return m_sessionName; } pid_t MockSession::process_id() const { return m_sessionId; } void MockSession::resume_prompt_session() {} void MockSession::suspend_prompt_session() {} void MockSession::stop_prompt_session() {} void MockSession::start_prompt_session() {} std::shared_ptr MockSession::surface_after(const std::shared_ptr &) const { return {}; } void MockSession::configure_streams(scene::Surface &, const std::vector &) {} } // namespace scene } // namespace mir ./tests/modules/0000755000015600001650000000000012677054330013716 5ustar jenkinsjenkins./tests/modules/ApplicationManager/0000755000015600001650000000000012677054330017454 5ustar jenkinsjenkins./tests/modules/ApplicationManager/application_manager_test.cpp0000644000015600001650000024332212677054330025222 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #define MIR_INCLUDE_DEPRECATED_EVENT_HEADER #include #include #include #include #include #include #include #include #include using namespace qtmir; using mir::scene::MockSession; namespace ms = mir::scene; class ApplicationManagerTests : public ::testing::QtMirTest { public: ApplicationManagerTests() {} inline void onSessionStarting(const std::shared_ptr &session) { applicationManager.onSessionStarting(session); sessionManager.onSessionStarting(session); } inline void onSessionStopping(const std::shared_ptr &session) { applicationManager.onSessionStopping(session); sessionManager.onSessionStopping(session); } inline void onSessionCreatedSurface(const mir::scene::Session *mirSession, MirSurfaceInterface *qmlSurface) { SessionInterface* qmlSession = sessionManager.findSession(mirSession); if (qmlSession) { qmlSession->registerSurface(qmlSurface); } // I assume that applicationManager ignores the mirSurface parameter, so sending // a null shared pointer must suffice std::shared_ptr mirSurface(nullptr); applicationManager.onSessionCreatedSurface(mirSession, mirSurface); } inline void suspend(Application *application) { application->setRequestedState(Application::RequestedSuspended); ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState()); static_cast(application->session())->doSuspend(); ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState()); applicationManager.onProcessSuspended(application->appId()); ASSERT_EQ(Application::InternalState::Suspended, application->internalState()); } static inline qtmir::DesktopFileReader* createMockDesktopFileReader(const QString &appId, const QFileInfo &fi) { using namespace ::testing; auto mockDesktopFileReader = new NiceMock(appId, fi); ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true)); return mockDesktopFileReader; } protected: virtual void SetUp() override { if (m_tempDir.isValid()) qputenv("XDG_CACHE_HOME", m_tempDir.path().toUtf8()); } private: const QTemporaryDir m_tempDir; }; TEST_F(ApplicationManagerTests,bug_case_1240400_second_dialer_app_fails_to_authorize_and_gets_mixed_up_with_first_one) { using namespace ::testing; const pid_t firstProcId = 5921; const pid_t secondProcId = 5922; const char dialer_app_id[] = "dialer-app"; QByteArray cmdLine( "/usr/bin/dialer-app --desktop_file_hint=dialer-app"); QByteArray secondcmdLine( "/usr/bin/dialer-app"); FakeMirSurface *surface = new FakeMirSurface; EXPECT_CALL(procInfo,command_line(firstProcId)) .Times(1) .WillOnce(Return(cmdLine)); EXPECT_CALL(procInfo,command_line(secondProcId)) .Times(1) .WillOnce(Return(secondcmdLine)); bool authed = true; std::shared_ptr mirSession = std::make_shared(dialer_app_id, firstProcId); applicationManager.authorizeSession(firstProcId, authed); ASSERT_EQ(true, authed); onSessionStarting(mirSession); onSessionCreatedSurface(mirSession.get(), surface); surface->drawFirstFrame(); Application * application = applicationManager.findApplication(dialer_app_id); ASSERT_NE(nullptr,application); ASSERT_EQ(Application::InternalState::Running, application->internalState()); // now a second session without desktop file is launched: applicationManager.authorizeSession(secondProcId, authed); applicationManager.onProcessStarting(dialer_app_id); EXPECT_FALSE(authed); EXPECT_EQ(application, applicationManager.findApplication(dialer_app_id)); } TEST_F(ApplicationManagerTests,application_dies_while_starting) { using namespace ::testing; const pid_t procId = 5921; const char app_id[] = "my-app"; QByteArray cmdLine( "/usr/bin/my-app --desktop_file_hint=my-app"); EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(cmdLine)); bool authed = true; std::shared_ptr mirSession = std::make_shared(app_id, procId); applicationManager.authorizeSession(procId, authed); onSessionStarting(mirSession); Application * beforeFailure = applicationManager.findApplication(app_id); applicationManager.onProcessStarting(app_id); onSessionStopping(mirSession); applicationManager.onProcessFailed(app_id, TaskController::Error::APPLICATION_FAILED_TO_START); Application * afterFailure = applicationManager.findApplication(app_id); EXPECT_EQ(true, authed); EXPECT_NE(nullptr, beforeFailure); EXPECT_EQ(nullptr, afterFailure); } TEST_F(ApplicationManagerTests,startApplicationSupportsShortAppId) { using namespace ::testing; const QString shortAppId("com.canonical.test_test"); EXPECT_CALL(*taskController, start(_, _)).Times(1); EXPECT_CALL(*taskController, findDesktopFileForAppId(shortAppId)).Times(1); EXPECT_CALL(desktopFileReaderFactory, createInstance(_, _)).Times(1); auto application = applicationManager.startApplication( shortAppId, QStringList()); EXPECT_EQ(shortAppId, application->appId()); } TEST_F(ApplicationManagerTests,startApplicationSupportsLongAppId) { using namespace ::testing; const QString longAppId("com.canonical.test_test_0.1.235"); const QString shortAppId("com.canonical.test_test"); EXPECT_CALL(*taskController, start(_, _)).Times(1); EXPECT_CALL(*taskController, findDesktopFileForAppId(shortAppId)).Times(1); EXPECT_CALL(desktopFileReaderFactory, createInstance(_, _)).Times(1); auto application = applicationManager.startApplication( longAppId, QStringList()); EXPECT_EQ(shortAppId, application->appId()); } TEST_F(ApplicationManagerTests,testAppIdGuessFromDesktopFileName) { using namespace ::testing; const pid_t procId = 5921; QString appId("sudoku-app"); QString cmdLine = QString("/usr/bin/my-app --desktop_file_hint=/usr/share/click/preinstalled/com.ubuntu.sudoku/1.0.180/%1.desktop").arg(appId); EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(qPrintable(cmdLine))); bool authed = true; applicationManager.authorizeSession(procId, authed); Application *app = applicationManager.findApplication(appId); EXPECT_EQ(true, authed); EXPECT_NE(app, nullptr); EXPECT_EQ(appId, app->appId()); } TEST_F(ApplicationManagerTests,testAppIdGuessFromDesktopFileNameWithLongAppId) { using namespace ::testing; const pid_t procId = 5921; QString shortAppId("com.ubuntu.desktop_desktop"); QString cmdLine = QString("/usr/bin/my-app --desktop_file_hint=/usr/share/applications/%1_1.0.180.desktop").arg(shortAppId); EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(qPrintable(cmdLine))); bool authed = true; applicationManager.authorizeSession(procId, authed); Application *app = applicationManager.findApplication(shortAppId); EXPECT_EQ(true, authed); EXPECT_NE(app, nullptr); EXPECT_EQ(shortAppId, app->appId()); } TEST_F(ApplicationManagerTests,bug_case_1281075_session_ptrs_always_distributed_to_last_started_app) { using namespace ::testing; const pid_t first_procId = 5921; const pid_t second_procId = 5922; const pid_t third_procId = 5923; std::shared_ptr aSurface(nullptr); const char first_app_id[] = "app1"; QByteArray first_cmdLine( "/usr/bin/app1 --desktop_file_hint=app1"); const char second_app_id[] = "app2"; QByteArray second_cmdLine( "/usr/bin/app2--desktop_file_hint=app2"); const char third_app_id[] = "app3"; QByteArray third_cmdLine( "/usr/bin/app3 --desktop_file_hint=app3"); EXPECT_CALL(procInfo,command_line(first_procId)) .Times(1) .WillOnce(Return(first_cmdLine)); ON_CALL(*taskController,appIdHasProcessId(_,_)).WillByDefault(Return(false)); EXPECT_CALL(procInfo,command_line(second_procId)) .Times(1) .WillOnce(Return(second_cmdLine)); EXPECT_CALL(procInfo,command_line(third_procId)) .Times(1) .WillOnce(Return(third_cmdLine)); bool authed = true; std::shared_ptr first_session = std::make_shared("Oo", first_procId); std::shared_ptr second_session = std::make_shared("oO", second_procId); std::shared_ptr third_session = std::make_shared("OO", third_procId); applicationManager.authorizeSession(first_procId, authed); applicationManager.authorizeSession(second_procId, authed); applicationManager.authorizeSession(third_procId, authed); onSessionStarting(first_session); onSessionStarting(third_session); onSessionStarting(second_session); Application * firstApp = applicationManager.findApplication(first_app_id); Application * secondApp = applicationManager.findApplication(second_app_id); Application * thirdApp = applicationManager.findApplication(third_app_id); EXPECT_EQ(first_session, firstApp->session()->session()); EXPECT_EQ(second_session, secondApp->session()->session()); EXPECT_EQ(third_session, thirdApp->session()->session()); } TEST_F(ApplicationManagerTests,two_session_on_one_application) { using namespace ::testing; const pid_t a_procId = 5921; const char an_app_id[] = "some_app"; QByteArray a_cmd( "/usr/bin/app1 --desktop_file_hint=some_app"); ON_CALL(procInfo,command_line(_)).WillByDefault(Return(a_cmd)); ON_CALL(*taskController,appIdHasProcessId(_,_)).WillByDefault(Return(false)); bool authed = true; std::shared_ptr first_session = std::make_shared("Oo", a_procId); std::shared_ptr second_session = std::make_shared("oO", a_procId); applicationManager.authorizeSession(a_procId, authed); onSessionStarting(first_session); onSessionStarting(second_session); Application * the_app = applicationManager.findApplication(an_app_id); EXPECT_EQ(true, authed); EXPECT_EQ(second_session, the_app->session()->session()); } TEST_F(ApplicationManagerTests,DISABLED_upstart_launching_sidestage_app_on_phone_forced_into_mainstage) { using namespace ::testing; QString appId("sideStage"); EXPECT_CALL(*taskController, findDesktopFileForAppId(appId)).Times(1); auto mockDesktopFileReader = new NiceMock(appId, QFileInfo()); ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true)); ON_CALL(*mockDesktopFileReader, stageHint()).WillByDefault(Return("SideStage")); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader)); // mock upstart launching an app which reports itself as sidestage, but we're on phone applicationManager.onProcessStarting(appId); // ensure the app stage is overridden to be main stage Application* theApp = applicationManager.findApplication(appId); ASSERT_NE(theApp, nullptr); EXPECT_EQ(Application::MainStage, theApp->stage()); } TEST_F(ApplicationManagerTests,two_session_on_one_application_after_starting) { using namespace ::testing; const pid_t a_procId = 5921; const char an_app_id[] = "some_app"; QByteArray a_cmd( "/usr/bin/app1 --desktop_file_hint=some_app"); FakeMirSurface *aSurface = new FakeMirSurface; ON_CALL(procInfo,command_line(_)).WillByDefault(Return(a_cmd)); ON_CALL(*taskController,appIdHasProcessId(_,_)).WillByDefault(Return(false)); bool authed = true; std::shared_ptr first_session = std::make_shared("Oo", a_procId); std::shared_ptr second_session = std::make_shared("oO", a_procId); applicationManager.authorizeSession(a_procId, authed); onSessionStarting(first_session); onSessionCreatedSurface(first_session.get(), aSurface); aSurface->drawFirstFrame(); onSessionStarting(second_session); Application * the_app = applicationManager.findApplication(an_app_id); EXPECT_EQ(true, authed); EXPECT_EQ(Application::Running, the_app->state()); EXPECT_EQ(first_session, the_app->session()->session()); } TEST_F(ApplicationManagerTests, focused_app_can_rerequest_focus) { using namespace ::testing; const pid_t a_procId = 5921; const char an_app_id[] = "some_app"; QByteArray a_cmd("/usr/bin/app1 --desktop_file_hint=some_app"); FakeMirSurface *aSurface = new FakeMirSurface; ON_CALL(procInfo, command_line(_)).WillByDefault(Return(a_cmd)); ON_CALL(*taskController, appIdHasProcessId(_,_)).WillByDefault(Return(false)); bool authed = true; std::shared_ptr a_session = std::make_shared("Oo", a_procId); applicationManager.authorizeSession(a_procId, authed); onSessionStarting(a_session); onSessionCreatedSurface(a_session.get(), aSurface); aSurface->drawFirstFrame(); Application * the_app = applicationManager.findApplication(an_app_id); applicationManager.focusApplication(an_app_id); EXPECT_EQ(Application::Running, the_app->state()); EXPECT_EQ(true, the_app->focused()); applicationManager.focusApplication(an_app_id); EXPECT_EQ(true, the_app->focused()); } TEST_F(ApplicationManagerTests,starting_app_is_suspended_when_it_gets_ready_if_requested) { using namespace ::testing; const pid_t procId = 5921; FakeMirSurface *aSurface = new FakeMirSurface; QByteArray cmdLine( "/usr/bin/app --desktop_file_hint=app"); EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(cmdLine)); ON_CALL(*taskController,appIdHasProcessId(_,_)).WillByDefault(Return(false)); bool authed = true; std::shared_ptr session = std::make_shared("Oo", procId); applicationManager.authorizeSession(procId, authed); onSessionStarting(session); Application * app = applicationManager.findApplication("app"); app->setRequestedState(Application::RequestedSuspended); // First app starting... EXPECT_EQ(Application::Starting, app->state()); // Signal app is ready now applicationManager.onProcessStarting("app"); onSessionCreatedSurface(session.get(), aSurface); aSurface->drawFirstFrame(); // now that its ready, suspend process should have begun EXPECT_EQ(Application::InternalState::SuspendingWaitSession, app->internalState()); } TEST_F(ApplicationManagerTests,requestFocusApplication) { using namespace ::testing; const pid_t first_procId = 5921; const pid_t second_procId = 5922; const pid_t third_procId = 5923; std::shared_ptr aSurface(nullptr); QByteArray first_cmdLine( "/usr/bin/app1 --desktop_file_hint=app1"); QByteArray second_cmdLine( "/usr/bin/app2--desktop_file_hint=app2"); QByteArray third_cmdLine( "/usr/bin/app3 --desktop_file_hint=app3"); EXPECT_CALL(procInfo,command_line(first_procId)) .Times(1) .WillOnce(Return(first_cmdLine)); ON_CALL(*taskController,appIdHasProcessId(_,_)).WillByDefault(Return(false)); EXPECT_CALL(procInfo,command_line(second_procId)) .Times(1) .WillOnce(Return(second_cmdLine)); EXPECT_CALL(procInfo,command_line(third_procId)) .Times(1) .WillOnce(Return(third_cmdLine)); bool authed = true; std::shared_ptr first_session = std::make_shared("Oo", first_procId); std::shared_ptr second_session = std::make_shared("oO", second_procId); std::shared_ptr third_session = std::make_shared("OO", third_procId); applicationManager.authorizeSession(first_procId, authed); applicationManager.authorizeSession(second_procId, authed); applicationManager.authorizeSession(third_procId, authed); onSessionStarting(first_session); onSessionStarting(third_session); onSessionStarting(second_session); QSignalSpy spy(&applicationManager, SIGNAL(focusRequested(const QString &))); applicationManager.requestFocusApplication("app3"); EXPECT_EQ(spy.count(), 1); QList arguments = spy.takeFirst(); // take the first signal EXPECT_EQ(arguments.at(0).toString(), "app3"); } /* * Test that an application launched by shell itself creates the correct Application instance and * emits signals indicating the model updated */ TEST_F(ApplicationManagerTests,appStartedByShell) { using namespace ::testing; const QString appId("testAppId"); const QString name("Test App"); // Set up Mocks & signal watcher auto mockDesktopFileReader = new NiceMock(appId, QFileInfo()); ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true)); ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId)); ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &))); // start the application Application *theApp = applicationManager.startApplication(appId); // check application data EXPECT_EQ(Application::Starting, theApp->state()); EXPECT_EQ(appId, theApp->appId()); EXPECT_EQ(name, theApp->name()); EXPECT_FALSE(theApp->canBeResumed()); // check signals were emitted EXPECT_EQ(2, countSpy.count()); //FIXME(greyback) EXPECT_EQ(1, applicationManager.count()); EXPECT_EQ(1, addedSpy.count()); EXPECT_EQ(appId, addedSpy.takeFirst().at(0).toString()); // check application in list of apps Application *theAppAgain = applicationManager.findApplication(appId); EXPECT_EQ(theApp, theAppAgain); } /* * Test that an application launched upstart (i.e. not by shell itself) creates the correct Application * instance and emits signals indicating the model updated */ TEST_F(ApplicationManagerTests,appStartedByUpstart) { using namespace ::testing; const QString appId("testAppId"); const QString name("Test App"); // Set up Mocks & signal watcher auto mockDesktopFileReader = new NiceMock(appId, QFileInfo()); ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true)); ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId)); ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader)); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &))); QSignalSpy focusSpy(&applicationManager, SIGNAL(focusRequested(const QString &))); // upstart sends notification that the application was started applicationManager.onProcessStarting(appId); Application *theApp = applicationManager.findApplication(appId); // check application data EXPECT_EQ(Application::Starting, theApp->state()); EXPECT_EQ(appId, theApp->appId()); EXPECT_EQ(name, theApp->name()); EXPECT_EQ(true, theApp->canBeResumed()); // check signals were emitted EXPECT_EQ(2, countSpy.count()); //FIXME(greyback) EXPECT_EQ(1, applicationManager.count()); EXPECT_EQ(1, addedSpy.count()); EXPECT_EQ(appId, addedSpy.takeFirst().at(0).toString()); EXPECT_EQ(1, focusSpy.count()); EXPECT_EQ(appId, focusSpy.takeFirst().at(0).toString()); } /* * Test that an application launched via the command line with a correct --desktop_file_hint is accepted, * creates the correct Application instance and emits signals indicating the model updated */ TEST_F(ApplicationManagerTests,appStartedUsingCorrectDesktopFileHintSwitch) { using namespace ::testing; const QString appId("testAppId"); const QString name("Test App"); const pid_t procId = 5551; QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint="); cmdLine = cmdLine.append(appId); // Set up Mocks & signal watcher EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(cmdLine)); auto mockDesktopFileReader = new NiceMock(appId, QFileInfo()); ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true)); ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId)); ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader)); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &))); // Mir requests authentication for an application that was started bool authed = false; applicationManager.authorizeSession(procId, authed); EXPECT_EQ(authed, true); Application *theApp = applicationManager.findApplication(appId); // check application data EXPECT_EQ(theApp->state(), Application::Starting); EXPECT_EQ(theApp->appId(), appId); EXPECT_EQ(theApp->name(), name); EXPECT_EQ(theApp->canBeResumed(), false); // check signals were emitted EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback) EXPECT_EQ(applicationManager.count(), 1); EXPECT_EQ(addedSpy.count(), 1); EXPECT_EQ(addedSpy.takeFirst().at(0).toString(), appId); } /* * Test that an application launched via the command line without the correct --desktop_file_hint is rejected */ TEST_F(ApplicationManagerTests,appDoesNotStartWhenUsingBadDesktopFileHintSwitch) { using namespace ::testing; const QString appId("testAppId"); const QString name("Test App"); const pid_t procId = 5551; QByteArray cmdLine("/usr/bin/testApp"); // Set up Mocks & signal watcher EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(cmdLine)); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &))); // Mir requests authentication for an application that was started bool authed = true; applicationManager.authorizeSession(procId, authed); EXPECT_EQ(authed, false); Application *theApp = applicationManager.findApplication(appId); EXPECT_EQ(theApp, nullptr); // check no new signals were emitted EXPECT_EQ(countSpy.count(), 0); EXPECT_EQ(applicationManager.count(), 0); EXPECT_EQ(addedSpy.count(), 0); } /* * Test that an application launched via the command line with the --desktop_file_hint but an incorrect * desktop file specified is rejected */ TEST_F(ApplicationManagerTests,appDoesNotStartWhenUsingBadDesktopFileHintFile) { using namespace ::testing; const QString appId("testAppId"); const QString badDesktopFile = QString("%1.desktop").arg(appId); const pid_t procId = 5551; QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint="); cmdLine = cmdLine.append(badDesktopFile); // Set up Mocks & signal watcher EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(cmdLine)); auto mockDesktopFileReader = new NiceMock(appId, QFileInfo()); ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(false)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader)); // Mir requests authentication for an application that was started, should fail bool authed = true; applicationManager.authorizeSession(procId, authed); EXPECT_EQ(authed, false); } /* * Test that if TaskController synchronously calls back processStarted, that ApplicationManager * does not add the app to the model twice. */ TEST_F(ApplicationManagerTests,synchronousProcessStartedCallDoesNotDuplicateEntryInModel) { using namespace ::testing; const QString appId("testAppId"); const QString name("Test App"); // Set up Mocks & signal watcher auto mockDesktopFileReader = new NiceMock(appId, QFileInfo()); ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true)); ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId)); ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader)); ON_CALL(*taskController, start(appId, _)) .WillByDefault(Invoke( [&](const QString &appId, Unused) { applicationManager.onProcessStarting(appId); return true; } )); // start the application Application *theApp = applicationManager.startApplication(appId); // check application data EXPECT_EQ(theApp->state(), Application::Starting); EXPECT_EQ(theApp->appId(), appId); EXPECT_EQ(theApp->name(), name); EXPECT_EQ(theApp->canBeResumed(), true); // check only once instance in the model EXPECT_EQ(applicationManager.count(), 1); // check application in list of apps Application *theAppAgain = applicationManager.findApplication(appId); EXPECT_EQ(theAppAgain, theApp); } /* * Test that the child sessions of a webapp process are accepted */ TEST_F(ApplicationManagerTests,webAppSecondarySessionsAccepted) { using namespace ::testing; const pid_t procId = 5551; QByteArray cmdLine("/usr/bin/qt5/libexec/QtWebProcess"); // Set up Mocks & signal watcher EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(cmdLine)); bool authed = false; applicationManager.authorizeSession(procId, authed); EXPECT_EQ(authed, true); } /* * Test that maliit sessions are accepted */ TEST_F(ApplicationManagerTests,maliitSessionsAccepted) { using namespace ::testing; const pid_t procId = 151; QByteArray cmdLine("maliit-server --blah"); // Set up Mocks & signal watcher EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(cmdLine)); bool authed = false; applicationManager.authorizeSession(procId, authed); EXPECT_EQ(authed, true); } /* * Test that an application in the Starting state is not impacted by the upstart "Starting" message * for that application (i.e. the upstart message is effectively useless) */ TEST_F(ApplicationManagerTests,onceAppAddedToApplicationLists_upstartStartingEventIgnored) { using namespace ::testing; const QString appId("testAppId"); // Set up Mocks & signal watcher ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &))); // upstart sends notification that the application was started applicationManager.onProcessStarting(appId); // check no new signals were emitted and application state unchanged EXPECT_EQ(countSpy.count(), 0); EXPECT_EQ(applicationManager.count(), 1); EXPECT_EQ(addedSpy.count(), 0); Application *theApp = applicationManager.findApplication(appId); EXPECT_EQ(Application::Starting, theApp->state()); } /* * Test that an application in the Starting state reacts correctly to the Mir sessionStarted * event for that application (i.e. the Session is associated) */ TEST_F(ApplicationManagerTests,onceAppAddedToApplicationLists_mirSessionStartingEventHandled) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &))); std::shared_ptr session = std::make_shared("", procId); // Authorize session and emit Mir sessionStarting event bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); EXPECT_EQ(countSpy.count(), 0); EXPECT_EQ(applicationManager.count(), 1); EXPECT_EQ(addedSpy.count(), 0); // Check application state and session are correctly set Application *theApp = applicationManager.findApplication(appId); EXPECT_EQ(theApp->session()->session(), session); EXPECT_EQ(theApp->focused(), false); } /* * Test that an application in the Starting state reacts correctly to the Mir surfaceCreated * event for that application (i.e. the Surface is associated and state set to Running) */ TEST_F(ApplicationManagerTests,onceAppAddedToApplicationLists_mirSurfaceCreatedEventHandled) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); // Check application state is correctly set Application *theApp = applicationManager.findApplication(appId); EXPECT_EQ(theApp->state(), Application::Running); } /* * Test that an application is stopped correctly, if it has not yet created a surface (still in Starting state) */ TEST_F(ApplicationManagerTests,shellStopsAppCorrectlyBeforeSurfaceCreated) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); EXPECT_CALL(*taskController, stop(appId)) .Times(1) .WillOnce(Return(true)); // Stop app applicationManager.stopApplication(appId); EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback) EXPECT_EQ(applicationManager.count(), 0); EXPECT_EQ(removedSpy.count(), 1); EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId); } /* * Test that a running application is stopped correctly (is in Running state, has surface) */ TEST_F(ApplicationManagerTests,shellStopsForegroundAppCorrectly) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Stop app applicationManager.stopApplication(appId); EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback) EXPECT_EQ(applicationManager.count(), 0); EXPECT_EQ(removedSpy.count(), 1); EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId); } /* * Test that a suspended application is stopped correctly */ TEST_F(ApplicationManagerTests,shellStopsSuspendedAppCorrectly) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); Application *application = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); applicationManager.onProcessStarting(appId); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); suspend(application); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Stop app applicationManager.stopApplication(appId); EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback) EXPECT_EQ(applicationManager.count(), 0); EXPECT_EQ(removedSpy.count(), 1); EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId); } /* * Test that if the foreground Running application is stopped by upstart, AppMan cleans up after it ok */ TEST_F(ApplicationManagerTests,upstartNotifiesOfStoppingForegroundApp) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); onSessionStopping(session); // Upstart notifies of stopping app applicationManager.onProcessStopped(appId); EXPECT_EQ(2, countSpy.count()); //FIXME(greyback) EXPECT_EQ(0, applicationManager.count()); EXPECT_EQ(1, removedSpy.count()); EXPECT_EQ(appId, removedSpy.takeFirst().at(0).toString()); } /* * Test that if a running application is reported to have stopped unexpectedly by upstart, AppMan * cleans up after it ok (as was not suspended, had not lifecycle saved its state, so cannot be resumed) */ TEST_F(ApplicationManagerTests,upstartNotifiesOfUnexpectedStopOfRunningApp) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); onSessionStopping(session); // Upstart notifies of crashing / OOM killed app applicationManager.onProcessFailed(appId, TaskController::Error::APPLICATION_CRASHED); // Upstart finally notifies the app stopped applicationManager.onProcessStopped(appId); EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback) EXPECT_EQ(applicationManager.count(), 0); EXPECT_EQ(removedSpy.count(), 1); EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId); } /* * Test that if a background application is reported to unexpectedly stop by upstart, AppMan does not remove * it from the app lists but instead considers it Stopped, ready to be resumed. This is due to the fact the * app should have saved its state, so can be resumed. This situation can occur due to the OOM killer, or * a dodgy app crashing. */ TEST_F(ApplicationManagerTests,unexpectedStopOfBackgroundApp) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); Application *app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); suspend(app); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy focusSpy(&applicationManager, SIGNAL(focusedApplicationIdChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Mir reports disconnection onSessionStopping(session); // Upstart notifies of crashing / OOM-killed app applicationManager.onProcessFailed(appId, TaskController::Error::APPLICATION_CRASHED); EXPECT_EQ(0, focusSpy.count()); // Upstart finally notifies the app stopped applicationManager.onProcessStopped(appId); EXPECT_EQ(0, countSpy.count()); EXPECT_EQ(1, applicationManager.count()); EXPECT_EQ(0, removedSpy.count()); } /* * Test that if a background application is reported to have stopped on startup by upstart, that it * is kept in the application model, as the app can be lifecycle resumed. * * Note that upstart reports this "stopped on startup" even for applications which are running. * This may be an upstart bug, or AppMan mis-understanding upstart's intentions. But need to check * we're doing the right thing in either case. CHECKME(greyback)! */ TEST_F(ApplicationManagerTests,unexpectedStopOfBackgroundAppCheckingUpstartBug) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); Application *app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); suspend(app); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy focusSpy(&applicationManager, SIGNAL(focusedApplicationIdChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Mir reports disconnection onSessionStopping(session); // Upstart notifies of crashing app applicationManager.onProcessFailed(appId, TaskController::Error::APPLICATION_FAILED_TO_START); EXPECT_EQ(focusSpy.count(), 0); // Upstart finally notifies the app stopped applicationManager.onProcessStopped(appId); EXPECT_EQ(countSpy.count(), 0); EXPECT_EQ(applicationManager.count(), 1); EXPECT_EQ(removedSpy.count(), 0); } /* * Test that if a Starting application is then reported to be stopping by Mir, AppMan cleans up after it ok */ TEST_F(ApplicationManagerTests,mirNotifiesStartingAppIsNowStopping) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Mir notifies of stopping app onSessionStopping(session); EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback) EXPECT_EQ(applicationManager.count(), 0); EXPECT_EQ(removedSpy.count(), 1); EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId); } /* * Test that if a Running foreground application is reported to be stopping by Mir, AppMan cleans up after it ok */ TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingForegroundApp) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); // Associate a surface so AppMan considers app Running, check focused FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Mir notifies of stopping app onSessionStopping(session); EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback) EXPECT_EQ(applicationManager.count(), 0); EXPECT_EQ(removedSpy.count(), 1); EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId); } /* * Test that if an application (one launched via desktop_file_hint) is reported to be stopping by * Mir, AppMan removes it from the model immediately */ TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingAppLaunchedWithDesktopFileHint) { using namespace ::testing; const QString appId("testAppId"); const QString name("Test App"); const pid_t procId = 5551; QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint="); cmdLine = cmdLine.append(appId); // Set up Mocks & signal watcher EXPECT_CALL(procInfo,command_line(procId)) .Times(1) .WillOnce(Return(cmdLine)); auto mockDesktopFileReader = new NiceMock(appId, QFileInfo()); ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true)); ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId)); ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader)); // Mir requests authentication for an application that was started bool authed = true; applicationManager.authorizeSession(procId, authed); EXPECT_EQ(authed, true); std::shared_ptr session = std::make_shared("", procId); onSessionStarting(session); // Associate a surface so AppMan considers app Running, check focused FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Mir notifies of stopping app onSessionStopping(session); EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback) EXPECT_EQ(applicationManager.count(), 0); EXPECT_EQ(removedSpy.count(), 1); Application *app = applicationManager.findApplication(appId); EXPECT_EQ(nullptr, app); } /* * Test that if a background application is reported to be stopping by Mir, AppMan sets its state to Stopped * but does not remove it from the model */ TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingBackgroundApp) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); Application *app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); EXPECT_EQ(Application::Starting, app->state()); app->setRequestedState(Application::RequestedSuspended); // should not suspend an app that`s still starting up ASSERT_EQ(Application::InternalState::Starting, app->internalState()); // Associate a surface so AppMan considers app Running FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); ASSERT_EQ(Application::InternalState::SuspendingWaitSession, app->internalState()); static_cast(app->session())->doSuspend(); ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, app->internalState()); applicationManager.onProcessSuspended(app->appId()); ASSERT_EQ(Application::InternalState::Suspended, app->internalState()); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Mir notifies of stopping app onSessionStopping(session); EXPECT_EQ(0, countSpy.count()); EXPECT_EQ(1, applicationManager.count()); EXPECT_EQ(0, removedSpy.count()); EXPECT_EQ(Application::Stopped, app->state()); } /* * Test that when an application is stopped correctly by shell, the upstart stopping event is ignored */ TEST_F(ApplicationManagerTests,shellStoppedApp_upstartStoppingEventIgnored) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); EXPECT_CALL(*taskController, stop(appId)) .Times(1) .WillOnce(Return(true)); applicationManager.stopApplication(appId); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // the mir session always ends before upstart notifies the process has stopped onSessionStopping(session); // Upstart notifies of stopping app applicationManager.onProcessStopped(appId); EXPECT_EQ(0, countSpy.count()); EXPECT_EQ(0, removedSpy.count()); } /* * Webapps have multiple sessions, but only one is linked to the application (other is considered a hidden session). * If webapp in foreground stops unexpectedly, remove it and it alone from app list */ TEST_F(ApplicationManagerTests,unexpectedStopOfForegroundWebapp) { using namespace ::testing; const QString appId("webapp"); const pid_t procId1 = 5551; const pid_t procId2 = 5564; QByteArray cmdLine("/usr/bin/qt5/libexec/QtWebProcess"); // Set up Mocks & signal watcher EXPECT_CALL(procInfo,command_line(procId2)) .Times(1) .WillOnce(Return(cmdLine)); ON_CALL(*taskController,appIdHasProcessId(appId, procId1)).WillByDefault(Return(true)); ON_CALL(*taskController,appIdHasProcessId(_, procId2)).WillByDefault(Return(false)); // Set up Mocks & signal watcher ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session1 = std::make_shared("", procId1); std::shared_ptr session2 = std::make_shared("", procId2); bool authed = false; applicationManager.authorizeSession(procId1, authed); onSessionStarting(session1); EXPECT_EQ(authed, true); applicationManager.authorizeSession(procId2, authed); onSessionStarting(session2); EXPECT_EQ(authed, true); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session2.get(), surface); surface->drawFirstFrame(); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Mir notifies of stopping app/Session onSessionStopping(session2); onSessionStopping(session1); EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback) EXPECT_EQ(applicationManager.count(), 0); EXPECT_EQ(removedSpy.count(), 1); EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId); } /* * Webapps have multiple sessions, but only one is linked to the application (other is considered a hidden session). * If webapp in background stops unexpectedly, do not remove it from app list */ TEST_F(ApplicationManagerTests,unexpectedStopOfBackgroundWebapp) { using namespace ::testing; const QString appId("webapp"); const pid_t procId1 = 5551; const pid_t procId2 = 5564; QByteArray cmdLine("/usr/bin/qt5/libexec/QtWebProcess"); // Set up Mocks & signal watcher EXPECT_CALL(procInfo,command_line(procId2)) .Times(1) .WillOnce(Return(cmdLine)); ON_CALL(*taskController,appIdHasProcessId(appId, procId1)).WillByDefault(Return(true)); ON_CALL(*taskController,appIdHasProcessId(_, procId2)).WillByDefault(Return(false)); // Set up Mocks & signal watcher ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); Application *app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session1 = std::make_shared("", procId1); std::shared_ptr session2 = std::make_shared("", procId2); bool authed = false; applicationManager.authorizeSession(procId1, authed); onSessionStarting(session1); EXPECT_EQ(true, authed); applicationManager.authorizeSession(procId2, authed); onSessionStarting(session2); EXPECT_EQ(true, authed); // both sessions create surfaces, then get them all suspended FakeMirSurface *surface1 = new FakeMirSurface; onSessionCreatedSurface(session1.get(), surface1); surface1->drawFirstFrame(); FakeMirSurface *surface2 = new FakeMirSurface; onSessionCreatedSurface(session2.get(), surface2); surface2->drawFirstFrame(); suspend(app); EXPECT_EQ(Application::Suspended, app->state()); QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged())); QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &))); // Mir notifies of stopping app/Session onSessionStopping(session2); onSessionStopping(session1); EXPECT_EQ(0, countSpy.count()); EXPECT_EQ(0, removedSpy.count()); } /* * Test for when a background application that has been OOM killed is relaunched by upstart. * AppMan will have the application in the app lists, in a Stopped state. Upstart will notify of * the app launching (like any normal app). Need to set the old Application instance to Starting * state and emit focusRequested to shell - authorizeSession will then associate new process with * the Application as normal. */ TEST_F(ApplicationManagerTests,stoppedBackgroundAppRelaunchedByUpstart) { using namespace ::testing; const QString appId("testAppId"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); Application *app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); // App creates surface, puts it in background, then is OOM killed. FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); suspend(app); onSessionStopping(session); applicationManager.onProcessFailed(appId, TaskController::Error::APPLICATION_CRASHED); applicationManager.onProcessStopped(appId); EXPECT_EQ(Application::Stopped, app->state()); QSignalSpy focusRequestSpy(&applicationManager, SIGNAL(focusRequested(const QString &))); // Upstart re-launches app applicationManager.onProcessStarting(appId); EXPECT_EQ(Application::Starting, app->state()); EXPECT_EQ(1, focusRequestSpy.count()); EXPECT_EQ(1, applicationManager.count()); } /* * Test that screenshotting callback works cross thread. */ TEST_F(ApplicationManagerTests, threadedScreenshot) { using namespace testing; const QString appId("webapp"); const pid_t procId = 5551; std::mutex mutex; std::condition_variable cv; bool done = false; ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); auto session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); ON_CALL(*session, take_snapshot(_)).WillByDefault(Invoke( [&](mir::scene::SnapshotCallback const& callback) { std::thread ([&, callback]() { std::unique_lock lk(mutex); mir::scene::Snapshot snapshot{mir::geometry::Size{0,0}, mir::geometry::Stride{0}, nullptr}; callback(snapshot); done = true; lk.unlock(); cv.notify_one(); }).detach(); })); auto mockSurface = std::make_shared(); EXPECT_CALL(*session, default_surface()).WillRepeatedly(Return(mockSurface)); { ApplicationScreenshotProvider screenshotProvider(&applicationManager); QSize actualSize; QSize requestedSize; QString imageId(appId + "/123456"); screenshotProvider.requestImage(imageId, &actualSize, requestedSize); } { std::unique_lock lk(mutex); cv.wait(lk, [&] { return done; } ); EXPECT_TRUE(done); } EXPECT_CALL(*taskController, stop(appId)) .Times(1) .WillOnce(Return(true)); applicationManager.stopApplication(appId); } /* * Test that screenshotting callback works when application has been deleted */ TEST_F(ApplicationManagerTests, threadedScreenshotAfterAppDelete) { using namespace testing; const pid_t procId1 = 5551; std::mutex mutex; std::condition_variable cv; bool done = false; auto application = startApplication(procId1, "webapp"); auto session = std::dynamic_pointer_cast(application->session()->session()); ON_CALL(*session, take_snapshot(_)).WillByDefault(Invoke( [&](mir::scene::SnapshotCallback const& callback) { std::thread ([&, callback]() { mir::scene::Snapshot snapshot{mir::geometry::Size{0,0}, mir::geometry::Stride{0}, nullptr}; // stop the application before calling the callback applicationManager.stopApplication(application->appId()); callback(snapshot); done = true; cv.notify_one(); }).detach(); })); auto mockSurface = std::make_shared(); EXPECT_CALL(*session, default_surface()).WillRepeatedly(Return(mockSurface)); { ApplicationScreenshotProvider screenshotProvider(&applicationManager); QSize actualSize; QSize requestedSize; QString imageId("webapp/123456"); screenshotProvider.requestImage(imageId, &actualSize, requestedSize); } { std::unique_lock lk(mutex); cv.wait(lk, [&] { return done; } ); } } TEST_F(ApplicationManagerTests, lifecycleExemptAppIsNotSuspended) { using namespace ::testing; const QString appId("testAppId"); const quint64 procId = 12345; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); auto the_app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); // App creates surface, focuses it so state is running FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); // Test normal lifecycle management as a control group ASSERT_EQ(Application::InternalState::Running, the_app->internalState()); ASSERT_EQ(Application::ProcessState::ProcessRunning, the_app->processState()); EXPECT_CALL(*(mir::scene::MockSession*)session.get(), set_lifecycle_state(mir_lifecycle_state_will_suspend)); the_app->setRequestedState(Application::RequestedSuspended); ASSERT_EQ(Application::InternalState::SuspendingWaitSession, the_app->internalState()); static_cast(the_app->session())->doSuspend(); ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, the_app->internalState()); applicationManager.onProcessSuspended(the_app->appId()); ASSERT_EQ(Application::InternalState::Suspended, the_app->internalState()); EXPECT_CALL(*(mir::scene::MockSession*)session.get(), set_lifecycle_state(mir_lifecycle_state_resumed)); the_app->setRequestedState(Application::RequestedRunning); EXPECT_EQ(Application::Running, the_app->state()); // Now mark the app as exempt from lifecycle management and retest the_app->setExemptFromLifecycle(true); EXPECT_EQ(Application::Running, the_app->state()); EXPECT_CALL(*(mir::scene::MockSession*)session.get(), set_lifecycle_state(_)).Times(0); the_app->setRequestedState(Application::RequestedSuspended); // And expect it to be running still ASSERT_EQ(Application::InternalState::RunningInBackground, the_app->internalState()); the_app->setRequestedState(Application::RequestedRunning); EXPECT_EQ(Application::Running, the_app->state()); ASSERT_EQ(Application::InternalState::Running, the_app->internalState()); } /* * Test lifecycle exempt applications have their wakelocks released when shell tries to suspend them */ TEST_F(ApplicationManagerTests, lifecycleExemptAppHasWakelockReleasedOnAttemptedSuspend) { using namespace ::testing; const QString appId("testAppId"); const quint64 procId = 12345; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); auto application = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); // App creates surface, focuses it so state is running FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); // Mark app as exempt application->setExemptFromLifecycle(true); application->setRequestedState(Application::RequestedSuspended); EXPECT_FALSE(sharedWakelock.enabled()); ASSERT_EQ(Application::InternalState::RunningInBackground, application->internalState()); EXPECT_EQ(Application::Running, application->state()); } /* * Test that when user stops an application, application does not delete QML cache */ TEST_F(ApplicationManagerTests,QMLcacheRetainedOnAppStop) { using namespace ::testing; const QString appId("testAppId1234"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); // Create fake QML cache for this app QString path(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/QML/Apps/") + appId); QDir dir(path); dir.mkpath(path); EXPECT_CALL(*taskController, stop(appId)) .Times(1) .WillOnce(Return(true)); // Stop app applicationManager.stopApplication(appId); EXPECT_EQ(0, applicationManager.count()); EXPECT_TRUE(dir.exists()); } /* * Test that if running application stops unexpectedly, AppMan deletes the QML cache */ TEST_F(ApplicationManagerTests,DISABLED_QMLcacheDeletedOnAppCrash) { using namespace ::testing; const QString appId("testAppId12345"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); Application *the_app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); // Have app in fully Running state FakeMirSurface *aSurface = new FakeMirSurface; onSessionCreatedSurface(session.get(), aSurface); aSurface->drawFirstFrame(); ASSERT_EQ(Application::InternalState::Running, the_app->internalState()); // Create fake QML cache for this app QString path(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/QML/Apps/") + appId); QDir dir(path); dir.mkpath(path); // Report app crash onSessionStopping(session); // Upstart notifies of **crashing** app applicationManager.onProcessFailed(appId, TaskController::Error::APPLICATION_FAILED_TO_START); applicationManager.onProcessStopped(appId); EXPECT_EQ(0, applicationManager.count()); EXPECT_FALSE(dir.exists()); } /* * Test that if running application stops itself cleanly, AppMan retains the QML cache */ TEST_F(ApplicationManagerTests,QMLcacheRetainedOnAppShutdown) { using namespace ::testing; const QString appId("testAppId123456"); const pid_t procId = 5551; // Set up Mocks & signal watcher ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); Application *the_app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); // Have app in fully Running state FakeMirSurface *aSurface = new FakeMirSurface; onSessionCreatedSurface(session.get(), aSurface); aSurface->drawFirstFrame(); ASSERT_EQ(Application::InternalState::Running, the_app->internalState()); // Create fake QML cache for this app QString path(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/QML/Apps/") + appId); QDir dir(path); dir.mkpath(path); // Report app stop onSessionStopping(session); // Upstart notifies of stopping app applicationManager.onProcessStopped(appId); EXPECT_EQ(0, applicationManager.count()); EXPECT_TRUE(dir.exists()); } /* * Test that there is an attempt at polite exiting of the app by requesting closure of the surface. */ TEST_F(ApplicationManagerTests,requestSurfaceCloseOnStop) { using namespace ::testing; const QString appId("testAppId"); quint64 procId = 5551; Application* app = startApplication(procId, appId); std::shared_ptr session = app->session()->session(); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); QSignalSpy spy(surface, SIGNAL(closeRequested())); // Stop app applicationManager.stopApplication(appId); EXPECT_EQ(1, spy.count()); } // * Test that if there is no surface available to the app when it is stopped, that it is forced to close. TEST_F(ApplicationManagerTests,forceAppDeleteWhenRemovedWithMissingSurface) { using namespace ::testing; int argc = 0; char* argv[0]; QCoreApplication qtApp(argc, argv); // app for deleteLater event const QString appId("testAppId"); quint64 procId = 5551; ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); auto app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); QSignalSpy spy(app, SIGNAL(destroyed(QObject*))); QObject::connect(app, &QObject::destroyed, app, [&qtApp](){ qtApp.quit(); }); // Stop app EXPECT_CALL(*taskController, stop(appId)) .Times(1) .WillOnce(Return(true)); applicationManager.stopApplication(appId); // the mir session always ends before upstart notifies the process has stopped onSessionStopping(session); // Upstart notifies of stopping app applicationManager.onProcessStopped(appId); qtApp.exec(); EXPECT_EQ(1, spy.count()); } /* * Test that if an application is started while it is still attempting to close, it is queued to start again. */ TEST_F(ApplicationManagerTests,applicationStartQueuedOnStartStopStart) { using namespace ::testing; int argc = 0; char* argv[0]; QCoreApplication qtApp(argc, argv); // app for deleteLater event const QString appId("testAppId"); quint64 procId = 5551; ON_CALL(*taskController, primaryPidForAppId(appId)).WillByDefault(Return(procId)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Invoke(createMockDesktopFileReader)); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); auto app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); EXPECT_EQ(Application::InternalState::Running, app->internalState()); // Stop app Mock::VerifyAndClearExpectations(taskController); EXPECT_CALL(*taskController, start(appId, _)) .Times(1) .WillOnce(Return(true)); QSignalSpy closeRequestedSpy(surface, SIGNAL(closeRequested())); applicationManager.stopApplication(appId); // Asking ApplicationManager to stop the application just makes it request its surfaces to be // closed EXPECT_EQ(Application::InternalState::Closing, app->internalState()); EXPECT_EQ(1, closeRequestedSpy.count()); // Trying to start a new instance of this app while we are still waiting for its current // instance to end yields no immediate result. This command gets queued instead. EXPECT_EQ(nullptr, applicationManager.startApplication(appId)); QSignalSpy appAddedSpy(&applicationManager, SIGNAL(applicationAdded(const QString&))); // Simulates that the application complied to the close() request and stopped itself onSessionStopping(session); applicationManager.onProcessStopped(appId); // DeferredDelete is special: likes to be called out specifically or it won't come out qtApp.sendPostedEvents(app, QEvent::DeferredDelete); qtApp.sendPostedEvents(); EXPECT_EQ(1, appAddedSpy.count()); } /* * Test that there is an attempt at polite exiting of the app by requesting closure of the surface. */ TEST_F(ApplicationManagerTests,suspendedApplicationResumesClosesAndDeletes) { using namespace ::testing; const QString appId("testAppId"); quint64 procId = 5551; Application* app = startApplication(procId, appId); std::shared_ptr session = app->session()->session(); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); EXPECT_EQ(Application::InternalState::Running, app->internalState()); EXPECT_EQ(SessionInterface::Running, app->session()->state()); // Suspend the application. suspend(app); EXPECT_EQ(Application::InternalState::Suspended, app->internalState()); // Stop app applicationManager.stopApplication(appId); EXPECT_EQ(Application::InternalState::Closing, app->internalState()); EXPECT_EQ(SessionInterface::Running, app->session()->state()); } /* * Test that a application which fails to close will eventually be forceable closed. */ TEST_F(ApplicationManagerTests,failedApplicationCloseEventualyDeletesApplication) { using namespace ::testing; int argc = 0; char* argv[0]; QCoreApplication qtApp(argc, argv); // app for deleteLater event const QString appId("testAppId"); quint64 procId = 5551; ON_CALL(procInfo,command_line(procId)).WillByDefault(Return(QByteArray("/usr/bin/testAppId"))); ON_CALL(*taskController,appIdHasProcessId(appId, procId)).WillByDefault(Return(true)); ON_CALL(desktopFileReaderFactory, createInstance(appId, _)) .WillByDefault(Invoke( [](const QString &appId, const QFileInfo&) { return new FakeDesktopFileReader(appId); } )); auto app = applicationManager.startApplication(appId); applicationManager.onProcessStarting(appId); std::shared_ptr session = std::make_shared("", procId); bool authed = true; applicationManager.authorizeSession(procId, authed); onSessionStarting(session); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session.get(), surface); surface->drawFirstFrame(); EXPECT_EQ(Application::InternalState::Running, app->internalState()); QSharedPointer fakeTimeSource(new FakeTimeSource); FakeTimer *fakeCloseTimer = new FakeTimer(fakeTimeSource); app->setCloseTimer(fakeCloseTimer); EXPECT_CALL(*taskController, stop(appId)) .Times(1) .WillOnce(Invoke( [this, session](const QString &appIdParam) { // No point in emitting it as applicationManager is not connected to the taskController // FIXME: Connect applicationManager to taskController so that we have a better test environment! // In the meantime call the ApplicationManager method directly, emulating the missing // signal-slot connection // Q_EMIT taskController->processStopped(appIdParam); onSessionStopping(session); applicationManager.onProcessStopped(appIdParam); return true; } )); QSignalSpy appDestroyedSpy(app, SIGNAL(destroyed(QObject*))); // Stop app applicationManager.stopApplication(appId); if (fakeCloseTimer->isRunning()) { // Simulate that closeTimer has timed out. fakeTimeSource->m_msecsSinceReference = fakeCloseTimer->nextTimeoutTime() + 1; fakeCloseTimer->update(); } // DeferredDelete is special: likes to be called out specifically or it won't come out qtApp.sendPostedEvents(app, QEvent::DeferredDelete); qtApp.sendPostedEvents(); EXPECT_EQ(1, appDestroyedSpy.count()); } /* * Test that an application that is suspended after its session is stopped is closed */ TEST_F(ApplicationManagerTests,CloseWhenSuspendedAfterSessionStopped) { using namespace ::testing; const QString appId("testAppId"); quint64 procId = 5551; auto application = startApplication(procId, "testAppId"); qtmir::Session* session(static_cast(application->session())); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session->session().get(), surface); surface->drawFirstFrame(); EXPECT_EQ(Application::InternalState::Running, application->internalState()); // session is suspended application->setRequestedState(Application::RequestedSuspended); EXPECT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState()); session->doSuspend(); EXPECT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState()); session->setLive(false); EXPECT_EQ(Application::InternalState::Closing, application->internalState()); // The process can be suspended after the session has dissapeared. applicationManager.onProcessSuspended(application->appId()); EXPECT_EQ(Application::InternalState::Closing, application->internalState()); QSignalSpy spy(application, SIGNAL(stopped())); applicationManager.onProcessStopped(application->appId()); EXPECT_EQ(Application::Stopped, application->state()); EXPECT_EQ(spy.count(), 1); } /* * Test that an application that fails while suspended will stop on close request */ TEST_F(ApplicationManagerTests,CloseWhenSuspendedProcessFailed) { using namespace ::testing; const QString appId("testAppId"); quint64 procId = 5551; auto application = startApplication(procId, "testAppId"); qtmir::Session* session(static_cast(application->session())); FakeMirSurface *surface = new FakeMirSurface; onSessionCreatedSurface(session->session().get(), surface); surface->drawFirstFrame(); EXPECT_EQ(Application::InternalState::Running, application->internalState()); // Session is suspended suspend(application); // Process failed onSessionStopping(session->session()); applicationManager.onProcessFailed(appId, TaskController::Error::APPLICATION_FAILED_TO_START); applicationManager.onProcessStopped(appId); EXPECT_EQ(Application::InternalState::StoppedResumable, application->internalState()); QSignalSpy spy(application, SIGNAL(stopped())); application->close(); EXPECT_EQ(Application::Stopped, application->state()); EXPECT_EQ(spy.count(), 1); } ./tests/modules/ApplicationManager/CMakeLists.txt0000644000015600001650000000134512677054330022217 0ustar jenkinsjenkinsset( APPLICATION_MANAGER_TEST_SOURCES application_manager_test.cpp ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp ) include_directories( ${APPLICATION_API_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${CMAKE_SOURCE_DIR}/src/modules ${CMAKE_SOURCE_DIR}/tests/framework ${MIRSERVER_INCLUDE_DIRS} ) add_executable(applicationmanager_test ${APPLICATION_MANAGER_TEST_SOURCES}) add_dependencies(applicationmanager_test qtmir-test-framework-static) target_link_libraries( applicationmanager_test Qt5::Test unityapplicationplugin -L${CMAKE_BINARY_DIR}/tests/framework qtmir-test-framework-static ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(ApplicationManager applicationmanager_test) ./tests/modules/Application/0000755000015600001650000000000012677054330016161 5ustar jenkinsjenkins./tests/modules/Application/CMakeLists.txt0000644000015600001650000000130012677054330020713 0ustar jenkinsjenkinsset( APPLICATION_TEST_SOURCES application_test.cpp ) include_directories( ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${CMAKE_SOURCE_DIR}/src/modules ${CMAKE_SOURCE_DIR}/tests/framework ${MIRSERVER_INCLUDE_DIRS} ${Qt5Core_INCLUDE_DIRS} ${Qt5GUI_INCLUDE_DIRS} ${Qt5Quick_INCLUDE_DIRS} ${Qt5DBus_INCLUDE_DIRS} ) add_executable(application_test ${APPLICATION_TEST_SOURCES}) add_dependencies(application_test qtmir-test-framework-static) target_link_libraries( application_test Qt5::Test unityapplicationplugin -L${CMAKE_BINARY_DIR}/tests/framework qtmir-test-framework-static ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(Application, application_test) ./tests/modules/Application/application_test.cpp0000644000015600001650000002543512677054330022240 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #include "qtmir_test.h" #include #include #include #include #include using namespace qtmir; class ApplicationTests : public ::testing::QtMirTest { public: ApplicationTests() {} }; TEST_F(ApplicationTests, acquiresWakelockWhenRunningAndReleasesWhenSuspended) { using namespace ::testing; QScopedPointer application(new Application( QSharedPointer(&sharedWakelock, [](MockSharedWakelock *){}), new FakeDesktopFileReader, QStringList(), nullptr)); application->setProcessState(Application::ProcessRunning); FakeSession *session = new FakeSession; application->setSession(session); ASSERT_EQ(Application::InternalState::Starting, application->internalState()); session->setState(SessionInterface::Running); EXPECT_TRUE(sharedWakelock.enabled()); ASSERT_EQ(Application::InternalState::Running, application->internalState()); application->setRequestedState(Application::RequestedSuspended); ASSERT_EQ(SessionInterface::Suspending, session->state()); ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState()); session->setState(SessionInterface::Suspended); ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState()); application->setProcessState(Application::ProcessSuspended); ASSERT_EQ(Application::InternalState::Suspended, application->internalState()); EXPECT_FALSE(sharedWakelock.enabled()); } TEST_F(ApplicationTests, checkResumeAcquiresWakeLock) { using namespace ::testing; QScopedPointer application(new Application( QSharedPointer(&sharedWakelock, [](MockSharedWakelock *){}), new FakeDesktopFileReader, QStringList(), nullptr)); NiceMock *session = new NiceMock; // Get it running and then suspend it application->setProcessState(Application::ProcessRunning); application->setSession(session); session->setState(SessionInterface::Running); application->setRequestedState(Application::RequestedSuspended); session->setState(SessionInterface::Suspended); application->setProcessState(Application::ProcessSuspended); ASSERT_EQ(Application::InternalState::Suspended, application->internalState()); EXPECT_FALSE(sharedWakelock.enabled()); application->setRequestedState(Application::RequestedRunning); ASSERT_EQ(Application::InternalState::Running, application->internalState()); EXPECT_TRUE(sharedWakelock.enabled()); } TEST_F(ApplicationTests, checkRespawnAcquiresWakeLock) { using namespace ::testing; QScopedPointer application(new Application( QSharedPointer(&sharedWakelock, [](MockSharedWakelock *){}), new FakeDesktopFileReader, QStringList(), nullptr)); NiceMock *session = new NiceMock; // Get it running, suspend it, and finally stop it application->setProcessState(Application::ProcessRunning); application->setSession(session); session->setState(SessionInterface::Running); application->setRequestedState(Application::RequestedSuspended); session->setState(SessionInterface::Suspended); application->setProcessState(Application::ProcessSuspended); ASSERT_EQ(Application::InternalState::Suspended, application->internalState()); session->setState(SessionInterface::Stopped); application->setProcessState(Application::ProcessFailed); ASSERT_EQ(Application::InternalState::StoppedResumable, application->internalState()); EXPECT_FALSE(sharedWakelock.enabled()); QSignalSpy spyStartProcess(application.data(), SIGNAL(startProcessRequested())); application->setRequestedState(Application::RequestedRunning); ASSERT_EQ(1, spyStartProcess.count()); application->setProcessState(Application::ProcessRunning); ASSERT_EQ(Application::InternalState::Starting, application->internalState()); EXPECT_TRUE(sharedWakelock.enabled()); } TEST_F(ApplicationTests, checkDashDoesNotImpactWakeLock) { using namespace ::testing; EXPECT_CALL(sharedWakelock, acquire(_)).Times(0); EXPECT_CALL(sharedWakelock, release(_)).Times(0); FakeDesktopFileReader *desktopFileReader = new FakeDesktopFileReader; desktopFileReader->m_appId = QString("unity8-dash"); QScopedPointer application(new Application( QSharedPointer(&sharedWakelock, [](MockSharedWakelock *){}), desktopFileReader, QStringList(), nullptr)); application->setProcessState(Application::ProcessRunning); FakeSession *session = new FakeSession; application->setSession(session); ASSERT_EQ(Application::InternalState::Starting, application->internalState()); session->setState(SessionInterface::Running); ASSERT_EQ(Application::InternalState::Running, application->internalState()); application->setRequestedState(Application::RequestedSuspended); ASSERT_EQ(SessionInterface::Suspending, session->state()); ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState()); session->setState(SessionInterface::Suspended); ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState()); application->setProcessState(Application::ProcessSuspended); ASSERT_EQ(Application::InternalState::Suspended, application->internalState()); application->setRequestedState(Application::RequestedRunning); ASSERT_EQ(Application::InternalState::Running, application->internalState()); } TEST_F(ApplicationTests, emitsStoppedWhenRunningAppStops) { using namespace ::testing; QScopedPointer application(new Application( QSharedPointer(&sharedWakelock, [](MockSharedWakelock *){}), new FakeDesktopFileReader, QStringList(), nullptr)); application->setProcessState(Application::ProcessRunning); FakeSession *session = new FakeSession; application->setSession(session); QSignalSpy spyAppStopped(application.data(), SIGNAL(stopped())); session->setState(SessionInterface::Running); ASSERT_EQ(Application::InternalState::Running, application->internalState()); //// // Simulate a running application closing itself (ie, ending its own process) session->setState(SessionInterface::Stopped); ASSERT_EQ(Application::InternalState::Stopped, application->internalState()); application->setProcessState(Application::ProcessStopped); ASSERT_EQ(1, spyAppStopped.count()); } /** * Regression test for https://bugs.launchpad.net/qtmir/+bug/1485608 * In that case, the camera-app closes itself right after unity8 unfocus it (and thus requests it to be suspended). */ TEST_F(ApplicationTests, emitsStoppedWhenAppStopsWhileSuspending) { using namespace ::testing; QScopedPointer application(new Application( QSharedPointer(&sharedWakelock, [](MockSharedWakelock *){}), new FakeDesktopFileReader, QStringList(), nullptr)); application->setProcessState(Application::ProcessRunning); FakeSession *session = new FakeSession; application->setSession(session); QSignalSpy spyAppStopped(application.data(), SIGNAL(stopped())); session->setState(SessionInterface::Running); ASSERT_EQ(Application::InternalState::Running, application->internalState()); application->setRequestedState(Application::RequestedSuspended); ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState()); // Now the application closes itself (ie, ending its own process) // Session always responds before the process state. session->setState(SessionInterface::Stopped); application->setProcessState(Application::ProcessStopped); ASSERT_EQ(Application::InternalState::Stopped, application->internalState()); ASSERT_EQ(1, spyAppStopped.count()); } TEST_F(ApplicationTests, doesNotEmitStoppedWhenKilledWhileSuspended) { using namespace ::testing; QScopedPointer application(new Application( QSharedPointer(&sharedWakelock, [](MockSharedWakelock *){}), new FakeDesktopFileReader, QStringList(), nullptr)); application->setProcessState(Application::ProcessRunning); FakeSession *session = new FakeSession; application->setSession(session); QSignalSpy spyAppStopped(application.data(), SIGNAL(stopped())); session->setState(SessionInterface::Running); ASSERT_EQ(Application::InternalState::Running, application->internalState()); application->setRequestedState(Application::RequestedSuspended); ASSERT_EQ(SessionInterface::Suspending, session->state()); ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState()); session->setState(SessionInterface::Suspended); ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState()); application->setProcessState(Application::ProcessSuspended); /// // Now simulate the process getting killed. Mir session always ends before // we get notified about the process state session->setState(SessionInterface::Stopped); application->setProcessState(Application::ProcessFailed); ASSERT_EQ(Application::InternalState::StoppedResumable, application->internalState()); ASSERT_EQ(0, spyAppStopped.count()); } TEST_F(ApplicationTests, passesIsTouchAppThrough) { using namespace ::testing; auto mockDesktopFileReader = new NiceMock(QString(), QFileInfo()); QScopedPointer application(new Application( QSharedPointer(&sharedWakelock, [](MockSharedWakelock *){}), mockDesktopFileReader, QStringList(), nullptr)); ON_CALL(*mockDesktopFileReader, isTouchApp()).WillByDefault(Return(true)); ASSERT_TRUE(application->isTouchApp()); ON_CALL(*mockDesktopFileReader, isTouchApp()).WillByDefault(Return(false)); ASSERT_FALSE(application->isTouchApp()); } ./tests/modules/SharedWakelock/0000755000015600001650000000000012677054330016605 5ustar jenkinsjenkins./tests/modules/SharedWakelock/sharedwakelock_test.cpp0000644000015600001650000003026712677054330023347 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #include #include #include using namespace qtmir; using namespace testing; using namespace QtDBusTest; using namespace QtDBusMock; const char POWERD_NAME[] = "com.canonical.powerd"; const char POWERD_PATH[] = "/com/canonical/powerd"; const char POWERD_INTERFACE[] = "com.canonical.powerd"; class SharedWakelockTest: public Test { protected: SharedWakelockTest() : mock(dbus) { mock.registerCustomMock(POWERD_NAME, POWERD_PATH, POWERD_INTERFACE, QDBusConnection::SystemBus); dbus.startServices(); } virtual ~SharedWakelockTest() {} virtual OrgFreedesktopDBusMockInterface& powerdMockInterface() { return mock.mockInterface(POWERD_NAME, POWERD_PATH, POWERD_INTERFACE, QDBusConnection::SystemBus); } void implementRequestSysState() { // Defines the mock impementation of this DBus method powerdMockInterface().AddMethod("com.canonical.powerd", "requestSysState", "si", "s", "ret = 'cookie'").waitForFinished(); } void implementClearSysState() { powerdMockInterface().AddMethod("com.canonical.powerd", "clearSysState", "s", "", "").waitForFinished(); } void EXPECT_CALL(const QList &spy, int index, const QString &name, const QVariantList &args) { QVariant args2(QVariant::fromValue(args)); ASSERT_LT(index, spy.size()); const QVariantList &call(spy.at(index)); EXPECT_EQ(name, call.at(0).toString()); EXPECT_EQ(args2.toString().toStdString(), call.at(1).toString().toStdString()); } DBusTestRunner dbus; DBusMock mock; }; TEST_F(SharedWakelockTest, acquireCreatesAWakelock) { implementRequestSysState(); /* Note: we pass the DBusTestRunner constructed system DBus connection instead of letting * Qt create one. This is done as Qt has a non-testing friendly way of handling the built-in * system and session connections, and as DBusTestRunner restarts the DBus daemon for each * testcase, it unfortunately means Qt would end up pointing to a dead bus after one testcase. */ SharedWakelock wakelock(dbus.systemConnection()); // Verify the DBus method is called & wakelock QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); QScopedPointer object(new QObject); wakelock.acquire(object.data()); wakelockDBusMethodSpy.wait(); EXPECT_FALSE(wakelockDBusMethodSpy.empty()); EXPECT_CALL(wakelockDBusMethodSpy, 0, "requestSysState", QVariantList() << QString("active") << 1); // Ensure a wakelock created wakelockEnabledSpy.wait(); EXPECT_FALSE(wakelockEnabledSpy.empty()); EXPECT_TRUE(wakelock.enabled()); } TEST_F(SharedWakelockTest, acquireThenReleaseDestroysTheWakelock) { implementRequestSysState(); implementClearSysState(); SharedWakelock wakelock(dbus.systemConnection()); QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); QScopedPointer object(new QObject); wakelock.acquire(object.data()); wakelockEnabledSpy.wait(); // Verify the DBus method is called QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); wakelock.release(object.data()); wakelockDBusMethodSpy.wait(); EXPECT_FALSE(wakelockDBusMethodSpy.empty()); EXPECT_CALL(wakelockDBusMethodSpy, 0, "clearSysState", QVariantList() << QString("cookie")); EXPECT_FALSE(wakelock.enabled()); } TEST_F(SharedWakelockTest, doubleAcquireBySameOwnerOnlyCreatesASingleWakelock) { implementRequestSysState(); SharedWakelock wakelock(dbus.systemConnection()); // Verify the DBus method is called & wakelock QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); QScopedPointer object(new QObject); wakelock.acquire(object.data()); wakelock.acquire(object.data()); wakelockDBusMethodSpy.wait(); EXPECT_EQ(wakelockDBusMethodSpy.count(), 1); } TEST_F(SharedWakelockTest, doubleAcquireThenReleaseBySameOwnerDestroysWakelock) { implementRequestSysState(); implementClearSysState(); SharedWakelock wakelock(dbus.systemConnection()); QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); QScopedPointer object(new QObject); wakelock.acquire(object.data()); wakelock.acquire(object.data()); wakelock.release(object.data()); wakelockEnabledSpy.wait(); EXPECT_FALSE(wakelock.enabled()); } TEST_F(SharedWakelockTest, acquireByDifferentOwnerOnlyCreatesASingleWakelock) { implementRequestSysState(); SharedWakelock wakelock(dbus.systemConnection()); // Verify the DBus method is called & wakelock QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); QScopedPointer object1(new QObject); QScopedPointer object2(new QObject); wakelock.acquire(object1.data()); wakelock.acquire(object2.data()); wakelockDBusMethodSpy.wait(); EXPECT_EQ(wakelockDBusMethodSpy.count(), 1); } TEST_F(SharedWakelockTest, twoOwnersWhenBothReleaseWakelockReleased) { implementRequestSysState(); SharedWakelock wakelock(dbus.systemConnection()); QScopedPointer object1(new QObject); QScopedPointer object2(new QObject); wakelock.acquire(object1.data()); wakelock.acquire(object2.data()); QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); wakelock.release(object1.data()); wakelock.release(object2.data()); wakelockEnabledSpy.wait(); EXPECT_FALSE(wakelock.enabled()); } TEST_F(SharedWakelockTest, doubleReleaseOfSingleOwnerIgnored) { implementRequestSysState(); SharedWakelock wakelock(dbus.systemConnection()); QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); QScopedPointer object1(new QObject); QScopedPointer object2(new QObject); wakelock.acquire(object1.data()); wakelock.acquire(object2.data()); wakelock.release(object1.data()); wakelockEnabledSpy.wait(); wakelock.release(object1.data()); EXPECT_TRUE(wakelock.enabled()); } TEST_F(SharedWakelockTest, wakelockAcquireReleaseFlood) { implementRequestSysState(); implementClearSysState(); SharedWakelock wakelock(dbus.systemConnection()); QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); QScopedPointer object(new QObject); wakelock.acquire(object.data()); wakelock.release(object.data()); wakelock.acquire(object.data()); wakelock.release(object.data()); wakelock.acquire(object.data()); wakelock.release(object.data()); while (wakelockEnabledSpy.wait(100)) {} EXPECT_FALSE(wakelock.enabled()); } TEST_F(SharedWakelockTest, wakelockAcquireReleaseAcquireWithDelays) { powerdMockInterface().AddMethod("com.canonical.powerd", "requestSysState", "si", "s", "i=100000\n" // delay the response from the mock dbus instance "while i>0:\n" " i-=1\n" "ret = 'cookie'").waitForFinished(); implementClearSysState(); SharedWakelock wakelock(dbus.systemConnection()); QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); QScopedPointer object(new QObject); wakelock.acquire(object.data()); wakelock.release(object.data()); wakelock.acquire(object.data()); while (wakelockDBusMethodSpy.wait()) {} EXPECT_TRUE(wakelock.enabled()); // there must be at least one clearSysState call, but is not necessarily the second call // should the dbus response be slow enough, it may be the third call. Is hard to reliably // reproduce the racey condition for all platforms. bool found = false; for (int i=0; i object(new QObject); wakelock.acquire(object.data()); wakelockEnabledSpy.wait(); // Verify the DBus method is called QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); object.reset(); wakelockDBusMethodSpy.wait(); EXPECT_FALSE(wakelockDBusMethodSpy.empty()); EXPECT_CALL(wakelockDBusMethodSpy, 0, "clearSysState", QVariantList() << QString("cookie")); EXPECT_FALSE(wakelock.enabled()); } TEST_F(SharedWakelockTest, reloadCachedWakelockCookie) { const char cookieFile[] = "/tmp/qtmir_powerd_cookie"; // Custom Deleter for QScopedPointer to delete the qtmir cookie file no matter what struct ScopedQFileCustomDeleter { static inline void cleanup(QFile *file) { file->remove(); delete file; } }; QScopedPointer cookieCache(new QFile(cookieFile)); cookieCache->open(QFile::WriteOnly | QFile::Text); cookieCache->write("myCookie"); SharedWakelock wakelock(dbus.systemConnection()); EXPECT_TRUE(wakelock.enabled()); } TEST_F(SharedWakelockTest, wakelockReleasedOnSharedWakelockDestroyed) { implementRequestSysState(); implementClearSysState(); auto wakelock = new SharedWakelock(dbus.systemConnection()); QSignalSpy wakelockEnabledSpy(wakelock, SIGNAL( enabledChanged(bool) )); QScopedPointer object(new QObject); wakelock->acquire(object.data()); wakelockEnabledSpy.wait(); // wait for wakelock to be enabled QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); delete wakelock; wakelockDBusMethodSpy.wait(); EXPECT_FALSE(wakelockDBusMethodSpy.empty()); EXPECT_CALL(wakelockDBusMethodSpy, 0, "clearSysState", QVariantList() << QString("cookie")); } // Define custom main() as these tests rely on a QEventLoop int main(int argc, char** argv) { QCoreApplication app(argc, argv); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ./tests/modules/SharedWakelock/CMakeLists.txt0000644000015600001650000000074312677054330021351 0ustar jenkinsjenkinsset( SHARED_WAKELOCK_TEST_SOURCES sharedwakelock_test.cpp ) include_directories( ${CMAKE_SOURCE_DIR}/src/modules ${QTDBUSTEST_INCLUDE_DIRS} ${QTDBUSMOCK_INCLUDE_DIRS} ) add_executable(sharedwakelock_test ${SHARED_WAKELOCK_TEST_SOURCES}) target_link_libraries( sharedwakelock_test unityapplicationplugin Qt5::Test ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ${QTDBUSTEST_LIBRARIES} ${QTDBUSMOCK_LIBRARIES} ) add_test(SharedWakelock, sharedwakelock_test) ./tests/modules/General/0000755000015600001650000000000012677054330015273 5ustar jenkinsjenkins./tests/modules/General/objectlistmodel_test.cpp0000644000015600001650000000721712677054330022230 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #include using namespace qtmir; TEST(ObjectListModelTests, TestInsert) { using namespace testing; ObjectListModel model; QObject object1; QObject object2; QObject object3; QObject object4; QObject object5; // first model.insert(0, &object1); EXPECT_THAT(model.list(), ElementsAre(&object1)); // append model.insert(model.rowCount(), &object2); EXPECT_THAT(model.list(), ElementsAre(&object1, &object2)); // prepend model.insert(0, &object3); EXPECT_THAT(model.list(), ElementsAre(&object3, &object1, &object2)); // overflow model.insert(14, &object4); EXPECT_THAT(model.list(), ElementsAre(&object3, &object1, &object2, &object4)); // middle model.insert(2, &object5); EXPECT_THAT(model.list(), ElementsAre(&object3, &object1, &object5, &object2, &object4)); } TEST(ObjectListModelTests, TestMove) { using namespace testing; ObjectListModel model; QObject object1; QObject object2; QObject object3; QObject object4; QObject object5; model.insert(0, &object1); model.insert(1, &object2); model.insert(2, &object3); model.insert(3, &object4); model.insert(4, &object5); // move before model.insert(0, &object3); EXPECT_THAT(model.list(), ElementsAre(&object3, &object1, &object2, &object4, &object5)); // move after model.insert(3, &object1); EXPECT_THAT(model.list(), ElementsAre(&object3, &object2, &object4, &object1, &object5)); // move end model.insert(4, &object3); EXPECT_THAT(model.list(), ElementsAre(&object2, &object4, &object1, &object5, &object3)); // no move model.insert(1, &object4); EXPECT_THAT(model.list(), ElementsAre(&object2, &object4, &object1, &object5, &object3)); // move past end. model.insert(model.rowCount(), &object2); EXPECT_THAT(model.list(), ElementsAre(&object4, &object1, &object5, &object3, &object2)); } TEST(ObjectListModelTests, TestRemove) { using namespace testing; ObjectListModel model; QObject object1; QObject object2; QObject object3; QObject object4; QObject object5; QObject object6; model.insert(0, &object1); model.insert(1, &object2); model.insert(2, &object3); model.insert(3, &object4); model.insert(4, &object5); // first model.remove(&object1); EXPECT_THAT(model.list(), ElementsAre(&object2, &object3, &object4, &object5)); // just-removed model.remove(&object1); EXPECT_THAT(model.list(), ElementsAre(&object2, &object3, &object4, &object5)); // last model.remove(&object5); EXPECT_THAT(model.list(), ElementsAre(&object2, &object3, &object4)); // middle model.remove(&object3); EXPECT_THAT(model.list(), ElementsAre(&object2, &object4)); // non-existant model.remove(&object6); EXPECT_THAT(model.list(), ElementsAre(&object2, &object4)); } ./tests/modules/General/timestamp_test.cpp0000644000015600001650000000506512677054330021047 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "timestamp.h" #include #include #include #include using namespace qtmir; class TimestampTest: public ::testing::Test { protected: virtual void SetUp() { resetStartTime(std::chrono::nanoseconds(0)); } }; TEST_F(TimestampTest, TestCompressAndUncompress) { using namespace testing; std::chrono::time_point now; now = std::chrono::system_clock::now(); auto original_timestamp = now.time_since_epoch(); qtmir::Timestamp addToTimestamp(0); for (int i = 0; i < 100; i++) { auto timestamp = original_timestamp + addToTimestamp; qtmir::Timestamp compressedTimestamp = qtmir::compressTimestamp(timestamp); EXPECT_EQ(addToTimestamp, compressedTimestamp); EXPECT_EQ(qtmir::uncompressTimestamp(compressedTimestamp), timestamp); addToTimestamp += std::chrono::seconds(1); } } TEST_F(TimestampTest, TestOverflowWhenExceeding32bitCompression) { using namespace testing; std::chrono::time_point now; now = std::chrono::system_clock::now(); auto timestamp = now.time_since_epoch(); typedef std::chrono::duration Timestamp32bit; // Do first compression. This will result in qield of 0 as seen in TestCompressUncompress auto compressedTimestamp = qtmir::compressTimestamp(timestamp); // Add the quint32 limit +1 to get an overflow when we compress the timestamp timestamp += Timestamp32bit::max() + std::chrono::nanoseconds(1); compressedTimestamp = qtmir::compressTimestamp(timestamp); EXPECT_EQ(0, compressedTimestamp.count()); // ensure the uncompression will yields the original timestamp EXPECT_EQ(qtmir::uncompressTimestamp(compressedTimestamp), timestamp); }./tests/modules/General/CMakeLists.txt0000644000015600001650000000062312677054330020034 0ustar jenkinsjenkinsset( GENERAL_TEST_SOURCES objectlistmodel_test.cpp timestamp_test.cpp ${CMAKE_SOURCE_DIR}/src/common/timestamp.cpp ) include_directories( ${CMAKE_SOURCE_DIR}/src/common ${CMAKE_SOURCE_DIR}/src/modules ) add_executable(general_test ${GENERAL_TEST_SOURCES}) target_link_libraries( general_test Qt5::Gui ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(General general_test) ./tests/modules/SessionManager/0000755000015600001650000000000012677054330016634 5ustar jenkinsjenkins./tests/modules/SessionManager/session_manager_test.cpp0000644000015600001650000001110212677054330023547 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #include #include #include "qtmir_test.h" using namespace qtmir; using mir::scene::MockSession; namespace ms = mir::scene; class SessionManagerTests : public ::testing::QtMirTest { public: SessionManagerTests() {} QList> listPromptSessions(SessionInterface* session) { QList> promptSessions; session->foreachPromptSession([&promptSessions](const std::shared_ptr& promptSession) { promptSessions << promptSession; }); return promptSessions; } QList listChildSessions(SessionInterface* session) { QList sessions; session->foreachChildSession([&sessions](SessionInterface* session) { sessions << session; }); return sessions; } }; TEST_F(SessionManagerTests, sessionTracksPromptSession) { using namespace testing; std::shared_ptr mirAppSession = std::make_shared("mirAppSession", __LINE__); sessionManager.onSessionStarting(mirAppSession); SessionInterface* qtmirAppSession = sessionManager.findSession(mirAppSession.get()); EXPECT_TRUE(qtmirAppSession != nullptr); auto promptSession = std::make_shared(); ON_CALL(*promptSessionManager, application_for(_)).WillByDefault(Return(mirAppSession)); sessionManager.onPromptSessionStarting(promptSession); EXPECT_EQ(qtmirAppSession->activePromptSession(), promptSession); sessionManager.onPromptSessionStopping(promptSession); EXPECT_EQ(qtmirAppSession->activePromptSession(), nullptr); delete qtmirAppSession; } TEST_F(SessionManagerTests, TestPromptSession) { using namespace testing; std::shared_ptr mirAppSession = std::make_shared("mirAppSession", __LINE__); sessionManager.onSessionStarting(mirAppSession); SessionInterface* qtmirAppSession = sessionManager.findSession(mirAppSession.get()); EXPECT_TRUE(qtmirAppSession != nullptr); EXPECT_CALL(*promptSessionManager, application_for(_)).WillRepeatedly(Return(mirAppSession)); EXPECT_CALL(*promptSessionManager, helper_for(_)).WillRepeatedly(Return(nullptr)); std::shared_ptr mirPromptSession = std::make_shared(); // prompt provider session std::shared_ptr mirProviderSession = std::make_shared("mirProviderSession", __LINE__); sessionManager.onSessionStarting(mirProviderSession); SessionInterface* qtmirProviderSession = sessionManager.findSession(mirProviderSession.get()); EXPECT_CALL(*promptSessionManager, for_each_provider_in(mirPromptSession,_)).WillRepeatedly(WithArgs<1>(Invoke( [&](std::function const& prompt_provider)> const& f) { f(mirProviderSession); }))); EXPECT_THAT(listPromptSessions(qtmirAppSession), IsEmpty()); sessionManager.onPromptSessionStarting(mirPromptSession); EXPECT_THAT(listPromptSessions(qtmirAppSession), ElementsAre(mirPromptSession)); EXPECT_THAT(listChildSessions(qtmirAppSession), IsEmpty()); sessionManager.onPromptProviderAdded(mirPromptSession.get(), mirProviderSession); EXPECT_THAT(listChildSessions(qtmirAppSession), ElementsAre(qtmirProviderSession)); EXPECT_CALL(*promptSessionManager, for_each_provider_in(mirPromptSession,_)).WillRepeatedly(InvokeWithoutArgs([]{})); EXPECT_EQ(qtmirProviderSession->live(), true); sessionManager.onPromptProviderRemoved(mirPromptSession.get(), mirProviderSession); EXPECT_EQ(qtmirProviderSession->live(), false); sessionManager.onPromptSessionStopping(mirPromptSession); EXPECT_THAT(listPromptSessions(qtmirAppSession), IsEmpty()); delete qtmirProviderSession; delete qtmirAppSession; } ./tests/modules/SessionManager/session_test.cpp0000644000015600001650000002607112677054330022070 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #include #include #include "stub_scene_surface.h" using namespace qtmir; using mir::scene::MockSession; namespace ms = mir::scene; namespace mtd = mir::test::doubles; class SessionTests : public ::testing::QtMirTest { public: SessionTests() {} QList listChildSessions(Session* session) { QList sessions; session->foreachChildSession([&sessions](SessionInterface* session) { sessions << session; }); return sessions; } }; TEST_F(SessionTests, FromStartingToRunningOnceSurfaceDrawsFirstFrame) { using namespace testing; const QString appId("test-app"); const pid_t procId = 5551; auto mirSession = std::make_shared(appId.toStdString(), procId); auto session = std::make_shared(mirSession, mirServer->the_prompt_session_manager()); // On Starting as it has no surface. EXPECT_EQ(Session::Starting, session->state()); FakeMirSurface *surface = new FakeMirSurface; session->registerSurface(surface); // Still on Starting as the surface hasn't drawn its first frame yet EXPECT_EQ(Session::Starting, session->state()); surface->drawFirstFrame(); EXPECT_EQ(Session::Running, session->state()); } TEST_F(SessionTests, AddChildSession) { using namespace testing; const QString appId("test-app"); const pid_t procId = 5551; std::shared_ptr mirSession = std::make_shared(appId.toStdString(), procId); Session session(mirSession, mirServer->the_prompt_session_manager()); Session session1(mirSession, mirServer->the_prompt_session_manager()); Session session2(mirSession, mirServer->the_prompt_session_manager()); Session session3(mirSession, mirServer->the_prompt_session_manager()); // add surfaces session.addChildSession(&session1); EXPECT_EQ(session1.parentSession(), &session); EXPECT_THAT(listChildSessions(&session), ElementsAre(&session1)); session.addChildSession(&session2); EXPECT_EQ(session2.parentSession(), &session); EXPECT_THAT(listChildSessions(&session), ElementsAre(&session1, &session2)); session.addChildSession(&session3); EXPECT_EQ(session3.parentSession(), &session); EXPECT_THAT(listChildSessions(&session), ElementsAre(&session1, &session2, &session3)); } TEST_F(SessionTests, InsertChildSession) { using namespace testing; const QString appId("test-app"); const pid_t procId = 5551; std::shared_ptr mirSession = std::make_shared(appId.toStdString(), procId); Session session(mirSession, mirServer->the_prompt_session_manager()); Session session1(mirSession, mirServer->the_prompt_session_manager()); Session session2(mirSession, mirServer->the_prompt_session_manager()); Session session3(mirSession, mirServer->the_prompt_session_manager()); // add surfaces session.insertChildSession(100, &session1); // test overflow EXPECT_EQ(session1.parentSession(), &session); EXPECT_THAT(listChildSessions(&session), ElementsAre(&session1)); session.insertChildSession(0, &session2); // test insert before EXPECT_EQ(session2.parentSession(), &session); EXPECT_THAT(listChildSessions(&session), ElementsAre(&session2, &session1)); session.insertChildSession(1, &session3); // test before end EXPECT_EQ(session3.parentSession(), &session); EXPECT_THAT(listChildSessions(&session), ElementsAre(&session2, &session3, &session1)); } TEST_F(SessionTests, RemoveChildSession) { using namespace testing; const QString appId("test-app"); const pid_t procId = 5551; std::shared_ptr mirSession = std::make_shared(appId.toStdString(), procId); Session session(mirSession, mirServer->the_prompt_session_manager()); Session session1(mirSession, mirServer->the_prompt_session_manager()); Session session2(mirSession, mirServer->the_prompt_session_manager()); Session session3(mirSession, mirServer->the_prompt_session_manager()); // add surfaces session.addChildSession(&session1); session.addChildSession(&session2); session.addChildSession(&session3); // remove surfaces session.removeChildSession(&session2); EXPECT_EQ(session2.parentSession(), nullptr); EXPECT_THAT(listChildSessions(&session), ElementsAre(&session1, &session3)); session.removeChildSession(&session3); EXPECT_EQ(session3.parentSession(), nullptr); EXPECT_THAT(listChildSessions(&session), ElementsAre(&session1)); session.removeChildSession(&session1); EXPECT_EQ(session1.parentSession(), nullptr); EXPECT_THAT(listChildSessions(&session), IsEmpty()); } TEST_F(SessionTests, DeleteChildSessionRemovesFromApplication) { using namespace testing; const QString appId("test-app"); const pid_t procId = 5551; std::shared_ptr mirSession = std::make_shared(appId.toStdString(), procId); Session session(mirSession, mirServer->the_prompt_session_manager()); Session* session1 = new Session(mirSession, mirServer->the_prompt_session_manager()); Session* session2 = new Session(mirSession, mirServer->the_prompt_session_manager()); Session* session3 = new Session(mirSession, mirServer->the_prompt_session_manager()); // add surfaces session.addChildSession(session1); session.addChildSession(session2); session.addChildSession(session3); // delete surfaces delete session2; EXPECT_THAT(listChildSessions(&session), ElementsAre(session1, session3)); // delete surfaces delete session3; EXPECT_THAT(listChildSessions(&session), ElementsAre(session1)); // delete surfaces delete session1; EXPECT_THAT(listChildSessions(&session), IsEmpty()); } TEST_F(SessionTests, DeleteSessionDeletesChildSessions) { using namespace testing; const QString appId("test-app"); const pid_t procId = 5551; std::shared_ptr mirSession = std::make_shared(appId.toStdString(), procId); Session* session = new Session(mirSession, mirServer->the_prompt_session_manager()); Session* session1 = new Session(mirSession, mirServer->the_prompt_session_manager()); Session* session2 = new Session(mirSession, mirServer->the_prompt_session_manager()); Session* session3 = new Session(mirSession, mirServer->the_prompt_session_manager()); // add surfaces session->addChildSession(session1); session->addChildSession(session2); session->addChildSession(session3); QList destroyed; QObject::connect(session1, &QObject::destroyed, [&](QObject*) { destroyed << session1; }); QObject::connect(session2, &QObject::destroyed, [&](QObject*) { destroyed << session2; }); QObject::connect(session3, &QObject::destroyed, [&](QObject*) { destroyed << session3; }); delete session; EXPECT_THAT(destroyed, UnorderedElementsAre(session1, session2, session3)); } TEST_F(SessionTests, SuspendPromptSessionWhenSessionSuspends) { using namespace testing; const QString appId("test-app"); const pid_t procId = 5551; auto mirSession = std::make_shared(appId.toStdString(), procId); auto session = std::make_shared(mirSession, mirServer->the_prompt_session_manager()); { FakeMirSurface *surface = new FakeMirSurface; session->registerSurface(surface); surface->drawFirstFrame(); } EXPECT_EQ(Session::Running, session->state()); auto mirPromptSession = std::make_shared(); session->appendPromptSession(mirPromptSession); EXPECT_CALL(*promptSessionManager, suspend_prompt_session(_)).Times(1); EXPECT_CALL(*mirSession, set_lifecycle_state(mir_lifecycle_state_will_suspend)); session->suspend(); EXPECT_EQ(Session::Suspending, session->state()); session->doSuspend(); EXPECT_EQ(Session::Suspended, session->state()); Mock::VerifyAndClear(promptSessionManager.get()); } TEST_F(SessionTests, ResumePromptSessionWhenSessionResumes) { using namespace testing; const QString appId("test-app"); const pid_t procId = 5551; auto mirSession = std::make_shared(appId.toStdString(), procId); auto session = std::make_shared(mirSession, promptSessionManager); { FakeMirSurface *surface = new FakeMirSurface; session->registerSurface(surface); surface->drawFirstFrame(); } EXPECT_EQ(Session::Running, session->state()); EXPECT_CALL(*mirSession, set_lifecycle_state(mir_lifecycle_state_will_suspend)); session->suspend(); EXPECT_EQ(Session::Suspending, session->state()); session->doSuspend(); EXPECT_EQ(Session::Suspended, session->state()); auto mirPromptSession = std::make_shared(); session->appendPromptSession(mirPromptSession); EXPECT_CALL(*promptSessionManager, resume_prompt_session(_)).Times(1); EXPECT_CALL(*mirSession, set_lifecycle_state(mir_lifecycle_state_resumed)); session->resume(); EXPECT_EQ(Session::Running, session->state()); Mock::VerifyAndClear(promptSessionManager.get()); } TEST_F(SessionTests, SessionStopsWhileSuspendingDoesntSuspend) { using namespace testing; class SessionTestClass : public qtmir::Session { public: SessionTestClass(const std::shared_ptr& session, const std::shared_ptr& promptSessionManager) : Session(session, promptSessionManager) {} using Session::m_suspendTimer; }; const QString appId("test-app"); const pid_t procId = 5551; auto mirSession = std::make_shared(appId.toStdString(), procId); auto session = std::make_shared(mirSession, mirServer->the_prompt_session_manager()); { FakeMirSurface *surface = new FakeMirSurface; session->registerSurface(surface); surface->drawFirstFrame(); } EXPECT_EQ(Session::Running, session->state()); EXPECT_CALL(*mirSession, set_lifecycle_state(mir_lifecycle_state_will_suspend)); session->suspend(); EXPECT_EQ(Session::Suspending, session->state()); EXPECT_TRUE(session->m_suspendTimer->isActive()); session->setLive(false); EXPECT_EQ(Session::Stopped, session->state()); EXPECT_FALSE(session->m_suspendTimer->isActive()); } ./tests/modules/SessionManager/CMakeLists.txt0000644000015600001650000000132712677054330021377 0ustar jenkinsjenkinsset( SESSION_MANAGER_TEST_SOURCES session_manager_test.cpp session_test.cpp ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp ) include_directories( ${APPLICATION_API_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${CMAKE_SOURCE_DIR}/src/modules ${CMAKE_SOURCE_DIR}/tests/framework ${MIRSERVER_INCLUDE_DIRS} ) add_executable(sessionmanager_test ${SESSION_MANAGER_TEST_SOURCES}) add_dependencies(sessionmanager_test qtmir-test-framework-static) target_link_libraries( sessionmanager_test unityapplicationplugin Qt5::Test -L${CMAKE_BINARY_DIR}/tests/framework qtmir-test-framework-static ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(SessionManager, sessionmanager_test) ./tests/modules/CMakeLists.txt0000644000015600001650000000034412677054330016457 0ustar jenkinsjenkinsadd_subdirectory(Application) add_subdirectory(ApplicationManager) add_subdirectory(DesktopFileReader) add_subdirectory(General) add_subdirectory(SharedWakelock) add_subdirectory(SessionManager) add_subdirectory(SurfaceManager) ./tests/modules/DesktopFileReader/0000755000015600001650000000000012677054330017252 5ustar jenkinsjenkins./tests/modules/DesktopFileReader/calculator.desktop0000644000015600001650000002555412677054330023011 0ustar jenkinsjenkins[Desktop Entry] Name=Calculator Name[am]=መደመሪያ Name[ar]=الآلة الحاسبة Name[ast]=Calculadora Name[az]=Kalkulyator Name[be]=КалькулÑтар Name[bg]=Калкулатор Name[br]=Jederez Name[bs]=Kalkulator Name[ca]=Calculadora Name[cs]=KalkulacÌŒka Name[cy]=Cyfrifiannell Name[da]=Lommeregner Name[de]=Taschenrechner Name[el]=ΑÏιθμομηχανή Name[en_AU]=Calculator Name[en_CA]=Calculator Name[en_GB]=Calculator Name[es]=Calculadora Name[eu]=Kalkulagailua Name[fa]=ماشین‌حساب Name[fi]=Laskin Name[fr]=Calculatrice Name[gd]=An t-àireamhair Name[gl]=Calculadora Name[gu]=કેલà«àª•à«àª¯à«àª²à«‡àªŸàª° Name[he]=מחשבון Name[hi]=कैलकà¥à¤²à¥‡à¤Ÿà¤° Name[hu]=Számológép Name[id]=Kalkulator Name[is]=Reiknivél Name[it]=Calcolatrice Name[ja]=é›»å“ Name[km]=ម៉ាស៊ីន​គិážâ€‹áž›áŸáž Name[ko]=계산기 Name[lv]=Kalkulators Name[mr]=गणनयंतà¥à¤° Name[ms]=Kalkulator Name[my]=ဂá€á€”်းá€á€½á€€á€ºá€…က် Name[nb]=Kalkulator Name[ne]=कà¥à¤¯à¤¾à¤²à¥à¤•à¥à¤²à¥‡à¤Ÿà¤° Name[nl]=Rekenmachine Name[pa]=ਕੈਲਕੂਲੇਟਰ Name[pl]=Kalkulator Name[pt]=Calculadora Name[pt_BR]=Calculadora Name[ro]=Calculator Name[ru]=КалькулÑтор Name[sa]=सङà¥à¤•लकमॠName[shn]=á¸á¢áµá€ºá‚ˆá¼á€•့်သွá¼á€º Name[sl]=RaÄunalo Name[sr]=Калкулатор Name[sv]=Kalkylator Name[ta]=கணிபà¯à®ªà®¾à®©à¯ Name[te]=గణన పరికరం Name[tr]=Hesap Makinesi Name[ug]=Ú¾ÛØ³Ø§Ø¨Ù„ىغۇچ Name[uk]=КалькулÑтор Name[zh_CN]=计算器 Name[zh_HK]=計數機 Name[zh_TW]=計算機 Comment=A simple calculator for Ubuntu. Comment[am]=ለ ኡቡንቱ ቀላሠመደመሪያ Comment[ar]=آلة حاسبة بسيطة لأوبونتو. Comment[ast]=Una calculadora cenciella pa Ubuntu. Comment[az]=Ubuntu üçün sadÓ™ kalkulyator. Comment[br]=Ur jederez eeun evit Ubuntu. Comment[ca]=Una calculadora simple per a l'Ubuntu. Comment[cy]=Cyfrifiannell hawdd ar gyfer Ubuntu. Comment[da]=En simpel lommeregner til Ubuntu. Comment[de]=Ein einfach Tachenrechner für Ubuntu. Comment[el]=Μια απλή αÏιθμομηχανή για το Ubuntu Comment[en_AU]=A simple calculator for Ubuntu. Comment[en_GB]=A simple calculator for Ubuntu. Comment[es]=Una calculadora sencilla para Ubuntu. Comment[eu]=Ubunturako kalkulagailu sinplea. Comment[fa]=یک ماشین حساب ساده برای اوبونتو. Comment[fi]=Laskin Ubuntulle. Comment[fr]=Une calculatrice simple pour Ubuntu. Comment[gd]=Àireamhair simplidh airson Ubuntu. Comment[gl]=Calculadora sinxela para Ubuntu. Comment[he]=מחשבון פשוט ל×ובונטו. Comment[hu]=Egyszerű számológép Ubuntuhoz. Comment[is]=Einföld reiknivél Comment[it]=Una semplice calcolatrice per Ubuntu. Comment[ja]=Ubuntu用ã®ã‚·ãƒ³ãƒ—ルãªé›»å“ã§ã™ã€‚ Comment[km]=ម៉ាស៊ីន​គិážâ€‹áž›áŸážâ€‹áž’ម្មážáž¶â€‹ážŸáž˜áŸ’រាប់ Ubuntu ។ Comment[ko]=우분투를 위한 간단한 계산기 Comment[lv]=VienkÄrÅ¡s kalkulators priekÅ¡ Ubuntu Comment[nb]=En enkel kalkulator for Ubuntu. Comment[ne]=Ubuntu को लागि à¤à¤• सरल कैलकà¥à¤²à¥‡à¤Ÿà¤°à¥¤ Comment[nl]=Een eenvoudige rekenmachine voor Ubuntu. Comment[pa]=ਉਬੰਤੂ ਲਈ ਇੱਕ ਸਧਾਰਨ ਕੈਲਕੂਲੇਟਰ Comment[pl]=Prosty kalkulator dla Ubuntu Comment[pt]=Uma calculadora simples para o Ubuntu. Comment[pt_BR]=Uma simples calculadora para Ubuntu Comment[ro]=Un calculator simplu pentru Ubuntu. Comment[ru]=ПроÑтой калькулÑтор Ð´Ð»Ñ Ubuntu Comment[sa]=Ubuntu इतà¥à¤¯à¤¸à¥à¤®à¤¾ à¤à¤•ं सरलं सङà¥à¤•लकमà¥à¥¤ Comment[sl]=Preprost kalkulator za Ubuntu. Comment[sr]=ЈедноÑтаван калкулатор за Убунту Comment[sv]=En enkel kalkylator för Ubuntu. Comment[tr]=Ubuntu için sade bir hesap makinesi. Comment[ug]=ئاددىي Ú¾ÛØ³Ø§Ø¨Ù„ىغۇچى Comment[uk]=ПроÑтий калькулÑтор Ð´Ð»Ñ Ubuntu. Comment[zh_CN]=Ubuntu 简易计算器 Comment[zh_TW]=Ubuntu 簡易計算機。 Keywords=math;addition;subtraction;multiplication;division; Keywords[am]=ሂሳብ;መደመሪያ;መቀáŠáˆ»;ማባዣ;ማካáˆá‹«; Keywords[ar]=رياضة;ياضيات;حساب;حسابات;جمع;طرح;ضرب;قسمة; Keywords[ast]=matemática;suma;resta;multiplicación;división; Keywords[az]=riyaziyyat;Ó™lavÓ™ etmÓ™;çıxma;vurma;bölmÓ™;toplama; Keywords[br]=matematikoù;sammadenn;lamadenn;lieskementadenn;rannadenn; Keywords[ca]=suma;resta;calculadora;multiplica;divideix Keywords[da]=matematik;plus;minus;gange;dividere;beregning;lommeregner; Keywords[de]=Mathe;Mathematik;Multiplikation;Subtraktion;Addition;Division; Keywords[en_AU]=math;addition;subtraction;multiplication;division; Keywords[en_GB]=maths;addition;subtraction;multiplication;division; Keywords[es]=matemática;suma;resta;multiplicación;división; Keywords[eu]=matematikak;gehitu;kendu;biderkatu;zatitu; Keywords[fa]=حساب;جمع;ØªÙØ±ÛŒÙ‚;ضرب;تقسیم Keywords[fi]=math;addition;subtraction;multiplication;division;matematiikka;lisäys;vähennys;kertaus;jakaminen; Keywords[fr]=math;addition;soustraction;multiplication;division; Keywords[gd]=math;addition;subtraction;multiplication;division;matamataig;roinneadh;toirt air falbh;cur ris;iomadadh Keywords[gl]=matemáticas;suma;resta;multiplicación;división; Keywords[he]=מתמטיקה;חשבון;חיבור;חיסור;כפל;חילוק; Keywords[hu]=számolás;összeadás;kivonás;szorzás;osztás; Keywords[is]=reikna;samlagning;frádráttur;margföldun;deiling Keywords[it]=matematica;addizioni;sottrazioni;moltiplicazioni;divisioni; Keywords[ja]=math;addition;subtraction;multiplication;division;計算;é›»å“;è¶³ã—ç®—;引ãç®—;ã‹ã‘ç®—;ã‚り算; Keywords[km]=math;addition;subtraction;multiplication;division; Keywords[ko]=수학;ë§ì…ˆ;뺄셈;곱셈;나눗셈; Keywords[nb]=matte;addisjon;subtraksjon;multiplikasjon;divisjon; Keywords[ne]=गणित; जोडà¥; घटाउ; गà¥à¤£à¤¨; विभाजन; Keywords[nl]=math;addition;subtraction;multiplication;division;optellen;aftrekken;vermenigvuldigen;delen; Keywords[pa]=ਹਿਸਾਬ;ਜੋੜ;ਘਟਾਉ;ਗà©à¨£à¨¾;ਭਾਗ; Keywords[pl]=liczenie;dodawanie;odejmowanie;mnożenie;dzielenie; Keywords[pt]=matemática;soma;subtracção;multiplicação;divisão; Keywords[pt_BR]=matemática;adição;subtração;multiplicação;divisão Keywords[ro]=matematică;adunare;scădere;înmulÈ›ire;împărÈ›ire Keywords[ru]=математика;Ñложение;умножение;деление; Keywords[sa]=गणितमà¥;योजनमà¥;वियोजनमà¥;गà¥à¤£à¤¨à¤®à¥;विभाजनमà¥; Keywords[sl]=matematika;seÅ¡tevanje;odÅ¡tevanje;množenje;deljenje; Keywords[sr]=математика;Ñабирање;одузимање;множење;дељење;рачунање;дигитрон; Keywords[sv]=matematik;addition;substraktion;multiplikation;division Keywords[tr]=matematik;ekleme;çıkarma;çarpma;bölme;toplama Keywords[ug]=ماتÛماتىكا;قوشۇش;ئÛلىش;كۆپەيتىش;بۆلۈش; Keywords[uk]=math;addition;subtraction;multiplication;division;математика;додаваннÑ;відніманнÑ;множеннÑ;діленнÑ;калькулÑтор; Keywords[zh_CN]=æ•°å­¦;加;å‡;乘;除; Keywords[zh_TW]=math;addition;subtraction;multiplication;division;數學;加;減;乘;除; Type=Application Terminal=false Exec=yes -p com.ubuntu.calculator_calculator_1.3.329 -- qmlscene -qt5 ubuntu-calculator-app.qml Icon=/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png X-Ubuntu-Touch=true X-Ubuntu-StageHint=SideStage X-Ubuntu-Default-Department-ID=accessories Path=/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator X-Ubuntu-Old-Icon=calculator-app@30.png X-Ubuntu-Application-ID=com.ubuntu.calculator_calculator_1.3.329 X-Ubuntu-Splash-Show-Header=True X-Ubuntu-Splash-Color=#aabbcc X-Ubuntu-Splash-Color-Header=purple X-Ubuntu-Splash-Color-Footer=#deadbeefda X-Ubuntu-Splash-Image=/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png X-Ubuntu-Splash-Title=Calculator 2.0 X-Ubuntu-Splash-Title[am]=መደመሪያ 2.0 X-Ubuntu-Splash-Title[ar]=الآلة 2.0الحاسبة X-Ubuntu-Splash-Title[ast]=Calculadora 2.0 X-Ubuntu-Splash-Title[az]=Kalkulyator 2.0 X-Ubuntu-Splash-Title[be]=КалькулÑтар 2.0 X-Ubuntu-Splash-Title[bg]=Калкулатор 2.0 X-Ubuntu-Splash-Title[br]=Jederez 2.0 X-Ubuntu-Splash-Title[bs]=Kalkulator 2.0 X-Ubuntu-Splash-Title[ca]=Calculadora 2.0 X-Ubuntu-Splash-Title[cs]=KalkulacÌŒka 2.0 X-Ubuntu-Splash-Title[cy]=Cyfrifiannell 2.0 X-Ubuntu-Splash-Title[da]=Lommeregner 2.0 X-Ubuntu-Splash-Title[de]=Taschenrechner 2.0 X-Ubuntu-Splash-Title[el]=ΑÏιθμομηχανή 2.0 X-Ubuntu-Splash-Title[en_AU]=Calculator 2.0 X-Ubuntu-Splash-Title[en_CA]=Calculator 2.0 X-Ubuntu-Splash-Title[en_GB]=Calculator 2.0 X-Ubuntu-Splash-Title[es]=Calculadora 2.0 X-Ubuntu-Splash-Title[eu]=Kalkulagailua 2.0 X-Ubuntu-Splash-Title[fa]= 2.0ماشین‌حساب X-Ubuntu-Splash-Title[fi]=Laskin 2.0 X-Ubuntu-Splash-Title[fr]=Calculatrice 2.0 X-Ubuntu-Splash-Title[gd]=An t-àireamhair 2.0 X-Ubuntu-Splash-Title[gl]=Calculadora 2.0 X-Ubuntu-Splash-Title[gu]=કેલà«àª•à«àª¯à«àª²à«‡àªŸàª° 2.0 X-Ubuntu-Splash-Title[he]= 2.0מחשבון X-Ubuntu-Splash-Title[hi]=कैलकà¥à¤²à¥‡à¤Ÿà¤° 2.0 X-Ubuntu-Splash-Title[hu]=Számológép 2.0 X-Ubuntu-Splash-Title[id]=Kalkulator 2.0 X-Ubuntu-Splash-Title[is]=Reiknivél 2.0 X-Ubuntu-Splash-Title[it]=Calcolatrice 2.0 X-Ubuntu-Splash-Title[ja]=é›»å“ 2.0 X-Ubuntu-Splash-Title[km]=ម៉ាស៊ីន​គិážâ€‹áž›áŸáž 2.0 X-Ubuntu-Splash-Title[ko]=계산기 2.0 X-Ubuntu-Splash-Title[lv]=Kalkulators 2.0 X-Ubuntu-Splash-Title[mr]=गणनयंतà¥à¤° 2.0 X-Ubuntu-Splash-Title[ms]=Kalkulator 2.0 X-Ubuntu-Splash-Title[my]=ဂá€á€”်းá€á€½á€€á€ºá€…က် 2.0 X-Ubuntu-Splash-Title[nb]=Kalkulator 2.0 X-Ubuntu-Splash-Title[ne]=कà¥à¤¯à¤¾à¤²à¥à¤•à¥à¤²à¥‡à¤Ÿà¤° 2.0 X-Ubuntu-Splash-Title[nl]=Rekenmachine 2.0 X-Ubuntu-Splash-Title[pa]=ਕੈਲਕੂਲੇਟਰ 2.0 X-Ubuntu-Splash-Title[pl]=Kalkulator 2.0 X-Ubuntu-Splash-Title[pt]=Calculadora 2.0 X-Ubuntu-Splash-Title[pt_BR]=Calculadora 2.0 X-Ubuntu-Splash-Title[ro]=Calculator 2.0 X-Ubuntu-Splash-Title[ru]=КалькулÑтор 2.0 X-Ubuntu-Splash-Title[sa]=सङà¥à¤•लकमॠ2.0 X-Ubuntu-Splash-Title[shn]=á¸á¢áµá€ºá‚ˆá¼á€•့်သွá¼á€º 2.0 X-Ubuntu-Splash-Title[sl]=RaÄunalo 2.0 X-Ubuntu-Splash-Title[sr]=Калкулатор 2.0 X-Ubuntu-Splash-Title[sv]=Kalkylator 2.0 X-Ubuntu-Splash-Title[ta]=கணிபà¯à®ªà®¾à®©à¯ 2.0 X-Ubuntu-Splash-Title[te]=గణన పరికరం 2.0 X-Ubuntu-Splash-Title[tr]=Hesap Makinesi 2.0 X-Ubuntu-Splash-Title[ug]= 2.0Ú¾ÛØ³Ø§Ø¨Ù„ىغۇچ X-Ubuntu-Splash-Title[uk]=КалькулÑтор 2.0 X-Ubuntu-Splash-Title[zh_CN]=计算器 2.0 X-Ubuntu-Splash-Title[zh_HK]=計數機 2.0 X-Ubuntu-Splash-Title[zh_TW]=計算機 2.0 ./tests/modules/DesktopFileReader/CMakeLists.txt0000644000015600001650000000066312677054330022017 0ustar jenkinsjenkinsadd_definitions(-DTEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") set( DESKTOP_FILE_READER_TEST_SOURCES desktopfilereader_test.cpp ) include_directories( ${CMAKE_SOURCE_DIR}/src/modules ) add_executable(desktop_file_reader_test ${DESKTOP_FILE_READER_TEST_SOURCES}) target_link_libraries( desktop_file_reader_test unityapplicationplugin ${GTEST_BOTH_LIBRARIES} ) add_test(DesktopFileReader desktop_file_reader_test) ./tests/modules/DesktopFileReader/desktopfilereader_test.cpp0000644000015600001650000001707712677054330024525 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #include #include #include #include #include using namespace qtmir; namespace { static void setLocale(const char *locale) { qputenv("LANGUAGE", locale); qputenv("LC_ALL", locale); // set these for GIO QLocale::setDefault(QString(locale)); // set for Qt } } TEST(DesktopFileReader, testReadsDesktopFile) { using namespace ::testing; setLocale("C"); QFileInfo fileInfo(QString(TEST_SOURCE_DIR) + "/calculator.desktop"); DesktopFileReader::Factory readerFactory; DesktopFileReader *reader = readerFactory.createInstance("calculator", fileInfo); EXPECT_EQ(reader->loaded(), true); EXPECT_EQ(reader->appId(), "calculator"); EXPECT_EQ(reader->name(), "Calculator"); EXPECT_EQ(reader->exec(), "yes -p com.ubuntu.calculator_calculator_1.3.329 -- qmlscene -qt5 ubuntu-calculator-app.qml"); EXPECT_EQ(reader->icon(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png"); EXPECT_EQ(reader->path(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator"); EXPECT_EQ(reader->comment(), "A simple calculator for Ubuntu."); EXPECT_EQ(reader->stageHint(), "SideStage"); EXPECT_EQ(reader->splashColor(), "#aabbcc"); EXPECT_EQ(reader->splashColorFooter(), "#deadbeefda"); EXPECT_EQ(reader->splashColorHeader(), "purple"); EXPECT_EQ(reader->splashImage(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png"); EXPECT_EQ(reader->splashShowHeader(), "True"); EXPECT_EQ(reader->splashTitle(), "Calculator 2.0"); EXPECT_EQ(reader->isTouchApp(), true); } TEST(DesktopFileReader, testReadsLocalizedDesktopFile) { using namespace ::testing; setLocale("de"); QFileInfo fileInfo(QString(TEST_SOURCE_DIR) + "/calculator.desktop"); DesktopFileReader::Factory readerFactory; DesktopFileReader *reader = readerFactory.createInstance("calculator", fileInfo); EXPECT_EQ(reader->loaded(), true); EXPECT_EQ(reader->appId(), "calculator"); EXPECT_EQ(reader->name(), "Taschenrechner"); EXPECT_EQ(reader->exec(), "yes -p com.ubuntu.calculator_calculator_1.3.329 -- qmlscene -qt5 ubuntu-calculator-app.qml"); EXPECT_EQ(reader->icon(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png"); EXPECT_EQ(reader->path(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator"); EXPECT_EQ(reader->comment(), "Ein einfach Tachenrechner für Ubuntu."); EXPECT_EQ(reader->stageHint(), "SideStage"); EXPECT_EQ(reader->splashColor(), "#aabbcc"); EXPECT_EQ(reader->splashColorFooter(), "#deadbeefda"); EXPECT_EQ(reader->splashColorHeader(), "purple"); EXPECT_EQ(reader->splashImage(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png"); EXPECT_EQ(reader->splashShowHeader(), "True"); EXPECT_EQ(reader->splashTitle(), "Taschenrechner 2.0"); EXPECT_EQ(reader->isTouchApp(), true); } TEST(DesktopFileReader, testMissingDesktopFile) { using namespace ::testing; setLocale("C"); QFileInfo fileInfo(QString(TEST_SOURCE_DIR) + "/missing.desktop"); DesktopFileReader::Factory readerFactory; DesktopFileReader *reader = readerFactory.createInstance("calculator", fileInfo); EXPECT_EQ(reader->loaded(), false); EXPECT_EQ(reader->appId(), "calculator"); EXPECT_EQ(reader->name(), ""); EXPECT_EQ(reader->exec(), ""); EXPECT_EQ(reader->icon(), ""); EXPECT_EQ(reader->path(), ""); EXPECT_EQ(reader->comment(), ""); EXPECT_EQ(reader->stageHint(), ""); EXPECT_EQ(reader->splashColor(), ""); EXPECT_EQ(reader->splashColorFooter(), ""); EXPECT_EQ(reader->splashColorHeader(), ""); EXPECT_EQ(reader->splashImage(), ""); EXPECT_EQ(reader->splashShowHeader(), ""); EXPECT_EQ(reader->splashTitle(), ""); EXPECT_EQ(reader->isTouchApp(), false); } TEST(DesktopFileReader, testUTF8Characters) { using namespace ::testing; setLocale("zh_CN"); QFileInfo fileInfo(QString(TEST_SOURCE_DIR) + "/calculator.desktop"); DesktopFileReader::Factory readerFactory; DesktopFileReader *reader = readerFactory.createInstance("calculator", fileInfo); EXPECT_EQ(reader->loaded(), true); EXPECT_EQ(reader->appId(), "calculator"); EXPECT_EQ(reader->name(), "计算器"); EXPECT_EQ(reader->comment(), "Ubuntu 简易计算器"); EXPECT_EQ(reader->splashTitle(), "计算器 2.0"); } TEST(DesktopFileReader, parseOrientations) { using namespace ::testing; const Qt::ScreenOrientations defaultOrientations = Qt::PortraitOrientation | Qt::LandscapeOrientation | Qt::InvertedPortraitOrientation | Qt::InvertedLandscapeOrientation; bool ok; Qt::ScreenOrientations orientations; ok = DesktopFileReader::parseOrientations(QString(), orientations); ASSERT_TRUE(ok); EXPECT_EQ(defaultOrientations, orientations); ok = DesktopFileReader::parseOrientations("An invalid string!", orientations); ASSERT_FALSE(ok); EXPECT_EQ(defaultOrientations, orientations); ok = DesktopFileReader::parseOrientations("landscape", orientations); ASSERT_TRUE(ok); EXPECT_EQ(Qt::LandscapeOrientation, orientations); ok = DesktopFileReader::parseOrientations(" InvertedPortrait , Portrait ", orientations); ASSERT_TRUE(ok); EXPECT_EQ(Qt::InvertedPortraitOrientation | Qt::PortraitOrientation, orientations); ok = DesktopFileReader::parseOrientations(",inverted-landscape, inverted_portrait, ", orientations); ASSERT_TRUE(ok); EXPECT_EQ(Qt::InvertedPortraitOrientation | Qt::InvertedLandscapeOrientation, orientations); ok = DesktopFileReader::parseOrientations(",inverted-landscape, some-invalid-text, ", orientations); ASSERT_FALSE(ok); EXPECT_EQ(defaultOrientations, orientations); ok = DesktopFileReader::parseOrientations("landscape;portrait", orientations); ASSERT_TRUE(ok); EXPECT_EQ(Qt::PortraitOrientation | Qt::LandscapeOrientation, orientations); ok = DesktopFileReader::parseOrientations("primary", orientations); ASSERT_TRUE(ok); EXPECT_EQ(Qt::PrimaryOrientation, orientations); ok = DesktopFileReader::parseOrientations("landscpe,primary", orientations); ASSERT_FALSE(ok); EXPECT_EQ(defaultOrientations, orientations); } TEST(DesktopFileReader, parseBoolean) { using namespace ::testing; bool ok; bool boolean; ok = DesktopFileReader::parseBoolean(QString(), boolean); ASSERT_TRUE(ok); EXPECT_FALSE(boolean); ok = DesktopFileReader::parseBoolean(" Yes ", boolean); ASSERT_TRUE(ok); EXPECT_TRUE(boolean); ok = DesktopFileReader::parseBoolean("False", boolean); ASSERT_TRUE(ok); EXPECT_FALSE(boolean); ok = DesktopFileReader::parseBoolean("Hello World!", boolean); ASSERT_FALSE(ok); EXPECT_FALSE(boolean); } ./tests/modules/SurfaceManager/0000755000015600001650000000000012677054330016601 5ustar jenkinsjenkins./tests/modules/SurfaceManager/mirsurfaceitem_test.cpp0000644000015600001650000001143612677054330023370 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #define MIR_INCLUDE_DEPRECATED_EVENT_HEADER #include #include #include // the test subject #include // tests/framework #include using namespace qtmir; class MirSurfaceItemTest : public ::testing::Test { public: MirSurfaceItemTest() { setenv("QT_QPA_PLATFORM", "minimal", 1); int argc = 0; char **argv = nullptr; m_app = new QGuiApplication(argc, argv); // We don't want the logging spam cluttering the test results QLoggingCategory::setFilterRules(QStringLiteral("qtmir.surfaces=false")); } virtual ~MirSurfaceItemTest() { delete m_app; } QGuiApplication *m_app; }; /* Tests that even if Qt fails to finish a touch sequence, MirSurfaceItem will properly finish it when forwarding it to its mir::input::surface. So mir::input::surface will still consume a proper sequence of touch events (comprised of a begin, zero or more updates and an end). */ TEST_F(MirSurfaceItemTest, MissingTouchEnd) { MirSurfaceItem *surfaceItem = new MirSurfaceItem; FakeMirSurface *fakeSurface = new FakeMirSurface; surfaceItem->setSurface(fakeSurface); surfaceItem->setConsumesInput(true); ulong timestamp = 1234; QList touchPoints; touchPoints.append(QTouchEvent::TouchPoint()); touchPoints[0].setId(0); touchPoints[0].setState(Qt::TouchPointPressed); surfaceItem->processTouchEvent(QEvent::TouchBegin, timestamp, Qt::NoModifier, touchPoints, touchPoints[0].state()); touchPoints[0].setState(Qt::TouchPointMoved); surfaceItem->processTouchEvent(QEvent::TouchUpdate, timestamp + 10, Qt::NoModifier, touchPoints, touchPoints[0].state()); // Starting a new touch sequence (with touch 1) without ending the current one // (wich has touch 0). touchPoints[0].setId(1); touchPoints[0].setState(Qt::TouchPointPressed); surfaceItem->processTouchEvent(QEvent::TouchBegin, timestamp + 20, Qt::NoModifier, touchPoints, touchPoints[0].state()); auto touchesReceived = fakeSurface->touchesReceived(); // MirSurface should have received 4 events: // 1 - (id=0,down) // 2 - (id=0,move) // 3 - (id=0,up) <- that's the one MirSurfaceItem should have synthesized to keep the event stream sane. // 4 - (id=1,down) ASSERT_EQ(4, touchesReceived.count()); ASSERT_EQ(0, touchesReceived[0].touchPoints[0].id()); ASSERT_EQ(Qt::TouchPointPressed, touchesReceived[0].touchPoints[0].state()); ASSERT_EQ(0, touchesReceived[1].touchPoints[0].id()); ASSERT_EQ(Qt::TouchPointMoved, touchesReceived[1].touchPoints[0].state()); ASSERT_EQ(0, touchesReceived[2].touchPoints[0].id()); ASSERT_EQ(Qt::TouchPointReleased, touchesReceived[2].touchPoints[0].state()); ASSERT_EQ(1, touchesReceived[3].touchPoints[0].id()); ASSERT_EQ(Qt::TouchPointPressed, touchesReceived[3].touchPoints[0].state()); delete surfaceItem; delete fakeSurface; } TEST_F(MirSurfaceItemTest, SetSurfaceInitializesVisiblity) { MirSurfaceItem *surfaceItem = new MirSurfaceItem; surfaceItem->setVisible(false); FakeMirSurface *fakeSurface = new FakeMirSurface; surfaceItem->setSurface(fakeSurface); EXPECT_FALSE(fakeSurface->visible()); delete surfaceItem; delete fakeSurface; } TEST_F(MirSurfaceItemTest, AggregateSurfaceVisibility) { MirSurfaceItem *surfaceItem1 = new MirSurfaceItem; surfaceItem1->setVisible(true); MirSurfaceItem *surfaceItem2 = new MirSurfaceItem; surfaceItem1->setVisible(true); FakeMirSurface *fakeSurface = new FakeMirSurface; surfaceItem1->setSurface(fakeSurface); surfaceItem2->setSurface(fakeSurface); EXPECT_TRUE(fakeSurface->visible()); surfaceItem1->setVisible(false); EXPECT_TRUE(fakeSurface->visible()); surfaceItem2->setVisible(false); EXPECT_FALSE(fakeSurface->visible()); surfaceItem1->setVisible(true); EXPECT_TRUE(fakeSurface->visible()); delete surfaceItem1; EXPECT_FALSE(fakeSurface->visible()); delete surfaceItem2; delete fakeSurface; } ./tests/modules/SurfaceManager/mirsurface_test.cpp0000644000015600001650000000771212677054330022513 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #define MIR_INCLUDE_DEPRECATED_EVENT_HEADER #include #include #include #include // the test subject #include // tests/modules/common #include #include #include // mirserver #include using namespace qtmir; namespace ms = mir::scene; class MirSurfaceTest : public ::testing::Test { public: MirSurfaceTest() { // We don't want the logging spam cluttering the test results QLoggingCategory::setFilterRules(QStringLiteral("qtmir.surfaces=false")); } }; TEST_F(MirSurfaceTest, UpdateTextureBeforeDraw) { using namespace testing; int argc = 0; char* argv[0]; QCoreApplication qtApp(argc, argv); // app for deleteLater event auto fakeSession = new FakeSession(); auto mockSurface = std::make_shared>(); auto surfaceObserver = std::make_shared(); EXPECT_CALL(*mockSurface.get(),buffers_ready_for_compositor(_)) .WillRepeatedly(Return(1)); MirSurface *surface = new MirSurface(mockSurface, fakeSession, nullptr, surfaceObserver, CreationHints()); surfaceObserver->frame_posted(1, mir::geometry::Size{1,1}); QSignalSpy spyFrameDropped(surface, SIGNAL(frameDropped())); QTest::qWait(300); ASSERT_TRUE(spyFrameDropped.count() > 0); delete fakeSession; delete surface; } TEST_F(MirSurfaceTest, DeleteMirSurfaceOnLastNonLiveUnregisterView) { using namespace testing; int argc = 0; char* argv[0]; QCoreApplication qtApp(argc, argv); // app for deleteLater event auto fakeSession = new FakeSession(); auto mockSurface = std::make_shared>(); MirSurface *surface = new MirSurface(mockSurface, fakeSession, nullptr, nullptr, CreationHints()); bool surfaceDeleted = false; QObject::connect(surface, &QObject::destroyed, surface, [&surfaceDeleted](){ surfaceDeleted = true; }); qintptr view1 = (qintptr)1; qintptr view2 = (qintptr)2; surface->registerView(view1); surface->registerView(view2); surface->setLive(false); EXPECT_FALSE(surfaceDeleted); surface->unregisterView(view1); surface->unregisterView(view2); QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); EXPECT_TRUE(surfaceDeleted); delete fakeSession; } TEST_F(MirSurfaceTest, DoNotDeleteMirSurfaceOnLastLiveUnregisterView) { using namespace testing; int argc = 0; char* argv[0]; QCoreApplication qtApp(argc, argv); // app for deleteLater event auto fakeSession = new FakeSession(); auto mockSurface = std::make_shared>(); MirSurface *surface = new MirSurface(mockSurface, fakeSession, nullptr, nullptr, CreationHints()); bool surfaceDeleted = false; QObject::connect(surface, &QObject::destroyed, surface, [&surfaceDeleted](){ surfaceDeleted = true; }); qintptr view1 = (qintptr)1; qintptr view2 = (qintptr)2; surface->registerView(view1); surface->registerView(view2); surface->unregisterView(view1); surface->unregisterView(view2); QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); EXPECT_FALSE(surfaceDeleted); delete fakeSession; delete surface; } ./tests/modules/SurfaceManager/CMakeLists.txt0000644000015600001650000000117312677054330021343 0ustar jenkinsjenkinsset( MIR_SURFACE_MANAGER_TEST_SOURCES mirsurfaceitem_test.cpp mirsurface_test.cpp ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp ) include_directories( ${CMAKE_SOURCE_DIR}/src/modules ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${CMAKE_SOURCE_DIR}/tests/framework ${MIRSERVER_INCLUDE_DIRS} ) add_executable(surfacemanager_test ${MIR_SURFACE_MANAGER_TEST_SOURCES}) target_link_libraries( surfacemanager_test unityapplicationplugin Qt5::Test -L${CMAKE_BINARY_DIR}/tests/framework qtmir-test-framework-static ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(SurfaceManager surfacemanager_test) ./tests/mirserver/0000755000015600001650000000000012677054330014264 5ustar jenkinsjenkins./tests/mirserver/Screen/0000755000015600001650000000000012677054330015503 5ustar jenkinsjenkins./tests/mirserver/Screen/CMakeLists.txt0000644000015600001650000000074312677054330020247 0ustar jenkinsjenkinsset( SCREEN_TEST_SOURCES screen_test.cpp ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp ) include_directories( ${CMAKE_SOURCE_DIR}/tests/framework ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${CMAKE_SOURCE_DIR}/src/common ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${MIRSERVER_INCLUDE_DIRS} ) add_executable(ScreenTest ${SCREEN_TEST_SOURCES}) target_link_libraries( ScreenTest qpa-mirserver ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(Screen, ScreenTest) ./tests/mirserver/Screen/screen_test.cpp0000644000015600001650000000611412677054330020527 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #include "mir/graphics/display_configuration.h" #include "fake_displayconfigurationoutput.h" #include using namespace ::testing; namespace mg = mir::graphics; namespace geom = mir::geometry; class ScreenTest : public ::testing::Test { protected: void SetUp() override; }; void ScreenTest::SetUp() { if (!qEnvironmentVariableIsSet("QT_ACCEL_FILEPATH")) { // Trick Qt >= 5.4.1 to load the generic sensors qputenv("QT_ACCEL_FILEPATH", "dummy"); } Screen::skipDBusRegistration = true; } TEST_F(ScreenTest, OrientationSensorForExternalDisplay) { Screen *screen = new Screen(fakeOutput1); // is external display (dvi) // Default state should be disabled ASSERT_FALSE(screen->orientationSensorEnabled()); screen->onDisplayPowerStateChanged(0,0); ASSERT_FALSE(screen->orientationSensorEnabled()); screen->onDisplayPowerStateChanged(1,0); ASSERT_FALSE(screen->orientationSensorEnabled()); } TEST_F(ScreenTest, OrientationSensorForInternalDisplay) { Screen *screen = new Screen(fakeOutput2); // is internal display // Default state should be active ASSERT_TRUE(screen->orientationSensorEnabled()); screen->onDisplayPowerStateChanged(0,0); ASSERT_FALSE(screen->orientationSensorEnabled()); screen->onDisplayPowerStateChanged(1,0); ASSERT_TRUE(screen->orientationSensorEnabled()); } TEST_F(ScreenTest, ReadConfigurationFromDisplayConfig) { Screen *screen = new Screen(fakeOutput1); EXPECT_EQ(screen->geometry(), QRect(0, 0, 150, 200)); EXPECT_EQ(screen->availableGeometry(), QRect(0, 0, 150, 200)); EXPECT_EQ(screen->depth(), 32); EXPECT_EQ(screen->format(), QImage::Format_RGBA8888); EXPECT_EQ(screen->refreshRate(), 59); EXPECT_EQ(screen->physicalSize(), QSize(1111, 2222)); EXPECT_EQ(screen->outputType(), mg::DisplayConfigurationOutputType::dvid); } TEST_F(ScreenTest, ReadDifferentConfigurationFromDisplayConfig) { Screen *screen = new Screen(fakeOutput2); EXPECT_EQ(screen->geometry(), QRect(500, 600, 1500, 2000)); EXPECT_EQ(screen->availableGeometry(), QRect(500, 600, 1500, 2000)); EXPECT_EQ(screen->depth(), 32); EXPECT_EQ(screen->format(), QImage::Format_RGBX8888); EXPECT_EQ(screen->refreshRate(), 75); EXPECT_EQ(screen->physicalSize(), QSize(1000, 2000)); EXPECT_EQ(screen->outputType(), mg::DisplayConfigurationOutputType::lvds); } ./tests/mirserver/QtEventFeeder/0000755000015600001650000000000012677054330016765 5ustar jenkinsjenkins./tests/mirserver/QtEventFeeder/qteventfeeder_test.cpp0000644000015600001650000002561212677054330023377 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #include #include #include #include #include "mir/events/event_builders.h" #include "mock_qtwindowsystem.h" using ::testing::_; using ::testing::AllOf; using ::testing::AnyNumber; using ::testing::Contains; using ::testing::AtLeast; using ::testing::InSequence; using ::testing::Mock; using ::testing::SizeIs; using ::testing::Return; // own gmock extensions using ::testing::IsPressed; using ::testing::IsStationary; using ::testing::IsReleased; using ::testing::HasId; using ::testing::StateIsMoved; namespace mev = mir::events; // used by google mock in error messages void PrintTo(const struct QWindowSystemInterface::TouchPoint& touchPoint, ::std::ostream* os) { *os << "TouchPoint(" << "id=" << touchPoint.id << "," << touchPointStateToString(touchPoint.state) << ")"; } ::std::ostream& operator<<(::std::ostream& os, QTouchDevice*) { // actually don't care about its contents. Just to avoit having a raw // pointer address being printed in google mock error messages. return os << "QTouchDevice*"; } class QtEventFeederTest : public ::testing::Test { protected: void SetUp() override; void TearDown() override; void setIrrelevantMockWindowSystemExpectations(); MockQtWindowSystem *mockWindowSystem; QtEventFeeder *qtEventFeeder; QWindow *window; QGuiApplication *app; }; void QtEventFeederTest::SetUp() { mockWindowSystem = new MockQtWindowSystem; auto screens = QSharedPointer(); EXPECT_CALL(*mockWindowSystem, registerTouchDevice(_)); qtEventFeeder = new QtEventFeeder(screens, mockWindowSystem); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); int argc = 0; char **argv = nullptr; setenv("QT_QPA_PLATFORM", "minimal", 1); app = new QGuiApplication(argc, argv); window = new QWindow; } void QtEventFeederTest::TearDown() { // mockWindowSystem will be deleted by QtEventFeeder delete qtEventFeeder; delete window; delete app; } void QtEventFeederTest::setIrrelevantMockWindowSystemExpectations() { EXPECT_CALL(*mockWindowSystem, getWindowForTouchPoint(_)) .Times(AnyNumber()) .WillRepeatedly(Return(window)); EXPECT_CALL(*mockWindowSystem, focusedWindow()) .Times(AnyNumber()) .WillRepeatedly(Return(window)); } /* Mir sends a MirEvent([touch(id=0,state=pressed)]. QtEventFeeder happily forwards that to Qt. Then, Mir sends a MirEvent([touch(id=1,state=pressed)]). In MirEvents, every single active touch point must be listed in the event even if it didn't change at all in the meantime. So that's a bug. But instead of crashing or forwarding the bogus event stream down to Qt, QtEventFeeder should attempt to fix the situation by synthesizing a touch[id=0,state=released] to be send along with the touch(id=1,state=pressed) it got. So that Qt receives a correct touch event stream. */ TEST_F(QtEventFeederTest, GenerateMissingTouchEnd) { setIrrelevantMockWindowSystemExpectations(); EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,_,_,AllOf(SizeIs(1), Contains(AllOf(HasId(0), IsPressed()))),_)).Times(1); auto ev1 = mev::make_event(MirInputDeviceId(), std::chrono::milliseconds(123), std::vector{} /* cookie */, 0); mev::add_touch(*ev1, 0 /* touch ID */, mir_touch_action_down, mir_touch_tooltype_unknown, 10, 10, 10 /* x, y, pressure */, 1, 1, 10 /* touch major, minor, size */); qtEventFeeder->dispatch(*ev1); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); setIrrelevantMockWindowSystemExpectations(); // There can be only one pressed or released touch per event { InSequence sequence; EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,_,_,AllOf(SizeIs(1), Contains(AllOf(HasId(0),IsReleased())) ),_)).Times(1); EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,_,_,AllOf(SizeIs(1), Contains(AllOf(HasId(1),IsPressed())) ),_)).Times(1); } auto ev2 = mev::make_event(MirInputDeviceId(), std::chrono::milliseconds(125), std::vector{} /* cookie */, 0); mev::add_touch(*ev2, 1 /* touch ID */, mir_touch_action_down, mir_touch_tooltype_unknown, 20, 20, 10 /* x, y, pressure*/, 1, 1, 10 /* touch major, minor, size */); qtEventFeeder->dispatch(*ev2); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); } TEST_F(QtEventFeederTest, GenerateMissingTouchEnd2) { setIrrelevantMockWindowSystemExpectations(); auto ev1 = mev::make_event(MirInputDeviceId(), std::chrono::milliseconds(123), std::vector{} /* cookie */, 0); mev::add_touch(*ev1, 0 /* touch ID */, mir_touch_action_down, mir_touch_tooltype_unknown, 10, 10, 10 /* x, y, pressure*/, 1, 1, 10 /* touch major, minor, size */); EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,_,_,AllOf(SizeIs(1), Contains(AllOf(HasId(0), IsPressed()))),_)).Times(1); qtEventFeeder->dispatch(*ev1); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); // --- setIrrelevantMockWindowSystemExpectations(); auto ev2 = mev::make_event(MirInputDeviceId(), std::chrono::milliseconds(124), std::vector{} /* cookie */, 0); mev::add_touch(*ev2, 0 /* touch ID */, mir_touch_action_change, mir_touch_tooltype_unknown, 10, 10, 10 /* x, y, pressure*/, 1, 1, 10 /* touch major, minor, size */); mev::add_touch(*ev2, 1 /* touch ID */, mir_touch_action_down, mir_touch_tooltype_unknown, 20, 20, 10 /* x, y, pressure*/, 1, 1, 10 /* touch major, minor, size */); EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,_,_,AllOf(SizeIs(2), Contains(AllOf(HasId(0), StateIsMoved())), Contains(AllOf(HasId(1), IsPressed())) ),_)).Times(1); qtEventFeeder->dispatch(*ev2); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); // --- setIrrelevantMockWindowSystemExpectations(); // touch 0 disappeared and touch 2 got pressed auto ev3 = mev::make_event(MirInputDeviceId(), std::chrono::milliseconds(125), std::vector{} /* cookie */, 0); mev::add_touch(*ev3, 1 /* touch ID */, mir_touch_action_change, mir_touch_tooltype_unknown, 20, 20, 10 /* x, y, pressure*/, 1, 1, 10 /* touch major, minor, size */); mev::add_touch(*ev3, 2 /* touch ID */, mir_touch_action_down, mir_touch_tooltype_unknown, 30, 30, 10 /* x, y, pressure*/, 1, 1, 10 /* touch major, minor, size */); // There can be only one pressed or released touch per event { InSequence sequence; // first release touch 0 EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,_,_,AllOf(SizeIs(2), Contains(AllOf(HasId(0), IsReleased())), Contains(AllOf(HasId(1), IsStationary())) ),_)).Times(1); // then press touch 2 EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,_,_,AllOf(SizeIs(2), Contains(AllOf(HasId(1), StateIsMoved())), Contains(AllOf(HasId(2), IsPressed())) ),_)).Times(1); } qtEventFeeder->dispatch(*ev3); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); } TEST_F(QtEventFeederTest, PressSameTouchTwice) { setIrrelevantMockWindowSystemExpectations(); EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,_,_,AllOf(SizeIs(1), Contains(AllOf(HasId(0), IsPressed()))),_)).Times(1); auto ev1 = mev::make_event(MirInputDeviceId(), std::chrono::milliseconds(123), std::vector{} /* cookie */, 0); mev::add_touch(*ev1, /* touch ID */ 0, mir_touch_action_down, mir_touch_tooltype_unknown, 10, 10, 10, 1, 1, 10); qtEventFeeder->dispatch(*ev1); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); setIrrelevantMockWindowSystemExpectations(); EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,_,_,AllOf(SizeIs(1), Contains(AllOf(HasId(0), StateIsMoved())) ),_)).Times(1); auto ev2 = mev::make_event(MirInputDeviceId(), std::chrono::milliseconds(123), std::vector{} /* cookie */, 0); mev::add_touch(*ev2, /* touch ID */ 0, mir_touch_action_down, mir_touch_tooltype_unknown, 10, 10, 10, 1, 1, 10); qtEventFeeder->dispatch(*ev2); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); } TEST_F(QtEventFeederTest, TimestampInMilliseconds) { setIrrelevantMockWindowSystemExpectations(); EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,0,_,_,_)).Times(1); auto ev1 = mev::make_event(MirInputDeviceId(), std::chrono::milliseconds(123), std::vector{} /* cookie */, 0); qtEventFeeder->dispatch(*ev1); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); setIrrelevantMockWindowSystemExpectations(); EXPECT_CALL(*mockWindowSystem, handleTouchEvent(_,2,_,_,_)).Times(1); auto ev2 = mev::make_event(MirInputDeviceId(), std::chrono::milliseconds(125), std::vector{} /* cookie */, 0); qtEventFeeder->dispatch(*ev2); ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockWindowSystem)); } ./tests/mirserver/QtEventFeeder/mock_qtwindowsystem.h0000644000015600001650000000547312677054330023301 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * */ #ifndef MOCK_QTWINDOWSYSTEM_H #define MOCK_QTWINDOWSYSTEM_H #include #include class MockQtWindowSystem : public QtEventFeeder::QtWindowSystemInterface { public: MOCK_CONST_METHOD0(ready, bool()); MOCK_METHOD1(setScreenController, void(const QSharedPointer &)); MOCK_METHOD1(getWindowForTouchPoint, QWindow*(const QPoint &point)); MOCK_METHOD0(lastWindow, QWindow*()); MOCK_METHOD0(focusedWindow, QWindow*()); MOCK_METHOD1(registerTouchDevice, void(QTouchDevice* device)); // Wanted to use GMock, but MOCK_METHOD11 not implemented void handleExtendedKeyEvent(QWindow */*window*/, ulong /*timestamp*/, QEvent::Type /*type*/, int /*key*/, Qt::KeyboardModifiers /*modifiers*/, quint32 /*nativeScanCode*/, quint32 /*nativeVirtualKey*/, quint32 /*nativeModifiers*/, const QString& /*text*/ = QString(), bool /*autorep*/ = false, ushort /*count*/ = 1) {} MOCK_METHOD5(handleTouchEvent, void(QWindow *window, ulong timestamp, QTouchDevice *device, const QList &points, Qt::KeyboardModifiers mods)); MOCK_METHOD4(handleMouseEvent, void(ulong timestamp, QPointF point, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)); MOCK_METHOD3(handleWheelEvent, void(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers mods)); }; namespace testing { MATCHER(IsPressed, std::string(negation ? "isn't" : "is") + " pressed") { return arg.state == Qt::TouchPointPressed; } MATCHER(IsReleased, std::string(negation ? "isn't" : "is") + " released") { return arg.state == Qt::TouchPointReleased; } MATCHER(IsStationary, std::string(negation ? "isn't" : "is") + " stationary") { return arg.state == Qt::TouchPointStationary; } MATCHER(StateIsMoved, "state " + std::string(negation ? "isn't" : "is") + " 'moved'") { return arg.state == Qt::TouchPointMoved; } MATCHER_P(HasId, expectedId, "id " + std::string(negation ? "isn't " : "is ") + PrintToString(expectedId)) { return arg.id == expectedId; } } // namespace testing #endif // MOCK_QTWINDOWSYSTEM_H ./tests/mirserver/QtEventFeeder/CMakeLists.txt0000644000015600001650000000075312677054330021532 0ustar jenkinsjenkinsset( EVENT_FEEDER_TEST_SOURCES qteventfeeder_test.cpp ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp ) include_directories( ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${CMAKE_SOURCE_DIR}/src/common ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${MIRSERVER_INCLUDE_DIRS} ) add_executable(QtEventFeederTest ${EVENT_FEEDER_TEST_SOURCES}) target_link_libraries( QtEventFeederTest qpa-mirserver ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(QtEventFeeder, QtEventFeederTest) ./tests/mirserver/ScreenController/0000755000015600001650000000000012677054330017547 5ustar jenkinsjenkins./tests/mirserver/ScreenController/stub_screen.h0000644000015600001650000000170712677054330022241 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef STUBSCREEN_H #define STUBSCREEN_H #include "screen.h" class StubScreen : public Screen { Q_OBJECT public: StubScreen(const mir::graphics::DisplayConfigurationOutput &output) : Screen(output) {} void makeCurrent() { Screen::makeCurrent(); } }; #endif // STUBSCREEN_H ./tests/mirserver/ScreenController/testable_screencontroller.h0000644000015600001650000000226712677054330025175 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "screencontroller.h" #include "stub_screen.h" struct TestableScreenController : public ScreenController { Q_OBJECT public: Screen *createScreen(const mir::graphics::DisplayConfigurationOutput &output) const override { return new StubScreen(output); } void do_init(const std::shared_ptr &display, const std::shared_ptr &compositor) { init(display, compositor); } void do_terminate() { terminate(); } }; ./tests/mirserver/ScreenController/screencontroller_test.cpp0000644000015600001650000001407712677054330024706 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include "gmock_fixes.h" #include "stub_display.h" #include "mock_main_loop.h" #include "qtcompositor.h" #include "fake_displayconfigurationoutput.h" #include "testable_screencontroller.h" #include "screen.h" #include "screenwindow.h" #include using namespace ::testing; namespace mg = mir::graphics; namespace geom = mir::geometry; class ScreenControllerTest : public ::testing::Test { protected: void SetUp() override; void TearDown() override; ScreenController *screenController; std::shared_ptr display; std::shared_ptr compositor; QGuiApplication *app; }; void ScreenControllerTest::SetUp() { setenv("QT_QPA_PLATFORM", "minimal", 1); Screen::skipDBusRegistration = true; screenController = new TestableScreenController; display = std::make_shared(); compositor = std::make_shared(); static_cast(screenController)->do_init(display, compositor); int argc = 0; char **argv = nullptr; setenv("QT_QPA_PLATFORM", "minimal", 1); app = new QGuiApplication(argc, argv); } void ScreenControllerTest::TearDown() { delete screenController; } TEST_F(ScreenControllerTest, SingleScreenFound) { // Set up display state std::vector config{fakeOutput1}; std::vector bufferConfig; // only used to match buffer with display, unecessary here display->setFakeConfiguration(config, bufferConfig); screenController->update(); ASSERT_EQ(1, screenController->screens().count()); Screen* screen = screenController->screens().first(); EXPECT_EQ(QRect(0, 0, 150, 200), screen->geometry()); } TEST_F(ScreenControllerTest, MultipleScreenFound) { std::vector config{fakeOutput1, fakeOutput2}; std::vector bufferConfig; // only used to match buffer with display, unecessary here display->setFakeConfiguration(config, bufferConfig); screenController->update(); ASSERT_EQ(2, screenController->screens().count()); EXPECT_EQ(QRect(0, 0, 150, 200), screenController->screens().at(0)->geometry()); EXPECT_EQ(QRect(500, 600, 1500, 2000), screenController->screens().at(1)->geometry()); } TEST_F(ScreenControllerTest, ScreenAdded) { std::vector config{fakeOutput1}; std::vector bufferConfig; // only used to match buffer with display, unecessary here display->setFakeConfiguration(config, bufferConfig); screenController->update(); config.push_back(fakeOutput2); display->setFakeConfiguration(config, bufferConfig); ASSERT_EQ(1, screenController->screens().count()); EXPECT_EQ(QRect(0, 0, 150, 200), screenController->screens().at(0)->geometry()); screenController->update(); ASSERT_EQ(2, screenController->screens().count()); EXPECT_EQ(QRect(0, 0, 150, 200), screenController->screens().at(0)->geometry()); EXPECT_EQ(QRect(500, 600, 1500, 2000), screenController->screens().at(1)->geometry()); } TEST_F(ScreenControllerTest, ScreenRemoved) { std::vector config{fakeOutput2, fakeOutput1}; std::vector bufferConfig; // only used to match buffer with display, unecessary here display->setFakeConfiguration(config, bufferConfig); screenController->update(); config.pop_back(); display->setFakeConfiguration(config, bufferConfig); ASSERT_EQ(2, screenController->screens().count()); EXPECT_EQ(QRect(500, 600, 1500, 2000), screenController->screens().at(0)->geometry()); EXPECT_EQ(QRect(0, 0, 150, 200), screenController->screens().at(1)->geometry()); screenController->update(); ASSERT_EQ(1, screenController->screens().count()); EXPECT_EQ(QRect(500, 600, 1500, 2000), screenController->screens().at(0)->geometry()); } TEST_F(ScreenControllerTest, MatchBufferWithDisplay) { std::vector config{fakeOutput1}; MockGLDisplayBuffer buffer1; std::vector buffers {&buffer1}; geom::Rectangle buffer1Geom{{0, 0}, {150, 200}}; EXPECT_CALL(buffer1, view_area()) .WillRepeatedly(Return(buffer1Geom)); display->setFakeConfiguration(config, buffers); screenController->update(); ASSERT_EQ(1, screenController->screens().count()); EXPECT_CALL(buffer1, make_current()); static_cast(screenController->screens().at(0))->makeCurrent(); } TEST_F(ScreenControllerTest, MultipleMatchBuffersWithDisplays) { std::vector config{fakeOutput1, fakeOutput2}; MockGLDisplayBuffer buffer1, buffer2; std::vector buffers {&buffer1, &buffer2}; geom::Rectangle buffer1Geom{{500, 600}, {1500, 2000}}; geom::Rectangle buffer2Geom{{0, 0}, {150, 200}}; EXPECT_CALL(buffer1, view_area()) .WillRepeatedly(Return(buffer1Geom)); EXPECT_CALL(buffer2, view_area()) .WillRepeatedly(Return(buffer2Geom)); display->setFakeConfiguration(config, buffers); screenController->update(); ASSERT_EQ(2, screenController->screens().count()); EXPECT_CALL(buffer1, make_current()); EXPECT_CALL(buffer2, make_current()); static_cast(screenController->screens().at(0))->makeCurrent(); static_cast(screenController->screens().at(1))->makeCurrent(); } ./tests/mirserver/ScreenController/CMakeLists.txt0000644000015600001650000000132212677054330022305 0ustar jenkinsjenkinsset( SCREENCONTROLLER_TEST_SOURCES screencontroller_test.cpp ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp # to be moc-ed stub_screen.h testable_screencontroller.h ) include_directories( ${CMAKE_SOURCE_DIR}/tests/framework ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${CMAKE_SOURCE_DIR}/src/common ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${MIRSERVER_INCLUDE_DIRS} ${MIRRENDERERGLDEV_INCLUDE_DIRS} ) add_executable(ScreenControllerTest ${SCREENCONTROLLER_TEST_SOURCES}) target_link_libraries( ScreenControllerTest qpa-mirserver -L${CMAKE_BINARY_DIR}/tests/framework qtmir-test-framework-static ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(ScreenController, ScreenControllerTest) ./tests/mirserver/ScreenController/stub_display.h0000644000015600001650000000557612677054330022437 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef STUB_DISPLAY_H #define STUB_DISPLAY_H #include "mock_display.h" #include "mock_gl_display_buffer.h" #include "mock_display_configuration.h" namespace mg = mir::graphics; namespace geom = mir::geometry; class StubDisplayConfiguration : public MockDisplayConfiguration { public: StubDisplayConfiguration(const std::vector &config) : m_config(config) {} void for_each_output(std::function f) const override { for (auto config : m_config) { f(config); } } std::unique_ptr clone() const override { return std::make_unique(); } private: const std::vector m_config; }; class StubDisplaySyncGroup : public MockDisplaySyncGroup { public: StubDisplaySyncGroup(MockGLDisplayBuffer *buffer) : buffer(buffer) {} void for_each_display_buffer(std::function const& f) override { f(*buffer); } std::chrono::milliseconds recommended_sleep() const { std::chrono::milliseconds one{1}; return one; } private: MockGLDisplayBuffer *buffer; }; class StubDisplay : public MockDisplay { public: // Note, GMock cannot mock functions which return non-copyable objects, so stubbing needed std::unique_ptr configuration() const override { return std::unique_ptr( new StubDisplayConfiguration(m_config) ); } void for_each_display_sync_group(std::function const& f) override { for (auto displayBuffer : m_displayBuffers) { StubDisplaySyncGroup b(displayBuffer); f(b); } } void setFakeConfiguration(std::vector &config, std::vector &displayBuffers) { m_config = config; m_displayBuffers = displayBuffers; } private: std::vector m_config; std::vector m_displayBuffers; }; #endif // STUB_DISPLAY_H ./tests/mirserver/Clipboard/0000755000015600001650000000000012677054330016163 5ustar jenkinsjenkins./tests/mirserver/Clipboard/clipboard_test.cpp0000644000015600001650000000506412677054330021672 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include #include #include using namespace qtmir; TEST(ClipboardTest, MimeDataSerialization) { QMimeData *mimeData = new QMimeData; mimeData->setData("text/plain", "Hello World!"); mimeData->setData("text/html", "Hello World!"); QByteArray serializedMimeData = serializeMimeData(mimeData); ASSERT_TRUE(serializedMimeData.size() > 0); QMimeData *deserializedMimeData = deserializeMimeData(serializedMimeData); ASSERT_TRUE(deserializedMimeData != nullptr); ASSERT_TRUE(deserializedMimeData->hasFormat("text/plain")); ASSERT_EQ(mimeData->data("text/plain"), deserializedMimeData->data("text/plain")); ASSERT_TRUE(deserializedMimeData->hasFormat("text/html")); ASSERT_EQ(mimeData->data("text/html"), deserializedMimeData->data("text/html")); delete mimeData; delete deserializedMimeData; } TEST(ClipboardTest, RefuseContentsThatAreTooBig) { QLoggingCategory::setFilterRules(QStringLiteral("*=false")); DBusClipboard::skipDBusRegistration = true; DBusClipboard *dbusClipboard = new DBusClipboard; // Was getting a "warning: overflow in implicit constant conversion [-Woverflow]" // when I used that constant directly in the QByteArray constructors below. Don't // understand why so here's the workaround for it. int maxContentsSize = DBusClipboard::maxContentsSize; QByteArray reasonableContents(maxContentsSize * 0.9, 'R'); QByteArray tooBigContents(maxContentsSize * 1.2, 'B'); dbusClipboard->SetContents(reasonableContents); ASSERT_EQ(dbusClipboard->contents(), reasonableContents); dbusClipboard->SetContents(tooBigContents); // tooBigContents were refused. So it stays with the previously // set contents ASSERT_EQ(dbusClipboard->contents(), reasonableContents); delete dbusClipboard; } ./tests/mirserver/Clipboard/CMakeLists.txt0000644000015600001650000000066112677054330020726 0ustar jenkinsjenkinsset( CLIPBOARD_TEST_SOURCES clipboard_test.cpp ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp ) include_directories( ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${MIRSERVER_INCLUDE_DIRS} ) add_executable(ClipboardTest ${CLIPBOARD_TEST_SOURCES}) target_link_libraries( ClipboardTest qpa-mirserver ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(Clipboard, ClipboardTest) ./tests/mirserver/WindowManager/0000755000015600001650000000000012677054331017027 5ustar jenkinsjenkins./tests/mirserver/WindowManager/stub_surface.cpp0000644000015600001650000001001112677054330022210 0ustar jenkinsjenkins/* * 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "stub_surface.h" #include //#include std::string StubSurface::name() const { return {}; } void StubSurface::move_to(mir::geometry::Point const& /*top_left*/) { } float StubSurface::alpha() const { return 0; } mir::geometry::Size StubSurface::size() const { return {}; } mir::geometry::Size StubSurface::client_size() const { return {}; } std::shared_ptr StubSurface::primary_buffer_stream() const { return {}; } void StubSurface::set_streams(std::list const& /*streams*/) { } bool StubSurface::supports_input() const { return false; } int StubSurface::client_input_fd() const { return 0; } std::shared_ptr StubSurface::input_channel() const { return {}; } mir::input::InputReceptionMode StubSurface::reception_mode() const { return mir::input::InputReceptionMode::normal; } void StubSurface::set_reception_mode(mir::input::InputReceptionMode /*mode*/) { } void StubSurface::set_input_region(std::vector const& /*input_rectangles*/) { } void StubSurface::resize(mir::geometry::Size const& /*size*/) { } mir::geometry::Point StubSurface::top_left() const { return {}; } mir::geometry::Rectangle StubSurface::input_bounds() const { return {}; } bool StubSurface::input_area_contains(mir::geometry::Point const& /*point*/) const { return false; } void StubSurface::consume(MirEvent const* /*event*/) { } void StubSurface::set_alpha(float /*alpha*/) { } void StubSurface::set_orientation(MirOrientation /*orientation*/) { } void StubSurface::set_transformation(glm::mat4 const& /*mat4*/) { } bool StubSurface::visible() const { return false; } mir::graphics::RenderableList StubSurface::generate_renderables(mir::compositor::CompositorID /*id*/) const { return {}; } int StubSurface::buffers_ready_for_compositor(void const* /*compositor_id*/) const { return 0; } MirSurfaceType StubSurface::type() const { return MirSurfaceType::mir_surface_type_normal; } MirSurfaceState StubSurface::state() const { return MirSurfaceState::mir_surface_state_fullscreen; } int StubSurface::configure(MirSurfaceAttrib /*attrib*/, int value) { return value; } int StubSurface::query(MirSurfaceAttrib /*attrib*/) const { return 0; } void StubSurface::hide() { } void StubSurface::show() { } void StubSurface::set_cursor_image(std::shared_ptr const& /*image*/) { } std::shared_ptr StubSurface::cursor_image() const { return {}; } void StubSurface::set_cursor_stream( std::shared_ptr const& /*stream*/, mir::geometry::Displacement const& /*hotspot*/) { } void StubSurface::request_client_surface_close() { } std::shared_ptr StubSurface::parent() const { return {}; } void StubSurface::add_observer(std::shared_ptr const& /*observer*/) { } void StubSurface::remove_observer(std::weak_ptr < mir::scene::SurfaceObserver > const& /*observer*/) { } void StubSurface::set_keymap(MirInputDeviceId /*id*/, std::string const& /*model*/, std::string const& /*layout*/, std::string const& /*variant*/, std::string const& /*options*/) { } void StubSurface::rename(std::string const& /*title*/) { } ./tests/mirserver/WindowManager/stub_session.cpp0000644000015600001650000000556512677054330022265 0ustar jenkinsjenkins/* * 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "stub_session.h" std::shared_ptr StubSession::get_surface( mir::frontend::SurfaceId /*surface*/) const { return {}; } std::string StubSession::name() const { return {}; } void StubSession::force_requests_to_complete() { } pid_t StubSession::process_id() const { return {0}; } void StubSession::take_snapshot( mir::scene::SnapshotCallback const& /*snapshot_taken*/) { } std::shared_ptr StubSession::default_surface() const { return {}; } void StubSession::set_lifecycle_state(MirLifecycleState /*state*/) { } void StubSession::send_display_config( mir::graphics::DisplayConfiguration const& /*configuration*/) { } void StubSession::hide() { } void StubSession::show() { } void StubSession::start_prompt_session() { } void StubSession::stop_prompt_session() { } void StubSession::suspend_prompt_session() { } void StubSession::resume_prompt_session() { } mir::frontend::SurfaceId StubSession::create_surface( mir::scene::SurfaceCreationParameters const& /*params*/, std::shared_ptr const& /*sink*/) { return mir::frontend::SurfaceId{0}; } void StubSession::destroy_surface(mir::frontend::SurfaceId /*surface*/) { } void StubSession::destroy_surface(std::weak_ptr const& /*surface*/) { } std::shared_ptr StubSession::surface( mir::frontend::SurfaceId /*surface*/) const { return {}; } std::shared_ptr StubSession::surface_after( std::shared_ptr const& /*ptr*/) const { return {}; } std::shared_ptr StubSession::get_buffer_stream( mir::frontend::BufferStreamId /*stream*/) const { return {}; } mir::frontend::BufferStreamId StubSession::create_buffer_stream( mir::graphics::BufferProperties const& /*props*/) { return {}; } void StubSession::destroy_buffer_stream(mir::frontend::BufferStreamId /*stream*/) { } void StubSession::configure_streams( mir::scene::Surface& /*surface*/, std::vector const& /*config*/) { } void StubSession::send_input_device_change(std::vector> const& /*devices*/) { } ./tests/mirserver/WindowManager/window_manager.cpp0000644000015600001650000002755712677054330022553 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * */ #include #include "mirwindowmanager.h" #include "stub_surface.h" #include "stub_session.h" #include "surfaceobserver.h" #include #include #include #include "gtest/gtest.h" #include "gmock/gmock.h" namespace mf = mir::frontend; namespace ms = mir::scene; namespace msh = mir::shell; using namespace mir::geometry; using namespace testing; using mir::events::make_event; namespace { struct MockDisplayLayout : msh::DisplayLayout { MOCK_METHOD1(clip_to_output, void (Rectangle& rect)); MOCK_METHOD1(size_to_output, void (Rectangle& rect)); MOCK_METHOD2(place_in_output, bool (mir::graphics::DisplayConfigurationOutputId id, Rectangle& rect)); }; struct MockSurface : StubSurface { MOCK_METHOD2(configure, int (MirSurfaceAttrib attrib, int value)); }; struct MockSession : StubSession { MOCK_CONST_METHOD1(surface, std::shared_ptr (mir::frontend::SurfaceId surface)); }; struct WindowManager : Test { const std::shared_ptr mock_display_layout = std::make_shared>(); std::shared_ptr sessionListener = std::make_shared(); const std::shared_ptr window_manager = MirWindowManager::create(mock_display_layout, sessionListener); const Rectangle arbitrary_display{{0, 0}, {97, 101}}; const std::shared_ptr arbitrary_session = std::make_shared>(); const std::shared_ptr arbitrary_surface = std::make_shared(); const ms::SurfaceCreationParameters arbitrary_params; const mf::SurfaceId arbitrary_surface_id{__LINE__}; MOCK_METHOD2(build_surface, mf::SurfaceId(std::shared_ptr const& session, ms::SurfaceCreationParameters const& params)); void SetUp() override { ON_CALL(*this, build_surface(_, _)).WillByDefault(Return(arbitrary_surface_id)); ON_CALL(*arbitrary_session, surface(_)).WillByDefault(Return(arbitrary_surface)); window_manager->add_session(arbitrary_session); } void TearDown() override { window_manager->remove_session(arbitrary_session); } void add_surface() { EXPECT_CALL(*this, build_surface(_, _)); window_manager->add_surface( arbitrary_session, arbitrary_params, [this](std::shared_ptr const& session, ms::SurfaceCreationParameters const& params) { return build_surface(session, params); }); } std::vector const arbitrary_cookie; }; } TEST_F(WindowManager, CreatesSurfaceUsingSuppliedBuilder) { EXPECT_CALL(*this, build_surface(_, _)); const auto surface = window_manager->add_surface( arbitrary_session, arbitrary_params, [this](std::shared_ptr const& session, ms::SurfaceCreationParameters const& params) { return build_surface(session, params); }); EXPECT_THAT(surface, Eq(arbitrary_surface_id)); } TEST_F(WindowManager, SizesNewSurfaceToOutput) { EXPECT_CALL(*this, build_surface(_, _)).Times(AnyNumber()); const Size request_size{0, 0}; const Size expect_size{57, 91}; ms::SurfaceCreationParameters params; params.size = request_size; EXPECT_CALL(*mock_display_layout, size_to_output(_)). WillOnce(Invoke([&](Rectangle& rect) { EXPECT_THAT(rect.size, Eq(request_size)); rect.size = expect_size; })); add_surface(); } namespace { struct AttribValuePair { MirSurfaceAttrib attribute; int value; friend std::ostream& operator<<(std::ostream& out, AttribValuePair const& pair) { return out << "attribute:" << pair.attribute << ", value:" << pair.value; } }; struct SetAttribute : WindowManager, ::testing::WithParamInterface {}; } TEST_P(SetAttribute, ConfiguresSurface) { const auto attribute = GetParam().attribute; const auto value = GetParam().value; const auto surface = std::make_shared(); EXPECT_CALL(*arbitrary_session, surface(_)).Times(AnyNumber()).WillRepeatedly(Return(surface)); add_surface(); EXPECT_CALL(*surface, configure(attribute, value)).WillOnce(Return(value)); window_manager->set_surface_attribute(arbitrary_session, surface, attribute, value); } INSTANTIATE_TEST_CASE_P(WindowManager, SetAttribute, Values( AttribValuePair{mir_surface_attrib_state, mir_surface_state_restored}, AttribValuePair{mir_surface_attrib_state, mir_surface_state_minimized}, AttribValuePair{mir_surface_attrib_state, mir_surface_state_maximized}, AttribValuePair{mir_surface_attrib_state, mir_surface_state_vertmaximized}, AttribValuePair{mir_surface_attrib_state, mir_surface_state_fullscreen}, AttribValuePair{mir_surface_attrib_state, mir_surface_state_horizmaximized}, AttribValuePair{mir_surface_attrib_state, mir_surface_state_hidden}, AttribValuePair{mir_surface_attrib_type, mir_surface_type_normal}, AttribValuePair{mir_surface_attrib_type, mir_surface_type_utility}, AttribValuePair{mir_surface_attrib_type, mir_surface_type_dialog}, AttribValuePair{mir_surface_attrib_type, mir_surface_type_overlay}, AttribValuePair{mir_surface_attrib_type, mir_surface_type_freestyle}, AttribValuePair{mir_surface_attrib_type, mir_surface_type_popover}, AttribValuePair{mir_surface_attrib_type, mir_surface_type_inputmethod}, AttribValuePair{mir_surface_attrib_type, mir_surface_type_satellite}, AttribValuePair{mir_surface_attrib_type, mir_surface_type_tip}, AttribValuePair{mir_surface_attrib_preferred_orientation, mir_orientation_mode_portrait}, AttribValuePair{mir_surface_attrib_preferred_orientation, mir_orientation_mode_landscape}, AttribValuePair{mir_surface_attrib_preferred_orientation, mir_orientation_mode_portrait_inverted}, AttribValuePair{mir_surface_attrib_preferred_orientation, mir_orientation_mode_landscape_inverted}, AttribValuePair{mir_surface_attrib_preferred_orientation, mir_orientation_mode_portrait_any}, AttribValuePair{mir_surface_attrib_preferred_orientation, mir_orientation_mode_landscape_any}, AttribValuePair{mir_surface_attrib_preferred_orientation, mir_orientation_mode_any} )); // The following calls are /currently/ ignored, but we can check they don't "blow up" TEST_F(WindowManager, HandlesAddSession) { EXPECT_NO_THROW(window_manager->add_session(arbitrary_session)); } TEST_F(WindowManager, HandlesRemoveSession) { EXPECT_NO_THROW(window_manager->remove_session(arbitrary_session)); } TEST_F(WindowManager, HandlesAddDisplay) { EXPECT_NO_THROW(window_manager->add_display(arbitrary_display)); } TEST_F(WindowManager, HandlesRemoveDisplay) { EXPECT_NO_THROW(window_manager->remove_display(arbitrary_display)); } TEST_F(WindowManager, HandlesModifySurface) { add_surface(); SurfaceObserver surfaceObserver; SurfaceObserver::registerObserverForSurface(&surfaceObserver, arbitrary_surface.get()); mir::shell::SurfaceSpecification modifications; modifications.min_width = mir::geometry::Width(100); modifications.min_height = mir::geometry::Height(101); modifications.max_width = mir::geometry::Width(102); modifications.max_height = mir::geometry::Height(103); modifications.shell_chrome = mir_shell_chrome_low; QSignalSpy spyMinimumWidthChanged(&surfaceObserver, SIGNAL(minimumWidthChanged(int))); QSignalSpy spyMinimumHeightChanged(&surfaceObserver, SIGNAL(minimumHeightChanged(int))); QSignalSpy spyMaximumWidthChanged(&surfaceObserver, SIGNAL(maximumWidthChanged(int))); QSignalSpy spyMaximumHeightChanged(&surfaceObserver, SIGNAL(maximumHeightChanged(int))); QSignalSpy spyShellChromeChanged(&surfaceObserver, SIGNAL(shellChromeChanged(MirShellChrome))); window_manager->modify_surface(arbitrary_session, arbitrary_surface, modifications); EXPECT_EQ(100, spyMinimumWidthChanged.takeFirst().at(0).toInt()); EXPECT_EQ(101, spyMinimumHeightChanged.takeFirst().at(0).toInt()); EXPECT_EQ(102, spyMaximumWidthChanged.takeFirst().at(0).toInt()); EXPECT_EQ(103, spyMaximumHeightChanged.takeFirst().at(0).toInt()); EXPECT_EQ(mir_shell_chrome_low, spyShellChromeChanged.takeFirst().at(0).toInt()); window_manager->remove_surface(arbitrary_session, arbitrary_surface); } TEST_F(WindowManager, HandlesKeyboardEvent) { const MirInputDeviceId arbitrary_device{0}; const auto arbitrary_timestamp = std::chrono::steady_clock().now().time_since_epoch(); const auto arbitrary_action = mir_keyboard_action_down; const xkb_keysym_t arbitrary_key_code{0}; const int arbitrary_scan_code = 0; const MirInputEventModifiers arbitrary_event_modifiers{0}; const auto generic_event = make_event( arbitrary_device, arbitrary_timestamp, arbitrary_cookie, arbitrary_action, arbitrary_key_code, arbitrary_scan_code, arbitrary_event_modifiers); const auto input_event = mir_event_get_input_event(generic_event.get()); const auto event = mir_input_event_get_keyboard_event(input_event); EXPECT_NO_THROW(window_manager->handle_keyboard_event(event)); } TEST_F(WindowManager, HandlesTouchEvent) { const MirInputDeviceId arbitrary_device{0}; const auto arbitrary_timestamp = std::chrono::steady_clock().now().time_since_epoch(); const MirInputEventModifiers arbitrary_event_modifiers{0}; const auto generic_event = make_event( arbitrary_device, arbitrary_timestamp, arbitrary_cookie, arbitrary_event_modifiers); const auto input_event = mir_event_get_input_event(generic_event.get()); const auto event = mir_input_event_get_touch_event(input_event); EXPECT_NO_THROW(window_manager->handle_touch_event(event)); } TEST_F(WindowManager, HandlesPointerEvent) { const MirInputDeviceId arbitrary_device{0}; const auto arbitrary_timestamp = std::chrono::steady_clock().now().time_since_epoch(); const MirInputEventModifiers arbitrary_event_modifiers{0}; const auto arbitrary_pointer_action = mir_pointer_action_button_down; const auto arbitrary_pointer_buttons = mir_pointer_button_primary; const float arbitrary_x_axis_value{0}; const float arbitrary_y_axis_value{0}; const float arbitrary_hscroll_value{0}; const float arbitrary_vscroll_value{0}; const float arbitrary_relative_x_value{0}; const float arbitrary_relative_y_value{0}; const auto generic_event = make_event( arbitrary_device, arbitrary_timestamp, arbitrary_cookie, arbitrary_event_modifiers, arbitrary_pointer_action, arbitrary_pointer_buttons, arbitrary_x_axis_value, arbitrary_y_axis_value, arbitrary_hscroll_value, arbitrary_vscroll_value, arbitrary_relative_x_value, arbitrary_relative_y_value); const auto input_event = mir_event_get_input_event(generic_event.get()); const auto event = mir_input_event_get_pointer_event(input_event); EXPECT_NO_THROW(window_manager->handle_pointer_event(event)); } ./tests/mirserver/WindowManager/stub_session.h0000644000015600001650000000524712677054330021727 0ustar jenkinsjenkins/* * 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QPAMIRSERVER_STUBSESSION_H #define QPAMIRSERVER_STUBSESSION_H #include struct StubSession : mir::scene::Session { std::shared_ptr get_surface(mir::frontend::SurfaceId surface) const override; std::string name() const override; void force_requests_to_complete() override; pid_t process_id() const override; void take_snapshot(mir::scene::SnapshotCallback const& snapshot_taken) override; std::shared_ptr default_surface() const override; void set_lifecycle_state(MirLifecycleState state) override; void send_display_config(mir::graphics::DisplayConfiguration const&) override; void hide() override; void show() override; void start_prompt_session() override; void stop_prompt_session() override; void suspend_prompt_session() override; void resume_prompt_session() override; mir::frontend::SurfaceId create_surface( mir::scene::SurfaceCreationParameters const& params, std::shared_ptr const& sink) override; void destroy_surface(mir::frontend::SurfaceId surface) override; void destroy_surface(std::weak_ptr const& surface) override; std::shared_ptr surface(mir::frontend::SurfaceId surface) const override; std::shared_ptr surface_after(std::shared_ptr const&) const override; std::shared_ptr get_buffer_stream(mir::frontend::BufferStreamId stream) const override; mir::frontend::BufferStreamId create_buffer_stream(mir::graphics::BufferProperties const& props) override; void destroy_buffer_stream(mir::frontend::BufferStreamId stream) override; void configure_streams(mir::scene::Surface& surface, std::vector const& config) override; void send_input_device_change(std::vector> const& devices) override; }; #endif //QPAMIRSERVER_STUBSESSION_H ./tests/mirserver/WindowManager/stub_surface.h0000644000015600001650000000674212677054330021675 0ustar jenkinsjenkins/* * 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 QPAMIRSERVER_STUB_SURFACE_H #define QPAMIRSERVER_STUB_SURFACE_H #include // mir::scene::Surface is a horribly wide interface to expose from Mir // and there's not even a proper stub in the Mir-0.15 package mirtest-dev struct StubSurface : mir::scene::Surface { std::string name() const override; void move_to(mir::geometry::Point const& top_left) override; float alpha() const override; mir::geometry::Size size() const override; mir::geometry::Size client_size() const override; std::shared_ptr primary_buffer_stream() const override; void set_streams(std::list const& streams) override; bool supports_input() const override; int client_input_fd() const override; std::shared_ptr input_channel() const override; mir::input::InputReceptionMode reception_mode() const override; void set_reception_mode(mir::input::InputReceptionMode mode) override; void set_input_region(std::vector const& input_rectangles) override; void resize(mir::geometry::Size const& size) override; mir::geometry::Point top_left() const override; mir::geometry::Rectangle input_bounds() const override; bool input_area_contains(mir::geometry::Point const& point) const override; void consume(MirEvent const* event) override; void set_alpha(float alpha) override; void set_orientation(MirOrientation orientation) override; void set_transformation(glm::mat4 const&) override; bool visible() const override; mir::graphics::RenderableList generate_renderables(mir::compositor::CompositorID id) const override; int buffers_ready_for_compositor(void const* compositor_id) const override; MirSurfaceType type() const override; MirSurfaceState state() const override; int configure(MirSurfaceAttrib attrib, int value) override; int query(MirSurfaceAttrib attrib) const override; void hide() override; void show() override; void set_cursor_image(std::shared_ptr const& image) override; std::shared_ptr cursor_image() const override; void set_cursor_stream(std::shared_ptr const& stream, mir::geometry::Displacement const& hotspot) override; void request_client_surface_close() override; std::shared_ptr parent() const override; void add_observer(std::shared_ptr const& observer) override; void remove_observer(std::weak_ptr const& observer) override; void set_keymap(MirInputDeviceId id, std::string const& model, std::string const& layout, std::string const& variant, std::string const& options) override; void rename(std::string const& title) override; }; #endif //QPAMIRSERVER_STUB_SURFACE_H ./tests/mirserver/WindowManager/CMakeLists.txt0000644000015600001650000000055012677054330021566 0ustar jenkinsjenkinsinclude_directories( ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${MIRSERVER_INCLUDE_DIRS} ) add_executable(WindowManagerTest window_manager.cpp stub_surface.cpp stub_session.cpp ) target_link_libraries( WindowManagerTest Qt5::Test qpa-mirserver ${GTEST_BOTH_LIBRARIES} ${GMOCK_LIBRARIES} ) add_test(WindowManager, WindowManagerTest) ./tests/mirserver/CMakeLists.txt0000644000015600001650000000026512677054330017027 0ustar jenkinsjenkinsadd_subdirectory(ArgvHelper) add_subdirectory(QtEventFeeder) add_subdirectory(Clipboard) add_subdirectory(Screen) add_subdirectory(ScreenController) add_subdirectory(WindowManager) ./tests/mirserver/ArgvHelper/0000755000015600001650000000000012677054330016323 5ustar jenkinsjenkins./tests/mirserver/ArgvHelper/argvHelper_test.cpp0000644000015600001650000000443012677054330022166 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include using namespace qtmir; TEST(ArgvHelperTest, StripsCorrectly) { int argc = 5; const char *argv[6] = { "/usr/bin/unity8", "-fullscreen", "--debug", "--platform-input-lib=/path/to/lib.so", "-testability", nullptr }; const int filteredArgc = 2; const char *filteredArgv[3] = { "-fullscreen", "-testability", nullptr }; editArgvToMatch(argc, const_cast(argv), filteredArgc, filteredArgv); EXPECT_EQ(argc, 3); EXPECT_EQ(argv[0], "/usr/bin/unity8"); EXPECT_EQ(argv[1], "-fullscreen"); EXPECT_EQ(argv[2], "-testability"); EXPECT_EQ(argv[3], nullptr); } TEST(ArgvHelperTest, NothingToStrip) { int argc = 4; const char *argv[5] = { "/usr/bin/unity8", "-fullscreen", "--multisample", "-testability", nullptr }; const int filteredArgc = 3; const char *filteredArgv[4] = { "-fullscreen", "-testability", "--multisample", nullptr }; editArgvToMatch(argc, const_cast(argv), filteredArgc, filteredArgv); EXPECT_EQ(argc, 4); EXPECT_EQ(argv[0], "/usr/bin/unity8"); EXPECT_EQ(argv[1], "-fullscreen"); EXPECT_EQ(argv[2], "-testability"); EXPECT_EQ(argv[3], "--multisample"); EXPECT_EQ(argv[4], nullptr); } TEST(ArgvHelperTest, NothingToDo) { int argc = 1; const char *argv[2] = { "/usr/bin/unity8", nullptr }; const int filteredArgc = 0; const char *filteredArgv[1] = { nullptr }; editArgvToMatch(argc, const_cast(argv), filteredArgc, filteredArgv); EXPECT_EQ(argc, 1); EXPECT_EQ(argv[0], "/usr/bin/unity8"); EXPECT_EQ(argv[1], nullptr); } ./tests/mirserver/ArgvHelper/CMakeLists.txt0000644000015600001650000000044412677054330021065 0ustar jenkinsjenkinsset( ARGVHELPER_TEST_SOURCES argvHelper_test.cpp ) add_executable(ArgvHelperTest ${ARGVHELPER_TEST_SOURCES}) include_directories( ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ) target_link_libraries( ArgvHelperTest ${GTEST_BOTH_LIBRARIES} ) add_test(ArgvHelper, ArgvHelperTest) ./tests/CMakeLists.txt0000644000015600001650000000014712677054330015010 0ustar jenkinsjenkinsfind_package(GMock) add_subdirectory(framework) add_subdirectory(mirserver) add_subdirectory(modules) ./src/0000755000015600001650000000000012677054330011673 5ustar jenkinsjenkins./src/modules/0000755000015600001650000000000012677054330013343 5ustar jenkinsjenkins./src/modules/CMakeLists.txt0000644000015600001650000000067712677054330016115 0ustar jenkinsjenkinsinclude(QmlPlugins) # Set up and install a QML plugin. # # add_qml_plugin(plugin version path # [...] # See export_qmlfiles and export_qmlplugin documentation for more options # ) macro(add_qml_plugin PLUGIN VERSION PATH) export_qmlfiles(${PLUGIN} ${PATH} DESTINATION ${QML_MODULE_INSTALL_DIR} ${ARGN}) export_qmlplugin(${PLUGIN} ${VERSION} ${PATH} DESTINATION ${QML_MODULE_INSTALL_DIR} ${ARGN}) endmacro() add_subdirectory(Unity) ./src/modules/Unity/0000755000015600001650000000000012677054330014453 5ustar jenkinsjenkins./src/modules/Unity/Application/0000755000015600001650000000000012677054330016716 5ustar jenkinsjenkins./src/modules/Unity/Application/mirbuffersgtexture.h0000644000015600001650000000270412677054330023026 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIRBUFFERSGTEXTURE_H #define MIRBUFFERSGTEXTURE_H #include #include #include namespace mir { namespace graphics { class Buffer; }} class MirBufferSGTexture : public QSGTexture { Q_OBJECT public: MirBufferSGTexture(); virtual ~MirBufferSGTexture(); void setBuffer(std::shared_ptr buffer); void freeBuffer(); bool hasBuffer() const; int textureId() const override; QSize textureSize() const override; bool hasAlphaChannel() const override; bool hasMipmaps() const override { return false; } void bind() override; private: std::shared_ptr m_mirBuffer; int m_width; int m_height; GLuint m_textureId; }; #endif // MIRBUFFERSGTEXTURE_H ./src/modules/Unity/Application/applicationscreenshotprovider.cpp0000644000015600001650000001014012677054330025572 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // local #include "applicationscreenshotprovider.h" #include "application_manager.h" #include "application.h" #include "session.h" // QPA-mirserver #include "logging.h" // mir #include // Qt #include #include #include namespace qtmir { ApplicationScreenshotProvider::ApplicationScreenshotProvider(ApplicationManager *appManager) : QQuickImageProvider(QQuickImageProvider::Image) , m_appManager(appManager) { } QImage ApplicationScreenshotProvider::requestImage(const QString &imageId, QSize *size, const QSize &requestedSize) { qCDebug(QTMIR_APPLICATIONS) << "ApplicationScreenshotProvider::requestImage - imageId=" << imageId; QString appId = imageId.split('/').first(); Application* app = static_cast(m_appManager->findApplication(appId)); if (app == nullptr) { qWarning() << "ApplicationScreenshotProvider - app with appId" << appId << "not found"; return QImage(); } // TODO: if app not ready, return an app-provided splash image. If app has been stopped with saved state // return the screenshot that was saved to disk. SessionInterface* session = app->session(); if (!session || !session->session() || !session->session()->default_surface()) { qWarning() << "ApplicationScreenshotProvider - app session not found - asking for screenshot too early"; return QImage(); } QImage screenshotImage; QMutex screenshotMutex; QWaitCondition screenshotTakenCondition; bool screenShotDone = false; session->session()->take_snapshot( [&](mir::scene::Snapshot const& snapshot) { qCDebug(QTMIR_APPLICATIONS) << "ApplicationScreenshotProvider - Mir snapshot ready with size" << snapshot.size.height.as_int() << "x" << snapshot.size.width.as_int(); { // since we mirror, no need to offset starting position of the pixels QImage fullSizeScreenshot = QImage( (const uchar*)snapshot.pixels, snapshot.size.width.as_int(), snapshot.size.height.as_int(), QImage::Format_ARGB32_Premultiplied).mirrored(); if (!fullSizeScreenshot.isNull()) { if (requestedSize.isValid()) { *size = requestedSize.boundedTo(fullSizeScreenshot.size()); screenshotImage = fullSizeScreenshot.scaled(*size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } else { *size = fullSizeScreenshot.size(); screenshotImage = fullSizeScreenshot; } } { // Sync point with Qt's ImageProviderThread QMutexLocker screenshotMutexLocker(&screenshotMutex); screenShotDone = true; } screenshotTakenCondition.wakeAll(); } }); { // Sync point with Mir's snapshot thread QMutexLocker screenshotMutexLocker(&screenshotMutex); if (!screenShotDone) { screenshotTakenCondition.wait(&screenshotMutex); } } qCDebug(QTMIR_APPLICATIONS) << "ApplicationScreenshotProvider - working with size" << screenshotImage; return screenshotImage; } } // namespace qtmir ./src/modules/Unity/Application/application.cpp0000644000015600001650000005554512677054330021743 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // local #include "application.h" #include "application_manager.h" #include "desktopfilereader.h" #include "session.h" #include "sharedwakelock.h" #include "timer.h" // common #include // QPA mirserver #include "logging.h" // mir #include #include namespace ms = mir::scene; namespace qtmir { Application::Application(const QSharedPointer& sharedWakelock, DesktopFileReader *desktopFileReader, const QStringList &arguments, ApplicationManager *parent) : ApplicationInfoInterface(desktopFileReader->appId(), parent) , m_sharedWakelock(sharedWakelock) , m_desktopData(desktopFileReader) , m_pid(0) , m_stage((desktopFileReader->stageHint() == "SideStage") ? Application::SideStage : Application::MainStage) , m_supportedStages(Application::MainStage|Application::SideStage) , m_state(InternalState::Starting) , m_focused(false) , m_arguments(arguments) , m_session(nullptr) , m_requestedState(RequestedRunning) , m_processState(ProcessUnknown) , m_closeTimer(nullptr) , m_exemptFromLifecycle(false) { qCDebug(QTMIR_APPLICATIONS) << "Application::Application - appId=" << desktopFileReader->appId(); // Because m_state is InternalState::Starting acquireWakelock(); // FIXME(greyback) need to save long appId internally until ubuntu-app-launch can hide it from us m_longAppId = desktopFileReader->file().remove(QRegExp(".desktop$")).split('/').last(); m_supportedOrientations = m_desktopData->supportedOrientations(); m_rotatesWindowContents = m_desktopData->rotatesWindowContents(); setCloseTimer(new Timer); } Application::~Application() { qCDebug(QTMIR_APPLICATIONS) << "Application::~Application"; // (ricmm) -- To be on the safe side, better wipe the application QML compile cache if it crashes on startup if (m_processState == Application::ProcessUnknown) { wipeQMLCache(); } switch (m_state) { case InternalState::Starting: case InternalState::Running: case InternalState::RunningInBackground: case InternalState::SuspendingWaitSession: case InternalState::SuspendingWaitProcess: wipeQMLCache(); break; case InternalState::Closing: case InternalState::Suspended: case InternalState::StoppedResumable: break; case InternalState::Stopped: if (m_processState == Application::ProcessFailed) { // process crashed wipeQMLCache(); } break; } if (m_session) { m_session->setApplication(nullptr); delete m_session; } delete m_desktopData; delete m_closeTimer; } void Application::wipeQMLCache() { QString path(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/QML/Apps/")); QDir dir(path); QStringList apps = dir.entryList(); for (int i = 0; i < apps.size(); i++) { if (apps.at(i).contains(appId())) { qCDebug(QTMIR_APPLICATIONS) << "Application appId=" << apps.at(i) << " Wiping QML Cache"; dir.cd(apps.at(i)); dir.removeRecursively(); break; } } } bool Application::isValid() const { return m_desktopData->loaded(); } QString Application::desktopFile() const { return m_desktopData->file(); } QString Application::appId() const { return m_desktopData->appId(); } QString Application::name() const { return m_desktopData->name(); } QString Application::comment() const { return m_desktopData->comment(); } QUrl Application::icon() const { QString iconString = m_desktopData->icon(); QString pathString = m_desktopData->path(); if (QFileInfo(iconString).exists()) { return QUrl(iconString); } else if (QFileInfo(pathString + '/' + iconString).exists()) { return QUrl(pathString + '/' + iconString); } else { return QUrl("image://theme/" + iconString); } } QString Application::splashTitle() const { return m_desktopData->splashTitle(); } QUrl Application::splashImage() const { if (m_desktopData->splashImage().isEmpty()) { return QUrl(); } else { QFileInfo imageFileInfo(m_desktopData->path(), m_desktopData->splashImage()); if (imageFileInfo.exists()) { return QUrl::fromLocalFile(imageFileInfo.canonicalFilePath()); } else { qCWarning(QTMIR_APPLICATIONS) << QString("Application(%1).splashImage file does not exist: \"%2\". Ignoring it.") .arg(appId()).arg(imageFileInfo.absoluteFilePath()); return QUrl(); } } } QColor Application::colorFromString(const QString &colorString, const char *colorName) const { // NB: If a colour which is not fully opaque is specified in the desktop file, it will // be ignored and the default colour will be used instead. QColor color; if (colorString.isEmpty()) { color.setRgba(qRgba(0, 0, 0, 0)); } else { color.setNamedColor(colorString); if (color.isValid()) { // Force a fully opaque color. color.setAlpha(255); } else { color.setRgba(qRgba(0, 0, 0, 0)); qCWarning(QTMIR_APPLICATIONS) << QString("Invalid %1: \"%2\"") .arg(colorName).arg(colorString); } } return color; } const char* Application::internalStateToStr(InternalState state) { switch (state) { case InternalState::Starting: return "Starting"; case InternalState::Running: return "Running"; case InternalState::RunningInBackground: return "RunningInBackground"; case InternalState::SuspendingWaitSession: return "SuspendingWaitSession"; case InternalState::SuspendingWaitProcess: return "SuspendingWaitProcess"; case InternalState::Suspended: return "Suspended"; case InternalState::Closing: return "Closing"; case InternalState::StoppedResumable: return "StoppedResumable"; case InternalState::Stopped: return "Stopped"; default: return "???"; } } bool Application::splashShowHeader() const { QString showHeader = m_desktopData->splashShowHeader(); if (showHeader.toLower() == "true") { return true; } else { return false; } } QColor Application::splashColor() const { QString colorStr = m_desktopData->splashColor(); return colorFromString(colorStr, "splashColor"); } QColor Application::splashColorHeader() const { QString colorStr = m_desktopData->splashColorHeader(); return colorFromString(colorStr, "splashColorHeader"); } QColor Application::splashColorFooter() const { QString colorStr = m_desktopData->splashColorFooter(); return colorFromString(colorStr, "splashColorFooter"); } QString Application::exec() const { return m_desktopData->exec(); } Application::Stage Application::stage() const { return m_stage; } Application::Stages Application::supportedStages() const { return m_supportedStages; } Application::State Application::state() const { // The public state is a simplified version of the internal one as our consumers // don't have to know or care about all the nasty details. switch (m_state) { case InternalState::Starting: return Starting; case InternalState::Running: case InternalState::RunningInBackground: case InternalState::SuspendingWaitSession: case InternalState::SuspendingWaitProcess: case InternalState::Closing: return Running; case InternalState::Suspended: return Suspended; case InternalState::StoppedResumable: case InternalState::Stopped: default: return Stopped; } } Application::RequestedState Application::requestedState() const { return m_requestedState; } void Application::setRequestedState(RequestedState value) { if (m_requestedState == value) { // nothing to do return; } qCDebug(QTMIR_APPLICATIONS) << "Application::setRequestedState - appId=" << appId() << "requestedState=" << applicationStateToStr(value); m_requestedState = value; Q_EMIT requestedStateChanged(m_requestedState); applyRequestedState(); } void Application::applyRequestedState() { if (m_requestedState == RequestedRunning) { applyRequestedRunning(); } else { applyRequestedSuspended(); } } void Application::applyRequestedRunning() { switch (m_state) { case InternalState::Starting: // should leave the app alone until it reaches Running state break; case InternalState::Running: // already where it's wanted to be break; case InternalState::RunningInBackground: case InternalState::SuspendingWaitSession: case InternalState::Suspended: resume(); break; case InternalState::SuspendingWaitProcess: // should leave the app alone until it reaches Suspended state break; case InternalState::Closing: break; case InternalState::StoppedResumable: respawn(); break; case InternalState::Stopped: // dead end. break; } } void Application::applyRequestedSuspended() { switch (m_state) { case InternalState::Starting: // should leave the app alone until it reaches Running state break; case InternalState::Running: if (m_processState == ProcessRunning) { suspend(); } else { // we can't suspend it since we have no information on the app process Q_ASSERT(m_processState == ProcessUnknown); } break; case InternalState::RunningInBackground: case InternalState::SuspendingWaitSession: case InternalState::SuspendingWaitProcess: case InternalState::Suspended: // it's already going where we it's wanted break; case InternalState::Closing: // don't suspend while it is closing break; case InternalState::StoppedResumable: case InternalState::Stopped: // the app doesn't have a process in the first place, so there's nothing to suspend break; } } bool Application::focused() const { return m_focused; } bool Application::fullscreen() const { return m_session ? m_session->fullscreen() : false; } bool Application::canBeResumed() const { return m_processState != ProcessUnknown; } pid_t Application::pid() const { return m_pid; } void Application::close() { qCDebug(QTMIR_APPLICATIONS) << "Application::close - appId=" << appId(); switch (m_state) { case InternalState::Starting: stop(); break; case InternalState::Running: doClose(); break; case InternalState::RunningInBackground: case InternalState::SuspendingWaitSession: case InternalState::SuspendingWaitProcess: case InternalState::Suspended: setRequestedState(RequestedRunning); doClose(); break; case InternalState::Closing: // already on the way break; case InternalState::StoppedResumable: // session stopped while suspended. Stop it for good now. setInternalState(InternalState::Stopped); break; case InternalState::Stopped: // too late break; } } void Application::doClose() { Q_ASSERT(!m_closeTimer->isRunning());; Q_ASSERT(m_session != nullptr); m_session->close(); m_closeTimer->start(); setInternalState(InternalState::Closing); } void Application::setPid(pid_t pid) { m_pid = pid; } void Application::setArguments(const QStringList arguments) { m_arguments = arguments; } void Application::setSession(SessionInterface *newSession) { qCDebug(QTMIR_APPLICATIONS) << "Application::setSession - appId=" << appId() << "session=" << newSession; if (newSession == m_session) return; if (m_session) { m_session->disconnect(this); m_session->setApplication(nullptr); m_session->setParent(nullptr); } bool oldFullscreen = fullscreen(); m_session = newSession; if (m_session) { m_session->setParent(this); m_session->setApplication(this); switch (m_state) { case InternalState::Starting: case InternalState::Running: case InternalState::RunningInBackground: case InternalState::Closing: m_session->resume(); break; case InternalState::SuspendingWaitSession: case InternalState::SuspendingWaitProcess: case InternalState::Suspended: m_session->suspend(); break; case InternalState::Stopped: default: m_session->stop(); break; } connect(m_session, &SessionInterface::stateChanged, this, &Application::onSessionStateChanged); connect(m_session, &SessionInterface::fullscreenChanged, this, &Application::fullscreenChanged); if (oldFullscreen != fullscreen()) Q_EMIT fullscreenChanged(fullscreen()); } else { // this can only happen after the session has stopped and QML code called Session::release() Q_ASSERT(m_state == InternalState::Stopped || m_state == InternalState::StoppedResumable); } Q_EMIT sessionChanged(m_session); } void Application::setStage(Application::Stage stage) { qCDebug(QTMIR_APPLICATIONS) << "Application::setStage - appId=" << appId() << "stage=" << stage; if (m_stage != stage) { if ((stage | m_supportedStages) == 0) { return; } m_stage = stage; Q_EMIT stageChanged(stage); } } void Application::setInternalState(Application::InternalState state) { if (m_state == state) { return; } qCDebug(QTMIR_APPLICATIONS) << "Application::setInternalState - appId=" << appId() << "state=" << internalStateToStr(state); auto oldPublicState = this->state(); m_state = state; switch (m_state) { case InternalState::Starting: case InternalState::Running: acquireWakelock(); break; case InternalState::RunningInBackground: case InternalState::Suspended: releaseWakelock(); break; case InternalState::Closing: acquireWakelock(); break; case InternalState::StoppedResumable: releaseWakelock(); break; case InternalState::Stopped: Q_EMIT stopped(); releaseWakelock(); break; case InternalState::SuspendingWaitSession: case InternalState::SuspendingWaitProcess: // transitory states. leave as it is default: break; }; if (this->state() != oldPublicState) { Q_EMIT stateChanged(this->state()); } applyRequestedState(); } void Application::setFocused(bool focused) { qCDebug(QTMIR_APPLICATIONS) << "Application::setFocused - appId=" << appId() << "focused=" << focused; if (m_focused != focused) { m_focused = focused; Q_EMIT focusedChanged(focused); } } void Application::setProcessState(ProcessState newProcessState) { if (m_processState == newProcessState) { return; } m_processState = newProcessState; switch (m_processState) { case ProcessUnknown: // it would be a coding error Q_ASSERT(false); break; case ProcessRunning: if (m_state == InternalState::StoppedResumable) { setInternalState(InternalState::Starting); } break; case ProcessSuspended: if (m_state == InternalState::Closing) { // If we get a process suspension event while we're closing, resume the process. Q_EMIT resumeProcessRequested(); } else { setInternalState(InternalState::Suspended); } break; case ProcessFailed: // we assume the session always stop before the process Q_ASSERT(!m_session || m_session->state() == Session::Stopped); if (m_state == InternalState::Starting) { // that was way too soon. let it go away setInternalState(InternalState::Stopped); } else { Q_ASSERT(m_state == InternalState::Stopped || m_state == InternalState::StoppedResumable); } break; case ProcessStopped: // we assume the session always stop before the process Q_ASSERT(!m_session || m_session->state() == Session::Stopped); if (m_state == InternalState::Starting) { // that was way too soon. let it go away setInternalState(InternalState::Stopped); } else if (m_state == InternalState::StoppedResumable || m_state == InternalState::Closing) { // The application stopped nicely, likely closed itself. Thus not meant to be resumed later. setInternalState(InternalState::Stopped); } else { Q_ASSERT(m_state == InternalState::Stopped); } break; } applyRequestedState(); } void Application::suspend() { qCDebug(QTMIR_APPLICATIONS) << "Application::suspend - appId=" << appId(); Q_ASSERT(m_state == InternalState::Running); Q_ASSERT(m_session != nullptr); if (exemptFromLifecycle()) { // There's no need to keep the wakelock as the process is never suspended // and thus has no cleanup to perform when (for example) the display is // blanked. setInternalState(InternalState::RunningInBackground); } else { setInternalState(InternalState::SuspendingWaitSession); m_session->suspend(); } } void Application::resume() { qCDebug(QTMIR_APPLICATIONS) << "Application::resume - appId=" << appId(); if (m_state == InternalState::Suspended || m_state == InternalState::SuspendingWaitProcess) { setInternalState(InternalState::Running); Q_EMIT resumeProcessRequested(); if (m_processState == ProcessSuspended) { setProcessState(ProcessRunning); // should we wait for a resumed() signal? } m_session->resume(); } else if (m_state == InternalState::SuspendingWaitSession) { setInternalState(InternalState::Running); m_session->resume(); } else if (m_state == InternalState::RunningInBackground) { setInternalState(InternalState::Running); } } void Application::respawn() { qCDebug(QTMIR_APPLICATIONS) << "Application::respawn - appId=" << appId(); setInternalState(InternalState::Starting); Q_EMIT startProcessRequested(); } void Application::stop() { qCDebug(QTMIR_APPLICATIONS) << "Application::stop - appId=" << appId(); Q_EMIT stopProcessRequested(); } bool Application::isTouchApp() const { return m_desktopData->isTouchApp(); } bool Application::exemptFromLifecycle() const { return m_exemptFromLifecycle; } void Application::setExemptFromLifecycle(bool exemptFromLifecycle) { if (m_exemptFromLifecycle != exemptFromLifecycle) { // We don't adjust current suspension state, we only care about exempt // status going into a suspend. m_exemptFromLifecycle = exemptFromLifecycle; Q_EMIT exemptFromLifecycleChanged(m_exemptFromLifecycle); } } QString Application::longAppId() const { return m_longAppId; } Qt::ScreenOrientations Application::supportedOrientations() const { return m_supportedOrientations; } bool Application::rotatesWindowContents() const { return m_rotatesWindowContents; } SessionInterface* Application::session() const { return m_session; } void Application::acquireWakelock() const { if (appId() == "unity8-dash") return; m_sharedWakelock->acquire(this); } void Application::releaseWakelock() const { if (appId() == "unity8-dash") return; m_sharedWakelock->release(this); } void Application::onSessionStateChanged(Session::State sessionState) { switch (sessionState) { case Session::Starting: break; case Session::Running: if (m_state == InternalState::Starting) { setInternalState(InternalState::Running); } break; case Session::Suspending: break; case Session::Suspended: Q_ASSERT(m_state == InternalState::SuspendingWaitSession); setInternalState(InternalState::SuspendingWaitProcess); Q_EMIT suspendProcessRequested(); break; case Session::Stopped: if ((m_state == InternalState::SuspendingWaitProcess || m_state == InternalState::SuspendingWaitProcess) && m_processState != Application::ProcessFailed) { // Session stopped normally while we're waiting for suspension. doClose(); Q_EMIT resumeProcessRequested(); } else if (!canBeResumed() || m_state == InternalState::Starting || m_state == InternalState::Running || m_state == InternalState::Closing) { /* 1. application is not managed by upstart * 2. application is managed by upstart, but has stopped before it managed * to create a surface, we can assume it crashed on startup, and thus * cannot be resumed * 3. application is managed by upstart and is in foreground (i.e. has * Running state), if Mir reports the application disconnects, it * either crashed or stopped itself. * 4. We're expecting the application to stop after a close request */ setInternalState(InternalState::Stopped); } else { setInternalState(InternalState::StoppedResumable); } } } void Application::setCloseTimer(AbstractTimer *timer) { delete m_closeTimer; m_closeTimer = timer; m_closeTimer->setInterval(3000); m_closeTimer->setSingleShot(true); connect(m_closeTimer, &Timer::timeout, this, &Application::stop); } QSize Application::initialSurfaceSize() const { return m_initialSurfaceSize; } void Application::setInitialSurfaceSize(const QSize &size) { qCDebug(QTMIR_APPLICATIONS).nospace() << "Application::setInitialSurfaceSize - appId=" << appId() << " size=" << size; if (size != m_initialSurfaceSize) { m_initialSurfaceSize = size; Q_EMIT initialSurfaceSizeChanged(m_initialSurfaceSize); } } } // namespace qtmir ./src/modules/Unity/Application/objectlistmodel.h0000644000015600001650000000622612677054330022260 0ustar jenkinsjenkins/* * Copyright (C) 2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef OBJECTLISTMODEL_H #define OBJECTLISTMODEL_H // Qt #include namespace qtmir { template class ObjectListModel : public QAbstractListModel { public: ObjectListModel(QObject *parent = 0) : QAbstractListModel(parent) {} enum Roles { RoleModelData = Qt::UserRole, }; const QList& list() const { return m_items; } bool contains(TYPE* item) const { return m_items.contains(item); } void insert(uint index, TYPE* item) { index = qMin(index, (uint)m_items.count()); int existingIndex = m_items.indexOf(item); if (existingIndex != -1) { move(existingIndex, qMin(index, (uint)(m_items.count()-1))); } else { beginInsertRows(QModelIndex(), index, index); m_items.insert(index, item); endInsertRows(); } } void remove(TYPE* item) { int existingIndex = m_items.indexOf(item); if (existingIndex != -1) { beginRemoveRows(QModelIndex(), existingIndex, existingIndex); m_items.removeAt(existingIndex); endRemoveRows(); } } // from QAbstractItemModel int rowCount(const QModelIndex& = QModelIndex()) const override { return m_items.count(); } QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override { if (index.row() >= 0 && index.row() < m_items.count()) { if (role == RoleModelData) { TYPE *item = m_items.at(index.row()); return QVariant::fromValue(item); } } return QVariant(); } QHash roleNames() const override { QHash roleNames; roleNames.insert(RoleModelData, "modelData"); return roleNames; } protected: void move(int from, int to) { if (from == to) return; if (from >= 0 && from < m_items.size() && to >= 0 && to < m_items.size()) { QModelIndex parent; /* When moving an item down, the destination index needs to be incremented by one, as explained in the documentation: http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0)); m_items.move(from, to); endMoveRows(); } } QList m_items; }; } // namespace qtmir #endif // OBJECTLISTMODEL_H ./src/modules/Unity/Application/taskcontroller.h0000644000015600001650000000407112677054330022137 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * */ #ifndef QTMIR_TASK_CONTROLLER_H #define QTMIR_TASK_CONTROLLER_H #include #include #include #include namespace qtmir { class TaskController : public QObject { Q_OBJECT public: enum class Error { APPLICATION_CRASHED, APPLICATION_FAILED_TO_START }; TaskController(const TaskController&) = delete; virtual ~TaskController() = default; TaskController& operator=(const TaskController&) = delete; virtual pid_t primaryPidForAppId(const QString &appId) = 0; virtual bool appIdHasProcessId(const QString &appId, pid_t pid) = 0; virtual bool stop(const QString &appId) = 0; virtual bool start(const QString &appId, const QStringList &arguments) = 0; virtual bool suspend(const QString &appId) = 0; virtual bool resume(const QString &appId) = 0; virtual QFileInfo findDesktopFileForAppId(const QString &appId) const = 0; Q_SIGNALS: void processStarting(const QString &appId); void applicationStarted(const QString &appId); void processStopped(const QString &appId); void processSuspended(const QString &appId); void focusRequested(const QString &appId); void resumeRequested(const QString &appId); void processFailed(const QString &appId, TaskController::Error error); protected: TaskController() = default; }; } // namespace qtmir #endif // QTMIR_TASK_CONTROLLER_H ./src/modules/Unity/Application/sharedwakelock.h0000644000015600001650000000272412677054330022063 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Author: Gerry Boland */ #ifndef WAKELOCK_H #define WAKELOCK_H #include #include #include namespace qtmir { class Wakelock; class SharedWakelock : public QObject { Q_OBJECT Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) public: SharedWakelock(const QDBusConnection& connection = QDBusConnection::systemBus()); virtual ~SharedWakelock(); virtual bool enabled() const; virtual void acquire(const QObject *caller); Q_SLOT virtual void release(const QObject *caller); Q_SIGNALS: void enabledChanged(bool enabled); protected: QScopedPointer m_wakelock; QSet m_owners; private: Q_DISABLE_COPY(SharedWakelock) }; } // namespace qtmir #endif // WAKELOCK_H ./src/modules/Unity/Application/session.cpp0000644000015600001650000003126612677054330021115 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // local #include "application.h" #include "debughelpers.h" #include "session.h" #include "mirsurfacemanager.h" #include "mirsurfaceitem.h" // mirserver #include "logging.h" // mir #include #include #include // Qt #include #include namespace ms = mir::scene; using unity::shell::application::ApplicationInfoInterface; namespace qtmir { namespace { const char *sessionStateToString(SessionInterface::State state) { switch (state) { case SessionInterface::Starting: return "starting"; case SessionInterface::Running: return "running"; case SessionInterface::Suspending: return "suspending"; case SessionInterface::Suspended: return "suspended"; case SessionInterface::Stopped: return "stopped"; default: return "???"; } } } Session::Session(const std::shared_ptr& session, const std::shared_ptr& promptSessionManager, QObject *parent) : SessionInterface(parent) , m_session(session) , m_application(nullptr) , m_parentSession(nullptr) , m_children(new SessionModel(this)) , m_fullscreen(false) , m_state(State::Starting) , m_live(true) , m_released(false) , m_suspendTimer(new QTimer(this)) , m_promptSessionManager(promptSessionManager) { qCDebug(QTMIR_SESSIONS) << "Session::Session() " << this->name(); QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); m_suspendTimer->setSingleShot(true); connect(m_suspendTimer, &QTimer::timeout, this, &Session::doSuspend); } Session::~Session() { qCDebug(QTMIR_SESSIONS) << "Session::~Session() " << name(); stopPromptSessions(); QList children(m_children->list()); for (SessionInterface* child : children) { delete child; } if (m_parentSession) { m_parentSession->removeChildSession(this); } if (m_application) { m_application->setSession(nullptr); } delete m_children; m_children = nullptr; } void Session::doSuspend() { Q_ASSERT(m_state == Session::Suspending); auto surfaceList = m_surfaces.list(); if (surfaceList.empty()) { qCDebug(QTMIR_SESSIONS) << "Application::suspend - no surface to call stopFrameDropper() on!"; } else { for (int i = 0; i < surfaceList.count(); ++i) { surfaceList[i]->stopFrameDropper(); } } setState(Suspended); } void Session::release() { qCDebug(QTMIR_SESSIONS) << "Session::release " << name(); m_released = true; if (m_state == Stopped) { deleteLater(); } } QString Session::name() const { return QString::fromStdString(m_session->name()); } std::shared_ptr Session::session() const { return m_session; } ApplicationInfoInterface* Session::application() const { return m_application; } const ObjectListModel* Session::surfaces() const { return &m_surfaces; } SessionInterface* Session::parentSession() const { return m_parentSession; } Session::State Session::state() const { return m_state; } void Session::setState(State state) { if (m_state == state) { return; } qCDebug(QTMIR_SESSIONS) << "Session::setState - session=" << name() << "state=" << sessionStateToString(state); if (m_state == Suspending) { m_suspendTimer->stop(); } m_state = state; switch (m_state) { case Starting: case Running: break; case Suspending: m_suspendTimer->start(1500); break; case Suspended: case Stopped: break; } Q_EMIT stateChanged(m_state); } bool Session::fullscreen() const { return m_fullscreen; } bool Session::live() const { return m_live; } void Session::setApplication(ApplicationInfoInterface* application) { if (m_application == application) return; m_application = static_cast(application); Q_EMIT applicationChanged(application); } void Session::registerSurface(MirSurfaceInterface *newSurface) { qCDebug(QTMIR_SESSIONS) << "Session::resgisterSurface - session=" << name() << "surface=" << newSurface; // Only notify QML of surface creation once it has drawn its first frame. if (newSurface->isFirstFrameDrawn()) { appendSurface(newSurface); } else { connect(newSurface, &MirSurfaceInterface::firstFrameDrawn, this, [this, newSurface]() { this->appendSurface(newSurface); }); } } void Session::appendSurface(MirSurfaceInterface *newSurface) { qCDebug(QTMIR_SESSIONS) << "Session::appendSurface - session=" << name() << "surface=" << newSurface; connect(newSurface, &MirSurfaceInterface::stateChanged, this, &Session::updateFullscreenProperty); m_surfaces.insert(m_surfaces.rowCount(), newSurface); Q_EMIT lastSurfaceChanged(newSurface); if (m_state == Starting) { setState(Running); } updateFullscreenProperty(); } void Session::removeSurface(MirSurfaceInterface* surface) { qCDebug(QTMIR_SESSIONS) << "Session::removeSurface - session=" << name() << "surface=" << surface; surface->disconnect(this); if (m_surfaces.contains(surface)) { bool lastSurfaceWasRemoved = lastSurface() == surface; m_surfaces.remove(surface); if (lastSurfaceWasRemoved) { Q_EMIT lastSurfaceChanged(lastSurface()); } } updateFullscreenProperty(); } void Session::updateFullscreenProperty() { if (m_surfaces.rowCount() > 0) { // TODO: Figure out something better setFullscreen(lastSurface()->state() == Mir::FullscreenState); } else { // Keep the current value of the fullscreen property until we get a new // surface } } void Session::setFullscreen(bool fullscreen) { qCDebug(QTMIR_SESSIONS) << "Session::setFullscreen - session=" << this << "fullscreen=" << fullscreen; if (m_fullscreen != fullscreen) { m_fullscreen = fullscreen; Q_EMIT fullscreenChanged(m_fullscreen); } } void Session::suspend() { qCDebug(QTMIR_SESSIONS) << "Session::suspend - session=" << this << "state=" << sessionStateToString(m_state); if (m_state == Running) { session()->set_lifecycle_state(mir_lifecycle_state_will_suspend); m_suspendTimer->start(1500); foreachPromptSession([this](const std::shared_ptr& promptSession) { m_promptSessionManager->suspend_prompt_session(promptSession); }); foreachChildSession([](SessionInterface* session) { session->suspend(); }); setState(Suspending); } } void Session::resume() { qCDebug(QTMIR_SESSIONS) << "Session::resume - session=" << this << "state=" << sessionStateToString(m_state); if (m_state == Suspending || m_state == Suspended) { doResume(); } } void Session::doResume() { if (m_state == Suspended) { Q_ASSERT(m_surfaces.rowCount() > 0); auto surfaceList = m_surfaces.list(); for (int i = 0; i < surfaceList.count(); ++i) { surfaceList[i]->startFrameDropper(); } } session()->set_lifecycle_state(mir_lifecycle_state_resumed); foreachPromptSession([this](const std::shared_ptr& promptSession) { m_promptSessionManager->resume_prompt_session(promptSession); }); foreachChildSession([](SessionInterface* session) { session->resume(); }); setState(Running); } void Session::close() { qCDebug(QTMIR_SESSIONS) << "Session::close - " << name(); if (m_state == Stopped) return; auto surfaceList = m_surfaces.list(); for (int i = 0; i < surfaceList.count(); ++i) { surfaceList[i]->close(); } } void Session::stop() { qCDebug(QTMIR_SESSIONS) << "Session::stop - " << name(); if (m_state != Stopped) { stopPromptSessions(); { auto surfaceList = m_surfaces.list(); for (int i = 0; i < surfaceList.count(); ++i) { surfaceList[i]->stopFrameDropper(); } } foreachChildSession([](SessionInterface* session) { session->stop(); }); setState(Stopped); if (m_released) { deleteLater(); } } } void Session::setLive(const bool live) { if (m_live != live) { qCDebug(QTMIR_SESSIONS) << "Session::setLive - " << name() << "live=" << live; m_live = live; Q_EMIT liveChanged(m_live); if (!live) { setState(Stopped); if (m_released) { deleteLater(); } } } } void Session::setParentSession(Session* session) { if (m_parentSession == session || session == this) return; m_parentSession = session; Q_EMIT parentSessionChanged(session); } void Session::addChildSession(SessionInterface* session) { insertChildSession(m_children->rowCount(), session); } void Session::insertChildSession(uint index, SessionInterface* session) { qCDebug(QTMIR_SESSIONS) << "Session::insertChildSession - " << session->name() << " to " << name() << " @ " << index; static_cast(session)->setParentSession(this); m_children->insert(index, session); switch (m_state) { case Starting: case Running: session->resume(); break; case Suspending: case Suspended: session->suspend(); break; case Stopped: session->stop(); break; } } void Session::removeChildSession(SessionInterface* session) { qCDebug(QTMIR_SESSIONS) << "Session::removeChildSession - " << session->name() << " from " << name(); if (m_children->contains(session)) { m_children->remove(session); static_cast(session)->setParentSession(nullptr); } } void Session::foreachChildSession(std::function f) const { QList children(m_children->list()); for (SessionInterface* child : children) { f(child); } } SessionModel* Session::childSessions() const { return m_children; } void Session::appendPromptSession(const std::shared_ptr& promptSession) { qCDebug(QTMIR_SESSIONS) << "Session::appendPromptSession session=" << name() << "promptSession=" << (promptSession ? promptSession.get() : nullptr); m_promptSessions.append(promptSession); } void Session::removePromptSession(const std::shared_ptr& promptSession) { qCDebug(QTMIR_SESSIONS) << "Session::removePromptSession session=" << name() << "promptSession=" << (promptSession ? promptSession.get() : nullptr); m_promptSessions.removeAll(promptSession); } void Session::stopPromptSessions() { QList children(m_children->list()); for (SessionInterface* child : children) { static_cast(child)->stopPromptSessions(); } QList> copy(m_promptSessions); QListIterator> it(copy); for ( it.toBack(); it.hasPrevious(); ) { std::shared_ptr promptSession = it.previous(); qCDebug(QTMIR_SESSIONS) << "Session::stopPromptSessions - promptSession=" << promptSession.get(); m_promptSessionManager->stop_prompt_session(promptSession); } } std::shared_ptr Session::activePromptSession() const { if (m_promptSessions.count() > 0) return m_promptSessions.back(); return nullptr; } void Session::foreachPromptSession(std::function&)> f) const { for (std::shared_ptr promptSession : m_promptSessions) { f(promptSession); } } MirSurfaceInterface* Session::lastSurface() const { if (m_surfaces.rowCount() > 0) { return m_surfaces.list().last(); } else { return nullptr; } } } // namespace qtmir ./src/modules/Unity/Application/desktopfilereader.cpp0000644000015600001650000002167712677054330023133 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // local #include "desktopfilereader.h" #include "gscopedpointer.h" #include "logging.h" // Qt #include #include // GIO #include namespace qtmir { DesktopFileReader* DesktopFileReader::Factory::createInstance(const QString &appId, const QFileInfo& fi) { return new DesktopFileReader(appId, fi); } typedef GObjectScopedPointer GAppInfoPointer; class DesktopFileReaderPrivate { public: DesktopFileReaderPrivate(DesktopFileReader *parent): q_ptr( parent ) {} QString getKey(const char *key) const { if (!loaded()) return QString(); return QString::fromUtf8(g_desktop_app_info_get_string((GDesktopAppInfo*)appInfo.data(), key)); } bool loaded() const { return !appInfo.isNull(); } DesktopFileReader * const q_ptr; Q_DECLARE_PUBLIC(DesktopFileReader) QString appId; QString file; GAppInfoPointer appInfo; // GAppInfo is actually implemented by GDesktopAppInfo }; DesktopFileReader::DesktopFileReader(const QString &appId, const QFileInfo &desktopFile) : d_ptr(new DesktopFileReaderPrivate(this)) { Q_D(DesktopFileReader); qCDebug(QTMIR_APPLICATIONS) << "Loading desktop file" << desktopFile.absoluteFilePath() << "for appId" << appId; d->appId = appId; d->file = desktopFile.absoluteFilePath(); d->appInfo.reset((GAppInfo*) g_desktop_app_info_new_from_filename(d->file.toUtf8().constData())); if (!d->loaded()) { if (!desktopFile.exists()) { qCWarning(QTMIR_APPLICATIONS) << "Desktop file for appId:" << appId << "at:" << d->file << "does not exist"; } else { qCWarning(QTMIR_APPLICATIONS) << "Desktop file for appId:" << appId << "at:" << d->file << "is not valid - check its syntax, and that the binary specified" << "by the Exec line is installed!"; } } } DesktopFileReader::~DesktopFileReader() { delete d_ptr; } QString DesktopFileReader::file() const { Q_D(const DesktopFileReader); return d->file; } QString DesktopFileReader::appId() const { Q_D(const DesktopFileReader); return d->appId; } QString DesktopFileReader::name() const { Q_D(const DesktopFileReader); if (!d->loaded()) return QString(); return QString::fromUtf8(g_app_info_get_name(d->appInfo.data())); } QString DesktopFileReader::comment() const { Q_D(const DesktopFileReader); if (!d->loaded()) return QString(); return QString::fromUtf8(g_app_info_get_description(d->appInfo.data())); } QString DesktopFileReader::icon() const { Q_D(const DesktopFileReader); return d->getKey("Icon"); } QString DesktopFileReader::exec() const { Q_D(const DesktopFileReader); if (!d->loaded()) return QString(); return QString::fromUtf8(g_app_info_get_commandline(d->appInfo.data())); } QString DesktopFileReader::path() const { Q_D(const DesktopFileReader); return d->getKey("Path"); } QString DesktopFileReader::stageHint() const { Q_D(const DesktopFileReader); return d->getKey("X-Ubuntu-StageHint"); } QString DesktopFileReader::splashTitle() const { Q_D(const DesktopFileReader); if (!d->loaded()) return QString(); /* Sadly GDesktopAppInfo only considers Name, GenericName, Comments and Keywords to be keys * which can have locale-specific entries. So we need to work to make X-Ubuntu-Splash-Title * locale-aware, by generating a locale-correct key name and seeing if that exists. If yes, * get the value and return it. Else fallback to the non-localized value. */ GDesktopAppInfo *info = (GDesktopAppInfo*)d->appInfo.data(); QLocale defaultLocale; QStringList locales = defaultLocale.uiLanguages(); QString keyTemplate("X-Ubuntu-Splash-Title[%1]"); for (QString locale: locales) { // Desktop files use local specifiers with underscore separators but Qt uses hyphens locale = locale.replace('-', '_'); const char* key = keyTemplate.arg(locale).toUtf8().constData(); if (g_desktop_app_info_has_key(info, key)) { return d->getKey(key); } } // Fallback to the non-localized string, if available return d->getKey("X-Ubuntu-Splash-Title"); } QString DesktopFileReader::splashImage() const { Q_D(const DesktopFileReader); return d->getKey("X-Ubuntu-Splash-Image"); } QString DesktopFileReader::splashShowHeader() const { Q_D(const DesktopFileReader); return d->getKey("X-Ubuntu-Splash-Show-Header"); } QString DesktopFileReader::splashColor() const { Q_D(const DesktopFileReader); return d->getKey("X-Ubuntu-Splash-Color"); } QString DesktopFileReader::splashColorHeader() const { Q_D(const DesktopFileReader); return d->getKey("X-Ubuntu-Splash-Color-Header"); } QString DesktopFileReader::splashColorFooter() const { Q_D(const DesktopFileReader); return d->getKey("X-Ubuntu-Splash-Color-Footer"); } Qt::ScreenOrientations DesktopFileReader::supportedOrientations() const { Q_D(const DesktopFileReader); Qt::ScreenOrientations result; if (!parseOrientations(d->getKey("X-Ubuntu-Supported-Orientations"), result)) { qCWarning(QTMIR_APPLICATIONS) << d->file << "has an invalid X-Ubuntu-Supported-Orientations entry."; } return result; } bool DesktopFileReader::rotatesWindowContents() const { Q_D(const DesktopFileReader); bool result; if (!parseBoolean(d->getKey("X-Ubuntu-Rotates-Window-Contents"), result)) { qCWarning(QTMIR_APPLICATIONS) << d->file << "has an invalid X-Ubuntu-Rotates-Window-Contents entry."; } return result; } bool DesktopFileReader::isTouchApp() const { Q_D(const DesktopFileReader); bool result; if (!parseBoolean(d->getKey("X-Ubuntu-Touch"), result)) { qCWarning(QTMIR_APPLICATIONS) << d->file << "has an invalid X-Ubuntu-Touch entry."; } return result; } bool DesktopFileReader::parseOrientations(const QString &rawString, Qt::ScreenOrientations &result) { // Default to all orientations result = Qt::PortraitOrientation | Qt::LandscapeOrientation | Qt::InvertedPortraitOrientation | Qt::InvertedLandscapeOrientation; if (rawString.isEmpty()) { return true; } Qt::ScreenOrientations parsedOrientations = 0; bool ok = true; QStringList orientationsList = rawString .simplified() .replace(QChar(','), ";") .remove(QChar(' ')) .remove(QChar('-')) .remove(QChar('_')) .toLower() .split(";"); for (int i = 0; i < orientationsList.count() && ok; ++i) { const QString &orientationString = orientationsList.at(i); if (orientationString.isEmpty()) { // skip it continue; } if (orientationString == "portrait") { parsedOrientations |= Qt::PortraitOrientation; } else if (orientationString == "landscape") { parsedOrientations |= Qt::LandscapeOrientation; } else if (orientationString == "invertedportrait") { parsedOrientations |= Qt::InvertedPortraitOrientation; } else if (orientationString == "invertedlandscape") { parsedOrientations |= Qt::InvertedLandscapeOrientation; } else if (orientationsList.count() == 1 && orientationString == "primary") { // Special case: primary orientation must be alone // There's no sense in supporting primary orientation + other orientations // like "primary,landscape" parsedOrientations = Qt::PrimaryOrientation; } else { ok = false; } } if (ok) { result = parsedOrientations; } return ok; } bool DesktopFileReader::parseBoolean(const QString &rawString, bool &result) { QString cookedString = rawString.trimmed().toLower(); result = cookedString == "y" || cookedString == "1" || cookedString == "yes" || cookedString == "true"; return result || rawString.isEmpty() || cookedString == "n" || cookedString == "0" || cookedString == "no" || cookedString == "false"; } bool DesktopFileReader::loaded() const { Q_D(const DesktopFileReader); return d->loaded(); } } // namespace qtmir ./src/modules/Unity/Application/mirsurfaceitem.h0000644000015600001650000001224112677054330022106 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIRSURFACEITEM_H #define MIRSURFACEITEM_H #include // Qt #include #include // Unity API #include #include "mirsurfaceinterface.h" #include "session_interface.h" namespace qtmir { class MirSurfaceManager; class QSGMirSurfaceNode; class MirTextureProvider; class MirSurfaceItem : public unity::shell::application::MirSurfaceItemInterface { Q_OBJECT public: explicit MirSurfaceItem(QQuickItem *parent = 0); virtual ~MirSurfaceItem(); //////// // MirSurfaceItemInterface Mir::Type type() const override; QString name() const override; bool live() const override; Mir::ShellChrome shellChrome() const override; Mir::State surfaceState() const override; void setSurfaceState(Mir::State) override; Mir::OrientationAngle orientationAngle() const override; void setOrientationAngle(Mir::OrientationAngle angle) override; unity::shell::application::MirSurfaceInterface* surface() const override; void setSurface(unity::shell::application::MirSurfaceInterface*) override; bool consumesInput() const override; void setConsumesInput(bool value) override; int surfaceWidth() const override; void setSurfaceWidth(int value) override; int surfaceHeight() const override; void setSurfaceHeight(int value) override; FillMode fillMode() const override; void setFillMode(FillMode value) override; //////// // QQuickItem bool isTextureProvider() const override { return true; } QSGTextureProvider *textureProvider() const override; //////// // own API // to allow easy touch event injection from tests bool processTouchEvent(int eventType, ulong timestamp, Qt::KeyboardModifiers modifiers, const QList &touchPoints, Qt::TouchPointStates touchPointStates); public Q_SLOTS: // Called by QQuickWindow from the rendering thread void invalidateSceneGraph(); protected: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void hoverEnterEvent(QHoverEvent *event) override; void hoverLeaveEvent(QHoverEvent *event) override; void hoverMoveEvent(QHoverEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; void touchEvent(QTouchEvent *event) override; QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) override; void releaseResources() override; private Q_SLOTS: void scheduleMirSurfaceSizeUpdate(); void updateMirSurfaceSize(); void updateMirSurfaceFocus(bool focused); void updateMirSurfaceVisibility(); void onActualSurfaceSizeChanged(const QSize &size); void onCompositorSwappedBuffers(); void onWindowChanged(QQuickWindow *window); private: void ensureTextureProvider(); bool hasTouchInsideUbuntuKeyboard(const QList &touchPoints); bool isMouseInsideUbuntuKeyboard(const QMouseEvent *event); QString appId() const; void endCurrentTouchSequence(ulong timestamp); void validateAndDeliverTouchEvent(int eventType, ulong timestamp, Qt::KeyboardModifiers modifiers, const QList &touchPoints, Qt::TouchPointStates touchPointStates); MirSurfaceInterface* m_surface; QQuickWindow* m_window; QMutex m_mutex; MirTextureProvider *m_textureProvider; QTimer m_updateMirSurfaceSizeTimer; class TouchEvent { public: TouchEvent &operator= (const QTouchEvent &qtEvent) { type = qtEvent.type(); timestamp = qtEvent.timestamp(); modifiers = qtEvent.modifiers(); touchPoints = qtEvent.touchPoints(); touchPointStates = qtEvent.touchPointStates(); return *this; } void updateTouchPointStatesAndType(); int type; ulong timestamp; Qt::KeyboardModifiers modifiers; QList touchPoints; Qt::TouchPointStates touchPointStates; } *m_lastTouchEvent; unsigned int *m_lastFrameNumberRendered; int m_surfaceWidth; int m_surfaceHeight; Mir::OrientationAngle *m_orientationAngle; bool m_consumesInput; FillMode m_fillMode; }; } // namespace qtmir #endif // MIRSURFACEITEM_H ./src/modules/Unity/Application/timer.cpp0000644000015600001650000000431112677054330020541 0ustar jenkinsjenkins/* * Copyright (C) 2015 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * 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 "timer.h" using namespace qtmir; Timer::Timer(QObject *parent) : AbstractTimer(parent) { m_timer.setSingleShot(false); connect(&m_timer, &QTimer::timeout, this, &AbstractTimer::timeout); } int Timer::interval() const { return m_timer.interval(); } void Timer::setInterval(int msecs) { m_timer.setInterval(msecs); } void Timer::start() { m_timer.start(); AbstractTimer::start(); } void Timer::stop() { m_timer.stop(); AbstractTimer::stop(); } bool Timer::isSingleShot() const { return m_timer.isSingleShot(); } void Timer::setSingleShot(bool value) { m_timer.setSingleShot(value); } /////////////////////////////////// FakeTimer ////////////////////////////////// FakeTimer::FakeTimer(const SharedTimeSource &timeSource, QObject *parent) : qtmir::AbstractTimer(parent) , m_interval(0) , m_singleShot(false) , m_timeSource(timeSource) { } void FakeTimer::update() { if (!isRunning()) { return; } if (m_nextTimeoutTime <= m_timeSource->msecsSinceReference()) { if (isSingleShot()) { stop(); } else { m_nextTimeoutTime += interval(); } Q_EMIT timeout(); } } void FakeTimer::start() { AbstractTimer::start(); m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval(); } int FakeTimer::interval() const { return m_interval; } void FakeTimer::setInterval(int msecs) { m_interval = msecs; } bool FakeTimer::isSingleShot() const { return m_singleShot; } void FakeTimer::setSingleShot(bool value) { m_singleShot = value; } ./src/modules/Unity/Application/proc_info.h0000644000015600001650000000245212677054330021050 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // Process Information #ifndef PROC_INFO_H #define PROC_INFO_H // std #include // boost #include // Qt #include #include class QString; namespace qtmir { class ProcInfo { public: class CommandLine { public: QByteArray m_command; bool startsWith(const char* prefix) const; bool contains(const char* prefix) const; QString getParameter(const char* name) const; QStringList asStringList() const; }; virtual std::unique_ptr commandLine(pid_t pid); virtual ~ProcInfo(); }; } // namespace qtmir #endif ./src/modules/Unity/Application/application_manager.cpp0000644000015600001650000010223612677054330023423 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // local #include "application_manager.h" #include "application.h" #include "desktopfilereader.h" #include "dbuswindowstack.h" #include "session.h" #include "sharedwakelock.h" #include "proc_info.h" #include "upstart/taskcontroller.h" #include "tracepoints.h" // generated from tracepoints.tp #include "settings.h" // mirserver #include "mirserver.h" #include "nativeinterface.h" #include "sessionlistener.h" #include "sessionauthorizer.h" #include "logging.h" #include // mir #include #include #include #include #include // Qt #include #include #include #include // std #include namespace ms = mir::scene; Q_LOGGING_CATEGORY(QTMIR_APPLICATIONS, "qtmir.applications") using namespace unity::shell::application; namespace qtmir { namespace { // FIXME: To be removed once shell has fully adopted short appIds!! QString toShortAppIdIfPossible(const QString &appId) { QRegExp longAppIdMask("[a-z0-9][a-z0-9+.-]+_[a-zA-Z0-9+.-]+_[0-9][a-zA-Z0-9.+:~-]*"); if (longAppIdMask.exactMatch(appId)) { qWarning() << "WARNING: long App ID encountered:" << appId; // input string a long AppId, chop the version string off the end QStringList parts = appId.split("_"); return QString("%1_%2").arg(parts.first()).arg(parts.at(1)); } return appId; } void connectToSessionListener(ApplicationManager *manager, SessionListener *listener) { QObject::connect(listener, &SessionListener::sessionStarting, manager, &ApplicationManager::onSessionStarting); QObject::connect(listener, &SessionListener::sessionStopping, manager, &ApplicationManager::onSessionStopping); QObject::connect(listener, &SessionListener::sessionCreatedSurface, manager, &ApplicationManager::onSessionCreatedSurface); QObject::connect(listener, &SessionListener::sessionDestroyingSurface, manager, &ApplicationManager::onSessionDestroyingSurface); } void connectToSessionAuthorizer(ApplicationManager *manager, SessionAuthorizer *authorizer) { QObject::connect(authorizer, &SessionAuthorizer::requestAuthorizationForSession, manager, &ApplicationManager::authorizeSession, Qt::BlockingQueuedConnection); } void connectToTaskController(ApplicationManager *manager, TaskController *controller) { QObject::connect(controller, &TaskController::processStarting, manager, &ApplicationManager::onProcessStarting); QObject::connect(controller, &TaskController::processStopped, manager, &ApplicationManager::onProcessStopped); QObject::connect(controller, &TaskController::processSuspended, manager, &ApplicationManager::onProcessSuspended); QObject::connect(controller, &TaskController::processFailed, manager, &ApplicationManager::onProcessFailed); QObject::connect(controller, &TaskController::focusRequested, manager, &ApplicationManager::onFocusRequested); QObject::connect(controller, &TaskController::resumeRequested, manager, &ApplicationManager::onResumeRequested); } } // namespace ApplicationManager* ApplicationManager::Factory::Factory::create() { NativeInterface *nativeInterface = dynamic_cast(QGuiApplication::platformNativeInterface()); if (!nativeInterface) { qCritical() << "ERROR: Unity.Application QML plugin requires use of the 'mirserver' QPA plugin"; QGuiApplication::quit(); return nullptr; } auto mirServer = nativeInterface->m_mirServer.lock(); SessionListener *sessionListener = static_cast(nativeInterface->nativeResourceForIntegration("SessionListener")); SessionAuthorizer *sessionAuthorizer = static_cast(nativeInterface->nativeResourceForIntegration("SessionAuthorizer")); QSharedPointer taskController(new upstart::TaskController()); QSharedPointer fileReaderFactory(new DesktopFileReader::Factory()); QSharedPointer procInfo(new ProcInfo()); QSharedPointer sharedWakelock(new SharedWakelock); QSharedPointer settings(new Settings()); // FIXME: We should use a QSharedPointer to wrap this ApplicationManager object, which requires us // to use the data() method to pass the raw pointer to the QML engine. However the QML engine appears // to take ownership of the object, and deletes it when it wants to. This conflicts with the purpose // of the QSharedPointer, and a double-delete results. Trying QQmlEngine::setObjectOwnership on the // object no effect, which it should. Need to investigate why. ApplicationManager* appManager = new ApplicationManager( mirServer, taskController, sharedWakelock, fileReaderFactory, procInfo, settings ); connectToSessionListener(appManager, sessionListener); connectToSessionAuthorizer(appManager, sessionAuthorizer); connectToTaskController(appManager, taskController.data()); connect(mirServer->windowManager(), &MirWindowManager::sessionAboutToCreateSurface, appManager, &ApplicationManager::onSessionAboutToCreateSurface, Qt::BlockingQueuedConnection); // Emit signal to notify Upstart that Mir is ready to receive client connections // see http://upstart.ubuntu.com/cookbook/#expect-stop // FIXME: should not be qtmir's job, instead should notify the user of this library // that they should emit this signal, perhaps by posting an event to the // QMirServerApplication event loop when it comes up if (qgetenv("UNITY_MIR_EMITS_SIGSTOP") == "1") { raise(SIGSTOP); } return appManager; } ApplicationManager* ApplicationManager::singleton() { static ApplicationManager* instance; if (!instance) { Factory appFactory; instance = appFactory.create(); } return instance; } ApplicationManager::ApplicationManager( const QSharedPointer& mirServer, const QSharedPointer& taskController, const QSharedPointer& sharedWakelock, const QSharedPointer& desktopFileReaderFactory, const QSharedPointer& procInfo, const QSharedPointer& settings, QObject *parent) : ApplicationManagerInterface(parent) , m_mirServer(mirServer) , m_focusedApplication(nullptr) , m_dbusWindowStack(new DBusWindowStack(this)) , m_taskController(taskController) , m_desktopFileReaderFactory(desktopFileReaderFactory) , m_procInfo(procInfo) , m_sharedWakelock(sharedWakelock) , m_settings(settings) { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::ApplicationManager (this=%p)" << this; setObjectName("qtmir::ApplicationManager"); m_roleNames.insert(RoleSession, "session"); m_roleNames.insert(RoleFullscreen, "fullscreen"); } ApplicationManager::~ApplicationManager() { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::~ApplicationManager"; } int ApplicationManager::rowCount(const QModelIndex &parent) const { return !parent.isValid() ? m_applications.size() : 0; } QVariant ApplicationManager::data(const QModelIndex &index, int role) const { if (index.row() >= 0 && index.row() < m_applications.size()) { Application *application = m_applications.at(index.row()); switch (role) { case RoleAppId: return QVariant::fromValue(application->appId()); case RoleName: return QVariant::fromValue(application->name()); case RoleComment: return QVariant::fromValue(application->comment()); case RoleIcon: return QVariant::fromValue(application->icon()); case RoleStage: return QVariant::fromValue((int)application->stage()); case RoleState: return QVariant::fromValue((int)application->state()); case RoleFocused: return QVariant::fromValue(application->focused()); case RoleIsTouchApp: return QVariant::fromValue(application->isTouchApp()); case RoleExemptFromLifecycle: return QVariant::fromValue(application->exemptFromLifecycle()); case RoleSession: return QVariant::fromValue(application->session()); case RoleFullscreen: return QVariant::fromValue(application->fullscreen()); default: return QVariant(); } } else { return QVariant(); } } Application* ApplicationManager::get(int index) const { if (index < 0 || index >= m_applications.count()) { return nullptr; } return m_applications.at(index); } Application* ApplicationManager::findApplication(const QString &inputAppId) const { const QString appId = toShortAppIdIfPossible(inputAppId); for (Application *app : m_applications) { if (app->appId() == appId) { return app; } } return nullptr; } bool ApplicationManager::requestFocusApplication(const QString &inputAppId) { const QString appId = toShortAppIdIfPossible(inputAppId); qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::requestFocusApplication - appId=" << appId; Application *application = findApplication(appId); if (!application) { qDebug() << "No such running application with appId=" << appId; return false; } Q_EMIT focusRequested(appId); return true; } QString ApplicationManager::focusedApplicationId() const { if (m_focusedApplication) { return m_focusedApplication->appId(); } else { return QString(); } } bool ApplicationManager::focusApplication(const QString &inputAppId) { const QString appId = toShortAppIdIfPossible(inputAppId); qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::focusApplication - appId=" << appId; Application *application = findApplication(appId); if (!application) { qDebug() << "No such running application with appId=" << appId; return false; } if (m_focusedApplication) { m_focusedApplication->setFocused(false); } m_focusedApplication = application; m_focusedApplication->setFocused(true); move(m_applications.indexOf(application), 0); Q_EMIT focusedApplicationIdChanged(); m_dbusWindowStack->FocusedWindowChanged(0, application->appId(), application->stage()); return true; } void ApplicationManager::unfocusCurrentApplication() { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::unfocusCurrentApplication"; m_focusedApplication = nullptr; Q_EMIT focusedApplicationIdChanged(); } /** * @brief ApplicationManager::startApplication launches an application identified by an "application id" or appId. * * Note: due to an implementation detail, appIds come in two forms: * * long appId: $(click_package)_$(application)_$(version) * * short appId: $(click_package)_$(application) * It is expected that the shell uses _only_ short appIds (but long appIds are accepted by this method for legacy * reasons - but be warned, this ability will be removed) * * Unless stated otherwise, we always use short appIds in this API. * * @param inputAppId AppId of application to launch (long appId supported) * @param arguments Command line arguments to pass to the application to be launched * @return Pointer to Application object representing the launched process. If process already running, return nullptr */ Application* ApplicationManager::startApplication(const QString &inputAppId, const QStringList &arguments) { tracepoint(qtmir, startApplication); QString appId = toShortAppIdIfPossible(inputAppId); qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::startApplication - this=" << this << "appId" << qPrintable(appId); Application *application = findApplication(appId); if (application) { qWarning() << "ApplicationManager::startApplication - application appId=" << appId << " already exists"; return nullptr; } if (m_queuedStartApplications.contains(inputAppId)) { qWarning() << "ApplicationManager::startApplication - application appId=" << appId << " is queued to start"; return nullptr; } else { application = findClosingApplication(inputAppId); if (application) { m_queuedStartApplications.append(inputAppId); qWarning() << "ApplicationManager::startApplication - application appId=" << appId << " is closing. Queuing start"; connect(application, &QObject::destroyed, this, [this, application, inputAppId, arguments]() { m_queuedStartApplications.removeAll(inputAppId); // start the app. startApplication(inputAppId, arguments); }, Qt::QueuedConnection); // Queued so that we finish the app removal before starting again. return nullptr; } } if (!m_taskController->start(appId, arguments)) { qWarning() << "Upstart failed to start application with appId" << appId; return nullptr; } // The TaskController may synchroneously callback onProcessStarting, so check if application already added application = findApplication(appId); if (application) { application->setArguments(arguments); } else { application = new Application( m_sharedWakelock, m_desktopFileReaderFactory->createInstance(appId, m_taskController->findDesktopFileForAppId(appId)), arguments, this); if (!application->isValid()) { qWarning() << "Unable to instantiate application with appId" << appId; return nullptr; } add(application); } return application; } void ApplicationManager::onProcessStarting(const QString &appId) { tracepoint(qtmir, onProcessStarting); qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessStarting - appId=" << appId; Application *application = findApplication(appId); if (!application) { // then shell did not start this application, so ubuntu-app-launch must have - add to list application = new Application( m_sharedWakelock, m_desktopFileReaderFactory->createInstance(appId, m_taskController->findDesktopFileForAppId(appId)), QStringList(), this); if (!application->isValid()) { qWarning() << "Unable to instantiate application with appId" << appId; return; } add(application); Q_EMIT focusRequested(appId); } else { if (application->state() == Application::Stopped) { // url-dispatcher can relaunch apps which have been OOM-killed - AppMan must accept the newly spawned // application and focus it immediately (as user expects app to still be running). qCDebug(QTMIR_APPLICATIONS) << "Stopped application appId=" << appId << "is being resumed externally"; Q_EMIT focusRequested(appId); } else { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessStarting application already found with appId" << appId; } } application->setProcessState(Application::ProcessRunning); } /** * @brief ApplicationManager::stopApplication - stop a running application and remove from list * @param inputAppId * @return True if running application was stopped, false if application did not exist or could not be stopped */ bool ApplicationManager::stopApplication(const QString &inputAppId) { const QString appId = toShortAppIdIfPossible(inputAppId); qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::stopApplication - appId=" << appId; Application *application = findApplication(appId); if (!application) { qCritical() << "No such running application with appId" << appId; return false; } application->close(); remove(application); connect(application, &QObject::destroyed, this, [this, application](QObject*) { m_closingApplications.removeAll(application); }); m_closingApplications.append(application); return true; } void ApplicationManager::onProcessFailed(const QString &appId, TaskController::Error error) { // Applications fail if they fail to launch, crash or are killed. qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessFailed - appId=" << appId; Application *application = findApplication(appId); if (!application) { qWarning() << "ApplicationManager::onProcessFailed - upstart reports failure of application" << appId << "that AppManager is not managing"; return; } Q_UNUSED(error); // FIXME(greyback) upstart reports app that fully started up & crashes as failing during startup?? application->setProcessState(Application::ProcessFailed); application->setPid(0); } void ApplicationManager::onProcessStopped(const QString &appId) { tracepoint(qtmir, onProcessStopped); qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessStopped - appId=" << appId; Application *application = findApplication(appId); if (!application) { application = findClosingApplication(appId); } if (!application) { qDebug() << "ApplicationManager::onProcessStopped reports stop of appId=" << appId << "which AppMan is not managing, ignoring the event"; return; } // if an application gets killed, onProcessFailed is called first, followed by onProcessStopped. // we don't want to override what onProcessFailed already set. if (application->processState() != Application::ProcessFailed) { application->setProcessState(Application::ProcessStopped); application->setPid(0); } } void ApplicationManager::onProcessSuspended(const QString &appId) { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessSuspended - appId=" << appId; Application *application = findApplication(appId); if (!application) { qDebug() << "ApplicationManager::onProcessSuspended reports stop of appId=" << appId << "which AppMan is not managing, ignoring the event"; return; } application->setProcessState(Application::ProcessSuspended); } void ApplicationManager::onFocusRequested(const QString& appId) { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onFocusRequested - appId=" << appId; Q_EMIT focusRequested(appId); } void ApplicationManager::onResumeRequested(const QString& appId) { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onResumeRequested - appId=" << appId; Application *application = findApplication(appId); if (!application) { qCritical() << "ApplicationManager::onResumeRequested: No such running application" << appId; return; } // We interpret this as a focus request for a suspended app. // Shell will have this app resumed if it complies with the focus request if (application->state() == Application::Suspended) { Q_EMIT focusRequested(appId); } } void ApplicationManager::onAppDataChanged(const int role) { if (sender()) { Application *application = static_cast(sender()); QModelIndex appIndex = findIndex(application); Q_EMIT dataChanged(appIndex, appIndex, QVector() << role); qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onAppDataChanged: Received " << m_roleNames[role] << " update" << application->appId(); } else { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onAppDataChanged: Received " << m_roleNames[role] << " signal but application has disappeard."; } } void ApplicationManager::authorizeSession(const pid_t pid, bool &authorized) { tracepoint(qtmir, authorizeSession); authorized = false; //to be proven wrong qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::authorizeSession - pid=" << pid; for (Application *app : m_applications) { if (app->state() == Application::Starting) { tracepoint(qtmir, appIdHasProcessId_start); if (m_taskController->appIdHasProcessId(app->appId(), pid)) { app->setPid(pid); authorized = true; tracepoint(qtmir, appIdHasProcessId_end, 1); //found return; } tracepoint(qtmir, appIdHasProcessId_end, 0); // not found } } /* * Hack: Allow applications to be launched without being managed by upstart, where AppManager * itself manages processes executed with a "--desktop_file_hint=/path/to/desktopFile.desktop" * parameter attached. This exists until ubuntu-app-launch can notify shell any application is * and so shell should allow it. Also reads the --stage parameter to determine the desired stage */ std::unique_ptr info = m_procInfo->commandLine(pid); if (!info) { qWarning() << "ApplicationManager REJECTED connection from app with pid" << pid << "as unable to read the process command line"; return; } if (info->startsWith("maliit-server") || info->contains("qt5/libexec/QtWebProcess")) { authorized = true; return; } const QString desktopFileName = info->getParameter("--desktop_file_hint="); if (desktopFileName.isNull()) { qCritical() << "ApplicationManager REJECTED connection from app with pid" << pid << "as it was not launched by upstart, and no desktop_file_hint is specified"; return; } qCDebug(QTMIR_APPLICATIONS) << "Process supplied desktop_file_hint, loading:" << desktopFileName; // Guess appId from the desktop file hint const QString appId = toShortAppIdIfPossible(desktopFileName.split('/').last().remove(QRegExp(".desktop$"))); // FIXME: right now we support --desktop_file_hint=appId for historical reasons. So let's try that in // case we didn't get an existing .desktop file path DesktopFileReader* desktopData; if (QFileInfo::exists(desktopFileName)) { desktopData = m_desktopFileReaderFactory->createInstance(appId, QFileInfo(desktopFileName)); } else { qCDebug(QTMIR_APPLICATIONS) << "Unable to find file:" << desktopFileName << "so will search standard paths for one named" << appId << ".desktop"; desktopData = m_desktopFileReaderFactory->createInstance(appId, m_taskController->findDesktopFileForAppId(appId)); } if (!desktopData->loaded()) { delete desktopData; qCritical() << "ApplicationManager REJECTED connection from app with pid" << pid << "as the file specified by the desktop_file_hint argument could not be opened"; return; } // some naughty applications use a script to launch the actual application. Check for the // case where shell actually launched the script. Application *application = findApplication(desktopData->appId()); if (application && application->state() == Application::Starting) { qCDebug(QTMIR_APPLICATIONS) << "Process with pid" << pid << "appeared, attaching to existing entry" << "in application list with appId:" << application->appId(); delete desktopData; application->setPid(pid); authorized = true; return; } // if stage supplied in CLI, fetch that Application::Stage stage = Application::MainStage; QString stageParam = info->getParameter("--stage_hint="); if (stageParam == "side_stage") { stage = Application::SideStage; } qCDebug(QTMIR_APPLICATIONS) << "New process with pid" << pid << "appeared, adding new application to the" << "application list with appId:" << desktopData->appId(); QStringList arguments(info->asStringList()); application = new Application( m_sharedWakelock, desktopData, arguments, this); application->setPid(pid); application->setStage(stage); add(application); authorized = true; } void ApplicationManager::onSessionStarting(std::shared_ptr const& session) { Q_UNUSED(session); } void ApplicationManager::onSessionStopping(std::shared_ptr const& session) { Application* application = findApplicationWithSession(session); if (application) { m_dbusWindowStack->WindowDestroyed(0, application->appId()); } } void ApplicationManager::onSessionCreatedSurface(ms::Session const* session, std::shared_ptr const& surface) { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onSessionCreatedSurface - sessionName=" << session->name().c_str(); Q_UNUSED(surface); Application* application = findApplicationWithSession(session); if (application) { m_dbusWindowStack->WindowCreated(0, application->appId()); } } void ApplicationManager::onSessionDestroyingSurface(ms::Session const* session, std::shared_ptr const& surface) { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onSessionDestroyingSurface - sessionName=" << session->name().c_str(); Q_UNUSED(surface); Application* application = findApplicationWithSession(session); if (application && application->state() == Application::Running) { // If app in Running state but it destroys its surface, it's probably shutting down, // in which case, we can preempt it and remove it from the App list immediately. // FIXME: this is not desktop application friendly, but resolves issue where trust-prompt // helpers take a long time to shut down, but destroys their surface quickly. remove(application); application->deleteLater(); } } Application* ApplicationManager::findApplicationWithSession(const std::shared_ptr &session) { return findApplicationWithSession(session.get()); } Application* ApplicationManager::findApplicationWithSession(const ms::Session *session) { if (!session) return nullptr; return findApplicationWithPid(session->process_id()); } Application* ApplicationManager::findApplicationWithPid(const pid_t pid) { if (pid <= 0) return nullptr; for (Application *app : m_applications) { if (app->m_pid == pid) { return app; } } return nullptr; } void ApplicationManager::add(Application* application) { Q_ASSERT(application != nullptr); qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::add - appId=" << application->appId(); connect(application, &Application::fullscreenChanged, this, [this](bool) { onAppDataChanged(RoleFullscreen); }); connect(application, &Application::focusedChanged, this, [this](bool) { onAppDataChanged(RoleFocused); }); connect(application, &Application::stateChanged, this, [this](Application::State) { onAppDataChanged(RoleState); }); connect(application, &Application::stageChanged, this, [this](Application::Stage) { onAppDataChanged(RoleStage); }); QString appId = application->appId(); QString longAppId = application->longAppId(); QStringList arguments = application->arguments(); // The connection is queued as a workaround an issue in the PhoneStage animation that // happens when you tap on a killed app in the spread to bring it to foreground, causing // a Application::respawn() to take place. // In any case, it seems like in general QML works better when don't do too many things // in the same event loop iteration. connect(application, &Application::startProcessRequested, this, [=]() { m_taskController->start(appId, arguments); }, Qt::QueuedConnection); connect(application, &Application::stopProcessRequested, this, [=]() { if (!m_taskController->stop(application->longAppId()) && application->pid() > 0) { qWarning() << "FAILED to ask Upstart to stop application with appId" << appId << "Sending SIGTERM to process:" << appId; kill(application->pid(), SIGTERM); application->setProcessState(Application::ProcessStopped); } }); connect(application, &Application::suspendProcessRequested, this, [=]() { m_taskController->suspend(longAppId); } ); connect(application, &Application::resumeProcessRequested, this, [=]() { m_taskController->resume(longAppId); } ); connect(application, &Application::stopped, this, [=]() { remove(application); application->deleteLater(); }); beginInsertRows(QModelIndex(), m_applications.count(), m_applications.count()); m_applications.append(application); endInsertRows(); Q_EMIT countChanged(); Q_EMIT applicationAdded(application->appId()); if (m_applications.size() == 1) { Q_EMIT emptyChanged(); } } void ApplicationManager::remove(Application *application) { Q_ASSERT(application != nullptr); qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::remove - appId=" << application->appId(); disconnect(application, &Application::fullscreenChanged, this, 0); disconnect(application, &Application::focusedChanged, this, 0); disconnect(application, &Application::stateChanged, this, 0); disconnect(application, &Application::stageChanged, this, 0); int i = m_applications.indexOf(application); if (i != -1) { beginRemoveRows(QModelIndex(), i, i); m_applications.removeAt(i); endRemoveRows(); Q_EMIT applicationRemoved(application->appId()); Q_EMIT countChanged(); if (i == 0) { Q_EMIT emptyChanged(); } } if (application == m_focusedApplication) { m_focusedApplication = nullptr; Q_EMIT focusedApplicationIdChanged(); } } void ApplicationManager::move(int from, int to) { qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::move - from=" << from << "to=" << to; if (from == to) return; if (from >= 0 && from < m_applications.size() && to >= 0 && to < m_applications.size()) { QModelIndex parent; /* When moving an item down, the destination index needs to be incremented by one, as explained in the documentation: http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#beginMoveRows */ beginMoveRows(parent, from, from, parent, to + (to > from ? 1 : 0)); m_applications.move(from, to); endMoveRows(); } qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::move after " << toString(); } QModelIndex ApplicationManager::findIndex(Application* application) { for (int i = 0; i < m_applications.size(); ++i) { if (m_applications.at(i) == application) { return index(i); } } return QModelIndex(); } QString ApplicationManager::toString() const { QString result; for (int i = 0; i < m_applications.count(); ++i) { if (i > 0) { result.append(","); } result.append(m_applications.at(i)->appId()); } return result; } Application *ApplicationManager::findClosingApplication(const QString &inputAppId) const { const QString appId = toShortAppIdIfPossible(inputAppId); for (Application *app : m_closingApplications) { if (app->appId() == appId) { return app; } } return nullptr; } void ApplicationManager::onSessionAboutToCreateSurface( const std::shared_ptr &session, int type, QSize &size) { if (type == mir_surface_type_normal) { Application* application = findApplicationWithSession(session); if (application) { qCDebug(QTMIR_APPLICATIONS).nospace() << "ApplicationManager::onSessionAboutToCreateSurface appId=" << application->appId(); size = application->initialSurfaceSize(); } else { qCDebug(QTMIR_APPLICATIONS).nospace() << "ApplicationManager::onSessionAboutToCreateSurface unknown app"; } } else { qCDebug(QTMIR_APPLICATIONS).nospace() << "ApplicationManager::onSessionAboutToCreateSurface type=" << type << " NOOP"; } } } // namespace qtmir ./src/modules/Unity/Application/mirsurface.cpp0000644000015600001650000006231412677054330021570 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mirsurface.h" #include "timestamp.h" // from common dir #include // mirserver #include // Mir #include #include #include #include // mirserver #include // Qt #include using namespace qtmir; #define DEBUG_MSG qCDebug(QTMIR_SURFACES).nospace() << "MirSurface[" << (void*)this << "," << appId() <<"]::" << __func__ namespace { // Would be better if QMouseEvent had nativeModifiers MirInputEventModifiers getMirModifiersFromQt(Qt::KeyboardModifiers mods) { MirInputEventModifiers m_mods = mir_input_event_modifier_none; if (mods & Qt::ShiftModifier) m_mods |= mir_input_event_modifier_shift; if (mods & Qt::ControlModifier) m_mods |= mir_input_event_modifier_ctrl; if (mods & Qt::AltModifier) m_mods |= mir_input_event_modifier_alt; if (mods & Qt::MetaModifier) m_mods |= mir_input_event_modifier_meta; return m_mods; } MirPointerButtons getMirButtonsFromQt(Qt::MouseButtons buttons) { MirPointerButtons result = 0; if (buttons & Qt::LeftButton) result |= mir_pointer_button_primary; if (buttons & Qt::RightButton) result |= mir_pointer_button_secondary; if (buttons & Qt::MiddleButton) result |= mir_pointer_button_tertiary; if (buttons & Qt::BackButton) result |= mir_pointer_button_back; if (buttons & Qt::ForwardButton) result |= mir_pointer_button_forward; return result; } mir::EventUPtr makeMirEvent(QMouseEvent *qtEvent, MirPointerAction action) { auto timestamp = uncompressTimestamp(qtmir::Timestamp(qtEvent->timestamp())); auto modifiers = getMirModifiersFromQt(qtEvent->modifiers()); auto buttons = getMirButtonsFromQt(qtEvent->buttons()); return mir::events::make_event(0 /*DeviceID */, timestamp, std::vector{} /* cookie */, modifiers, action, buttons, qtEvent->x(), qtEvent->y(), 0, 0, 0, 0); } mir::EventUPtr makeMirEvent(QHoverEvent *qtEvent, MirPointerAction action) { auto timestamp = uncompressTimestamp(qtmir::Timestamp(qtEvent->timestamp())); MirPointerButtons buttons = 0; return mir::events::make_event(0 /*DeviceID */, timestamp, std::vector{} /* cookie */, mir_input_event_modifier_none, action, buttons, qtEvent->posF().x(), qtEvent->posF().y(), 0, 0, 0, 0); } mir::EventUPtr makeMirEvent(QWheelEvent *qtEvent) { auto timestamp = std::chrono::duration_cast(std::chrono::milliseconds(qtEvent->timestamp())); auto modifiers = getMirModifiersFromQt(qtEvent->modifiers()); auto buttons = getMirButtonsFromQt(qtEvent->buttons()); return mir::events::make_event(0 /*DeviceID */, timestamp, std::vector{} /* cookie */, modifiers, mir_pointer_action_motion, buttons, qtEvent->x(), qtEvent->y(), qtEvent->angleDelta().x(), qtEvent->angleDelta().y(), 0, 0); } mir::EventUPtr makeMirEvent(QKeyEvent *qtEvent) { MirKeyboardAction action = mir_keyboard_action_down; switch (qtEvent->type()) { case QEvent::KeyPress: action = mir_keyboard_action_down; break; case QEvent::KeyRelease: action = mir_keyboard_action_up; break; default: break; } if (qtEvent->isAutoRepeat()) action = mir_keyboard_action_repeat; return mir::events::make_event(0 /* DeviceID */, uncompressTimestamp(qtmir::Timestamp(qtEvent->timestamp())), std::vector{} /* cookie */, action, qtEvent->nativeVirtualKey(), qtEvent->nativeScanCode(), qtEvent->nativeModifiers()); } mir::EventUPtr makeMirEvent(Qt::KeyboardModifiers qmods, const QList &qtTouchPoints, Qt::TouchPointStates /* qtTouchPointStates */, ulong qtTimestamp) { auto modifiers = getMirModifiersFromQt(qmods); auto ev = mir::events::make_event(0, uncompressTimestamp(qtmir::Timestamp(qtTimestamp)), std::vector{} /* cookie */, modifiers); for (int i = 0; i < qtTouchPoints.count(); ++i) { auto touchPoint = qtTouchPoints.at(i); auto id = touchPoint.id(); MirTouchAction action = mir_touch_action_change; if (touchPoint.state() == Qt::TouchPointReleased) { action = mir_touch_action_up; } if (touchPoint.state() == Qt::TouchPointPressed) { action = mir_touch_action_down; } MirTouchTooltype tooltype = mir_touch_tooltype_finger; if (touchPoint.flags() & QTouchEvent::TouchPoint::Pen) tooltype = mir_touch_tooltype_stylus; mir::events::add_touch(*ev, id, action, tooltype, touchPoint.pos().x(), touchPoint.pos().y(), touchPoint.pressure(), touchPoint.rect().width(), touchPoint.rect().height(), 0 /* size */); } return ev; } } // namespace { MirSurface::MirSurface(std::shared_ptr surface, SessionInterface* session, mir::shell::Shell* shell, std::shared_ptr observer, const CreationHints &creationHints) : MirSurfaceInterface() , m_surface(surface) , m_session(session) , m_shell(shell) , m_firstFrameDrawn(false) , m_orientationAngle(Mir::Angle0) , m_textureUpdated(false) , m_currentFrameNumber(0) , m_live(true) , m_shellChrome(Mir::NormalChrome) { m_minimumWidth = creationHints.minWidth; m_minimumHeight = creationHints.minHeight; m_maximumWidth = creationHints.maxWidth; m_maximumHeight = creationHints.maxHeight; m_widthIncrement = creationHints.widthIncrement; m_heightIncrement = creationHints.heightIncrement; m_shellChrome = creationHints.shellChrome; m_surfaceObserver = observer; if (observer) { connect(observer.get(), &SurfaceObserver::framesPosted, this, &MirSurface::onFramesPostedObserved); connect(observer.get(), &SurfaceObserver::attributeChanged, this, &MirSurface::onAttributeChanged); connect(observer.get(), &SurfaceObserver::keymapChanged, this, &MirSurface::onKeymapChanged); connect(observer.get(), &SurfaceObserver::nameChanged, this, &MirSurface::nameChanged); connect(observer.get(), &SurfaceObserver::cursorChanged, this, &MirSurface::setCursor); connect(observer.get(), &SurfaceObserver::minimumWidthChanged, this, &MirSurface::setMinimumWidth); connect(observer.get(), &SurfaceObserver::minimumHeightChanged, this, &MirSurface::setMinimumHeight); connect(observer.get(), &SurfaceObserver::maximumWidthChanged, this, &MirSurface::setMaximumWidth); connect(observer.get(), &SurfaceObserver::maximumHeightChanged, this, &MirSurface::setMaximumHeight); connect(observer.get(), &SurfaceObserver::widthIncrementChanged, this, &MirSurface::setWidthIncrement); connect(observer.get(), &SurfaceObserver::heightIncrementChanged, this, &MirSurface::setHeightIncrement); connect(observer.get(), &SurfaceObserver::shellChromeChanged, this, [&](MirShellChrome shell_chrome) { setShellChrome(static_cast(shell_chrome)); }); observer->setListener(this); } connect(session, &QObject::destroyed, this, &MirSurface::onSessionDestroyed); connect(&m_frameDropperTimer, &QTimer::timeout, this, &MirSurface::dropPendingBuffer); // Rationale behind the frame dropper and its interval value: // // We want to give ample room for Qt scene graph to have a chance to fetch and render // the next pending buffer before we take the drastic action of dropping it (so don't set // it anywhere close to our target render interval). // // We also want to guarantee a minimal frames-per-second (fps) frequency for client applications // as they get stuck on swap_buffers() if there's no free buffer to swap to yet (ie, they // are all pending consumption by the compositor, us). But on the other hand, we don't want // that minimal fps to be too high as that would mean this timer would be triggered way too often // for nothing causing unnecessary overhead as actually dropping frames from an app should // in practice rarely happen. m_frameDropperTimer.setInterval(200); m_frameDropperTimer.setSingleShot(false); QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); } MirSurface::~MirSurface() { qCDebug(QTMIR_SURFACES).nospace() << "MirSurface::~MirSurface this=" << this << " viewCount=" << m_views.count(); Q_ASSERT(m_views.isEmpty()); if (m_session) { m_session->removeSurface(this); } QMutexLocker locker(&m_mutex); m_surface->remove_observer(m_surfaceObserver); } void MirSurface::onFramesPostedObserved() { if (!m_firstFrameDrawn) { m_firstFrameDrawn = true; Q_EMIT firstFrameDrawn(); } // restart the frame dropper so that items have enough time to render the next frame. m_frameDropperTimer.start(); Q_EMIT framesPosted(); } void MirSurface::onAttributeChanged(const MirSurfaceAttrib attribute, const int /*value*/) { switch (attribute) { case mir_surface_attrib_type: Q_EMIT typeChanged(type()); break; case mir_surface_attrib_state: Q_EMIT stateChanged(state()); break; case mir_surface_attrib_visibility: Q_EMIT visibleChanged(visible()); break; default: break; } } Mir::Type MirSurface::type() const { switch (m_surface->type()) { case mir_surface_type_normal: return Mir::NormalType; case mir_surface_type_utility: return Mir::UtilityType; case mir_surface_type_dialog: return Mir::DialogType; case mir_surface_type_gloss: return Mir::GlossType; case mir_surface_type_freestyle: return Mir::FreeStyleType; case mir_surface_type_menu: return Mir::MenuType; case mir_surface_type_inputmethod: return Mir::InputMethodType; case mir_surface_type_satellite: return Mir::SatelliteType; case mir_surface_type_tip: return Mir::TipType; default: return Mir::UnknownType; } } void MirSurface::dropPendingBuffer() { QMutexLocker locker(&m_mutex); const void* const userId = (void*)123; // TODO: Multimonitor support int framesPending = m_surface->buffers_ready_for_compositor(userId); if (framesPending > 0) { m_textureUpdated = false; locker.unlock(); if (updateTexture()) { DEBUG_MSG << "() dropped=1 left=" << framesPending-1; } else { // If we haven't managed to update the texture, don't keep banging away. m_frameDropperTimer.stop(); DEBUG_MSG << "() dropped=0" << " left=" << framesPending << " - failed to upate texture"; } Q_EMIT frameDropped(); } else { // The client can't possibly be blocked in swap buffers if the // queue is empty. So we can safely enter deep sleep now. If the // client provides any new frames, the timer will get restarted // via scheduleTextureUpdate()... m_frameDropperTimer.stop(); } } void MirSurface::stopFrameDropper() { DEBUG_MSG << "()"; m_frameDropperTimer.stop(); } void MirSurface::startFrameDropper() { DEBUG_MSG << "()"; if (!m_frameDropperTimer.isActive()) { m_frameDropperTimer.start(); } } QSharedPointer MirSurface::texture() { QMutexLocker locker(&m_mutex); if (!m_texture) { QSharedPointer texture(new MirBufferSGTexture); m_texture = texture.toWeakRef(); return texture; } else { return m_texture.toStrongRef(); } } bool MirSurface::updateTexture() { QMutexLocker locker(&m_mutex); MirBufferSGTexture *texture = static_cast(m_texture.data()); if (!texture) return false; if (m_textureUpdated) { return texture->hasBuffer(); } const void* const userId = (void*)123; auto renderables = m_surface->generate_renderables(userId); if (renderables.size() > 0 && (m_surface->buffers_ready_for_compositor(userId) > 0 || !texture->hasBuffer()) ) { // Avoid holding two buffers for the compositor at the same time. Thus free the current // before acquiring the next texture->freeBuffer(); texture->setBuffer(renderables[0]->buffer()); ++m_currentFrameNumber; if (texture->textureSize() != m_size) { m_size = texture->textureSize(); QMetaObject::invokeMethod(this, "emitSizeChanged", Qt::QueuedConnection); } m_textureUpdated = true; } if (m_surface->buffers_ready_for_compositor(userId) > 0) { // restart the frame dropper to give MirSurfaceItems enough time to render the next frame. // queued since the timer lives in a different thread QMetaObject::invokeMethod(&m_frameDropperTimer, "start", Qt::QueuedConnection); } return texture->hasBuffer(); } void MirSurface::onCompositorSwappedBuffers() { QMutexLocker locker(&m_mutex); m_textureUpdated = false; } bool MirSurface::numBuffersReadyForCompositor() { QMutexLocker locker(&m_mutex); const void* const userId = (void*)123; return m_surface->buffers_ready_for_compositor(userId); } void MirSurface::setFocus(bool focus) { if (!m_session) { return; } // Temporary hotfix for http://pad.lv/1483752 if (m_session->childSessions()->rowCount() > 0) { // has child trusted session, ignore any focus change attempts DEBUG_MSG << "(" << focus << ") - has child trusted session, ignore any focus change attempts"; return; } DEBUG_MSG << "(" << focus << ")"; if (focus) { m_shell->set_surface_attribute(m_session->session(), m_surface, mir_surface_attrib_focus, mir_surface_focused); } else { m_shell->set_surface_attribute(m_session->session(), m_surface, mir_surface_attrib_focus, mir_surface_unfocused); } } void MirSurface::close() { if (m_surface) { m_surface->request_client_surface_close(); } } void MirSurface::resize(int width, int height) { int mirWidth = m_surface->size().width.as_int(); int mirHeight = m_surface->size().height.as_int(); bool mirSizeIsDifferent = width != mirWidth || height != mirHeight; if (clientIsRunning() && mirSizeIsDifferent) { mir::geometry::Size newMirSize(width, height); m_surface->resize(newMirSize); DEBUG_MSG << " old (" << mirWidth << "," << mirHeight << ")" << ", new (" << width << "," << height << ")"; } } QSize MirSurface::size() const { return m_size; } Mir::State MirSurface::state() const { switch (m_surface->state()) { case mir_surface_state_unknown: return Mir::UnknownState; case mir_surface_state_restored: return Mir::RestoredState; case mir_surface_state_minimized: return Mir::MinimizedState; case mir_surface_state_maximized: return Mir::MaximizedState; case mir_surface_state_vertmaximized: return Mir::VertMaximizedState; case mir_surface_state_fullscreen: return Mir::FullscreenState; case mir_surface_state_horizmaximized: return Mir::HorizMaximizedState; case mir_surface_state_hidden: return Mir::HiddenState; default: return Mir::UnknownState; } } Mir::OrientationAngle MirSurface::orientationAngle() const { return m_orientationAngle; } void MirSurface::setOrientationAngle(Mir::OrientationAngle angle) { MirOrientation mirOrientation; if (angle == m_orientationAngle) { return; } m_orientationAngle = angle; switch (angle) { case Mir::Angle0: mirOrientation = mir_orientation_normal; break; case Mir::Angle90: mirOrientation = mir_orientation_right; break; case Mir::Angle180: mirOrientation = mir_orientation_inverted; break; case Mir::Angle270: mirOrientation = mir_orientation_left; break; default: qCWarning(QTMIR_SURFACES, "Unsupported orientation angle: %d", angle); return; } if (m_surface) { m_surface->set_orientation(mirOrientation); } Q_EMIT orientationAngleChanged(angle); } QString MirSurface::name() const { return QString::fromStdString(m_surface->name()); } void MirSurface::setState(Mir::State qmlState) { int mirState; switch (qmlState) { default: case Mir::UnknownState: mirState = mir_surface_state_unknown; break; case Mir::RestoredState: mirState = mir_surface_state_restored; break; case Mir::MinimizedState: mirState = mir_surface_state_minimized; break; case Mir::MaximizedState: mirState = mir_surface_state_maximized; break; case Mir::VertMaximizedState: mirState = mir_surface_state_vertmaximized; break; case Mir::FullscreenState: mirState = mir_surface_state_fullscreen; break; case Mir::HorizMaximizedState: mirState = mir_surface_state_horizmaximized; break; case Mir::HiddenState: mirState = mir_surface_state_hidden; break; } m_shell->set_surface_attribute(m_session->session(), m_surface, mir_surface_attrib_state, mirState); } void MirSurface::setLive(bool value) { if (value != m_live) { m_live = value; Q_EMIT liveChanged(value); } } bool MirSurface::live() const { return m_live; } bool MirSurface::visible() const { return m_surface->query(mir_surface_attrib_visibility) == mir_surface_visibility_exposed; } void MirSurface::mousePressEvent(QMouseEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_button_down); m_surface->consume(ev.get()); event->accept(); } void MirSurface::mouseMoveEvent(QMouseEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_motion); m_surface->consume(ev.get()); event->accept(); } void MirSurface::mouseReleaseEvent(QMouseEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_button_up); m_surface->consume(ev.get()); event->accept(); } void MirSurface::hoverEnterEvent(QHoverEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_enter); m_surface->consume(ev.get()); event->accept(); } void MirSurface::hoverLeaveEvent(QHoverEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_leave); m_surface->consume(ev.get()); event->accept(); } void MirSurface::hoverMoveEvent(QHoverEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_motion); m_surface->consume(ev.get()); event->accept(); } void MirSurface::wheelEvent(QWheelEvent *event) { auto ev = makeMirEvent(event); m_surface->consume(ev.get()); event->accept(); } void MirSurface::keyPressEvent(QKeyEvent *qtEvent) { auto ev = makeMirEvent(qtEvent); m_surface->consume(ev.get()); qtEvent->accept(); } void MirSurface::keyReleaseEvent(QKeyEvent *qtEvent) { auto ev = makeMirEvent(qtEvent); m_surface->consume(ev.get()); qtEvent->accept(); } void MirSurface::touchEvent(Qt::KeyboardModifiers mods, const QList &touchPoints, Qt::TouchPointStates touchPointStates, ulong timestamp) { auto ev = makeMirEvent(mods, touchPoints, touchPointStates, timestamp); m_surface->consume(ev.get()); } bool MirSurface::clientIsRunning() const { return (m_session && (m_session->state() == Session::State::Running || m_session->state() == Session::State::Starting || m_session->state() == Session::State::Suspending)) || !m_session; } bool MirSurface::isBeingDisplayed() const { return !m_views.isEmpty(); } void MirSurface::registerView(qintptr viewId) { m_views.insert(viewId, MirSurface::View{false}); DEBUG_MSG << "(" << viewId << ")" << " after=" << m_views.count(); if (m_views.count() == 1) { Q_EMIT isBeingDisplayedChanged(); } } void MirSurface::unregisterView(qintptr viewId) { m_views.remove(viewId); DEBUG_MSG << "(" << viewId << ")" << " after=" << m_views.count() << " live=" << m_live; if (m_views.count() == 0) { Q_EMIT isBeingDisplayedChanged(); if (m_session.isNull() || !m_live) { deleteLater(); } } updateVisibility(); } void MirSurface::setViewVisibility(qintptr viewId, bool visible) { if (!m_views.contains(viewId)) return; m_views[viewId].visible = visible; updateVisibility(); } void MirSurface::updateVisibility() { // FIXME: https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1514556 return; bool newVisible = false; QHashIterator i(m_views); while (i.hasNext()) { i.next(); newVisible |= i.value().visible; } if (newVisible != visible()) { DEBUG_MSG << "(" << newVisible << ")"; m_surface->configure(mir_surface_attrib_visibility, newVisible ? mir_surface_visibility_exposed : mir_surface_visibility_occluded); } } unsigned int MirSurface::currentFrameNumber() const { QMutexLocker locker(&m_mutex); return m_currentFrameNumber; } void MirSurface::onSessionDestroyed() { if (m_views.isEmpty()) { deleteLater(); } } void MirSurface::emitSizeChanged() { Q_EMIT sizeChanged(m_size); } void MirSurface::onKeymapChanged(const QString &layout, const QString &variant) { m_keyMap = qMakePair(layout, variant); Q_EMIT keymapChanged(layout, variant); } QString MirSurface::appId() const { QString appId; if (m_session && m_session->application()) { appId = m_session->application()->appId(); } else { appId.append("-"); } return appId; } void MirSurface::setKeymap(const QString &layout, const QString &variant) { if (layout.isEmpty()) { qCWarning(QTMIR_SURFACES) << "Setting keymap with empty layout is not supported"; return; } m_surface->set_keymap(MirInputDeviceId(), "", layout.toStdString(), variant.toStdString(), ""); } QCursor MirSurface::cursor() const { return m_cursor; } Mir::ShellChrome MirSurface::shellChrome() const { return m_shellChrome; } QString MirSurface::keymapLayout() const { return m_keyMap.first; } QString MirSurface::keymapVariant() const { return m_keyMap.second; } void MirSurface::setShellChrome(Mir::ShellChrome shellChrome) { if (m_shellChrome != shellChrome) { m_shellChrome = shellChrome; Q_EMIT shellChromeChanged(shellChrome); } } void MirSurface::setCursor(const QCursor &cursor) { DEBUG_MSG << "(" << qtCursorShapeToStr(cursor.shape()) << ")"; m_cursor = cursor; Q_EMIT cursorChanged(m_cursor); } int MirSurface::minimumWidth() const { return m_minimumWidth; } int MirSurface::minimumHeight() const { return m_minimumHeight; } int MirSurface::maximumWidth() const { return m_maximumWidth; } int MirSurface::maximumHeight() const { return m_maximumHeight; } int MirSurface::widthIncrement() const { return m_widthIncrement; } int MirSurface::heightIncrement() const { return m_heightIncrement; } void MirSurface::setMinimumWidth(int value) { if (value != m_minimumWidth) { m_minimumWidth = value; Q_EMIT minimumWidthChanged(value); } } void MirSurface::setMinimumHeight(int value) { if (value != m_minimumHeight) { m_minimumHeight = value; Q_EMIT minimumHeightChanged(value); } } void MirSurface::setMaximumWidth(int value) { if (value != m_maximumWidth) { m_maximumWidth = value; Q_EMIT maximumWidthChanged(value); } } void MirSurface::setMaximumHeight(int value) { if (value != m_maximumHeight) { m_maximumHeight = value; Q_EMIT maximumHeightChanged(value); } } void MirSurface::setWidthIncrement(int value) { if (value != m_widthIncrement) { m_widthIncrement = value; Q_EMIT widthIncrementChanged(value); } } void MirSurface::setHeightIncrement(int value) { if (value != m_heightIncrement) { m_heightIncrement = value; Q_EMIT heightIncrementChanged(value); } } ./src/modules/Unity/Application/mirbuffersgtexture.cpp0000644000015600001650000000475112677054330023365 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mirbuffersgtexture.h" // Mir #include #include #include namespace mg = mir::geometry; namespace mrg = mir::renderer::gl; MirBufferSGTexture::MirBufferSGTexture() : QSGTexture() , m_width(0) , m_height(0) , m_textureId(0) { glGenTextures(1, &m_textureId); setFiltering(QSGTexture::Linear); setHorizontalWrapMode(QSGTexture::ClampToEdge); setVerticalWrapMode(QSGTexture::ClampToEdge); } MirBufferSGTexture::~MirBufferSGTexture() { if (m_textureId) { glDeleteTextures(1, &m_textureId); } } void MirBufferSGTexture::freeBuffer() { m_mirBuffer.reset(); m_width = 0; m_height = 0; } void MirBufferSGTexture::setBuffer(std::shared_ptr buffer) { m_mirBuffer = buffer; mg::Size size = buffer->size(); m_height = size.height.as_int(); m_width = size.width.as_int(); } bool MirBufferSGTexture::hasBuffer() const { return !!m_mirBuffer; } int MirBufferSGTexture::textureId() const { return m_textureId; } QSize MirBufferSGTexture::textureSize() const { return QSize(m_width, m_height); } bool MirBufferSGTexture::hasAlphaChannel() const { if (hasBuffer()) { return m_mirBuffer->pixel_format() == mir_pixel_format_abgr_8888 || m_mirBuffer->pixel_format() == mir_pixel_format_argb_8888; } else { return false; } } void MirBufferSGTexture::bind() { Q_ASSERT(hasBuffer()); glBindTexture(GL_TEXTURE_2D, m_textureId); updateBindOptions(true/* force */); auto const texture_source = dynamic_cast(m_mirBuffer->native_buffer_base()); if (!texture_source) throw std::logic_error("Buffer does not support GL rendering"); texture_source->gl_bind_to_texture(); } ./src/modules/Unity/Application/settings_interface.h0000644000015600001650000000204412677054330022747 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SETTINGS_INTERFACE_H #define SETTINGS_INTERFACE_H //Qt #include namespace qtmir { class SettingsInterface: public QObject { Q_OBJECT public: explicit SettingsInterface(QObject *parent = 0): QObject(parent) {} virtual QVariant get(const QString &key) const = 0; Q_SIGNALS: void changed(const QString &key); }; } #endif // SETTINGS_INTERFACE_H ./src/modules/Unity/Application/sessionmanager.cpp0000644000015600001650000001757212677054330022454 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // Qt #include // local #include "application_manager.h" #include "debughelpers.h" #include "sessionmanager.h" // QPA mirserver #include "nativeinterface.h" #include "mirserver.h" #include "sessionlistener.h" #include "logging.h" #include "promptsessionlistener.h" // mir #include #include Q_LOGGING_CATEGORY(QTMIR_SESSIONS, "qtmir.sessions") namespace ms = mir::scene; namespace qtmir { SessionManager *SessionManager::the_session_manager = nullptr; void connectToSessionListener(SessionManager *manager, SessionListener *listener) { QObject::connect(listener, &SessionListener::sessionStarting, manager, &SessionManager::onSessionStarting); QObject::connect(listener, &SessionListener::sessionStopping, manager, &SessionManager::onSessionStopping); } void connectToPromptSessionListener(SessionManager * manager, PromptSessionListener * listener) { QObject::connect(listener, &PromptSessionListener::promptSessionStarting, manager, &SessionManager::onPromptSessionStarting); QObject::connect(listener, &PromptSessionListener::promptSessionStopping, manager, &SessionManager::onPromptSessionStopping); QObject::connect(listener, &PromptSessionListener::promptProviderAdded, manager, &SessionManager::onPromptProviderAdded); QObject::connect(listener, &PromptSessionListener::promptProviderRemoved, manager, &SessionManager::onPromptProviderRemoved); } SessionManager* SessionManager::singleton() { if (!the_session_manager) { NativeInterface *nativeInterface = dynamic_cast(QGuiApplication::platformNativeInterface()); if (!nativeInterface) { qCritical("ERROR: Unity.Application QML plugin requires use of the 'mirserver' QPA plugin"); QGuiApplication::quit(); return nullptr; } SessionListener *sessionListener = static_cast(nativeInterface->nativeResourceForIntegration("SessionListener")); PromptSessionListener *promptSessionListener = static_cast(nativeInterface->nativeResourceForIntegration("PromptSessionListener")); the_session_manager = new SessionManager(nativeInterface->m_mirServer, ApplicationManager::singleton()); connectToSessionListener(the_session_manager, sessionListener); connectToPromptSessionListener(the_session_manager, promptSessionListener); } return the_session_manager; } SessionManager::SessionManager( const QSharedPointer& mirServer, ApplicationManager* applicationManager, QObject *parent) : SessionModel(parent) , m_mirServer(mirServer) , m_applicationManager(applicationManager) { qCDebug(QTMIR_SESSIONS) << "SessionManager::SessionManager - this=" << this; setObjectName("qtmir::SessionManager"); } SessionManager::~SessionManager() { qCDebug(QTMIR_SESSIONS) << "SessionManager::~SessionManager - this=" << this; } SessionInterface *SessionManager::findSession(const mir::scene::Session* session) const { if (!session) return nullptr; for (SessionInterface* child : list()) { if (child->session().get() == session) return child; } return nullptr; } void SessionManager::onSessionStarting(std::shared_ptr const& session) { qCDebug(QTMIR_SESSIONS) << "SessionManager::onSessionStarting - sessionName=" << session->name().c_str(); Session* qmlSession = new Session(session, m_mirServer->the_prompt_session_manager()); insert(0, qmlSession); Application* application = m_applicationManager->findApplicationWithSession(session); if (application && application->state() != Application::Running) { application->setSession(qmlSession); } // need to remove if we've destroyed outside connect(qmlSession, &Session::destroyed, this, [&](QObject *item) { auto sessionToRemove = static_cast(item); remove(sessionToRemove); }); Q_EMIT sessionStarting(qmlSession); } void SessionManager::onSessionStopping(std::shared_ptr const& session) { qCDebug(QTMIR_SESSIONS) << "SessionManager::onSessionStopping - sessionName=" << session->name().c_str(); SessionInterface* qmlSession = findSession(session.get()); if (!qmlSession) return; remove(qmlSession); qmlSession->setLive(false); Q_EMIT sessionStopping(qmlSession); } void SessionManager::onPromptSessionStarting(const std::shared_ptr& promptSession) { qCDebug(QTMIR_SESSIONS) << "SessionManager::onPromptSessionStarting - promptSession=" << promptSession.get(); std::shared_ptr appSession = m_mirServer->the_prompt_session_manager()->application_for(promptSession); SessionInterface *qmlAppSession = findSession(appSession.get()); if (qmlAppSession) { m_mirPromptToSessionHash[promptSession.get()] = qmlAppSession; qmlAppSession->appendPromptSession(promptSession); } else { qCDebug(QTMIR_SESSIONS) << "SessionManager::onPromptSessionStarting - could not find app session for prompt session"; } } void SessionManager::onPromptSessionStopping(const std::shared_ptr& promptSession) { qCDebug(QTMIR_SESSIONS) << "SessionManager::onPromptSessionStopping - promptSession=" << promptSession.get(); for (SessionInterface *qmlSession : this->list()) { qmlSession->removePromptSession(promptSession); } m_mirPromptToSessionHash.remove(promptSession.get()); } void SessionManager::onPromptProviderAdded(const mir::scene::PromptSession *promptSession, const std::shared_ptr &promptProvider) { qCDebug(QTMIR_SESSIONS) << "SessionManager::onPromptProviderAdded - promptSession=" << promptSession << " promptProvider=" << promptProvider.get(); SessionInterface* qmlAppSession = m_mirPromptToSessionHash.value(promptSession, nullptr); if (!qmlAppSession) { qCDebug(QTMIR_SESSIONS) << "SessionManager::onPromptProviderAdded - could not find session item for app session"; return; } SessionInterface* qmlPromptProvider = findSession(promptProvider.get()); if (!qmlPromptProvider) { qCDebug(QTMIR_SESSIONS) << "SessionManager::onPromptProviderAdded - could not find session item for provider session"; return; } qmlAppSession->addChildSession(qmlPromptProvider); } void SessionManager::onPromptProviderRemoved(const mir::scene::PromptSession *promptSession, const std::shared_ptr &promptProvider) { qCDebug(QTMIR_SESSIONS) << "SessionManager::onPromptProviderRemoved - promptSession=" << promptSession << " promptProvider=" << promptProvider.get(); SessionInterface* qmlPromptProvider = findSession(promptProvider.get()); if (!qmlPromptProvider) { qCDebug(QTMIR_SESSIONS) << "SessionManager::onPromptProviderAdded - could not find session item for provider session"; return; } qmlPromptProvider->setLive(false); } } // namespace qtmir ./src/modules/Unity/Application/applicationscreenshotprovider.h0000644000015600001650000000226512677054330025250 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef APPLICATIONSCREENSHOTPROVIDER_H #define APPLICATIONSCREENSHOTPROVIDER_H #include namespace qtmir { class ApplicationManager; class ApplicationScreenshotProvider : public QQuickImageProvider { public: explicit ApplicationScreenshotProvider(ApplicationManager *appManager); QImage requestImage(const QString &appId, QSize *size, const QSize &requestedSize) override; private: ApplicationManager* m_appManager; }; } // namespace qtmir #endif // APPLICATIONSCREENSHOTPROVIDER_H ./src/modules/Unity/Application/sessionmanager.h0000644000015600001650000000473112677054330022112 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SESSIONMANAGER_H #define SESSIONMANAGER_H // std #include // Qt #include #include // Mir #include // local #include "session.h" #include "sessionmodel.h" namespace mir { namespace scene { class Session; class PromptSession; } } class MirServer; namespace qtmir { class Application; class ApplicationManager; class SessionManager : public SessionModel { Q_OBJECT public: explicit SessionManager( const QSharedPointer& mirServer, ApplicationManager* applicationManager, QObject *parent = 0 ); ~SessionManager(); static SessionManager* singleton(); SessionInterface *findSession(const mir::scene::Session* session) const; Q_SIGNALS: void sessionStarting(SessionInterface* session); void sessionStopping(SessionInterface* session); public Q_SLOTS: void onSessionStarting(std::shared_ptr const& session); void onSessionStopping(std::shared_ptr const& session); void onPromptSessionStarting(const std::shared_ptr& promptSession); void onPromptSessionStopping(const std::shared_ptr& promptSession); void onPromptProviderAdded(const mir::scene::PromptSession *, const std::shared_ptr &); void onPromptProviderRemoved(const mir::scene::PromptSession *, const std::shared_ptr &); protected: private: QSharedPointer m_mirServer; ApplicationManager* m_applicationManager; static SessionManager *the_session_manager; QList m_sessions; QHash m_mirPromptToSessionHash; }; } // namespace qtmir #endif // SESSIONMANAGER_H ./src/modules/Unity/Application/mirsurfaceitem.cpp0000644000015600001650000005444012677054330022450 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // local #include "application.h" #include "session.h" #include "mirsurfaceitem.h" #include "logging.h" #include "ubuntukeyboardinfo.h" #include "tracepoints.h" // generated from tracepoints.tp #include "timestamp.h" // common #include // Qt #include #include #include #include #include #include #include #include #include #include namespace qtmir { namespace { class MirSurfaceItemReleaseResourcesJob : public QRunnable { public: MirSurfaceItemReleaseResourcesJob() : textureProvider(nullptr) {} void run() { delete textureProvider; textureProvider = nullptr; } QObject *textureProvider; }; } // namespace { class MirTextureProvider : public QSGTextureProvider { Q_OBJECT public: MirTextureProvider(QSharedPointer texture) : t(texture) {} QSGTexture *texture() const { if (t) t->setFiltering(smooth ? QSGTexture::Linear : QSGTexture::Nearest); return t.data(); } bool smooth{false}; void releaseTexture() { t.reset(); } void setTexture(QSharedPointer newTexture) { t = newTexture; } private: QSharedPointer t; }; MirSurfaceItem::MirSurfaceItem(QQuickItem *parent) : MirSurfaceItemInterface(parent) , m_surface(nullptr) , m_window(nullptr) , m_textureProvider(nullptr) , m_lastTouchEvent(nullptr) , m_lastFrameNumberRendered(nullptr) , m_surfaceWidth(0) , m_surfaceHeight(0) , m_orientationAngle(nullptr) , m_consumesInput(false) , m_fillMode(Stretch) { qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::MirSurfaceItem"; setSmooth(true); setFlag(QQuickItem::ItemHasContents, true); //so scene graph will render this item if (!UbuntuKeyboardInfo::instance()) { new UbuntuKeyboardInfo; } m_updateMirSurfaceSizeTimer.setSingleShot(true); m_updateMirSurfaceSizeTimer.setInterval(1); connect(&m_updateMirSurfaceSizeTimer, &QTimer::timeout, this, &MirSurfaceItem::updateMirSurfaceSize); connect(this, &QQuickItem::activeFocusChanged, this, &MirSurfaceItem::updateMirSurfaceFocus); connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceVisibility); connect(this, &QQuickItem::windowChanged, this, &MirSurfaceItem::onWindowChanged); } MirSurfaceItem::~MirSurfaceItem() { qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::~MirSurfaceItem - this=" << this; setSurface(nullptr); delete m_lastTouchEvent; delete m_lastFrameNumberRendered; delete m_orientationAngle; // Belongs to the scene graph thread. Can't delete here. // Scene graph should call MirSurfaceItem::releaseResources() or invalidateSceneGraph() // delete m_textureProvider; } Mir::Type MirSurfaceItem::type() const { if (m_surface) { return m_surface->type(); } else { return Mir::UnknownType; } } Mir::OrientationAngle MirSurfaceItem::orientationAngle() const { if (m_orientationAngle) { Q_ASSERT(!m_surface); return *m_orientationAngle; } else if (m_surface) { return m_surface->orientationAngle(); } else { return Mir::Angle0; } } void MirSurfaceItem::setOrientationAngle(Mir::OrientationAngle angle) { qCDebug(QTMIR_SURFACES, "MirSurfaceItem::setOrientationAngle(%d)", angle); if (m_surface) { Q_ASSERT(!m_orientationAngle); m_surface->setOrientationAngle(angle); } else if (!m_orientationAngle) { m_orientationAngle = new Mir::OrientationAngle; *m_orientationAngle = angle; Q_EMIT orientationAngleChanged(angle); } else if (*m_orientationAngle != angle) { *m_orientationAngle = angle; Q_EMIT orientationAngleChanged(angle); } } QString MirSurfaceItem::name() const { if (m_surface) { return m_surface->name(); } else { return QString(); } } bool MirSurfaceItem::live() const { return m_surface && m_surface->live(); } Mir::ShellChrome MirSurfaceItem::shellChrome() const { return m_surface ? m_surface->shellChrome() : Mir::NormalChrome; } // Called from the rendering (scene graph) thread QSGTextureProvider *MirSurfaceItem::textureProvider() const { QMutexLocker mutexLocker(const_cast(&m_mutex)); const_cast(this)->ensureTextureProvider(); return m_textureProvider; } void MirSurfaceItem::ensureTextureProvider() { if (!m_surface) { return; } if (!m_textureProvider) { m_textureProvider = new MirTextureProvider(m_surface->texture()); // Check that the item is indeed using the texture from the MirSurface it currently holds // If until now we were drawing a MirSurface "A" and it replaced with a MirSurface "B", // we will still hold the texture from "A" until the first time we're asked to draw "B". // That's the moment when we finally discard the texture from "A" and get the one from "B". // // Also note that m_surface->weakTexture() will return null if m_surface->texture() was never // called before. } else if (!m_textureProvider->texture() || m_textureProvider->texture() != m_surface->weakTexture()) { m_textureProvider->setTexture(m_surface->texture()); } } QSGNode *MirSurfaceItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) // called by render thread { QMutexLocker mutexLocker(&m_mutex); if (!m_surface) { if (m_textureProvider) { m_textureProvider->releaseTexture(); } delete oldNode; return 0; } ensureTextureProvider(); if (!m_textureProvider->texture() || !m_surface->updateTexture()) { delete oldNode; return 0; } if (m_surface->numBuffersReadyForCompositor() > 0) { QTimer::singleShot(0, this, SLOT(update())); } m_textureProvider->smooth = smooth(); QSGDefaultImageNode *node = static_cast(oldNode); if (!node) { node = new QSGDefaultImageNode; node->setMipmapFiltering(QSGTexture::None); node->setHorizontalWrapMode(QSGTexture::ClampToEdge); node->setVerticalWrapMode(QSGTexture::ClampToEdge); } else { if (!m_lastFrameNumberRendered || (*m_lastFrameNumberRendered != m_surface->currentFrameNumber())) { node->markDirty(QSGNode::DirtyMaterial); } } node->setTexture(m_textureProvider->texture()); if (m_fillMode == PadOrCrop) { const QSize &textureSize = m_textureProvider->texture()->textureSize(); QRectF targetRect; targetRect.setWidth(qMin(width(), static_cast(textureSize.width()))); targetRect.setHeight(qMin(height(), static_cast(textureSize.height()))); qreal u = targetRect.width() / textureSize.width(); qreal v = targetRect.height() / textureSize.height(); node->setSubSourceRect(QRectF(0, 0, u, v)); node->setTargetRect(targetRect); node->setInnerTargetRect(targetRect); } else { // Stretch node->setSubSourceRect(QRectF(0, 0, 1, 1)); node->setTargetRect(QRectF(0, 0, width(), height())); node->setInnerTargetRect(QRectF(0, 0, width(), height())); } node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); node->setAntialiasing(antialiasing()); node->update(); if (!m_lastFrameNumberRendered) { m_lastFrameNumberRendered = new unsigned int; } *m_lastFrameNumberRendered = m_surface->currentFrameNumber(); return node; } void MirSurfaceItem::mousePressEvent(QMouseEvent *event) { if (m_consumesInput && m_surface && m_surface->live()) { if (type() == Mir::InputMethodType) { // FIXME: Hack to get the VKB use case working while we don't have the proper solution in place. if (isMouseInsideUbuntuKeyboard(event)) { m_surface->mousePressEvent(event); } else { event->ignore(); } } else { m_surface->mousePressEvent(event); } } else { event->ignore(); } } void MirSurfaceItem::mouseMoveEvent(QMouseEvent *event) { if (m_consumesInput && m_surface && m_surface->live()) { m_surface->mouseMoveEvent(event); } else { event->ignore(); } } void MirSurfaceItem::mouseReleaseEvent(QMouseEvent *event) { if (m_consumesInput && m_surface && m_surface->live()) { m_surface->mouseReleaseEvent(event); } else { event->ignore(); } } void MirSurfaceItem::wheelEvent(QWheelEvent *event) { if (m_consumesInput && m_surface && m_surface->live()) { m_surface->wheelEvent(event); } else { event->ignore(); } } void MirSurfaceItem::hoverEnterEvent(QHoverEvent *event) { if (m_consumesInput && m_surface && m_surface->live()) { m_surface->hoverEnterEvent(event); } else { event->ignore(); } } void MirSurfaceItem::hoverLeaveEvent(QHoverEvent *event) { if (m_consumesInput && m_surface && m_surface->live()) { m_surface->hoverLeaveEvent(event); } else { event->ignore(); } } void MirSurfaceItem::hoverMoveEvent(QHoverEvent *event) { if (m_consumesInput && m_surface && m_surface->live()) { m_surface->hoverMoveEvent(event); } else { event->ignore(); } } void MirSurfaceItem::keyPressEvent(QKeyEvent *event) { if (m_consumesInput && m_surface && m_surface->live()) { m_surface->keyPressEvent(event); } else { event->ignore(); } } void MirSurfaceItem::keyReleaseEvent(QKeyEvent *event) { if (m_consumesInput && m_surface && m_surface->live()) { m_surface->keyReleaseEvent(event); } else { event->ignore(); } } QString MirSurfaceItem::appId() const { if (m_surface) { return m_surface->appId(); } else { return QString("-"); } } void MirSurfaceItem::endCurrentTouchSequence(ulong timestamp) { Q_ASSERT(m_lastTouchEvent); Q_ASSERT(m_lastTouchEvent->type != QEvent::TouchEnd); Q_ASSERT(m_lastTouchEvent->touchPoints.count() > 0); TouchEvent touchEvent = *m_lastTouchEvent; touchEvent.timestamp = timestamp; // Remove all already released touch points int i = 0; while (i < touchEvent.touchPoints.count()) { if (touchEvent.touchPoints[i].state() == Qt::TouchPointReleased) { touchEvent.touchPoints.removeAt(i); } else { ++i; } } // And release the others one by one as Mir expects one press/release per event while (touchEvent.touchPoints.count() > 0) { touchEvent.touchPoints[0].setState(Qt::TouchPointReleased); touchEvent.updateTouchPointStatesAndType(); m_surface->touchEvent(touchEvent.modifiers, touchEvent.touchPoints, touchEvent.touchPointStates, touchEvent.timestamp); *m_lastTouchEvent = touchEvent; touchEvent.touchPoints.removeAt(0); } } void MirSurfaceItem::validateAndDeliverTouchEvent(int eventType, ulong timestamp, Qt::KeyboardModifiers mods, const QList &touchPoints, Qt::TouchPointStates touchPointStates) { if (eventType == QEvent::TouchBegin && m_lastTouchEvent && m_lastTouchEvent->type != QEvent::TouchEnd) { qCWarning(QTMIR_SURFACES) << qPrintable(QString("MirSurfaceItem(%1) - Got a QEvent::TouchBegin while " "there's still an active/unfinished touch sequence.").arg(appId())); // Qt forgot to end the last touch sequence. Let's do it ourselves. endCurrentTouchSequence(timestamp); } m_surface->touchEvent(mods, touchPoints, touchPointStates, timestamp); if (!m_lastTouchEvent) { m_lastTouchEvent = new TouchEvent; } m_lastTouchEvent->type = eventType; m_lastTouchEvent->timestamp = timestamp; m_lastTouchEvent->touchPoints = touchPoints; m_lastTouchEvent->touchPointStates = touchPointStates; tracepoint(qtmir, touchEventConsume_end, uncompressTimestamp(timestamp).count()); } void MirSurfaceItem::touchEvent(QTouchEvent *event) { tracepoint(qtmir, touchEventConsume_start, uncompressTimestamp(event->timestamp()).count()); bool accepted = processTouchEvent(event->type(), event->timestamp(), event->modifiers(), event->touchPoints(), event->touchPointStates()); event->setAccepted(accepted); } bool MirSurfaceItem::processTouchEvent( int eventType, ulong timestamp, Qt::KeyboardModifiers mods, const QList &touchPoints, Qt::TouchPointStates touchPointStates) { if (!m_consumesInput || !m_surface || !m_surface->live()) { return false; } bool accepted = true; if (type() == Mir::InputMethodType && eventType == QEvent::TouchBegin) { // FIXME: Hack to get the VKB use case working while we don't have the proper solution in place. if (hasTouchInsideUbuntuKeyboard(touchPoints)) { validateAndDeliverTouchEvent(eventType, timestamp, mods, touchPoints, touchPointStates); } else { accepted = false; } } else { // NB: If we are getting QEvent::TouchUpdate or QEvent::TouchEnd it's because we've // previously accepted the corresponding QEvent::TouchBegin validateAndDeliverTouchEvent(eventType, timestamp, mods, touchPoints, touchPointStates); } return accepted; } bool MirSurfaceItem::hasTouchInsideUbuntuKeyboard(const QList &touchPoints) { UbuntuKeyboardInfo *ubuntuKeyboardInfo = UbuntuKeyboardInfo::instance(); for (int i = 0; i < touchPoints.count(); ++i) { QPoint pos = touchPoints.at(i).pos().toPoint(); if (pos.x() >= ubuntuKeyboardInfo->x() && pos.x() <= (ubuntuKeyboardInfo->x() + ubuntuKeyboardInfo->width()) && pos.y() >= ubuntuKeyboardInfo->y() && pos.y() <= (ubuntuKeyboardInfo->y() + ubuntuKeyboardInfo->height())) { return true; } } return false; } bool MirSurfaceItem::isMouseInsideUbuntuKeyboard(const QMouseEvent *event) { UbuntuKeyboardInfo *ubuntuKeyboardInfo = UbuntuKeyboardInfo::instance(); const QPointF &pos = event->localPos(); return pos.x() >= ubuntuKeyboardInfo->x() && pos.x() <= (ubuntuKeyboardInfo->x() + ubuntuKeyboardInfo->width()) && pos.y() >= ubuntuKeyboardInfo->y() && pos.y() <= (ubuntuKeyboardInfo->y() + ubuntuKeyboardInfo->height()); } Mir::State MirSurfaceItem::surfaceState() const { if (m_surface) { return m_surface->state(); } else { return Mir::UnknownState; } } void MirSurfaceItem::setSurfaceState(Mir::State state) { if (m_surface) { m_surface->setState(state); } } void MirSurfaceItem::scheduleMirSurfaceSizeUpdate() { if (!m_updateMirSurfaceSizeTimer.isActive()) { m_updateMirSurfaceSizeTimer.start(); } } void MirSurfaceItem::updateMirSurfaceSize() { if (!m_surface || !m_surface->live() || (m_surfaceWidth <= 0 && m_surfaceHeight <= 0)) { return; } // If one dimension is not set, fallback to the current value int width = m_surfaceWidth > 0 ? m_surfaceWidth : m_surface->size().width(); int height = m_surfaceHeight > 0 ? m_surfaceHeight : m_surface->size().height(); m_surface->resize(width, height); } void MirSurfaceItem::updateMirSurfaceVisibility() { if (!m_surface || !m_surface->live()) { return; } m_surface->setViewVisibility((qintptr)this, isVisible()); } void MirSurfaceItem::updateMirSurfaceFocus(bool focused) { if (m_surface && m_consumesInput && m_surface->live()) { m_surface->setFocus(focused); } } void MirSurfaceItem::invalidateSceneGraph() { delete m_textureProvider; m_textureProvider = nullptr; } void MirSurfaceItem::TouchEvent::updateTouchPointStatesAndType() { touchPointStates = 0; for (int i = 0; i < touchPoints.count(); ++i) { touchPointStates |= touchPoints.at(i).state(); } if (touchPointStates == Qt::TouchPointReleased) { type = QEvent::TouchEnd; } else if (touchPointStates == Qt::TouchPointPressed) { type = QEvent::TouchBegin; } else { type = QEvent::TouchUpdate; } } bool MirSurfaceItem::consumesInput() const { return m_consumesInput; } void MirSurfaceItem::setConsumesInput(bool value) { if (m_consumesInput == value) { return; } m_consumesInput = value; if (m_consumesInput) { setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton | Qt::ExtraButton1 | Qt::ExtraButton2 | Qt::ExtraButton3 | Qt::ExtraButton4 | Qt::ExtraButton5 | Qt::ExtraButton6 | Qt::ExtraButton7 | Qt::ExtraButton8 | Qt::ExtraButton9 | Qt::ExtraButton10 | Qt::ExtraButton11 | Qt::ExtraButton12 | Qt::ExtraButton13); setAcceptHoverEvents(true); } else { setAcceptedMouseButtons(Qt::NoButton); setAcceptHoverEvents(false); } Q_EMIT consumesInputChanged(value); } unity::shell::application::MirSurfaceInterface* MirSurfaceItem::surface() const { return m_surface; } void MirSurfaceItem::setSurface(unity::shell::application::MirSurfaceInterface *unitySurface) { QMutexLocker mutexLocker(&m_mutex); auto surface = static_cast(unitySurface); qCDebug(QTMIR_SURFACES).nospace() << "MirSurfaceItem::setSurface surface=" << surface; if (surface == m_surface) { return; } if (m_surface) { disconnect(m_surface, nullptr, this, nullptr); if (hasActiveFocus() && m_consumesInput && m_surface->live()) { m_surface->setFocus(false); } m_surface->unregisterView((qintptr)this); unsetCursor(); } m_surface = surface; if (m_surface) { m_surface->registerView((qintptr)this); // When a new mir frame gets posted we notify the QML engine that this item needs redrawing, // schedules call to updatePaintNode() from the rendering thread connect(m_surface, &MirSurfaceInterface::framesPosted, this, &QQuickItem::update); connect(m_surface, &MirSurfaceInterface::stateChanged, this, &MirSurfaceItem::surfaceStateChanged); connect(m_surface, &MirSurfaceInterface::liveChanged, this, &MirSurfaceItem::liveChanged); connect(m_surface, &MirSurfaceInterface::sizeChanged, this, &MirSurfaceItem::onActualSurfaceSizeChanged); connect(m_surface, &MirSurfaceInterface::cursorChanged, this, &MirSurfaceItem::setCursor); connect(m_surface, &MirSurfaceInterface::shellChromeChanged, this, &MirSurfaceItem::shellChromeChanged); Q_EMIT typeChanged(m_surface->type()); Q_EMIT liveChanged(true); Q_EMIT surfaceStateChanged(m_surface->state()); updateMirSurfaceSize(); setImplicitSize(m_surface->size().width(), m_surface->size().height()); updateMirSurfaceVisibility(); // Qt::ArrowCursor is the default when no cursor has been explicitly set, so no point forwarding it. if (m_surface->cursor().shape() != Qt::ArrowCursor) { setCursor(m_surface->cursor()); } if (m_orientationAngle) { m_surface->setOrientationAngle(*m_orientationAngle); connect(m_surface, &MirSurfaceInterface::orientationAngleChanged, this, &MirSurfaceItem::orientationAngleChanged); delete m_orientationAngle; m_orientationAngle = nullptr; } else { connect(m_surface, &MirSurfaceInterface::orientationAngleChanged, this, &MirSurfaceItem::orientationAngleChanged); Q_EMIT orientationAngleChanged(m_surface->orientationAngle()); } if (m_consumesInput) { m_surface->setFocus(hasActiveFocus()); } } update(); Q_EMIT surfaceChanged(m_surface); } void MirSurfaceItem::onCompositorSwappedBuffers() { if (Q_LIKELY(m_surface)) { m_surface->onCompositorSwappedBuffers(); } } void MirSurfaceItem::onWindowChanged(QQuickWindow *window) { if (m_window) { disconnect(m_window, nullptr, this, nullptr); } m_window = window; if (m_window) { connect(m_window, &QQuickWindow::frameSwapped, this, &MirSurfaceItem::onCompositorSwappedBuffers, Qt::DirectConnection); } } void MirSurfaceItem::releaseResources() { if (m_textureProvider) { Q_ASSERT(window()); MirSurfaceItemReleaseResourcesJob *job = new MirSurfaceItemReleaseResourcesJob; job->textureProvider = m_textureProvider; m_textureProvider = nullptr; window()->scheduleRenderJob(job, QQuickWindow::AfterSynchronizingStage); } } int MirSurfaceItem::surfaceWidth() const { return m_surfaceWidth; } void MirSurfaceItem::setSurfaceWidth(int value) { if (value != m_surfaceWidth) { m_surfaceWidth = value; scheduleMirSurfaceSizeUpdate(); Q_EMIT surfaceWidthChanged(value); } } void MirSurfaceItem::onActualSurfaceSizeChanged(const QSize &size) { setImplicitSize(size.width(), size.height()); } int MirSurfaceItem::surfaceHeight() const { return m_surfaceHeight; } void MirSurfaceItem::setSurfaceHeight(int value) { if (value != m_surfaceHeight) { m_surfaceHeight = value; scheduleMirSurfaceSizeUpdate(); Q_EMIT surfaceHeightChanged(value); } } MirSurfaceItem::FillMode MirSurfaceItem::fillMode() const { return m_fillMode; } void MirSurfaceItem::setFillMode(FillMode value) { if (m_fillMode != value) { m_fillMode = value; Q_EMIT fillModeChanged(m_fillMode); } } } // namespace qtmir #include "mirsurfaceitem.moc" ./src/modules/Unity/Application/sessionmodel.h0000644000015600001650000000167012677054330021577 0ustar jenkinsjenkins/* * Copyright (C) 2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SESSIONMODEL_H #define SESSIONMODEL_H // Local #include "objectlistmodel.h" namespace qtmir { class SessionInterface; typedef ObjectListModel SessionModel; } // namespace qtmir Q_DECLARE_METATYPE(qtmir::SessionModel*) #endif // SESSIONMODEL_H ./src/modules/Unity/Application/dbuswindowstack.cpp0000644000015600001650000000657412677054330022651 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // local #include "dbuswindowstack.h" #include "application_manager.h" // Qt #include #include namespace qtmir { DBusWindowStack::DBusWindowStack(ApplicationManager *parent) : QObject(parent) { qRegisterMetaType(); qDBusRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType< QList >(); qDBusRegisterMetaType(); qDBusRegisterMetaType< QList >(); QDBusConnection::sessionBus().registerService("com.canonical.Unity.WindowStack"); // TODO ExportScriptableSlots shouldn't be needed but without it i don't get the methods :-/ QDBusConnection::sessionBus().registerObject("/com/canonical/Unity/WindowStack", this, QDBusConnection::ExportAllSignals | QDBusConnection::ExportScriptableSlots |QDBusConnection::ExportScriptableInvokables ); } DBusWindowStack::~DBusWindowStack() { } AppIdDesktopFile DBusWindowStack::GetAppIdFromPid(unsigned int pid) { AppIdDesktopFile res; ApplicationManager *appMgr = static_cast(parent()); const Application *app = appMgr->findApplicationWithPid(pid); if (app) { res.app_id = app->appId(); res.desktop_file = app->desktopFile(); } return res; } QList DBusWindowStack::GetWindowStack() { QList res; ApplicationManager *appMgr = static_cast(parent()); const QList &applications = appMgr->list(); Q_FOREACH(Application* app, applications) { WindowInfo wi; wi.window_id = 0; wi.app_id = app->appId(); wi.focused = app->focused(); wi.stage = 0; res << wi; } return res; } QStringList DBusWindowStack::GetWindowProperties(unsigned int window_id, const QString &app_id, const QStringList &names) { Q_UNUSED(window_id); Q_UNUSED(app_id); Q_UNUSED(names); return QStringList(); } QDBusArgument &operator<<(QDBusArgument &a, const AppIdDesktopFile &aidf) { a.beginStructure(); a << aidf.app_id << aidf.desktop_file; a.endStructure(); return a; } const QDBusArgument &operator>>(const QDBusArgument &a, AppIdDesktopFile &aidf) { a.beginStructure(); a >> aidf.app_id >> aidf.desktop_file; a.endStructure(); return a; } QDBusArgument &operator<<(QDBusArgument &a, const WindowInfo &wi) { a.beginStructure(); a << wi.window_id << wi.app_id << wi.focused << wi.stage; a.endStructure(); return a; } const QDBusArgument &operator>>(const QDBusArgument &a, WindowInfo &wi) { a.beginStructure(); a >> wi.window_id >> wi.app_id >> wi.focused >> wi.stage; a.endStructure(); return a; } } // namespace qtmir ./src/modules/Unity/Application/timer.h0000644000015600001650000000444312677054330020214 0ustar jenkinsjenkins/* * Copyright (C) 2015 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * 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 QTMIR_TIMER_H #define QTMIR_TIMER_H #include "timesource.h" #include #include #include namespace qtmir { /* Defines an interface for a Timer. Useful for tests. */ class AbstractTimer : public QObject { Q_OBJECT public: AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {} virtual int interval() const = 0; virtual void setInterval(int msecs) = 0; virtual void start() { m_isRunning = true; } virtual void stop() { m_isRunning = false; } bool isRunning() const { return m_isRunning; } virtual bool isSingleShot() const = 0; virtual void setSingleShot(bool value) = 0; Q_SIGNALS: void timeout(); private: bool m_isRunning; }; /* Essentially a QTimer wrapper */ class Timer : public AbstractTimer { Q_OBJECT public: Timer(QObject *parent = nullptr); int interval() const override; void setInterval(int msecs) override; void start() override; void stop() override; bool isSingleShot() const override; void setSingleShot(bool value) override; private: QTimer m_timer; }; /* For tests */ class FakeTimer : public AbstractTimer { Q_OBJECT public: FakeTimer(const SharedTimeSource &timeSource, QObject *parent = nullptr); void update(); qint64 nextTimeoutTime() const { return m_nextTimeoutTime; } int interval() const override; void setInterval(int msecs) override; void start() override; bool isSingleShot() const override; void setSingleShot(bool value) override; private: int m_interval; bool m_singleShot; SharedTimeSource m_timeSource; qint64 m_nextTimeoutTime; }; } // namespace qtmir #endif // QTMIR_TIMER_H ./src/modules/Unity/Application/plugin.cpp0000644000015600001650000001221212677054330020716 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // Qt #include // local #include "application.h" #include "application_manager.h" #include "applicationscreenshotprovider.h" #include "mirsurfacemanager.h" #include "mirsurfaceinterface.h" #include "mirsurfaceitem.h" #include "sessionmanager.h" #include "ubuntukeyboardinfo.h" // platforms/mirserver #include // qtmir #include "logging.h" // unity-api #include using namespace qtmir; static QObject* applicationManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) { Q_UNUSED(engine); Q_UNUSED(scriptEngine); qCDebug(QTMIR_APPLICATIONS) << "applicationManagerSingleton - engine=" << engine << "scriptEngine=" << scriptEngine; return qtmir::ApplicationManager::singleton(); } static QObject* surfaceManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) { Q_UNUSED(engine); Q_UNUSED(scriptEngine); qCDebug(QTMIR_APPLICATIONS) << "surfaceManagerSingleton - engine=" << engine << "scriptEngine=" << scriptEngine; return qtmir::MirSurfaceManager::singleton(); } static QObject* sessionManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) { Q_UNUSED(engine); Q_UNUSED(scriptEngine); return qtmir::SessionManager::singleton(); } namespace { QObject* ubuntuKeyboardInfoSingleton(QQmlEngine* /*engine*/, QJSEngine* /*scriptEngine*/) { if (!UbuntuKeyboardInfo::instance()) { new UbuntuKeyboardInfo; } return UbuntuKeyboardInfo::instance(); } QObject* mirSingleton(QQmlEngine* /*engine*/, QJSEngine* /*scriptEngine*/) { return qtmir::Mir::instance(); } } // anonymous namespace class UnityApplicationPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0") virtual void registerTypes(const char* uri) { qCDebug(QTMIR_APPLICATIONS) << "UnityApplicationPlugin::registerTypes - this=" << this << "uri=" << uri; Q_ASSERT(QLatin1String(uri) == QLatin1String("Unity.Application")); qRegisterMetaType("ApplicationManager*"); //need for queueing signals qRegisterMetaType("Application*"); qRegisterMetaType("MirSurfaceInterface*"); qRegisterMetaType("Session*"); qRegisterMetaType("SessionInterface*"); qRegisterMetaType("SessionModel*"); qRegisterMetaType("MirSurfaceAttrib"); qmlRegisterUncreatableType( uri, 0, 1, "ApplicationManagerInterface", "Abstract interface. Cannot be created in QML"); qmlRegisterSingletonType( uri, 0, 1, "ApplicationManager", applicationManagerSingleton); qmlRegisterUncreatableType( uri, 0, 1, "ApplicationInfoInterface", "Abstract interface. Cannot be created in QML"); qmlRegisterUncreatableType( uri, 0, 1, "ApplicationInfo", "Application can't be instantiated"); qmlRegisterSingletonType( uri, 0, 1, "SurfaceManager", surfaceManagerSingleton); qmlRegisterSingletonType( uri, 0, 1, "SessionManager", sessionManagerSingleton); qmlRegisterUncreatableType( uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML"); qmlRegisterType(uri, 0, 1, "MirSurfaceItem"); qmlRegisterUncreatableType( uri, 0, 1, "Session", "Session can't be instantiated from QML"); qmlRegisterSingletonType( uri, 0, 1, "UbuntuKeyboardInfo", ubuntuKeyboardInfoSingleton); qmlRegisterSingletonType(uri, 0, 1, "Mir", mirSingleton); } virtual void initializeEngine(QQmlEngine *engine, const char *uri) { QQmlExtensionPlugin::initializeEngine(engine, uri); qtmir::ApplicationManager* appManager = static_cast(applicationManagerSingleton(engine, nullptr)); engine->addImageProvider(QLatin1String("application"), new qtmir::ApplicationScreenshotProvider(appManager)); } }; #include "plugin.moc" ./src/modules/Unity/Application/session_interface.h0000644000015600001650000001103712677054330022574 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SESSION_INTERFACE_H #define SESSION_INTERFACE_H #include #include // Unity API #include // local #include "objectlistmodel.h" #include "sessionmodel.h" // Qt #include namespace mir { namespace scene { class Session; class PromptSession; } } namespace qtmir { class MirSurfaceInterface; class SessionInterface : public QObject { Q_OBJECT // FIXME: Remove this once unity8 starts trully supporting multiple surfaces per applicaton. // Ie, remove this once untiy8 moves from using this property to using the surfaces one. Q_PROPERTY(MirSurfaceInterface* lastSurface READ lastSurface NOTIFY lastSurfaceChanged); Q_PROPERTY(const ObjectListModel* surfaces READ surfaces CONSTANT); Q_PROPERTY(unity::shell::application::ApplicationInfoInterface* application READ application NOTIFY applicationChanged DESIGNABLE false) Q_PROPERTY(SessionInterface* parentSession READ parentSession NOTIFY parentSessionChanged DESIGNABLE false) Q_PROPERTY(SessionModel* childSessions READ childSessions DESIGNABLE false CONSTANT) Q_PROPERTY(bool fullscreen READ fullscreen NOTIFY fullscreenChanged) Q_PROPERTY(bool live READ live NOTIFY liveChanged) public: SessionInterface(QObject *parent = 0) : QObject(parent) {} virtual ~SessionInterface() {} enum State { Starting, Running, Suspending, Suspended, Stopped }; Q_INVOKABLE virtual void release() = 0; //getters virtual QString name() const = 0; virtual unity::shell::application::ApplicationInfoInterface* application() const = 0; virtual MirSurfaceInterface* lastSurface() const = 0; virtual const ObjectListModel* surfaces() const = 0; virtual SessionInterface* parentSession() const = 0; virtual SessionModel* childSessions() const = 0; virtual State state() const = 0; virtual bool fullscreen() const = 0; virtual bool live() const = 0; virtual std::shared_ptr session() const = 0; // For MirSurface and MirSurfaceManager use virtual void registerSurface(MirSurfaceInterface* surface) = 0; virtual void removeSurface(MirSurfaceInterface* surface) = 0; // For Application use virtual void setApplication(unity::shell::application::ApplicationInfoInterface* item) = 0; virtual void suspend() = 0; virtual void resume() = 0; virtual void close() = 0; virtual void stop() = 0; // For SessionManager use virtual void addChildSession(SessionInterface* session) = 0; virtual void insertChildSession(uint index, SessionInterface* session) = 0; virtual void removeChildSession(SessionInterface* session) = 0; virtual void foreachChildSession(std::function f) const = 0; virtual std::shared_ptr activePromptSession() const = 0; virtual void foreachPromptSession(std::function&)> f) const = 0; virtual void setFullscreen(bool fullscreen) = 0; virtual void setLive(const bool) = 0; virtual void appendPromptSession(const std::shared_ptr& session) = 0; virtual void removePromptSession(const std::shared_ptr& session) = 0; Q_SIGNALS: void parentSessionChanged(SessionInterface*); void applicationChanged(unity::shell::application::ApplicationInfoInterface* application); void stateChanged(State state); void fullscreenChanged(bool fullscreen); void liveChanged(bool live); void lastSurfaceChanged(MirSurfaceInterface* surface); }; } // namespace qtmir Q_DECLARE_METATYPE(qtmir::SessionInterface*) Q_DECLARE_METATYPE(qtmir::ObjectListModel*) #endif // SESSION_INTERFACE_H ./src/modules/Unity/Application/timesource.h0000644000015600001650000000325612677054330021254 0ustar jenkinsjenkins/* * Copyright (C) 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, as * published by the Free Software Foundation; either version 2.1 or 3.0 * of the License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR * PURPOSE. See the applicable version of the GNU Lesser General Public * License for more details. * * You should have received a copy of both the GNU Lesser General Public * License along with this program. If not, see */ #ifndef QTMIR_TIMESOURCE_H #define QTMIR_TIMESOURCE_H #include namespace qtmir { /* Interface for a time source. */ class TimeSource { public: virtual ~TimeSource() {} /* Returns the current time in milliseconds since some reference time in the past. */ virtual qint64 msecsSinceReference() = 0; }; typedef QSharedPointer SharedTimeSource; /* Implementation of a time source */ class RealTimeSourcePrivate; class RealTimeSource : public TimeSource { public: RealTimeSource(); virtual ~RealTimeSource(); qint64 msecsSinceReference() override; private: RealTimeSourcePrivate *d; }; /* A fake time source, useful for tests */ class FakeTimeSource : public TimeSource { public: FakeTimeSource() { m_msecsSinceReference = 0; } qint64 msecsSinceReference() override { return m_msecsSinceReference; } qint64 m_msecsSinceReference; }; } // namespace qtmir #endif // QTMIR_TIMESOURCE_H ./src/modules/Unity/Application/mirsurfacemanager.cpp0000644000015600001650000001422112677054330023115 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mirsurfacemanager.h" // Qt #include #include // local #include "mirsurface.h" #include "sessionmanager.h" #include "application_manager.h" #include "tracepoints.h" // generated from tracepoints.tp // common #include // QPA mirserver #include "nativeinterface.h" #include "mirserver.h" #include "sessionlistener.h" #include "logging.h" #include "creationhints.h" Q_LOGGING_CATEGORY(QTMIR_SURFACES, "qtmir.surfaces") namespace ms = mir::scene; namespace qtmir { MirSurfaceManager *MirSurfaceManager::instance = nullptr; void connectToSessionListener(MirSurfaceManager *manager, SessionListener *listener) { QObject::connect(listener, &SessionListener::sessionCreatedSurface, manager, &MirSurfaceManager::onSessionCreatedSurface); QObject::connect(listener, &SessionListener::sessionDestroyingSurface, manager, &MirSurfaceManager::onSessionDestroyingSurface); } MirSurfaceManager* MirSurfaceManager::singleton() { if (!instance) { NativeInterface *nativeInterface = dynamic_cast(QGuiApplication::platformNativeInterface()); if (!nativeInterface) { qCritical("ERROR: Unity.Application QML plugin requires use of the 'mirserver' QPA plugin"); QGuiApplication::quit(); return nullptr; } SessionListener *sessionListener = static_cast(nativeInterface->nativeResourceForIntegration("SessionListener")); MirShell *shell = static_cast(nativeInterface->nativeResourceForIntegration("Shell")); instance = new MirSurfaceManager(nativeInterface->m_mirServer, shell, SessionManager::singleton()); connectToSessionListener(instance, sessionListener); } return instance; } MirSurfaceManager::MirSurfaceManager( const QSharedPointer& mirServer, MirShell *shell, SessionManager* sessionManager, QObject *parent) : QObject(parent) , m_mirServer(mirServer) , m_shell(shell) , m_sessionManager(sessionManager) { qCDebug(QTMIR_SURFACES) << "MirSurfaceManager::MirSurfaceManager - this=" << this; setObjectName("qtmir::SurfaceManager"); } MirSurfaceManager::~MirSurfaceManager() { qCDebug(QTMIR_SURFACES) << "MirSurfaceManager::~MirSurfaceManager - this=" << this; m_mirSurfaceToQmlSurfaceHash.clear(); } void MirSurfaceManager::onSessionCreatedSurface(const mir::scene::Session *mirSession, const std::shared_ptr &surface, const std::shared_ptr &observer, qtmir::CreationHints creationHints) { qCDebug(QTMIR_SURFACES) << "MirSurfaceManager::onSessionCreatedSurface - mirSession=" << mirSession << "surface=" << surface.get() << "surface.name=" << surface->name().c_str() << "creationHints=" << creationHints.toString(); SessionInterface* session = m_sessionManager->findSession(mirSession); auto qmlSurface = new MirSurface(surface, session, m_shell, observer, creationHints); { QMutexLocker lock(&m_mutex); m_mirSurfaceToQmlSurfaceHash.insert(surface.get(), qmlSurface); } if (session) session->registerSurface(qmlSurface); if (qmlSurface->type() == Mir::InputMethodType) { m_inputMethodSurface = qmlSurface; Q_EMIT inputMethodSurfaceChanged(); } // Only notify QML of surface creation once it has drawn its first frame. connect(qmlSurface, &MirSurfaceInterface::firstFrameDrawn, this, [=]() { tracepoint(qtmir, firstFrameDrawn); Q_EMIT surfaceCreated(qmlSurface); }); // clean up after MirSurface is destroyed connect(qmlSurface, &QObject::destroyed, this, [&](QObject *obj) { auto qmlSurface = static_cast(obj); { QMutexLocker lock(&m_mutex); m_mirSurfaceToQmlSurfaceHash.remove(m_mirSurfaceToQmlSurfaceHash.key(qmlSurface)); } tracepoint(qtmir, surfaceDestroyed); }); tracepoint(qtmir, surfaceCreated); } void MirSurfaceManager::onSessionDestroyingSurface(const mir::scene::Session *session, const std::shared_ptr &surface) { qCDebug(QTMIR_SURFACES) << "MirSurfaceManager::onSessionDestroyingSurface - session=" << session << "surface=" << surface.get() << "surface.name=" << surface->name().c_str(); MirSurfaceInterface* qmlSurface = nullptr; { QMutexLocker lock(&m_mutex); auto it = m_mirSurfaceToQmlSurfaceHash.find(surface.get()); if (it != m_mirSurfaceToQmlSurfaceHash.end()) { qmlSurface = it.value(); m_mirSurfaceToQmlSurfaceHash.erase(it); } else { qCritical() << "MirSurfaceManager::onSessionDestroyingSurface: unable to find MirSurface corresponding" << "to surface=" << surface.get() << "surface.name=" << surface->name().c_str(); return; } } if (qmlSurface->type() == Mir::InputMethodType) { m_inputMethodSurface = nullptr; Q_EMIT inputMethodSurfaceChanged(); } qmlSurface->setLive(false); Q_EMIT surfaceDestroyed(qmlSurface); } MirSurfaceInterface* MirSurfaceManager::inputMethodSurface() const { return m_inputMethodSurface; } } // namespace qtmir ./src/modules/Unity/Application/gscopedpointer.h0000644000015600001650000000571312677054330022122 0ustar jenkinsjenkins/* * Copyright (C) 2011-2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * * Authors: * - Aurélien Gâteau */ #ifndef GSCOPEDPOINTER_H #define GSCOPEDPOINTER_H // Local // Qt #include // GLib #include /** * Helper class for GScopedPointer */ template class GScopedPointerDeleter { public: static void cleanup(T* ptr) { if (ptr) { cleanup_fcn(ptr); } } }; /** * A GScopedPointer works like a QScopedPointer, except the cleanup static * method is replaced with a cleanup function, making it useful to handle C * structs whose cleanup function prototype is "void cleanup(T*)" * * Best way to use it is to define a typedef for your C struct, like this: * * typedef GScopedPointer GFooPointer; */ template class GScopedPointer : public QScopedPointer > { public: GScopedPointer(T* ptr = 0) : QScopedPointer >(ptr) {} }; /** * Helper class for GObjectScopedPointer */ template class GObjectScopedPointerDeleter { public: static void cleanup(T* ptr) { if (ptr) { cleanup_fcn(ptr); } } }; /** * A GObjectScopedPointer is similar to a GScopedPointer. The only difference * is its cleanup function signature is "void cleanup(gpointer)" and defaults to * g_object_unref(), making it useful for GObject-based classes. * * You can use it directly like this: * * GObjectScopedPointer foo; * * Or define a typedef for your class: * * typedef GObjectScopedPointer GFooPointer; * * Note: GObjectScopedPointer does *not* call gobject_ref() when assigned a * pointer. */ template class GObjectScopedPointer : public QScopedPointer > { public: GObjectScopedPointer(T* ptr = 0) : QScopedPointer >(ptr) {} }; // A Generic GObject pointer typedef GObjectScopedPointer GObjectPointer; // Take advantage of the cleanup signature of GObjectScopedPointer to define // a GCharPointer typedef GObjectScopedPointer GCharPointer; #endif /* GSCOPEDPOINTER_H */ ./src/modules/Unity/Application/mirsurface.h0000644000015600001650000001371112677054330021232 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QTMIR_MIRSURFACE_H #define QTMIR_MIRSURFACE_H #include "mirsurfaceinterface.h" // Qt #include #include #include #include #include #include #include #include #include "mirbuffersgtexture.h" #include "session.h" // mirserver #include "creationhints.h" // mir #include namespace mir { namespace shell { class Shell; }} class SurfaceObserver; namespace qtmir { class MirSurface : public MirSurfaceInterface { Q_OBJECT public: MirSurface(std::shared_ptr surface, SessionInterface* session, mir::shell::Shell *shell, std::shared_ptr observer, const CreationHints &); virtual ~MirSurface(); //// // unity::shell::application::MirSurfaceInterface Mir::Type type() const override; QString name() const override; QSize size() const override; void resize(int width, int height) override; void resize(const QSize &size) override { resize(size.width(), size.height()); } Mir::State state() const override; void setState(Mir::State qmlState) override; bool live() const override; bool visible() const override; Mir::OrientationAngle orientationAngle() const override; void setOrientationAngle(Mir::OrientationAngle angle) override; int minimumWidth() const override; int minimumHeight() const override; int maximumWidth() const override; int maximumHeight() const override; int widthIncrement() const override; int heightIncrement() const override; //// // qtmir::MirSurfaceInterface void setLive(bool value) override; bool isFirstFrameDrawn() const override { return m_firstFrameDrawn; } void stopFrameDropper() override; void startFrameDropper() override; bool isBeingDisplayed() const override; void registerView(qintptr viewId) override; void unregisterView(qintptr viewId) override; void setViewVisibility(qintptr viewId, bool visible) override; // methods called from the rendering (scene graph) thread: QSharedPointer texture() override; QSGTexture *weakTexture() const override { return m_texture.data(); } bool updateTexture() override; unsigned int currentFrameNumber() const override; bool numBuffersReadyForCompositor() override; // end of methods called from the rendering (scene graph) thread void setFocus(bool focus) override; void close() override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void hoverEnterEvent(QHoverEvent *event) override; void hoverLeaveEvent(QHoverEvent *event) override; void hoverMoveEvent(QHoverEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; void touchEvent(Qt::KeyboardModifiers qmods, const QList &qtTouchPoints, Qt::TouchPointStates qtTouchPointStates, ulong qtTimestamp) override; QString appId() const override; QCursor cursor() const override; Mir::ShellChrome shellChrome() const override; QString keymapLayout() const override; QString keymapVariant() const override; Q_INVOKABLE void setKeymap(const QString &layout, const QString &variant) override; public Q_SLOTS: void onCompositorSwappedBuffers() override; void setMinimumWidth(int) override; void setMinimumHeight(int) override; void setMaximumWidth(int) override; void setMaximumHeight(int) override; void setWidthIncrement(int) override; void setHeightIncrement(int) override; void setShellChrome(Mir::ShellChrome shellChrome) override; private Q_SLOTS: void dropPendingBuffer(); void onAttributeChanged(const MirSurfaceAttrib, const int); void onFramesPostedObserved(); void onSessionDestroyed(); void emitSizeChanged(); void onKeymapChanged(const QString &layout, const QString &variant); void setCursor(const QCursor &cursor); private: void syncSurfaceSizeWithItemSize(); bool clientIsRunning() const; void updateVisibility(); std::shared_ptr m_surface; QPointer m_session; mir::shell::Shell *const m_shell; bool m_firstFrameDrawn; //FIXME - have to save the state as Mir has no getter for it (bug:1357429) Mir::OrientationAngle m_orientationAngle; QTimer m_frameDropperTimer; mutable QMutex m_mutex; // Lives in the rendering (scene graph) thread QWeakPointer m_texture; bool m_textureUpdated; unsigned int m_currentFrameNumber; bool m_live; struct View { bool visible; }; QHash m_views; std::shared_ptr m_surfaceObserver; QSize m_size; QPair m_keyMap; // pair of layout+variant QCursor m_cursor; Mir::ShellChrome m_shellChrome; int m_minimumWidth{0}; int m_minimumHeight{0}; int m_maximumWidth{0}; int m_maximumHeight{0}; int m_widthIncrement{0}; int m_heightIncrement{0}; }; } // namespace qtmir #endif // QTMIR_MIRSURFACE_H ./src/modules/Unity/Application/settings.cpp0000644000015600001650000000213212677054330021260 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // Ubuntu #include #include // local #include "settings.h" namespace qtmir { Settings::Settings(QObject *parent) :SettingsInterface(parent) ,m_settings(new QGSettings("com.canonical.qtmir", "/com/canonical/qtmir/")) { connect(m_settings, &QGSettings::changed, this, &Settings::changed); } QVariant Settings::get(const QString &key) const { return m_settings->get(key); } } // namespace qtmir ./src/modules/Unity/Application/mirsurfaceinterface.h0000644000015600001650000000700612677054330023113 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QTMIR_MIRSURFACEINTERFACE_H #define QTMIR_MIRSURFACEINTERFACE_H // Unity API #include #include "session_interface.h" // Qt #include #include #include class QHoverEvent; class QMouseEvent; class QKeyEvent; class QSGTexture; namespace qtmir { class MirSurfaceInterface : public unity::shell::application::MirSurfaceInterface { Q_OBJECT public: MirSurfaceInterface(QObject *parent = nullptr) : unity::shell::application::MirSurfaceInterface(parent) {} virtual ~MirSurfaceInterface() {} virtual void setLive(bool value) = 0; virtual bool isFirstFrameDrawn() const = 0; virtual void stopFrameDropper() = 0; virtual void startFrameDropper() = 0; virtual bool isBeingDisplayed() const = 0; virtual void registerView(qintptr viewId) = 0; virtual void unregisterView(qintptr viewId) = 0; virtual void setViewVisibility(qintptr viewId, bool visible) = 0; // methods called from the rendering (scene graph) thread: virtual QSharedPointer texture() = 0; virtual QSGTexture *weakTexture() const = 0; virtual bool updateTexture() = 0; virtual unsigned int currentFrameNumber() const = 0; virtual bool numBuffersReadyForCompositor() = 0; // end of methods called from the rendering (scene graph) thread virtual void setFocus(bool focus) = 0; virtual void close() = 0; virtual void mousePressEvent(QMouseEvent *event) = 0; virtual void mouseMoveEvent(QMouseEvent *event) = 0; virtual void mouseReleaseEvent(QMouseEvent *event) = 0; virtual void hoverEnterEvent(QHoverEvent *event) = 0; virtual void hoverLeaveEvent(QHoverEvent *event) = 0; virtual void hoverMoveEvent(QHoverEvent *event) = 0; virtual void wheelEvent(QWheelEvent *event) = 0; virtual void keyPressEvent(QKeyEvent *event) = 0; virtual void keyReleaseEvent(QKeyEvent *event) = 0; virtual void touchEvent(Qt::KeyboardModifiers qmods, const QList &qtTouchPoints, Qt::TouchPointStates qtTouchPointStates, ulong qtTimestamp) = 0; virtual QString appId() const = 0; virtual QCursor cursor() const = 0; Q_SIGNALS: void cursorChanged(const QCursor &cursor); public Q_SLOTS: virtual void onCompositorSwappedBuffers() = 0; virtual void setMinimumWidth(int) = 0; virtual void setMinimumHeight(int) = 0; virtual void setMaximumWidth(int) = 0; virtual void setMaximumHeight(int) = 0; virtual void setWidthIncrement(int) = 0; virtual void setHeightIncrement(int) = 0; virtual void setShellChrome(Mir::ShellChrome shellChrome) = 0; Q_SIGNALS: void firstFrameDrawn(); void framesPosted(); void isBeingDisplayedChanged(); void frameDropped(); }; } // namespace qtmir #endif // QTMIR_MIRSURFACEINTERFACE_H ./src/modules/Unity/Application/tracepoints.tp0000644000015600001650000000166012677054330021621 0ustar jenkinsjenkins#include TRACEPOINT_EVENT(qtmir, startApplication, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmir, onProcessStarting, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmir, authorizeSession, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmir, onProcessStopped, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmir, surfaceCreated, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmir, surfaceDestroyed, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmir, firstFrameDrawn, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmir, appIdHasProcessId_start, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmir, appIdHasProcessId_end, TP_ARGS(int, found), TP_FIELDS(ctf_integer(int, found, found))) TRACEPOINT_EVENT(qtmir, touchEventConsume_start, TP_ARGS(int64_t, event_time), TP_FIELDS(ctf_integer(int64_t, event_time, event_time))) TRACEPOINT_EVENT(qtmir, touchEventConsume_end, TP_ARGS(int64_t, event_time), TP_FIELDS(ctf_integer(int64_t, event_time, event_time))) ./src/modules/Unity/Application/ubuntukeyboardinfo.cpp0000644000015600001650000001201012677054330023333 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "ubuntukeyboardinfo.h" #include namespace qtmir { namespace { const int gConnectionAttemptIntervalMs = 5000; const int gMaxConsecutiveAttempts = 10; const char gServerName[] = "ubuntu-keyboard-info"; } UbuntuKeyboardInfo* UbuntuKeyboardInfo::m_instance = nullptr; UbuntuKeyboardInfo* UbuntuKeyboardInfo::instance() { return m_instance; } UbuntuKeyboardInfo::UbuntuKeyboardInfo(QObject *parent) : QObject(parent), m_consecutiveAttempts(0), m_lastWidth(0), m_lastHeight(0) { if (m_instance) { qFatal("Cannot have more than one instance of UbuntuKeyboardInfo simultaneously."); } m_instance = this; connect(&m_socket, &QLocalSocket::stateChanged, this, &UbuntuKeyboardInfo::onSocketStateChanged); connect(&m_socket, &QIODevice::readyRead, this, &UbuntuKeyboardInfo::readAllBytesFromSocket); buildSocketFilePath(); typedef void (QLocalSocket::*MemberFunctionType)(QLocalSocket::LocalSocketError); MemberFunctionType funcPointer = &QLocalSocket::error; connect(&m_socket, funcPointer, this, &UbuntuKeyboardInfo::onSocketError); m_connectionRetryTimer.setInterval(gConnectionAttemptIntervalMs); m_connectionRetryTimer.setSingleShot(true); connect(&m_connectionRetryTimer, &QTimer::timeout, this, &UbuntuKeyboardInfo::tryConnectingToServer); tryConnectingToServer(); } UbuntuKeyboardInfo::~UbuntuKeyboardInfo() { // Make sure we don't get onSocketStateChanged() called during // destruction. m_socket.disconnect(this); Q_ASSERT(m_instance); m_instance = nullptr; } void UbuntuKeyboardInfo::tryConnectingToServer() { ++m_consecutiveAttempts; Q_ASSERT(!m_socketFilePath.isEmpty()); m_socket.connectToServer(m_socketFilePath, QIODevice::ReadOnly); } void UbuntuKeyboardInfo::onSocketStateChanged(QLocalSocket::LocalSocketState socketState) { switch (socketState) { case QLocalSocket::UnconnectedState: retryConnection(); break; case QLocalSocket::ConnectedState: m_consecutiveAttempts = 0; break; default: break; } } void UbuntuKeyboardInfo::onSocketError(QLocalSocket::LocalSocketError socketError) { Q_UNUSED(socketError); qWarning() << "UbuntuKeyboardInfo - socket error:" << m_socket.errorString(); } void UbuntuKeyboardInfo::retryConnection() { // Polling every gConnectionAttemptIntervalMs. Not the best approach but could be worse. if (m_consecutiveAttempts < gMaxConsecutiveAttempts) { if (!m_connectionRetryTimer.isActive()) { m_connectionRetryTimer.start(); } } else { qCritical() << "Failed to connect to" << m_socketFilePath << "after" << m_consecutiveAttempts << "failed attempts"; // it shouldn't be running, but just to be sure. m_connectionRetryTimer.stop(); } } void UbuntuKeyboardInfo::readAllBytesFromSocket() { while (m_socket.bytesAvailable() > 0) { readInfoFromSocket(); } } void UbuntuKeyboardInfo::readInfoFromSocket() { const size_t sharedInfoSize = sizeof(struct SharedInfo); QByteArray bytes = m_socket.read(sharedInfoSize); if (bytes.size() != sharedInfoSize) { qWarning() << "UbuntuKeyboardInfo: expected to receive" << sharedInfoSize << "but got" << bytes.size(); return; } { struct SharedInfo *sharedInfo = reinterpret_cast(bytes.data()); if (m_lastX != sharedInfo->keyboardX) { m_lastX = sharedInfo->keyboardX; Q_EMIT xChanged(m_lastX); } if (m_lastY != sharedInfo->keyboardY) { m_lastY = sharedInfo->keyboardY; Q_EMIT yChanged(m_lastY); } if (m_lastWidth != sharedInfo->keyboardWidth) { m_lastWidth = sharedInfo->keyboardWidth; Q_EMIT widthChanged(m_lastWidth); } if (m_lastHeight != sharedInfo->keyboardHeight) { m_lastHeight = sharedInfo->keyboardHeight; Q_EMIT heightChanged(m_lastHeight); } } } void UbuntuKeyboardInfo::buildSocketFilePath() { char *xdgRuntimeDir = getenv("XDG_RUNTIME_DIR"); if (xdgRuntimeDir) { m_socketFilePath = QDir(xdgRuntimeDir).filePath(gServerName); } else { m_socketFilePath = QDir("/tmp").filePath(gServerName); } } } // namespace qtmir ./src/modules/Unity/Application/proc_info.cpp0000644000015600001650000000346312677054330021406 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "proc_info.h" // Qt #include #include namespace qtmir { ProcInfo::~ProcInfo() { } std::unique_ptr ProcInfo::commandLine(pid_t pid) { QFile cmdline(QString("/proc/%1/cmdline").arg(pid)); if (!cmdline.open(QIODevice::ReadOnly | QIODevice::Text)) { return nullptr; } return std::unique_ptr(new CommandLine{ cmdline.readLine().replace('\0', ' ') }); } QStringList ProcInfo::CommandLine::asStringList() const { return QString(m_command.data()).split(' '); } bool ProcInfo::CommandLine::startsWith(char const* prefix) const { return m_command.startsWith(prefix); } bool ProcInfo::CommandLine::contains(char const* prefix) const { return m_command.contains(prefix); } QString ProcInfo::CommandLine::getParameter(const char* name) const { QString pattern = QRegularExpression::escape(name) + "(\\S+)"; QRegularExpression regExp(pattern); QRegularExpressionMatch regExpMatch = regExp.match(m_command); if (!regExpMatch.hasMatch()) { return QString(); } return QString(regExpMatch.captured(1)); } } // namespace qtmir ./src/modules/Unity/Application/qmldir0000644000015600001650000000006712677054330020134 0ustar jenkinsjenkinsmodule Unity.Application plugin unityapplicationplugin ./src/modules/Unity/Application/upstart/0000755000015600001650000000000012677054330020420 5ustar jenkinsjenkins./src/modules/Unity/Application/upstart/taskcontroller.h0000644000015600001650000000277212677054330023647 0ustar jenkinsjenkins/* * Copyright (C) 2013,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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * */ #ifndef QTMIR_UPSTART_TASK_CONTROLLER_H #define QTMIR_UPSTART_TASK_CONTROLLER_H #include "../taskcontroller.h" namespace qtmir { namespace upstart { class TaskController : public qtmir::TaskController { public: TaskController(); ~TaskController(); pid_t primaryPidForAppId(const QString& appId) override; bool appIdHasProcessId(const QString& appId, pid_t pid) override; bool stop(const QString& appId) override; bool start(const QString& appId, const QStringList& arguments) override; bool suspend(const QString& appId) override; bool resume(const QString& appId) override; QFileInfo findDesktopFileForAppId(const QString &appId) const override; private: struct Private; QScopedPointer impl; }; } // namespace upstart } // namespace qtmir #endif // QTMIR_UPSTART_TASK_CONTROLLER_H ./src/modules/Unity/Application/upstart/taskcontroller.cpp0000644000015600001650000002243612677054330024201 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * */ #include "taskcontroller.h" // qtmir #include // Qt #include // upstart extern "C" { #include "ubuntu-app-launch.h" } namespace qtmir { namespace upstart { struct TaskController::Private { UbuntuAppLaunchAppObserver preStartCallback = nullptr; UbuntuAppLaunchAppObserver startedCallback = nullptr; UbuntuAppLaunchAppObserver stopCallback = nullptr; UbuntuAppLaunchAppObserver focusCallback = nullptr; UbuntuAppLaunchAppObserver resumeCallback = nullptr; UbuntuAppLaunchAppPausedResumedObserver pausedCallback = nullptr; UbuntuAppLaunchAppFailedObserver failureCallback = nullptr; }; namespace { /** * @brief toShortAppIdIfPossible * @param appId - any string that you think is an appId * @return if a valid appId was input, a shortened appId is returned, else returns the input string unaltered */ QString toShortAppIdIfPossible(const QString &appId) { gchar *package, *application; if (ubuntu_app_launch_app_id_parse(appId.toLatin1().constData(), &package, &application, nullptr)) { // is long appId, so assemble its short appId QString shortAppId = QString("%1_%2").arg(package).arg(application); g_free(package); g_free(application); return shortAppId; } else { return appId; } } /** * @brief toLongAppIdIfPossible * @param shortAppId - any string that you think is a short appId * @return if valid short appId was input, the corresponding long appId is returned. If a long appId was * entered, it is returned unchanged. Anything else is also returned unchanged. */ QString toLongAppIdIfPossible(const QString &shortAppId) { if (ubuntu_app_launch_app_id_parse(shortAppId.toLatin1().constData(), nullptr, nullptr, nullptr)) { // then we got a long appId after all, just return it return shortAppId; } else { // try to parse the string in the form "$package_$application" QRegExp shortAppIdMask("[a-z0-9][a-z0-9+.-]+_[a-zA-Z0-9+.-]+"); if (!shortAppIdMask.exactMatch(shortAppId)) { // input string not a short appId, so just return it unchanged return shortAppId; } // ask upstart for the long appId corresponding to this short appId QStringList parts = shortAppId.split("_"); gchar *longAppId; longAppId = ubuntu_app_launch_triplet_to_app_id(parts.first().toLatin1().constData(), parts.last().toLatin1().constData(), nullptr); if (longAppId == nullptr) { // was unable to construct a long appId from the short appId, return input unchanged return shortAppId; } else { QString appId(longAppId); g_free(longAppId); return appId; } } } } // namespace TaskController::TaskController() : qtmir::TaskController(), impl(new Private()) { impl->preStartCallback = [](const gchar * appId, gpointer userData) { auto thiz = static_cast(userData); Q_EMIT(thiz->processStarting(toShortAppIdIfPossible(appId))); }; impl->startedCallback = [](const gchar * appId, gpointer userData) { auto thiz = static_cast(userData); Q_EMIT(thiz->applicationStarted(toShortAppIdIfPossible(appId))); }; impl->stopCallback = [](const gchar * appId, gpointer userData) { auto thiz = static_cast(userData); Q_EMIT(thiz->processStopped(toShortAppIdIfPossible(appId))); }; impl->focusCallback = [](const gchar * appId, gpointer userData) { auto thiz = static_cast(userData); Q_EMIT(thiz->focusRequested(toShortAppIdIfPossible(appId))); }; impl->resumeCallback = [](const gchar * appId, gpointer userData) { auto thiz = static_cast(userData); Q_EMIT(thiz->resumeRequested(toShortAppIdIfPossible(appId))); }; impl->pausedCallback = [](const gchar * appId, GPid *, gpointer userData) { auto thiz = static_cast(userData); Q_EMIT(thiz->processSuspended(toShortAppIdIfPossible(appId))); }; impl->failureCallback = [](const gchar * appId, UbuntuAppLaunchAppFailed failureType, gpointer userData) { TaskController::Error error; switch(failureType) { case UBUNTU_APP_LAUNCH_APP_FAILED_CRASH: error = TaskController::Error::APPLICATION_CRASHED; case UBUNTU_APP_LAUNCH_APP_FAILED_START_FAILURE: error = TaskController::Error::APPLICATION_FAILED_TO_START; } auto thiz = static_cast(userData); Q_EMIT(thiz->processFailed(toShortAppIdIfPossible(appId), error)); }; ubuntu_app_launch_observer_add_app_starting(impl->preStartCallback, this); ubuntu_app_launch_observer_add_app_started(impl->startedCallback, this); ubuntu_app_launch_observer_add_app_stop(impl->stopCallback, this); ubuntu_app_launch_observer_add_app_focus(impl->focusCallback, this); ubuntu_app_launch_observer_add_app_resume(impl->resumeCallback, this); ubuntu_app_launch_observer_add_app_paused(impl->pausedCallback, this); ubuntu_app_launch_observer_add_app_failed(impl->failureCallback, this); } TaskController::~TaskController() { ubuntu_app_launch_observer_delete_app_starting(impl->preStartCallback, this); ubuntu_app_launch_observer_delete_app_started(impl->startedCallback, this); ubuntu_app_launch_observer_delete_app_stop(impl->stopCallback, this); ubuntu_app_launch_observer_delete_app_focus(impl->focusCallback, this); ubuntu_app_launch_observer_delete_app_resume(impl->resumeCallback, this); ubuntu_app_launch_observer_delete_app_paused(impl->pausedCallback, this); ubuntu_app_launch_observer_delete_app_failed(impl->failureCallback, this); } pid_t TaskController::primaryPidForAppId(const QString& appId) { GPid pid = ubuntu_app_launch_get_primary_pid(toLongAppIdIfPossible(appId).toLatin1().constData()); if (!pid) qDebug() << "TaskController::primaryPidForAppId FAILED to get PID for appId=" << appId; return pid; } bool TaskController::appIdHasProcessId(const QString& appId, pid_t pid) { return ubuntu_app_launch_pid_in_app_id(pid, toLongAppIdIfPossible(appId).toLatin1().constData()); } bool TaskController::stop(const QString& appId) { auto result = ubuntu_app_launch_stop_application(toLongAppIdIfPossible(appId).toLatin1().constData()); if (!result) qDebug() << "TaskController::stopApplication FAILED to stop appId=" << appId; return result; } bool TaskController::start(const QString& appId, const QStringList& arguments) { // Convert arguments QStringList into format suitable for ubuntu-app-launch // The last item should be null, which is done by g_new0, we just don't fill it. auto upstartArgs = g_new0(gchar *, arguments.length() + 1); for (int i=0; i(upstartArgs)); g_strfreev(upstartArgs); if (!result) qDebug() << "TaskController::start FAILED to start appId" << appId; return result; } bool TaskController::suspend(const QString& appId) { auto result = ubuntu_app_launch_pause_application(toLongAppIdIfPossible(appId).toLatin1().constData()); if (!result) qDebug() << "TaskController::pauseApplication FAILED to pause appId=" << appId; return result; } bool TaskController::resume(const QString& appId) { auto result = ubuntu_app_launch_resume_application(toLongAppIdIfPossible(appId).toLatin1().constData()); if (!result) qDebug() << "TaskController::resumeApplication FAILED to resume appId=" << appId; return result; } QFileInfo TaskController::findDesktopFileForAppId(const QString &appId) const { qCDebug(QTMIR_APPLICATIONS) << "TaskController::desktopFilePathForAppId - appId=" << appId; // Search for the correct desktop file using a simple heuristic int dashPos = -1; QString helper = toLongAppIdIfPossible(appId); QString desktopFile; do { if (dashPos != -1) { helper = helper.replace(dashPos, 1, '/'); } desktopFile = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QString("%1.desktop").arg(helper)); if (!desktopFile.isEmpty()) return desktopFile; dashPos = helper.indexOf("-"); } while (dashPos != -1); return QFileInfo(); } } // namespace upstart } // namespace qtmir ./src/modules/Unity/Application/CMakeLists.txt0000644000015600001650000000510412677054330021456 0ustar jenkinsjenkinsinclude_directories( ${GLIB_INCLUDE_DIRS} ${GIO_INCLUDE_DIRS} ${GIO_UNIX_INCLUDE_DIRS} ${MIRSERVER_INCLUDE_DIRS} ${MIRRENDERERGLDEV_INCLUDE_DIRS} ${PROCESS_CPP_INCLUDE_DIRS} ${UBUNTU_PLATFORM_API_INCLUDE_DIRS} ${UBUNTU_APP_LAUNCH_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${CMAKE_SOURCE_DIR}/src/common ${GSETTINGS_QT_INCLUDE_DIRS} ${LTTNG_INCLUDE_DIRS} ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${Qt5Qml_PRIVATE_INCLUDE_DIRS} ${Qt5Quick_PRIVATE_INCLUDE_DIRS} ) # We have to remove -pedantic in order to compile tracepoints.c string (REPLACE " -pedantic " " " CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) # Needed to compile tracepoints in C99 mode. add_definitions(-DBYTE_ORDER=__BYTE_ORDER) set(QMLMIRPLUGIN_SRC application_manager.cpp application.cpp ../../../common/abstractdbusservicemonitor.cpp ../../../common/debughelpers.cpp desktopfilereader.cpp plugin.cpp applicationscreenshotprovider.cpp dbuswindowstack.cpp mirsurfacemanager.cpp ubuntukeyboardinfo.cpp mirsurface.cpp mirsurfaceinterface.h mirsurfaceitem.cpp mirbuffersgtexture.cpp proc_info.cpp session.cpp sessionmanager.cpp sharedwakelock.cpp upstart/taskcontroller.cpp timer.cpp timesource.cpp tracepoints.c settings.cpp # We need to run moc on these headers ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceItemInterface.h ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h # Feed the automoc monster session_interface.h taskcontroller.h settings_interface.h ) add_library(unityapplicationplugin SHARED ${QMLMIRPLUGIN_SRC} ) target_link_libraries( unityapplicationplugin ${CMAKE_THREAD_LIBS_INIT} ${GLIB_LDFLAGS} ${UBUNTU_PLATFORM_API_LDFLAGS} ${MIRSERVER_LDFLAGS} ${PROCESS_CPP_LDFLAGS} ${UBUNTU_APP_LAUNCH_LDFLAGS} ${LTTNG_LDFLAGS} ${GSETTINGS_QT_LDFLAGS} ${GL_LIBRARIES} Qt5::Core Qt5::DBus Qt5::Qml Qt5::Quick qpa-mirserver ) # Generate tracepoints.c and .h from tracepoints.tp include(UseLttngGenTp) add_lttng_gen_tp(NAME tracepoints) # install add_qml_plugin(Unity.Application 0.1 Unity/Application TARGETS unityapplicationplugin) install(FILES com.canonical.qtmir.gschema.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/glib-2.0/schemas) ./src/modules/Unity/Application/application_manager.h0000644000015600001650000001355012677054330023070 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef APPLICATIONMANAGER_H #define APPLICATIONMANAGER_H // std #include // Qt #include #include // Unity API #include // local #include "application.h" #include "desktopfilereader.h" #include "taskcontroller.h" namespace mir { namespace scene { class Session; class Surface; class PromptSession; } } class MirServer; namespace qtmir { class DBusWindowStack; class MirSurfaceManager; class ProcInfo; class SharedWakelock; class SettingsInterface; class ApplicationManager : public unity::shell::application::ApplicationManagerInterface { Q_OBJECT Q_ENUMS(MoreRoles) // TODO: Move to unity::shell::application::ApplicationManagerInterface Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged) public: class Factory { public: ApplicationManager* create(); }; // FIXME: these roles should be added to unity-api and removed from here enum MoreRoles { RoleSession = RoleExemptFromLifecycle+1, RoleFullscreen, }; static ApplicationManager* singleton(); explicit ApplicationManager( const QSharedPointer &mirServer, const QSharedPointer &taskController, const QSharedPointer &sharedWakelock, const QSharedPointer &desktopFileReaderFactory, const QSharedPointer &processInfo, const QSharedPointer &settings, QObject *parent = 0); virtual ~ApplicationManager(); // ApplicationManagerInterface QString focusedApplicationId() const override; Q_INVOKABLE qtmir::Application* get(int index) const override; Q_INVOKABLE qtmir::Application* findApplication(const QString &appId) const override; Q_INVOKABLE bool requestFocusApplication(const QString &appId) override; Q_INVOKABLE bool focusApplication(const QString &appId) override; Q_INVOKABLE void unfocusCurrentApplication() override; Q_INVOKABLE qtmir::Application* startApplication(const QString &appId, const QStringList &arguments = QStringList()) override; Q_INVOKABLE bool stopApplication(const QString &appId) override; // QAbstractListModel int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role) const override; Q_INVOKABLE void move(int from, int to); bool isEmpty() const { return rowCount() == 0; } const QList &list() const { return m_applications; } qtmir::Application* findApplicationWithPid(const pid_t pid); public Q_SLOTS: void authorizeSession(const pid_t pid, bool &authorized); void onSessionStarting(std::shared_ptr const& session); void onSessionStopping(std::shared_ptr const& session); void onSessionCreatedSurface(mir::scene::Session const*, std::shared_ptr const&); void onSessionDestroyingSurface(mir::scene::Session const* session, std::shared_ptr const& surface); void onProcessStarting(const QString& appId); void onProcessStopped(const QString& appId); void onProcessSuspended(const QString& appId); void onProcessFailed(const QString& appId, TaskController::Error error); void onFocusRequested(const QString& appId); void onResumeRequested(const QString& appId); Q_SIGNALS: void focusRequested(const QString &appId); void emptyChanged(); private Q_SLOTS: void onAppDataChanged(const int role); void onSessionAboutToCreateSurface(const std::shared_ptr &session, int type, QSize &size); private: void setFocused(Application *application); void add(Application *application); void remove(Application* application); Application* findApplicationWithSession(const std::shared_ptr &session); Application* findApplicationWithSession(const mir::scene::Session *session); QModelIndex findIndex(Application* application); void resumeApplication(Application *application); QString toString() const; Application* findApplicationWithPromptSession(const mir::scene::PromptSession* promptSession); Application *findClosingApplication(const QString &inputAppId) const; QSharedPointer m_mirServer; QList m_applications; Application* m_focusedApplication; DBusWindowStack* m_dbusWindowStack; QSharedPointer m_taskController; QSharedPointer m_desktopFileReaderFactory; QSharedPointer m_procInfo; QSharedPointer m_sharedWakelock; QSharedPointer m_settings; QList m_closingApplications; QList m_queuedStartApplications; static ApplicationManager* the_application_manager; friend class Application; friend class DBusWindowStack; friend class MirSurfaceManager; friend class SessionManager; }; } // namespace qtmir Q_DECLARE_METATYPE(qtmir::ApplicationManager*) #endif // APPLICATIONMANAGER_H ./src/modules/Unity/Application/desktopfilereader.h0000644000015600001650000000451212677054330022565 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef DESKTOPFILEREADER_H #define DESKTOPFILEREADER_H #include #include namespace qtmir { class DesktopFileReaderPrivate; class DesktopFileReader { public: class Factory { public: Factory() = default; virtual ~Factory() = default; Factory(const Factory&) = delete; Factory& operator=(const Factory&) = delete; virtual DesktopFileReader* createInstance(const QString &appId, const QFileInfo& fi); }; virtual ~DesktopFileReader(); virtual QString file() const; virtual QString appId() const; virtual QString name() const; virtual QString comment() const; virtual QString icon() const; virtual QString exec() const; virtual QString path() const; virtual QString stageHint() const; virtual QString splashTitle() const; virtual QString splashImage() const; virtual QString splashShowHeader() const; virtual QString splashColor() const; virtual QString splashColorHeader() const; virtual QString splashColorFooter() const; virtual Qt::ScreenOrientations supportedOrientations() const; virtual bool rotatesWindowContents() const; virtual bool isTouchApp() const; virtual bool loaded() const; static bool parseOrientations(const QString &rawString, Qt::ScreenOrientations &result); static bool parseBoolean(const QString &rawString, bool &result); protected: DesktopFileReader() : d_ptr(nullptr) {} DesktopFileReader(const QString &appId, const QFileInfo &desktopFile); DesktopFileReaderPrivate * const d_ptr; private: Q_DECLARE_PRIVATE(DesktopFileReader) }; } // namespace qtmir #endif // DESKTOPFILEREADER_H ./src/modules/Unity/Application/timesource.cpp0000644000015600001650000000220512677054330021600 0ustar jenkinsjenkins/* * Copyright (C) 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, as * published by the Free Software Foundation; either version 2.1 or 3.0 * of the License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR * PURPOSE. See the applicable version of the GNU Lesser General Public * License for more details. * * You should have received a copy of both the GNU Lesser General Public * License along with this program. If not, see * */ #include "timesource.h" #include namespace qtmir { class RealTimeSourcePrivate { public: QElapsedTimer timer; }; } using namespace qtmir; RealTimeSource::RealTimeSource() : TimeSource() , d(new RealTimeSourcePrivate) { d->timer.start(); } RealTimeSource::~RealTimeSource() { delete d; } qint64 RealTimeSource::msecsSinceReference() { return d->timer.elapsed(); } ./src/modules/Unity/Application/settings.h0000644000015600001650000000203112677054330020723 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SETTINGS_H #define SETTINGS_H //Qt #include // local #include "settings_interface.h" class QGSettings; namespace qtmir { class Settings: public SettingsInterface { Q_OBJECT public: explicit Settings(QObject *parent = 0); QVariant get(const QString &key) const override; private: QGSettings *m_settings; }; } #endif // SETTINGS_H ./src/modules/Unity/Application/mirsurfacemanager.h0000644000015600001650000000523712677054330022571 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIR_SURFACE_MANAGER_H #define MIR_SURFACE_MANAGER_H // std #include // Qt #include #include #include #include // Mir #include // mirserver qpa #include namespace mir { namespace scene { class Surface; class Session; class PromptSession; } namespace shell { class Shell; } } class MirServer; class SurfaceObserver; namespace qtmir { class Application; class ApplicationManager; class MirSurfaceInterface; class SessionManager; class MirSurfaceManager : public QObject { Q_OBJECT Q_PROPERTY(MirSurfaceInterface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged) public: explicit MirSurfaceManager( const QSharedPointer& mirServer, mir::shell::Shell *shell, SessionManager* sessionManager, QObject *parent = 0 ); ~MirSurfaceManager(); static MirSurfaceManager* singleton(); MirSurfaceInterface* inputMethodSurface() const; Q_SIGNALS: void inputMethodSurfaceChanged(); void surfaceCreated(MirSurfaceInterface* surface); void surfaceDestroyed(MirSurfaceInterface* surface); public Q_SLOTS: void onSessionCreatedSurface(const mir::scene::Session *, const std::shared_ptr &, std::shared_ptr const&, qtmir::CreationHints); void onSessionDestroyingSurface(const mir::scene::Session *, const std::shared_ptr &); protected: QHash m_mirSurfaceToQmlSurfaceHash; QMutex m_mutex; private: QSharedPointer m_mirServer; mir::shell::Shell *const m_shell; SessionManager* m_sessionManager; static MirSurfaceManager *instance; MirSurfaceInterface* m_inputMethodSurface = nullptr; }; } // namespace qtmir #endif // MIR_SURFACE_MANAGER_H ./src/modules/Unity/Application/dbuswindowstack.h0000644000015600001650000000427312677054330022310 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef DBUSWINDOWSTACK_H #define DBUSWINDOWSTACK_H #include #include namespace qtmir { class ApplicationManager; class AppIdDesktopFile { public: QString app_id; QString desktop_file; }; QDBusArgument &operator<<(QDBusArgument &a, const AppIdDesktopFile &aidf); const QDBusArgument &operator>>(const QDBusArgument &a, AppIdDesktopFile &aidf); class WindowInfo { public: unsigned int window_id; QString app_id; bool focused; unsigned int stage; }; QDBusArgument &operator<<(QDBusArgument &a, const WindowInfo &aidf); const QDBusArgument &operator>>(const QDBusArgument &a, WindowInfo &aidf); class DBusWindowStack : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity.WindowStack") public: explicit DBusWindowStack(ApplicationManager* parent); ~DBusWindowStack(); Q_INVOKABLE Q_SCRIPTABLE qtmir::AppIdDesktopFile GetAppIdFromPid(unsigned int pid); Q_INVOKABLE Q_SCRIPTABLE QList GetWindowStack(); Q_INVOKABLE Q_SCRIPTABLE QStringList GetWindowProperties(unsigned int window_id, const QString &app_id, const QStringList &names); Q_SIGNALS: void FocusedWindowChanged(unsigned int window_id, const QString &app_id, unsigned int stage); void WindowCreated(unsigned int window_id, const QString &app_id); void WindowDestroyed(unsigned int window_id, const QString &app_id); }; } // namespace qtmir Q_DECLARE_METATYPE(qtmir::AppIdDesktopFile); Q_DECLARE_METATYPE(qtmir::WindowInfo); #endif // DBUSWINDOWSTACK_H ./src/modules/Unity/Application/ubuntukeyboardinfo.h0000644000015600001650000000515312677054330023012 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef UBUNTU_KEYBOARD_INFO_H #define UBUNTU_KEYBOARD_INFO_H #include #include namespace qtmir { // Temporary solution to get information about the onscreen keyboard // This shouldn't be needed once the OSK is a properly sized surface // instead of a fullscreen one. class UbuntuKeyboardInfo : public QObject { Q_OBJECT Q_PROPERTY(qreal x READ x NOTIFY xChanged) Q_PROPERTY(qreal y READ y NOTIFY yChanged) Q_PROPERTY(qreal width READ width NOTIFY widthChanged) Q_PROPERTY(qreal height READ height NOTIFY heightChanged) public: UbuntuKeyboardInfo(QObject *parent = 0); virtual ~UbuntuKeyboardInfo(); static UbuntuKeyboardInfo* instance(); qreal x() const { return m_lastX; } qreal y() const { return m_lastY; } qreal width() const { return m_lastWidth; } qreal height() const { return m_lastHeight; } Q_SIGNALS: void xChanged(qreal x); void yChanged(qreal y); void widthChanged(qreal width); void heightChanged(qreal height); private Q_SLOTS: void tryConnectingToServer(); void onSocketStateChanged(QLocalSocket::LocalSocketState socketState); void onSocketError(QLocalSocket::LocalSocketError socketError); void readAllBytesFromSocket(); private: // NB! Must match the definition in ubuntu-keyboard. Not worth creating a shared header // just for that. struct SharedInfo { qint32 keyboardX; qint32 keyboardY; qint32 keyboardWidth; qint32 keyboardHeight; }; void readInfoFromSocket(); void retryConnection(); void buildSocketFilePath(); int m_consecutiveAttempts; QLocalSocket m_socket; qint32 m_lastX; qint32 m_lastY; qint32 m_lastWidth; qint32 m_lastHeight; QTimer m_connectionRetryTimer; // Path to the socket file created by ubuntu-keyboard QString m_socketFilePath; static UbuntuKeyboardInfo* m_instance; }; } // namespace qtmir #endif // UBUNTU_KEYBOARD_INFO_H ./src/modules/Unity/Application/com.canonical.qtmir.gschema.xml0000644000015600001650000000070312677054330024705 0ustar jenkinsjenkins [ 'com.ubuntu.music' ] List of apps that should be excluded from the app lifecycle ./src/modules/Unity/Application/application.h0000644000015600001650000001372612677054330021403 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef APPLICATION_H #define APPLICATION_H // std #include //Qt #include #include #include // Unity API #include #include "session_interface.h" namespace mir { namespace scene { class Session; } } namespace qtmir { class ApplicationManager; class DesktopFileReader; class Session; class SharedWakelock; class AbstractTimer; class Application : public unity::shell::application::ApplicationInfoInterface { Q_OBJECT Q_PROPERTY(QString desktopFile READ desktopFile CONSTANT) Q_PROPERTY(QString exec READ exec CONSTANT) Q_PROPERTY(bool fullscreen READ fullscreen NOTIFY fullscreenChanged) Q_PROPERTY(SessionInterface* session READ session NOTIFY sessionChanged DESIGNABLE false) public: Q_DECLARE_FLAGS(Stages, Stage) enum ProcessState { ProcessUnknown, ProcessRunning, ProcessSuspended, ProcessFailed, // it stopped, but because it was killed or because it crashed ProcessStopped }; enum class InternalState { Starting, Running, RunningInBackground, SuspendingWaitSession, SuspendingWaitProcess, Suspended, Closing, // The user has requested the app be closed StoppedResumable, // The process stopped but we want to keep the Application object around // so it can be respawned as if it never stopped running in the first place. Stopped // It closed itself, crashed or it stopped and we can't respawn it // In any case, this is a dead end. The Application object can be deleted at // any moment once in this state. }; Application(const QSharedPointer& sharedWakelock, DesktopFileReader *desktopFileReader, const QStringList &arguments, ApplicationManager *parent); virtual ~Application(); // ApplicationInfoInterface QString appId() const override; QString name() const override; QString comment() const override; QUrl icon() const override; Stage stage() const override; void setStage(Stage stage) override; State state() const override; RequestedState requestedState() const override; void setRequestedState(RequestedState) override; bool focused() const override; QString splashTitle() const override; QUrl splashImage() const override; bool splashShowHeader() const override; QColor splashColor() const override; QColor splashColorHeader() const override; QColor splashColorFooter() const override; Qt::ScreenOrientations supportedOrientations() const override; bool rotatesWindowContents() const override; bool isTouchApp() const override; bool exemptFromLifecycle() const override; void setExemptFromLifecycle(bool) override; QSize initialSurfaceSize() const override; void setInitialSurfaceSize(const QSize &size) override; ProcessState processState() const { return m_processState; } void setProcessState(ProcessState value); QStringList arguments() const { return m_arguments; } SessionInterface* session() const; void setSession(SessionInterface *session); bool canBeResumed() const; bool isValid() const; QString desktopFile() const; QString exec() const; bool fullscreen() const; Stages supportedStages() const; pid_t pid() const; void close(); // for tests InternalState internalState() const { return m_state; } void setCloseTimer(AbstractTimer *timer); Q_SIGNALS: void fullscreenChanged(bool fullscreen); void stageChanged(Stage stage); void sessionChanged(SessionInterface *session); void startProcessRequested(); void stopProcessRequested(); void suspendProcessRequested(); void resumeProcessRequested(); void stopped(); private Q_SLOTS: void onSessionStateChanged(SessionInterface::State sessionState); void respawn(); private: QString longAppId() const; void acquireWakelock() const; void releaseWakelock() const; void setPid(pid_t pid); void setArguments(const QStringList arguments); void setFocused(bool focus); void setInternalState(InternalState state); void wipeQMLCache(); void suspend(); void resume(); void stop(); QColor colorFromString(const QString &colorString, const char *colorName) const; static const char* internalStateToStr(InternalState state); void applyRequestedState(); void applyRequestedRunning(); void applyRequestedSuspended(); void doClose(); QSharedPointer m_sharedWakelock; DesktopFileReader* m_desktopData; QString m_longAppId; pid_t m_pid; Stage m_stage; Stages m_supportedStages; InternalState m_state; bool m_focused; QStringList m_arguments; Qt::ScreenOrientations m_supportedOrientations; bool m_rotatesWindowContents; SessionInterface *m_session; RequestedState m_requestedState; ProcessState m_processState; AbstractTimer *m_closeTimer; bool m_exemptFromLifecycle; QSize m_initialSurfaceSize; friend class ApplicationManager; friend class SessionManager; friend class Session; }; } // namespace qtmir Q_DECLARE_METATYPE(qtmir::Application*) #endif // APPLICATION_H ./src/modules/Unity/Application/session.h0000644000015600001650000000734412677054330020562 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SESSION_H #define SESSION_H // std #include // local #include "session_interface.h" // Qt #include #include namespace mir { namespace scene { class PromptSessionManager; } } namespace qtmir { class Application; class Session : public SessionInterface { Q_OBJECT public: explicit Session(const std::shared_ptr& session, const std::shared_ptr& promptSessionManager, QObject *parent = 0); virtual ~Session(); Q_INVOKABLE void release() override; //getters QString name() const override; unity::shell::application::ApplicationInfoInterface* application() const override; MirSurfaceInterface* lastSurface() const override; const ObjectListModel* surfaces() const override; SessionInterface* parentSession() const override; State state() const override; bool fullscreen() const override; bool live() const override; void setApplication(unity::shell::application::ApplicationInfoInterface* item) override; void registerSurface(MirSurfaceInterface* surface) override; void removeSurface(MirSurfaceInterface* surface) override; void suspend() override; void resume() override; void close() override; void stop() override; void addChildSession(SessionInterface* session) override; void insertChildSession(uint index, SessionInterface* session) override; void removeChildSession(SessionInterface* session) override; void foreachChildSession(std::function f) const override; std::shared_ptr session() const override; std::shared_ptr activePromptSession() const override; void foreachPromptSession(std::function&)> f) const override; SessionModel* childSessions() const override; void setFullscreen(bool fullscreen) override; void setLive(const bool) override; void appendPromptSession(const std::shared_ptr& session) override; void removePromptSession(const std::shared_ptr& session) override; public Q_SLOTS: // it's public to ease testing void doSuspend(); private Q_SLOTS: void updateFullscreenProperty(); protected: void setParentSession(Session* session); void setState(State state); void doResume(); void stopPromptSessions(); void appendSurface(MirSurfaceInterface* surface); std::shared_ptr m_session; Application* m_application; ObjectListModel m_surfaces; SessionInterface* m_parentSession; SessionModel* m_children; bool m_fullscreen; State m_state; bool m_live; bool m_released; QTimer* m_suspendTimer; QList> m_promptSessions; std::shared_ptr const m_promptSessionManager; }; } // namespace qtmir #endif // SESSION_H ./src/modules/Unity/Application/sharedwakelock.cpp0000644000015600001650000001620512677054330022415 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "sharedwakelock.h" #include "abstractdbusservicemonitor.h" #include "logging.h" #include #include #include #include namespace qtmir { const int POWERD_SYS_STATE_ACTIVE = 1; // copied from private header file powerd.h const char cookieFile[] = "/tmp/qtmir_powerd_cookie"; /** * @brief The Wakelock class - wraps a single system wakelock * Should the PowerD service vanish from the bus, the wakelock will be re-acquired when it re-joins the bus. */ class Wakelock : public AbstractDBusServiceMonitor { Q_OBJECT public: Wakelock(const QDBusConnection &connection) noexcept : AbstractDBusServiceMonitor("com.canonical.powerd", "/com/canonical/powerd", "com.canonical.powerd", connection) , m_wakelockEnabled(false) { // (re-)acquire wake lock when powerd (re-)appears on the bus QObject::connect(this, &Wakelock::serviceAvailableChanged, this, &Wakelock::onServiceAvailableChanged); // WORKAROUND: if shell crashed while it held a wakelock, due to bug lp:1409722 powerd will not have released // the wakelock for it. As workaround, we save the cookie to file and restore it if possible. QFile cookieCache(cookieFile); if (cookieCache.exists() && cookieCache.open(QFile::ReadOnly | QFile::Text)) { m_wakelockEnabled = true; m_cookie = cookieCache.readAll(); } } virtual ~Wakelock() noexcept { release(); } Q_SIGNAL void enabledChanged(bool); bool enabled() const { return m_wakelockEnabled; } void acquire() { if (m_wakelockEnabled) { // wakelock already requested/set return; } m_wakelockEnabled = true; acquireWakelock(); } void release() { QFile::remove(cookieFile); if (!m_wakelockEnabled) { // no wakelock already requested/set return; } m_wakelockEnabled = false; Q_EMIT enabledChanged(false); if (!serviceAvailable()) { qWarning() << "com.canonical.powerd DBus interface not available, presuming no wakelocks held"; return; } if (!m_cookie.isEmpty()) { dbusInterface()->asyncCall("clearSysState", QString(m_cookie)); qCDebug(QTMIR_SESSIONS) << "Wakelock released" << m_cookie; m_cookie.clear(); } } private Q_SLOTS: void onServiceAvailableChanged(bool available) { // Assumption is if service vanishes & reappears on the bus, it has lost its wakelock state and // we must re-acquire if necessary if (!m_wakelockEnabled) { return; } if (available) { acquireWakelock(); } else { m_cookie.clear(); QFile::remove(cookieFile); } } void onWakeLockAcquired(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) { qCDebug(QTMIR_SESSIONS) << "Wakelock was NOT acquired, error:" << QDBusError::errorString(reply.error().type()); if (m_wakelockEnabled) { m_wakelockEnabled = false; Q_EMIT enabledChanged(false); } call->deleteLater(); return; } QByteArray cookie = reply.argumentAt<0>().toLatin1(); call->deleteLater(); if (!m_wakelockEnabled || !m_cookie.isEmpty()) { // notified wakelock was created, but we either don't want it, or already have one - release it immediately dbusInterface()->asyncCall("clearSysState", QString(cookie)); return; } m_cookie = cookie; // see WORKAROUND above for why we save cookie to disk QFile cookieCache(cookieFile); cookieCache.open(QFile::WriteOnly | QFile::Text); cookieCache.write(m_cookie); qCDebug(QTMIR_SESSIONS) << "Wakelock acquired" << m_cookie; Q_EMIT enabledChanged(true); } private: void acquireWakelock() { if (!serviceAvailable()) { qWarning() << "com.canonical.powerd DBus interface not available, waiting for it"; return; } QDBusPendingCall pcall = dbusInterface()->asyncCall("requestSysState", "active", POWERD_SYS_STATE_ACTIVE); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &Wakelock::onWakeLockAcquired); } QByteArray m_cookie; bool m_wakelockEnabled; Q_DISABLE_COPY(Wakelock) }; #include "sharedwakelock.moc" /** * @brief SharedWakelock - allow a single wakelock instance to be shared between multiple owners * * QtMir has application management duties to perform even if display is off. To prevent device * going to deep sleep before QtMir is ready, have QtMir register a system wakelock when it needs to. * * This class allows multiple objects to own the wakelock simultaneously. The wakelock is first * registered when acquire has been called by one caller. Multiple callers may then share the * wakelock. The wakelock is only destroyed when all callers have called release. * * Note a caller cannot have multiple shares of the wakelock. Multiple calls to acquire are ignored. */ SharedWakelock::SharedWakelock(const QDBusConnection &connection) : m_wakelock(new Wakelock(connection)) { connect(m_wakelock.data(), &Wakelock::enabledChanged, this, &SharedWakelock::enabledChanged); } // Define empty deconstructor here, as QScopedPointer requires the destructor of the Wakelock class // to be defined first. SharedWakelock::~SharedWakelock() { } bool SharedWakelock::enabled() const { return m_wakelock->enabled(); } void SharedWakelock::acquire(const QObject *caller) { if (caller == nullptr || m_owners.contains(caller)) { return; } // register a slot to remove itself from owners list if destroyed QObject::connect(caller, &QObject::destroyed, this, &SharedWakelock::release); m_wakelock->acquire(); m_owners.insert(caller); } void SharedWakelock::release(const QObject *caller) { if (caller == nullptr || !m_owners.remove(caller)) { return; } QObject::disconnect(caller, &QObject::destroyed, this, 0); if (m_owners.empty()) { m_wakelock->release(); } } } // namespace qtmir ./src/modules/Unity/Screens/0000755000015600001650000000000012677054330016055 5ustar jenkinsjenkins./src/modules/Unity/Screens/screens.h0000644000015600001650000000363612677054330017700 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SCREENS_H #define SCREENS_H #include class QScreen; namespace qtmir { class Screens : public QAbstractListModel { Q_OBJECT Q_ENUMS(OutputTypes) Q_PROPERTY(int count READ count NOTIFY countChanged) public: enum ItemRoles { ScreenRole = Qt::UserRole + 1, OutputTypeRole }; enum OutputTypes { Unknown, VGA, DVII, DVID, DVIA, Composite, SVideo, LVDS, Component, NinePinDIN, DisplayPort, HDMIA, HDMIB, TV, EDP }; explicit Screens(QObject *parent = 0); virtual ~Screens() noexcept = default; /* QAbstractItemModel */ QHash roleNames() const override; QVariant data(const QModelIndex &index, int role = ScreenRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int count() const; Q_SIGNALS: void countChanged(); void screenAdded(QScreen *screen); void screenRemoved(QScreen *screen); private Q_SLOTS: void onScreenAdded(QScreen *screen); void onScreenRemoved(QScreen *screen); private: QList m_screenList; }; } // namespace qtmir #endif // SCREENS_H ./src/modules/Unity/Screens/plugin.cpp0000644000015600001650000000231512677054330020060 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // Qt #include #include #include // local #include "screens.h" using namespace qtmir; class UnityScreensPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0") virtual void registerTypes(const char* uri) { Q_ASSERT(QLatin1String(uri) == QLatin1String("Unity.Screens")); qRegisterMetaType("QScreen*"); qmlRegisterType(uri, 0, 1, "Screens"); } }; #include "plugin.moc" ./src/modules/Unity/Screens/screens.cpp0000644000015600001650000000526212677054330020230 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "screens.h" // mirserver #include "screen.h" // Qt #include #include Q_DECLARE_METATYPE(QScreen*) namespace qtmir { Screens::Screens(QObject *parent) : QAbstractListModel(parent) { auto app = static_cast(QGuiApplication::instance()); if (!app) { return; } connect(app, &QGuiApplication::screenAdded, this, &Screens::onScreenAdded); connect(app, &QGuiApplication::screenRemoved, this, &Screens::onScreenRemoved); m_screenList = QGuiApplication::screens(); } QHash Screens::roleNames() const { QHash roles; roles[ScreenRole] = "screen"; roles[OutputTypeRole] = "outputType"; return roles; } QVariant Screens::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_screenList.size()) { return QVariant(); } switch(role) { case ScreenRole: return QVariant::fromValue(m_screenList.at(index.row())); case OutputTypeRole: auto screen = static_cast(m_screenList.at(index.row())->handle()); if (screen) { return QVariant(static_cast(screen->outputType())); //FIXME: cheeky } else return OutputTypes::Unknown; } return QVariant(); } int Screens::rowCount(const QModelIndex &) const { return count(); } int Screens::count() const { return m_screenList.size(); } void Screens::onScreenAdded(QScreen *screen) { if (m_screenList.contains(screen)) return; beginInsertRows(QModelIndex(), count(), count()); m_screenList.push_back(screen); endInsertRows(); Q_EMIT screenAdded(screen); Q_EMIT countChanged(); } void Screens::onScreenRemoved(QScreen *screen) { int index = m_screenList.indexOf(screen); if (index < 0) return; beginRemoveRows(QModelIndex(), index, index); m_screenList.removeAt(index); endRemoveRows(); Q_EMIT screenRemoved(screen); Q_EMIT countChanged(); } } // namespace qtmir ./src/modules/Unity/Screens/qmldir0000644000015600001650000000005712677054330017272 0ustar jenkinsjenkinsmodule Unity.Screens plugin unityscreensplugin ./src/modules/Unity/Screens/CMakeLists.txt0000644000015600001650000000065412677054330020622 0ustar jenkinsjenkinsinclude_directories( ${CMAKE_SOURCE_DIR}/src/platforms/mirserver ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${MIRSERVER_INCLUDE_DIRS} ) set(SCREENSPLUGIN_SRC plugin.cpp screens.cpp ) add_library(unityscreensplugin SHARED ${SCREENSPLUGIN_SRC} ) target_link_libraries( unityscreensplugin Qt5::Gui Qt5::Qml ) # install add_qml_plugin(Unity.Screens 0.1 Unity/Screens TARGETS unityscreensplugin) ./src/modules/Unity/CMakeLists.txt0000644000015600001650000000007012677054330017210 0ustar jenkinsjenkinsadd_subdirectory(Application) add_subdirectory(Screens) ./src/platforms/0000755000015600001650000000000012677054330013702 5ustar jenkinsjenkins./src/platforms/mirserver/0000755000015600001650000000000012677054330015720 5ustar jenkinsjenkins./src/platforms/mirserver/mirsingleton.h0000644000015600001650000000222512677054330020604 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QTMIR_MIRSINGLETON_H #define QTMIR_MIRSINGLETON_H // unity-api #include namespace qtmir { class Mir : public ::Mir { Q_OBJECT public: virtual ~Mir(); static Mir *instance(); void setCursorName(const QString &cursorName) override; QString cursorName() const override; private: Mir(); Q_DISABLE_COPY(Mir) QString m_cursorName; static qtmir::Mir *m_instance; }; } // namespace qtmir #endif // QTMIR_MIRSINGLETON_H ./src/platforms/mirserver/mirserverintegration.h0000644000015600001650000000465512677054330022365 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIRSERVERINTEGRATION_H #define MIRSERVERINTEGRATION_H // qt #include #include class NativeInterface; class QMirServer; namespace qtmir { class Clipboard; } class MirServerIntegration : public QPlatformIntegration { public: MirServerIntegration(int &argc, char **argv); ~MirServerIntegration(); bool hasCapability(QPlatformIntegration::Capability cap) const override; QPlatformWindow *createPlatformWindow(QWindow *window) const override; QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; QAbstractEventDispatcher *createEventDispatcher() const override; void initialize() override; QPlatformClipboard *clipboard() const override; QPlatformInputContext* inputContext() const override { return m_inputContext; } QPlatformFontDatabase *fontDatabase() const override; QStringList themeNames() const override; QPlatformTheme* createPlatformTheme(const QString& name) const override; QPlatformServices *services() const override; QPlatformAccessibility *accessibility() const override; QPlatformNativeInterface *nativeInterface() const override; QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override; private: QScopedPointer m_accessibility; QScopedPointer m_fontDb; QScopedPointer m_services; QScopedPointer m_mirServer; NativeInterface *m_nativeInterface; QPlatformInputContext* m_inputContext; QScopedPointer m_clipboard; }; #endif // MIRSERVERINTEGRATION_H ./src/platforms/mirserver/creationhints.cpp0000644000015600001650000000447312677054330021306 0ustar jenkinsjenkins/* * Copyright (C) 2016 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include "creationhints.h" using namespace qtmir; inline const char* shellChromeToString(Mir::ShellChrome chrome) { switch (chrome) { case Mir::ShellChrome::NormalChrome: return "normal"; case Mir::ShellChrome::LowChrome: return "low"; } return "unknown"; } CreationHints::CreationHints(const mir::scene::SurfaceCreationParameters ¶ms) { minWidth = params.min_width.is_set() ? params.min_width.value().as_int() : 0; maxWidth = params.max_width.is_set() ? params.max_width.value().as_int() : 0; minHeight = params.min_height.is_set() ? params.min_height.value().as_int() : 0; maxHeight = params.max_height.is_set() ? params.max_height.value().as_int() : 0; widthIncrement = params.width_inc.is_set() ? params.width_inc.value().as_int() : 0; heightIncrement = params.height_inc.is_set() ? params.height_inc.value().as_int() : 0; if (params.shell_chrome.is_set()) { switch (params.shell_chrome.value()) { case mir_shell_chrome_normal: default: shellChrome = Mir::ShellChrome::NormalChrome; break; case mir_shell_chrome_low: shellChrome = Mir::ShellChrome::LowChrome; break; } } } QString CreationHints::toString() const { return QString("CreationHints(minW=%1,minH=%2,maxW=%3,maxH=%4,wIncr=%5,hInc=%6,shellChrome=%7)") .arg(minWidth) .arg(minHeight) .arg(maxWidth) .arg(maxHeight) .arg(widthIncrement) .arg(heightIncrement) .arg(shellChromeToString(shellChrome)); } ./src/platforms/mirserver/qmirserver.cpp0000644000015600001650000000465612677054330020636 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // Qt #include #include #include // local #include "mirserver.h" #include "qmirserver.h" #include "qmirserver_p.h" #include "screencontroller.h" #include "screen.h" QMirServer::QMirServer(int &argc, char **argv, QObject *parent) : QObject(parent) , d_ptr(new QMirServerPrivate()) { Q_D(QMirServer); d->screenController = QSharedPointer(new ScreenController()); d->server = QSharedPointer(new MirServer(argc, argv, d->screenController)); d->serverThread = new MirServerThread(d->server); connect(d->serverThread, &MirServerThread::stopped, this, &QMirServer::stopped); } QMirServer::~QMirServer() { stop(); } bool QMirServer::start() { Q_D(QMirServer); d->serverThread->start(QThread::TimeCriticalPriority); if (!d->serverThread->waitForMirStartup()) { qCritical() << "ERROR: QMirServer - Mir failed to start"; return false; } d->screenController->update(); Q_EMIT started(); return true; } void QMirServer::stop() { Q_D(QMirServer); if (d->serverThread->isRunning()) { d->serverThread->stop(); if (!d->serverThread->wait(10000)) { // do something to indicate fail during shutdown qCritical() << "ERROR: QMirServer - Mir failed to shut down correctly, terminating it"; d->serverThread->terminate(); } } } bool QMirServer::isRunning() const { Q_D(const QMirServer); return d->serverThread->isRunning(); } QWeakPointer QMirServer::mirServer() const { Q_D(const QMirServer); return d->server.toWeakRef(); } QWeakPointer QMirServer::screenController() const { Q_D(const QMirServer); return d->screenController; } ./src/platforms/mirserver/qmirserver.h0000644000015600001650000000253112677054330020271 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QMIRSERVER_H #define QMIRSERVER_H // Qt #include #include class QMirServerPrivate; class MirServer; class ScreenController; class QMirServer: public QObject { Q_OBJECT public: QMirServer(int &argc, char **argv, QObject* parent=0); virtual ~QMirServer(); bool start(); Q_SLOT void stop(); bool isRunning() const; QWeakPointer mirServer() const; QWeakPointer screenController() const; Q_SIGNALS: void started(); void stopped(); protected: QMirServerPrivate * const d_ptr; private: Q_DISABLE_COPY(QMirServer) Q_DECLARE_PRIVATE(QMirServer) }; #endif // QMIRSERVER_H ./src/platforms/mirserver/cursor.h0000644000015600001650000000350612677054330017412 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * */ #ifndef QTMIR_CURSOR_H #define QTMIR_CURSOR_H #include #include // Unity API #include namespace qtmir { class Cursor : public MirPlatformCursor { public: Cursor(); // Called form Mir input thread bool handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers); bool handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers mods); //// // MirPlatformCursor // Called from Qt's GUI thread void setMousePointer(MirMousePointerInterface *mousePointer) override; //// // QPlatformCursor void changeCursor(QCursor *windowCursor, QWindow *window) override; void setPos(const QPoint &pos) override; QPoint pos() const override; private Q_SLOTS: void setMirCursorName(const QString &mirCursorName); private: void updateMousePointerCursorName(); QMutex m_mutex; QPointer m_mousePointer; QMap m_shapeToCursorName; QString m_qtCursorName; QString m_mirCursorName; }; } // namespace qtmir #endif // QTMIR_CURSOR_H ./src/platforms/mirserver/mirwindowmanager.h0000644000015600001650000000274512677054330021453 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QPAMIRSERVER_WINDOW_MANAGER_H #define QPAMIRSERVER_WINDOW_MANAGER_H #include #include "sessionlistener.h" #include #include namespace mir { namespace shell { class DisplayLayout; class FocusController; } } class MirWindowManager : public QObject, public mir::shell::WindowManager { Q_OBJECT public: static std::shared_ptr create( const std::shared_ptr &displayLayout, std::shared_ptr<::SessionListener> sessionListener); Q_SIGNALS: // requires Qt::BlockingQueuedConnection!! void sessionAboutToCreateSurface(const std::shared_ptr &session, int type, QSize &size); }; #endif /* QPAMIRSERVER_WINDOW_MANAGER_H */ ./src/platforms/mirserver/logging.h0000644000015600001650000000226612677054330017525 0ustar jenkinsjenkins/* * Copyright (C) 2013-2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef UBUNTU_APPLICATION_PLUGIN_LOGGING_H #define UBUNTU_APPLICATION_PLUGIN_LOGGING_H #include Q_DECLARE_LOGGING_CATEGORY(QTMIR_APPLICATIONS) Q_DECLARE_LOGGING_CATEGORY(QTMIR_SESSIONS) Q_DECLARE_LOGGING_CATEGORY(QTMIR_SURFACES) Q_DECLARE_LOGGING_CATEGORY(QTMIR_MIR_MESSAGES) Q_DECLARE_LOGGING_CATEGORY(QTMIR_SENSOR_MESSAGES) Q_DECLARE_LOGGING_CATEGORY(QTMIR_MIR_INPUT) Q_DECLARE_LOGGING_CATEGORY(QTMIR_CLIPBOARD) Q_DECLARE_LOGGING_CATEGORY(QTMIR_SCREENS) #endif // UBUNTU_APPLICATION_PLUGIN_LOGGING_H ./src/platforms/mirserver/namedcursor.h0000644000015600001650000000263612677054330020422 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QTMIR_NAMEDCURSOR_H #define QTMIR_NAMEDCURSOR_H #include #include #include namespace qtmir { /* A hack for storing a named cursor inside a CursorImage, as a way to delegate cursor loading to shell code. */ class NamedCursor : public mir::graphics::CursorImage { public: NamedCursor(const char *name) : m_name(name) {} const QByteArray &name() const { return m_name; } const void *as_argb_8888() const override { return nullptr; } mir::geometry::Size size() const override { return {0,0}; } mir::geometry::Displacement hotspot() const override { return {0,0}; } private: QByteArray m_name; }; } // namespace qtmir #endif // QTMIR_NAMEDCURSOR_H ./src/platforms/mirserver/qtcompositor.cpp0000644000015600001650000000243012677054330021166 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "qtcompositor.h" #include "logging.h" #include // Lives in a Mir thread void QtCompositor::start() { qCDebug(QTMIR_SCREENS) << "QtCompositor::start"; // FIXME: Hack to work around https://bugs.launchpad.net/mir/+bug/1502200 // See the FIXME in mirserver.cpp if (m_cursor) { m_cursor->hide(); } Q_EMIT starting(); // blocks } void QtCompositor::stop() { qCDebug(QTMIR_SCREENS) << "QtCompositor::stop"; Q_EMIT stopping(); // blocks } void QtCompositor::setCursor(std::shared_ptr cursor) { m_cursor = cursor; } ./src/platforms/mirserver/mirserverstatuslistener.cpp0000644000015600001650000000165212677054330023460 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // local #include "mirserverstatuslistener.h" // Qt #include #include // std #include void MirServerStatusListener::paused() { } void MirServerStatusListener::resumed() { } void MirServerStatusListener::started() { } ./src/platforms/mirserver/qtcompositor.h0000644000015600001650000000244112677054330020635 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QTCOMPOSITOR_H #define QTCOMPOSITOR_H #include // std lib #include // Qt #include namespace mir { namespace graphics { class Cursor; } } class QtCompositor : public QObject, public mir::compositor::Compositor { Q_OBJECT public: QtCompositor() = default; virtual ~QtCompositor() noexcept = default; void start(); void stop(); void setCursor(std::shared_ptr); Q_SIGNALS: void starting(); void stopping(); private: std::shared_ptr m_cursor; }; #endif // QTCOMPOSITOR_H ./src/platforms/mirserver/mirserverintegration.cpp0000644000015600001650000001424412677054330022713 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mirserverintegration.h" #include #include #include #include #include #include #include #include #include #include #include #include // Mir #include #include // local #include "clipboard.h" #include "miropenglcontext.h" #include "nativeinterface.h" #include "offscreensurface.h" #include "qmirserver.h" #include "screen.h" #include "screencontroller.h" #include "screenwindow.h" #include "services.h" #include "ubuntutheme.h" #include "logging.h" namespace mg = mir::graphics; using qtmir::Clipboard; MirServerIntegration::MirServerIntegration(int &argc, char **argv) : m_accessibility(new QPlatformAccessibility()) , m_fontDb(new QGenericUnixFontDatabase()) , m_services(new Services) , m_mirServer(new QMirServer(argc, argv)) , m_nativeInterface(nullptr) , m_clipboard(new Clipboard) { // For access to sensors, qtmir uses qtubuntu-sensors. qtubuntu-sensors reads the // UBUNTU_PLATFORM_API_BACKEND variable to decide if to load a valid sensor backend or not. // For it to function we need to ensure a valid backend has been specified if (qEnvironmentVariableIsEmpty("UBUNTU_PLATFORM_API_BACKEND")) { if (qgetenv("DESKTOP_SESSION").contains("mir") || !qEnvironmentVariableIsSet("ANDROID_DATA")) { qputenv("UBUNTU_PLATFORM_API_BACKEND", "desktop_mirclient"); } else { qputenv("UBUNTU_PLATFORM_API_BACKEND", "touch_mirclient"); } } // If Mir shuts down, quit. QObject::connect(m_mirServer.data(), &QMirServer::stopped, QCoreApplication::instance(), &QCoreApplication::quit); m_inputContext = QPlatformInputContextFactory::create(); // Default Qt behaviour doesn't match a shell's intentions, so customize: qGuiApp->setQuitOnLastWindowClosed(false); } MirServerIntegration::~MirServerIntegration() { delete m_nativeInterface; } bool MirServerIntegration::hasCapability(QPlatformIntegration::Capability cap) const { switch (cap) { case ThreadedPixmaps: return true; case OpenGL: return true; case ThreadedOpenGL: return true; case BufferQueueingOpenGL: return true; case MultipleWindows: return true; // multi-monitor support case WindowManagement: return false; // platform has no WM, as this implements the WM! case NonFullScreenWindows: return false; default: return QPlatformIntegration::hasCapability(cap); } } QPlatformWindow *MirServerIntegration::createPlatformWindow(QWindow *window) const { QWindowSystemInterface::flushWindowSystemEvents(); auto screens = m_mirServer->screenController().lock(); if (!screens) { qCritical("Screens are not initialized, unable to create a new QWindow/ScreenWindow"); return nullptr; } auto platformWindow = new ScreenWindow(window); if (screens->compositing()) { platformWindow->setExposed(true); } qCDebug(QTMIR_SCREENS) << "QWindow" << window << "with geom" << window->geometry() << "is backed by a" << static_cast(window->screen()->handle()) << "with geometry" << window->screen()->geometry(); return platformWindow; } QPlatformBackingStore *MirServerIntegration::createPlatformBackingStore(QWindow */*window*/) const { return nullptr; } QPlatformOpenGLContext *MirServerIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { return new MirOpenGLContext(m_mirServer->mirServer(), context->format()); } QAbstractEventDispatcher *MirServerIntegration::createEventDispatcher() const { return createUnixEventDispatcher(); } void MirServerIntegration::initialize() { // Creates instance of and start the Mir server in a separate thread if (!m_mirServer->start()) { exit(2); } auto screens = m_mirServer->screenController().lock(); if (!screens) { qFatal("ScreenController not initialized"); } QObject::connect(screens.data(), &ScreenController::screenAdded, [this](Screen *screen) { this->screenAdded(screen); }); Q_FOREACH(auto screen, screens->screens()) { screenAdded(screen); } m_nativeInterface = new NativeInterface(m_mirServer->mirServer()); m_clipboard->setupDBusService(); } QPlatformAccessibility *MirServerIntegration::accessibility() const { return m_accessibility.data(); } QPlatformFontDatabase *MirServerIntegration::fontDatabase() const { return m_fontDb.data(); } QStringList MirServerIntegration::themeNames() const { return QStringList(UbuntuTheme::name); } QPlatformTheme *MirServerIntegration::createPlatformTheme(const QString& name) const { Q_UNUSED(name); return new UbuntuTheme; } QPlatformServices *MirServerIntegration::services() const { return m_services.data(); } QPlatformNativeInterface *MirServerIntegration::nativeInterface() const { return m_nativeInterface; } QPlatformClipboard *MirServerIntegration::clipboard() const { return m_clipboard.data(); } QPlatformOffscreenSurface *MirServerIntegration::createPlatformOffscreenSurface( QOffscreenSurface *surface) const { return new OffscreenSurface(surface); } ./src/platforms/mirserver/services.cpp0000644000015600001650000000273412677054330020255 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "services.h" #include #include #include bool Services::openUrl(const QUrl &url) { return callDispatcher(url); } bool Services::openDocument(const QUrl &url) { return callDispatcher(url); } bool Services::callDispatcher(const QUrl &qUrl) { QByteArray encoded = qUrl.toEncoded(); const char *url = encoded.constData(); url_dispatch_send(url, nullptr /*dispatch_callback*/, nullptr /*callback_data*/); // We are returning true here because the other option // is spawning a nested event loop and wait for the // callback. But there is no guarantee on how fast // the callback is going to be so we prefer to avoid the // nested event loop. Long term plan is improve Qt API // to support an async openUrl return true; } ./src/platforms/mirserver/mirsingleton.cpp0000644000015600001650000000102612677054330021135 0ustar jenkinsjenkins#include "mirsingleton.h" qtmir::Mir *qtmir::Mir::m_instance = nullptr; qtmir::Mir::Mir() { } qtmir::Mir::~Mir() { m_instance = nullptr; } qtmir::Mir *qtmir::Mir::instance() { if (!m_instance) { m_instance = new qtmir::Mir; } return m_instance; } void qtmir::Mir::setCursorName(const QString &cursorName) { if (m_cursorName != cursorName) { m_cursorName = cursorName; Q_EMIT cursorNameChanged(m_cursorName); } } QString qtmir::Mir::cursorName() const { return m_cursorName; } ./src/platforms/mirserver/plugin.h0000644000015600001650000000210012677054330017360 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef PLUGIN_H #define PLUGIN_H #include class MirServerIntegrationPlugin : public QPlatformIntegrationPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "mirserver.json") public: QPlatformIntegration *create(const QString &system, const QStringList ¶mList, int &argc, char **argv) override; }; #endif // PLUGIN_H ./src/platforms/mirserver/promptsessionlistener.cpp0000644000015600001650000000655412677054330023131 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "promptsessionlistener.h" #include "logging.h" namespace ms = mir::scene; Q_DECLARE_METATYPE(std::shared_ptr) PromptSessionListener::PromptSessionListener(QObject *parent) : QObject(parent) { qCDebug(QTMIR_MIR_MESSAGES) << "PromptSessionListener::PromptSessionListener - this=" << this; qRegisterMetaType>("std::shared_ptr"); } PromptSessionListener::~PromptSessionListener() { qCDebug(QTMIR_MIR_MESSAGES) << "PromptSessionListener::~PromptSessionListener - this=" << this; } void PromptSessionListener::starting(std::shared_ptr const& prompt_session) { qCDebug(QTMIR_MIR_MESSAGES) << "PromptSessionListener::starting - this=" << this << "prompt_session=" << prompt_session.get(); Q_EMIT promptSessionStarting(prompt_session); } void PromptSessionListener::stopping(std::shared_ptr const& prompt_session) { qCDebug(QTMIR_MIR_MESSAGES) << "PromptSessionListener::stopping - this=" << this << "prompt_session=" << prompt_session.get(); Q_EMIT promptSessionStopping(prompt_session); } void PromptSessionListener::suspending(std::shared_ptr const& prompt_session) { qCDebug(QTMIR_MIR_MESSAGES) << "PromptSessionListener::suspending - this=" << this << "prompt_session=" << prompt_session.get(); Q_EMIT promptSessionSuspending(prompt_session); } void PromptSessionListener::resuming(std::shared_ptr const& prompt_session) { qCDebug(QTMIR_MIR_MESSAGES) << "PromptSessionListener::resuming - this=" << this << "prompt_session=" << prompt_session.get(); Q_EMIT promptSessionResuming(prompt_session); } void PromptSessionListener::prompt_provider_added(ms::PromptSession const& prompt_session, std::shared_ptr const& prompt_provider) { qCDebug(QTMIR_MIR_MESSAGES) << "PromptSessionListener::prompt_provider_added - this=" << this << "prompt_session=" << &prompt_session << "prompt_provider=" << prompt_provider.get(); Q_EMIT promptProviderAdded(&prompt_session, prompt_provider); } void PromptSessionListener::prompt_provider_removed(ms::PromptSession const& prompt_session, std::shared_ptr const& prompt_provider) { qCDebug(QTMIR_MIR_MESSAGES) << "PromptSessionListener::prompt_provider_removed - this=" << this << "prompt_session=" << &prompt_session << "prompt_provider=" << prompt_provider.get(); Q_EMIT promptProviderRemoved(&prompt_session, prompt_provider); } ./src/platforms/mirserver/screen.h0000644000015600001650000000652012677054330017353 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SCREEN_H #define SCREEN_H // Qt #include #include #include #include // Mir #include // local #include "cursor.h" #include "screenwindow.h" class QOrientationSensor; namespace mir { namespace graphics { class DisplayBuffer; class DisplaySyncGroup; } namespace renderer { namespace gl { class RenderTarget; }} } class Screen : public QObject, public QPlatformScreen { Q_OBJECT public: Screen(const mir::graphics::DisplayConfigurationOutput &); ~Screen(); // QPlatformScreen methods. QRect geometry() const override { return m_geometry; } int depth() const override { return m_depth; } QImage::Format format() const override { return m_format; } QSizeF physicalSize() const override { return m_physicalSize; } qreal refreshRate() const override { return m_refreshRate; } Qt::ScreenOrientation nativeOrientation() const override { return m_nativeOrientation; } Qt::ScreenOrientation orientation() const override { return m_currentOrientation; } QPlatformCursor *cursor() const override; mir::graphics::DisplayConfigurationOutputType outputType() const { return m_type; } ScreenWindow* window() const; // QObject methods. void customEvent(QEvent* event) override; // To make it testable static bool skipDBusRegistration; bool orientationSensorEnabled(); public Q_SLOTS: void onDisplayPowerStateChanged(int, int); void onOrientationReadingChanged(); protected: void setWindow(ScreenWindow *window); void setMirDisplayConfiguration(const mir::graphics::DisplayConfigurationOutput &); void setMirDisplayBuffer(mir::graphics::DisplayBuffer *, mir::graphics::DisplaySyncGroup *); void swapBuffers(); void makeCurrent(); void doneCurrent(); private: void toggleSensors(const bool enable) const; bool internalDisplay() const; QRect m_geometry; int m_depth; QImage::Format m_format; QSizeF m_physicalSize; qreal m_refreshRate; mir::renderer::gl::RenderTarget *m_renderTarget; mir::graphics::DisplaySyncGroup *m_displayGroup; mir::graphics::DisplayConfigurationOutputId m_outputId; mir::graphics::DisplayConfigurationCardId m_cardId; mir::graphics::DisplayConfigurationOutputType m_type; MirPowerMode m_powerMode; Qt::ScreenOrientation m_nativeOrientation; Qt::ScreenOrientation m_currentOrientation; QOrientationSensor *m_orientationSensor; ScreenWindow *m_screenWindow; QDBusInterface *m_unityScreen; qtmir::Cursor m_cursor; friend class ScreenController; friend class ScreenWindow; }; #endif // SCREEN_H ./src/platforms/mirserver/sessionlistener.h0000644000015600001650000000455712677054330021335 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SESSIONLISTENER_H #define SESSIONLISTENER_H #include #include #include "mir/scene/session_listener.h" #include "mir/scene/session.h" #include "creationhints.h" class SurfaceObserver; class SessionListener : public QObject, public mir::scene::SessionListener { Q_OBJECT public: explicit SessionListener(QObject *parent = 0); ~SessionListener(); void starting(std::shared_ptr const& session) override; void stopping(std::shared_ptr const& session) override; void focused(std::shared_ptr const& session) override; void unfocused() override; void surface_created(mir::scene::Session&, std::shared_ptr const&) override; void destroying_surface(mir::scene::Session&, std::shared_ptr const&) override; void surfaceAboutToBeCreated(mir::scene::Session&, qtmir::CreationHints creationHints); Q_SIGNALS: void sessionStarting(std::shared_ptr const& session); void sessionStopping(std::shared_ptr const& session); void sessionFocused(std::shared_ptr const& session); void sessionUnfocused(); void sessionCreatedSurface(mir::scene::Session const*, std::shared_ptr const&, std::shared_ptr const&, qtmir::CreationHints); void sessionDestroyingSurface(mir::scene::Session const*, std::shared_ptr const&); private: QMap m_creationHintsForNewSurface; }; #endif // SESSIONLISTENER_H ./src/platforms/mirserver/screencontroller.h0000644000015600001650000000601412677054330021455 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SCREENCONTROLLER_H #define SCREENCONTROLLER_H #include #include // Mir #include // std #include namespace mir { namespace graphics { class Display; } namespace compositor { class Compositor; } } class Screen; class QWindow; /* * ScreenController monitors the Mir display configuration and compositor status, and updates * the relevant QScreen and QWindow states accordingly. * * Primary purposes are: * 1. to update QScreen state on Mir display configuration changes * 2. to stop the Qt renderer by hiding its QWindow when Mir wants to stop all compositing, * and resume Qt's renderer by showing its QWindow when Mir wants to resume compositing. * * * Threading Note: * This object must have affinity to the main Qt GUI thread, as it creates & destroys Platform * objects which Qt uses internally. However beware as the init() & terminate() methods need to * be called on the MirServerThread thread, as we need to monitor the screen state *after* * Mir has initialized but before Qt's event loop has started, and tear down before Mir terminates. * Also note the MirServerThread does not have an QEventLoop. * * All other methods must be called on the Qt GUI thread. */ class ScreenController : public QObject { Q_OBJECT public: explicit ScreenController(QObject *parent = 0); QList screens() const { return m_screenList; } bool compositing() const { return m_compositing; } QWindow* getWindowForPoint(const QPoint &point); Q_SIGNALS: void screenAdded(Screen *screen); public Q_SLOTS: void update(); public: // called by MirServer void init(const std::shared_ptr &display, const std::shared_ptr &compositor); void terminate(); // override for testing purposes virtual Screen *createScreen(const mir::graphics::DisplayConfigurationOutput &output) const; protected Q_SLOTS: void onCompositorStarting(); void onCompositorStopping(); private: Screen* findScreenWithId(const QList &list, const mir::graphics::DisplayConfigurationOutputId id); std::weak_ptr m_display; std::shared_ptr m_compositor; QList m_screenList; bool m_compositing; }; #endif // SCREENCONTROLLER_H ./src/platforms/mirserver/sessionauthorizer.cpp0000644000015600001650000000657712677054330022243 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "sessionauthorizer.h" #include "logging.h" #include "tracepoints.h" // generated from tracepoints.tp #include #include // mir #include using mir::frontend::SessionCredentials; SessionAuthorizer::SessionAuthorizer(QObject *parent) : QObject(parent) , m_connectionChecked(false) { } SessionAuthorizer::~SessionAuthorizer() { } bool SessionAuthorizer::connection_is_allowed(SessionCredentials const& creds) { tracepoint(qtmirserver, sessionAuthorizeStart); qCDebug(QTMIR_MIR_MESSAGES) << "SessionAuthorizer::connection_is_allowed - this=" << this << "pid=" << creds.pid(); bool authorized = true; if (!m_connectionChecked) { // Wait until the ApplicationManager is ready to receive requestAuthorizationForSession signals const QMetaObject *mo = metaObject(); QMetaMethod mm = mo->method(mo->indexOfSignal("requestAuthorizationForSession(pid_t,bool&)")); for (int i = 0; i < 100 && !isSignalConnected(mm); ++i) { QThread::usleep(10000); } if (!isSignalConnected(mm)) { qCDebug(QTMIR_MIR_MESSAGES) << "SessionAuthorizer::connection_is_allowed - Gave up waiting for signal listeners"; } m_connectionChecked = true; } Q_EMIT requestAuthorizationForSession(creds.pid(), authorized); // needs to block until authorized value returned tracepoint(qtmirserver, sessionAuthorizeEnd); return authorized; } bool SessionAuthorizer::configure_display_is_allowed(SessionCredentials const& creds) { qCDebug(QTMIR_MIR_MESSAGES) << "SessionAuthorizer::configure_display_is_allowed - this=" << this << "pid=" << creds.pid(); //FIXME(ricmm) Actually mediate this access for clients Q_UNUSED(creds) return true; } bool SessionAuthorizer::screencast_is_allowed(SessionCredentials const& creds) { qCDebug(QTMIR_MIR_MESSAGES) << "SessionAuthorizer::screencast_is_allowed - this=" << this << "pid=" << creds.pid(); //FIXME Actually mediate this access for clients Q_UNUSED(creds) return true; } bool SessionAuthorizer::prompt_session_is_allowed(SessionCredentials const& creds) { qCDebug(QTMIR_MIR_MESSAGES) << "SessionAuthorizer::prompt_session_is_allowed - this=" << this << "pid=" << creds.pid(); //FIXME Actually mediate this access for clients Q_UNUSED(creds) return true; } bool SessionAuthorizer::set_base_display_configuration_is_allowed(SessionCredentials const& creds) { qCDebug(QTMIR_MIR_MESSAGES) << "SessionAuthorizer::set_base_display_configuration_is_allowed - this=" << this << "pid=" << creds.pid(); //FIXME Actually mediate this access for clients Q_UNUSED(creds) return false; } ./src/platforms/mirserver/ubuntutheme.cpp0000644000015600001650000000235712677054330021000 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "ubuntutheme.h" #include const char *UbuntuTheme::name = "ubuntu"; UbuntuTheme::UbuntuTheme() { } UbuntuTheme::~UbuntuTheme() { } QVariant UbuntuTheme::themeHint(ThemeHint hint) const { if (hint == QPlatformTheme::SystemIconThemeName) { QByteArray iconTheme = qgetenv("QTUBUNTU_ICON_THEME"); if (iconTheme.isEmpty()) { return QVariant(QStringLiteral("ubuntu-mobile")); } else { return QVariant(QString(iconTheme)); } } else { return QGenericUnixTheme::themeHint(hint); } } ./src/platforms/mirserver/mircursorimages.cpp0000644000015600001650000000221412677054330021636 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mircursorimages.h" #include "namedcursor.h" using namespace qtmir; std::shared_ptr MirCursorImages::image(const std::string &cursor_name, const mir::geometry::Size&) { // We are not responsible for loading cursors. This is left for shell to do as it's drawing its own QML cursor. // So here we work around Mir API by storing just the cursor name in the CursorImage. return std::make_shared(cursor_name.c_str()); } ./src/platforms/mirserver/miropenglcontext.h0000644000015600001650000000322712677054330021476 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIROPENGLCONTEXT_H #define MIROPENGLCONTEXT_H #include #ifndef QT_NO_DEBUG #include #endif class MirServer; class ScreenWindow; class MirOpenGLContext : public QObject, public QPlatformOpenGLContext { Q_OBJECT public: MirOpenGLContext(const QSharedPointer &, const QSurfaceFormat &); ~MirOpenGLContext() = default; QSurfaceFormat format() const override; void swapBuffers(QPlatformSurface *surface) override; bool makeCurrent(QPlatformSurface *surface) override; void doneCurrent() override; bool isSharing() const override { return false; } QFunctionPointer getProcAddress(const QByteArray &procName) override; #ifndef QT_NO_DEBUG Q_SLOT void onGlDebugMessageLogged(QOpenGLDebugMessage m) { qDebug() << m; } #endif private: QSurfaceFormat m_format; ScreenWindow *m_currentWindow; #ifndef QT_NO_DEBUG QOpenGLDebugLogger *m_logger; #endif }; #endif // MIROPENGLCONTEXT_H ./src/platforms/mirserver/tileddisplayconfigurationpolicy.cpp0000644000015600001650000000275012677054330025127 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "tileddisplayconfigurationpolicy.h" #include #include namespace mg = mir::graphics; TiledDisplayConfigurationPolicy::TiledDisplayConfigurationPolicy( const std::shared_ptr &wrapped) : m_wrapped(wrapped) { } void TiledDisplayConfigurationPolicy::apply_to(mg::DisplayConfiguration& conf) { int nextTopLeftPosition = 0; m_wrapped->apply_to(conf); conf.for_each_output( [&](mg::UserDisplayConfigurationOutput& output) { if (output.connected && output.used) { output.top_left = mir::geometry::Point{nextTopLeftPosition, 0}; nextTopLeftPosition += output.modes[output.current_mode_index].size.width.as_int(); } }); } ./src/platforms/mirserver/plugin.cpp0000644000015600001650000000202412677054330017720 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "plugin.h" #include "mirserverintegration.h" QPlatformIntegration *MirServerIntegrationPlugin::create(const QString &system, const QStringList &/*paramList*/, int &argc, char **argv) { if (system.toLower() == "mirserver") return new MirServerIntegration(argc, argv); return 0; } ./src/platforms/mirserver/sessionlistener.cpp0000644000015600001650000000745212677054330021665 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "sessionlistener.h" #include "surfaceobserver.h" #include "logging.h" #include "tracepoints.h" // generated from tracepoints.tp #include namespace ms = mir::scene; Q_DECLARE_METATYPE(std::shared_ptr) Q_DECLARE_METATYPE(std::shared_ptr) SessionListener::SessionListener(QObject *parent) : QObject(parent) { qCDebug(QTMIR_MIR_MESSAGES) << "SessionListener::SessionListener - this=" << this; // need to register type to send over threads with signal/slot qRegisterMetaType>("std::shared_ptr"); qRegisterMetaType>("std::shared_ptr"); qRegisterMetaType>("std::shared_ptr"); qRegisterMetaType(); } SessionListener::~SessionListener() { qCDebug(QTMIR_MIR_MESSAGES) << "SessionListener::~SessionListener - this=" << this; } void SessionListener::starting(std::shared_ptr const& session) { tracepoint(qtmirserver, starting); qCDebug(QTMIR_MIR_MESSAGES) << "SessionListener::starting - this=" << this << "session=" << session.get(); Q_EMIT sessionStarting(session); } void SessionListener::stopping(std::shared_ptr const& session) { tracepoint(qtmirserver, stopping); qCDebug(QTMIR_MIR_MESSAGES) << "SessionListener::stopping - this=" << this << "session=" << session.get(); Q_EMIT sessionStopping(session); } void SessionListener::focused(std::shared_ptr const& session) { qCDebug(QTMIR_MIR_MESSAGES) << "SessionListener::focused - this=" << this << "session=" << session.get(); Q_EMIT sessionFocused(session); } void SessionListener::unfocused() { qCDebug(QTMIR_MIR_MESSAGES) << "SessionListener::unfocused - this=" << this; Q_EMIT sessionUnfocused(); } void SessionListener::surface_created(ms::Session& session, std::shared_ptr const& surface) { tracepoint(qtmirserver, surfaceCreated); qCDebug(QTMIR_MIR_MESSAGES) << "SessionListener::surface_created - this=" << this << "session=" << &session << "surface=" << surface.get(); std::shared_ptr surfaceObserver = std::make_shared(); SurfaceObserver::registerObserverForSurface(surfaceObserver.get(), surface.get()); surface->add_observer(surfaceObserver); qtmir::CreationHints creationHints = m_creationHintsForNewSurface.take(&session); Q_EMIT sessionCreatedSurface(&session, surface, surfaceObserver, creationHints); } void SessionListener::destroying_surface(ms::Session& session, std::shared_ptr const& surface) { tracepoint(qtmirserver, surfaceDestroyed); qCDebug(QTMIR_MIR_MESSAGES) << "SessionListener::destroying_surface - this=" << this << "session=" << &session << "surface=" << surface.get(); Q_EMIT sessionDestroyingSurface(&session, surface); } void SessionListener::surfaceAboutToBeCreated(mir::scene::Session& session, qtmir::CreationHints creationHints) { m_creationHintsForNewSurface[&session] = creationHints; } ./src/platforms/mirserver/nativeinterface.cpp0000644000015600001650000000255712677054330021604 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "nativeinterface.h" NativeInterface::NativeInterface(const QWeakPointer &server) : m_mirServer(server) { } void *NativeInterface::nativeResourceForIntegration(const QByteArray &resource) { void *result = nullptr; auto const server = m_mirServer.lock(); if (server) { if (resource == "SessionAuthorizer") result = server->sessionAuthorizer(); else if (resource == "Shell") result = server->shell(); else if (resource == "SessionListener") result = server->sessionListener(); else if (resource == "PromptSessionListener") result = server->promptSessionListener(); } return result; } ./src/platforms/mirserver/sessionauthorizer.h0000644000015600001650000000330712677054330021674 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SESSIONAUTHORIZER_H #define SESSIONAUTHORIZER_H //std #include // mir #include // Qt #include class SessionAuthorizer : public QObject, public mir::frontend::SessionAuthorizer { Q_OBJECT public: SessionAuthorizer(QObject *parent = 0); ~SessionAuthorizer(); bool connection_is_allowed(mir::frontend::SessionCredentials const& creds) override; bool configure_display_is_allowed(mir::frontend::SessionCredentials const& creds) override; bool set_base_display_configuration_is_allowed(mir::frontend::SessionCredentials const& creds) override; bool screencast_is_allowed(mir::frontend::SessionCredentials const& creds) override; bool prompt_session_is_allowed(mir::frontend::SessionCredentials const& creds) override; Q_SIGNALS: // needs to be blocked queued signal which returns value for authorized void requestAuthorizationForSession(const pid_t &pid, bool &authorized); private: bool m_connectionChecked; }; #endif // SESSIONAUTHORIZER_H ./src/platforms/mirserver/promptsessionlistener.h0000644000015600001650000000444712677054330022575 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef PROMPTSESSIONLISTENER_H #define PROMPTSESSIONLISTENER_H #include #include "mir/scene/prompt_session_listener.h" class PromptSessionListener : public QObject, public mir::scene::PromptSessionListener { Q_OBJECT public: explicit PromptSessionListener(QObject *parent = 0); ~PromptSessionListener(); void starting(std::shared_ptr const& prompt_session) override; void stopping(std::shared_ptr const& prompt_session) override; void suspending(std::shared_ptr const& prompt_session) override; void resuming(std::shared_ptr const& prompt_session) override; void prompt_provider_added(mir::scene::PromptSession const& prompt_session, std::shared_ptr const& prompt_provider) override; void prompt_provider_removed(mir::scene::PromptSession const& prompt_session, std::shared_ptr const& prompt_provider) override; Q_SIGNALS: void promptSessionStarting(std::shared_ptr const& session); void promptSessionStopping(std::shared_ptr const& session); void promptSessionSuspending(std::shared_ptr const& session); void promptSessionResuming(std::shared_ptr const& session); void promptProviderAdded(mir::scene::PromptSession const*, std::shared_ptr const&); void promptProviderRemoved(mir::scene::PromptSession const*, std::shared_ptr const&); }; #endif // SESSIONLISTENER_H ./src/platforms/mirserver/miropenglcontext.cpp0000644000015600001650000001317412677054330022033 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "miropenglcontext.h" #include "offscreensurface.h" #include "mirglconfig.h" #include "mirserver.h" #include "screenwindow.h" #include #include #include #include // Mir #include #include // Qt supports one GL context per screen, but also shared contexts. // The Mir "Display" generates a shared GL context for all DisplayBuffers // (i.e. individual display output buffers) to use as a common base context. MirOpenGLContext::MirOpenGLContext(const QSharedPointer &server, const QSurfaceFormat &format) : m_currentWindow(nullptr) #ifndef QT_NO_DEBUG , m_logger(new QOpenGLDebugLogger(this)) #endif { auto display = server->the_display(); // create a temporary GL context to fetch the EGL display and config, so Qt can determine the surface format std::unique_ptr mirContext = display->create_gl_context(); mirContext->make_current(); EGLDisplay eglDisplay = eglGetCurrentDisplay(); if (eglDisplay == EGL_NO_DISPLAY) { qFatal("Unable to determine current EGL Display"); } EGLContext eglContext = eglGetCurrentContext(); if (eglContext == EGL_NO_CONTEXT) { qFatal("Unable to determine current EGL Context"); } EGLint eglConfigId = -1; EGLBoolean result; result = eglQueryContext(eglDisplay, eglContext, EGL_CONFIG_ID, &eglConfigId); if (result != EGL_TRUE || eglConfigId < 0) { qFatal("Unable to determine current EGL Config ID"); } EGLConfig eglConfig; EGLint matchingEglConfigCount; EGLint const attribList[] = { EGL_CONFIG_ID, eglConfigId, EGL_NONE }; result = eglChooseConfig(eglDisplay, attribList, &eglConfig, 1, &matchingEglConfigCount); if (result != EGL_TRUE || eglConfig == nullptr || matchingEglConfigCount < 1) { qFatal("Unable to select EGL Config with the supposed current config ID"); } QSurfaceFormat formatCopy = format; formatCopy.setRenderableType(QSurfaceFormat::OpenGLES); m_format = q_glFormatFromConfig(eglDisplay, eglConfig, formatCopy); // FIXME: the temporary gl context created by Mir does not have the attributes we specified // in the GLConfig, so need to set explicitly for now m_format.setDepthBufferSize(server->the_gl_config()->depth_buffer_bits()); m_format.setStencilBufferSize(server->the_gl_config()->stencil_buffer_bits()); m_format.setSamples(-1); #ifndef QT_NO_DEBUG const char* string = (const char*) glGetString(GL_VENDOR); qDebug() << "OpenGL ES vendor: " << qPrintable(string); string = (const char*) glGetString(GL_RENDERER); qDebug() << "OpenGL ES renderer" << qPrintable(string); string = (const char*) glGetString(GL_VERSION); qDebug() << "OpenGL ES version" << qPrintable(string); string = (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION); qDebug() << "OpenGL ES Shading Language version:" << qPrintable(string); string = (const char*) glGetString(GL_EXTENSIONS); qDebug() << "OpenGL ES extensions:" << qPrintable(string); q_printEglConfig(eglDisplay, eglConfig); QObject::connect(m_logger, &QOpenGLDebugLogger::messageLogged, this, &MirOpenGLContext::onGlDebugMessageLogged, Qt::DirectConnection); #endif // debug } QSurfaceFormat MirOpenGLContext::format() const { return m_format; } void MirOpenGLContext::swapBuffers(QPlatformSurface *surface) { if (surface->surface()->surfaceClass() == QSurface::Offscreen) { // NOOP } else { // ultimately calls Mir's DisplayBuffer::post_update() ScreenWindow *screenWindow = static_cast(surface); screenWindow->swapBuffers(); //blocks for vsync } } bool MirOpenGLContext::makeCurrent(QPlatformSurface *surface) { if (surface->surface()->surfaceClass() == QSurface::Offscreen) { auto offscreen = static_cast(surface); if (!offscreen->buffer()) { auto buffer = new QOpenGLFramebufferObject(surface->surface()->size()); offscreen->setBuffer(buffer); } return offscreen->buffer()->bind(); } // ultimately calls Mir's DisplayBuffer::make_current() ScreenWindow *screenWindow = static_cast(surface); if (screenWindow) { m_currentWindow = screenWindow; screenWindow->makeCurrent(); #ifndef QT_NO_DEBUG if (!m_logger->isLogging() && m_logger->initialize()) { m_logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); m_logger->enableMessages(); } #endif return true; } return false; } void MirOpenGLContext::doneCurrent() { if (m_currentWindow) { m_currentWindow->doneCurrent(); m_currentWindow = nullptr; } } QFunctionPointer MirOpenGLContext::getProcAddress(const QByteArray &procName) { return eglGetProcAddress(procName.constData()); } ./src/platforms/mirserver/cursor.cpp0000644000015600001650000001312312677054330017741 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . * */ #include "cursor.h" #include "logging.h" #include "mirsingleton.h" // Unity API #include using namespace qtmir; Cursor::Cursor() { m_shapeToCursorName[Qt::ArrowCursor] = "left_ptr"; m_shapeToCursorName[Qt::UpArrowCursor] = "up_arrow"; m_shapeToCursorName[Qt::CrossCursor] = "cross"; m_shapeToCursorName[Qt::WaitCursor] = "watch"; m_shapeToCursorName[Qt::IBeamCursor] = "xterm"; m_shapeToCursorName[Qt::SizeVerCursor] = "size_ver"; m_shapeToCursorName[Qt::SizeHorCursor] = "size_hor"; m_shapeToCursorName[Qt::SizeBDiagCursor] = "size_bdiag"; m_shapeToCursorName[Qt::SizeFDiagCursor] = "size_fdiag"; m_shapeToCursorName[Qt::SizeAllCursor] = "size_all"; m_shapeToCursorName[Qt::BlankCursor] = "blank"; m_shapeToCursorName[Qt::SplitVCursor] = "split_v"; m_shapeToCursorName[Qt::SplitHCursor] = "split_h"; m_shapeToCursorName[Qt::PointingHandCursor] = "hand"; m_shapeToCursorName[Qt::ForbiddenCursor] = "forbidden"; m_shapeToCursorName[Qt::WhatsThisCursor] = "whats_this"; m_shapeToCursorName[Qt::BusyCursor] = "left_ptr_watch"; m_shapeToCursorName[Qt::OpenHandCursor] = "openhand"; m_shapeToCursorName[Qt::ClosedHandCursor] = "closedhand"; m_shapeToCursorName[Qt::DragCopyCursor] = "dnd-copy"; m_shapeToCursorName[Qt::DragMoveCursor] = "dnd-move"; m_shapeToCursorName[Qt::DragLinkCursor] = "dnd-link"; connect(Mir::instance(), &Mir::cursorNameChanged, this, &Cursor::setMirCursorName); } void Cursor::changeCursor(QCursor *windowCursor, QWindow * /*window*/) { if (m_mousePointer.isNull()) { return; } if (windowCursor) { if (windowCursor->pixmap().isNull()) { m_qtCursorName = m_shapeToCursorName.value(windowCursor->shape(), QLatin1String("left_ptr")); m_mousePointer->setCustomCursor(QCursor()); } else { m_qtCursorName = QLatin1String("custom"); m_mousePointer->setCustomCursor(*windowCursor); } } else { m_qtCursorName.clear(); m_mousePointer->setCustomCursor(QCursor()); } updateMousePointerCursorName(); } void Cursor::setMirCursorName(const QString &mirCursorName) { m_mirCursorName = mirCursorName; updateMousePointerCursorName(); } void Cursor::setMousePointer(MirMousePointerInterface *mousePointer) { QMutexLocker locker(&m_mutex); if (mousePointer && !m_mousePointer.isNull()) { qFatal("QPA mirserver: Only one MousePointer per screen is allowed!"); } m_mousePointer = mousePointer; updateMousePointerCursorName(); } bool Cursor::handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) { QMutexLocker locker(&m_mutex); if (!m_mousePointer || !m_mousePointer->isVisible()) { return false; } // Must not be called directly as we're most likely not in Qt's GUI (main) thread. bool ok = QMetaObject::invokeMethod(m_mousePointer, "handleMouseEvent", Qt::AutoConnection, Q_ARG(ulong, timestamp), Q_ARG(QPointF, movement), Q_ARG(Qt::MouseButtons, buttons), Q_ARG(Qt::KeyboardModifiers, modifiers)); if (!ok) { qCWarning(QTMIR_MIR_INPUT) << "Failed to invoke MousePointer::handleMouseEvent"; } return ok; } bool Cursor::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers) { QMutexLocker locker(&m_mutex); if (!m_mousePointer || !m_mousePointer->isVisible()) { return false; } // Must not be called directly as we're most likely not in Qt's GUI (main) thread. bool ok = QMetaObject::invokeMethod(m_mousePointer, "handleWheelEvent", Qt::AutoConnection, Q_ARG(ulong, timestamp), Q_ARG(QPoint, angleDelta), Q_ARG(Qt::KeyboardModifiers, modifiers)); if (!ok) { qCWarning(QTMIR_MIR_INPUT) << "Failed to invoke MousePointer::handleMouseEvent"; } return ok; } void Cursor::setPos(const QPoint &pos) { if (!m_mousePointer) { QPlatformCursor::setPos(pos); return; } QPointF movement; QPointF mouseScenePos = m_mousePointer->mapToItem(nullptr, QPointF(0, 0)); movement.setX(pos.x() - mouseScenePos.x()); movement.setY(pos.y() - mouseScenePos.y()); m_mousePointer->handleMouseEvent(0 /*timestamp*/, movement, Qt::NoButton, Qt::NoModifier); } QPoint Cursor::pos() const { if (m_mousePointer) { return m_mousePointer->mapToItem(nullptr, QPointF(0, 0)).toPoint(); } else { return QPlatformCursor::pos(); } } void Cursor::updateMousePointerCursorName() { if (!m_mousePointer) { return; } if (m_mirCursorName.isEmpty()) { if (m_qtCursorName.isEmpty()) { m_mousePointer->setCursorName("left_ptr"); } else { m_mousePointer->setCursorName(m_qtCursorName); } } else { m_mousePointer->setCursorName(m_mirCursorName); } } ./src/platforms/mirserver/qteventfeeder.cpp0000644000015600001650000010705712677054330021277 0ustar jenkinsjenkins/* * Copyright (C) 2013-2016 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "qteventfeeder.h" #include "cursor.h" #include "logging.h" #include "timestamp.h" #include "tracepoints.h" // generated from tracepoints.tp #include "screen.h" // NEEDED? #include "screencontroller.h" #include #include #include #include #include #include #include #include // common dir #include Q_LOGGING_CATEGORY(QTMIR_MIR_INPUT, "qtmir.mir.input", QtWarningMsg) // XKB Keysyms which do not map directly to Qt types (i.e. Unicode points) static const uint32_t KeyTable[] = { // misc keys XKB_KEY_Escape, Qt::Key_Escape, XKB_KEY_Tab, Qt::Key_Tab, XKB_KEY_ISO_Left_Tab, Qt::Key_Backtab, XKB_KEY_BackSpace, Qt::Key_Backspace, XKB_KEY_Return, Qt::Key_Return, XKB_KEY_Insert, Qt::Key_Insert, XKB_KEY_Delete, Qt::Key_Delete, XKB_KEY_Clear, Qt::Key_Delete, XKB_KEY_Pause, Qt::Key_Pause, XKB_KEY_Print, Qt::Key_Print, 0x1005FF60, Qt::Key_SysReq, // hardcoded Sun SysReq 0x1007ff00, Qt::Key_SysReq, // hardcoded X386 SysReq // cursor movement XKB_KEY_Home, Qt::Key_Home, XKB_KEY_End, Qt::Key_End, XKB_KEY_Left, Qt::Key_Left, XKB_KEY_Up, Qt::Key_Up, XKB_KEY_Right, Qt::Key_Right, XKB_KEY_Down, Qt::Key_Down, XKB_KEY_Prior, Qt::Key_PageUp, XKB_KEY_Next, Qt::Key_PageDown, // modifiers XKB_KEY_Shift_L, Qt::Key_Shift, XKB_KEY_Shift_R, Qt::Key_Shift, XKB_KEY_Shift_Lock, Qt::Key_Shift, XKB_KEY_Control_L, Qt::Key_Control, XKB_KEY_Control_R, Qt::Key_Control, XKB_KEY_Meta_L, Qt::Key_Meta, XKB_KEY_Meta_R, Qt::Key_Meta, XKB_KEY_Alt_L, Qt::Key_Alt, XKB_KEY_Alt_R, Qt::Key_Alt, XKB_KEY_Caps_Lock, Qt::Key_CapsLock, XKB_KEY_Num_Lock, Qt::Key_NumLock, XKB_KEY_Scroll_Lock, Qt::Key_ScrollLock, XKB_KEY_Super_L, Qt::Key_Super_L, XKB_KEY_Super_R, Qt::Key_Super_R, XKB_KEY_Menu, Qt::Key_Menu, XKB_KEY_Hyper_L, Qt::Key_Hyper_L, XKB_KEY_Hyper_R, Qt::Key_Hyper_R, XKB_KEY_Help, Qt::Key_Help, 0x1000FF74, Qt::Key_Backtab, // hardcoded HP backtab 0x1005FF10, Qt::Key_F11, // hardcoded Sun F36 (labeled F11) 0x1005FF11, Qt::Key_F12, // hardcoded Sun F37 (labeled F12) // numeric and function keypad keys XKB_KEY_KP_Space, Qt::Key_Space, XKB_KEY_KP_Tab, Qt::Key_Tab, XKB_KEY_KP_Enter, Qt::Key_Enter, //XKB_KEY_KP_F1, Qt::Key_F1, //XKB_KEY_KP_F2, Qt::Key_F2, //XKB_KEY_KP_F3, Qt::Key_F3, //XKB_KEY_KP_F4, Qt::Key_F4, XKB_KEY_KP_Home, Qt::Key_Home, XKB_KEY_KP_Left, Qt::Key_Left, XKB_KEY_KP_Up, Qt::Key_Up, XKB_KEY_KP_Right, Qt::Key_Right, XKB_KEY_KP_Down, Qt::Key_Down, XKB_KEY_KP_Prior, Qt::Key_PageUp, XKB_KEY_KP_Next, Qt::Key_PageDown, XKB_KEY_KP_End, Qt::Key_End, XKB_KEY_KP_Begin, Qt::Key_Clear, XKB_KEY_KP_Insert, Qt::Key_Insert, XKB_KEY_KP_Delete, Qt::Key_Delete, XKB_KEY_KP_Equal, Qt::Key_Equal, XKB_KEY_KP_Multiply, Qt::Key_Asterisk, XKB_KEY_KP_Add, Qt::Key_Plus, XKB_KEY_KP_Separator, Qt::Key_Comma, XKB_KEY_KP_Subtract, Qt::Key_Minus, XKB_KEY_KP_Decimal, Qt::Key_Period, XKB_KEY_KP_Divide, Qt::Key_Slash, // International input method support keys // International & multi-key character composition XKB_KEY_ISO_Level3_Shift, Qt::Key_AltGr, XKB_KEY_Multi_key, Qt::Key_Multi_key, XKB_KEY_Codeinput, Qt::Key_Codeinput, XKB_KEY_SingleCandidate, Qt::Key_SingleCandidate, XKB_KEY_MultipleCandidate, Qt::Key_MultipleCandidate, XKB_KEY_PreviousCandidate, Qt::Key_PreviousCandidate, // Misc Functions XKB_KEY_Mode_switch, Qt::Key_Mode_switch, XKB_KEY_script_switch, Qt::Key_Mode_switch, // Japanese keyboard support XKB_KEY_Kanji, Qt::Key_Kanji, XKB_KEY_Muhenkan, Qt::Key_Muhenkan, //XKB_KEY_Henkan_Mode, Qt::Key_Henkan_Mode, XKB_KEY_Henkan_Mode, Qt::Key_Henkan, XKB_KEY_Henkan, Qt::Key_Henkan, XKB_KEY_Romaji, Qt::Key_Romaji, XKB_KEY_Hiragana, Qt::Key_Hiragana, XKB_KEY_Katakana, Qt::Key_Katakana, XKB_KEY_Hiragana_Katakana, Qt::Key_Hiragana_Katakana, XKB_KEY_Zenkaku, Qt::Key_Zenkaku, XKB_KEY_Hankaku, Qt::Key_Hankaku, XKB_KEY_Zenkaku_Hankaku, Qt::Key_Zenkaku_Hankaku, XKB_KEY_Touroku, Qt::Key_Touroku, XKB_KEY_Massyo, Qt::Key_Massyo, XKB_KEY_Kana_Lock, Qt::Key_Kana_Lock, XKB_KEY_Kana_Shift, Qt::Key_Kana_Shift, XKB_KEY_Eisu_Shift, Qt::Key_Eisu_Shift, XKB_KEY_Eisu_toggle, Qt::Key_Eisu_toggle, //XKB_KEY_Kanji_Bangou, Qt::Key_Kanji_Bangou, //XKB_KEY_Zen_Koho, Qt::Key_Zen_Koho, //XKB_KEY_Mae_Koho, Qt::Key_Mae_Koho, XKB_KEY_Kanji_Bangou, Qt::Key_Codeinput, XKB_KEY_Zen_Koho, Qt::Key_MultipleCandidate, XKB_KEY_Mae_Koho, Qt::Key_PreviousCandidate, #ifdef XKB_KEY_KOREAN // Korean keyboard support XKB_KEY_Hangul, Qt::Key_Hangul, XKB_KEY_Hangul_Start, Qt::Key_Hangul_Start, XKB_KEY_Hangul_End, Qt::Key_Hangul_End, XKB_KEY_Hangul_Hanja, Qt::Key_Hangul_Hanja, XKB_KEY_Hangul_Jamo, Qt::Key_Hangul_Jamo, XKB_KEY_Hangul_Romaja, Qt::Key_Hangul_Romaja, //XKB_KEY_Hangul_Codeinput, Qt::Key_Hangul_Codeinput, XKB_KEY_Hangul_Codeinput, Qt::Key_Codeinput, XKB_KEY_Hangul_Jeonja, Qt::Key_Hangul_Jeonja, XKB_KEY_Hangul_Banja, Qt::Key_Hangul_Banja, XKB_KEY_Hangul_PreHanja, Qt::Key_Hangul_PreHanja, XKB_KEY_Hangul_PostHanja, Qt::Key_Hangul_PostHanja, //XKB_KEY_Hangul_SingleCandidate,Qt::Key_Hangul_SingleCandidate, //XKB_KEY_Hangul_MultipleCandidate,Qt::Key_Hangul_MultipleCandidate, //XKB_KEY_Hangul_PreviousCandidate,Qt::Key_Hangul_PreviousCandidate, XKB_KEY_Hangul_SingleCandidate, Qt::Key_SingleCandidate, XKB_KEY_Hangul_MultipleCandidate,Qt::Key_MultipleCandidate, XKB_KEY_Hangul_PreviousCandidate,Qt::Key_PreviousCandidate, XKB_KEY_Hangul_Special, Qt::Key_Hangul_Special, //XKB_KEY_Hangul_switch, Qt::Key_Hangul_switch, XKB_KEY_Hangul_switch, Qt::Key_Mode_switch, #endif // XKB_KEY_KOREAN // dead keys XKB_KEY_dead_grave, Qt::Key_Dead_Grave, XKB_KEY_dead_acute, Qt::Key_Dead_Acute, XKB_KEY_dead_circumflex, Qt::Key_Dead_Circumflex, XKB_KEY_dead_tilde, Qt::Key_Dead_Tilde, XKB_KEY_dead_macron, Qt::Key_Dead_Macron, XKB_KEY_dead_breve, Qt::Key_Dead_Breve, XKB_KEY_dead_abovedot, Qt::Key_Dead_Abovedot, XKB_KEY_dead_diaeresis, Qt::Key_Dead_Diaeresis, XKB_KEY_dead_abovering, Qt::Key_Dead_Abovering, XKB_KEY_dead_doubleacute, Qt::Key_Dead_Doubleacute, XKB_KEY_dead_caron, Qt::Key_Dead_Caron, XKB_KEY_dead_cedilla, Qt::Key_Dead_Cedilla, XKB_KEY_dead_ogonek, Qt::Key_Dead_Ogonek, XKB_KEY_dead_iota, Qt::Key_Dead_Iota, XKB_KEY_dead_voiced_sound, Qt::Key_Dead_Voiced_Sound, XKB_KEY_dead_semivoiced_sound, Qt::Key_Dead_Semivoiced_Sound, XKB_KEY_dead_belowdot, Qt::Key_Dead_Belowdot, XKB_KEY_dead_hook, Qt::Key_Dead_Hook, XKB_KEY_dead_horn, Qt::Key_Dead_Horn, // Special keys from X.org - This include multimedia keys, // wireless/bluetooth/uwb keys, special launcher keys, etc. XKB_KEY_XF86Back, Qt::Key_Back, XKB_KEY_XF86Forward, Qt::Key_Forward, XKB_KEY_XF86Stop, Qt::Key_Stop, XKB_KEY_XF86Refresh, Qt::Key_Refresh, XKB_KEY_XF86Favorites, Qt::Key_Favorites, XKB_KEY_XF86AudioMedia, Qt::Key_LaunchMedia, XKB_KEY_XF86OpenURL, Qt::Key_OpenUrl, XKB_KEY_XF86HomePage, Qt::Key_HomePage, XKB_KEY_XF86Search, Qt::Key_Search, XKB_KEY_XF86AudioLowerVolume, Qt::Key_VolumeDown, XKB_KEY_XF86AudioMute, Qt::Key_VolumeMute, XKB_KEY_XF86AudioRaiseVolume, Qt::Key_VolumeUp, XKB_KEY_XF86AudioPlay, Qt::Key_MediaPlay, XKB_KEY_XF86AudioStop, Qt::Key_MediaStop, XKB_KEY_XF86AudioPrev, Qt::Key_MediaPrevious, XKB_KEY_XF86AudioNext, Qt::Key_MediaNext, XKB_KEY_XF86AudioRecord, Qt::Key_MediaRecord, XKB_KEY_XF86AudioPause, Qt::Key_MediaPause, XKB_KEY_XF86Mail, Qt::Key_LaunchMail, XKB_KEY_XF86MyComputer, Qt::Key_Launch0, // ### Qt 6: remap properly XKB_KEY_XF86Calculator, Qt::Key_Launch1, XKB_KEY_XF86Memo, Qt::Key_Memo, XKB_KEY_XF86ToDoList, Qt::Key_ToDoList, XKB_KEY_XF86Calendar, Qt::Key_Calendar, XKB_KEY_XF86PowerDown, Qt::Key_PowerDown, XKB_KEY_XF86ContrastAdjust, Qt::Key_ContrastAdjust, XKB_KEY_XF86Standby, Qt::Key_Standby, XKB_KEY_XF86MonBrightnessUp, Qt::Key_MonBrightnessUp, XKB_KEY_XF86MonBrightnessDown, Qt::Key_MonBrightnessDown, XKB_KEY_XF86KbdLightOnOff, Qt::Key_KeyboardLightOnOff, XKB_KEY_XF86KbdBrightnessUp, Qt::Key_KeyboardBrightnessUp, XKB_KEY_XF86KbdBrightnessDown, Qt::Key_KeyboardBrightnessDown, XKB_KEY_XF86PowerOff, Qt::Key_PowerOff, XKB_KEY_XF86WakeUp, Qt::Key_WakeUp, XKB_KEY_XF86Eject, Qt::Key_Eject, XKB_KEY_XF86ScreenSaver, Qt::Key_ScreenSaver, XKB_KEY_XF86WWW, Qt::Key_WWW, XKB_KEY_XF86Sleep, Qt::Key_Sleep, XKB_KEY_XF86LightBulb, Qt::Key_LightBulb, XKB_KEY_XF86Shop, Qt::Key_Shop, XKB_KEY_XF86History, Qt::Key_History, XKB_KEY_XF86AddFavorite, Qt::Key_AddFavorite, XKB_KEY_XF86HotLinks, Qt::Key_HotLinks, XKB_KEY_XF86BrightnessAdjust, Qt::Key_BrightnessAdjust, XKB_KEY_XF86Finance, Qt::Key_Finance, XKB_KEY_XF86Community, Qt::Key_Community, XKB_KEY_XF86AudioRewind, Qt::Key_AudioRewind, XKB_KEY_XF86BackForward, Qt::Key_BackForward, XKB_KEY_XF86ApplicationLeft, Qt::Key_ApplicationLeft, XKB_KEY_XF86ApplicationRight, Qt::Key_ApplicationRight, XKB_KEY_XF86Book, Qt::Key_Book, XKB_KEY_XF86CD, Qt::Key_CD, XKB_KEY_XF86Calculater, Qt::Key_Calculator, XKB_KEY_XF86Clear, Qt::Key_Clear, XKB_KEY_XF86ClearGrab, Qt::Key_ClearGrab, XKB_KEY_XF86Close, Qt::Key_Close, XKB_KEY_XF86Copy, Qt::Key_Copy, XKB_KEY_XF86Cut, Qt::Key_Cut, XKB_KEY_XF86Display, Qt::Key_Display, XKB_KEY_XF86DOS, Qt::Key_DOS, XKB_KEY_XF86Documents, Qt::Key_Documents, XKB_KEY_XF86Excel, Qt::Key_Excel, XKB_KEY_XF86Explorer, Qt::Key_Explorer, XKB_KEY_XF86Game, Qt::Key_Game, XKB_KEY_XF86Go, Qt::Key_Go, XKB_KEY_XF86iTouch, Qt::Key_iTouch, XKB_KEY_XF86LogOff, Qt::Key_LogOff, XKB_KEY_XF86Market, Qt::Key_Market, XKB_KEY_XF86Meeting, Qt::Key_Meeting, XKB_KEY_XF86MenuKB, Qt::Key_MenuKB, XKB_KEY_XF86MenuPB, Qt::Key_MenuPB, XKB_KEY_XF86MySites, Qt::Key_MySites, XKB_KEY_XF86New, Qt::Key_New, XKB_KEY_XF86News, Qt::Key_News, XKB_KEY_XF86OfficeHome, Qt::Key_OfficeHome, XKB_KEY_XF86Open, Qt::Key_Open, XKB_KEY_XF86Option, Qt::Key_Option, XKB_KEY_XF86Paste, Qt::Key_Paste, XKB_KEY_XF86Phone, Qt::Key_Phone, XKB_KEY_XF86Reply, Qt::Key_Reply, XKB_KEY_XF86Reload, Qt::Key_Reload, XKB_KEY_XF86RotateWindows, Qt::Key_RotateWindows, XKB_KEY_XF86RotationPB, Qt::Key_RotationPB, XKB_KEY_XF86RotationKB, Qt::Key_RotationKB, XKB_KEY_XF86Save, Qt::Key_Save, XKB_KEY_XF86Send, Qt::Key_Send, XKB_KEY_XF86Spell, Qt::Key_Spell, XKB_KEY_XF86SplitScreen, Qt::Key_SplitScreen, XKB_KEY_XF86Support, Qt::Key_Support, XKB_KEY_XF86TaskPane, Qt::Key_TaskPane, XKB_KEY_XF86Terminal, Qt::Key_Terminal, XKB_KEY_XF86Tools, Qt::Key_Tools, XKB_KEY_XF86Travel, Qt::Key_Travel, XKB_KEY_XF86Video, Qt::Key_Video, XKB_KEY_XF86Word, Qt::Key_Word, XKB_KEY_XF86Xfer, Qt::Key_Xfer, XKB_KEY_XF86ZoomIn, Qt::Key_ZoomIn, XKB_KEY_XF86ZoomOut, Qt::Key_ZoomOut, XKB_KEY_XF86Away, Qt::Key_Away, XKB_KEY_XF86Messenger, Qt::Key_Messenger, XKB_KEY_XF86WebCam, Qt::Key_WebCam, XKB_KEY_XF86MailForward, Qt::Key_MailForward, XKB_KEY_XF86Pictures, Qt::Key_Pictures, XKB_KEY_XF86Music, Qt::Key_Music, XKB_KEY_XF86Battery, Qt::Key_Battery, XKB_KEY_XF86Bluetooth, Qt::Key_Bluetooth, XKB_KEY_XF86WLAN, Qt::Key_WLAN, XKB_KEY_XF86UWB, Qt::Key_UWB, XKB_KEY_XF86AudioForward, Qt::Key_AudioForward, XKB_KEY_XF86AudioRepeat, Qt::Key_AudioRepeat, XKB_KEY_XF86AudioRandomPlay, Qt::Key_AudioRandomPlay, XKB_KEY_XF86Subtitle, Qt::Key_Subtitle, XKB_KEY_XF86AudioCycleTrack, Qt::Key_AudioCycleTrack, XKB_KEY_XF86Time, Qt::Key_Time, XKB_KEY_XF86Select, Qt::Key_Select, XKB_KEY_XF86View, Qt::Key_View, XKB_KEY_XF86TopMenu, Qt::Key_TopMenu, XKB_KEY_XF86Red, Qt::Key_Red, XKB_KEY_XF86Green, Qt::Key_Green, XKB_KEY_XF86Yellow, Qt::Key_Yellow, XKB_KEY_XF86Blue, Qt::Key_Blue, XKB_KEY_XF86Bluetooth, Qt::Key_Bluetooth, XKB_KEY_XF86Suspend, Qt::Key_Suspend, XKB_KEY_XF86Hibernate, Qt::Key_Hibernate, XKB_KEY_XF86TouchpadToggle, Qt::Key_TouchpadToggle, XKB_KEY_XF86TouchpadOn, Qt::Key_TouchpadOn, XKB_KEY_XF86TouchpadOff, Qt::Key_TouchpadOff, XKB_KEY_XF86AudioMicMute, Qt::Key_MicMute, XKB_KEY_XF86Launch0, Qt::Key_Launch2, // ### Qt 6: remap properly XKB_KEY_XF86Launch1, Qt::Key_Launch3, XKB_KEY_XF86Launch2, Qt::Key_Launch4, XKB_KEY_XF86Launch3, Qt::Key_Launch5, XKB_KEY_XF86Launch4, Qt::Key_Launch6, XKB_KEY_XF86Launch5, Qt::Key_Launch7, XKB_KEY_XF86Launch6, Qt::Key_Launch8, XKB_KEY_XF86Launch7, Qt::Key_Launch9, XKB_KEY_XF86Launch8, Qt::Key_LaunchA, XKB_KEY_XF86Launch9, Qt::Key_LaunchB, XKB_KEY_XF86LaunchA, Qt::Key_LaunchC, XKB_KEY_XF86LaunchB, Qt::Key_LaunchD, XKB_KEY_XF86LaunchC, Qt::Key_LaunchE, XKB_KEY_XF86LaunchD, Qt::Key_LaunchF, XKB_KEY_XF86LaunchE, Qt::Key_LaunchG, XKB_KEY_XF86LaunchF, Qt::Key_LaunchH, 0, 0 }; static uint32_t translateKeysym(uint32_t sym, const QString &text) { int code = 0; QTextCodec *systemCodec = QTextCodec::codecForLocale(); if (sym < 128 || (sym < 256 && systemCodec->mibEnum() == 4)) { // upper-case key, if known code = isprint((int)sym) ? toupper((int)sym) : 0; } else if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F35) { return Qt::Key_F1 + (int(sym) - XKB_KEY_F1); } else if (text.length() == 1 && text.unicode()->unicode() > 0x1f && text.unicode()->unicode() != 0x7f && !(sym >= XKB_KEY_dead_grave && sym <= XKB_KEY_dead_currency)) { code = text.unicode()->toUpper().unicode(); } else { for (int i = 0; KeyTable[i]; i += 2) if (sym == KeyTable[i]) code = KeyTable[i + 1]; } return code; } namespace { class QtWindowSystem : public QtEventFeeder::QtWindowSystemInterface { public: QtWindowSystem() { // because we're using QMetaObject::invoke with arguments of those types qRegisterMetaType("Qt::KeyboardModifiers"); qRegisterMetaType("Qt::MouseButtons"); } void setScreenController(const QSharedPointer &sc) override { m_screenController = sc; } virtual QWindow* focusedWindow() override { return QGuiApplication::focusWindow(); } QWindow* getWindowForTouchPoint(const QPoint &point) override //FIXME: not efficient, not updating focused window { return m_screenController->getWindowForPoint(point); } void registerTouchDevice(QTouchDevice *device) override { QWindowSystemInterface::registerTouchDevice(device); } void handleExtendedKeyEvent(QWindow *window, ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, const QString& text, bool autorep, ushort count) override { QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count); } void handleTouchEvent(QWindow *window, ulong timestamp, QTouchDevice *device, const QList &points, Qt::KeyboardModifiers mods) override { QWindowSystemInterface::handleTouchEvent(window, timestamp, device, points, mods); } void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) override { // Send to the first screen that handles the mouse event // TODO: Have a mechanism to tell which screen currently has the logical mouse pointer // (because they all might have their own separate graphical mouse pointer item) // This will probably come once we implement the feature of having the mouse pointer // crossing adjacent screens. QList screens = m_screenController->screens(); bool eventHandled = false; int i = 0; while (i < screens.count() && !eventHandled) { auto platformCursor = static_cast(screens[i]->cursor()); eventHandled = platformCursor->handleMouseEvent(timestamp, movement, buttons, modifiers); ++i; } } void handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers mods) override { // Send to the first screen that handles the mouse event // TODO: Have a mechanism to tell which screen currently has the logical mouse pointer // (because they all might have their own separate graphical mouse pointer item) // This will probably come once we implement the feature of having the mouse pointer // crossing adjacent screens. QList screens = m_screenController->screens(); bool eventHandled = false; int i = 0; while (i < screens.count() && !eventHandled) { auto platformCursor = static_cast(screens.at(i)->cursor()); eventHandled = platformCursor->handleWheelEvent(timestamp, angleDelta, mods); ++i; } } private: QSharedPointer m_screenController; }; } // anonymous namespace QtEventFeeder::QtEventFeeder(const QSharedPointer &screenController) : QtEventFeeder(screenController, new QtWindowSystem) { } QtEventFeeder::QtEventFeeder(const QSharedPointer &screenController, QtEventFeeder::QtWindowSystemInterface *windowSystem) : mQtWindowSystem(windowSystem) { // Initialize touch device. Hardcoded just like in qtubuntu // TODO: Create them from info gathered from Mir and store things like device id and source // in a QTouchDevice-derived class created by us. So that we can properly assemble back // MirEvents our of QTouchEvents to give to mir::scene::Surface::consume. mTouchDevice = new QTouchDevice(); // Qt takes ownership of mTouchDevice with registerTouchDevice mTouchDevice->setType(QTouchDevice::TouchScreen); mTouchDevice->setCapabilities( QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::Pressure | QTouchDevice::NormalizedPosition); mQtWindowSystem->setScreenController(screenController); mQtWindowSystem->registerTouchDevice(mTouchDevice); } QtEventFeeder::~QtEventFeeder() { delete mQtWindowSystem; } bool QtEventFeeder::dispatch(MirEvent const& event) { auto type = mir_event_get_type(&event); if (type != mir_event_type_input) return false; auto iev = mir_event_get_input_event(&event); switch (mir_input_event_get_type(iev)) { case mir_input_event_type_key: dispatchKey(iev); break; case mir_input_event_type_touch: dispatchTouch(iev); break; case mir_input_event_type_pointer: dispatchPointer(iev); default: break; } return true; } namespace { Qt::KeyboardModifiers getQtModifiersFromMir(MirInputEventModifiers modifiers) { Qt::KeyboardModifiers qtModifiers = Qt::NoModifier; if (modifiers & mir_input_event_modifier_shift) { qtModifiers |= Qt::ShiftModifier; } if (modifiers & mir_input_event_modifier_ctrl) { qtModifiers |= Qt::ControlModifier; } if (modifiers & mir_input_event_modifier_alt) { qtModifiers |= Qt::AltModifier; } if (modifiers & mir_input_event_modifier_meta) { qtModifiers |= Qt::MetaModifier; } if (modifiers & mir_input_event_modifier_alt_right) { qtModifiers |= Qt::GroupSwitchModifier; } return qtModifiers; } Qt::MouseButtons getQtMouseButtonsfromMirPointerEvent(MirPointerEvent const* pev) { Qt::MouseButtons buttons = Qt::NoButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_primary)) buttons |= Qt::LeftButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_secondary)) buttons |= Qt::RightButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_tertiary)) buttons |= Qt::MiddleButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_back)) buttons |= Qt::BackButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_forward)) buttons |= Qt::ForwardButton; return buttons; } } void QtEventFeeder::dispatchPointer(MirInputEvent const* ev) { auto timestamp = qtmir::compressTimestamp(std::chrono::nanoseconds(mir_input_event_get_event_time(ev))); auto pev = mir_input_event_get_pointer_event(ev); auto action = mir_pointer_event_action(pev); qCDebug(QTMIR_MIR_INPUT) << "Received" << qPrintable(mirPointerEventToString(pev)); auto modifiers = getQtModifiersFromMir(mir_pointer_event_modifiers(pev)); auto movement = QPointF(mir_pointer_event_axis_value(pev, mir_pointer_axis_relative_x), mir_pointer_event_axis_value(pev, mir_pointer_axis_relative_y)); switch (action) { case mir_pointer_action_button_up: case mir_pointer_action_button_down: case mir_pointer_action_motion: { const float hDelta = mir_pointer_event_axis_value(pev, mir_pointer_axis_hscroll); const float vDelta = mir_pointer_event_axis_value(pev, mir_pointer_axis_vscroll); if (hDelta != 0 || vDelta != 0) { const QPoint angleDelta = QPoint(hDelta * 15, vDelta * 15); mQtWindowSystem->handleWheelEvent(timestamp.count(), angleDelta, modifiers); } auto buttons = getQtMouseButtonsfromMirPointerEvent(pev); mQtWindowSystem->handleMouseEvent(timestamp.count(), movement, buttons, modifiers); break; } default: qCDebug(QTMIR_MIR_INPUT) << "Unrecognized pointer event"; } } void QtEventFeeder::dispatchKey(MirInputEvent const* event) { auto timestamp = qtmir::compressTimestamp(std::chrono::nanoseconds(mir_input_event_get_event_time(event))); auto kev = mir_input_event_get_keyboard_event(event); xkb_keysym_t xk_sym = mir_keyboard_event_key_code(kev); // Key modifier and unicode index mapping. auto modifiers = getQtModifiersFromMir(mir_keyboard_event_modifiers(kev)); // Key action QEvent::Type keyType = QEvent::KeyRelease; bool is_auto_rep = false; switch (mir_keyboard_event_action(kev)) { case mir_keyboard_action_repeat: is_auto_rep = true; // fall-through case mir_keyboard_action_down: keyType = QEvent::KeyPress; break; case mir_keyboard_action_up: keyType = QEvent::KeyRelease; break; default: break; } // Key event propagation. QString text; QVarLengthArray chars(32); { int result = xkb_keysym_to_utf8(xk_sym, chars.data(), chars.size()); if (result > 0) { text = QString::fromUtf8(chars.constData()); } } int keyCode = translateKeysym(xk_sym, text); QPlatformInputContext* context = QGuiApplicationPrivate::platformIntegration()->inputContext(); if (context) { // TODO: consider event.repeat_count QKeyEvent qKeyEvent(keyType, keyCode, modifiers, mir_keyboard_event_scan_code(kev), mir_keyboard_event_key_code(kev), mir_keyboard_event_modifiers(kev), text, is_auto_rep); qKeyEvent.setTimestamp(timestamp.count()); if (context->filterEvent(&qKeyEvent)) { qCDebug(QTMIR_MIR_INPUT) << "Received" << qPrintable(mirKeyboardEventToString(kev)) << "but not dispatching as it was filtered out by input context"; return; } } qCDebug(QTMIR_MIR_INPUT).nospace() << "Received" << qPrintable(mirKeyboardEventToString(kev)) << ". Dispatching to " << mQtWindowSystem->focusedWindow(); mQtWindowSystem->handleExtendedKeyEvent(mQtWindowSystem->focusedWindow(), timestamp.count(), keyType, keyCode, modifiers, mir_keyboard_event_scan_code(kev), xk_sym, mir_keyboard_event_modifiers(kev), text, is_auto_rep); } void QtEventFeeder::dispatchTouch(MirInputEvent const* event) { auto timestamp = qtmir::compressTimestamp(std::chrono::nanoseconds(mir_input_event_get_event_time(event))); tracepoint(qtmirserver, touchEventDispatch_start, std::chrono::nanoseconds(timestamp).count()); auto tev = mir_input_event_get_touch_event(event); qCDebug(QTMIR_MIR_INPUT) << "Received" << qPrintable(mirTouchEventToString(tev)); // FIXME(loicm) Max pressure is device specific. That one is for the Samsung Galaxy Nexus. That // needs to be fixed as soon as the compat input lib adds query support. const float kMaxPressure = 1.28; const int kPointerCount = mir_touch_event_point_count(tev); QList touchPoints; QWindow *window = nullptr; if (kPointerCount > 0) { window = mQtWindowSystem->getWindowForTouchPoint( QPoint(mir_touch_event_axis_value(tev, 0, mir_touch_axis_x), mir_touch_event_axis_value(tev, 0, mir_touch_axis_y))); if (!window) { qCDebug(QTMIR_MIR_INPUT) << "REJECTING INPUT EVENT, no matching window"; return; } const QRect kWindowGeometry = window->geometry(); // TODO: Is it worth setting the Qt::TouchPointStationary ones? Currently they are left // as Qt::TouchPointMoved for (int i = 0; i < kPointerCount; ++i) { QWindowSystemInterface::TouchPoint touchPoint; const float kX = mir_touch_event_axis_value(tev, i, mir_touch_axis_x); const float kY = mir_touch_event_axis_value(tev, i, mir_touch_axis_y); const float kW = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_major); const float kH = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_minor); const float kP = mir_touch_event_axis_value(tev, i, mir_touch_axis_pressure); touchPoint.id = mir_touch_event_id(tev, i); touchPoint.normalPosition = QPointF(kX / kWindowGeometry.width(), kY / kWindowGeometry.height()); touchPoint.area = QRectF(kX - (kW / 2.0), kY - (kH / 2.0), kW, kH); touchPoint.pressure = kP / kMaxPressure; switch (mir_touch_event_action(tev, i)) { case mir_touch_action_up: touchPoint.state = Qt::TouchPointReleased; break; case mir_touch_action_down: touchPoint.state = Qt::TouchPointPressed; break; case mir_touch_action_change: touchPoint.state = Qt::TouchPointMoved; break; default: break; } touchPoints.append(touchPoint); } } // Qt needs a happy, sane stream of touch events. So let's make sure we're not forwarding // any insanity. validateTouches(window, timestamp.count(), touchPoints); // Touch event propagation. qCDebug(QTMIR_MIR_INPUT) << "Sending to Qt" << qPrintable(touchesToString(touchPoints)); mQtWindowSystem->handleTouchEvent(window, //scales down the nsec_t (int64) to fit a ulong, precision lost but time difference suitable timestamp.count(), mTouchDevice, touchPoints); tracepoint(qtmirserver, touchEventDispatch_end, std::chrono::nanoseconds(timestamp).count()); } void QtEventFeeder::start() { // not used } void QtEventFeeder::stop() { // not used } void QtEventFeeder::validateTouches(QWindow *window, ulong timestamp, QList &touchPoints) { QSet updatedTouches; { int i = 0; while (i < touchPoints.count()) { bool mustDiscardTouch = !validateTouch(touchPoints[i]); if (mustDiscardTouch) { touchPoints.removeAt(i); } else { updatedTouches.insert(touchPoints.at(i).id); ++i; } } } // Release all unmentioned touches, one by one. QHash::iterator it = mActiveTouches.begin(); while (it != mActiveTouches.end()) { if (!updatedTouches.contains(it.key())) { qCWarning(QTMIR_MIR_INPUT) << "There's a touch (id =" << it.key() << ") missing. Releasing it."; sendActiveTouchRelease(window, timestamp, it.key()); it = mActiveTouches.erase(it); } else { ++it; } } // update mActiveTouches for (int i = 0; i < touchPoints.count(); ++i) { auto &touchPoint = touchPoints.at(i); if (touchPoint.state == Qt::TouchPointReleased) { mActiveTouches.remove(touchPoint.id); } else { mActiveTouches[touchPoint.id] = touchPoint; } } } void QtEventFeeder::sendActiveTouchRelease(QWindow *window, ulong timestamp, int id) { QList touchPoints = mActiveTouches.values(); for (int i = 0; i < touchPoints.count(); ++i) { QWindowSystemInterface::TouchPoint &touchPoint = touchPoints[i]; if (touchPoint.id == id) { touchPoint.state = Qt::TouchPointReleased; } else { touchPoint.state = Qt::TouchPointStationary; } } qCDebug(QTMIR_MIR_INPUT) << "Sending to Qt" << qPrintable(touchesToString(touchPoints)); mQtWindowSystem->handleTouchEvent(window, timestamp, mTouchDevice, touchPoints); } bool QtEventFeeder::validateTouch(QWindowSystemInterface::TouchPoint &touchPoint) { bool ok = true; switch (touchPoint.state) { case Qt::TouchPointPressed: if (mActiveTouches.contains(touchPoint.id)) { qCWarning(QTMIR_MIR_INPUT) << "Would press an already existing touch (id =" << touchPoint.id << "). Making it move instead."; touchPoint.state = Qt::TouchPointMoved; } break; case Qt::TouchPointMoved: if (!mActiveTouches.contains(touchPoint.id)) { qCWarning(QTMIR_MIR_INPUT) << "Would move a touch that wasn't pressed before (id =" << touchPoint.id << "). Making it press instead."; touchPoint.state = Qt::TouchPointPressed; } break; case Qt::TouchPointStationary: if (!mActiveTouches.contains(touchPoint.id)) { qCWarning(QTMIR_MIR_INPUT) << "There's an stationary touch that wasn't pressed before (id =" << touchPoint.id << "). Making it press instead."; touchPoint.state = Qt::TouchPointPressed; } break; case Qt::TouchPointReleased: if (!mActiveTouches.contains(touchPoint.id)) { qCWarning(QTMIR_MIR_INPUT) << "Would release a touch that wasn't pressed before (id =" << touchPoint.id << "). Ignoring it."; ok = false; } break; default: qFatal("QtEventFeeder: invalid touch state"); } return ok; } QString QtEventFeeder::touchesToString(const QList &points) { QString result; for (int i = 0; i < points.count(); ++i) { if (i > 0) { result.append(","); } const struct QWindowSystemInterface::TouchPoint &point = points.at(i); result.append(QString("(id=%1,state=%2,normalPosition=(%3,%4))") .arg(point.id) .arg(touchPointStateToString(point.state)) .arg(point.normalPosition.x()) .arg(point.normalPosition.y()) ); } return result; } ./src/platforms/mirserver/tracepoints.tp0000644000015600001650000000157212677054330020625 0ustar jenkinsjenkins#include TRACEPOINT_EVENT(qtmirserver, starting, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmirserver, stopping, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmirserver, surfaceCreated, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmirserver, surfaceDestroyed, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmirserver, sessionAuthorizeStart, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmirserver, sessionAuthorizeEnd, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmirserver, surfacePlacementStart, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmirserver, surfacePlacementEnd, TP_ARGS(0), TP_FIELDS()) TRACEPOINT_EVENT(qtmirserver, touchEventDispatch_start, TP_ARGS(int64_t, event_time), TP_FIELDS(ctf_integer(int64_t, event_time, event_time))) TRACEPOINT_EVENT(qtmirserver, touchEventDispatch_end, TP_ARGS(int64_t, event_time), TP_FIELDS(ctf_integer(int64_t, event_time, event_time))) ./src/platforms/mirserver/mircursorimages.h0000644000015600001650000000203212677054330021301 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QTMIR_MIRCURSORIMAGES_H_ #define QTMIR_MIRCURSORIMAGES_H_ #include namespace qtmir { class MirCursorImages : public mir::input::CursorImages { public: std::shared_ptr image(const std::string &cursor_name, const mir::geometry::Size &size) override; }; } #endif // QTMIR_MIRCURSORIMAGES_H_ ./src/platforms/mirserver/clipboard.cpp0000644000015600001650000002042112677054330020362 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "clipboard.h" #include "logging.h" // C++ std lib #include #include #include #include Q_LOGGING_CATEGORY(QTMIR_CLIPBOARD, "qtmir.clipboard") // FIXME(loicm) The clipboard data format is not defined by Ubuntu Platform API // which makes it impossible to have non-Qt applications communicate with Qt // applications through the clipboard API. The solution would be to have // Ubuntu Platform define the data format or propose an API that supports // embedding different mime types in the clipboard. // Data format: // number of mime types (sizeof(int)) // data layout ((4 * sizeof(int)) * number of mime types) // mime type string offset (sizeof(int)) // mime type string size (sizeof(int)) // data offset (sizeof(int)) // data size (sizeof(int)) // data (n bytes) namespace { const int maxFormatsCount = 16; } namespace qtmir { QByteArray serializeMimeData(QMimeData *mimeData) { const QStringList formats = mimeData->formats(); const int formatCount = qMin(formats.size(), maxFormatsCount); const int headerSize = sizeof(int) + (formatCount * 4 * sizeof(int)); int bufferSize = headerSize; for (int i = 0; i < formatCount; i++) bufferSize += formats[i].size() + mimeData->data(formats[i]).size(); // Serialize data. QByteArray serializedMimeData(bufferSize, 0 /* char to fill with */); { char *buffer = serializedMimeData.data(); int* header = reinterpret_cast(serializedMimeData.data()); int offset = headerSize; header[0] = formatCount; for (int i = 0; i < formatCount; i++) { const int formatOffset = offset; const int formatSize = formats[i].size(); const int dataOffset = offset + formatSize; const int dataSize = mimeData->data(formats[i]).size(); memcpy(&buffer[formatOffset], formats[i].toLatin1().data(), formatSize); memcpy(&buffer[dataOffset], mimeData->data(formats[i]).data(), dataSize); header[i*4+1] = formatOffset; header[i*4+2] = formatSize; header[i*4+3] = dataOffset; header[i*4+4] = dataSize; offset += formatSize + dataSize; } } return serializedMimeData; } QMimeData *deserializeMimeData(const QByteArray &serializedMimeData) { if (static_cast(serializedMimeData.size()) < sizeof(int)) { // Data is invalid return nullptr; } QMimeData *mimeData = new QMimeData; const char* const buffer = serializedMimeData.constData(); const int* const header = reinterpret_cast(serializedMimeData.constData()); const int count = qMin(header[0], maxFormatsCount); for (int i = 0; i < count; i++) { const int formatOffset = header[i*4+1]; const int formatSize = header[i*4+2]; const int dataOffset = header[i*4+3]; const int dataSize = header[i*4+4]; if (formatOffset + formatSize <= serializedMimeData.size() && dataOffset + dataSize <= serializedMimeData.size()) { QString mimeType = QString::fromLatin1(&buffer[formatOffset], formatSize); QByteArray mimeDataBytes(&buffer[dataOffset], dataSize); mimeData->setData(mimeType, mimeDataBytes); } } return mimeData; } /************************************ DBusClipboard *****************************************/ bool DBusClipboard::skipDBusRegistration = false; DBusClipboard::DBusClipboard(QObject *parent) : QObject(parent) { if (!skipDBusRegistration) { performDBusRegistration(); } } void DBusClipboard::setContents(QByteArray newContents) { setContentsHelper(std::move(newContents)); } void DBusClipboard::SetContents(QByteArray newContents) { qCDebug(QTMIR_CLIPBOARD, "D-Bus SetContents - %d bytes", newContents.size()); if (setContentsHelper(std::move(newContents))) { Q_EMIT contentsChangedRemotely(); } } bool DBusClipboard::setContentsHelper(QByteArray newContents) { if (newContents.size() > maxContentsSize) { qCWarning(QTMIR_CLIPBOARD, "D-Bus clipboard refused the new contents (%d bytes) as they're" " bigger than the maximum allowed size of %d bytes.", newContents.size(), maxContentsSize); return false; } if (newContents != m_contents) { m_contents = std::move(newContents); Q_EMIT ContentsChanged(m_contents); return true; } else { return false; } } QByteArray DBusClipboard::GetContents() const { qCDebug(QTMIR_CLIPBOARD, "D-Bus GetContents - returning %d bytes", m_contents.size()); return m_contents; } void DBusClipboard::performDBusRegistration() { QDBusConnection connection = QDBusConnection::sessionBus(); const char *serviceName = "com.canonical.QtMir"; const char *objectName = "/com/canonical/QtMir/Clipboard"; bool serviceOk = connection.registerService(serviceName); if (!serviceOk) { QDBusError error = connection.lastError(); QString errorMessage; if (error.isValid()) { errorMessage = error.message(); } qCCritical(QTMIR_CLIPBOARD, "Failed to register service %s. %s", serviceName, qPrintable(errorMessage)); } bool objectOk = connection.registerObject(objectName, this, QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportScriptableSlots); if (!objectOk) { QDBusError error = connection.lastError(); QString errorMessage; if (error.isValid()) { errorMessage = error.message(); } qCCritical(QTMIR_CLIPBOARD, "Failed to register object %s. %s", objectName, qPrintable(errorMessage)); } if (serviceOk && objectOk) { qCDebug(QTMIR_CLIPBOARD, "D-Bus registration successful."); } } /************************************ Clipboard *****************************************/ Clipboard::Clipboard(QObject *parent) : QObject(parent) , m_dbusClipboard(nullptr) { } QMimeData *Clipboard::mimeData(QClipboard::Mode mode) { if (mode == QClipboard::Clipboard) { return QPlatformClipboard::mimeData(mode); } else { return nullptr; } } void Clipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) { if (mode != QClipboard::Clipboard) return; if (m_dbusClipboard) { QByteArray serializedMimeData = serializeMimeData(data); m_dbusClipboard->setContents(std::move(serializedMimeData)); } QPlatformClipboard::setMimeData(data, mode); } void Clipboard::setupDBusService() { Q_ASSERT(!m_dbusClipboard); m_dbusClipboard = new DBusClipboard(this); connect(m_dbusClipboard, &DBusClipboard::contentsChangedRemotely, this, &Clipboard::setMimeDataWithDBusClibpboardContents); } void Clipboard::setMimeDataWithDBusClibpboardContents() { Q_ASSERT(m_dbusClipboard); QMimeData *newMimeData = deserializeMimeData(m_dbusClipboard->contents()); if (newMimeData) { // Don't call Clipboard::setMimeData as it will also propagate the change // to the D-Bus clipboard, which doesn't make sense here as we're doing // the other way round (propagating the D-Bus clipboard change to the local // clipboard). QPlatformClipboard::setMimeData(newMimeData, QClipboard::Clipboard); } else { qCWarning(QTMIR_CLIPBOARD, "Failed to deserialize D-Bus clipboard contents (%d bytes)", m_dbusClipboard->contents().size()); } } } // namespace qtmir ./src/platforms/mirserver/mirserver.h0000644000015600001650000000471212677054330020113 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIRSERVER_H #define MIRSERVER_H #include #include #include class QtEventFeeder; class SessionListener; class SessionAuthorizer; using MirShell = mir::shell::Shell; class PromptSessionListener; class ScreenController; class MirWindowManager; // We use virtual inheritance of mir::Server to facilitate derived classes (e.g. testing) // calling initialization functions before MirServer is constructed. class MirServer : public QObject, private virtual mir::Server { Q_OBJECT Q_PROPERTY(SessionAuthorizer* sessionAuthorizer READ sessionAuthorizer CONSTANT) Q_PROPERTY(SessionListener* sessionListener READ sessionListener CONSTANT) Q_PROPERTY(MirShell* shell READ shell CONSTANT) Q_PROPERTY(PromptSessionListener* promptSessionListener READ promptSessionListener CONSTANT) public: MirServer(int &argc, char **argv, const QSharedPointer &, QObject* parent = 0); ~MirServer() = default; /* mir specific */ using mir::Server::run; using mir::Server::the_compositor; using mir::Server::the_display; using mir::Server::the_gl_config; using mir::Server::the_main_loop; using mir::Server::the_prompt_session_listener; using mir::Server::the_prompt_session_manager; using mir::Server::the_session_authorizer; using mir::Server::the_session_listener; void stop(); /* qt specific */ // getters SessionAuthorizer *sessionAuthorizer(); SessionListener *sessionListener(); PromptSessionListener *promptSessionListener(); MirWindowManager *windowManager(); MirShell *shell(); private: std::weak_ptr m_shell; std::weak_ptr m_windowManager; const QSharedPointer m_screenController; }; #endif // MIRSERVER_H ./src/platforms/mirserver/offscreensurface.h0000644000015600001650000000242212677054330021414 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef OFFSCREENSURFACE_H #define OFFSCREENSURFACE_H #include #include #include class MirServer; class QOpenGLFramebufferObject; class OffscreenSurface : public QPlatformOffscreenSurface { public: OffscreenSurface(QOffscreenSurface *offscreenSurface); QSurfaceFormat format() const override; bool isValid() const override; QOpenGLFramebufferObject* buffer() const; void setBuffer(QOpenGLFramebufferObject *buffer); private: QOpenGLFramebufferObject *m_buffer; QSurfaceFormat m_format; }; #endif // OFFSCREENSURFACE_H ./src/platforms/mirserver/mirserver.json0000644000015600001650000000004012677054330020623 0ustar jenkinsjenkins{ "Keys": [ "mirserver" ] } ./src/platforms/mirserver/mirwindowmanager.cpp0000644000015600001650000001647312677054330022011 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "mirwindowmanager.h" #include "logging.h" #include "surfaceobserver.h" #include "tracepoints.h" // generated from tracepoints.tp #include #include #include #include #include #include namespace ms = mir::scene; namespace { class MirWindowManagerImpl : public MirWindowManager { public: MirWindowManagerImpl(const std::shared_ptr &displayLayout, std::shared_ptr<::SessionListener> sessionListener); void add_session(std::shared_ptr const& session) override; void remove_session(std::shared_ptr const& session) override; mir::frontend::SurfaceId add_surface( std::shared_ptr const& session, mir::scene::SurfaceCreationParameters const& params, std::function const& session, mir::scene::SurfaceCreationParameters const& params)> const& build) override; void remove_surface( std::shared_ptr const& session, std::weak_ptr const& surface) override; void add_display(mir::geometry::Rectangle const& area) override; void remove_display(mir::geometry::Rectangle const& area) override; bool handle_keyboard_event(MirKeyboardEvent const* event) override; bool handle_touch_event(MirTouchEvent const* event) override; bool handle_pointer_event(MirPointerEvent const* event) override; int set_surface_attribute( std::shared_ptr const& session, std::shared_ptr const& surface, MirSurfaceAttrib attrib, int value) override; void handle_raise_surface( std::shared_ptr const& session, std::shared_ptr const& surface, uint64_t timestamp) override; void modify_surface( const std::shared_ptr&, const std::shared_ptr& surface, const mir::shell::SurfaceSpecification& modifications) override; private: std::shared_ptr const m_displayLayout; std::shared_ptr<::SessionListener> m_sessionListener; }; } MirWindowManagerImpl::MirWindowManagerImpl(const std::shared_ptr &displayLayout, std::shared_ptr<::SessionListener> sessionListener) : m_displayLayout{displayLayout}, m_sessionListener(sessionListener) { qCDebug(QTMIR_MIR_MESSAGES) << "MirWindowManagerImpl::MirWindowManagerImpl"; } void MirWindowManagerImpl::add_session(std::shared_ptr const& /*session*/) { } void MirWindowManagerImpl::remove_session(std::shared_ptr const& /*session*/) { } mir::frontend::SurfaceId MirWindowManagerImpl::add_surface( std::shared_ptr const& session, ms::SurfaceCreationParameters const& requestParameters, std::function const& session, ms::SurfaceCreationParameters const& params)> const& build) { tracepoint(qtmirserver, surfacePlacementStart); m_sessionListener->surfaceAboutToBeCreated(*session.get(), qtmir::CreationHints(requestParameters)); QSize initialSize; // can be connected to via Qt::BlockingQueuedConnection to alter surface initial size { int surfaceType = requestParameters.type.is_set() ? requestParameters.type.value() : -1; Q_EMIT sessionAboutToCreateSurface(session, surfaceType, initialSize); } ms::SurfaceCreationParameters placedParameters = requestParameters; if (initialSize.isValid()) { placedParameters.size.width = mir::geometry::Width(initialSize.width()); placedParameters.size.height = mir::geometry::Height(initialSize.height()); } else { qCWarning(QTMIR_MIR_MESSAGES) << "MirWindowManagerImpl::add_surface(): didn't get a initial surface" " size from shell. Falling back to fullscreen placement"; // This is bad. Fallback to fullscreen mir::geometry::Rectangle rect{requestParameters.top_left, requestParameters.size}; m_displayLayout->size_to_output(rect); placedParameters.size = rect.size; } qCDebug(QTMIR_MIR_MESSAGES) << "MirWindowManagerImpl::add_surface(): size requested (" << requestParameters.size.width.as_int() << "," << requestParameters.size.height.as_int() << ") and placed (" << placedParameters.size.width.as_int() << "," << placedParameters.size.height.as_int() << ")"; tracepoint(qtmirserver, surfacePlacementEnd); auto const result = build(session, placedParameters); auto const surface = session->surface(result); return result; } void MirWindowManagerImpl::remove_surface( std::shared_ptr const& /*session*/, std::weak_ptr const& /*surface*/) { } void MirWindowManagerImpl::add_display(mir::geometry::Rectangle const& /*area*/) { } void MirWindowManagerImpl::remove_display(mir::geometry::Rectangle const& /*area*/) { } bool MirWindowManagerImpl::handle_keyboard_event(MirKeyboardEvent const* /*event*/) { return false; } bool MirWindowManagerImpl::handle_touch_event(MirTouchEvent const* /*event*/) { return false; } bool MirWindowManagerImpl::handle_pointer_event(MirPointerEvent const* /*event*/) { return false; } void MirWindowManagerImpl::handle_raise_surface( std::shared_ptr const& /*session*/, std::shared_ptr const& /*surface*/, uint64_t /*timestamp*/) { } int MirWindowManagerImpl::set_surface_attribute( std::shared_ptr const& /*session*/, std::shared_ptr const& surface, MirSurfaceAttrib attrib, int value) { return surface->configure(attrib, value); } void MirWindowManagerImpl::modify_surface(const std::shared_ptr&, const std::shared_ptr& surface, const mir::shell::SurfaceSpecification& modifications) { QMutexLocker(&SurfaceObserver::mutex); SurfaceObserver *observer = SurfaceObserver::observerForSurface(surface.get()); if (observer) { observer->notifySurfaceModifications(modifications); } } std::shared_ptr MirWindowManager::create( const std::shared_ptr &displayLayout, std::shared_ptr<::SessionListener> sessionListener) { return std::make_shared(displayLayout, sessionListener); } ./src/platforms/mirserver/qmirserver_p.h0000644000015600001650000000302412677054330020606 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QMIRSERVER_P_H #define QMIRSERVER_P_H // Qt #include #include // std #include #include // local #include "mirserver.h" #include "screencontroller.h" class QMirServer; class MirServerThread; class QMirServerPrivate { public: QSharedPointer server; QSharedPointer screenController; MirServerThread *serverThread; }; class MirServerThread : public QThread { Q_OBJECT public: MirServerThread(const QSharedPointer &server) : server(server) {} bool waitForMirStartup(); Q_SIGNALS: void stopped(); public Q_SLOTS: void run() override; void stop(); private: std::mutex mutex; std::condition_variable started_cv; bool mir_running{false}; const QSharedPointer server; }; #endif // QMIRSERVER_P_H ./src/platforms/mirserver/qmirserver_p.cpp0000644000015600001650000000277712677054330021157 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // Mir #include // local #include "qmirserver_p.h" void MirServerThread::run() { auto const main_loop = server->the_main_loop(); // By enqueuing the notification code in the main loop, we are // ensuring that the server has really and fully started before // leaving wait_for_startup(). main_loop->enqueue( this, [&] { std::lock_guard lock(mutex); mir_running = true; started_cv.notify_one(); }); server->run(); // blocks until Mir server stopped Q_EMIT stopped(); } void MirServerThread::stop() { server->stop(); } bool MirServerThread::waitForMirStartup() { std::unique_lock lock(mutex); started_cv.wait_for(lock, std::chrono::seconds{10}, [&]{ return mir_running; }); return mir_running; } ./src/platforms/mirserver/screenwindow.cpp0000644000015600001650000000613712677054330021142 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "screenwindow.h" #include "screen.h" // Mir #include #include // Qt #include #include #include #include #include #include "logging.h" static WId newWId() { static WId id = 0; if (id == std::numeric_limits::max()) qWarning("MirServer QPA: Out of window IDs"); return ++id; } ScreenWindow::ScreenWindow(QWindow *window) : QPlatformWindow(window) , m_exposed(false) , m_winId(newWId()) { // Register with the Screen it is associated with auto myScreen = static_cast(screen()); Q_ASSERT(myScreen); myScreen->setWindow(this); qCDebug(QTMIR_SCREENS) << "ScreenWindow" << this << "with window ID" << uint(m_winId) << "backed by" << myScreen; QRect screenGeometry(screen()->availableGeometry()); if (window->geometry() != screenGeometry) { setGeometry(screenGeometry); window->setGeometry(screenGeometry); } window->setSurfaceType(QSurface::OpenGLSurface); } ScreenWindow::~ScreenWindow() { qCDebug(QTMIR_SCREENS) << "Destroying ScreenWindow" << this; static_cast(screen())->setWindow(nullptr); } bool ScreenWindow::isExposed() const { return m_exposed; } void ScreenWindow::setExposed(const bool exposed) { qCDebug(QTMIR_SCREENS) << "ScreenWindow::setExposed" << this << exposed; if (m_exposed == exposed) return; m_exposed = exposed; if (!window()) return; // If backing a QQuickWindow, need to stop/start its renderer immediately auto quickWindow = static_cast(window()); if (!quickWindow) return; auto renderer = QSGRenderLoop::instance(); if (exposed) { renderer->show(quickWindow); QWindowSystemInterface::handleExposeEvent(window(), geometry()); // else it won't redraw } else { quickWindow->setPersistentOpenGLContext(false); quickWindow->setPersistentSceneGraph(false); renderer->hide(quickWindow); // ExposeEvent will arrive too late, need to stop compositor immediately } } void ScreenWindow::swapBuffers() { static_cast(screen())->swapBuffers(); } void ScreenWindow::makeCurrent() { static_cast(screen())->makeCurrent(); } void ScreenWindow::doneCurrent() { static_cast(screen())->doneCurrent(); } ./src/platforms/mirserver/screencontroller.cpp0000644000015600001650000001763612677054330022024 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "screencontroller.h" #include "screenwindow.h" #include "qtcompositor.h" #include "logging.h" #include "mirserverintegration.h" #include "screen.h" // Mir #include #include // Qt #include #include #include #include // for qApp // std #include Q_LOGGING_CATEGORY(QTMIR_SCREENS, "qtmir.screens") namespace mg = mir::graphics; ScreenController::ScreenController(QObject *parent) : QObject(parent) , m_compositing(false) { qCDebug(QTMIR_SCREENS) << "ScreenController::ScreenController"; } // init only after MirServer has initialized - runs on MirServerThread!!! void ScreenController::init(const std::shared_ptr &display, const std::shared_ptr &compositor) { m_display = display; m_compositor = compositor; // Use a Blocking Queued Connection to enforce synchronization of Qt GUI thread with Mir thread(s) // on compositor shutdown. Compositor startup can be lazy. // Queued connections work because the thread affinity of this class is with the Qt GUI thread. auto qtCompositor = static_cast(compositor.get()); connect(qtCompositor, &QtCompositor::starting, this, &ScreenController::onCompositorStarting); connect(qtCompositor, &QtCompositor::stopping, this, &ScreenController::onCompositorStopping, Qt::BlockingQueuedConnection); } // terminate before shutting down the Mir server, or else liable to deadlock with the blocking connection above // Runs on MirServerThread!!! void ScreenController::terminate() { auto qtCompositor = static_cast(m_compositor.get()); qtCompositor->disconnect(); } void ScreenController::onCompositorStarting() { qCDebug(QTMIR_SCREENS) << "ScreenController::onCompositorStarting"; m_compositing = true; update(); // (Re)Start Qt's render thread by setting all windows with a corresponding screen to exposed. for (auto screen : m_screenList) { auto window = static_cast(screen->window()); if (window && window->window()) { window->setExposed(true); } } } void ScreenController::onCompositorStopping() { qCDebug(QTMIR_SCREENS) << "ScreenController::onCompositorStopping"; m_compositing = false; // Stop Qt's render threads by setting all its windows it obscured. Must // block until all windows have their GL contexts released. for (auto screen : m_screenList) { auto window = static_cast(screen->window()); if (window && window->window()) { window->setExposed(false); } } update(); } void ScreenController::update() { qCDebug(QTMIR_SCREENS) << "ScreenController::update"; auto display = m_display.lock(); if (!display) return; auto displayConfig = display->configuration(); // Mir only tells us something changed, it is up to us to figure out what. QList newScreenList; QList oldScreenList = m_screenList; m_screenList.clear(); displayConfig->for_each_output( [this, &oldScreenList, &newScreenList](const mg::DisplayConfigurationOutput &output) { if (output.used && output.connected) { Screen *screen = findScreenWithId(oldScreenList, output.id); if (screen) { // we've already set up this display before, refresh its internals screen->setMirDisplayConfiguration(output); oldScreenList.removeAll(screen); } else { // new display, so create Screen for it screen = this->createScreen(output); newScreenList.append(screen); qCDebug(QTMIR_SCREENS) << "Added Screen with id" << output.id.as_value() << "and geometry" << screen->geometry(); } m_screenList.append(screen); } } ); // Delete any old & unused Screens for (auto screen: oldScreenList) { qCDebug(QTMIR_SCREENS) << "Removed Screen with id" << screen->m_outputId.as_value() << "and geometry" << screen->geometry(); // The screen is automatically removed from Qt's internal list by the QPlatformScreen destructor. auto window = static_cast(screen->window()); if (window && window->window() && window->isExposed()) { window->window()->hide(); } bool ok = QMetaObject::invokeMethod(qApp, "onScreenAboutToBeRemoved", Qt::DirectConnection, Q_ARG(QScreen*, screen->screen())); if (!ok) { qCWarning(QTMIR_SCREENS) << "Failed to invoke QGuiApplication::onScreenAboutToBeRemoved(QScreen*) slot."; } delete screen; } // Match up the new Mir DisplayBuffers with each Screen display->for_each_display_sync_group([&](mg::DisplaySyncGroup &group) { group.for_each_display_buffer([&](mg::DisplayBuffer &buffer) { // only way to match Screen to a DisplayBuffer is by matching the geometry QRect dbGeom(buffer.view_area().top_left.x.as_int(), buffer.view_area().top_left.y.as_int(), buffer.view_area().size.width.as_int(), buffer.view_area().size.height.as_int()); for (auto screen : m_screenList) { if (dbGeom == screen->geometry()) { screen->setMirDisplayBuffer(&buffer, &group); break; } } }); }); qCDebug(QTMIR_SCREENS) << "======================================="; for (auto screen: m_screenList) { qCDebug(QTMIR_SCREENS) << screen << "- id:" << screen->m_outputId.as_value() << "geometry:" << screen->geometry() << "window:" << screen->window() << "type" << static_cast(screen->outputType()); } qCDebug(QTMIR_SCREENS) << "======================================="; for (auto screen : newScreenList) { Q_EMIT screenAdded(screen); } } Screen* ScreenController::createScreen(const mir::graphics::DisplayConfigurationOutput &output) const { return new Screen(output); } Screen* ScreenController::findScreenWithId(const QList &list, const mg::DisplayConfigurationOutputId id) { for (Screen *screen : list) { if (screen->m_outputId == id) { return screen; } } return nullptr; } QWindow* ScreenController::getWindowForPoint(const QPoint &point) //FIXME - not thread safe & not efficient { // This is a part optimization, and a part work-around for AP generated input events occasionally // appearing outside the screen borders: https://bugs.launchpad.net/qtmir/+bug/1508415 if (m_screenList.length() == 1 && m_screenList.first()->window()) { return m_screenList.first()->window()->window(); } for (Screen *screen : m_screenList) { if (screen->window() && screen->geometry().contains(point)) { return screen->window()->window(); } } return nullptr; } ./src/platforms/mirserver/creationhints.h0000644000015600001650000000255412677054330020751 0ustar jenkinsjenkins/* * Copyright (C) 2016 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QTMIR_CREATIONHINTS_H #define QTMIR_CREATIONHINTS_H #include #include #include namespace mir { namespace scene { class SurfaceCreationParameters; } } namespace qtmir { class CreationHints { public: CreationHints() {} CreationHints(const mir::scene::SurfaceCreationParameters&); QString toString() const; int minWidth{0}; int maxWidth{0}; int minHeight{0}; int maxHeight{0}; int widthIncrement{0}; int heightIncrement{0}; Mir::ShellChrome shellChrome{Mir::ShellChrome::NormalChrome}; }; } // namespace qtmir Q_DECLARE_METATYPE(qtmir::CreationHints) #endif // QTMIR_CREATIONHINTS_H ./src/platforms/mirserver/ubuntutheme.h0000644000015600001650000000201112677054330020430 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef UBUNTU_THEME_H #define UBUNTU_THEME_H #include class UbuntuTheme : public QGenericUnixTheme { public: static const char* name; UbuntuTheme(); virtual ~UbuntuTheme(); // From QPlatformTheme QVariant themeHint(ThemeHint hint) const override; }; #endif // UBUNTU_THEME_H ./src/platforms/mirserver/screenwindow.h0000644000015600001650000000253512677054330020605 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SCREENWINDOW_H #define SCREENWINDOW_H #include // ScreenWindow implements the basics of a QPlatformWindow. // QtMir enforces one Window per Screen, so Window and Screen are tightly coupled. // All Mir specifics live in the associated Screen object. class ScreenWindow : public QPlatformWindow { public: explicit ScreenWindow(QWindow *window); virtual ~ScreenWindow(); bool isExposed() const override; void setExposed(const bool exposed); WId winId() const override { return m_winId; } void swapBuffers(); void makeCurrent(); void doneCurrent(); private: bool m_exposed; WId m_winId; }; #endif // SCREENWINDOW_H ./src/platforms/mirserver/mirserver.cpp0000644000015600001650000001460212677054330020445 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include #include "mirserver.h" // local #include "argvHelper.h" #include "mircursorimages.h" #include "mirwindowmanager.h" #include "mirglconfig.h" #include "mirserverstatuslistener.h" #include "promptsessionlistener.h" #include "screencontroller.h" #include "sessionlistener.h" #include "sessionauthorizer.h" #include "qtcompositor.h" #include "qteventfeeder.h" #include "tileddisplayconfigurationpolicy.h" #include "logging.h" // std #include // egl #define MESA_EGL_NO_X11_HEADERS #include // mir #include namespace mg = mir::graphics; namespace mo = mir::options; namespace msh = mir::shell; namespace ms = mir::scene; Q_LOGGING_CATEGORY(QTMIR_MIR_MESSAGES, "qtmir.mir") MirServer::MirServer(int &argc, char **argv, const QSharedPointer &screenController, QObject* parent) : QObject(parent) , m_screenController(screenController) { bool unknownArgsFound = false; set_command_line_handler([&argc, &argv, &unknownArgsFound](int filteredCount, const char* const filteredArgv[]) { unknownArgsFound = true; // Want to edit argv to match that which Mir returns, as those are for to Qt alone to process. Edit existing // argc as filteredArgv only defined in this scope. qtmir::editArgvToMatch(argc, argv, filteredCount, filteredArgv); }); // Casting char** to be a const char** safe as Mir won't change it, nor will we set_command_line(argc, const_cast(argv)); override_the_session_listener([] { return std::make_shared(); }); override_the_prompt_session_listener([] { return std::make_shared(); }); override_the_session_authorizer([] { return std::make_shared(); }); override_the_compositor([] { return std::make_shared(); }); override_the_cursor_images([] { return std::make_shared(); }); override_the_input_dispatcher([&screenController] { return std::make_shared(screenController); }); override_the_gl_config([] { return std::make_shared(); }); override_the_server_status_listener([] { return std::make_shared(); }); override_the_window_manager_builder([this](mir::shell::FocusController*) -> std::shared_ptr { auto windowManager = MirWindowManager::create(the_shell_display_layout(), std::static_pointer_cast<::SessionListener>(the_session_listener())); m_windowManager = windowManager; return windowManager; }); wrap_display_configuration_policy( [](const std::shared_ptr &wrapped) -> std::shared_ptr { return std::make_shared(wrapped); }); set_terminator([](int) { qDebug() << "Signal caught by Mir, stopping Mir server.."; QCoreApplication::quit(); }); add_init_callback([this, &screenController] { screenController->init(the_display(), the_compositor()); }); try { apply_settings(); } catch (const std::exception &ex) { qCritical() << ex.what(); exit(1); } if (!unknownArgsFound) { // mir parsed all the arguments, so edit argv to pretend to have just argv[0] argc = 1; } // We will draw our own cursor. // FIXME: Call override_the_cusor() instead once this method becomes available in a // future version of Mir. add_init_callback([this]() { the_cursor()->hide(); // Hack to work around https://bugs.launchpad.net/mir/+bug/1502200 static_cast(the_compositor().get())->setCursor(the_cursor()); }); qCDebug(QTMIR_MIR_MESSAGES) << "MirServer created"; qCDebug(QTMIR_MIR_MESSAGES) << "Command line arguments passed to Qt:" << QCoreApplication::arguments(); } // Override default implementation to ensure we terminate the ScreenController first. // Code path followed when Qt tries to shutdown the server. void MirServer::stop() { m_screenController->terminate(); mir::Server::stop(); } /************************************ Shell side ************************************/ // // Note about the // if (sharedPtr.unique()) return 0; // constructs used in the functions below. // The rationale is that if when you do // the_session_authorizer() // get a pointer that is unique means that Mir is not // holding the pointer and thus when we return from the // sessionAuthorizer() // scope the unique pointer will be destroyed so we return 0 // SessionAuthorizer *MirServer::sessionAuthorizer() { auto sharedPtr = the_session_authorizer(); if (sharedPtr.unique()) return 0; return static_cast(sharedPtr.get()); } SessionListener *MirServer::sessionListener() { auto sharedPtr = the_session_listener(); if (sharedPtr.unique()) return 0; return static_cast(sharedPtr.get()); } PromptSessionListener *MirServer::promptSessionListener() { auto sharedPtr = the_prompt_session_listener(); if (sharedPtr.unique()) return 0; return static_cast(sharedPtr.get()); } MirShell *MirServer::shell() { std::weak_ptr m_shell = the_shell(); return m_shell.lock().get(); } MirWindowManager *MirServer::windowManager() { return m_windowManager.lock().get(); } ./src/platforms/mirserver/wm-wip/0000755000015600001650000000000012677054330017140 5ustar jenkinsjenkins./src/platforms/mirserver/wm-wip/server_example_basic_window_manager.h0000644000015600001650000002246212677054330026562 0ustar jenkinsjenkins/* * 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_BASIC_WINDOW_MANAGER_H_ #define MIR_EXAMPLE_BASIC_WINDOW_MANAGER_H_ #include "server_example_window_management_info.h" #include "mir/geometry/rectangles.h" #include "mir/shell/abstract_shell.h" #include "mir/shell/window_manager.h" #include #include ///\example server_example_basic_window_manager.h /// A generic policy-based window manager implementation namespace mir { namespace examples { using shell::SurfaceSet; /// The interface through which the policy instructs the controller. /// These functions assume that the BasicWindowManager data structures can be accessed freely. /// I.e. should only be invoked by the policy handle_... methods (where any necessary locks are held). class WindowManagerTools { public: using SurfaceInfoMap = std::map, SurfaceInfo, std::owner_less>>; using SessionInfoMap = std::map, SessionInfo, std::owner_less>>; virtual auto find_session(std::function const& predicate) -> std::shared_ptr = 0; virtual auto info_for(std::weak_ptr const& session) const -> SessionInfo& = 0; virtual auto info_for(std::weak_ptr const& surface) const -> SurfaceInfo& = 0; virtual std::shared_ptr focused_session() const = 0; virtual std::shared_ptr focused_surface() const = 0; virtual void focus_next_session() = 0; virtual void set_focus_to( std::shared_ptr const& focus, std::shared_ptr const& surface) = 0; virtual auto surface_at(geometry::Point cursor) const -> std::shared_ptr = 0; virtual auto active_display() -> geometry::Rectangle const = 0; virtual void forget(std::weak_ptr const& surface) = 0; virtual void raise_tree(std::shared_ptr const& root) = 0; virtual ~WindowManagerTools() = default; WindowManagerTools() = default; WindowManagerTools(WindowManagerTools const&) = delete; WindowManagerTools& operator=(WindowManagerTools const&) = delete; }; class WindowManagementPolicy { public: using SessionInfoMap = typename WindowManagerTools::SessionInfoMap; using SurfaceInfoMap = typename WindowManagerTools::SurfaceInfoMap; virtual void handle_session_info_updated(SessionInfoMap& session_info, geometry::Rectangles const& displays) = 0; virtual void handle_displays_updated(SessionInfoMap& session_info, geometry::Rectangles const& displays) = 0; virtual auto handle_place_new_surface( std::shared_ptr const& session, scene::SurfaceCreationParameters const& request_parameters) -> scene::SurfaceCreationParameters = 0; virtual void handle_new_surface(std::shared_ptr const& session, std::shared_ptr const& surface) = 0; virtual void handle_modify_surface( std::shared_ptr const& session, std::shared_ptr const& surface, shell::SurfaceSpecification const& modifications) = 0; virtual void handle_delete_surface(std::shared_ptr const& session, std::weak_ptr const& surface) = 0; virtual int handle_set_state(std::shared_ptr const& surface, MirSurfaceState value) = 0; virtual void generate_decorations_for( std::shared_ptr const& session, std::shared_ptr const& surface, SurfaceInfoMap& surface_info, std::function const&, scene::SurfaceCreationParameters const&)> const& build) = 0; virtual bool handle_keyboard_event(MirKeyboardEvent const* event) = 0; virtual bool handle_touch_event(MirTouchEvent const* event) = 0; virtual bool handle_pointer_event(MirPointerEvent const* event) = 0; virtual void handle_raise_surface( std::shared_ptr const& session, std::shared_ptr const& surface) = 0; virtual ~WindowManagementPolicy() = default; WindowManagementPolicy() = default; WindowManagementPolicy(WindowManagementPolicy const&) = delete; WindowManagementPolicy& operator=(WindowManagementPolicy const&) = delete; }; /// A policy based window manager. /// This takes care of the management of any meta implementation held for the sessions and surfaces. class BasicWindowManager : public virtual shell::WindowManager, protected WindowManagerTools { protected: BasicWindowManager( shell::FocusController* focus_controller, std::unique_ptr policy); public: using typename WindowManagerTools::SurfaceInfoMap; using typename WindowManagerTools::SessionInfoMap; void add_session(std::shared_ptr const& session) override; void remove_session(std::shared_ptr const& session) override; auto add_surface( std::shared_ptr const& session, scene::SurfaceCreationParameters const& params, std::function const& session, scene::SurfaceCreationParameters const& params)> const& build) -> frontend::SurfaceId override; void modify_surface( std::shared_ptr const& session, std::shared_ptr const& surface, shell::SurfaceSpecification const& modifications) override; void remove_surface( std::shared_ptr const& session, std::weak_ptr const& surface) override; void forget(std::weak_ptr const& surface) override; void add_display(geometry::Rectangle const& area) override; void remove_display(geometry::Rectangle const& area) override; bool handle_keyboard_event(MirKeyboardEvent const* event) override; bool handle_touch_event(MirTouchEvent const* event) override; bool handle_pointer_event(MirPointerEvent const* event) override; void handle_raise_surface( std::shared_ptr const& session, std::shared_ptr const& surface, uint64_t timestamp) override; int set_surface_attribute( std::shared_ptr const& /*session*/, std::shared_ptr const& surface, MirSurfaceAttrib attrib, int value) override; auto find_session(std::function const& predicate) -> std::shared_ptr override; auto info_for(std::weak_ptr const& session) const -> SessionInfo& override; auto info_for(std::weak_ptr const& surface) const -> SurfaceInfo& override; std::shared_ptr focused_session() const override; std::shared_ptr focused_surface() const override; void focus_next_session() override; void set_focus_to( std::shared_ptr const& focus, std::shared_ptr const& surface) override; auto surface_at(geometry::Point cursor) const -> std::shared_ptr override; auto active_display() -> geometry::Rectangle const override; void raise_tree(std::shared_ptr const& root) override; private: shell::FocusController* const focus_controller; std::unique_ptr const policy; std::mutex mutex; SessionInfoMap session_info; SurfaceInfoMap surface_info; geometry::Rectangles displays; geometry::Point cursor; uint64_t last_input_event_timestamp{0}; void update_event_timestamp(MirKeyboardEvent const* kev); void update_event_timestamp(MirPointerEvent const* pev); void update_event_timestamp(MirTouchEvent const* tev); }; /// A policy based window manager. This exists to initialize BasicWindowManager and /// the WMPolicy (in an awkward manner). /// TODO revisit this initialization sequence. template class WindowManagerBuilder : public BasicWindowManager { public: template WindowManagerBuilder( shell::FocusController* focus_controller, PolicyArgs&&... policy_args) : BasicWindowManager( focus_controller, build_policy(std::forward(policy_args)...)) { } private: template auto build_policy(PolicyArgs&&... policy_args) -> std::unique_ptr { return std::unique_ptr( new WMPolicy(this, std::forward(policy_args)...)); } }; } } #endif /* MIR_EXAMPLE_BASIC_WINDOW_MANAGER_H_ */ ./src/platforms/mirserver/wm-wip/server_example_window_management_info.h0000644000015600001650000000610312677054330027130 0ustar jenkinsjenkins/* * 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_SERVER_EXAMPLE_WINDOW_MANAGEMENT_INFO_H #define MIR_SERVER_EXAMPLE_WINDOW_MANAGEMENT_INFO_H #include "mir/geometry/rectangles.h" #include "mir/optional_value.h" #include "mir/shell/surface_specification.h" #include namespace mir { namespace scene { class Session; class Surface; class SurfaceCreationParameters; } namespace examples { struct SurfaceInfo { SurfaceInfo( std::shared_ptr const& session, std::shared_ptr const& surface, scene::SurfaceCreationParameters const& params); bool can_be_active() const; bool can_morph_to(MirSurfaceType new_type) const; bool must_have_parent() const; bool must_not_have_parent() const; bool is_visible() const; static bool needs_titlebar(MirSurfaceType type); void constrain_resize( std::shared_ptr const& surface, geometry::Point& requested_pos, geometry::Size& requested_size, const bool left_resize, const bool top_resize, geometry::Rectangle const& bounds) const; MirSurfaceType type; MirSurfaceState state; geometry::Rectangle restore_rect; std::weak_ptr session; std::weak_ptr parent; std::vector > children; std::shared_ptr titlebar; frontend::SurfaceId titlebar_id; bool is_titlebar = false; geometry::Width min_width; geometry::Height min_height; geometry::Width max_width; geometry::Height max_height; mir::optional_value width_inc; mir::optional_value height_inc; mir::optional_value min_aspect; mir::optional_value max_aspect; mir::optional_value output_id; void init_titlebar(std::shared_ptr const& surface); void paint_titlebar(int intensity); private: struct StreamPainter; struct AllocatingPainter; struct SwappingPainter; std::shared_ptr stream_painter; }; struct SessionInfo { std::vector> surfaces; // This is only used by the TilingWindowManagerPolicy, // perhaps we need a more extensible mechanism. (std::experimental::any?) geometry::Rectangle tile; }; } } #endif //MIR_SERVER_EXAMPLE_WINDOW_MANAGEMENT_INFO_H ./src/platforms/mirserver/wm-wip/server_example_basic_window_manager.cpp0000644000015600001650000002350412677054330027113 0ustar jenkinsjenkins/* * 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 "server_example_basic_window_manager.h" #include "mir/scene/session.h" #include "mir/scene/surface.h" #include "mir/scene/surface_creation_parameters.h" namespace me = mir::examples; me::BasicWindowManager::BasicWindowManager( shell::FocusController* focus_controller, std::unique_ptr policy) : focus_controller(focus_controller), policy(std::move(policy)) { } void me::BasicWindowManager::add_session(std::shared_ptr const& session) { std::lock_guard lock(mutex); session_info[session] = SessionInfo(); policy->handle_session_info_updated(session_info, displays); } void me::BasicWindowManager::remove_session(std::shared_ptr const& session) { std::lock_guard lock(mutex); session_info.erase(session); policy->handle_session_info_updated(session_info, displays); } auto me::BasicWindowManager::add_surface( std::shared_ptr const& session, scene::SurfaceCreationParameters const& params, std::function const& session, scene::SurfaceCreationParameters const& params)> const& build) -> frontend::SurfaceId { std::lock_guard lock(mutex); scene::SurfaceCreationParameters const placed_params = policy->handle_place_new_surface(session, params); auto const result = build(session, placed_params); auto const surface = session->surface(result); surface_info.emplace(surface, SurfaceInfo{session, surface, placed_params}); policy->handle_new_surface(session, surface); policy->generate_decorations_for(session, surface, surface_info, build); return result; } void me::BasicWindowManager::modify_surface( std::shared_ptr const& session, std::shared_ptr const& surface, shell::SurfaceSpecification const& modifications) { std::lock_guard lock(mutex); policy->handle_modify_surface(session, surface, modifications); } void me::BasicWindowManager::remove_surface( std::shared_ptr const& session, std::weak_ptr const& surface) { std::lock_guard lock(mutex); policy->handle_delete_surface(session, surface); surface_info.erase(surface); } void me::BasicWindowManager::forget(std::weak_ptr const& surface) { surface_info.erase(surface); } void me::BasicWindowManager::add_display(geometry::Rectangle const& area) { std::lock_guard lock(mutex); displays.add(area); policy->handle_displays_updated(session_info, displays); } void me::BasicWindowManager::remove_display(geometry::Rectangle const& area) { std::lock_guard lock(mutex); displays.remove(area); policy->handle_displays_updated(session_info, displays); } bool me::BasicWindowManager::handle_keyboard_event(MirKeyboardEvent const* event) { std::lock_guard lock(mutex); update_event_timestamp(event); return policy->handle_keyboard_event(event); } bool me::BasicWindowManager::handle_touch_event(MirTouchEvent const* event) { std::lock_guard lock(mutex); update_event_timestamp(event); return policy->handle_touch_event(event); } bool me::BasicWindowManager::handle_pointer_event(MirPointerEvent const* event) { std::lock_guard lock(mutex); update_event_timestamp(event); cursor = { mir_pointer_event_axis_value(event, mir_pointer_axis_x), mir_pointer_event_axis_value(event, mir_pointer_axis_y)}; return policy->handle_pointer_event(event); } void me::BasicWindowManager::handle_raise_surface( std::shared_ptr const& session, std::shared_ptr const& surface, uint64_t timestamp) { std::lock_guard lock(mutex); if (timestamp >= last_input_event_timestamp) policy->handle_raise_surface(session, surface); } int me::BasicWindowManager::set_surface_attribute( std::shared_ptr const& /*session*/, std::shared_ptr const& surface, MirSurfaceAttrib attrib, int value) { std::lock_guard lock(mutex); switch (attrib) { case mir_surface_attrib_state: { auto const state = policy->handle_set_state(surface, MirSurfaceState(value)); return surface->configure(attrib, state); } default: return surface->configure(attrib, value); } } auto me::BasicWindowManager::find_session(std::function const& predicate) -> std::shared_ptr { for(auto& info : session_info) { if (predicate(info.second)) { return info.first.lock(); } } return std::shared_ptr{}; } auto me::BasicWindowManager::info_for(std::weak_ptr const& session) const -> SessionInfo& { return const_cast(session_info.at(session)); } auto me::BasicWindowManager::info_for(std::weak_ptr const& surface) const -> SurfaceInfo& { return const_cast(surface_info.at(surface)); } auto me::BasicWindowManager::focused_session() const -> std::shared_ptr { return focus_controller->focused_session(); } auto me::BasicWindowManager::focused_surface() const ->std::shared_ptr { return focus_controller->focused_surface(); } void me::BasicWindowManager::focus_next_session() { focus_controller->focus_next_session(); } void me::BasicWindowManager::set_focus_to( std::shared_ptr const& focus, std::shared_ptr const& surface) { focus_controller->set_focus_to(focus, surface); } auto me::BasicWindowManager::surface_at(geometry::Point cursor) const -> std::shared_ptr { return focus_controller->surface_at(cursor); } auto me::BasicWindowManager::active_display() -> geometry::Rectangle const { geometry::Rectangle result; // 1. If a window has input focus, whichever display contains the largest // proportion of the area of that window. if (auto const surface = focused_surface()) { auto const surface_rect = surface->input_bounds(); int max_overlap_area = -1; for (auto const& display : displays) { auto const intersection = surface_rect.intersection_with(display).size; if (intersection.width.as_int()*intersection.height.as_int() > max_overlap_area) { max_overlap_area = intersection.width.as_int()*intersection.height.as_int(); result = display; } } return result; } // 2. Otherwise, if any window previously had input focus, for the window that had // it most recently, the display that contained the largest proportion of the // area of that window at the moment it closed, as long as that display is still // available. // 3. Otherwise, the display that contains the pointer, if there is one. for (auto const& display : displays) { if (display.contains(cursor)) { // Ignore the (unspecified) possiblity of overlapping displays return display; } } // 4. Otherwise, the primary display, if there is one (for example, the laptop display). // 5. Otherwise, the first display. if (displays.size()) result = *displays.begin(); return result; } void me::BasicWindowManager::raise_tree(std::shared_ptr const& root) { SurfaceSet surfaces; std::function const& surface)> const add_children = [&,this](std::weak_ptr const& surface) { auto const& info = info_for(surface); surfaces.insert(begin(info.children), end(info.children)); for (auto const& child : info.children) add_children(child); }; surfaces.insert(root); add_children(root); focus_controller->raise(surfaces); } void me::BasicWindowManager::update_event_timestamp(MirKeyboardEvent const* kev) { auto iev = mir_keyboard_event_input_event(kev); last_input_event_timestamp = mir_input_event_get_event_time(iev); } void me::BasicWindowManager::update_event_timestamp(MirPointerEvent const* pev) { auto iev = mir_pointer_event_input_event(pev); auto pointer_action = mir_pointer_event_action(pev); if (pointer_action == mir_pointer_action_button_up || pointer_action == mir_pointer_action_button_down) { last_input_event_timestamp = mir_input_event_get_event_time(iev); } } void me::BasicWindowManager::update_event_timestamp(MirTouchEvent const* tev) { auto iev = mir_touch_event_input_event(tev); auto touch_count = mir_touch_event_point_count(tev); for (unsigned i = 0; i < touch_count; i++) { auto touch_action = mir_touch_event_action(tev, i); if (touch_action == mir_touch_action_up || touch_action == mir_touch_action_down) { last_input_event_timestamp = mir_input_event_get_event_time(iev); break; } } } ./src/platforms/mirserver/wm-wip/server_example_canonical_window_manager.cpp0000644000015600001650000007071012677054330027762 0ustar jenkinsjenkins/* * 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 "server_example_canonical_window_manager.h" #include "mir/scene/session.h" #include "mir/scene/surface.h" #include "mir/scene/surface_creation_parameters.h" #include "mir/shell/surface_ready_observer.h" #include "mir/shell/display_layout.h" #include #include #include namespace me = mir::examples; namespace ms = mir::scene; using namespace mir::geometry; ///\example server_example_canonical_window_manager.cpp // Based on "Mir and Unity: Surfaces, input, and displays (v0.3)" namespace { int const title_bar_height = 10; Size titlebar_size_for_window(Size window_size) { return {window_size.width, Height{title_bar_height}}; } Point titlebar_position_for_window(Point window_position) { return { window_position.x, window_position.y - DeltaY(title_bar_height) }; } } me::CanonicalWindowManagerPolicyCopy::CanonicalWindowManagerPolicyCopy( WindowManagerTools* const tools, std::shared_ptr const& display_layout) : tools{tools}, display_layout{display_layout} { } void me::CanonicalWindowManagerPolicyCopy::click(Point cursor) { if (auto const surface = tools->surface_at(cursor)) select_active_surface(surface); } void me::CanonicalWindowManagerPolicyCopy::handle_session_info_updated(SessionInfoMap& /*session_info*/, Rectangles const& /*displays*/) { } void me::CanonicalWindowManagerPolicyCopy::handle_displays_updated(SessionInfoMap& /*session_info*/, Rectangles const& displays) { display_area = displays.bounding_rectangle(); for (auto const weak_surface : fullscreen_surfaces) { if (auto const surface = weak_surface.lock()) { auto const& info = tools->info_for(weak_surface); Rectangle rect{surface->top_left(), surface->size()}; display_layout->place_in_output(info.output_id.value(), rect); surface->move_to(rect.top_left); surface->resize(rect.size); } } } void me::CanonicalWindowManagerPolicyCopy::resize(Point cursor) { select_active_surface(tools->surface_at(old_cursor)); resize(active_surface(), cursor, old_cursor, display_area); } auto me::CanonicalWindowManagerPolicyCopy::handle_place_new_surface( std::shared_ptr const& session, ms::SurfaceCreationParameters const& request_parameters) -> ms::SurfaceCreationParameters { auto parameters = request_parameters; auto surf_type = parameters.type.is_set() ? parameters.type.value() : mir_surface_type_normal; bool const needs_titlebar = SurfaceInfo::needs_titlebar(surf_type); if (needs_titlebar) parameters.size.height = parameters.size.height + DeltaY{title_bar_height}; if (!parameters.state.is_set()) parameters.state = mir_surface_state_restored; auto const active_display = tools->active_display(); auto const width = parameters.size.width.as_int(); auto const height = parameters.size.height.as_int(); bool positioned = false; auto const parent = parameters.parent.lock(); if (parameters.output_id != mir::graphics::DisplayConfigurationOutputId{0}) { Rectangle rect{parameters.top_left, parameters.size}; display_layout->place_in_output(parameters.output_id, rect); parameters.top_left = rect.top_left; parameters.size = rect.size; parameters.state = mir_surface_state_fullscreen; positioned = true; } else if (!parent) // No parent => client can't suggest positioning { if (auto const default_surface = session->default_surface()) { static Displacement const offset{title_bar_height, title_bar_height}; parameters.top_left = default_surface->top_left() + offset; geometry::Rectangle display_for_app{default_surface->top_left(), default_surface->size()}; display_layout->size_to_output(display_for_app); positioned = display_for_app.overlaps(Rectangle{parameters.top_left, parameters.size}); } } if (parent && parameters.aux_rect.is_set() && parameters.edge_attachment.is_set()) { auto const edge_attachment = parameters.edge_attachment.value(); auto const aux_rect = parameters.aux_rect.value(); auto const parent_top_left = parent->top_left(); auto const top_left = aux_rect.top_left -Point{} + parent_top_left; auto const top_right= aux_rect.top_right() -Point{} + parent_top_left; auto const bot_left = aux_rect.bottom_left()-Point{} + parent_top_left; if (edge_attachment & mir_edge_attachment_vertical) { if (active_display.contains(top_right + Displacement{width, height})) { parameters.top_left = top_right; positioned = true; } else if (active_display.contains(top_left + Displacement{-width, height})) { parameters.top_left = top_left + Displacement{-width, 0}; positioned = true; } } if (edge_attachment & mir_edge_attachment_horizontal) { if (active_display.contains(bot_left + Displacement{width, height})) { parameters.top_left = bot_left; positioned = true; } else if (active_display.contains(top_left + Displacement{width, -height})) { parameters.top_left = top_left + Displacement{0, -height}; positioned = true; } } } else if (parent) { // o Otherwise, if the dialog is not the same as any previous dialog for the // same parent window, and/or it does not have user-customized position: // o It should be optically centered relative to its parent, unless this // would overlap or cover the title bar of the parent. // o Otherwise, it should be cascaded vertically (but not horizontally) // relative to its parent, unless, this would cause at least part of // it to extend into shell space. auto const parent_top_left = parent->top_left(); auto const centred = parent_top_left + 0.5*(as_displacement(parent->size()) - as_displacement(parameters.size)) - DeltaY{(parent->size().height.as_int()-height)/6}; parameters.top_left = centred; positioned = true; } if (!positioned) { auto const centred = active_display.top_left + 0.5*(as_displacement(active_display.size) - as_displacement(parameters.size)) - DeltaY{(active_display.size.height.as_int()-height)/6}; switch (parameters.state.value()) { case mir_surface_state_fullscreen: case mir_surface_state_maximized: parameters.top_left = active_display.top_left; parameters.size = active_display.size; break; case mir_surface_state_vertmaximized: parameters.top_left = centred; parameters.top_left.y = active_display.top_left.y; parameters.size.height = active_display.size.height; break; case mir_surface_state_horizmaximized: parameters.top_left = centred; parameters.top_left.x = active_display.top_left.x; parameters.size.width = active_display.size.width; break; default: parameters.top_left = centred; } if (parameters.top_left.y < display_area.top_left.y) parameters.top_left.y = display_area.top_left.y; } if (parameters.state != mir_surface_state_fullscreen && needs_titlebar) { parameters.top_left.y = parameters.top_left.y + DeltaY{title_bar_height}; parameters.size.height = parameters.size.height - DeltaY{title_bar_height}; } return parameters; } void me::CanonicalWindowManagerPolicyCopy::generate_decorations_for( std::shared_ptr const& session, std::shared_ptr const& surface, SurfaceInfoMap& surface_map, std::function const& session, scene::SurfaceCreationParameters const& params)> const& build) { if (!SurfaceInfo::needs_titlebar(surface->type())) return; auto format = mir_pixel_format_xrgb_8888; ms::SurfaceCreationParameters params; params.of_size(titlebar_size_for_window(surface->size())) .of_name("decoration") .of_pixel_format(format) .of_buffer_usage(mir::graphics::BufferUsage::software) .of_position(titlebar_position_for_window(surface->top_left())) .of_type(mir_surface_type_gloss); auto id = build(session, params); auto titlebar = session->surface(id); titlebar->set_alpha(0.9); auto& surface_info = tools->info_for(surface); surface_info.titlebar = titlebar; surface_info.titlebar_id = id; surface_info.children.push_back(titlebar); SurfaceInfo& titlebar_info = surface_map.emplace(titlebar, SurfaceInfo{session, titlebar, {}}).first->second; titlebar_info.is_titlebar = true; titlebar_info.parent = surface; titlebar_info.init_titlebar(titlebar); } void me::CanonicalWindowManagerPolicyCopy::handle_new_surface(std::shared_ptr const& session, std::shared_ptr const& surface) { auto& surface_info = tools->info_for(surface); if (auto const parent = surface_info.parent.lock()) { tools->info_for(parent).children.push_back(surface); } tools->info_for(session).surfaces.push_back(surface); if (surface_info.can_be_active()) { surface->add_observer(std::make_shared( [this](std::shared_ptr const& /*session*/, std::shared_ptr const& surface) { select_active_surface(surface); }, session, surface)); } if (surface_info.state == mir_surface_state_fullscreen) fullscreen_surfaces.insert(surface); } void me::CanonicalWindowManagerPolicyCopy::handle_modify_surface( std::shared_ptr const& session, std::shared_ptr const& surface, shell::SurfaceSpecification const& modifications) { auto& surface_info_old = tools->info_for(surface); auto surface_info = surface_info_old; if (modifications.parent.is_set()) surface_info.parent = modifications.parent.value(); if (modifications.type.is_set() && surface_info.type != modifications.type.value()) { auto const new_type = modifications.type.value(); if (!surface_info.can_morph_to(new_type)) { throw std::runtime_error("Unsupported surface type change"); } surface_info.type = new_type; if (surface_info.must_not_have_parent()) { if (modifications.parent.is_set()) throw std::runtime_error("Target surface type does not support parent"); surface_info.parent.reset(); } else if (surface_info.must_have_parent()) { if (!surface_info.parent.lock()) throw std::runtime_error("Target surface type requires parent"); } surface->configure(mir_surface_attrib_type, new_type); } #define COPY_IF_SET(field)\ if (modifications.field.is_set())\ surface_info.field = modifications.field.value() COPY_IF_SET(min_width); COPY_IF_SET(min_height); COPY_IF_SET(max_width); COPY_IF_SET(max_height); COPY_IF_SET(min_width); COPY_IF_SET(width_inc); COPY_IF_SET(height_inc); COPY_IF_SET(min_aspect); COPY_IF_SET(max_aspect); COPY_IF_SET(output_id); #undef COPY_IF_SET std::swap(surface_info, surface_info_old); if (modifications.name.is_set()) surface->rename(modifications.name.value()); if (modifications.streams.is_set()) { auto v = modifications.streams.value(); std::vector l (v.begin(), v.end()); session->configure_streams(*surface, l); } if (modifications.input_shape.is_set()) { surface->set_input_region(modifications.input_shape.value()); } if (modifications.width.is_set() || modifications.height.is_set()) { auto new_size = surface->size(); if (modifications.width.is_set()) new_size.width = modifications.width.value(); if (modifications.height.is_set()) new_size.height = modifications.height.value(); auto top_left = surface->top_left(); surface_info.constrain_resize( surface, top_left, new_size, false, false, display_area); apply_resize(surface, surface_info.titlebar, top_left, new_size); } if (modifications.state.is_set()) { auto const state = handle_set_state(surface, modifications.state.value()); surface->configure(mir_surface_attrib_state, state); } } void me::CanonicalWindowManagerPolicyCopy::handle_delete_surface(std::shared_ptr const& session, std::weak_ptr const& surface) { fullscreen_surfaces.erase(surface); auto& info = tools->info_for(surface); if (auto const parent = info.parent.lock()) { auto& siblings = tools->info_for(parent).children; for (auto i = begin(siblings); i != end(siblings); ++i) { if (surface.lock() == i->lock()) { siblings.erase(i); break; } } } session->destroy_surface(surface); if (info.titlebar) { session->destroy_surface(info.titlebar_id); tools->forget(info.titlebar); } auto& surfaces = tools->info_for(session).surfaces; for (auto i = begin(surfaces); i != end(surfaces); ++i) { if (surface.lock() == i->lock()) { surfaces.erase(i); break; } } if (surfaces.empty() && session == tools->focused_session()) { active_surface_.reset(); tools->focus_next_session(); select_active_surface(tools->focused_surface()); } } int me::CanonicalWindowManagerPolicyCopy::handle_set_state(std::shared_ptr const& surface, MirSurfaceState value) { auto& info = tools->info_for(surface); switch (value) { case mir_surface_state_restored: case mir_surface_state_maximized: case mir_surface_state_vertmaximized: case mir_surface_state_horizmaximized: case mir_surface_state_fullscreen: case mir_surface_state_hidden: case mir_surface_state_minimized: break; default: return info.state; } if (info.state == mir_surface_state_restored) { info.restore_rect = {surface->top_left(), surface->size()}; } if (info.state != mir_surface_state_fullscreen) { info.output_id = decltype(info.output_id){}; fullscreen_surfaces.erase(surface); } else { fullscreen_surfaces.insert(surface); } if (info.state == value) { return info.state; } auto const old_pos = surface->top_left(); Displacement movement; switch (value) { case mir_surface_state_restored: movement = info.restore_rect.top_left - old_pos; surface->resize(info.restore_rect.size); if (info.titlebar) { info.titlebar->resize(titlebar_size_for_window(info.restore_rect.size)); info.titlebar->show(); } break; case mir_surface_state_maximized: movement = display_area.top_left - old_pos; surface->resize(display_area.size); if (info.titlebar) info.titlebar->hide(); break; case mir_surface_state_horizmaximized: movement = Point{display_area.top_left.x, info.restore_rect.top_left.y} - old_pos; surface->resize({display_area.size.width, info.restore_rect.size.height}); if (info.titlebar) { info.titlebar->resize(titlebar_size_for_window({display_area.size.width, info.restore_rect.size.height})); info.titlebar->show(); } break; case mir_surface_state_vertmaximized: movement = Point{info.restore_rect.top_left.x, display_area.top_left.y} - old_pos; surface->resize({info.restore_rect.size.width, display_area.size.height}); if (info.titlebar) info.titlebar->hide(); break; case mir_surface_state_fullscreen: { Rectangle rect{old_pos, surface->size()}; if (info.output_id.is_set()) { display_layout->place_in_output(info.output_id.value(), rect); } else { display_layout->size_to_output(rect); } movement = rect.top_left - old_pos; surface->resize(rect.size); break; } case mir_surface_state_hidden: case mir_surface_state_minimized: if (info.titlebar) info.titlebar->hide(); surface->hide(); return info.state = value; default: break; } // TODO It is rather simplistic to move a tree WRT the top_left of the root // TODO when resizing. But for more sophistication we would need to encode // TODO some sensible layout rules. move_tree(surface, movement); info.state = value; if (info.is_visible()) surface->show(); return info.state; } void me::CanonicalWindowManagerPolicyCopy::drag(Point cursor) { select_active_surface(tools->surface_at(old_cursor)); drag(active_surface(), cursor, old_cursor, display_area); } void me::CanonicalWindowManagerPolicyCopy::handle_raise_surface( std::shared_ptr const& /*session*/, std::shared_ptr const& surface) { select_active_surface(surface); } bool me::CanonicalWindowManagerPolicyCopy::handle_keyboard_event(MirKeyboardEvent const* event) { auto const action = mir_keyboard_event_action(event); auto const scan_code = mir_keyboard_event_scan_code(event); auto const modifiers = mir_keyboard_event_modifiers(event) & modifier_mask; if (action == mir_keyboard_action_down && scan_code == KEY_F11) { switch (modifiers) { case mir_input_event_modifier_alt: toggle(mir_surface_state_maximized); return true; case mir_input_event_modifier_shift: toggle(mir_surface_state_vertmaximized); return true; case mir_input_event_modifier_ctrl: toggle(mir_surface_state_horizmaximized); return true; default: break; } } else if (action == mir_keyboard_action_down && scan_code == KEY_F4) { if (auto const session = tools->focused_session()) { switch (modifiers) { case mir_input_event_modifier_alt: kill(session->process_id(), SIGTERM); return true; case mir_input_event_modifier_ctrl: if (auto const surf = session->default_surface()) { surf->request_client_surface_close(); return true; } default: break; } } } else if (action == mir_keyboard_action_down && modifiers == mir_input_event_modifier_alt && scan_code == KEY_TAB) { tools->focus_next_session(); if (auto const surface = tools->focused_surface()) select_active_surface(surface); return true; } else if (action == mir_keyboard_action_down && modifiers == mir_input_event_modifier_alt && scan_code == KEY_GRAVE) { if (auto const prev = tools->focused_surface()) { if (auto const app = tools->focused_session()) select_active_surface(app->surface_after(prev)); } return true; } return false; } bool me::CanonicalWindowManagerPolicyCopy::handle_touch_event(MirTouchEvent const* event) { auto const count = mir_touch_event_point_count(event); long total_x = 0; long total_y = 0; for (auto i = 0U; i != count; ++i) { total_x += mir_touch_event_axis_value(event, i, mir_touch_axis_x); total_y += mir_touch_event_axis_value(event, i, mir_touch_axis_y); } Point const cursor{total_x/count, total_y/count}; bool is_drag = true; for (auto i = 0U; i != count; ++i) { switch (mir_touch_event_action(event, i)) { case mir_touch_action_up: return false; case mir_touch_action_down: is_drag = false; case mir_touch_action_change: continue; } } bool consumes_event = false; if (is_drag) { switch (count) { case 2: resize(cursor); consumes_event = true; break; case 3: drag(cursor); consumes_event = true; break; } } old_cursor = cursor; return consumes_event; } bool me::CanonicalWindowManagerPolicyCopy::handle_pointer_event(MirPointerEvent const* event) { auto const action = mir_pointer_event_action(event); auto const modifiers = mir_pointer_event_modifiers(event) & modifier_mask; Point const cursor{ mir_pointer_event_axis_value(event, mir_pointer_axis_x), mir_pointer_event_axis_value(event, mir_pointer_axis_y)}; bool consumes_event = false; if (action == mir_pointer_action_button_down) { click(cursor); } else if (action == mir_pointer_action_motion && modifiers == mir_input_event_modifier_alt) { if (mir_pointer_event_button_state(event, mir_pointer_button_primary)) { drag(cursor); consumes_event = true; } if (mir_pointer_event_button_state(event, mir_pointer_button_tertiary)) { resize(cursor); consumes_event = true; } } else if (action == mir_pointer_action_motion && !modifiers) { if (mir_pointer_event_button_state(event, mir_pointer_button_primary)) { if (auto const possible_titlebar = tools->surface_at(old_cursor)) { if (tools->info_for(possible_titlebar).is_titlebar) { drag(cursor); consumes_event = true; } } } } old_cursor = cursor; return consumes_event; } void me::CanonicalWindowManagerPolicyCopy::toggle(MirSurfaceState state) { if (auto const surface = active_surface()) { auto& info = tools->info_for(surface); if (info.state == state) state = mir_surface_state_restored; auto const value = handle_set_state(surface, MirSurfaceState(state)); surface->configure(mir_surface_attrib_state, value); } } void me::CanonicalWindowManagerPolicyCopy::select_active_surface(std::shared_ptr const& surface) { if (surface == active_surface_.lock()) return; if (!surface) { if (auto const active_surface = active_surface_.lock()) { if (auto const titlebar = tools->info_for(active_surface).titlebar) { tools->info_for(titlebar).paint_titlebar(0x3F); } } if (active_surface_.lock()) tools->set_focus_to({}, {}); active_surface_.reset(); return; } auto const& info_for = tools->info_for(surface); if (info_for.can_be_active()) { if (auto const active_surface = active_surface_.lock()) { if (auto const titlebar = tools->info_for(active_surface).titlebar) { tools->info_for(titlebar).paint_titlebar(0x3F); } } if (auto const titlebar = tools->info_for(surface).titlebar) { tools->info_for(titlebar).paint_titlebar(0xFF); } tools->set_focus_to(info_for.session.lock(), surface); tools->raise_tree(surface); active_surface_ = surface; } else { // Cannot have input focus - try the parent if (auto const parent = info_for.parent.lock()) select_active_surface(parent); } } auto me::CanonicalWindowManagerPolicyCopy::active_surface() const -> std::shared_ptr { if (auto const surface = active_surface_.lock()) return surface; if (auto const session = tools->focused_session()) { if (auto const surface = session->default_surface()) return surface; } return std::shared_ptr{}; } bool me::CanonicalWindowManagerPolicyCopy::resize(std::shared_ptr const& surface, Point cursor, Point old_cursor, Rectangle bounds) { if (!surface || !surface->input_area_contains(old_cursor)) return false; auto const top_left = surface->top_left(); Rectangle const old_pos{top_left, surface->size()}; auto anchor = top_left; for (auto const& corner : { old_pos.top_right(), old_pos.bottom_left(), old_pos.bottom_right()}) { if ((old_cursor - anchor).length_squared() < (old_cursor - corner).length_squared()) { anchor = corner; } } bool const left_resize = anchor.x != top_left.x; bool const top_resize = anchor.y != top_left.y; int const x_sign = left_resize? -1 : 1; int const y_sign = top_resize? -1 : 1; auto const delta = cursor-old_cursor; Size new_size{old_pos.size.width + x_sign*delta.dx, old_pos.size.height + y_sign*delta.dy}; Point new_pos = top_left + left_resize*delta.dx + top_resize*delta.dy; auto const& surface_info = tools->info_for(surface); surface_info.constrain_resize(surface, new_pos, new_size, left_resize, top_resize, bounds); apply_resize(surface, surface_info.titlebar, new_pos, new_size); return true; } void me::CanonicalWindowManagerPolicyCopy::apply_resize( std::shared_ptr const& surface, std::shared_ptr const& titlebar, Point const& new_pos, Size const& new_size) const { if (titlebar) titlebar->resize({new_size.width, Height{title_bar_height}}); surface->resize(new_size); move_tree(surface, new_pos-surface->top_left()); } bool me::CanonicalWindowManagerPolicyCopy::drag(std::shared_ptr surface, Point to, Point from, Rectangle /*bounds*/) { if (!surface) return false; if (!surface->input_area_contains(from) && !tools->info_for(surface).titlebar) return false; auto movement = to - from; // placeholder - constrain onscreen switch (tools->info_for(surface).state) { case mir_surface_state_restored: break; // "A vertically maximised surface is anchored to the top and bottom of // the available workspace and can have any width." case mir_surface_state_vertmaximized: movement.dy = DeltaY(0); break; // "A horizontally maximised surface is anchored to the left and right of // the available workspace and can have any height" case mir_surface_state_horizmaximized: movement.dx = DeltaX(0); break; // "A maximised surface is anchored to the top, bottom, left and right of the // available workspace. For example, if the launcher is always-visible then // the left-edge of the surface is anchored to the right-edge of the launcher." case mir_surface_state_maximized: case mir_surface_state_fullscreen: default: return true; } move_tree(surface, movement); return true; } void me::CanonicalWindowManagerPolicyCopy::move_tree(std::shared_ptr const& root, Displacement movement) const { root->move_to(root->top_left() + movement); for (auto const& child: tools->info_for(root).children) { move_tree(child.lock(), movement); } } ./src/platforms/mirserver/wm-wip/server_example_canonical_window_manager.h0000644000015600001650000001215412677054330027425 0ustar jenkinsjenkins/* * 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_CANONICAL_WINDOW_MANAGER_H_ #define MIR_EXAMPLE_CANONICAL_WINDOW_MANAGER_H_ #include "server_example_basic_window_manager.h" #include "mir/geometry/displacement.h" #include #include ///\example server_example_canonical_window_manager.h // Based on "Mir and Unity: Surfaces, input, and displays (v0.3)" namespace mir { namespace shell { class DisplayLayout; } namespace examples { // standard window management algorithm: // o Switch apps: tap or click on the corresponding tile // o Move window: Alt-leftmousebutton drag (three finger drag) // o Resize window: Alt-middle_button drag (two finger drag) // o Maximize/restore current window (to display size): Alt-F11 // o Maximize/restore current window (to display height): Shift-F11 // o Maximize/restore current window (to display width): Ctrl-F11 // o client requests to maximize, vertically maximize & restore class CanonicalWindowManagerPolicyCopy : public WindowManagementPolicy { public: explicit CanonicalWindowManagerPolicyCopy( WindowManagerTools* const tools, std::shared_ptr const& display_layout); void click(geometry::Point cursor); void handle_session_info_updated(SessionInfoMap& session_info, geometry::Rectangles const& displays); void handle_displays_updated(SessionInfoMap& session_info, geometry::Rectangles const& displays); void resize(geometry::Point cursor); auto handle_place_new_surface( std::shared_ptr const& session, scene::SurfaceCreationParameters const& request_parameters) -> scene::SurfaceCreationParameters; void handle_new_surface(std::shared_ptr const& session, std::shared_ptr const& surface); void handle_modify_surface( std::shared_ptr const& session, std::shared_ptr const& surface, shell::SurfaceSpecification const& modifications); void handle_delete_surface(std::shared_ptr const& session, std::weak_ptr const& surface); int handle_set_state(std::shared_ptr const& surface, MirSurfaceState value); void drag(geometry::Point cursor); bool handle_keyboard_event(MirKeyboardEvent const* event); bool handle_touch_event(MirTouchEvent const* event); bool handle_pointer_event(MirPointerEvent const* event); void handle_raise_surface( std::shared_ptr const& session, std::shared_ptr const& surface); void generate_decorations_for( std::shared_ptr const& session, std::shared_ptr const& surface, SurfaceInfoMap& surface_map, std::function const& session, scene::SurfaceCreationParameters const& params)> const& build); private: static const int modifier_mask = mir_input_event_modifier_alt | mir_input_event_modifier_shift | mir_input_event_modifier_sym | mir_input_event_modifier_ctrl | mir_input_event_modifier_meta; void toggle(MirSurfaceState state); // "Mir and Unity: Surfaces, input, and displays (v0.3)" talks about active // *window*,but Mir really only understands surfaces void select_active_surface(std::shared_ptr const& surface); auto active_surface() const -> std::shared_ptr; bool resize(std::shared_ptr const& surface, geometry::Point cursor, geometry::Point old_cursor, geometry::Rectangle bounds); bool drag(std::shared_ptr surface, geometry::Point to, geometry::Point from, geometry::Rectangle bounds); void move_tree(std::shared_ptr const& root, geometry::Displacement movement) const; void apply_resize( std::shared_ptr const& surface, std::shared_ptr const& titlebar, geometry::Point const& new_pos, geometry::Size const& new_size) const; WindowManagerTools* const tools; std::shared_ptr const display_layout; geometry::Rectangle display_area; geometry::Point old_cursor{}; std::weak_ptr active_surface_; using FullscreenSurfaces = std::set, std::owner_less>>; FullscreenSurfaces fullscreen_surfaces; }; } } #endif /* MIR_EXAMPLE_CANONICAL_WINDOW_MANAGER_H_ */ ./src/platforms/mirserver/wm-wip/README0000644000015600001650000000052312677054330020020 0ustar jenkinsjenkinsexamples for mir clients. you should have package 'libmirclient-dev' installed you can compile with a command like: g++ -std=c++0x -o mir_demo_client_scroll `pkg-config --libs --cflags mirclient` demo_client_scroll.cpp graphics_utils.cpp gcc -o mir_demo_client_flicker `pkg-config --libs --cflags mirclient` demo_client_flicker.c ./src/platforms/mirserver/wm-wip/server_example_window_management.cpp0000644000015600001650000001321012677054330026445 0ustar jenkinsjenkins/* * 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: Alan Griffiths */ #include "server_example_window_management.h" #include "server_example_tiling_window_manager.h" #include "server_example_canonical_window_manager.h" #include "mir/abnormal_exit.h" #include "mir/server.h" #include "mir/input/composite_event_filter.h" #include "mir/options/option.h" #include "mir/scene/session.h" #include "mir/scene/surface_creation_parameters.h" #include "mir/shell/display_layout.h" #include "mir/shell/system_compositor_window_manager.h" namespace me = mir::examples; namespace mf = mir::frontend; namespace mg = mir::graphics; namespace mi = mir::input; namespace ms = mir::scene; namespace msh = mir::shell; using namespace mir::geometry; ///\example server_example_window_management.cpp /// Demonstrate introducing a window management strategy namespace { char const* const wm_option = "window-manager"; char const* const wm_description = "window management strategy [{tiling|fullscreen|canonical|system-compositor}]"; char const* const wm_tiling = "tiling"; char const* const wm_fullscreen = "fullscreen"; char const* const wm_canonical = "canonical"; char const* const wm_system_compositor = "system-compositor"; // Very simple - make every surface fullscreen class FullscreenWindowManagerPolicy : public me::WindowManagementPolicy { public: FullscreenWindowManagerPolicy(me::WindowManagerTools* const /*tools*/, std::shared_ptr const& display_layout) : display_layout{display_layout} {} void handle_session_info_updated(SessionInfoMap& /*session_info*/, Rectangles const& /*displays*/) {} void handle_displays_updated(SessionInfoMap& /*session_info*/, Rectangles const& /*displays*/) {} auto handle_place_new_surface( std::shared_ptr const& /*session*/, ms::SurfaceCreationParameters const& request_parameters) -> ms::SurfaceCreationParameters { auto placed_parameters = request_parameters; Rectangle rect{request_parameters.top_left, request_parameters.size}; display_layout->size_to_output(rect); placed_parameters.size = rect.size; return placed_parameters; } void handle_modify_surface( std::shared_ptr const& /*session*/, std::shared_ptr const& /*surface*/, msh::SurfaceSpecification const& /*modifications*/) { } void handle_new_surface(std::shared_ptr const& /*session*/, std::shared_ptr const& /*surface*/) { } void handle_delete_surface(std::shared_ptr const& session, std::weak_ptr const& surface) { session->destroy_surface(surface); } int handle_set_state(std::shared_ptr const& /*surface*/, MirSurfaceState value) { return value; } bool handle_keyboard_event(MirKeyboardEvent const* /*event*/) { return false; } bool handle_touch_event(MirTouchEvent const* /*event*/) { return false; } bool handle_pointer_event(MirPointerEvent const* /*event*/) { return false; } void handle_raise_surface( std::shared_ptr const& /*session*/, std::shared_ptr const& /*surface*/) { } void generate_decorations_for( std::shared_ptr const&, std::shared_ptr const&, SurfaceInfoMap&, std::function const&, ms::SurfaceCreationParameters const&)> const&) { } private: std::shared_ptr const display_layout; }; } using FullscreenWindowManager = me::WindowManagerBuilder; using CanonicalWindowManager = me::WindowManagerBuilder; void me::add_window_manager_option_to(Server& server) { server.add_configuration_option(wm_option, wm_description, wm_canonical); server.override_the_window_manager_builder([&server](msh::FocusController* focus_controller) -> std::shared_ptr { auto const options = server.get_options(); auto const selection = options->get(wm_option); if (selection == wm_tiling) { return std::make_shared(focus_controller); } else if (selection == wm_fullscreen) { return std::make_shared(focus_controller, server.the_shell_display_layout()); } else if (selection == wm_canonical) { return std::make_shared(focus_controller, server.the_shell_display_layout()); } else if (selection == wm_system_compositor) { return std::make_shared( focus_controller, server.the_shell_display_layout(), server.the_session_coordinator()); } throw mir::AbnormalExit("Unknown window manager: " + selection); }); } ./src/platforms/mirserver/wm-wip/server_example_tiling_window_manager.cpp0000644000015600001650000004734012677054330027324 0ustar jenkinsjenkins/* * 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 "server_example_tiling_window_manager.h" #include "mir/scene/session.h" #include "mir/scene/surface.h" #include "mir/scene/surface_creation_parameters.h" #include "mir/shell/surface_specification.h" #include "mir/shell/surface_stack.h" #include "mir/shell/surface_ready_observer.h" #include "mir/geometry/displacement.h" #include #include namespace me = mir::examples; namespace ms = mir::scene; namespace mf = mir::frontend; using namespace mir::geometry; ///\example server_example_tiling_window_manager.cpp /// Demonstrate implementing a simple tiling algorithm me::TilingWindowManagerPolicy::TilingWindowManagerPolicy(WindowManagerTools* const tools) : tools{tools} { } void me::TilingWindowManagerPolicy::click(Point cursor) { auto const session = session_under(cursor); auto const surface = tools->surface_at(cursor); select_active_surface(session, surface); } void me::TilingWindowManagerPolicy::handle_session_info_updated(SessionInfoMap& session_info, Rectangles const& displays) { update_tiles(session_info, displays); } void me::TilingWindowManagerPolicy::handle_displays_updated(SessionInfoMap& session_info, Rectangles const& displays) { update_tiles(session_info, displays); } void me::TilingWindowManagerPolicy::resize(Point cursor) { if (auto const session = session_under(cursor)) { if (session == session_under(old_cursor)) { if (auto const surface = select_active_surface(session, tools->surface_at(old_cursor))) { resize(surface, cursor, old_cursor, tools->info_for(session).tile); } } } } auto me::TilingWindowManagerPolicy::handle_place_new_surface( std::shared_ptr const& session, ms::SurfaceCreationParameters const& request_parameters) -> ms::SurfaceCreationParameters { auto parameters = request_parameters; Rectangle const& tile = tools->info_for(session).tile; parameters.top_left = parameters.top_left + (tile.top_left - Point{0, 0}); if (auto const parent = parameters.parent.lock()) { auto const width = parameters.size.width.as_int(); auto const height = parameters.size.height.as_int(); if (parameters.aux_rect.is_set() && parameters.edge_attachment.is_set()) { auto const edge_attachment = parameters.edge_attachment.value(); auto const aux_rect = parameters.aux_rect.value(); auto const parent_top_left = parent->top_left(); auto const top_left = aux_rect.top_left -Point{} + parent_top_left; auto const top_right= aux_rect.top_right() -Point{} + parent_top_left; auto const bot_left = aux_rect.bottom_left()-Point{} + parent_top_left; if (edge_attachment & mir_edge_attachment_vertical) { if (tile.contains(top_right + Displacement{width, height})) { parameters.top_left = top_right; } else if (tile.contains(top_left + Displacement{-width, height})) { parameters.top_left = top_left + Displacement{-width, 0}; } } if (edge_attachment & mir_edge_attachment_horizontal) { if (tile.contains(bot_left + Displacement{width, height})) { parameters.top_left = bot_left; } else if (tile.contains(top_left + Displacement{width, -height})) { parameters.top_left = top_left + Displacement{0, -height}; } } } else { auto const parent_top_left = parent->top_left(); auto const centred = parent_top_left + 0.5*(as_displacement(parent->size()) - as_displacement(parameters.size)) - DeltaY{(parent->size().height.as_int()-height)/6}; parameters.top_left = centred; } } clip_to_tile(parameters, tile); return parameters; } void me::TilingWindowManagerPolicy::generate_decorations_for( std::shared_ptr const&, std::shared_ptr const&, SurfaceInfoMap&, std::function const&, ms::SurfaceCreationParameters const&)> const&) { } void me::TilingWindowManagerPolicy::handle_new_surface(std::shared_ptr const& session, std::shared_ptr const& surface) { tools->info_for(session).surfaces.push_back(surface); auto& surface_info = tools->info_for(surface); if (auto const parent = surface_info.parent.lock()) { tools->info_for(parent).children.push_back(surface); } if (surface_info.can_be_active()) { surface->add_observer(std::make_shared( [this](std::shared_ptr const& session, std::shared_ptr const& surface) { select_active_surface(session, surface); }, session, surface)); } } void me::TilingWindowManagerPolicy::handle_modify_surface( std::shared_ptr const& /*session*/, std::shared_ptr const& surface, shell::SurfaceSpecification const& modifications) { if (modifications.name.is_set()) surface->rename(modifications.name.value()); } void me::TilingWindowManagerPolicy::handle_delete_surface(std::shared_ptr const& session, std::weak_ptr const& surface) { auto& info = tools->info_for(surface); if (auto const parent = info.parent.lock()) { auto& siblings = tools->info_for(parent).children; for (auto i = begin(siblings); i != end(siblings); ++i) { if (surface.lock() == i->lock()) { siblings.erase(i); break; } } } auto& surfaces = tools->info_for(session).surfaces; for (auto i = begin(surfaces); i != end(surfaces); ++i) { if (surface.lock() == i->lock()) { surfaces.erase(i); break; } } session->destroy_surface(surface); if (surfaces.empty() && session == tools->focused_session()) { tools->focus_next_session(); select_active_surface(tools->focused_session(), tools->focused_surface()); } } int me::TilingWindowManagerPolicy::handle_set_state(std::shared_ptr const& surface, MirSurfaceState value) { auto& info = tools->info_for(surface); switch (value) { case mir_surface_state_restored: case mir_surface_state_maximized: case mir_surface_state_vertmaximized: case mir_surface_state_horizmaximized: break; default: return info.state; } if (info.state == mir_surface_state_restored) { info.restore_rect = {surface->top_left(), surface->size()}; } if (info.state == value) { return info.state; } auto const& tile = tools->info_for(info.session).tile; switch (value) { case mir_surface_state_restored: surface->resize(info.restore_rect.size); drag(surface, info.restore_rect.top_left, surface->top_left(), tile); break; case mir_surface_state_maximized: surface->resize(tile.size); drag(surface, tile.top_left, surface->top_left(), tile); break; case mir_surface_state_horizmaximized: surface->resize({tile.size.width, info.restore_rect.size.height}); drag(surface, {tile.top_left.x, info.restore_rect.top_left.y}, surface->top_left(), tile); break; case mir_surface_state_vertmaximized: surface->resize({info.restore_rect.size.width, tile.size.height}); drag(surface, {info.restore_rect.top_left.x, tile.top_left.y}, surface->top_left(), tile); break; default: break; } return info.state = value; } void me::TilingWindowManagerPolicy::drag(Point cursor) { if (auto const session = session_under(cursor)) { if (session == session_under(old_cursor)) { if (auto const surface = select_active_surface(session, tools->surface_at(old_cursor))) { drag(surface, cursor, old_cursor, tools->info_for(session).tile); } } } } void me::TilingWindowManagerPolicy::handle_raise_surface( std::shared_ptr const& session, std::shared_ptr const& surface) { select_active_surface(session, surface); } bool me::TilingWindowManagerPolicy::handle_keyboard_event(MirKeyboardEvent const* event) { auto const action = mir_keyboard_event_action(event); auto const scan_code = mir_keyboard_event_scan_code(event); auto const modifiers = mir_keyboard_event_modifiers(event) & modifier_mask; if (action == mir_keyboard_action_down && scan_code == KEY_F11) { switch (modifiers & modifier_mask) { case mir_input_event_modifier_alt: toggle(mir_surface_state_maximized); return true; case mir_input_event_modifier_shift: toggle(mir_surface_state_vertmaximized); return true; case mir_input_event_modifier_ctrl: toggle(mir_surface_state_horizmaximized); return true; default: break; } } else if (action == mir_keyboard_action_down && scan_code == KEY_F4) { if (auto const session = tools->focused_session()) { switch (modifiers & modifier_mask) { case mir_input_event_modifier_alt: kill(session->process_id(), SIGTERM); return true; case mir_input_event_modifier_ctrl: if (auto const surf = session->default_surface()) { surf->request_client_surface_close(); return true; } default: break; } } } else if (action == mir_keyboard_action_down && modifiers == mir_input_event_modifier_alt && scan_code == KEY_TAB) { tools->focus_next_session(); select_active_surface(tools->focused_session(), tools->focused_surface()); return true; } else if (action == mir_keyboard_action_down && modifiers == mir_input_event_modifier_alt && scan_code == KEY_GRAVE) { if (auto const prev = tools->focused_surface()) { if (auto const app = tools->focused_session()) if (auto const surface = app->surface_after(prev)) { select_active_surface(app, surface); } } return true; } return false; } bool me::TilingWindowManagerPolicy::handle_touch_event(MirTouchEvent const* event) { auto const count = mir_touch_event_point_count(event); long total_x = 0; long total_y = 0; for (auto i = 0U; i != count; ++i) { total_x += mir_touch_event_axis_value(event, i, mir_touch_axis_x); total_y += mir_touch_event_axis_value(event, i, mir_touch_axis_y); } Point const cursor{total_x/count, total_y/count}; bool is_drag = true; for (auto i = 0U; i != count; ++i) { switch (mir_touch_event_action(event, i)) { case mir_touch_action_up: return false; case mir_touch_action_down: is_drag = false; case mir_touch_action_change: continue; } } bool consumes_event = false; if (is_drag) { switch (count) { case 2: resize(cursor); consumes_event = true; break; case 3: drag(cursor); consumes_event = true; break; } } old_cursor = cursor; return consumes_event; } bool me::TilingWindowManagerPolicy::handle_pointer_event(MirPointerEvent const* event) { auto const action = mir_pointer_event_action(event); auto const modifiers = mir_pointer_event_modifiers(event) & modifier_mask; Point const cursor{ mir_pointer_event_axis_value(event, mir_pointer_axis_x), mir_pointer_event_axis_value(event, mir_pointer_axis_y)}; bool consumes_event = false; if (action == mir_pointer_action_button_down) { click(cursor); } else if (action == mir_pointer_action_motion && modifiers == mir_input_event_modifier_alt) { if (mir_pointer_event_button_state(event, mir_pointer_button_primary)) { drag(cursor); consumes_event = true; } else if (mir_pointer_event_button_state(event, mir_pointer_button_tertiary)) { resize(cursor); consumes_event = true; } } old_cursor = cursor; return consumes_event; } void me::TilingWindowManagerPolicy::toggle(MirSurfaceState state) { if (auto const session = tools->focused_session()) { if (auto const surface = session->default_surface()) { if (surface->state() == state) state = mir_surface_state_restored; auto const value = handle_set_state(surface, MirSurfaceState(state)); surface->configure(mir_surface_attrib_state, value); } } } std::shared_ptr me::TilingWindowManagerPolicy::session_under(Point position) { return tools->find_session([&](SessionInfo const& info) { return info.tile.contains(position);}); } void me::TilingWindowManagerPolicy::update_tiles( SessionInfoMap& session_info, Rectangles const& displays) { if (session_info.size() < 1 || displays.size() < 1) return; auto const sessions = session_info.size(); auto const bounding_rect = displays.bounding_rectangle(); auto const total_width = bounding_rect.size.width.as_int(); auto const total_height = bounding_rect.size.height.as_int(); auto index = 0; for (auto& info : session_info) { auto const x = (total_width*index)/sessions; ++index; auto const dx = (total_width*index)/sessions - x; auto const old_tile = info.second.tile; Rectangle const new_tile{{x, 0}, {dx, total_height}}; update_surfaces(info.first, old_tile, new_tile); info.second.tile = new_tile; } } void me::TilingWindowManagerPolicy::update_surfaces(std::weak_ptr const& session, Rectangle const& old_tile, Rectangle const& new_tile) { auto displacement = new_tile.top_left - old_tile.top_left; auto& info = tools->info_for(session); for (auto const& ps : info.surfaces) { if (auto const surface = ps.lock()) { auto const old_pos = surface->top_left(); surface->move_to(old_pos + displacement); fit_to_new_tile(*surface, old_tile, new_tile); } } } void me::TilingWindowManagerPolicy::clip_to_tile(ms::SurfaceCreationParameters& parameters, Rectangle const& tile) { auto const displacement = parameters.top_left - tile.top_left; auto width = std::min(tile.size.width.as_int()-displacement.dx.as_int(), parameters.size.width.as_int()); auto height = std::min(tile.size.height.as_int()-displacement.dy.as_int(), parameters.size.height.as_int()); parameters.size = Size{width, height}; } void me::TilingWindowManagerPolicy::fit_to_new_tile(ms::Surface& surface, Rectangle const& old_tile, Rectangle const& new_tile) { auto const displacement = surface.top_left() - new_tile.top_left; // For now just scale if was filling width/height of tile auto const old_size = surface.size(); auto const scaled_width = old_size.width == old_tile.size.width ? new_tile.size.width : old_size.width; auto const scaled_height = old_size.height == old_tile.size.height ? new_tile.size.height : old_size.height; auto width = std::min(new_tile.size.width.as_int()-displacement.dx.as_int(), scaled_width.as_int()); auto height = std::min(new_tile.size.height.as_int()-displacement.dy.as_int(), scaled_height.as_int()); surface.resize({width, height}); } void me::TilingWindowManagerPolicy::drag(std::shared_ptr surface, Point to, Point from, Rectangle bounds) { if (surface && surface->input_area_contains(from)) { auto movement = to - from; constrained_move(surface, movement, bounds); for (auto const& child: tools->info_for(surface).children) { auto move = movement; constrained_move(child.lock(), move, bounds); } } } void me::TilingWindowManagerPolicy::constrained_move( std::shared_ptr const& surface, Displacement& movement, Rectangle const& bounds) { auto const top_left = surface->top_left(); auto const surface_size = surface->size(); auto const bottom_right = top_left + as_displacement(surface_size); if (movement.dx < DeltaX{0}) movement.dx = std::max(movement.dx, (bounds.top_left - top_left).dx); if (movement.dy < DeltaY{0}) movement.dy = std::max(movement.dy, (bounds.top_left - top_left).dy); if (movement.dx > DeltaX{0}) movement.dx = std::min(movement.dx, (bounds.bottom_right() - bottom_right).dx); if (movement.dy > DeltaY{0}) movement.dy = std::min(movement.dy, (bounds.bottom_right() - bottom_right).dy); auto new_pos = surface->top_left() + movement; surface->move_to(new_pos); } void me::TilingWindowManagerPolicy::resize(std::shared_ptr surface, Point cursor, Point old_cursor, Rectangle bounds) { if (surface && surface->input_area_contains(old_cursor)) { auto const top_left = surface->top_left(); auto const old_displacement = old_cursor - top_left; auto const new_displacement = cursor - top_left; auto const scale_x = new_displacement.dx.as_float()/std::max(1.0f, old_displacement.dx.as_float()); auto const scale_y = new_displacement.dy.as_float()/std::max(1.0f, old_displacement.dy.as_float()); if (scale_x <= 0.0f || scale_y <= 0.0f) return; auto const old_size = surface->size(); Size new_size{scale_x*old_size.width, scale_y*old_size.height}; auto const size_limits = as_size(bounds.bottom_right() - top_left); if (new_size.width > size_limits.width) new_size.width = size_limits.width; if (new_size.height > size_limits.height) new_size.height = size_limits.height; surface->resize(new_size); } } std::shared_ptr me::TilingWindowManagerPolicy::select_active_surface(std::shared_ptr const& session, std::shared_ptr const& surface) { if (!surface) { tools->set_focus_to({}, {}); return surface; } auto const& info_for = tools->info_for(surface); if (info_for.can_be_active()) { tools->set_focus_to(session, surface); tools->raise_tree(surface); return surface; } else { // Cannot have input focus - try the parent if (auto const parent = info_for.parent.lock()) return select_active_surface(session, parent); return {}; } } ./src/platforms/mirserver/wm-wip/server_example_tiling_window_manager.h0000644000015600001650000001155612677054330026771 0ustar jenkinsjenkins/* * 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_TILING_WINDOW_MANAGER_H_ #define MIR_EXAMPLE_TILING_WINDOW_MANAGER_H_ #include "server_example_basic_window_manager.h" ///\example server_example_tiling_window_manager.h /// Demonstrate implementing a simple tiling algorithm namespace mir { namespace examples { // simple tiling algorithm: // o Switch apps: tap or click on the corresponding tile // o Move window: Alt-leftmousebutton drag (three finger drag) // o Resize window: Alt-middle_button drag (two finger drag) // o Maximize/restore current window (to tile size): Alt-F11 // o Maximize/restore current window (to tile height): Shift-F11 // o Maximize/restore current window (to tile width): Ctrl-F11 // o client requests to maximize, vertically maximize & restore class TilingWindowManagerPolicy : public WindowManagementPolicy { public: explicit TilingWindowManagerPolicy(WindowManagerTools* const tools); void click(geometry::Point cursor); void handle_session_info_updated(SessionInfoMap& session_info, geometry::Rectangles const& displays); void handle_displays_updated(SessionInfoMap& session_info, geometry::Rectangles const& displays); void resize(geometry::Point cursor); auto handle_place_new_surface( std::shared_ptr const& session, scene::SurfaceCreationParameters const& request_parameters) -> scene::SurfaceCreationParameters; void handle_new_surface(std::shared_ptr const& session, std::shared_ptr const& surface); void handle_modify_surface( std::shared_ptr const& session, std::shared_ptr const& surface, shell::SurfaceSpecification const& modifications); void handle_delete_surface(std::shared_ptr const& session, std::weak_ptr const& surface); int handle_set_state(std::shared_ptr const& surface, MirSurfaceState value); void drag(geometry::Point cursor); bool handle_keyboard_event(MirKeyboardEvent const* event); bool handle_touch_event(MirTouchEvent const* event); bool handle_pointer_event(MirPointerEvent const* event); void handle_raise_surface( std::shared_ptr const& session, std::shared_ptr const& surface); void generate_decorations_for( std::shared_ptr const& session, std::shared_ptr const& surface, SurfaceInfoMap& surface_info, std::function const&, scene::SurfaceCreationParameters const&)> const& build); private: static const int modifier_mask = mir_input_event_modifier_alt | mir_input_event_modifier_shift | mir_input_event_modifier_sym | mir_input_event_modifier_ctrl | mir_input_event_modifier_meta; void toggle(MirSurfaceState state); std::shared_ptr session_under(geometry::Point position); void update_tiles( SessionInfoMap& session_info, geometry::Rectangles const& displays); void update_surfaces(std::weak_ptr const& session, geometry::Rectangle const& old_tile, geometry::Rectangle const& new_tile); static void clip_to_tile(scene::SurfaceCreationParameters& parameters, geometry::Rectangle const& tile); static void fit_to_new_tile(scene::Surface& surface, geometry::Rectangle const& old_tile, geometry::Rectangle const& new_tile); void drag(std::shared_ptr surface, geometry::Point to, geometry::Point from, geometry::Rectangle bounds); static void resize(std::shared_ptr surface, geometry::Point cursor, geometry::Point old_cursor, geometry::Rectangle bounds); static void constrained_move(std::shared_ptr const& surface, geometry::Displacement& movement, geometry::Rectangle const& bounds); std::shared_ptr select_active_surface(std::shared_ptr const& session, std::shared_ptr const& surface); WindowManagerTools* const tools; geometry::Point old_cursor{}; }; using TilingWindowManager = WindowManagerBuilder; } } #endif /* MIR_EXAMPLE_TILING_WINDOW_MANAGER_H_ */ ./src/platforms/mirserver/wm-wip/CMakeLists.txt0000644000015600001650000000114412677054330021700 0ustar jenkinsjenkinsset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fPIC") add_library(experimentalwindowmanager STATIC server_example_basic_window_manager.cpp server_example_basic_window_manager.h server_example_window_management_info.cpp server_example_window_management_info.h ) if (EXAMPLE_MIR_WINDOW_MANAGERS) add_library(examplewindowmanager STATIC server_example_canonical_window_manager.h server_example_tiling_window_manager.h server_example_window_management.h server_example_canonical_window_manager.cpp server_example_tiling_window_manager.cpp server_example_window_management.cpp ) endif() ./src/platforms/mirserver/wm-wip/server_example_window_management.h0000644000015600001650000000165412677054330026123 0ustar jenkinsjenkins/* * 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: Alan Griffiths */ #ifndef MIR_EXAMPLES_WINDOW_MANAGEMENT_H_ #define MIR_EXAMPLES_WINDOW_MANAGEMENT_H_ namespace mir { class Server; namespace examples { void add_window_manager_option_to(Server& server); } } // namespace mir #endif // MIR_EXAMPLES_WINDOW_MANAGEMENT_H_ ./src/platforms/mirserver/wm-wip/server_example_window_management_info.cpp0000644000015600001650000002737512677054330027501 0ustar jenkinsjenkins/* * 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 "server_example_window_management_info.h" #include "mir/scene/surface.h" #include "mir/scene/surface_creation_parameters.h" #include "mir/graphics/buffer.h" #include namespace me = mir::examples; namespace ms = mir::scene; namespace mg = mir::graphics; using namespace mir::geometry; me::SurfaceInfo::SurfaceInfo( std::shared_ptr const& session, std::shared_ptr const& surface, scene::SurfaceCreationParameters const& params) : type{surface->type()}, state{surface->state()}, restore_rect{surface->top_left(), surface->size()}, session{session}, parent{params.parent}, min_width{params.min_width.is_set() ? params.min_width.value() : Width{}}, min_height{params.min_height.is_set() ? params.min_height.value() : Height{}}, max_width{params.max_width.is_set() ? params.max_width.value() : Width{std::numeric_limits::max()}}, max_height{params.max_height.is_set() ? params.max_height.value() : Height{std::numeric_limits::max()}}, width_inc{params.width_inc}, height_inc{params.height_inc}, min_aspect{params.min_aspect}, max_aspect{params.max_aspect} { if (params.output_id != mir::graphics::DisplayConfigurationOutputId{0}) output_id = params.output_id; } bool me::SurfaceInfo::can_be_active() const { switch (type) { case mir_surface_type_normal: /**< AKA "regular" */ case mir_surface_type_utility: /**< AKA "floating" */ case mir_surface_type_dialog: case mir_surface_type_satellite: /**< AKA "toolbox"/"toolbar" */ case mir_surface_type_freestyle: case mir_surface_type_menu: case mir_surface_type_inputmethod: /**< AKA "OSK" or handwriting etc. */ return true; case mir_surface_type_gloss: case mir_surface_type_tip: /**< AKA "tooltip" */ default: // Cannot have input focus return false; } } bool me::SurfaceInfo::must_have_parent() const { switch (type) { case mir_surface_type_overlay:; case mir_surface_type_inputmethod: case mir_surface_type_satellite: case mir_surface_type_tip: return true; default: return false; } } bool me::SurfaceInfo::can_morph_to(MirSurfaceType new_type) const { switch (new_type) { case mir_surface_type_normal: case mir_surface_type_utility: case mir_surface_type_satellite: switch (type) { case mir_surface_type_normal: case mir_surface_type_utility: case mir_surface_type_dialog: case mir_surface_type_satellite: return true; default: break; } break; case mir_surface_type_dialog: switch (type) { case mir_surface_type_normal: case mir_surface_type_utility: case mir_surface_type_dialog: case mir_surface_type_popover: case mir_surface_type_satellite: return true; default: break; } break; default: break; } return false; } bool me::SurfaceInfo::must_not_have_parent() const { switch (type) { case mir_surface_type_normal: case mir_surface_type_utility: return true; default: return false; } } bool me::SurfaceInfo::is_visible() const { switch (state) { case mir_surface_state_hidden: case mir_surface_state_minimized: return false; default: break; } return true; } struct mir::examples::SurfaceInfo::StreamPainter { virtual void paint(int) = 0; virtual ~StreamPainter() = default; StreamPainter() = default; StreamPainter(StreamPainter const&) = delete; StreamPainter& operator=(StreamPainter const&) = delete; }; struct mir::examples::SurfaceInfo::SwappingPainter : mir::examples::SurfaceInfo::StreamPainter { SwappingPainter(std::shared_ptr const& buffer_stream) : buffer_stream{buffer_stream}, buffer{nullptr} { swap_buffers(); } void swap_buffers() { auto const callback = [this](mir::graphics::Buffer* new_buffer) { buffer.store(new_buffer); }; buffer_stream->swap_buffers(buffer, callback); } void paint(int intensity) override { if (auto const buf = buffer.load()) { auto const format = buffer_stream->pixel_format(); auto const sz = buf->size().height.as_int() * buf->size().width.as_int() * MIR_BYTES_PER_PIXEL(format); std::vector pixels(sz, intensity); buf->write(pixels.data(), sz); swap_buffers(); } } std::shared_ptr const buffer_stream; std::atomic buffer; }; struct mir::examples::SurfaceInfo::AllocatingPainter : mir::examples::SurfaceInfo::StreamPainter { AllocatingPainter(std::shared_ptr const& buffer_stream, Size size) : buffer_stream(buffer_stream), properties({ size, buffer_stream->pixel_format(), mg::BufferUsage::software }), front_buffer(buffer_stream->allocate_buffer(properties)), back_buffer(buffer_stream->allocate_buffer(properties)) { } void paint(int intensity) override { buffer_stream->with_buffer(back_buffer, [this, intensity](graphics::Buffer& buffer) { auto const format = buffer.pixel_format(); auto const sz = buffer.size().height.as_int() * buffer.size().width.as_int() * MIR_BYTES_PER_PIXEL(format); std::vector pixels(sz, intensity); buffer.write(pixels.data(), sz); buffer_stream->swap_buffers(&buffer, [](mg::Buffer*){}); }); std::swap(front_buffer, back_buffer); } ~AllocatingPainter() { buffer_stream->remove_buffer(front_buffer); buffer_stream->remove_buffer(back_buffer); } std::shared_ptr const buffer_stream; mg::BufferProperties properties; mg::BufferID front_buffer; mg::BufferID back_buffer; }; void mir::examples::SurfaceInfo::init_titlebar(std::shared_ptr const& surface) { auto stream = surface->primary_buffer_stream(); try { stream_painter = std::make_shared(stream, surface->size()); } catch (...) { stream_painter = std::make_shared(stream); } } void mir::examples::SurfaceInfo::paint_titlebar(int intensity) { stream_painter->paint(intensity); } void me::SurfaceInfo::constrain_resize( std::shared_ptr const& surface, Point& requested_pos, Size& requested_size, bool const left_resize, bool const top_resize, Rectangle const& /*bounds*/) const { Point new_pos = requested_pos; Size new_size = requested_size; if (min_aspect.is_set()) { auto const ar = min_aspect.value(); auto const error = new_size.height.as_int()*long(ar.width) - new_size.width.as_int()*long(ar.height); if (error > 0) { // Add (denominator-1) to numerator to ensure rounding up auto const width_correction = (error+(ar.height-1))/ar.height; auto const height_correction = (error+(ar.width-1))/ar.width; if (width_correction < height_correction) { new_size.width = new_size.width + DeltaX(width_correction); } else { new_size.height = new_size.height - DeltaY(height_correction); } } } if (max_aspect.is_set()) { auto const ar = max_aspect.value(); auto const error = new_size.width.as_int()*long(ar.height) - new_size.height.as_int()*long(ar.width); if (error > 0) { // Add (denominator-1) to numerator to ensure rounding up auto const height_correction = (error+(ar.width-1))/ar.width; auto const width_correction = (error+(ar.height-1))/ar.height; if (width_correction < height_correction) { new_size.width = new_size.width - DeltaX(width_correction); } else { new_size.height = new_size.height + DeltaY(height_correction); } } } if (min_width > new_size.width) new_size.width = min_width; if (min_height > new_size.height) new_size.height = min_height; if (max_width < new_size.width) new_size.width = max_width; if (max_height < new_size.height) new_size.height = max_height; if (width_inc.is_set()) { auto const width = new_size.width.as_int() - min_width.as_int(); auto inc = width_inc.value().as_int(); if (width % inc) new_size.width = min_width + DeltaX{inc*(((2L*width + inc)/2)/inc)}; } if (height_inc.is_set()) { auto const height = new_size.height.as_int() - min_height.as_int(); auto inc = height_inc.value().as_int(); if (height % inc) new_size.height = min_height + DeltaY{inc*(((2L*height + inc)/2)/inc)}; } if (left_resize) new_pos.x += new_size.width - requested_size.width; if (top_resize) new_pos.y += new_size.height - requested_size.height; // placeholder - constrain onscreen switch (state) { case mir_surface_state_restored: break; // "A vertically maximised surface is anchored to the top and bottom of // the available workspace and can have any width." case mir_surface_state_vertmaximized: new_pos.y = surface->top_left().y; new_size.height = surface->size().height; break; // "A horizontally maximised surface is anchored to the left and right of // the available workspace and can have any height" case mir_surface_state_horizmaximized: new_pos.x = surface->top_left().x; new_size.width = surface->size().width; break; // "A maximised surface is anchored to the top, bottom, left and right of the // available workspace. For example, if the launcher is always-visible then // the left-edge of the surface is anchored to the right-edge of the launcher." case mir_surface_state_maximized: default: new_pos.x = surface->top_left().x; new_pos.y = surface->top_left().y; new_size.width = surface->size().width; new_size.height = surface->size().height; break; } requested_pos = new_pos; requested_size = new_size; } bool me::SurfaceInfo::needs_titlebar(MirSurfaceType type) { switch (type) { case mir_surface_type_freestyle: case mir_surface_type_menu: case mir_surface_type_inputmethod: case mir_surface_type_gloss: case mir_surface_type_tip: // No decorations for these surface types return false; default: return true; } } ./src/platforms/mirserver/clipboard.h0000644000015600001650000000506112677054330020032 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QTMIR_CLIPBOARD_H #define QTMIR_CLIPBOARD_H #include #include namespace qtmir { class DBusClipboard : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.canonical.QtMir.Clipboard") public: DBusClipboard(QObject *parent = nullptr); virtual ~DBusClipboard() {} void setContents(QByteArray contents); const QByteArray &contents() const { return m_contents; } static const int maxContentsSize = 4 * 1024 * 1024; // 4 Mb // To make it testable static bool skipDBusRegistration; Q_SIGNALS: Q_SCRIPTABLE void ContentsChanged(const QByteArray &contents); void contentsChangedRemotely(); public Q_SLOTS: Q_SCRIPTABLE QByteArray GetContents() const; Q_SCRIPTABLE void SetContents(QByteArray contents); private: void performDBusRegistration(); bool setContentsHelper(QByteArray newContents); // Contains a serialized QMimeData // Serialization and deserialization is done by the QPlatformClipboard // implementation. QByteArray m_contents; }; class Clipboard : public QObject, public QPlatformClipboard { Q_OBJECT public: Clipboard(QObject *parent = nullptr); virtual ~Clipboard() {} QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; void setMimeData(QMimeData *data, QClipboard::Mode mode) override; void setupDBusService(); private Q_SLOTS: void setMimeDataWithDBusClibpboardContents(); private: DBusClipboard *m_dbusClipboard; }; // NB: Copied from qtubuntu. Must be kept in sync with the original version! // Best thing would be to share this code somehow, but not bothering with it right now // as the clipboard will move to content-hub at some point. QByteArray serializeMimeData(QMimeData *mimeData); QMimeData *deserializeMimeData(const QByteArray &serializedMimeData); } // namespace qtmir #endif // QTMIR_CLIPBOARD_H ./src/platforms/mirserver/CMakeLists.txt0000644000015600001650000000606312677054330020465 0ustar jenkinsjenkins# Qt5PlatformSupport is not available as a cmake module. pkg_check_modules(QT5PLATFORM_SUPPORT Qt5PlatformSupport REQUIRED) # Hacks for the QPA privates monster. pkg_check_modules(FONTCONFIG fontconfig REQUIRED) add_definitions(-DQ_FONTCONFIGDATABASE) #set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -fPIC") # Dirty hack: The private QtPlatformSupport include dirs are not made available via # a cmake module or the pkgconfig file (which only exposes the necessary linker flags) # So we generate the paths ourselves from the Qt5Gui private dirs! set(QT5_PLATFORMSUPPORT_INCLUDE_DIRS) foreach(item ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(newitem "") string(REGEX REPLACE "QtGui" "QtPlatformSupport" newitem ${item}) list(APPEND QT5_PLATFORMSUPPORT_INCLUDE_DIRS ${newitem}) endforeach(item ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) include_directories( ${CMAKE_SOURCE_DIR}/src/common ${MIRCOMMON_INCLUDE_DIRS} ${MIRSERVER_INCLUDE_DIRS} ${MIRRENDERERGLDEV_INCLUDE_DIRS} ${URL_DISPATCHER_INCLUDE_DIRS} ${EGL_INCLUDE_DIRS} ${LTTNG_INCLUDE_DIRS} ${QT5PLATFORM_SUPPORT_INCLUDE_DIRS} ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${QT5_PLATFORMSUPPORT_INCLUDE_DIRS} ${Qt5Quick_PRIVATE_INCLUDE_DIRS} ${APPLICATION_API_INCLUDE_DIRS} ) add_subdirectory(wm-wip) # We have to remove -pedantic for tracepoints.c string (REPLACE " -pedantic " " " CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) # Needed to compile tracepoints in C99 mode. add_definitions(-DBYTE_ORDER=__BYTE_ORDER) set(MIRSERVER_QPA_PLUGIN_SRC ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp ${CMAKE_SOURCE_DIR}/src/common/timestamp.cpp cursor.cpp mircursorimages.cpp mirwindowmanager.cpp mirsingleton.cpp qteventfeeder.cpp plugin.cpp qmirserver.cpp qmirserver_p.cpp sessionauthorizer.cpp sessionlistener.cpp surfaceobserver.cpp promptsessionlistener.cpp mirserver.cpp mirserverstatuslistener.cpp screen.cpp screencontroller.cpp screenwindow.cpp mirserverintegration.cpp miropenglcontext.cpp nativeinterface.cpp offscreensurface.cpp qtcompositor.cpp services.cpp ubuntutheme.cpp clipboard.cpp creationhints.cpp tileddisplayconfigurationpolicy.cpp tracepoints.c # We need to run moc on these headers ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/Mir.h ) add_library(qpa-mirserver SHARED ${MIRSERVER_QPA_PLUGIN_SRC} ) target_link_libraries( qpa-mirserver ${MIRSERVER_LDFLAGS} ${MIRCLIENT_LDFLAGS} ${URL_DISPATCHER_LDFLAGS} ${EGL_LDFLAGS} ${GL_LIBRARIES} ${LTTNG_LIBRARIES} ${QT5PLATFORM_SUPPORT_LDFLAGS} # TODO Qt5Platform support LDFLAGS dont provide actual required ldflags... # I found these were needed...perhaps there is some way to query qmake/qconfig? -lfreetype ${GIO_LDFLAGS} ${FONTCONFIG_LDFLAGS} ${XKBCOMMON_LIBRARIES} Qt5::Core Qt5::DBus Qt5::Quick Qt5::Sensors ) include(UseLttngGenTp) add_lttng_gen_tp(NAME tracepoints) install(TARGETS qpa-mirserver LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/qt5/plugins/platforms") ./src/platforms/mirserver/offscreensurface.cpp0000644000015600001650000000305012677054330021745 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "offscreensurface.h" #include "mirserver.h" // Mir #include #include //Qt #include #include #include #include namespace mg = mir::graphics; OffscreenSurface::OffscreenSurface(QOffscreenSurface *offscreenSurface) : QPlatformOffscreenSurface(offscreenSurface) , m_buffer(nullptr) , m_format(offscreenSurface->requestedFormat()) { } QSurfaceFormat OffscreenSurface::format() const { return m_format; } bool OffscreenSurface::isValid() const { if (m_buffer) { return m_buffer->isValid(); } return false; } QOpenGLFramebufferObject* OffscreenSurface::buffer() const { return m_buffer; } void OffscreenSurface::setBuffer(QOpenGLFramebufferObject *buffer) { m_buffer = buffer; } ./src/platforms/mirserver/services.h0000644000015600001650000000176112677054330017721 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIRSERVER_SERVICES_H #define MIRSERVER_SERVICES_H #include class Services : public QPlatformServices { public: bool openUrl(const QUrl &url) override; bool openDocument(const QUrl &url) override; private: bool callDispatcher(const QUrl &url); }; #endif // MIRSERVER_SERVICES_H ./src/platforms/mirserver/nativeinterface.h0000644000015600001650000000214012677054330021235 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef NATIVEINTEGRATION_H #define NATIVEINTEGRATION_H // qt #include #include // local #include "mirserver.h" class NativeInterface : public QPlatformNativeInterface { public: NativeInterface(const QWeakPointer &); virtual void *nativeResourceForIntegration(const QByteArray &resource); QWeakPointer m_mirServer; }; #endif // NATIVEINTEGRATION_H ./src/platforms/mirserver/surfaceobserver.cpp0000644000015600001650000001473112677054330021632 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "surfaceobserver.h" #include "namedcursor.h" #include #include #include #include #include #include #include QMap SurfaceObserver::m_surfaceToObserverMap; QMutex SurfaceObserver::mutex; SurfaceObserver::SurfaceObserver() : m_listener(nullptr) , m_framesPosted(false) { m_cursorNameToShape["left_ptr"] = Qt::ArrowCursor; m_cursorNameToShape["up_arrow"] = Qt::UpArrowCursor; m_cursorNameToShape["cross"] = Qt::CrossCursor; m_cursorNameToShape["watch"] = Qt::WaitCursor; m_cursorNameToShape["xterm"] = Qt::IBeamCursor; m_cursorNameToShape["size_ver"] = Qt::SizeVerCursor; m_cursorNameToShape["size_hor"] = Qt::SizeHorCursor; m_cursorNameToShape["size_bdiag"] = Qt::SizeBDiagCursor; m_cursorNameToShape["size_fdiag"] = Qt::SizeFDiagCursor; m_cursorNameToShape["size_all"] = Qt::SizeAllCursor; m_cursorNameToShape["blank"] = Qt::BlankCursor; m_cursorNameToShape["split_v"] = Qt::SplitVCursor; m_cursorNameToShape["split_h"] = Qt::SplitHCursor; m_cursorNameToShape["hand"] = Qt::PointingHandCursor; m_cursorNameToShape["forbidden"] = Qt::ForbiddenCursor; m_cursorNameToShape["whats_this"] = Qt::WhatsThisCursor; m_cursorNameToShape["left_ptr_watch"] = Qt::BusyCursor; m_cursorNameToShape["openhand"] = Qt::OpenHandCursor; m_cursorNameToShape["closedhand"] = Qt::ClosedHandCursor; m_cursorNameToShape["dnd-copy"] = Qt::DragCopyCursor; m_cursorNameToShape["dnd-move"] = Qt::DragMoveCursor; m_cursorNameToShape["dnd-link"] = Qt::DragLinkCursor; qRegisterMetaType("MirShellChrome"); } SurfaceObserver::~SurfaceObserver() { QMutexLocker locker(&mutex); QMutableMapIterator i(m_surfaceToObserverMap); while (i.hasNext()) { i.next(); if (i.value() == this) { i.remove(); return; } } } void SurfaceObserver::setListener(QObject *listener) { m_listener = listener; if (m_framesPosted) { Q_EMIT framesPosted(); } } void SurfaceObserver::frame_posted(int /*frames_available*/, mir::geometry::Size const& /*size*/) { m_framesPosted = true; if (m_listener) { Q_EMIT framesPosted(); } } void SurfaceObserver::renamed(char const * name) { Q_EMIT nameChanged(QString::fromUtf8(name)); } void SurfaceObserver::cursor_image_removed() { Q_EMIT cursorChanged(QCursor()); } void SurfaceObserver::attrib_changed(MirSurfaceAttrib attribute, int value) { if (m_listener) { Q_EMIT attributeChanged(attribute, value); } } void SurfaceObserver::resized_to(mir::geometry::Size const&size) { Q_EMIT resized(QSize(size.width.as_int(), size.height.as_int())); } void SurfaceObserver::notifySurfaceModifications(const mir::shell::SurfaceSpecification &modifications) { if (modifications.min_width.is_set()) { Q_EMIT minimumWidthChanged(modifications.min_width.value().as_int()); } if (modifications.min_height.is_set()) { Q_EMIT minimumHeightChanged(modifications.min_height.value().as_int()); } if (modifications.max_width.is_set()) { Q_EMIT maximumWidthChanged(modifications.max_width.value().as_int()); } if (modifications.max_height.is_set()) { Q_EMIT maximumHeightChanged(modifications.max_height.value().as_int()); } if (modifications.width_inc.is_set()) { Q_EMIT widthIncrementChanged(modifications.width_inc.value().as_int()); } if (modifications.height_inc.is_set()) { Q_EMIT heightIncrementChanged(modifications.height_inc.value().as_int()); } if (modifications.shell_chrome.is_set()) { Q_EMIT shellChromeChanged(modifications.shell_chrome.value()); } } SurfaceObserver *SurfaceObserver::observerForSurface(const mir::scene::Surface *surface) { if (m_surfaceToObserverMap.contains(surface)) { return m_surfaceToObserverMap.value(surface); } else { return nullptr; } } void SurfaceObserver::registerObserverForSurface(SurfaceObserver *observer, const mir::scene::Surface *surface) { QMutexLocker locker(&mutex); m_surfaceToObserverMap[surface] = observer; } void SurfaceObserver::cursor_image_set_to(const mir::graphics::CursorImage &cursorImage) { QCursor qcursor = createQCursorFromMirCursorImage(cursorImage); Q_EMIT cursorChanged(qcursor); } void SurfaceObserver::keymap_changed(MirInputDeviceId, const std::string &, const std::string &layout, const std::string &variant, const std::string &) { Q_EMIT keymapChanged(QString::fromStdString(layout), QString::fromStdString(variant)); } QCursor SurfaceObserver::createQCursorFromMirCursorImage(const mir::graphics::CursorImage &cursorImage) { if (cursorImage.as_argb_8888() == nullptr) { // Must be a named cursor auto namedCursor = dynamic_cast(&cursorImage); Q_ASSERT(namedCursor != nullptr); if (namedCursor) { // NB: If we need a named cursor not covered by Qt::CursorShape, we won't be able to // used Qt's cursor API anymore for transmitting MirSurface's cursor image. Qt::CursorShape cursorShape = m_cursorNameToShape.value(namedCursor->name(), Qt::ArrowCursor); return QCursor(cursorShape); } else { // shouldn't happen return QCursor(); } } else { QImage image((const uchar*)cursorImage.as_argb_8888(), cursorImage.size().width.as_int(), cursorImage.size().height.as_int(), QImage::Format_ARGB32); return QCursor(QPixmap::fromImage(image), cursorImage.hotspot().dx.as_int(), cursorImage.hotspot().dy.as_int()); } } ./src/platforms/mirserver/argvHelper.h0000644000015600001650000000414612677054330020175 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef ARGVHELPER_P_H #define ARGVHELPER_P_H #include namespace qtmir { // Function to edit argv to strip out unmatched entries of targetArray, so both arrays have identical contents // Note: Mir parses out arguments that it understands, but it also removes argv[0] (bug pad.lv/1511509) // so need to ensure argv[0] is the binary name as usual. void editArgvToMatch(int &argcToEdit, char** argvToEdit, int targetCount, const char* const targetArray[]) { // Make copy of the argv array of pointers, as we will be editing the original const size_t arraySize = (argcToEdit + 1) * sizeof(char*); char** argvCopy = static_cast(malloc(arraySize)); memcpy(argvCopy, argvToEdit, arraySize); int k=1; // index of argv we want to edit - note we'll leave argv[0] alone for (int i=0; i(argvCopy[j]); // edit argv to position that argument to match argv2. k++; break; } } } assert(k == targetCount+1); argvToEdit[k] = nullptr; free(argvCopy); argcToEdit = targetCount+1; // now argv and targetArray should have list the same strings. } } // namespace qtmir #endif // ARGVHELPER_P_H ./src/platforms/mirserver/mirglconfig.h0000644000015600001650000000172712677054330020400 0ustar jenkinsjenkins/* * Copyright (C) 2014 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QPA_MIR_GL_CONFIG_H #define QPA_MIR_GL_CONFIG_H #include class MirGLConfig : public mir::graphics::GLConfig { public: int depth_buffer_bits() const override { return 24; } int stencil_buffer_bits() const override { return 8; } }; #endif // QPA_MIR_GL_CONFIG_H ./src/platforms/mirserver/surfaceobserver.h0000644000015600001650000000655612677054330021305 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef SESSIONOBSERVER_H #define SESSIONOBSERVER_H #include #include #include #include #include #include #include #include namespace mir { namespace scene { class Surface; } namespace shell { class SurfaceSpecification; } } class SurfaceObserver : public QObject, public mir::scene::SurfaceObserver { Q_OBJECT public: SurfaceObserver(); virtual ~SurfaceObserver(); void setListener(QObject *listener); void attrib_changed(MirSurfaceAttrib, int) override; void resized_to(mir::geometry::Size const&) override; void moved_to(mir::geometry::Point const&) override {} void hidden_set_to(bool) override {} // Get new frame notifications from Mir, called from a Mir thread. void frame_posted(int frames_available, mir::geometry::Size const& size ) override; void alpha_set_to(float) override {} void transformation_set_to(glm::mat4 const&) override {} void reception_mode_set_to(mir::input::InputReceptionMode) override {} void cursor_image_set_to(mir::graphics::CursorImage const&) override; void orientation_set_to(MirOrientation) override {} void client_surface_close_requested() override {} void keymap_changed(MirInputDeviceId, std::string const& model, std::string const& layout, std::string const& variant, std::string const& options) override; void renamed(char const * name) override; void cursor_image_removed() override; void notifySurfaceModifications(const mir::shell::SurfaceSpecification&); static SurfaceObserver *observerForSurface(const mir::scene::Surface *surface); static void registerObserverForSurface(SurfaceObserver *observer, const mir::scene::Surface *surface); static QMutex mutex; Q_SIGNALS: void attributeChanged(const MirSurfaceAttrib attribute, const int value); void framesPosted(); void resized(const QSize &size); void keymapChanged(const QString &rules, const QString &variant); void nameChanged(const QString &name); void cursorChanged(const QCursor &cursor); void minimumWidthChanged(int); void minimumHeightChanged(int); void maximumWidthChanged(int); void maximumHeightChanged(int); void widthIncrementChanged(int); void heightIncrementChanged(int); void shellChromeChanged(MirShellChrome); private: QCursor createQCursorFromMirCursorImage(const mir::graphics::CursorImage &cursorImage); QObject *m_listener; bool m_framesPosted; QMap m_cursorNameToShape; static QMap m_surfaceToObserverMap; }; #endif ./src/platforms/mirserver/tileddisplayconfigurationpolicy.h0000644000015600001650000000235212677054330024572 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef TILEDDISPLAYCONFIGURATIONPOLICY_H #define TILEDDISPLAYCONFIGURATIONPOLICY_H #include #include class TiledDisplayConfigurationPolicy : public mir::graphics::DisplayConfigurationPolicy { public: TiledDisplayConfigurationPolicy(const std::shared_ptr &wrapped); void apply_to(mir::graphics::DisplayConfiguration& conf) override; private: const std::shared_ptr m_wrapped; }; #endif // TILEDDISPLAYCONFIGURATIONPOLICY_H ./src/platforms/mirserver/qteventfeeder.h0000644000015600001650000000727412677054330020744 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIR_QT_EVENT_FEEDER_H #define MIR_QT_EVENT_FEEDER_H #include #include #include class QTouchDevice; class ScreenController; /* Fills Qt's event loop with input events from Mir */ class QtEventFeeder : public mir::input::InputDispatcher { public: // Interface between QtEventFeeder and the actual QWindowSystemInterface functions // and other related Qt methods and objects to enable replacing them with mocks in // pure unit tests. class QtWindowSystemInterface { public: virtual ~QtWindowSystemInterface() {} virtual void setScreenController(const QSharedPointer &sc) = 0; virtual QWindow* getWindowForTouchPoint(const QPoint &point) = 0; virtual QWindow* focusedWindow() = 0; virtual void registerTouchDevice(QTouchDevice *device) = 0; virtual void handleExtendedKeyEvent(QWindow *window, ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, const QString& text = QString(), bool autorep = false, ushort count = 1) = 0; virtual void handleTouchEvent(QWindow *window, ulong timestamp, QTouchDevice *device, const QList &points, Qt::KeyboardModifiers mods = Qt::NoModifier) = 0; virtual void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) = 0; virtual void handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers mods) = 0; }; QtEventFeeder(const QSharedPointer &screenController); QtEventFeeder(const QSharedPointer &screenController, QtWindowSystemInterface *windowSystem); virtual ~QtEventFeeder(); static const int MirEventActionMask; static const int MirEventActionPointerIndexMask; static const int MirEventActionPointerIndexShift; bool dispatch(MirEvent const& event) override; void start() override; void stop() override; private: void dispatchKey(MirInputEvent const* event); void dispatchTouch(MirInputEvent const* event); void dispatchPointer(MirInputEvent const* event); void validateTouches(QWindow *window, ulong timestamp, QList &touchPoints); bool validateTouch(QWindowSystemInterface::TouchPoint &touchPoint); void sendActiveTouchRelease(QWindow *window, ulong timestamp, int id); QString touchesToString(const QList &points); QTouchDevice *mTouchDevice; QtWindowSystemInterface *mQtWindowSystem; // Maps the id of an active touch to its last known state QHash mActiveTouches; }; #endif // MIR_QT_EVENT_FEEDER_H ./src/platforms/mirserver/screen.cpp0000644000015600001650000003015012677054330017702 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ // local #include "screen.h" #include "logging.h" // Mir #include "mir/geometry/size.h" #include "mir/graphics/buffer.h" #include "mir/graphics/display_buffer.h" #include "mir/graphics/display.h" #include // Qt #include #include #include // Qt sensors #include #include namespace mg = mir::geometry; Q_LOGGING_CATEGORY(QTMIR_SENSOR_MESSAGES, "qtmir.sensor") namespace { bool isLittleEndian() { unsigned int i = 1; char *c = (char*)&i; return *c == 1; } static mir::renderer::gl::RenderTarget *as_render_target( mir::graphics::DisplayBuffer *displayBuffer) { auto const render_target = dynamic_cast( displayBuffer->native_display_buffer()); if (!render_target) throw std::logic_error("DisplayBuffer does not support GL rendering"); return render_target; } enum QImage::Format qImageFormatFromMirPixelFormat(MirPixelFormat mirPixelFormat) { switch (mirPixelFormat) { case mir_pixel_format_abgr_8888: if (isLittleEndian()) { // 0xRR,0xGG,0xBB,0xAA return QImage::Format_RGBA8888; } else { // 0xAA,0xBB,0xGG,0xRR qFatal("[mirserver QPA] " "Qt doesn't support mir_pixel_format_abgr_8888 in a big endian architecture"); } break; case mir_pixel_format_xbgr_8888: if (isLittleEndian()) { // 0xRR,0xGG,0xBB,0xXX return QImage::Format_RGBX8888; } else { // 0xXX,0xBB,0xGG,0xRR qFatal("[mirserver QPA] " "Qt doesn't support mir_pixel_format_xbgr_8888 in a big endian architecture"); } break; break; case mir_pixel_format_argb_8888: // 0xAARRGGBB return QImage::Format_ARGB32; break; case mir_pixel_format_xrgb_8888: // 0xffRRGGBB return QImage::Format_RGB32; break; case mir_pixel_format_bgr_888: qFatal("[mirserver QPA] Qt doesn't support mir_pixel_format_bgr_888"); break; default: qFatal("[mirserver QPA] Unknown mir pixel format"); break; } } } // namespace { class OrientationReadingEvent : public QEvent { public: OrientationReadingEvent(QEvent::Type type, QOrientationReading::Orientation orientation) : QEvent(type) , m_orientation(orientation) { } static const QEvent::Type m_type; QOrientationReading::Orientation m_orientation; }; const QEvent::Type OrientationReadingEvent::m_type = static_cast(QEvent::registerEventType()); bool Screen::skipDBusRegistration = false; Screen::Screen(const mir::graphics::DisplayConfigurationOutput &screen) : QObject(nullptr) , m_renderTarget(nullptr) , m_displayGroup(nullptr) , m_orientationSensor(new QOrientationSensor(this)) , m_screenWindow(nullptr) , m_unityScreen(nullptr) { setMirDisplayConfiguration(screen); // Set the default orientation based on the initial screen dimmensions. m_nativeOrientation = (m_geometry.width() >= m_geometry.height()) ? Qt::LandscapeOrientation : Qt::PortraitOrientation; qCDebug(QTMIR_SENSOR_MESSAGES) << "Screen - nativeOrientation is:" << m_nativeOrientation; // If it's a landscape device (i.e. some tablets), start in landscape, otherwise portrait m_currentOrientation = (m_nativeOrientation == Qt::LandscapeOrientation) ? Qt::LandscapeOrientation : Qt::PortraitOrientation; qCDebug(QTMIR_SENSOR_MESSAGES) << "Screen - initial currentOrientation is:" << m_currentOrientation; if (internalDisplay()) { // only enable orientation sensor for device-internal display QObject::connect(m_orientationSensor, &QOrientationSensor::readingChanged, this, &Screen::onOrientationReadingChanged); m_orientationSensor->start(); } if (!skipDBusRegistration) { // FIXME This is a unity8 specific dbus call and shouldn't be in qtmir m_unityScreen = new QDBusInterface("com.canonical.Unity.Screen", "/com/canonical/Unity/Screen", "com.canonical.Unity.Screen", QDBusConnection::systemBus(), this); m_unityScreen->connection().connect("com.canonical.Unity.Screen", "/com/canonical/Unity/Screen", "com.canonical.Unity.Screen", "DisplayPowerStateChange", this, SLOT(onDisplayPowerStateChanged(int, int))); } } Screen::~Screen() { //if a ScreenWindow associated with this screen, kill it if (m_screenWindow) { m_screenWindow->window()->destroy(); // ends up destroying m_ScreenWindow } } bool Screen::orientationSensorEnabled() { return m_orientationSensor->isActive(); } void Screen::onDisplayPowerStateChanged(int status, int reason) { Q_UNUSED(reason); if (internalDisplay()) { toggleSensors(status); } } void Screen::setMirDisplayConfiguration(const mir::graphics::DisplayConfigurationOutput &screen) { // Note: DisplayConfigurationOutput will be destroyed after this function returns // Output data - each output has a unique id and corresponding type. Can be multiple cards. m_outputId = screen.id; m_cardId = screen.card_id; m_type = screen.type; // Physical screen size m_physicalSize.setWidth(screen.physical_size_mm.width.as_float()); m_physicalSize.setHeight(screen.physical_size_mm.height.as_float()); // Pixel Format m_format = qImageFormatFromMirPixelFormat(screen.current_format); // Pixel depth m_depth = 8 * MIR_BYTES_PER_PIXEL(screen.current_format); // Power mode m_powerMode = screen.power_mode; QRect oldGeometry = m_geometry; // Position of screen in virtual desktop coordinate space m_geometry.setTop(screen.top_left.y.as_int()); m_geometry.setLeft(screen.top_left.x.as_int()); // Mode = current resolution & refresh rate mir::graphics::DisplayConfigurationMode mode = screen.modes.at(screen.current_mode_index); m_geometry.setWidth(mode.size.width.as_int()); m_geometry.setHeight(mode.size.height.as_int()); // DPI - unnecessary to calculate, default implementation in QPlatformScreen is sufficient // Check for Screen geometry change if (m_geometry != oldGeometry) { QWindowSystemInterface::handleScreenGeometryChange(this->screen(), m_geometry, m_geometry); if (m_screenWindow) { // resize corresponding window immediately m_screenWindow->setGeometry(m_geometry); } } // Refresh rate if (m_refreshRate != mode.vrefresh_hz) { m_refreshRate = mode.vrefresh_hz; QWindowSystemInterface::handleScreenRefreshRateChange(this->screen(), mode.vrefresh_hz); } } void Screen::toggleSensors(const bool enable) const { qCDebug(QTMIR_SENSOR_MESSAGES) << "Screen::toggleSensors - enable=" << enable; if (enable) { m_orientationSensor->start(); } else { m_orientationSensor->stop(); } } void Screen::customEvent(QEvent* event) { OrientationReadingEvent* oReadingEvent = static_cast(event); switch (oReadingEvent->m_orientation) { case QOrientationReading::LeftUp: { m_currentOrientation = (m_nativeOrientation == Qt::LandscapeOrientation) ? Qt::InvertedPortraitOrientation : Qt::LandscapeOrientation; break; } case QOrientationReading::TopUp: { m_currentOrientation = (m_nativeOrientation == Qt::LandscapeOrientation) ? Qt::LandscapeOrientation : Qt::PortraitOrientation; break; } case QOrientationReading::RightUp: { m_currentOrientation = (m_nativeOrientation == Qt::LandscapeOrientation) ? Qt::PortraitOrientation : Qt::InvertedLandscapeOrientation; break; } case QOrientationReading::TopDown: { m_currentOrientation = (m_nativeOrientation == Qt::LandscapeOrientation) ? Qt::InvertedLandscapeOrientation : Qt::InvertedPortraitOrientation; break; } default: { qWarning("Unknown orientation."); event->accept(); return; } } // Raise the event signal so that client apps know the orientation changed QWindowSystemInterface::handleScreenOrientationChange(screen(), m_currentOrientation); event->accept(); qCDebug(QTMIR_SENSOR_MESSAGES) << "Screen::customEvent - new orientation" << m_currentOrientation << "handled"; } void Screen::onOrientationReadingChanged() { qCDebug(QTMIR_SENSOR_MESSAGES) << "Screen::onOrientationReadingChanged"; // Make sure to switch to the main Qt thread context QCoreApplication::postEvent(this, new OrientationReadingEvent( OrientationReadingEvent::m_type, m_orientationSensor->reading()->orientation())); } QPlatformCursor *Screen::cursor() const { const QPlatformCursor *platformCursor = &m_cursor; return const_cast(platformCursor); } ScreenWindow *Screen::window() const { return m_screenWindow; } void Screen::setWindow(ScreenWindow *window) { if (window && m_screenWindow) { qCDebug(QTMIR_SENSOR_MESSAGES) << "Screen::setWindow - overwriting existing ScreenWindow"; } m_screenWindow = window; if (m_screenWindow) { if (m_screenWindow->geometry() != geometry()) { qCDebug(QTMIR_SCREENS) << "Screen::setWindow - new geometry for shell surface" << window->window() << geometry(); m_screenWindow->setGeometry(geometry()); } } } void Screen::setMirDisplayBuffer(mir::graphics::DisplayBuffer *buffer, mir::graphics::DisplaySyncGroup *group) { qCDebug(QTMIR_SCREENS) << "Screen::setMirDisplayBuffer" << buffer << group; // This operation should only be performed while rendering is stopped m_renderTarget = as_render_target(buffer); m_displayGroup = group; } void Screen::swapBuffers() { m_renderTarget->swap_buffers(); /* FIXME this exposes a QtMir architecture problem, as Screen is supposed to wrap a mg::DisplayBuffer. * We use Qt's multithreaded renderer, where each Screen is rendered to relatively independently, and * post() called also individually. * * But if this is a native server on Android, in the multimonitor case a DisplaySyncGroup can contain * 2+ DisplayBuffers, one post() call will submit all mg::DisplayBuffers in the group for flipping. * This will cause just one Screen to be updated, blocking the swap call for the other Screens, which * will slow rendering dramatically. * * Integrating the Qt Scenegraph renderer as a Mir renderer should solve this issue. */ m_displayGroup->post(); } void Screen::makeCurrent() { m_renderTarget->make_current(); } void Screen::doneCurrent() { m_renderTarget->release_current(); } bool Screen::internalDisplay() const { using namespace mir::graphics; if (m_type == DisplayConfigurationOutputType::lvds || m_type == DisplayConfigurationOutputType::edp) { return true; } return false; } ./src/platforms/mirserver/mirserverstatuslistener.h0000644000015600001650000000175112677054330023125 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef MIRSERVERSTATUSLISTENER_H #define MIRSERVERSTATUSLISTENER_H #include class MirServerStatusListener : public virtual mir::ServerStatusListener { public: void paused() override; void resumed() override; void started() override; }; #endif // MIRSERVERSTATUSLISTENER_H ./src/platforms/CMakeLists.txt0000644000015600001650000000003412677054330016437 0ustar jenkinsjenkinsadd_subdirectory(mirserver) ./src/common/0000755000015600001650000000000012677054330013163 5ustar jenkinsjenkins./src/common/timestamp.h0000644000015600001650000000253412677054330015343 0ustar jenkinsjenkins/* * Copyright (C) 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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef QTMIR_TIMESTAMP_H #define QTMIR_TIMESTAMP_H #include #include namespace qtmir { // Converts a mir timestamp (in nanoseconds) to and from a timestamp in milliseconds. // Qt system events only work with ulong timestamps. On 32bit archs a ulong is 4 bytes long, so the 64 bit nanoseconds // will be truncated and skewed. In order to fix this, we truncate the result by using time since "first call" template T compressTimestamp(std::chrono::nanoseconds timestamp); // "Re-inflate" a truncated timestamp. template std::chrono::nanoseconds uncompressTimestamp(T timestamp); } #include "timestamp_impl.h" #endif // QTMIR_TIMESTAMP_H ./src/common/timestamp_impl.h0000644000015600001650000000212112677054330016354 0ustar jenkinsjenkins#include #include extern "C" { void resetStartTime(std::chrono::nanoseconds timestamp); std::chrono::nanoseconds getStartTime(std::chrono::nanoseconds timestamp, bool allowReset = true); } namespace qtmir { typedef std::chrono::duration Timestamp; template T compressTimestamp(std::chrono::nanoseconds timestamp) { std::chrono::nanoseconds startTime = getStartTime(timestamp); if (Q_UNLIKELY((std::chrono::nanoseconds::max() > T::max() && timestamp - startTime > std::chrono::nanoseconds(T::max())) || timestamp < startTime)) { // we've overflowed the boundaries of the millisecond type. // or the timestamp has travelled to the past resetStartTime(timestamp); return T(0); } return std::chrono::duration_cast(timestamp - startTime); } template std::chrono::nanoseconds uncompressTimestamp(T timestamp) { auto tsNS = std::chrono::nanoseconds(timestamp); return getStartTime(tsNS, false) + std::chrono::nanoseconds(tsNS); } }./src/common/timestamp.cpp0000644000015600001650000000057012677054330015674 0ustar jenkinsjenkins #include #include "timestamp_impl.h" std::chrono::nanoseconds appStartTime(0); void resetStartTime(std::chrono::nanoseconds timestamp) { appStartTime = timestamp; } std::chrono::nanoseconds getStartTime(std::chrono::nanoseconds timestamp, bool allowReset) { if (allowReset && appStartTime.count() == 0) { resetStartTime(timestamp); } return appStartTime; } ./src/common/debughelpers.h0000644000015600001650000000322712677054330016011 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef UBUNTUGESTURES_DEBUG_HELPER_H #define UBUNTUGESTURES_DEBUG_HELPER_H #include #include #include class QTouchEvent; const char *touchPointStateToString(Qt::TouchPointState state); QString touchEventToString(const QTouchEvent *ev); QString mirSurfaceAttribAndValueToString(MirSurfaceAttrib attrib, int value); const char *mirSurfaceTypeToStr(int value); const char *mirSurfaceStateToStr(int value); const char *mirSurfaceFocusStateToStr(int value); const char *mirSurfaceVisibilityToStr(int value); const char *mirMotionActionToStr(int value); const char *applicationStateToStr(int state); QString mirPointerEventToString(MirPointerEvent const* event); QString mirTouchEventToString(MirTouchEvent const* event); QString mirKeyboardEventToString(MirKeyboardEvent const* event); const char *mirTouchActionToString(MirTouchAction touchAction); const char *qtCursorShapeToStr(Qt::CursorShape shape); #endif // UBUNTUGESTURES_DEBUG_HELPER_H ./src/common/debughelpers.cpp0000644000015600001650000002164412677054330016347 0ustar jenkinsjenkins/* * Copyright (C) 2013-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "debughelpers.h" #include #include // Unity API #include const char *touchPointStateToString(Qt::TouchPointState state) { switch (state) { case Qt::TouchPointPressed: return "pressed"; case Qt::TouchPointMoved: return "moved"; case Qt::TouchPointStationary: return "stationary"; case Qt::TouchPointReleased: return "released"; default: return "UNKNOWN!"; } } QString touchEventToString(const QTouchEvent *ev) { QString message; switch (ev->type()) { case QEvent::TouchBegin: message.append("TouchBegin "); break; case QEvent::TouchUpdate: message.append("TouchUpdate "); break; case QEvent::TouchEnd: message.append("TouchEnd "); break; case QEvent::TouchCancel: message.append("TouchCancel "); default: message.append("TouchUNKNOWN "); } for (int i=0; i < ev->touchPoints().size(); ++i) { const QTouchEvent::TouchPoint& touchPoint = ev->touchPoints().at(i); message.append( QString("(id:%1, state:%2, scenePos:(%3,%4), pos:(%5,%6)) ") .arg(touchPoint.id()) .arg(touchPointStateToString(touchPoint.state())) .arg(touchPoint.scenePos().x()) .arg(touchPoint.scenePos().y()) .arg(touchPoint.pos().x()) .arg(touchPoint.pos().y()) ); } return message; } QString mirSurfaceAttribAndValueToString(MirSurfaceAttrib attrib, int value) { QString str; switch (attrib) { case mir_surface_attrib_type: str = QString("type=%1").arg(mirSurfaceTypeToStr(value)); break; case mir_surface_attrib_state: str = QString("state=%1").arg(mirSurfaceStateToStr(value)); break; case mir_surface_attrib_swapinterval: str = QString("swapinterval=%1").arg(value); break; case mir_surface_attrib_focus: str = QString("focus=%1").arg(mirSurfaceFocusStateToStr(value)); break; case mir_surface_attrib_dpi: str = QString("dpi=%1").arg(value); break; case mir_surface_attrib_visibility: str = QString("visibility=%1").arg(mirSurfaceVisibilityToStr(value)); break; default: str = QString("type'%1'=%2").arg((int)attrib).arg(value); } return str; } const char *mirSurfaceTypeToStr(int value) { switch (value) { case mir_surface_type_normal: return "normal"; case mir_surface_type_utility: return "utility"; case mir_surface_type_dialog: return "dialog"; case mir_surface_type_overlay: return "overlay"; case mir_surface_type_freestyle: return "freestyle"; case mir_surface_type_popover: return "popover"; case mir_surface_type_inputmethod: return "inputmethod"; default: return "???"; } } const char *mirSurfaceStateToStr(int value) { switch (value) { case mir_surface_state_unknown: return "unknown"; case mir_surface_state_restored: return "restored"; case mir_surface_state_minimized: return "minimized"; case mir_surface_state_maximized: return "maximized"; case mir_surface_state_vertmaximized: return "vertmaximized"; case mir_surface_state_fullscreen: return "fullscreen"; default: return "???"; } } const char *mirSurfaceFocusStateToStr(int value) { switch (value) { case mir_surface_unfocused: return "unfocused"; case mir_surface_focused: return "focused"; default: return "???"; } } const char *mirSurfaceVisibilityToStr(int value) { switch (value) { case mir_surface_visibility_occluded: return "occluded"; case mir_surface_visibility_exposed: return "exposed"; default: return "???"; } } const char *mirTouchActionToStr(MirTouchAction action) { switch (action) { case mir_touch_action_up: return "up"; case mir_touch_action_down: return "down"; case mir_touch_action_change: return "change"; default: return "???"; } } using namespace unity::shell::application; const char *applicationStateToStr(int state) { switch (state) { case ApplicationInfoInterface::Starting: return "starting"; case ApplicationInfoInterface::Running: return "running"; case ApplicationInfoInterface::Suspended: return "suspended"; case ApplicationInfoInterface::Stopped: return "stopped"; default: return "???"; } } QString mirPointerEventToString(MirPointerEvent const* event) { QString string = QString("MirPointerEvent(x=%1,y=%2,relative_x=%3,relative_y=%4)") .arg(mir_pointer_event_axis_value(event, mir_pointer_axis_x)) .arg(mir_pointer_event_axis_value(event, mir_pointer_axis_y)) .arg(mir_pointer_event_axis_value(event, mir_pointer_axis_relative_x)) .arg(mir_pointer_event_axis_value(event, mir_pointer_axis_relative_y)); return string; } QString mirTouchEventToString(MirTouchEvent const* event) { const int pointerCount = mir_touch_event_point_count(event); QString string("MirTouchEvent("); for (int i = 0; i < pointerCount; ++i) { if (i > 0) { string.append(","); } MirTouchAction touchAction = mir_touch_event_action(event, i); QString touchStr = QString("(id=%1,action=%2,x=%3,y=%4)") .arg(mir_touch_event_id(event, i)) .arg(mirTouchActionToString(touchAction)) .arg(mir_touch_event_axis_value(event, i, mir_touch_axis_x)) .arg(mir_touch_event_axis_value(event, i, mir_touch_axis_y)); string.append(touchStr); } string.append(")"); return string; } const char *mirTouchActionToString(MirTouchAction touchAction) { switch (touchAction) { case mir_touch_action_up: return "up"; break; case mir_touch_action_down: return "down"; break; case mir_touch_action_change: return "change"; break; default: return "???"; break; } } namespace { const char *mirKeyboardActionToString(MirKeyboardAction keyboardAction) { switch (keyboardAction) { case mir_keyboard_action_up: return "up"; case mir_keyboard_action_down: return "down"; case mir_keyboard_action_repeat: return "repeat"; default: return "???"; break; } } } QString mirKeyboardEventToString(MirKeyboardEvent const* event) { MirKeyboardAction keyboardAction = mir_keyboard_event_action(event); xkb_keysym_t keyCode = mir_keyboard_event_key_code(event); return QString("MirKeyboardEvent(action=%1,key_code=0x%2)") .arg(mirKeyboardActionToString(keyboardAction)) .arg(keyCode, 4, 16, QLatin1Char('0')); } const char *qtCursorShapeToStr(Qt::CursorShape shape) { switch(shape) { case Qt::ArrowCursor: return "Arrow"; case Qt::UpArrowCursor: return "UpArrow"; case Qt::CrossCursor: return "Cross"; case Qt::WaitCursor: return "Wait"; case Qt::IBeamCursor: return "IBeam"; case Qt::SizeVerCursor: return "SizeVer"; case Qt::SizeHorCursor: return "SizeHor"; case Qt::SizeBDiagCursor: return "SizeBDiag"; case Qt::SizeFDiagCursor: return "SizeFDiag"; case Qt::SizeAllCursor: return "SizeAll"; case Qt::BlankCursor: return "Blank"; case Qt::SplitVCursor: return "SplitV"; case Qt::SplitHCursor: return "SplitH"; case Qt::PointingHandCursor: return "PointingHand"; case Qt::ForbiddenCursor: return "Forbidden"; case Qt::WhatsThisCursor: return "WhatsThis"; case Qt::BusyCursor: return "Busy"; case Qt::OpenHandCursor: return "OpenHand"; case Qt::ClosedHandCursor: return "ClosedHand"; case Qt::DragCopyCursor: return "DragCopy"; case Qt::DragMoveCursor: return "DragMove"; case Qt::DragLinkCursor: return "DragLink"; case Qt::BitmapCursor: return "Bitmap"; default: return "???"; } } ./src/common/abstractdbusservicemonitor.h0000644000015600001650000000356512677054330021017 0ustar jenkinsjenkins/* * Copyright (C) 2011-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #ifndef ABSTRACTDBUSSERVICEMONITOR_H #define ABSTRACTDBUSSERVICEMONITOR_H #include #include #include class QDBusAbstractInterface; class QDBusServiceWatcher; class Q_DECL_EXPORT AbstractDBusServiceMonitor : public QObject { Q_OBJECT Q_PROPERTY(bool serviceAvailable READ serviceAvailable NOTIFY serviceAvailableChanged) public: explicit AbstractDBusServiceMonitor(const QString &service, const QString &path, const QString &interface, const QDBusConnection &connection = QDBusConnection::sessionBus(), QObject *parent = 0); ~AbstractDBusServiceMonitor(); QDBusAbstractInterface* dbusInterface() const; bool serviceAvailable() const; Q_SIGNALS: void serviceAvailableChanged(bool available); private Q_SLOTS: void createInterface(const QString &service); void destroyInterface(const QString &service); protected: const QString m_service; const QString m_path; const QString m_interface; const QDBusConnection m_busConnection; QDBusServiceWatcher* m_watcher; QDBusAbstractInterface* m_dbusInterface; }; #endif // ABSTRACTDBUSSERVICEMONITOR_H ./src/common/abstractdbusservicemonitor.cpp0000644000015600001650000000654312677054330021351 0ustar jenkinsjenkins/* * Copyright (C) 2011-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 warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, 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 . */ #include "abstractdbusservicemonitor.h" #include #include #include #include #include // On construction QDBusInterface synchronously introspects the service, which will block the GUI // thread if the service is busy. QDBusAbstractInterface does not perform this introspection, so // let's subclass that and avoid the blocking scenario. class AsyncDBusInterface : public QDBusAbstractInterface { public: AsyncDBusInterface(const QString &service, const QString &path, const QString &interface, const QDBusConnection &connection, QObject *parent = 0) : QDBusAbstractInterface(service, path, interface.toLatin1().data(), connection, parent) {} ~AsyncDBusInterface() = default; }; AbstractDBusServiceMonitor::AbstractDBusServiceMonitor(const QString &service, const QString &path, const QString &interface, const QDBusConnection &connection, QObject *parent) : QObject(parent) , m_service(service) , m_path(path) , m_interface(interface) , m_busConnection(connection) , m_watcher(new QDBusServiceWatcher(service, m_busConnection)) , m_dbusInterface(nullptr) { connect(m_watcher, &QDBusServiceWatcher::serviceRegistered, this, &AbstractDBusServiceMonitor::createInterface); connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, &AbstractDBusServiceMonitor::destroyInterface); // Connect to the service if it's up already QDBusConnectionInterface* sessionBus = m_busConnection.interface(); QDBusReply reply = sessionBus->isServiceRegistered(m_service); if (reply.isValid() && reply.value()) { createInterface(m_service); } } AbstractDBusServiceMonitor::~AbstractDBusServiceMonitor() { delete m_watcher; delete m_dbusInterface; } void AbstractDBusServiceMonitor::createInterface(const QString &) { if (m_dbusInterface != nullptr) { delete m_dbusInterface; m_dbusInterface = nullptr; } m_dbusInterface = new AsyncDBusInterface(m_service, m_path, m_interface, m_busConnection); Q_EMIT serviceAvailableChanged(true); } void AbstractDBusServiceMonitor::destroyInterface(const QString &) { if (m_dbusInterface != nullptr) { delete m_dbusInterface; m_dbusInterface = nullptr; } Q_EMIT serviceAvailableChanged(false); } QDBusAbstractInterface* AbstractDBusServiceMonitor::dbusInterface() const { return m_dbusInterface; } bool AbstractDBusServiceMonitor::serviceAvailable() const { return m_dbusInterface != nullptr; } ./src/CMakeLists.txt0000644000015600001650000000006612677054330014435 0ustar jenkinsjenkinsadd_subdirectory(platforms) add_subdirectory(modules) ./README0000644000015600001650000000254312677054330011770 0ustar jenkinsjenkinsThis repo contains a QPA plugin to make Qt a Mir server. Handy way to grab dependencies is with: $ sudo mk-build-deps -ir debian/control To use, compile and install (building in a "build" subdir as an example): $ mkdir build $ cd build $ cmake -DCMAKE_INSTALL_PREFIX=/usr ../ $ make $ sudo make install To enable debug output, replace the single "cmake" command with $ cmake -DCMAKE_BUILD_TYPE=Debug (is good to "make clean" beforehand) To avoid building the tests (to speed up the build): $ cmake -DNO_TESTS=true To test the server, enter the demos directory, and run (NB: server should be run as root): $ sudo su $ export QT_QPA_PLATFORM=mirserver $ export MIR_SERVER_FILE=/tmp/mir_socket $ unset QT_QPA_PLATFORMTHEME then run the Qt application of your choice (qmlscene best). As example, try $ qmlscene qml-demo-shell/qml-demo-shell.qml prepare the environment for running mir clients: $ sudo chmod a+rw /tmp/mir_socket $ export MIR_SOCKET=/tmp/mir_socket and then try running an application: $ mir_demo_client_egltriangle --desktop_file_hint=/usr/share/applications/gallery-app.desktop or $ unset QT_QPA_PLATFORMTHEME $ export QT_QPA_PLATFORM=ubuntumirclient $ qmlscene qml-demo-client/qml-demo-client.qml --desktop_file_hint=/usr/share/click/preinstalled/com.ubuntu.calendar/0.4.172/calendar-app.desktop where the desktop file hint specifies an existing file. ./demos/0000755000015600001650000000000012677054330012213 5ustar jenkinsjenkins./demos/qml-demo-shell/0000755000015600001650000000000012677054331015034 5ustar jenkinsjenkins./demos/qml-demo-shell/main.cpp0000644000015600001650000000340312677054330016463 0ustar jenkinsjenkins/* * Copyright (C) 2012-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 as published by * the Free Software Foundation; version 3. * * 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 . */ // Qt #include #include #include #include #include #include #include #include #include #include "../paths.h" #include // REMOVEME - Should be able to use qmlscene, but in order to use the mir benchmarking we need // to parse command line switches. Wait until MIR_SOCKET supported by the benchmark framework. int main(int argc, const char *argv[]) { QGuiApplication::setApplicationName("qml-demo-shell"); QGuiApplication *application; application = new QGuiApplication(argc, (char**)argv); QQuickView* view = new QQuickView(); view->setResizeMode(QQuickView::SizeRootObjectToView); view->setColor("black"); view->setTitle("Demo Shell"); QUrl source(::qmlDirectory() + "qtmir-demo-shell/qml-demo-shell.qml"); view->setSource(source); QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit())); view->showFullScreen(); int result = application->exec(); delete view; delete application; return result; } ./demos/qml-demo-shell/Shell.qml0000644000015600001650000001060112677054330016613 0ustar jenkinsjenkinsimport QtQuick 2.4 import Unity.Application 0.1 Rectangle { id: root focus: true Keys.onVolumeUpPressed: { console.log("\"Volume Up\" pressed"); } Keys.onVolumeDownPressed: { console.log("\"Volume Down\" pressed"); } property bool resizeModeStretch: true gradient: Gradient { GradientStop { position: 0.0; color: "lightsteelblue" } GradientStop { position: 1.0; color: "pink" } } property bool thumbFriendlyBorders: false MultiPointTouchArea { anchors.fill: parent mouseEnabled: false onPressed: { root.thumbFriendlyBorders = true; } onReleased: { root.thumbFriendlyBorders = false; } } Image { id: unityLogo source: "UnityLogo.png" fillMode: Image.PreserveAspectFit anchors.centerIn: parent width: 600 height: 600 RotationAnimation { id: logoAnimation target: unityLogo from: 0 to: 359 duration: 3000 easing.type: Easing.Linear loops: Animation.Infinite } MultiPointTouchArea { anchors.fill: parent minimumTouchPoints:1 maximumTouchPoints:1 onPressed: { if (logoAnimation.paused) { logoAnimation.resume(); } else if (logoAnimation.running) { logoAnimation.pause(); } else { logoAnimation.start(); } } } } Item { id: windowContainer anchors.fill: root } Rectangle { id: quitButton width: 60 height: 40 color: "red" anchors { right: parent.right; bottom: parent.bottom } Text { anchors.centerIn: parent text: "Quit" } MouseArea { anchors.fill: parent onClicked: Qt.quit() } } Rectangle { id: resizeButton width: 90 height: 40 color: "blue" anchors { right: quitButton.left; bottom: parent.bottom } Text { anchors.centerIn: parent text: root.resizeModeStretch ? "Stretch" : "Wait Resize" color: "white" } MouseArea { anchors.fill: parent onClicked: { root.resizeModeStretch = !root.resizeModeStretch; } } } Rectangle { width: 40 height: 40 color: "green" anchors { right: resizeButton.left; bottom: parent.bottom } Text { anchors.centerIn: parent text: "⟳" color: "white" font.pixelSize: 35 } MouseArea { anchors.fill: parent onClicked: { root.rotation += 180; } } } Component { id: windowStretchComponent Window { x: 50 y: 50 //width: 200 //height: 200 touchMode: root.thumbFriendlyBorders onCloneRequested: { var window = windowStretchComponent.createObject(windowContainer); window.cloned = true; window.surface = surface; } } } Component { id: windowWaitResizeComponent WindowBufferSized { x: 50 y: 50 touchMode: root.thumbFriendlyBorders onCloneRequested: { var window = windowStretchComponent.createObject(windowContainer); window.cloned = true; window.surface = surface; } } } property var windowComponent: resizeModeStretch ? windowStretchComponent : windowWaitResizeComponent Connections { target: SurfaceManager onSurfaceCreated: { print("new surface", surface.name) var window = windowComponent.createObject(windowContainer); if (!window) { console.warn(windowComponent.errorString()); return; } window.surface = surface; openAnimation.target = window; openAnimation.start(); } } NumberAnimation { id: openAnimation property: "x"; from: root.width; to: 10; duration: 1200; easing.type: Easing.InOutQuad } } ./demos/qml-demo-shell/UnityLogo.png0000644000015600001650000000152212677054330017472 0ustar jenkinsjenkins‰PNG  IHDRÈÈ—–<ÝPLTE™.rüýûj9æ pHYs  šœtIMEÝ ÉtúõßIDATXíØ=n¦0à±\x;ï Ö‰–k¥X K[¤Ì•¶ß½ݶtq àì±gˆüé£ÈߣÀŒ!d^Þ ªøò—hJœÇHˆõ¢ZYð/]2GÑDQ‹Krª“˜Jì%ËùÀâ Ì…$¶UJy™Ô ¥)KŸ$C!3SˆE¢ AWšÒR Ж—ÚÒbqЖ‹ƒô<•‡¾ÄQ´EÇ•¢èP6E‡²¡º¡¹l Ú eÑN(ˆvÂjÑNhˆv.iÛñ­ÕŽo•‘Ñ‹#dð²°2b¼L„hV”Ë ç"0"NÙX¡–à\ž;q¤Œ¬ ‡,b™;E2uŠ:ÄvŠ<:Eì°=OŽ«¬Ý2Þˆën%6ñ2°ßÑváÄÿÌu³}#῵‹¹+'Ãnõñ¬/ÇZ72â(™ƒ|Ôò¢æ³ÓWñ¶±p^~‰ÿŒ¼Pâ¼HN~Ê?Œü`Å´¨ßŒh^Þ8Qo’“wNä?VþÒâ@nŒ|ŒŒN¬Œ¬ÀÈ@Ë«}ÜîFÒcÊËÒ/bæä}bDì¼€¬Ÿ('çëòQO–鉢’ù‰bndy¢ 7âºe¼‘µ[ö(+!-âNv? tˆ “C—¨$%)úVfRâô$ã°XÉBÊ&»[±•Œa‚w2U'UGéâ]•¦Û8~“â°È4Ea±k™NÙ°¨,þgYtšðÿQd1)˜3ƒˆœm‡”1t-cÊ%ª–+å0²ç\žßÊ)M‰JdÎY•¨œÍ,:ç9ƒÅä ¨± 97J¼'1YsGY½Ì§¦Lע̴²\‰²A<\ªÖlÏyÛ±é}eÿNÄí/w¦¶4r£Ü§pmi_î” $’©)`¯nL»‹³²;?;p»EÅ…t%K}™K6v'ë:jd©NVì²U'+dA{_h7¯lK~ˆk©^>X{¸)Gø-\IEND®B`‚./demos/qml-demo-shell/Window.qml0000644000015600001650000000547012677054330017023 0ustar jenkinsjenkinsimport QtQuick 2.0 import Unity.Application 0.1 Rectangle { id: root color: "red" property alias surface: surfaceItem.surface property bool touchMode: false width: surfaceItem.implicitWidth + 2*borderThickness height: surfaceItem.implicitHeight + 2*borderThickness + titleBar.height signal cloneRequested() property bool cloned: false onTouchModeChanged: { if (touchMode) { x -= borderThicknessTouch - borderThicknessMouse; width += 2*(borderThicknessTouch - borderThicknessMouse); y -= borderThicknessTouch - borderThicknessMouse; height += 2*(borderThicknessTouch - borderThicknessMouse); } else { x += borderThicknessTouch - borderThicknessMouse; width -= 2*(borderThicknessTouch - borderThicknessMouse); y += borderThicknessTouch - borderThicknessMouse; height -= 2*(borderThicknessTouch - borderThicknessMouse); } } readonly property real minWidth: 100 readonly property real minHeight: 100 property real borderThickness: touchMode ? borderThicknessTouch : borderThicknessMouse readonly property real borderThicknessMouse: 10 readonly property real borderThicknessTouch: 40 states: [ State { name: "closed" when: (surface && !surface.live) || titleBar.closeRequested } ] transitions: [ Transition { from: ""; to: "closed" SequentialAnimation { PropertyAnimation { target: root property: "scale" easing.type: Easing.InBack duration: 400 from: 1.0 to: 0.0 } ScriptAction { script: { root.destroy(); } } } } ] ResizeArea { anchors.fill: root borderThickness: root.borderThickness target: root } TitleBar { id: titleBar anchors.left: parent.left anchors.leftMargin: root.borderThickness anchors.right: parent.right anchors.rightMargin: root.borderThickness anchors.top: parent.top anchors.topMargin: root.borderThickness target: root cloned: root.cloned onCloneRequested: { root.cloneRequested(); } } MirSurfaceItem { id: surfaceItem anchors.top: titleBar.bottom anchors.left: parent.left anchors.leftMargin: root.borderThickness anchors.right: parent.right anchors.rightMargin: root.borderThickness anchors.bottom: parent.bottom anchors.bottomMargin: root.borderThickness consumesInput: !root.cloned surfaceWidth: root.cloned ? -1 : width surfaceHeight: root.cloned ? -1 : height } } ./demos/qml-demo-shell/TitleBar.qml0000644000015600001650000000344512677054330017262 0ustar jenkinsjenkinsimport QtQuick 2.4 import Unity.Application 0.1 Rectangle { id: root color: "brown" height: 25 property Item target property bool cloned: false property bool closeRequested: false signal cloneRequested() MouseArea { anchors.fill: parent property real distanceX property real distanceY property bool dragging onPressedChanged: { if (pressed) { var pos = mapToItem(root.target, mouseX, mouseY); distanceX = pos.x; distanceY = pos.y; dragging = true; Mir.cursorName = "grabbing"; } else { dragging = false; Mir.cursorName = ""; } } onMouseXChanged: { if (dragging) { var pos = mapToItem(root.target.parent, mouseX, mouseY); root.target.x = pos.x - distanceX; root.target.y = pos.y - distanceY; } } } Text { visible: !root.cloned anchors.top: parent.top anchors.bottom: parent.bottom text: "CLONE" MouseArea { anchors.fill: parent onClicked: { root.cloneRequested(); } } } Text { anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right width: contentWidth text: "X" fontSizeMode: Text.VerticalFit minimumPixelSize: 10; font.pixelSize: 72 font.weight: Font.Bold horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter MouseArea { anchors.fill: parent onClicked: { root.closeRequested = true; } } } } ./demos/qml-demo-shell/WindowBufferSized.qml0000644000015600001650000001274412677054330021156 0ustar jenkinsjenkinsimport QtQuick 2.0 import Unity.Application 0.1 Rectangle { id: root color: "red" property alias surface: surfaceItem.surface property bool touchMode: false width: surfaceItem.width + (borderThickness*2) height: surfaceItem.height + titleBar.height + (borderThickness*2) signal cloneRequested() onTouchModeChanged: { if (touchMode) { x -= borderThicknessTouch - borderThicknessMouse; width += 2*(borderThicknessTouch - borderThicknessMouse); y -= borderThicknessTouch - borderThicknessMouse; height += 2*(borderThicknessTouch - borderThicknessMouse); } else { x += borderThicknessTouch - borderThicknessMouse; width -= 2*(borderThicknessTouch - borderThicknessMouse); y += borderThicknessTouch - borderThicknessMouse; height -= 2*(borderThicknessTouch - borderThicknessMouse); } } readonly property real minWidth: 100 readonly property real minHeight: 100 property real borderThickness: touchMode ? borderThicknessTouch : borderThicknessMouse readonly property real borderThicknessMouse: 10 readonly property real borderThicknessTouch: 40 states: [ State { name: "closed" when: (surface && !surface.live) || titleBar.closeRequested } ] transitions: [ Transition { from: ""; to: "closed" SequentialAnimation { PropertyAnimation { target: root property: "scale" easing.type: Easing.InBack duration: 400 from: 1.0 to: 0.0 } ScriptAction { script: { root.destroy(); } } } } ] MouseArea { id: resizeArea anchors.fill: parent property real startX property real startY property real startWidth property real startHeight property bool leftBorder property bool rightBorder property bool topBorder property bool bottomBorder property bool dragging onPressedChanged: { if (pressed) { var pos = mapToItem(root.parent, mouseX, mouseY); startX = pos.x; startY = pos.y; startWidth = surfaceItem.width; startHeight = surfaceItem.height; leftBorder = mouseX > 0 && mouseX < root.borderThickness; rightBorder = mouseX > (root.width - root.borderThickness) && mouseX < root.width; topBorder = mouseY > 0 && mouseY < root.borderThickness; bottomBorder = mouseY > (root.height - root.borderThickness) && mouseY < root.height; dragging = true; } else { dragging = false; } } onMouseXChanged: { if (!pressed || !dragging) { return; } var pos = mapToItem(root.parent, mouseX, mouseY); var deltaX = pos.x - startX; if (leftBorder) { if (startWidth - deltaX >= root.minWidth) { surfaceItem.surfaceWidth = startWidth - deltaX; } else { surfaceItem.surfaceWidth = root.minWidth; } } else if (rightBorder) { if (startWidth + deltaX >= root.minWidth) { surfaceItem.surfaceWidth = startWidth + deltaX; } else { surfaceItem.surfaceWidth = root.minWidth; } } } onMouseYChanged: { if (!pressed || !dragging) { return; } var pos = mapToItem(root.parent, mouseX, mouseY); var deltaY = pos.y - startY; if (topBorder) { if (startHeight - deltaY >= root.minHeight) { surfaceItem.surfaceHeight = startHeight - deltaY; } else { surfaceItem.surfaceHeight = root.minHeight; } } else if (bottomBorder) { if (startHeight + deltaY >= root.minHeight) { surfaceItem.surfaceHeight = startHeight + deltaY; } else { surfaceItem.surfaceHeight = root.minHeight; } } } } TitleBar { id: titleBar anchors.left: parent.left anchors.leftMargin: root.borderThickness anchors.right: parent.right anchors.rightMargin: root.borderThickness anchors.top: parent.top anchors.topMargin: root.borderThickness target: root cloned: root.cloned onCloneRequested: { root.cloneRequested(); } } MirSurfaceItem { id: surfaceItem width: surface ? surface.size.width : 50 height: surface ? surface.size.height : 50 onWidthChanged: { if (resizeArea.dragging && resizeArea.leftBorder) { root.x = resizeArea.startX + resizeArea.startWidth - surfaceItem.width; } } onHeightChanged: { if (resizeArea.dragging && resizeArea.topBorder) { root.y = resizeArea.startY + resizeArea.startHeight - surfaceItem.height; } } anchors.top: titleBar.bottom anchors.left: parent.left anchors.leftMargin: root.borderThickness consumesInput: true } } ./demos/qml-demo-shell/CMakeLists.txt0000644000015600001650000000112512677054330017572 0ustar jenkinsjenkinsset(DEMO_SHELL qtmir-demo-shell) include_directories( ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ) add_executable(${DEMO_SHELL} main.cpp ) include_directories( ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${Qt5Qml_PRIVATE_INCLUDE_DIRS} ${Qt5Quick_PRIVATE_INCLUDE_DIRS} ) target_link_libraries( ${DEMO_SHELL} Qt5::Core Qt5::DBus Qt5::Qml Qt5::Quick ) file(GLOB QML_JS_FILES *.qml *.js *.png) # install binaries install(TARGETS ${DEMO_SHELL} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) install(FILES ${QML_JS_FILES} DESTINATION ${QTMIR_DATA_DIR}/${DEMO_SHELL} ) ./demos/qml-demo-shell/qml-demo-shell.qmlproject0000644000015600001650000000072112677054330021755 0ustar jenkinsjenkins/* File generated by Qt Creator, version 2.7.0 */ import QmlProject 1.1 Project { mainFile: "qml-demo-shell.qml" /* Include .qml, .js, and image files from current directory and subdirectories */ QmlFiles { directory: "." } JavaScriptFiles { directory: "." } ImageFiles { directory: "." } /* List of plugin directories passed to QML runtime */ importPaths: [ "../src/modules/Unity/Application" ] } ./demos/qml-demo-shell/qml-demo-shell.qml0000644000015600001650000000060312677054330020365 0ustar jenkinsjenkinsimport QtQuick 2.3 import QtQuick.Window 2.2 as QQW import Unity.Screens 0.1 Instantiator { id: root property var screens: Screens{} model: screens QQW.Window { id: window visible: true Shell{ anchors.fill: parent } Component.onCompleted: { print("HEY", screen, screen.geometry, outputType, Screens.HDMIA) } } } ./demos/qml-demo-shell/ResizeArea.qml0000644000015600001650000000707412677054330017610 0ustar jenkinsjenkinsimport QtQuick 2.4 import Unity.Application 0.1 MouseArea { id: root // to be set from outside property Item target property real borderThickness property bool leftBorder: false property bool rightBorder: false property bool topBorder: false property bool bottomBorder: false property bool dragging: false property real startX property real startY property real startWidth property real startHeight hoverEnabled: true property string cursorName: { if (containsMouse || pressed) { if (leftBorder && !topBorder && !bottomBorder) { return "left_side"; } else if (rightBorder && !topBorder && !bottomBorder) { return "right_side"; } else if (topBorder && !leftBorder && !rightBorder) { return "top_side"; } else if (bottomBorder && !leftBorder && !rightBorder) { return "bottom_side"; } else if (leftBorder && topBorder) { return "top_left_corner"; } else if (leftBorder && bottomBorder) { return "bottom_left_corner"; } else if (rightBorder && topBorder) { return "top_right_corner"; } else if (rightBorder && bottomBorder) { return "bottom_right_corner"; } else { return ""; } } else { return ""; } } onCursorNameChanged: { Mir.cursorName = cursorName; } function updateBorders() { leftBorder = mouseX <= borderThickness; rightBorder = mouseX >= width - borderThickness; topBorder = mouseY <= borderThickness; bottomBorder = mouseY >= height - borderThickness; } onPressedChanged: { if (pressed) { var pos = mapToItem(target.parent, mouseX, mouseY); startX = pos.x; startY = pos.y; startWidth = target.width; startHeight = target.height; dragging = true; } else { dragging = false; if (containsMouse) { updateBorders(); } } } onEntered: { if (!pressed) { updateBorders(); } } onPositionChanged: { if (!pressed) { updateBorders(); } if (!dragging) { return; } var pos = mapToItem(target.parent, mouse.x, mouse.y); if (leftBorder) { if (startX + startWidth - pos.x > target.minWidth) { target.x = pos.x; target.width = startX + startWidth - target.x; startX = target.x; startWidth = target.width; } } else if (rightBorder) { var deltaX = pos.x - startX; if (startWidth + deltaX >= target.minWidth) { target.width = startWidth + deltaX; } else { target.width = target.minWidth; } } if (topBorder) { if (startY + startHeight - pos.y > target.minHeight) { target.y = pos.y; target.height = startY + startHeight - target.y; startY = target.y; startHeight = target.height; } } else if (bottomBorder) { var deltaY = pos.y - startY; if (startHeight + deltaY >= target.minHeight) { target.height = startHeight + deltaY; } else { target.height = target.minHeight; } } } } ./demos/qml-demo-client/0000755000015600001650000000000012677054330015202 5ustar jenkinsjenkins./demos/qml-demo-client/main.cpp0000644000015600001650000000410312677054330016630 0ustar jenkinsjenkins/* * Copyright (C) 2012-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 as published by * the Free Software Foundation; version 3. * * 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 . */ // Qt #include #include #include #include #include #include #include "../paths.h" // REMOVEME - Should be able to use qmlscene, but in order to use the mir benchmarking we need // to parse command line switches. Wait until MIR_SOCKET supported by the benchmark framework. int main(int argc, char **argv) { int arg; opterr = 0; while ((arg = getopt (argc, argv, "hm:")) != -1) { switch (arg) { case 'm': setenv("MIR_SOCKET", optarg, 1); break; case '?': case 'h': default: puts(argv[0]); puts("Usage:"); puts(" -m "); puts(" -h: this help text"); return -1; } } QGuiApplication::setApplicationName("qml-demo-client"); QGuiApplication *application; application = new QGuiApplication(argc, (char**)argv); QQuickView* view = new QQuickView(); view->setResizeMode(QQuickView::SizeRootObjectToView); view->setColor("black"); view->setTitle("Demo Client"); QUrl source(::qmlDirectory() + "qtmir-demo-client/qml-demo-client.qml"); view->setSource(source); QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit())); view->showFullScreen(); int result = application->exec(); delete view; delete application; return result; } ./demos/qml-demo-client/qml-demo-client.qml0000644000015600001650000000133712677054330020710 0ustar jenkinsjenkinsimport QtQuick 2.0 Rectangle { onWidthChanged: print("WIDTH", width) onHeightChanged: print("HEIGHT", height) width: 600 height: 80*6 color: "blue" Column { anchors.fill: parent MovingRect {} MovingRect {} MovingRect {} MovingRect {} MovingRect {} MovingRect {} } Rectangle { id: flasher width: 100 height: 100 anchors.bottom: parent.bottom anchors.right: parent.right color: (on) ? "yellow" : "green" property bool on: false Timer { interval: 1000 repeat: true running: true onTriggered: flasher.on = !flasher.on } } } ./demos/qml-demo-client/qtmir-demo-client.desktop.in0000644000015600001650000000032012677054330022527 0ustar jenkinsjenkins[Desktop Entry] Type=Application Name=QtMir Demo Client Comment=QtMir demo client Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/qtmir-demo-client Terminal=false Icon= NoDisplay=false X-Ubuntu-Touch=true ./demos/qml-demo-client/CMakeLists.txt0000644000015600001650000000150012677054330017736 0ustar jenkinsjenkinsset(DEMO_CLIENT qtmir-demo-client) configure_file(${DEMO_CLIENT}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${DEMO_CLIENT}.desktop @ONLY) include_directories( ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ) add_executable(${DEMO_CLIENT} main.cpp ) include_directories( ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${Qt5Qml_PRIVATE_INCLUDE_DIRS} ${Qt5Quick_PRIVATE_INCLUDE_DIRS} ) target_link_libraries( ${DEMO_CLIENT} Qt5::Core Qt5::DBus Qt5::Qml Qt5::Quick ) file(GLOB QML_JS_FILES *.qml *.js *.png) # install binaries install(TARGETS ${DEMO_CLIENT} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) install(FILES ${QML_JS_FILES} DESTINATION ${QTMIR_DATA_DIR}/${DEMO_CLIENT} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${DEMO_CLIENT}.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications )./demos/qml-demo-client/MovingRect.qml0000644000015600001650000000216012677054330017771 0ustar jenkinsjenkinsimport QtQuick 2.0 Rectangle { id: movingRect width: 80 height: 80 color: "red" property real maxX: parent.width - width property real targetX: maxX function flipTargetX() { if (targetX == 0) { targetX = maxX } else { targetX = 0 } } Timer { property real step: 4 repeat: true running: true interval: 1000 / 60 onTriggered: { if (x < targetX) { if (x + step > targetX) { x = targetX; } else { x += step; } } else { if (x - step < targetX) { x = targetX; } else { x -= step; } } } } onXChanged: { if (x == targetX) { flipTargetX(); } } onWidthChanged: { if (targetX > 0) { targetX = maxX; } } MouseArea { anchors.fill: parent onPressed: { parent.flipTargetX(); } } } ./demos/paths.h.in0000644000015600001650000000234412677054330014113 0ustar jenkinsjenkins/* * Copyright (C) 2015 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * 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 PATHS_H #define PATHS_H // Qt #include #include #include #include #include inline bool isRunningInstalled() { static bool installed = (QCoreApplication::applicationDirPath() == QDir(("@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@")).canonicalPath()); return installed; } inline QString qmlDirectory() { if (isRunningInstalled()) { return QString("@CMAKE_INSTALL_PREFIX@/@QTMIR_DATA_DIR@/"); } else { return QString("@CMAKE_SOURCE_DIR@/demos/"); } } #endif./demos/CMakeLists.txt0000644000015600001650000000021012677054330014744 0ustar jenkinsjenkinsconfigure_file(paths.h.in ${CMAKE_CURRENT_BINARY_DIR}/paths.h @ONLY) add_subdirectory(qml-demo-client) add_subdirectory(qml-demo-shell)./benchmarks/0000755000015600001650000000000012677054330013221 5ustar jenkinsjenkins./benchmarks/touch_event_latency.py0000644000015600001650000002567212677054330017651 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 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 as published by # the Free Software Foundation; version 3. # # 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 . from mir_perf_framework import PerformanceTest, Server, Client import time import statistics import shutil import sys from common import get_pointing_device import report_types ####### TEST ####### def perform_test(): host = Server(reports=["input"]) nested = Server(executable=shutil.which("qtmir-demo-shell"), host=host, reports=["input","client-input-receiver"], env={"QT_QPA_PLATFORM": "mirserver", "QML_NO_TOUCH_COMPRESSION": "1"}) client = Client(executable=shutil.which("qtmir-demo-client"), server=nested, reports=["input","client-input-receiver"], env={"QT_QPA_PLATFORM": "ubuntumirclient", "QML_NO_TOUCH_COMPRESSION": "1"}, options=["--", "--desktop_file_hint=/usr/share/applications/qtmir-demo-client.desktop"]) test = PerformanceTest([host, nested, client]) test.start() results = report_types.Results() processes = report_types.Processes() processes.add_child(report_types.Process("Host", host.process.pid)) processes.add_child(report_types.Process("Nested Server", nested.process.pid)) processes.add_child(report_types.Process("Client", client.process.pid)) results.add_child(processes) time.sleep(3) # wait for settle host_pid = host.process.pid nested_pid = nested.process.pid client_pid = client.process.pid touch = get_pointing_device() time.sleep(1) # let mir pick up the new input device touch.drag(100, 100, 1000, 100, 5, 0.006) touch.drag(1000, 100, 1000, 1000, 5, 0.006) touch.drag(1000, 1000, 100, 1000, 5, 0.006) touch.drag(100, 1000, 100, 100, 5, 0.006) # time.sleep(5) # wait for settle time.sleep(2) # wait for settle test.stop() ####### TRACE PARSING ####### trace = test.babeltrace() server_touch_data_timestamps = {} client_touch_data_timestamps = {} client_touch_data_latency = {} qtmir_touch_dispatch_start = {} qtmir_touch_dispatch_end = {} qtmir_touch_consume_start = {} qtmir_touch_consume_end = {} events = report_types.Events() for event in trace.events: events.add_child(report_types.Event(event)) pid = event["vpid"] if event.name == "mir_client_input_receiver:touch_event": if pid not in client_touch_data_timestamps: client_touch_data_timestamps[pid] = [] if pid not in client_touch_data_latency: client_touch_data_latency[pid] = [] diff = (event.timestamp - event["event_time"]) / 1000000.0 if diff > 0: client_touch_data_timestamps[pid].append(event.timestamp) client_touch_data_latency[pid].append(diff) elif event.name == "mir_server_input:published_motion_event": if pid not in server_touch_data_timestamps: server_touch_data_timestamps[pid] = [] server_touch_data_timestamps[pid].append(event["event_time"]) elif event.name == "qtmirserver:touchEventDispatch_start": if pid not in qtmir_touch_dispatch_start: qtmir_touch_dispatch_start[pid] = [] qtmir_touch_dispatch_start[pid].append(event.timestamp) elif event.name == "qtmirserver:touchEventDispatch_end": if pid not in qtmir_touch_dispatch_end: qtmir_touch_dispatch_end[pid] = [] qtmir_touch_dispatch_end[pid].append(event.timestamp) elif event.name == "qtmir:touchEventConsume_start": if pid not in qtmir_touch_consume_start: qtmir_touch_consume_start[pid] = [] qtmir_touch_consume_start[pid].append(event.timestamp) elif event.name == "qtmir:touchEventConsume_end": if pid not in qtmir_touch_consume_end: qtmir_touch_consume_end[pid] = [] qtmir_touch_consume_end[pid].append(event.timestamp) # LATENCY MEANS if nested_pid in client_touch_data_latency: nested_data = client_touch_data_latency[nested_pid] nested_latency_xml = report_types.ResultsData( "nested_latency", statistics.mean(nested_data), statistics.stdev(nested_data), "Kernel to nested server latency") for value in nested_data: nested_latency_xml.add_data(value) results.add_child(nested_latency_xml) nested_latency_xml.generate_histogram("nested_latency") else: results.add_child(report_types.Error("No nested server touch latency data")) if client_pid in client_touch_data_latency: client_data = client_touch_data_latency[client_pid] client_latency_xml = report_types.ResultsData( "client_latency", statistics.mean(client_data), statistics.stdev(client_data), "Kernel to client latency") for value in client_data: client_latency_xml.add_data(value) results.add_child(client_latency_xml) client_latency_xml.generate_histogram("client_latency") else: results.add_child(report_types.Error("No client touch latency data")) # EVENT RATES if host_pid in server_touch_data_timestamps: last_timestamp = -1 input_rate = [] for next_timestamp in server_touch_data_timestamps[host_pid]: if last_timestamp != -1: diff = (next_timestamp - last_timestamp) / 1000000.0 input_rate.append(diff) last_timestamp = next_timestamp input_rate_xml = report_types.ResultsData( "host_input_ate", statistics.mean(input_rate), statistics.stdev(input_rate), "Host input event rate") for value in input_rate: input_rate_xml.add_data(value) results.add_child(input_rate_xml) input_rate_xml.generate_histogram("host_input_rate") else: results.add_child(report_types.Error("No host server input event timestamp data")) if nested_pid in client_touch_data_timestamps: last_timestamp = -1 input_rate = [] for next_timestamp in client_touch_data_timestamps[nested_pid]: if last_timestamp != -1: diff = (next_timestamp - last_timestamp) / 1000000.0 input_rate.append(diff) last_timestamp = next_timestamp input_rate_xml = report_types.ResultsData( "nested_input_rate", statistics.mean(input_rate), statistics.stdev(input_rate), "Nested server event rate") for value in input_rate: input_rate_xml.add_data(value) results.add_child(input_rate_xml) input_rate_xml.generate_histogram("nested_input_rate") else: results.add_child(report_types.Error("No nested server input event timestamp data")) if client_pid in client_touch_data_timestamps: last_timestamp = -1 input_rate = [] for next_timestamp in client_touch_data_timestamps[client_pid]: if last_timestamp != -1: diff = (next_timestamp - last_timestamp) / 1000000.0 input_rate.append(diff) last_timestamp = next_timestamp input_rate_xml = report_types.ResultsData( "client_input_rate", statistics.mean(input_rate), statistics.stdev(input_rate), "Client event rate") for value in input_rate: input_rate_xml.add_data(value) results.add_child(input_rate_xml) input_rate_xml.generate_histogram("client_input_rate") else: results.add_child(report_types.Error("No client event timestamp data")) qtmir_loop_data = [] dispatch_data = [] consume_data = [] # TIME BETWEEN TRACEPOINTS dispatch_starts = qtmir_touch_dispatch_start[nested_pid] if nested_pid in qtmir_touch_dispatch_start else [] dispatch_ends = qtmir_touch_dispatch_end[nested_pid] if nested_pid in qtmir_touch_dispatch_end else [] consume_starts = qtmir_touch_consume_start[nested_pid] if nested_pid in qtmir_touch_consume_start else [] consume_ends = qtmir_touch_consume_end[nested_pid] if nested_pid in qtmir_touch_consume_end else [] # since there's no uniqueness to events, we need to assume all events are 1:1 through system if len(dispatch_starts) > 0 and len(dispatch_starts) == len(dispatch_ends) and len(dispatch_starts) == len(consume_starts) and len(consume_starts) == len(consume_ends): i = 0 for start in dispatch_starts: dispatch_diff = (dispatch_ends[i] - start) / 1000000.0 consume_diff = (consume_ends[i] - consume_starts[i]) / 1000000.0 loop_dif = (consume_starts[i] - dispatch_ends[i]) / 1000000.0 dispatch_data.append(dispatch_diff); consume_data.append(consume_diff); qtmir_loop_data.append(loop_dif); i=i+1 qtmir_loop_xml = report_types.ResultsData( "qtmir_eventloop", statistics.mean(qtmir_loop_data), statistics.stdev(qtmir_loop_data), "Time spent in qtmir event loop") for value in qtmir_loop_data: qtmir_loop_xml.add_data(value) results.add_child(qtmir_loop_xml) qtmir_loop_xml.generate_histogram("qtmir_eventloop") qtmir_dispatch_xml = report_types.ResultsData( "qtmir_dispatch", statistics.mean(dispatch_data), statistics.stdev(dispatch_data), "Time QteventFeeder spent dispatching event to qt") for value in dispatch_data: qtmir_dispatch_xml.add_data(value) results.add_child(qtmir_dispatch_xml) qtmir_dispatch_xml.generate_histogram("qtmir_dispatch") qtmir_consume_xml = report_types.ResultsData( "qtmir_consume", statistics.mean(consume_data), statistics.stdev(consume_data), "Time MirSurfaceItem spent consiming event") for value in consume_data: qtmir_consume_xml.add_data(value) results.add_child(qtmir_consume_xml) qtmir_consume_xml.generate_histogram("qtmir_consume") else: results.add_child(report_types.Error("Cannot calculate QtMir loop data - Dispatch event count did not match surface consume event count")) results.add_child(events) return results if __name__ == "__main__": results = perform_test(); f = open("touch_event_latency.xml", "w") f.write(results.to_string()) ./benchmarks/report_types.py0000644000015600001650000000673412677054330016344 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 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 as published by # the Free Software Foundation; version 3. # # 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 . import subprocess import os import shutil class Node: def __init__(self, name): self.name = name self.children = [] def add_child(self, child): self.children.append(child) def to_string(self): output = "<%s>" % self.name for child in self.children: output += child.to_string() output += "" % self.name return output class Results(Node): def __init__(self): super().__init__("results") class Events(Node): def __init__(self): super().__init__("events") class Event: def __init__(self, event): self.pid = event["vpid"] self.name = event.name self.timestamp = event.timestamp def to_string(self): output = "".format(self.comment) return output./benchmarks/README0000644000015600001650000000063712677054330014107 0ustar jenkinsjenkinsTo run performance tests on device: Firstly, you will need to install the qtmir-tests package. So either install the package using apt or build and install from source $ sudo apt-get install qtmir-tests Then you need to stop unity8 & unity-system compositor so that we can start out test in a controlled environment. $ sudo stop lightdm Next, start the test! $ cd benchmarks $ sudo python3 touch_event_latency.py./benchmarks/touch_event_latency.R0000644000015600001650000000034512677054330017410 0ustar jenkinsjenkinsargs <- commandArgs(trailingOnly = TRUE) data <- scan(args[1], numeric(), sep=",") colors = c("red", "yellow", "green", "violet", "orange", "pink", "blue") png(filename=args[2]) hist(data, breaks=max(data), col=colors) dev.off() ./benchmarks/common.py0000644000015600001650000000221712677054330015065 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 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 as published by # the Free Software Foundation; version 3. # # 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 . from autopilot import ( input, platform ) def get_pointing_device(): """Return the pointing device depending on the platform. If the platform is `Desktop`, the pointing device will be a `Mouse`. If not, the pointing device will be `Touch`. """ if platform.model() == 'Desktop': input_device_class = input.Mouse else: input_device_class = input.Touch return input.Pointer(device=input_device_class.create())./benchmarks/CMakeLists.txt0000644000015600001650000000017012677054330015757 0ustar jenkinsjenkinsfile(GLOB BENCHMARK_FILES *.py *.R ) install(FILES ${BENCHMARK_FILES} DESTINATION ${QTMIR_DATA_DIR}/benchmarks ) ./CMakeLists.txt0000644000015600001650000001446712677054330013660 0ustar jenkinsjenkinscmake_minimum_required(VERSION 2.8.9) project(qtmir) set(QTMIR_VERSION_MAJOR 0) set(QTMIR_VERSION_MINOR 1) set(QTMIR_VERSION_PATCH 0) if(${PROJECT_BINARY_DIR} STREQUAL ${PROJECT_SOURCE_DIR}) message(FATAL_ERROR "In-tree build attempt detected, aborting. Set your build dir outside your source dir, delete CMakeCache.txt and CMakeFiles/ from source root and try again.") endif() # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) # add custom cmake modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -Werror") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fPIC -Wall -fno-strict-aliasing -Werror -Wextra") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") include(EnableCoverageReport) ##################################################################### # Enable code coverage calculation with gcov/gcovr/lcov # Usage: # * Switch build type to coverage (use ccmake or cmake-gui) # * Invoke make, make test, make coverage (or ninja if you use that backend) # * Find html report in subdir coveragereport # * Find xml report feasible for jenkins in coverage.xml ##################################################################### if(cmake_build_type_lower MATCHES coverage) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage" ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage" ) set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} --coverage" ) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage" ) ENABLE_COVERAGE_REPORT(TARGETS ${SHELL_APP} FILTER /usr/include ${CMAKE_SOURCE_DIR}/tests/* ${CMAKE_BINARY_DIR}/*) endif() # Make sure we have all the needed symbols set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,defs") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,defs") # Static C++ checks add_custom_target(cppcheck COMMAND cppcheck --enable=all -q --error-exitcode=2 ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests) include(FindPkgConfig) find_package(Qt5Core 5.4 REQUIRED) find_package(Qt5DBus 5.4 REQUIRED) find_package(Qt5Gui 5.4 REQUIRED) find_package(Qt5Qml 5.4 REQUIRED) find_package(Qt5Quick 5.4 REQUIRED) find_package(Qt5Sensors 5.4 REQUIRED) find_package(Qt5Test 5.4 REQUIRED) find_package(Threads REQUIRED) pkg_check_modules(MIRSERVER mirserver>=0.19 REQUIRED) pkg_check_modules(MIRCLIENT mirclient>=0.19 REQUIRED) pkg_check_modules(MIRRENDERERGLDEV mir-renderer-gl-dev>=0.19 REQUIRED) pkg_check_modules(XKBCOMMON xkbcommon REQUIRED) pkg_check_modules(GLIB glib-2.0 REQUIRED) pkg_check_modules(PROCESS_CPP process-cpp REQUIRED) pkg_check_modules(UBUNTU_APP_LAUNCH ubuntu-app-launch-2 REQUIRED) pkg_check_modules(URL_DISPATCHER url-dispatcher-1) pkg_check_modules(EGL egl) pkg_check_modules(GIO gio-2.0) pkg_check_modules(GIO_UNIX gio-unix-2.0) pkg_check_modules(LTTNG lttng-ust) pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt) pkg_check_modules(QTDBUSTEST libqtdbustest-1 REQUIRED) pkg_check_modules(QTDBUSMOCK libqtdbusmock-1 REQUIRED) pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=14) include_directories(${APPLICATION_API_INCLUDE_DIRS}) add_definitions(-DMIR_REQUIRE_DEPRECATED_EVENT_OPT_IN=1) # Use the fast string builder add_definitions(-DQT_USE_QSTRINGBUILDER) include_directories( ${APPLICATION_API_INCLUDE_DIRS} ) # We expect this to be set via debian/rules for GLES builds if ("${USE_OPENGLES}" STREQUAL 1) message(STATUS "Qt5 determined to be compiled with GLES support") pkg_check_modules(GLESv2 glesv2) add_definitions(-DQT_USING_GLES) include_directories (${GLESv2_INCLUDE_DIRS}) set (GL_LIBRARIES "${GLESv2_LIBRARIES}") # Because qt/gl and qt/gles are not parallel installable we have a difficulty # building qtmir-desktop on armhf. We would like to link against opengl and bind the # opengl API but Qt is already linked against the ES APIs. So in this setup we link against OpenGLES # to accomodate Qt but bind the OpenGL API anyway. This is technically broken though # I suspect Mesa may allow it ~racarr. elseif("${USE_OPENGL_BUT_LINK_AGAINST_OPENGLES}") message(STATUS "Linking against OpenGL ES but binding OpenGL API") pkg_check_modules(GLESv2 glesv2) add_definitions(-DQT_USING_GL) include_directories (${GLESv2_INCLUDE_DIRS}) set (GL_LIBRARIES "${GLESv2_LIBRARIES}") else() message(STATUS "Qt5 determined to be compiled with OpenGL support") pkg_check_modules(GL gl) add_definitions(-DQT_USING_OPENGL) include_directories (${GL_INCLUDE_DIRS}) endif() # Standard install paths include(GNUInstallDirs) # Determine QPA plugin install path # FIXME(greyback) there must be a better way than calling a private macro to set a property. # I'm manually calling a macro from Qt5GuiConfig.cmake, but I don't understand why it isn't being called. _populate_Gui_plugin_properties("Gui" "PLATFORMS" "platforms") get_target_property(Qt5Gui_QPA_Plugin_Path Qt5::Gui IMPORTED_LOCATION_PLATFORMS) # Set QML module install path set(QML_MODULE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/qt5/qml") set(QTMIR_DATA_DIR ${CMAKE_INSTALL_DATADIR}/qtmir) # Customisations for the build type if(NOT CMAKE_BUILD_TYPE) message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" cmake_build_type_lower) if(cmake_build_type_lower MATCHES relwithdebinfo) # workaround for http://public.kitware.com/Bug/view.php?id=14696 add_definitions(-DQT_NO_DEBUG) endif() if ("${CMAKE_BUILD_TYPE}" STREQUAL "release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "relwithdebinfo") option(Werror "Treat warnings as errors" ON) else() option(Werror "Treat warnings as errors" OFF) endif() if (Werror) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") endif() # Mir uses boost, which does not like Qt defining macros named "signals" and "slots" add_definitions(-DQT_NO_KEYWORDS) # Tests if (NOT NO_TESTS) include(CTest) enable_testing() add_subdirectory(tests) else() message(STATUS "Tests disabled") endif() # add subdirectories to build add_subdirectory(src) add_subdirectory(demos) add_subdirectory(benchmarks)