Merge lp:~afrantzis/unity-system-compositor/grand-refactoring-first-steps into lp:unity-system-compositor

Proposed by Alexandros Frantzis
Status: Merged
Approved by: Alexandros Frantzis
Approved revision: 156
Merged at revision: 157
Proposed branch: lp:~afrantzis/unity-system-compositor/grand-refactoring-first-steps
Merge into: lp:unity-system-compositor
Diff against target: 3292 lines (+2068/-857)
24 files modified
CMakeLists.txt (+10/-0)
cmake/FindGtest.cmake (+53/-0)
debian/control (+1/-0)
src/CMakeLists.txt (+22/-13)
src/asio_dm_connection.cpp (+42/-19)
src/asio_dm_connection.h (+73/-0)
src/dm_connection.h (+22/-45)
src/external_spinner.cpp (+54/-0)
src/external_spinner.h (+49/-0)
src/main.cpp (+8/-3)
src/server_configuration.cpp (+196/-0)
src/server_configuration.h (+119/-0)
src/session_coordinator.cpp (+117/-0)
src/session_coordinator.h (+54/-0)
src/session_switcher.cpp (+190/-0)
src/session_switcher.h (+98/-0)
src/spinner.h (+44/-0)
src/surface_coordinator.cpp (+86/-0)
src/surface_coordinator.h (+47/-0)
src/system_compositor.cpp (+45/-748)
src/system_compositor.h (+26/-29)
tests/CMakeLists.txt (+17/-0)
tests/unit-tests/CMakeLists.txt (+39/-0)
tests/unit-tests/test_session_switcher.cpp (+656/-0)
To merge this branch: bzr merge lp:~afrantzis/unity-system-compositor/grand-refactoring-first-steps
Reviewer Review Type Date Requested Status
Alberto Aguirre (community) Approve
Alan Griffiths Approve
Michael Terry (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+226000@code.launchpad.net

Commit message

First steps of refactoring to improve code comprehensibility and testability

Description of the change

First steps of refactoring to improve code comprehensibility and testability

There is still a lot of work to be done, mainly to allow acceptance and integration testing, but it's a start. In this MP I focused on refactoring the high-level wiring of the application components
and on our session switching algorithm (rewritten test-first from scratch).

Tested on the latest N4 image.

To post a comment you must log in.
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

I noticed I messed up/forgot copyrights in some cases. Will address that tomorrow along with other review comments.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> FAILURE: http://jenkins.qa.ubuntu.com/job/unity-system-compositor-utopic-armhf-ci/27/console

Some kind of build dependency failure (libgtest.a not built in proper order). Will take a look tomorrow, but feel free to review the code in the meantime.

Revision history for this message
Michael Terry (mterry) wrote :

Thanks for this work, it's great. Comments inline.

review: Needs Fixing
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> I noticed I messed up/forgot copyrights in some cases. Will address that tomorrow along with other review comments.

Fixed.

> Some kind of build dependency failure (libgtest.a not built in proper order).

Fixed.

> > + google-mock,
>
> Guh, why not nicely sorted?

Fixed.

> > + surface_coordinator.cpp
>
> You sort the new stuff but not the old stuff. I know it wasn't sorted before you got there,
> but it's a good chance to fix that.

Fixed.

> > # Link against libmirserver
> > +qt5_use_modules(usc Core DBus)
>
> This separates the comment above it from the code it comments.
> Though really, you could probably delete the comment, as it doesn't add much
> to the line it comments.

Fixed.

> > === added file 'src/asio_dm_connection.cpp'
>
> Please do a "bzr mv" instead of a delete and add for this file. That way, we keep bzr history.

Fixed.

> > +TEST_F(ASessionSwitcher, displays_ready_next_session)
>
> We shouldn't display a next session without a ready active session.

Fixed. Updated test name is "does_not_display_ready_next_session_without_ready_active_session".

> > +TEST_F(ASessionSwitcher, displays_spinner_if_next_is_not_ready)
>
> This test probably wants an active session configured too.
> Because without one, next should never be shown anyway.
> But I think you have that same test below.

Fixed. Kept test (renamed to "does_not_display_spinner_if_next_is_not_ready") to check behavior when we only have a "next" session.

> > +TEST_F(ASessionSwitcher, starts_and_stops_spinner_as_needed)
>
> Do you even need a next session for this test?

Fixed.

> > +TEST_F(ASessionSwitcher, displays_next_when_active_is_removed)
>
> No, if the active session is not set, we should show spinner (or nothing, if no spinner).

Fixed. "does_not_display_next_when_active_is_removed"

> > +TEST_F(ASessionSwitcher, displays_active_when_next_is_removed)
>
> Do we need spinner in this test?

Updated test name to better communicate its intent (and why we need a spinner): "displays_only_active_not_spinner_when_next_is_removed". Contrast with related test: "displays_spinner_under_active_if_next_is_removed_unexpectedly"

> > +TEST_F(ASessionSwitcher, can_handle_spinner_resurrection_under_different_name)
>
> Might also want to test that we handle it being resurrected under a different pid,
> which is more common.

Added: "can_handle_spinner_resurrection_under_different_pid"

> > +TEST_F(ASessionSwitcher, is_not_confused_by_other_session_with_name_of_dead_spinner)
>
> And again, might want to add test for a process that uses a dead spinner's pid.

Added: "is_not_confused_by_other_session_with_pid_of_dead_spinner"

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

Excellent, thanks Alexandros!

review: Approve
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Not a complete review but what I've followed looks like an improvement. (And makes my fingers itch to fix more.)

review: Approve
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

* Did you test your feature/code change/bug fix ? what device(s) ?

  YES, Desktop and N4

* Did you break mir server API or ABI and have the relevant bumps to .so and debian docs been made ?

  No (Not applicable)

* Did you break mir client API or ABI and have you followed up with the known clients & announced on mir-devel mailing list ?
  No (Not applicable)

Revision history for this message
Alberto Aguirre (albaguirre) wrote :

1507 + std::thread{action}.detach();

Neat! :)

Looks good, now I need to add some tests for all the display state/inactivity policy stuff :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2014-07-03 17:52:36 +0000
+++ CMakeLists.txt 2014-07-18 09:49:03 +0000
@@ -52,3 +52,13 @@
5252
53add_subdirectory(spinner/)53add_subdirectory(spinner/)
54add_subdirectory(src/)54add_subdirectory(src/)
55
56enable_testing()
57
58option(MIR_ENABLE_TESTS "Build tests" ON)
59
60if (MIR_ENABLE_TESTS)
61 find_package(Gtest REQUIRED)
62 include_directories(${GMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIR})
63 add_subdirectory(tests/)
64endif ()
5565
=== added file 'cmake/FindGtest.cmake'
--- cmake/FindGtest.cmake 1970-01-01 00:00:00 +0000
+++ cmake/FindGtest.cmake 2014-07-18 09:49:03 +0000
@@ -0,0 +1,53 @@
1include(ExternalProject)
2include(FindPackageHandleStandardArgs)
3
4#gtest
5set(GTEST_INSTALL_DIR /usr/src/gmock/gtest/include)
6find_path(GTEST_INCLUDE_DIR gtest/gtest.h
7 HINTS ${GTEST_INSTALL_DIR})
8
9#gmock
10find_path(GMOCK_INSTALL_DIR gmock/CMakeLists.txt
11 HINTS /usr/src)
12if(${GMOCK_INSTALL_DIR} STREQUAL "GMOCK_INSTALL_DIR-NOTFOUND")
13 message(FATAL_ERROR "google-mock package not found")
14endif()
15
16set(GMOCK_INSTALL_DIR ${GMOCK_INSTALL_DIR}/gmock)
17find_path(GMOCK_INCLUDE_DIR gmock/gmock.h)
18
19set(GMOCK_PREFIX gmock)
20set(GMOCK_BINARY_DIR ${CMAKE_BINARY_DIR}/${GMOCK_PREFIX}/libs)
21set(GTEST_BINARY_DIR ${GMOCK_BINARY_DIR}/gtest)
22
23set(GTEST_CMAKE_ARGS "")
24if (${CMAKE_CROSSCOMPILING})
25 set(GTEST_CMAKE_ARGS
26 -DCMAKE_TOOLCHAIN_FILE=${CMAKE_MODULE_PATH}/LinuxCrossCompile.cmake)
27endif()
28
29ExternalProject_Add(
30 GMock
31 #where to build in source tree
32 PREFIX ${GMOCK_PREFIX}
33 #where the source is external to the project
34 SOURCE_DIR ${GMOCK_INSTALL_DIR}
35 #forward the compilers to the subproject so cross-arch builds work
36 CMAKE_ARGS ${GTEST_CMAKE_ARGS}
37 BINARY_DIR ${GMOCK_BINARY_DIR}
38
39 #we don't need to install, so skip
40 INSTALL_COMMAND ""
41)
42
43set(GMOCK_LIBRARY ${GMOCK_BINARY_DIR}/libgmock.a)
44set(GMOCK_MAIN_LIBRARY ${GMOCK_BINARY_DIR}/libgmock_main.a)
45set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARY} ${GMOCK_MAIN_LIBRARY})
46set(GTEST_LIBRARY ${GTEST_BINARY_DIR}/libgtest.a)
47set(GTEST_MAIN_LIBRARY ${GTEST_BINARY_DIR}/libgtest_main.a)
48set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY})
49set(GTEST_ALL_LIBRARIES ${GTEST_BOTH_LIBRARIES} ${GMOCK_BOTH_LIBRARIES})
50
51find_package_handle_standard_args(GTest DEFAULT_MSG
52 GMOCK_INCLUDE_DIR
53 GTEST_INCLUDE_DIR)
054
=== modified file 'debian/control'
--- debian/control 2014-07-15 13:50:07 +0000
+++ debian/control 2014-07-18 09:49:03 +0000
@@ -5,6 +5,7 @@
5Build-Depends: cmake,5Build-Depends: cmake,
6 cmake-data,6 cmake-data,
7 debhelper (>= 9),7 debhelper (>= 9),
8 google-mock,
8 libandroid-properties-dev [i386 amd64 armhf],9 libandroid-properties-dev [i386 amd64 armhf],
9 libboost-chrono-dev,10 libboost-chrono-dev,
10 libboost-date-time-dev,11 libboost-date-time-dev,
1112
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 2014-06-16 16:53:55 +0000
+++ src/CMakeLists.txt 2014-07-18 09:49:03 +0000
@@ -15,18 +15,17 @@
15# Authored by: Robert Ancell <robert.ancell@canonical.com>15# Authored by: Robert Ancell <robert.ancell@canonical.com>
1616
17set(USC_SRCS17set(USC_SRCS
18 main.cpp18 asio_dm_connection.cpp
19 dbus_screen.cpp19 dbus_screen.cpp
20 dbus_screen.h20 external_spinner.cpp
21 powerd_mediator.cpp21 powerd_mediator.cpp
22 dm_connection.cpp22 powerkey_handler.cpp
23 dm_connection.h23 screen_state_handler.cpp
24 server_configuration.cpp
25 session_coordinator.cpp
26 session_switcher.cpp
27 surface_coordinator.cpp
24 system_compositor.cpp28 system_compositor.cpp
25 system_compositor.h
26 screen_state_handler.cpp
27 screen_state_handler.h
28 powerkey_handler.cpp
29 powerkey_handler.h
30)29)
3130
32qt5_generate_dbus_interface(dbus_screen.h com.canonical.Unity.Screen.xml)31qt5_generate_dbus_interface(dbus_screen.h com.canonical.Unity.Screen.xml)
@@ -36,9 +35,14 @@
36 dbus_screen_adaptor DBusScreenAdaptor)35 dbus_screen_adaptor DBusScreenAdaptor)
3736
38# Compile system compositor37# Compile system compositor
38add_library(
39 usc STATIC
40 ${USC_SRCS}
41)
42
39add_executable(43add_executable(
40 unity-system-compositor44 unity-system-compositor
41 ${USC_SRCS}45 main.cpp
42)46)
4347
44include_directories(48include_directories(
@@ -52,15 +56,20 @@
52 -DDEFAULT_SPINNER="${CMAKE_INSTALL_FULL_BINDIR}/unity-system-compositor-spinner"56 -DDEFAULT_SPINNER="${CMAKE_INSTALL_FULL_BINDIR}/unity-system-compositor-spinner"
53)57)
5458
55# Link against libmirserver59qt5_use_modules(usc Core DBus)
60
56link_directories(${MIRSERVER_LIBRARY_DIRS})61link_directories(${MIRSERVER_LIBRARY_DIRS})
57target_link_libraries(unity-system-compositor62
63target_link_libraries(usc
58 ${MIRSERVER_LDFLAGS}64 ${MIRSERVER_LDFLAGS}
59 pthread65 pthread
60 ${Boost_LIBRARIES}66 ${Boost_LIBRARIES}
61 ${GLESv2_LIBRARIES}67 ${GLESv2_LIBRARIES}
62)68)
63qt5_use_modules(unity-system-compositor Core DBus)69
70target_link_libraries(unity-system-compositor
71 usc
72)
6473
65# Install into bin directory74# Install into bin directory
66install(TARGETS unity-system-compositor75install(TARGETS unity-system-compositor
6776
=== renamed file 'src/dm_connection.cpp' => 'src/asio_dm_connection.cpp'
--- src/dm_connection.cpp 2013-07-09 19:18:59 +0000
+++ src/asio_dm_connection.cpp 2014-07-18 09:49:03 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright © 2013 Canonical Ltd.2 * Copyright © 2013-2014 Canonical Ltd.
3 *3 *
4 * This program is free software: you can redistribute it and/or modify4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as5 * it under the terms of the GNU General Public License version 3 as
@@ -14,37 +14,62 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Robert Ancell <robert.ancell@canonical.com>16 * Authored by: Robert Ancell <robert.ancell@canonical.com>
17 * Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */18 */
1819
19#include "dm_connection.h"20#include "asio_dm_connection.h"
2021
21#include <boost/signals2.hpp>
22#include <iostream>22#include <iostream>
23#include <thread>
2324
24namespace ba = boost::asio;25namespace ba = boost::asio;
25namespace bs = boost::system;26namespace bs = boost::system;
2627
27void DMConnection::start()28usc::AsioDMConnection::AsioDMConnection(
29 int from_dm_fd, int to_dm_fd,
30 std::shared_ptr<DMMessageHandler> const& dm_message_handler)
31 : from_dm_pipe{io_service, from_dm_fd},
32 to_dm_pipe{io_service, to_dm_fd},
33 dm_message_handler{dm_message_handler}
34{
35}
36
37usc::AsioDMConnection::~AsioDMConnection()
38{
39 io_service.stop();
40 if (io_thread.joinable())
41 io_thread.join();
42}
43
44void usc::AsioDMConnection::start()
28{45{
29 std::cerr << "dm_connection_start" << std::endl;46 std::cerr << "dm_connection_start" << std::endl;
47
48 send_ready();
30 read_header();49 read_header();
50
51 io_thread = std::thread{
52 [this]
53 {
54 io_service.run();
55 }};
31}56}
3257
33void DMConnection::send_ready()58void usc::AsioDMConnection::send_ready()
34{59{
35 send(USCMessageID::ready, "");60 send(USCMessageID::ready, "");
36}61}
3762
38void DMConnection::read_header()63void usc::AsioDMConnection::read_header()
39{64{
40 ba::async_read(from_dm_pipe,65 ba::async_read(from_dm_pipe,
41 ba::buffer(message_header_bytes),66 ba::buffer(message_header_bytes),
42 boost::bind(&DMConnection::on_read_header,67 std::bind(&AsioDMConnection::on_read_header,
43 this,68 this,
44 ba::placeholders::error));69 std::placeholders::_1));
45}70}
4671
47void DMConnection::on_read_header(const bs::error_code& ec)72void usc::AsioDMConnection::on_read_header(bs::error_code const& ec)
48{73{
49 if (!ec)74 if (!ec)
50 {75 {
@@ -52,15 +77,15 @@
52 ba::async_read(from_dm_pipe,77 ba::async_read(from_dm_pipe,
53 message_payload_buffer,78 message_payload_buffer,
54 ba::transfer_exactly(payload_length),79 ba::transfer_exactly(payload_length),
55 boost::bind(&DMConnection::on_read_payload,80 std::bind(&AsioDMConnection::on_read_payload,
56 this,81 this,
57 ba::placeholders::error));82 std::placeholders::_1));
58 }83 }
59 else84 else
60 std::cerr << "Failed to read header" << std::endl;85 std::cerr << "Failed to read header" << std::endl;
61}86}
6287
63void DMConnection::on_read_payload(const bs::error_code& ec)88void usc::AsioDMConnection::on_read_payload(const bs::error_code& ec)
64{89{
65 if (!ec)90 if (!ec)
66 {91 {
@@ -86,8 +111,7 @@
86 ss << &message_payload_buffer;111 ss << &message_payload_buffer;
87 auto client_name = ss.str();112 auto client_name = ss.str();
88 std::cerr << "set_active_session '" << client_name << "'" << std::endl;113 std::cerr << "set_active_session '" << client_name << "'" << std::endl;
89 if (handler)114 dm_message_handler->set_active_session(client_name);
90 handler->set_active_session(client_name);
91 break;115 break;
92 }116 }
93 case USCMessageID::set_next_session:117 case USCMessageID::set_next_session:
@@ -96,8 +120,7 @@
96 ss << &message_payload_buffer;120 ss << &message_payload_buffer;
97 auto client_name = ss.str();121 auto client_name = ss.str();
98 std::cerr << "set_next_session '" << client_name << "'" << std::endl;122 std::cerr << "set_next_session '" << client_name << "'" << std::endl;
99 if (handler)123 dm_message_handler->set_next_session(client_name);
100 handler->set_next_session(client_name);
101 break;124 break;
102 }125 }
103 default:126 default:
@@ -111,7 +134,7 @@
111 read_header();134 read_header();
112}135}
113136
114void DMConnection::send(USCMessageID id, std::string const& body)137void usc::AsioDMConnection::send(USCMessageID id, std::string const& body)
115{138{
116 const size_t size = body.size();139 const size_t size = body.size();
117 const uint16_t _id = (uint16_t) id;140 const uint16_t _id = (uint16_t) id;
118141
=== added file 'src/asio_dm_connection.h'
--- src/asio_dm_connection.h 1970-01-01 00:00:00 +0000
+++ src/asio_dm_connection.h 2014-07-18 09:49:03 +0000
@@ -0,0 +1,73 @@
1/*
2 * Copyright © 2013-2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Robert Ancell <robert.ancell@canonical.com>
17 * Alexandros Frantzis <alexandros.frantzis@canonical.com>
18 */
19
20#ifndef USC_ASIO_DM_CONNECTION_H_
21#define USC_ASIO_DM_CONNECTION_H_
22
23#include "dm_connection.h"
24
25#include <boost/asio.hpp>
26#include <thread>
27
28namespace usc
29{
30
31class AsioDMConnection : public DMConnection
32{
33public:
34 AsioDMConnection(
35 int from_dm_fd, int to_dm_fd,
36 std::shared_ptr<DMMessageHandler> const& dm_message_handler);
37 ~AsioDMConnection();
38
39 void start() override;
40
41private:
42 enum class USCMessageID
43 {
44 ping = 0,
45 pong = 1,
46 ready = 2,
47 session_connected = 3,
48 set_active_session = 4,
49 set_next_session = 5,
50 };
51
52 void send_ready();
53 void read_header();
54 void on_read_header(const boost::system::error_code& ec);
55 void on_read_payload(const boost::system::error_code& ec);
56 void send(USCMessageID id, std::string const& body);
57
58 boost::asio::io_service io_service;
59 boost::asio::posix::stream_descriptor from_dm_pipe;
60 boost::asio::posix::stream_descriptor to_dm_pipe;
61 std::thread io_thread;
62 std::shared_ptr<DMMessageHandler> const dm_message_handler;
63
64 static size_t const size_of_header = 4;
65 unsigned char message_header_bytes[size_of_header];
66 boost::asio::streambuf message_payload_buffer;
67 std::vector<char> write_buffer;
68
69};
70
71}
72
73#endif /* USC_ASIO_DM_CONNECTION_H_ */
074
=== modified file 'src/dm_connection.h'
--- src/dm_connection.h 2013-11-13 13:53:51 +0000
+++ src/dm_connection.h 2014-07-18 09:49:03 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright © 2013 Canonical Ltd.2 * Copyright © 2014 Canonical Ltd.
3 *3 *
4 * This program is free software: you can redistribute it and/or modify4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as5 * it under the terms of the GNU General Public License version 3 as
@@ -13,60 +13,37 @@
13 * You should have received a copy of the GNU General Public License13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Robert Ancell <robert.ancell@canonical.com>16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */17 */
1818
19#ifndef DM_CONNECTION_H_19#ifndef USC_DM_CONNECTION_H_
20#define DM_CONNECTION_H_20#define USC_DM_CONNECTION_H_
2121
22#include <boost/asio.hpp>22#include <string>
23
24namespace usc
25{
2326
24class DMMessageHandler27class DMMessageHandler
25{28{
26public:29public:
27 virtual void set_active_session(std::string client_name) = 0;30 virtual void set_active_session(std::string const& client_name) = 0;
28 virtual void set_next_session(std::string client_name) = 0;31 virtual void set_next_session(std::string const& client_name) = 0;
29};
30
31enum class USCMessageID
32{
33 ping = 0,
34 pong = 1,
35 ready = 2,
36 session_connected = 3,
37 set_active_session = 4,
38 set_next_session = 5,
39};32};
4033
41class DMConnection34class DMConnection
42{35{
43public:36public:
44 DMConnection(boost::asio::io_service& io_service, int from_dm_fd, int to_dm_fd) :37 virtual ~DMConnection() = default;
45 from_dm_pipe(io_service, from_dm_fd),38
46 to_dm_pipe(io_service, to_dm_fd) {};39 virtual void start() = 0;
4740
48 void set_handler(DMMessageHandler *handler)41protected:
49 {42 DMConnection() = default;
50 this->handler = handler;43 DMConnection(DMConnection const&) = delete;
51 }44 DMConnection& operator=(DMConnection const&) = delete;
52
53 void start();
54
55 void send_ready();
56
57private:
58 DMMessageHandler *handler;
59 boost::asio::posix::stream_descriptor from_dm_pipe;
60 boost::asio::posix::stream_descriptor to_dm_pipe;
61 static size_t const size_of_header = 4;
62 unsigned char message_header_bytes[size_of_header];
63 boost::asio::streambuf message_payload_buffer;
64 std::vector<char> write_buffer;
65
66 void read_header();
67 void on_read_header(const boost::system::error_code& ec);
68 void on_read_payload(const boost::system::error_code& ec);
69 void send(USCMessageID id, std::string const& body);
70};45};
7146
72#endif /* DM_CONNECTION_H_ */47}
48
49#endif /* USC_DM_CONNECTION_H_ */
7350
=== added file 'src/external_spinner.cpp'
--- src/external_spinner.cpp 1970-01-01 00:00:00 +0000
+++ src/external_spinner.cpp 2014-07-18 09:49:03 +0000
@@ -0,0 +1,54 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#include "external_spinner.h"
20
21usc::ExternalSpinner::ExternalSpinner(
22 std::string const& executable,
23 std::string const& mir_socket)
24 : executable{executable},
25 mir_socket{mir_socket}
26{
27}
28
29usc::ExternalSpinner::~ExternalSpinner()
30{
31 kill();
32}
33
34void usc::ExternalSpinner::ensure_running()
35{
36 if (executable.empty() || process.state() != QProcess::NotRunning)
37 return;
38
39 // Launch spinner process to provide default background when a session isn't ready
40 QStringList env = QProcess::systemEnvironment();
41 env << "MIR_SOCKET=" + QString::fromStdString(mir_socket);
42 process.setEnvironment(env);
43 process.start(executable.c_str());
44}
45
46void usc::ExternalSpinner::kill()
47{
48 process.close();
49}
50
51pid_t usc::ExternalSpinner::pid()
52{
53 return process.processId();
54}
055
=== added file 'src/external_spinner.h'
--- src/external_spinner.h 1970-01-01 00:00:00 +0000
+++ src/external_spinner.h 2014-07-18 09:49:03 +0000
@@ -0,0 +1,49 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#ifndef USC_EXTERNAL_SPINNER_H_
20#define USC_EXTERNAL_SPINNER_H_
21
22#include "spinner.h"
23
24#include <QProcess>
25#include <string>
26
27namespace usc
28{
29
30class ExternalSpinner : public Spinner
31{
32public:
33 ExternalSpinner(std::string const& executable,
34 std::string const& mir_socket);
35 ~ExternalSpinner();
36
37 void ensure_running() override;
38 void kill() override;
39 pid_t pid() override;
40
41private:
42 std::string const executable;
43 std::string const mir_socket;
44 QProcess process;
45};
46
47}
48
49#endif
050
=== modified file 'src/main.cpp'
--- src/main.cpp 2013-10-31 22:00:19 +0000
+++ src/main.cpp 2014-07-18 09:49:03 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright © 2013 Canonical Ltd.2 * Copyright © 2013-2014 Canonical Ltd.
3 *3 *
4 * This program is free software: you can redistribute it and/or modify4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as5 * it under the terms of the GNU General Public License version 3 as
@@ -14,17 +14,22 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Robert Ancell <robert.ancell@canonical.com>16 * Authored by: Robert Ancell <robert.ancell@canonical.com>
17 * Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */18 */
1819
19#include "system_compositor.h"20#include "system_compositor.h"
21#include "server_configuration.h"
22
20#include <mir/report_exception.h>23#include <mir/report_exception.h>
21#include <iostream>24#include <iostream>
2225
23int main(int argc, char *argv[])26int main(int argc, char *argv[])
24try27try
25{28{
26 SystemCompositor system_compositor;29 auto const config = std::make_shared<usc::ServerConfiguration>(argc, argv);
27 system_compositor.run(argc, argv);30
31 usc::SystemCompositor system_compositor{config};
32 system_compositor.run();
2833
29 return 0;34 return 0;
30}35}
3136
=== added file 'src/server_configuration.cpp'
--- src/server_configuration.cpp 1970-01-01 00:00:00 +0000
+++ src/server_configuration.cpp 2014-07-18 09:49:03 +0000
@@ -0,0 +1,196 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#include "server_configuration.h"
20#include "external_spinner.h"
21#include "session_coordinator.h"
22#include "surface_coordinator.h"
23#include "asio_dm_connection.h"
24#include "session_switcher.h"
25
26#include <mir/options/default_configuration.h>
27#include <mir/input/cursor_listener.h>
28#include <mir/server_status_listener.h>
29#include <mir/shell/focus_controller.h>
30#include <mir/scene/session.h>
31
32#include <boost/program_options.hpp>
33
34namespace msh = mir::shell;
35namespace ms = mir::scene;
36namespace mf = mir::frontend;
37namespace mi = mir::input;
38namespace mo = mir::options;
39namespace po = boost::program_options;
40
41namespace
42{
43
44class ConfigurationOptions : public mo::DefaultConfiguration
45{
46public:
47 ConfigurationOptions(int argc, char const* argv[]) :
48 DefaultConfiguration(argc, argv)
49 {
50 add_options()
51 ("from-dm-fd", po::value<int>(), "File descriptor of read end of pipe from display manager [int]")
52 ("to-dm-fd", po::value<int>(), "File descriptor of write end of pipe to display manager [int]")
53 ("blacklist", po::value<std::string>(), "Video blacklist regex to use")
54 ("version", "Show version of Unity System Compositor")
55 ("spinner", po::value<std::string>(), "Path to spinner executable")
56 ("public-socket", po::value<bool>(), "Make the socket file publicly writable")
57 ("enable-hardware-cursor", po::value<bool>(), "Enable the hardware cursor (disabled by default)")
58 ("inactivity-display-off-timeout", po::value<int>(), "The time in seconds before the screen is turned off when there are no active sessions")
59 ("inactivity-display-dim-timeout", po::value<int>(), "The time in seconds before the screen is dimmed when there are no active sessions")
60 ("shutdown-timeout", po::value<int>(), "The time in milli-seconds the power key must be held to initiate a clean system shutdown")
61 ("power-key-ignore-timeout", po::value<int>(), "The time in milli-seconds the power key must be held to ignore - must be less than shutdown-timeout")
62 ("disable-inactivity-policy", po::value<bool>(), "Disables handling user inactivity and power key");
63 }
64
65 void parse_config_file(
66 boost::program_options::options_description& options_description,
67 mo::ProgramOption& options) const override
68 {
69 options.parse_file(options_description, "unity-system-compositor.conf");
70 }
71};
72
73}
74
75usc::ServerConfiguration::ServerConfiguration(int argc, char** argv)
76 : mir::DefaultServerConfiguration(
77 std::make_shared<ConfigurationOptions>(argc, const_cast<char const **>(argv)))
78{
79}
80
81std::shared_ptr<mi::CursorListener>
82usc::ServerConfiguration::the_cursor_listener()
83{
84 struct NullCursorListener : public mi::CursorListener
85 {
86 void cursor_moved_to(float, float) override
87 {
88 }
89 };
90
91 // This is a workaround for u8 desktop preview in 14.04 for the lack of client cursor API.
92 // We need to disable the cursor for XMir but leave it on for the desktop preview.
93 // Luckily as it stands they run inside seperate instances of USC. ~racarr
94 if (enable_hardware_cursor())
95 return mir::DefaultServerConfiguration::the_cursor_listener();
96 else
97 return std::make_shared<NullCursorListener>();
98}
99
100std::shared_ptr<mir::ServerStatusListener>
101usc::ServerConfiguration::the_server_status_listener()
102{
103 struct ServerStatusListener : public mir::ServerStatusListener
104 {
105 ServerStatusListener(
106 std::shared_ptr<msh::FocusController> const& focus_controller)
107 : focus_controller{focus_controller}
108 {
109 }
110
111 void paused() override
112 {
113 std::cerr << "pause" << std::endl;
114
115 if (auto active_session = weak_active_session().lock())
116 active_session->set_lifecycle_state(mir_lifecycle_state_will_suspend);
117 }
118
119 void resumed() override
120 {
121 std::cerr << "resume" << std::endl;
122
123 if (auto active_session = weak_active_session().lock())
124 active_session->set_lifecycle_state(mir_lifecycle_state_resumed);
125 }
126
127 void started() override
128 {
129 }
130
131 std::weak_ptr<ms::Session> weak_active_session()
132 {
133 return focus_controller->focussed_application();
134 }
135
136 std::shared_ptr<msh::FocusController> const focus_controller;
137 };
138
139 return std::make_shared<ServerStatusListener>(the_focus_controller());
140}
141
142std::shared_ptr<mir::scene::SessionCoordinator>
143usc::ServerConfiguration::wrap_session_coordinator(
144 std::shared_ptr<ms::SessionCoordinator> const& wrapped)
145{
146 return std::make_shared<SessionCoordinator>(
147 wrapped,
148 the_session_switcher());
149}
150
151std::shared_ptr<mir::scene::SurfaceCoordinator>
152usc::ServerConfiguration::wrap_surface_coordinator(
153 std::shared_ptr<ms::SurfaceCoordinator> const& wrapped)
154{
155 return std::make_shared<SurfaceCoordinator>(
156 wrapped,
157 the_session_switcher());
158}
159
160std::shared_ptr<usc::Spinner> usc::ServerConfiguration::the_spinner()
161{
162 return spinner(
163 [this]
164 {
165 return std::make_shared<ExternalSpinner>(
166 spinner_executable(),
167 get_socket_file());
168 });
169}
170
171std::shared_ptr<usc::SessionSwitcher> usc::ServerConfiguration::the_session_switcher()
172{
173 return session_switcher(
174 [this]
175 {
176 return std::make_shared<SessionSwitcher>(
177 the_spinner());
178 });
179}
180
181std::shared_ptr<usc::DMMessageHandler> usc::ServerConfiguration::the_dm_message_handler()
182{
183 return the_session_switcher();
184}
185
186std::shared_ptr<usc::DMConnection> usc::ServerConfiguration::the_dm_connection()
187{
188 return dm_connection(
189 [this]
190 {
191 return std::make_shared<AsioDMConnection>(
192 the_options()->get("from-dm-fd", -1),
193 the_options()->get("to-dm-fd", -1),
194 the_dm_message_handler());
195 });
196}
0197
=== added file 'src/server_configuration.h'
--- src/server_configuration.h 1970-01-01 00:00:00 +0000
+++ src/server_configuration.h 2014-07-18 09:49:03 +0000
@@ -0,0 +1,119 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#ifndef USC_SERVER_CONFIGURATION_H_
20#define USC_SERVER_CONFIGURATION_H_
21
22#include <mir/default_server_configuration.h>
23#include <mir/options/option.h>
24
25namespace usc
26{
27class Spinner;
28class SessionSwitcher;
29class DMMessageHandler;
30class DMConnection;
31
32class ServerConfiguration : public mir::DefaultServerConfiguration
33{
34public:
35 ServerConfiguration(int argc, char** argv);
36
37 virtual std::shared_ptr<Spinner> the_spinner();
38 virtual std::shared_ptr<DMMessageHandler> the_dm_message_handler();
39 virtual std::shared_ptr<DMConnection> the_dm_connection();
40
41 bool show_version()
42 {
43 return the_options()->is_set("version");
44 }
45
46 int inactivity_display_off_timeout()
47 {
48 return the_options()->get("inactivity-display-off-timeout", 60);
49 }
50
51 int inactivity_display_dim_timeout()
52 {
53 return the_options()->get("inactivity-display-dim-timeout", 45);
54 }
55
56 int shutdown_timeout()
57 {
58 return the_options()->get("shutdown-timeout", 5000);
59 }
60
61 int power_key_ignore_timeout()
62 {
63 return the_options()->get("power-key-ignore-timeout", 1500);
64 }
65
66 bool enable_hardware_cursor()
67 {
68 return the_options()->get("enable-hardware-cursor", false);
69 }
70
71 bool disable_inactivity_policy()
72 {
73 return the_options()->get("disable-inactivity-policy", false);
74 }
75
76 std::string blacklist()
77 {
78 auto x = the_options()->get("blacklist", "");
79 //boost::trim(x);
80 return x;
81 }
82
83 std::string spinner_executable()
84 {
85 // TODO: once our default spinner is ready for use everywhere, replace
86 // default value with DEFAULT_SPINNER instead of the empty string.
87 auto x = the_options()->get("spinner", "");
88 //boost::trim(x);
89 return x;
90 }
91
92 bool public_socket()
93 {
94 return !the_options()->is_set("no-file") && the_options()->get("public-socket", true);
95 }
96
97 std::string get_socket_file()
98 {
99 // the_socket_file is private, so we have to re-implement it here
100 return the_options()->get("file", "/tmp/mir_socket");
101 }
102
103protected:
104 virtual std::shared_ptr<SessionSwitcher> the_session_switcher();
105 std::shared_ptr<mir::input::CursorListener> the_cursor_listener() override;
106 std::shared_ptr<mir::ServerStatusListener> the_server_status_listener() override;
107 std::shared_ptr<mir::scene::SessionCoordinator> wrap_session_coordinator(
108 std::shared_ptr<mir::scene::SessionCoordinator> const& wrapped) override;
109 std::shared_ptr<mir::scene::SurfaceCoordinator> wrap_surface_coordinator(
110 std::shared_ptr<mir::scene::SurfaceCoordinator> const& wrapped) override;
111
112 mir::CachedPtr<Spinner> spinner;
113 mir::CachedPtr<DMConnection> dm_connection;
114 mir::CachedPtr<SessionSwitcher> session_switcher;
115};
116
117}
118
119#endif
0120
=== added file 'src/session_coordinator.cpp'
--- src/session_coordinator.cpp 1970-01-01 00:00:00 +0000
+++ src/session_coordinator.cpp 2014-07-18 09:49:03 +0000
@@ -0,0 +1,117 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#include "session_coordinator.h"
20#include "session_switcher.h"
21
22#include <mir/scene/session.h>
23
24#include <iostream>
25
26namespace msh = mir::shell;
27namespace ms = mir::scene;
28namespace mf = mir::frontend;
29
30namespace
31{
32
33class UscSession : public usc::Session
34{
35public:
36 UscSession(
37 std::shared_ptr<ms::Session> const& scene_session,
38 msh::FocusController& focus_controller)
39 : scene_session{scene_session},
40 focus_controller(focus_controller)
41 {
42 }
43
44 std::string name()
45 {
46 return scene_session->name();
47 }
48
49 void show() override
50 {
51 scene_session->show();
52 }
53
54 void hide() override
55 {
56 scene_session->hide();
57 }
58
59 void raise_and_focus() override
60 {
61 focus_controller.set_focus_to(scene_session);
62 }
63
64 bool corresponds_to(mir::scene::Session const* s) override
65 {
66 return scene_session.get() == s;
67 }
68
69 std::shared_ptr<ms::Session> const scene_session;
70 msh::FocusController& focus_controller;
71};
72
73}
74
75usc::SessionCoordinator::SessionCoordinator(
76 std::shared_ptr<ms::SessionCoordinator> const& wrapped,
77 std::shared_ptr<SessionSwitcher> const& session_switcher)
78 : msh::SessionCoordinatorWrapper{wrapped},
79 session_switcher{session_switcher}
80{
81}
82
83std::shared_ptr<mf::Session>
84usc::SessionCoordinator::open_session(
85 pid_t client_pid,
86 std::string const& name,
87 std::shared_ptr<mf::EventSink> const& sink)
88{
89 std::cerr << "Opening session " << name << std::endl;
90
91 // We need ms::Session objects because that is what the focus controller
92 // works with. But the mf::SessionCoordinator interface deals with mf::Session objects.
93 // So we cast here since in practice, these objects are also ms::Sessions.
94 auto orig = std::dynamic_pointer_cast<ms::Session>(
95 msh::SessionCoordinatorWrapper::open_session(client_pid, name, sink));
96 if (!orig)
97 {
98 std::cerr << "Unexpected non-shell session" << std::endl;
99 return std::shared_ptr<mf::Session>();
100 }
101
102 auto const usc_session = std::make_shared<UscSession>(orig, *this);
103
104 session_switcher->add(usc_session, client_pid);
105
106 return orig;
107}
108
109void usc::SessionCoordinator::close_session(
110 std::shared_ptr<mf::Session> const& session)
111{
112 std::cerr << "Closing session " << session->name() << std::endl;
113
114 msh::SessionCoordinatorWrapper::close_session(session);
115
116 session_switcher->remove(session->name());
117}
0118
=== added file 'src/session_coordinator.h'
--- src/session_coordinator.h 1970-01-01 00:00:00 +0000
+++ src/session_coordinator.h 2014-07-18 09:49:03 +0000
@@ -0,0 +1,54 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#ifndef USC_SESSION_COORDINATOR_H_
20#define USC_SESSION_COORDINATOR_H_
21
22#include <mir/shell/session_coordinator_wrapper.h>
23
24#include <memory>
25
26namespace mir
27{
28namespace scene { class SurfaceCoordinator; }
29}
30
31namespace usc
32{
33class SessionSwitcher;
34
35class SessionCoordinator : public mir::shell::SessionCoordinatorWrapper
36{
37public:
38 SessionCoordinator(
39 std::shared_ptr<mir::scene::SessionCoordinator> const& wrapped,
40 std::shared_ptr<SessionSwitcher> const& session_switcher);
41
42private:
43 std::shared_ptr<mir::frontend::Session> open_session(
44 pid_t client_pid,
45 std::string const& name,
46 std::shared_ptr<mir::frontend::EventSink> const& sink) override;
47 void close_session(std::shared_ptr<mir::frontend::Session> const& session) override;
48
49 std::shared_ptr<SessionSwitcher> const session_switcher;
50};
51
52}
53
54#endif
055
=== added file 'src/session_switcher.cpp'
--- src/session_switcher.cpp 1970-01-01 00:00:00 +0000
+++ src/session_switcher.cpp 2014-07-18 09:49:03 +0000
@@ -0,0 +1,190 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#include "session_switcher.h"
20#include "spinner.h"
21
22usc::SessionSwitcher::SessionSwitcher(std::shared_ptr<Spinner> const& spinner)
23 : spinner_process{spinner},
24 booting{true}
25{
26}
27
28void usc::SessionSwitcher::add(std::shared_ptr<Session> const& session, pid_t pid)
29{
30 std::lock_guard<std::mutex> lock{mutex};
31
32 if (pid == spinner_process->pid())
33 spinner_name = session->name();
34
35 sessions[session->name()] = SessionInfo(session);
36 update_displayed_sessions();
37}
38
39void usc::SessionSwitcher::remove(std::string const& name)
40{
41 std::lock_guard<std::mutex> lock{mutex};
42
43 if (name == spinner_name)
44 spinner_name = "";
45
46 sessions.erase(name);
47 update_displayed_sessions();
48}
49
50void usc::SessionSwitcher::set_active_session(std::string const& name)
51{
52 std::lock_guard<std::mutex> lock{mutex};
53
54 active_name = name;
55 update_displayed_sessions();
56}
57
58void usc::SessionSwitcher::set_next_session(std::string const& name)
59{
60 std::lock_guard<std::mutex> lock{mutex};
61
62 next_name = name;
63 update_displayed_sessions();
64}
65
66void usc::SessionSwitcher::mark_ready(mir::scene::Session const* scene_session)
67{
68 std::lock_guard<std::mutex> lock{mutex};
69
70 for (auto& pair : sessions)
71 {
72 if (pair.second.session && pair.second.session->corresponds_to(scene_session))
73 pair.second.ready = true;
74 }
75
76 update_displayed_sessions();
77}
78
79void usc::SessionSwitcher::update_displayed_sessions()
80{
81 hide_uninteresting_sessions();
82
83 bool show_spinner = false;
84 ShowMode show_spinner_mode{ShowMode::as_next};
85 bool const allowed_to_display_active =
86 is_session_ready_for_display(next_name) ||
87 !is_session_expected_to_become_ready(next_name) ||
88 !booting;
89 bool show_active = false;
90
91 if (allowed_to_display_active && is_session_ready_for_display(active_name))
92 {
93 show_session(active_name, ShowMode::as_active);
94 show_active = true;
95 booting = false;
96 }
97 else if (is_session_expected_to_become_ready(active_name))
98 {
99 show_spinner = true;
100 show_spinner_mode = ShowMode::as_active;
101 }
102
103 bool const allowed_to_display_next = !show_spinner && show_active;
104
105 if (allowed_to_display_next)
106 {
107 if (is_session_ready_for_display(next_name))
108 {
109 show_session(next_name, ShowMode::as_next);
110 }
111 else if (is_session_expected_to_become_ready(next_name))
112 {
113 show_spinner = true;
114 show_spinner_mode = ShowMode::as_next;
115 }
116 }
117 else if (is_session_ready_for_display(next_name))
118 {
119 hide_session(next_name);
120 }
121
122 if (show_spinner)
123 ensure_spinner_will_be_shown(show_spinner_mode);
124 else
125 ensure_spinner_is_not_running();
126}
127
128void usc::SessionSwitcher::hide_uninteresting_sessions()
129{
130 for (auto const& pair : sessions)
131 {
132 if (pair.second.session->name() != active_name &&
133 pair.second.session->name() != next_name)
134 {
135 pair.second.session->hide();
136 }
137 }
138}
139
140bool usc::SessionSwitcher::is_session_ready_for_display(std::string const& name)
141{
142 auto const iter = sessions.find(name);
143 if (iter == sessions.end())
144 return false;
145
146 return iter->second.session && iter->second.ready;
147}
148
149bool usc::SessionSwitcher::is_session_expected_to_become_ready(std::string const& name)
150{
151 return !name.empty();
152}
153
154void usc::SessionSwitcher::show_session(
155 std::string const& name,
156 ShowMode show_mode)
157{
158 auto& session = sessions[name].session;
159
160 if (show_mode == ShowMode::as_active)
161 session->raise_and_focus();
162
163 session->show();
164}
165
166void usc::SessionSwitcher::hide_session(std::string const& name)
167{
168 auto& session = sessions[name].session;
169 session->hide();
170}
171
172void usc::SessionSwitcher::ensure_spinner_will_be_shown(ShowMode show_mode)
173{
174 auto const iter = sessions.find(spinner_name);
175 if (iter == sessions.end())
176 {
177 spinner_process->ensure_running();
178 }
179 else
180 {
181 if (show_mode == ShowMode::as_active)
182 iter->second.session->raise_and_focus();
183 iter->second.session->show();
184 }
185}
186
187void usc::SessionSwitcher::ensure_spinner_is_not_running()
188{
189 spinner_process->kill();
190}
0191
=== added file 'src/session_switcher.h'
--- src/session_switcher.h 1970-01-01 00:00:00 +0000
+++ src/session_switcher.h 2014-07-18 09:49:03 +0000
@@ -0,0 +1,98 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#ifndef USC_SESSION_SWITCHER_H_
20#define USC_SESSION_SWITCHER_H_
21
22#include "dm_connection.h"
23
24#include <map>
25#include <memory>
26#include <mutex>
27
28namespace mir { namespace scene { class Session; }};
29namespace usc
30{
31class Spinner;
32
33class Session
34{
35public:
36 virtual ~Session() = default;
37
38 virtual std::string name() = 0;
39 virtual void show() = 0;
40 virtual void hide() = 0;
41 virtual void raise_and_focus() = 0;
42 virtual bool corresponds_to(mir::scene::Session const*) = 0;
43
44protected:
45 Session() = default;
46 Session(Session const&) = delete;
47 Session& operator=(Session const&) = delete;
48};
49
50class SessionSwitcher : public DMMessageHandler
51{
52public:
53 SessionSwitcher(std::shared_ptr<Spinner> const& spinner);
54
55 void add(std::shared_ptr<Session> const& session, pid_t pid);
56 void remove(std::string const& name);
57 void mark_ready(mir::scene::Session const*);
58
59 /* From DMMessageHandler */
60 void set_active_session(std::string const& name) override;
61 void set_next_session(std::string const& name) override;
62
63private:
64 enum class ShowMode { as_active, as_next };
65
66 void update_displayed_sessions();
67 void hide_uninteresting_sessions();
68 bool is_session_ready_for_display(std::string const& name);
69 bool is_session_expected_to_become_ready(std::string const& name);
70 void show_session(std::string const& name, ShowMode show_mode);
71 void hide_session(std::string const& name);
72 void ensure_spinner_will_be_shown(ShowMode show_mode);
73 void ensure_spinner_is_not_running();
74
75 struct SessionInfo
76 {
77 SessionInfo() = default;
78 SessionInfo(std::shared_ptr<Session> session)
79 : session{session}
80 {
81 }
82 std::shared_ptr<Session> session;
83 bool ready = false;
84 };
85
86 std::mutex mutex;
87 std::shared_ptr<Spinner> const spinner_process;
88 std::map<std::string, SessionInfo> sessions;
89 std::string active_name;
90 std::string next_name;
91 std::string spinner_name;
92 bool booting;
93};
94
95}
96
97#endif
98
099
=== added file 'src/spinner.h'
--- src/spinner.h 1970-01-01 00:00:00 +0000
+++ src/spinner.h 2014-07-18 09:49:03 +0000
@@ -0,0 +1,44 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#ifndef USC_SPINNER_H_
20#define USC_SPINNER_H_
21
22#include <sys/types.h>
23
24namespace usc
25{
26
27class Spinner
28{
29public:
30 virtual ~Spinner() = default;
31
32 virtual void ensure_running() = 0;
33 virtual void kill() = 0;
34 virtual pid_t pid() = 0;
35
36protected:
37 Spinner() = default;
38 Spinner(Spinner const&) = delete;
39 Spinner& operator=(Spinner const&) = delete;
40};
41
42}
43
44#endif
045
=== added file 'src/surface_coordinator.cpp'
--- src/surface_coordinator.cpp 1970-01-01 00:00:00 +0000
+++ src/surface_coordinator.cpp 2014-07-18 09:49:03 +0000
@@ -0,0 +1,86 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#include "surface_coordinator.h"
20#include "session_switcher.h"
21
22#include <mir/scene/null_surface_observer.h>
23#include <mir/scene/surface.h>
24
25namespace ms = mir::scene;
26namespace msh = mir::shell;
27
28namespace
29{
30
31struct SessionReadyObserver : ms::NullSurfaceObserver,
32 std::enable_shared_from_this<SessionReadyObserver>
33{
34 SessionReadyObserver(
35 std::shared_ptr<usc::SessionSwitcher> const& switcher,
36 std::shared_ptr<ms::Surface> const& surface,
37 ms::Session const* session)
38 : switcher{switcher},
39 surface{surface},
40 session{session}
41 {
42 }
43
44 void frame_posted(int) override
45 {
46 ++num_frames_posted;
47 if (num_frames_posted == num_frames_for_session_ready)
48 {
49 switcher->mark_ready(session);
50 surface->remove_observer(shared_from_this());
51 }
52 }
53
54 std::shared_ptr<usc::SessionSwitcher> const switcher;
55 std::shared_ptr<ms::Surface> const surface;
56 ms::Session const* const session;
57 // We need to wait for the second frame before marking the session
58 // as ready. The first frame posted from sessions is a blank frame.
59 // TODO: Solve this issue at its root and remove this workaround
60 int const num_frames_for_session_ready{2};
61 int num_frames_posted{0};
62};
63
64}
65
66usc::SurfaceCoordinator::SurfaceCoordinator(
67 std::shared_ptr<ms::SurfaceCoordinator> const& wrapped,
68 std::shared_ptr<SessionSwitcher> const& session_switcher)
69 : msh::SurfaceCoordinatorWrapper{wrapped},
70 session_switcher{session_switcher}
71{
72}
73
74std::shared_ptr<ms::Surface> usc::SurfaceCoordinator::add_surface(
75 ms::SurfaceCreationParameters const& params,
76 ms::Session* session)
77{
78 auto const surface = msh::SurfaceCoordinatorWrapper::add_surface(params, session);
79
80 auto const session_ready_observer = std::make_shared<SessionReadyObserver>(
81 session_switcher, surface, session);
82
83 surface->add_observer(session_ready_observer);
84
85 return surface;
86}
087
=== added file 'src/surface_coordinator.h'
--- src/surface_coordinator.h 1970-01-01 00:00:00 +0000
+++ src/surface_coordinator.h 2014-07-18 09:49:03 +0000
@@ -0,0 +1,47 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#ifndef USC_SURFACE_COORDINATOR_H_
20#define USC_SURFACE_COORDINATOR_H_
21
22#include <mir/shell/surface_coordinator_wrapper.h>
23
24#include <memory>
25
26namespace usc
27{
28class SessionSwitcher;
29
30class SurfaceCoordinator : public mir::shell::SurfaceCoordinatorWrapper
31{
32public:
33 SurfaceCoordinator(
34 std::shared_ptr<mir::scene::SurfaceCoordinator> const& wrapped,
35 std::shared_ptr<SessionSwitcher> const& session_switcher);
36
37private:
38 std::shared_ptr<mir::scene::Surface> add_surface(
39 mir::scene::SurfaceCreationParameters const& params,
40 mir::scene::Session* session) override;
41
42 std::shared_ptr<SessionSwitcher> const session_switcher;
43};
44
45}
46
47#endif
048
=== modified file 'src/system_compositor.cpp'
--- src/system_compositor.cpp 2014-07-15 19:26:10 +0000
+++ src/system_compositor.cpp 2014-07-18 09:49:03 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright © 2013 Canonical Ltd.2 * Copyright © 2013-2014 Canonical Ltd.
3 *3 *
4 * This program is free software: you can redistribute it and/or modify4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as5 * it under the terms of the GNU General Public License version 3 as
@@ -14,10 +14,14 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Robert Ancell <robert.ancell@canonical.com>16 * Authored by: Robert Ancell <robert.ancell@canonical.com>
17 * Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */18 */
1819
1920
20#include "system_compositor.h"21#include "system_compositor.h"
22#include "server_configuration.h"
23#include "dm_connection.h"
24#include "spinner.h"
21#include "screen_state_handler.h"25#include "screen_state_handler.h"
22#include "powerkey_handler.h"26#include "powerkey_handler.h"
2327
@@ -26,21 +30,9 @@
26// method declarations30// method declarations
27#undef signals31#undef signals
2832
33#include <mir/input/composite_event_filter.h>
29#include <mir/run_mir.h>34#include <mir/run_mir.h>
30#include <mir/abnormal_exit.h>35#include <mir/abnormal_exit.h>
31#include <mir/compositor/compositor_id.h>
32#include <mir/compositor/scene.h>
33#include <mir/compositor/scene_element.h>
34#include <mir/default_server_configuration.h>
35#include <mir/options/default_configuration.h>
36#include <mir/frontend/shell.h>
37#include <mir/scene/surface.h>
38#include <mir/scene/surface_coordinator.h>
39#include <mir/scene/session.h>
40#include <mir/server_status_listener.h>
41#include <mir/shell/focus_controller.h>
42#include <mir/input/cursor_listener.h>
43#include <mir/input/composite_event_filter.h>
44#include <mir/main_loop.h>36#include <mir/main_loop.h>
4537
46#include <cerrno>38#include <cerrno>
@@ -49,664 +41,16 @@
49#include <thread>41#include <thread>
50#include <regex.h>42#include <regex.h>
51#include <GLES2/gl2.h>43#include <GLES2/gl2.h>
52#include <boost/algorithm/string.hpp>
53#include <QCoreApplication>44#include <QCoreApplication>
5445
55namespace geom = mir::geometry;46namespace
56namespace mc = mir::compositor;47{
57namespace msh = mir::shell;48
58namespace msc = mir::scene;49bool check_blacklist(
59namespace mf = mir::frontend;50 std::string const& blacklist,
60namespace mg = mir::graphics;51 const char *vendor,
61namespace mi = mir::input;52 const char *renderer,
62namespace mo = mir::options;53 const char *version)
63namespace po = boost::program_options;
64
65class SystemCompositorSurface;
66
67class SystemCompositorSession : public msc::Session
68{
69public:
70 SystemCompositorSession(std::shared_ptr<msc::Session> const& self,
71 SystemCompositorShell *shell)
72 : self{self}, shell{shell}, ready{false} {}
73
74 // These are defined below, since they reference methods defined in other classes
75 void mark_ready();
76 void raise(std::shared_ptr<msc::SurfaceCoordinator> const& coordinator);
77 std::shared_ptr<mf::Surface> get_surface(mf::SurfaceId surface) const override;
78 mf::SurfaceId create_surface(msc::SurfaceCreationParameters const& params) override;
79
80 bool is_ready() const
81 {
82 return ready;
83 }
84
85 std::shared_ptr<msc::Session> get_orig()
86 {
87 return self;
88 }
89
90 void destroy_surface(mf::SurfaceId surface) override
91 {
92 surfaces.erase(surface);
93 self->destroy_surface(surface);
94 }
95
96 // This is just for convience of USC
97 std::vector<std::shared_ptr<SystemCompositorSurface>> get_surfaces()
98 {
99 std::vector<std::shared_ptr<SystemCompositorSurface>> vector;
100 for(auto surface: surfaces)
101 vector.push_back(surface.second);
102 return vector;
103 }
104
105 std::string name() const override {return self->name();}
106 void hide() override {self->hide();}
107 void show() override {self->show();}
108 void send_display_config(mg::DisplayConfiguration const&config) override {self->send_display_config(config);}
109 pid_t process_id() const override {return self->process_id();}
110 void force_requests_to_complete() override {self->force_requests_to_complete();}
111 void take_snapshot(msc::SnapshotCallback const& snapshot_taken) override {self->take_snapshot(snapshot_taken);}
112 std::shared_ptr<msc::Surface> default_surface() const override {return self->default_surface();}
113 void set_lifecycle_state(MirLifecycleState state) override {self->set_lifecycle_state(state);}
114
115 void start_prompt_session() override { self->start_prompt_session(); }
116 void stop_prompt_session() override { self->stop_prompt_session(); }
117
118private:
119 std::shared_ptr<msc::Session> const self;
120 SystemCompositorShell *shell;
121 std::map<mf::SurfaceId, std::shared_ptr<SystemCompositorSurface>> surfaces;
122 bool ready;
123};
124
125class SystemCompositorSurface : public msc::Surface
126{
127public:
128 SystemCompositorSurface(std::shared_ptr<msc::Surface> const& self,
129 SystemCompositorSession *session)
130 : self{self}, session{session}, buffer_count{0} {}
131
132 std::shared_ptr<msc::Surface> get_orig()
133 {
134 return self;
135 }
136
137 void swap_buffers(mg::Buffer* old_buffer, std::function<void(mg::Buffer* new_buffer)> complete) override
138 {
139 self->swap_buffers(old_buffer, complete);
140 // Mark it available only after some content has been rendered
141 if (old_buffer != NULL && !session->is_ready() && buffer_count++ == 1)
142 session->mark_ready();
143 }
144
145 // mf::Surface methods
146 void force_requests_to_complete() override {self->force_requests_to_complete();}
147 geom::Size size() const override {return self->size();}
148 MirPixelFormat pixel_format() const override {return self->pixel_format();}
149 bool supports_input() const override {return self->supports_input();}
150 int client_input_fd() const override {return self->client_input_fd();}
151 int configure(MirSurfaceAttrib attrib, int value) override {return self->configure(attrib, value);}
152
153 // msc::Surface methods
154 std::string name() const override {return self->name();}
155 geom::Size client_size() const override { return self->client_size(); };
156 geom::Rectangle input_bounds() const override { return self->input_bounds(); };
157
158 MirSurfaceType type() const override {return self->type();}
159 MirSurfaceState state() const override {return self->state();}
160 void hide() override {self->hide();}
161 void show() override {self->show();}
162 void move_to(geom::Point const& top_left) override {self->move_to(top_left);}
163 geom::Point top_left() const override {return self->top_left();}
164 void take_input_focus(std::shared_ptr<msh::InputTargeter> const& targeter) override {self->take_input_focus(targeter);}
165 void set_input_region(std::vector<geom::Rectangle> const& region) override {self->set_input_region(region);}
166 void allow_framedropping(bool allow) override {self->allow_framedropping(allow);}
167 void resize(geom::Size const& size) override {self->resize(size);}
168 void set_transformation(glm::mat4 const& t) override {self->set_transformation(t);}
169 float alpha() const override {return self->alpha();}
170 void set_alpha(float alpha) override {self->set_alpha(alpha);}
171 void with_most_recent_buffer_do(std::function<void(mg::Buffer&)> const& exec) override {self->with_most_recent_buffer_do(exec);}
172
173 // msc::Surface methods
174 std::shared_ptr<mi::InputChannel> input_channel() const override {return self->input_channel();}
175 void set_reception_mode(mi::InputReceptionMode mode) override { self->set_reception_mode(mode); }
176 void add_observer(std::shared_ptr<msc::SurfaceObserver> const& observer) override {self->add_observer(observer);}
177 void remove_observer(std::weak_ptr<msc::SurfaceObserver> const& observer) override {self->remove_observer(observer);}
178 void consume(MirEvent const& event) override {self->consume(event);}
179 void set_orientation(MirOrientation orientation) override {self->set_orientation(orientation);}
180
181 // mi::Surface methods
182 bool input_area_contains(geom::Point const& point) const override {return self->input_area_contains(point);}
183 mi::InputReceptionMode reception_mode() const override { return self->reception_mode(); }
184
185 // mg::Renderable methods
186 std::unique_ptr<mg::Renderable> compositor_snapshot(void const* compositor_id) const override { return self->compositor_snapshot(compositor_id); }
187
188 void set_cursor_image(std::shared_ptr<mg::CursorImage> const& image) override { self->set_cursor_image(image); }
189 std::shared_ptr<mg::CursorImage> cursor_image() const override { return self->cursor_image(); }
190
191private:
192 std::shared_ptr<msc::Surface> const self;
193 SystemCompositorSession *session;
194 int buffer_count;
195};
196
197class SystemCompositorShell : public mf::Shell
198{
199public:
200 SystemCompositorShell(SystemCompositor *compositor,
201 std::shared_ptr<mf::Shell> const& self,
202 std::shared_ptr<msh::FocusController> const& focus_controller,
203 std::shared_ptr<msc::SurfaceCoordinator> const& surface_coordinator)
204 : compositor{compositor}, self(self), focus_controller{focus_controller}, surface_coordinator{surface_coordinator}, active_ever_used{false} {}
205
206 std::shared_ptr<mf::Session> session_named(std::string const& name)
207 {
208 return sessions[name];
209 }
210
211 void set_active_session(std::string const& name)
212 {
213 active_name = name;
214 update_session_focus();
215 }
216
217 void set_next_session(std::string const& name)
218 {
219 next_name = name;
220 update_session_focus();
221 }
222
223 std::shared_ptr<SystemCompositorSession> get_active_session()
224 {
225 return active_session;
226 }
227
228 std::shared_ptr<SystemCompositorSession> get_next_session()
229 {
230 return next_session;
231 }
232
233 void update_session_focus()
234 {
235 auto spinner = sessions[spinner_name];
236 auto next = sessions[next_name];
237 auto active = sessions[active_name];
238 bool need_spinner = false;
239
240 if (spinner)
241 spinner->hide();
242
243 if (next && next->is_ready())
244 {
245 std::cerr << "Setting next focus to session " << next_name;
246 next->hide();
247 next->raise(surface_coordinator);
248 next_session = next;
249 }
250 else if (!next_name.empty() && spinner)
251 {
252 std::cerr << "Setting next focus to spinner";
253 spinner->raise(surface_coordinator);
254 next_session = spinner;
255 need_spinner = true;
256 }
257 else
258 {
259 need_spinner = !next_name.empty();
260 std::cerr << "Setting no next focus";
261 next_session.reset();
262 }
263
264 // If we are booting, we want to wait for next session to be ready to
265 // go (it's a smoother experience if user is able to immediately swipe
266 // greeter out of way -- enough that it's worth the tiny wait). So
267 // check here to see if next is all ready for us (or we've already
268 // focused the active before in which case we're not booting anymore).
269 bool next_all_set = next_name.empty() || (next && next->is_ready());
270 if (active && active->is_ready() && (next_all_set || active_ever_used))
271 {
272 std::cerr << "; active focus to session " << active_name;
273 focus_controller->set_focus_to(active); // raises and focuses
274 active_ever_used = true;
275 active_session = active;
276 if (active_session == next_session)
277 next_session.reset();
278 }
279 else if (!active_name.empty() && spinner)
280 {
281 std::cerr << "; active focus to spinner";
282 focus_controller->set_focus_to(spinner); // raises and focuses
283 active_session = spinner;
284 next_session.reset();
285 need_spinner = true;
286 }
287 else
288 {
289 need_spinner = need_spinner || !active_name.empty();
290 std::cerr << "; no active focus";
291 active_session.reset();
292 next_session.reset();
293 }
294
295 if (active_session)
296 active_session->show();
297 if (next_session)
298 next_session->show();
299
300 if (need_spinner)
301 compositor->ensure_spinner();
302 else
303 compositor->kill_spinner();
304
305 std::cerr << std::endl;
306 }
307
308 std::shared_ptr<mf::PromptSession> start_prompt_session_for(
309 std::shared_ptr<mf::Session> const& session,
310 msc::PromptSessionCreationParameters const& params) override
311 {
312 return self->start_prompt_session_for(session, params);
313 }
314
315 void add_prompt_provider_for(
316 std::shared_ptr<mf::PromptSession> const& prompt_session,
317 std::shared_ptr<mf::Session> const& session) override
318 {
319 self->add_prompt_provider_for(prompt_session, session);
320 }
321
322 void stop_prompt_session(std::shared_ptr<mf::PromptSession> const& prompt_session) override
323 {
324 self->stop_prompt_session(prompt_session);
325 }
326
327private:
328 std::shared_ptr<mf::Session> open_session(
329 pid_t client_pid,
330 std::string const& name,
331 std::shared_ptr<mf::EventSink> const& sink) override
332 {
333 std::cerr << "Opening session " << name << std::endl;
334
335 // We need msc::Session objects because that is what the focus controller
336 // works with. But the mf::Shell interface deals with mf::Session objects.
337 // So we cast here since in practice, these objects are also msc::Sessions.
338 auto orig = std::dynamic_pointer_cast<msc::Session>(self->open_session(client_pid, name, sink));
339 if (!orig)
340 {
341 std::cerr << "Unexpected non-shell session" << std::endl;
342 return std::shared_ptr<mf::Session>();
343 }
344
345 auto result = std::make_shared<SystemCompositorSession>(orig, this);
346 sessions[name] = result;
347
348 if (client_pid == compositor->get_spinner_pid())
349 spinner_name = name;
350
351 return result;
352 }
353
354 void close_session(std::shared_ptr<mf::Session> const& session_in) override
355 {
356 std::cerr << "Closing session " << session_in->name() << std::endl;
357
358 auto session = std::dynamic_pointer_cast<SystemCompositorSession>(session_in);
359 if (!session)
360 return; // shouldn't happen
361
362 if (session->name() == spinner_name)
363 spinner_name = "";
364
365 self->close_session(session->get_orig());
366 sessions.erase(session->name());
367 }
368
369 mf::SurfaceId create_surface_for(
370 std::shared_ptr<mf::Session> const& session,
371 msc::SurfaceCreationParameters const& params) override
372 {
373 return self->create_surface_for(session, params);
374 }
375
376 void handle_surface_created(std::shared_ptr<mf::Session> const& session) override
377 {
378 self->handle_surface_created(session);
379
380 // Opening a new surface will steal focus from our active surface, so
381 // restore the focus if needed.
382 update_session_focus();
383 }
384
385 SystemCompositor *compositor;
386 std::shared_ptr<mf::Shell> const self;
387 std::shared_ptr<msh::FocusController> const focus_controller;
388 std::shared_ptr<msc::SurfaceCoordinator> const surface_coordinator;
389 std::map<std::string, std::shared_ptr<SystemCompositorSession>> sessions;
390 std::string active_name;
391 std::string next_name;
392 std::string spinner_name;
393 std::shared_ptr<SystemCompositorSession> active_session;
394 std::shared_ptr<SystemCompositorSession> next_session;
395 bool active_ever_used;
396};
397
398class SurfaceSceneElement : public mc::SceneElement
399{
400public:
401 SurfaceSceneElement(std::shared_ptr<mg::Renderable> renderable)
402 : renderable_{renderable}
403 {
404 }
405
406 std::shared_ptr<mg::Renderable> renderable() const override
407 {
408 return renderable_;
409 }
410
411 void rendered_in(mc::CompositorID) override {}
412 void occluded_in(mc::CompositorID) override {}
413
414private:
415 std::shared_ptr<mg::Renderable> const renderable_;
416};
417
418class SystemCompositorScene : public mc::Scene
419{
420public:
421 SystemCompositorScene(std::shared_ptr<mc::Scene> const& self)
422 : self{self}, shell{nullptr} {}
423
424 void set_shell(std::shared_ptr<SystemCompositorShell> const& the_shell)
425 {
426 shell = the_shell;
427 }
428
429 mc::SceneElementSequence scene_elements_for(mc::CompositorID id) override
430 {
431 if (shell == nullptr)
432 return self->scene_elements_for(id);
433
434 mc::SceneElementSequence elements;
435 std::shared_ptr<SystemCompositorSession> session;
436
437 session = shell->get_next_session();
438 if (session)
439 {
440 for (auto const& surface : session->get_surfaces()) {
441 // An open question is whether this memory allocation overhead (as this method is
442 // called for every rendered frame) is perceivable/significant at all to warrant
443 // an optimization here.
444 auto element = std::make_shared<SurfaceSceneElement>(surface->compositor_snapshot(id));
445 elements.emplace_back(element);
446 }
447 }
448
449 session = shell->get_active_session();
450 if (session)
451 {
452 for (auto const& surface : session->get_surfaces()) {
453 // see comment previous comment
454 auto element = std::make_shared<SurfaceSceneElement>(surface->compositor_snapshot(id));
455 elements.emplace_back(element);
456 }
457 }
458
459 return elements;
460 }
461
462 void add_observer(std::shared_ptr<msc::Observer> const& observer) override { self->add_observer(observer); }
463 void remove_observer(std::weak_ptr<msc::Observer> const& observer) override { self->remove_observer(observer); }
464
465 void register_compositor(mc::CompositorID id) override { self->register_compositor(id); }
466 void unregister_compositor(mc::CompositorID id) override { self->unregister_compositor(id); }
467
468
469private:
470 std::shared_ptr<mc::Scene> const self;
471 std::shared_ptr<SystemCompositorShell> shell;
472};
473
474
475void SystemCompositorSession::mark_ready()
476{
477 if (!ready)
478 {
479 ready = true;
480 shell->update_session_focus();
481 }
482}
483
484void SystemCompositorSession::raise(std::shared_ptr<msc::SurfaceCoordinator> const& coordinator)
485{
486 std::map<mf::SurfaceId, std::shared_ptr<SystemCompositorSurface>>::iterator iter;
487 for (iter = surfaces.begin(); iter != surfaces.end(); ++iter)
488 {
489 // This will iterate by creation order, which is fine. New surfaces on top
490 coordinator->raise(iter->second->get_orig());
491 }
492}
493
494std::shared_ptr<mf::Surface> SystemCompositorSession::get_surface(mf::SurfaceId surface) const
495{
496 return surfaces.at(surface);
497}
498
499mf::SurfaceId SystemCompositorSession::create_surface(msc::SurfaceCreationParameters const& params)
500{
501 mf::SurfaceId id = self->create_surface(params);
502 std::shared_ptr<mf::Surface> mf_surface = self->get_surface(id);
503
504 auto surface = std::dynamic_pointer_cast<msc::Surface>(mf_surface);
505 if (!surface)
506 {
507 std::cerr << "Unexpected non-scene surface" << std::endl;
508 self->destroy_surface(id);
509 return mf::SurfaceId(0);
510 }
511
512 surfaces[id] = std::make_shared<SystemCompositorSurface>(surface, this);
513 return id;
514}
515
516class SystemCompositorServerConfiguration : public mir::DefaultServerConfiguration
517{
518public:
519 SystemCompositorServerConfiguration(SystemCompositor *compositor, std::shared_ptr<mo::Configuration> options)
520 : mir::DefaultServerConfiguration(options), compositor{compositor}
521 {
522 }
523
524 int from_dm_fd()
525 {
526 return the_options()->get("from-dm-fd", -1);
527 }
528
529 int to_dm_fd()
530 {
531 return the_options()->get("to-dm-fd", -1);
532 }
533
534 bool show_version()
535 {
536 return the_options()->is_set("version");
537 }
538
539 int inactivity_display_off_timeout()
540 {
541 return the_options()->get("inactivity-display-off-timeout", 60);
542 }
543
544 int inactivity_display_dim_timeout()
545 {
546 return the_options()->get("inactivity-display-dim-timeout", 45);
547 }
548
549 int shutdown_timeout()
550 {
551 return the_options()->get("shutdown-timeout", 5000);
552 }
553
554 int power_key_ignore_timeout()
555 {
556 return the_options()->get("power-key-ignore-timeout", 1500);
557 }
558
559 bool enable_hardware_cursor()
560 {
561 return the_options()->get("enable-hardware-cursor", false);
562 }
563
564 bool disable_inactivity_policy()
565 {
566 return the_options()->get("disable-inactivity-policy", false);
567 }
568
569 std::string blacklist()
570 {
571 auto x = the_options()->get("blacklist", "");
572 boost::trim(x);
573 return x;
574 }
575
576 std::string spinner()
577 {
578 // TODO: once our default spinner is ready for use everywhere, replace
579 // default value with DEFAULT_SPINNER instead of the empty string.
580 auto x = the_options()->get("spinner", "");
581 boost::trim(x);
582 return x;
583 }
584
585 bool public_socket()
586 {
587 return !the_options()->is_set("no-file") && the_options()->get("public-socket", true);
588 }
589
590 std::shared_ptr<mi::CursorListener> the_cursor_listener() override
591 {
592 struct NullCursorListener : public mi::CursorListener
593 {
594 void cursor_moved_to(float, float) override
595 {
596 }
597 };
598
599 // This is a workaround for u8 desktop preview in 14.04 for the lack of client cursor API.
600 // We need to disable the cursor for XMir but leave it on for the desktop preview.
601 // Luckily as it stands they run inside seperate instances of USC. ~racarr
602 if (enable_hardware_cursor())
603 return mir::DefaultServerConfiguration::the_cursor_listener();
604 else
605 return std::make_shared<NullCursorListener>();
606 }
607
608 std::shared_ptr<mir::ServerStatusListener> the_server_status_listener() override
609 {
610 struct ServerStatusListener : public mir::ServerStatusListener
611 {
612 ServerStatusListener (SystemCompositor *compositor) : compositor{compositor} {}
613
614 void paused() override
615 {
616 compositor->pause();
617 }
618
619 void resumed() override
620 {
621 compositor->resume();
622 }
623
624 void started() override
625 {
626 }
627
628 SystemCompositor *compositor;
629 };
630 return std::make_shared<ServerStatusListener>(compositor);
631 }
632
633 std::string get_socket_file()
634 {
635 // the_socket_file is private, so we have to re-implement it here
636 return the_options()->get("file", "/tmp/mir_socket");
637 }
638
639 std::shared_ptr<SystemCompositorShell> the_system_compositor_shell()
640 {
641 auto shell = sc_shell([this]
642 {
643 return std::make_shared<SystemCompositorShell>(
644 compositor,
645 mir::DefaultServerConfiguration::the_frontend_shell(),
646 the_focus_controller(),
647 the_surface_coordinator());
648 });
649
650 the_system_compositor_scene()->set_shell(shell);
651 return shell;
652 }
653
654 std::shared_ptr<SystemCompositorScene> the_system_compositor_scene()
655 {
656 return sc_scene([this]
657 {
658 return std::make_shared<SystemCompositorScene>(
659 mir::DefaultServerConfiguration::the_scene());
660 });
661 }
662
663 std::shared_ptr<mc::Scene> the_scene()
664 {
665 return the_system_compositor_scene();
666 }
667
668private:
669 mir::CachedPtr<SystemCompositorShell> sc_shell;
670 mir::CachedPtr<SystemCompositorScene> sc_scene;
671
672 std::shared_ptr<mf::Shell> the_frontend_shell() override
673 {
674 return the_system_compositor_shell();
675 }
676
677 SystemCompositor *compositor;
678};
679
680class SystemCompositorConfigurationOptions : public mo::DefaultConfiguration
681{
682public:
683 SystemCompositorConfigurationOptions(int argc, char const* argv[]) :
684 DefaultConfiguration(argc, argv)
685 {
686 add_options()
687 ("from-dm-fd", po::value<int>(), "File descriptor of read end of pipe from display manager [int]")
688 ("to-dm-fd", po::value<int>(), "File descriptor of write end of pipe to display manager [int]")
689 ("blacklist", po::value<std::string>(), "Video blacklist regex to use")
690 ("version", "Show version of Unity System Compositor")
691 ("spinner", po::value<std::string>(), "Path to spinner executable")
692 ("public-socket", po::value<bool>(), "Make the socket file publicly writable")
693 ("enable-hardware-cursor", po::value<bool>(), "Enable the hardware cursor (disabled by default)")
694 ("inactivity-display-off-timeout", po::value<int>(), "The time in seconds before the screen is turned off when there are no active sessions")
695 ("inactivity-display-dim-timeout", po::value<int>(), "The time in seconds before the screen is dimmed when there are no active sessions")
696 ("shutdown-timeout", po::value<int>(), "The time in milli-seconds the power key must be held to initiate a clean system shutdown")
697 ("power-key-ignore-timeout", po::value<int>(), "The time in milli-seconds the power key must be held to ignore - must be less than shutdown-timeout")
698 ("disable-inactivity-policy", po::value<bool>(), "Disables handling user inactivity and power key");
699 }
700
701 void parse_config_file(
702 boost::program_options::options_description& options_description,
703 mo::ProgramOption& options) const override
704 {
705 options.parse_file(options_description, "unity-system-compositor.conf");
706 }
707};
708
709bool check_blacklist(std::string blacklist, const char *vendor, const char *renderer, const char *version)
710{54{
711 if (blacklist.empty())55 if (blacklist.empty())
712 return true;56 return true;
@@ -739,35 +83,37 @@
739 return true;83 return true;
740}84}
74185
742void SystemCompositor::run(int argc, char **argv)86}
743{87
744 auto const options = std::make_shared<SystemCompositorConfigurationOptions>(argc, const_cast<char const **>(argv));88usc::SystemCompositor::SystemCompositor(
745 config = std::make_shared<SystemCompositorServerConfiguration>(this, options);89 std::shared_ptr<ServerConfiguration> const& config)
74690 : config{config},
91 dm_connection{config->the_dm_connection()},
92 spinner{config->the_spinner()}
93{
94}
95
96void usc::SystemCompositor::run()
97{
747 if (config->show_version())98 if (config->show_version())
748 {99 {
749 std::cerr << "unity-system-compositor " << USC_VERSION << std::endl;100 std::cerr << "unity-system-compositor " << USC_VERSION << std::endl;
750 return;101 return;
751 }102 }
752103
753 dm_connection = std::make_shared<DMConnection>(io_service, config->from_dm_fd(), config->to_dm_fd());
754
755 struct ScopeGuard104 struct ScopeGuard
756 {105 {
757 explicit ScopeGuard(boost::asio::io_service& io_service) : io_service(io_service) {}
758 ~ScopeGuard()106 ~ScopeGuard()
759 {107 {
760 io_service.stop();
761 if (io_thread.joinable())
762 io_thread.join();
763 if (qt_thread.joinable())108 if (qt_thread.joinable())
109 {
110 QCoreApplication::quit();
764 qt_thread.join();111 qt_thread.join();
112 }
765 }113 }
766114
767 boost::asio::io_service& io_service;
768 std::thread io_thread;
769 std::thread qt_thread;115 std::thread qt_thread;
770 } guard(io_service);116 } guard;
771117
772 mir::run_mir(*config, [&](mir::DisplayServer&)118 mir::run_mir(*config, [&](mir::DisplayServer&)
773 {119 {
@@ -781,46 +127,12 @@
781 if (!check_blacklist(config->blacklist(), vendor, renderer, version))127 if (!check_blacklist(config->blacklist(), vendor, renderer, version))
782 throw mir::AbnormalExit ("Video driver is blacklisted, exiting");128 throw mir::AbnormalExit ("Video driver is blacklisted, exiting");
783129
784 shell = config->the_system_compositor_shell();130 main();
785 guard.io_thread = std::thread(&SystemCompositor::main, this);131 guard.qt_thread = std::thread(&SystemCompositor::qt_main, this);
786 guard.qt_thread = std::thread(&SystemCompositor::qt_main, this, argc, argv);
787 });132 });
788}133}
789134
790void SystemCompositor::pause()135void usc::SystemCompositor::main()
791{
792 std::cerr << "pause" << std::endl;
793
794 if (auto active_session = config->the_focus_controller()->focussed_application().lock())
795 active_session->set_lifecycle_state(mir_lifecycle_state_will_suspend);
796}
797
798void SystemCompositor::resume()
799{
800 std::cerr << "resume" << std::endl;
801
802 if (auto active_session = config->the_focus_controller()->focussed_application().lock())
803 active_session->set_lifecycle_state(mir_lifecycle_state_resumed);
804}
805
806pid_t SystemCompositor::get_spinner_pid() const
807{
808 return spinner_process.pid();
809}
810
811void SystemCompositor::set_active_session(std::string client_name)
812{
813 std::cerr << "set_active_session" << std::endl;
814 shell->set_active_session(client_name);
815}
816
817void SystemCompositor::set_next_session(std::string client_name)
818{
819 std::cerr << "set_next_session" << std::endl;
820 shell->set_next_session(client_name);
821}
822
823void SystemCompositor::main()
824{136{
825 // Make socket world-writable, since users need to talk to us. No worries137 // Make socket world-writable, since users need to talk to us. No worries
826 // about race condition, since we are adding permissions, not restricting138 // about race condition, since we are adding permissions, not restricting
@@ -828,33 +140,13 @@
828 if (config->public_socket() && chmod(config->get_socket_file().c_str(), 0777) == -1)140 if (config->public_socket() && chmod(config->get_socket_file().c_str(), 0777) == -1)
829 std::cerr << "Unable to chmod socket file " << config->get_socket_file() << ": " << strerror(errno) << std::endl;141 std::cerr << "Unable to chmod socket file " << config->get_socket_file() << ": " << strerror(errno) << std::endl;
830142
831 dm_connection->set_handler(this);
832 dm_connection->start();143 dm_connection->start();
833 dm_connection->send_ready();144}
834145
835 io_service.run();146void usc::SystemCompositor::qt_main()
836}147{
837148 int argc{0};
838void SystemCompositor::ensure_spinner()149 QCoreApplication app(argc, nullptr);
839{
840 if (config->spinner().empty() || spinner_process.state() != QProcess::NotRunning)
841 return;
842
843 // Launch spinner process to provide default background when a session isn't ready
844 QStringList env = QProcess::systemEnvironment();
845 env << "MIR_SOCKET=" + QString(config->get_socket_file().c_str());
846 spinner_process.setEnvironment(env);
847 spinner_process.start(config->spinner().c_str());
848}
849
850void SystemCompositor::kill_spinner()
851{
852 spinner_process.close();
853}
854
855void SystemCompositor::qt_main(int argc, char **argv)
856{
857 QCoreApplication app(argc, argv);
858150
859 if (!config->disable_inactivity_policy())151 if (!config->disable_inactivity_policy())
860 {152 {
@@ -877,6 +169,11 @@
877 composite_filter->append(power_key_handler);169 composite_filter->append(power_key_handler);
878 }170 }
879171
880 ensure_spinner();
881 app.exec();172 app.exec();
173
174 // Destroy components that depend on Qt event handling inside the Qt thread,
175 // to silence warnings during shutdown
176
177 // ScreenStateHandler uses the Qt DBus infrastructure
178 screen_state_handler.reset();
882}179}
883180
=== modified file 'src/system_compositor.h'
--- src/system_compositor.h 2014-06-20 16:35:15 +0000
+++ src/system_compositor.h 2014-07-18 09:49:03 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright © 2013 Canonical Ltd.2 * Copyright © 2013-2014 Canonical Ltd.
3 *3 *
4 * This program is free software: you can redistribute it and/or modify4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as5 * it under the terms of the GNU General Public License version 3 as
@@ -14,44 +14,41 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Robert Ancell <robert.ancell@canonical.com>16 * Authored by: Robert Ancell <robert.ancell@canonical.com>
17 * Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */18 */
1819
19#ifndef SYSTEM_COMPOSITOR_H_20#ifndef USC_SYSTEM_COMPOSITOR_H_
20#define SYSTEM_COMPOSITOR_H_21#define USC_SYSTEM_COMPOSITOR_H_
2122
22#include "dm_connection.h"23#include <memory>
23#include <QProcess>24
24
25namespace mir { namespace scene { class Session; } }
26
27class SystemCompositorShell;
28class SystemCompositorServerConfiguration;
29class ScreenStateHandler;25class ScreenStateHandler;
30class PowerKeyHandler;26class PowerKeyHandler;
3127
32class SystemCompositor : public DMMessageHandler28namespace usc
29{
30
31class ServerConfiguration;
32class DMConnection;
33class Spinner;
34
35class SystemCompositor
33{36{
34public:37public:
35 void run(int argc, char **argv);38 SystemCompositor(std::shared_ptr<ServerConfiguration> const& config);
36 void pause();39 void run();
37 void resume();
38 pid_t get_spinner_pid() const;
39 void ensure_spinner();
40 void kill_spinner();
4140
42private:41private:
43 std::shared_ptr<SystemCompositorServerConfiguration> config;42 void main();
44 std::shared_ptr<SystemCompositorShell> shell;43 void qt_main();
45 boost::asio::io_service io_service;44
46 std::shared_ptr<DMConnection> dm_connection;45 std::shared_ptr<ServerConfiguration> const config;
46 std::shared_ptr<DMConnection> const dm_connection;
47 std::shared_ptr<Spinner> const spinner;
47 std::shared_ptr<ScreenStateHandler> screen_state_handler;48 std::shared_ptr<ScreenStateHandler> screen_state_handler;
48 std::shared_ptr<PowerKeyHandler> power_key_handler;49 std::shared_ptr<PowerKeyHandler> power_key_handler;
49 QProcess spinner_process;
50
51 void set_active_session(std::string client_name);
52 void set_next_session(std::string client_name);
53 void main();
54 void qt_main(int argc, char **argv);
55};50};
5651
57#endif /* SYSTEM_COMPOSITOR_H_ */52}
53
54#endif /* USC_SYSTEM_COMPOSITOR_H_ */
5855
=== added file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/CMakeLists.txt 2014-07-18 09:49:03 +0000
@@ -0,0 +1,17 @@
1# Copyright © 2014 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License version 3 as
5# published by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14#
15# Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
16
17add_subdirectory(unit-tests/)
018
=== added directory 'tests/unit-tests'
=== added file 'tests/unit-tests/CMakeLists.txt'
--- tests/unit-tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/unit-tests/CMakeLists.txt 2014-07-18 09:49:03 +0000
@@ -0,0 +1,39 @@
1# Copyright © 2014 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License version 3 as
5# published by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14#
15# Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
16
17include_directories(
18 ${CMAKE_SOURCE_DIR}
19 ${MIRSERVER_INCLUDE_DIRS}
20)
21
22add_executable(
23 usc_unit_tests
24
25 test_session_switcher.cpp
26)
27
28target_link_libraries(
29 usc_unit_tests
30
31 usc
32 ${GTEST_BOTH_LIBRARIES}
33 ${GMOCK_LIBRARY}
34 ${GMOCK_MAIN_LIBRARY}
35)
36
37add_test(usc_unit_tests ${EXECUTABLE_OUTPUT_PATH}/usc_unit_tests)
38
39add_dependencies(usc_unit_tests GMock)
040
=== added file 'tests/unit-tests/test_session_switcher.cpp'
--- tests/unit-tests/test_session_switcher.cpp 1970-01-01 00:00:00 +0000
+++ tests/unit-tests/test_session_switcher.cpp 2014-07-18 09:49:03 +0000
@@ -0,0 +1,656 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#include "src/session_switcher.h"
20#include "src/spinner.h"
21
22#include <gtest/gtest.h>
23#include <gmock/gmock.h>
24
25#include <memory>
26#include <vector>
27#include <tuple>
28
29namespace
30{
31
32class FakeScene
33{
34public:
35 void add(usc::Session* session)
36 {
37 sessions.emplace_back(session, true);
38 }
39
40 void remove(usc::Session* session)
41 {
42 sessions.erase(find(session));
43 }
44
45 void show(usc::Session* session)
46 {
47 find(session)->second = true;
48 }
49
50 void hide(usc::Session* session)
51 {
52 find(session)->second = false;
53 }
54
55 void raise(usc::Session* session)
56 {
57 auto const iter = find(session);
58 auto const pair = *iter;
59 sessions.erase(iter);
60 sessions.push_back(pair);
61 }
62
63 std::vector<std::string> displayed_sessions()
64 {
65 std::vector<std::string> ret;
66 for (auto const& pair : sessions)
67 {
68 bool session_visible = pair.second;
69 if (session_visible)
70 ret.push_back(pair.first->name());
71 }
72
73 return ret;
74 }
75
76private:
77 std::vector<std::pair<usc::Session*,bool>> sessions;
78
79 decltype(sessions)::iterator find(usc::Session* session)
80 {
81 return std::find_if(
82 sessions.begin(), sessions.end(),
83 [session] (decltype(sessions)::value_type const& p)
84 {
85 return p.first == session;
86 });
87 }
88};
89
90class StubSession : public usc::Session
91{
92public:
93 StubSession(FakeScene& fake_scene, std::string const& name)
94 : fake_scene(fake_scene),
95 name_{name}
96 {
97 fake_scene.add(this);
98 }
99
100 ~StubSession()
101 {
102 fake_scene.remove(this);
103 }
104
105 std::string name() override
106 {
107 return name_;
108 }
109
110 void show() override
111 {
112 fake_scene.show(this);
113 }
114
115 void hide() override
116 {
117 fake_scene.hide(this);
118 }
119
120 void raise_and_focus() override
121 {
122 fake_scene.raise(this);
123 }
124
125 bool corresponds_to(mir::scene::Session const* s) override
126 {
127 return s == corresponding_scene_session();
128 }
129
130 mir::scene::Session const* corresponding_scene_session()
131 {
132 return reinterpret_cast<mir::scene::Session const*>(this);
133 }
134
135private:
136 FakeScene& fake_scene;
137 std::string const name_;
138};
139
140struct StubSpinner : usc::Spinner
141{
142 void ensure_running() override { is_running_ = true; }
143 void kill() override { is_running_ = false; }
144 pid_t pid() override { return pid_; }
145
146 void set_pid(pid_t new_pid) { pid_ = new_pid; }
147 bool is_running() { return is_running_; }
148
149private:
150 bool is_running_ = false;
151 pid_t pid_ = 666;
152};
153
154struct ASessionSwitcher : testing::Test
155{
156 std::shared_ptr<StubSession> create_stub_session(std::string const& name)
157 {
158 return std::make_shared<StubSession>(fake_scene, name);
159 }
160
161 std::tuple<std::string,std::string> boot()
162 {
163 std::string const boot_active_name{"boot_active"};
164 std::string const boot_next_name{"boot_next"};
165
166 auto const boot_active = create_stub_session(boot_active_name);
167 auto const boot_next = create_stub_session(boot_next_name);
168
169 switcher.add(boot_active, active_pid);
170 switcher.add(boot_next, next_pid);
171
172 switcher.set_next_session(boot_next_name);
173 switcher.set_active_session(boot_active_name);
174 switcher.mark_ready(boot_active->corresponding_scene_session());
175 switcher.mark_ready(boot_next->corresponding_scene_session());
176
177 return std::make_tuple(boot_active_name, boot_next_name);
178 }
179
180 FakeScene fake_scene;
181 std::shared_ptr<StubSpinner> const stub_spinner{std::make_shared<StubSpinner>()};
182 usc::SessionSwitcher switcher{stub_spinner};
183 std::string const active_name{"active"};
184 std::string const next_name{"next"};
185 std::string const spinner_name{"spinner"};
186 pid_t const invalid_pid{0};
187 pid_t const active_pid{1000};
188 pid_t const next_pid{1001};
189 pid_t const other_pid{1002};
190};
191
192}
193
194TEST_F(ASessionSwitcher, does_not_display_any_session_if_active_and_next_not_set)
195{
196 using namespace testing;
197
198 switcher.add(create_stub_session("s1"), invalid_pid);
199 switcher.add(create_stub_session("s2"), invalid_pid);
200
201 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
202}
203
204TEST_F(ASessionSwitcher, does_not_display_not_ready_active_session)
205{
206 using namespace testing;
207
208 auto const active = create_stub_session(active_name);
209
210 switcher.add(active, active_pid);
211 switcher.set_active_session(active_name);
212
213 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
214}
215
216TEST_F(ASessionSwitcher, does_not_display_not_ready_next_session)
217{
218 using namespace testing;
219
220 auto const next = create_stub_session(next_name);
221
222 switcher.add(next, next_pid);
223 switcher.set_next_session(next_name);
224
225 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
226}
227
228TEST_F(ASessionSwitcher, does_not_display_ready_next_session_without_ready_active_session)
229{
230 using namespace testing;
231
232 auto const next = create_stub_session(next_name);
233
234 switcher.add(next, next_pid);
235 switcher.set_next_session(next_name);
236 switcher.mark_ready(next->corresponding_scene_session());
237
238 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
239}
240
241TEST_F(ASessionSwitcher,
242 does_not_display_any_session_on_boot_if_not_both_active_and_next_are_ready)
243{
244 using namespace testing;
245
246 auto const active = create_stub_session(active_name);
247 auto const next = create_stub_session(next_name);
248
249 switcher.add(active, active_pid);
250 switcher.add(next, next_pid);
251
252 switcher.set_active_session(active_name);
253 switcher.set_next_session(next_name);
254 switcher.mark_ready(active->corresponding_scene_session());
255
256 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
257}
258
259TEST_F(ASessionSwitcher,
260 displays_the_active_session_on_boot_if_it_is_ready_and_there_is_no_next_session)
261{
262 using namespace testing;
263
264 auto const active = create_stub_session(active_name);
265 auto const spinner = create_stub_session(spinner_name);
266
267 switcher.add(active, active_pid);
268 switcher.add(spinner, stub_spinner->pid());
269
270 switcher.set_active_session(active_name);
271 switcher.mark_ready(active->corresponding_scene_session());
272
273 EXPECT_THAT(fake_scene.displayed_sessions(),
274 ElementsAre(active_name));
275}
276
277TEST_F(ASessionSwitcher, displays_the_active_session_after_boot_if_it_is_ready)
278{
279 using namespace testing;
280
281 boot();
282
283 auto const active = create_stub_session(active_name);
284 auto const next = create_stub_session(next_name);
285
286 switcher.add(active, active_pid);
287 switcher.add(next, next_pid);
288
289 switcher.set_next_session(next_name);
290 switcher.set_active_session(active_name);
291 switcher.mark_ready(active->corresponding_scene_session());
292
293 EXPECT_THAT(fake_scene.displayed_sessions(),
294 ElementsAre(active_name));
295}
296
297TEST_F(ASessionSwitcher, displays_active_over_next_if_both_are_ready)
298{
299 using namespace testing;
300
301 auto const active = create_stub_session(active_name);
302 auto const next = create_stub_session(next_name);
303
304 switcher.add(active, active_pid);
305 switcher.add(next, next_pid);
306
307 switcher.set_next_session(next_name);
308 switcher.set_active_session(active_name);
309 switcher.mark_ready(active->corresponding_scene_session());
310 switcher.mark_ready(next->corresponding_scene_session());
311
312 EXPECT_THAT(fake_scene.displayed_sessions(),
313 ElementsAre(next_name, active_name));
314}
315
316
317TEST_F(ASessionSwitcher, displays_only_active_if_next_equals_active)
318{
319 using namespace testing;
320
321 auto const active = create_stub_session(active_name);
322
323 switcher.add(active, active_pid);
324
325 switcher.set_next_session(active_name);
326 switcher.set_active_session(active_name);
327 switcher.mark_ready(active->corresponding_scene_session());
328
329 EXPECT_THAT(fake_scene.displayed_sessions(),
330 ElementsAre(active_name));
331}
332
333TEST_F(ASessionSwitcher, displays_only_active_and_next_sessions)
334{
335 using namespace testing;
336
337 auto const active = create_stub_session(active_name);
338 auto const next = create_stub_session(next_name);
339 auto const other = create_stub_session("other");
340
341 switcher.add(active, active_pid);
342 switcher.add(next, next_pid);
343 switcher.add(other, other_pid);
344
345 switcher.set_next_session(next_name);
346 switcher.set_active_session(active_name);
347 switcher.mark_ready(active->corresponding_scene_session());
348 switcher.mark_ready(next->corresponding_scene_session());
349 switcher.mark_ready(other->corresponding_scene_session());
350
351 EXPECT_THAT(fake_scene.displayed_sessions(),
352 ElementsAre(next_name, active_name));
353}
354
355TEST_F(ASessionSwitcher, displays_spinner_if_active_is_not_ready)
356{
357 using namespace testing;
358
359 auto const active = create_stub_session(active_name);
360 auto const spinner = create_stub_session(spinner_name);
361
362 switcher.add(active, active_pid);
363 switcher.add(spinner, stub_spinner->pid());
364
365 switcher.set_active_session(active_name);
366
367 EXPECT_THAT(fake_scene.displayed_sessions(),
368 ElementsAre(spinner_name));
369}
370
371TEST_F(ASessionSwitcher, does_not_display_spinner_if_next_is_not_ready)
372{
373 using namespace testing;
374
375 auto const next = create_stub_session(next_name);
376 auto const spinner = create_stub_session(spinner_name);
377
378 switcher.add(next, next_pid);
379 switcher.add(spinner, stub_spinner->pid());
380
381 switcher.set_next_session(next_name);
382
383 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
384}
385
386TEST_F(ASessionSwitcher, displays_only_spinner_when_active_is_not_ready_but_next_is_ready)
387{
388 using namespace testing;
389
390 auto const active = create_stub_session(active_name);
391 auto const next = create_stub_session(next_name);
392 auto const spinner = create_stub_session(spinner_name);
393
394 switcher.add(active, active_pid);
395 switcher.add(next, next_pid);
396 switcher.add(spinner, stub_spinner->pid());
397
398 switcher.set_active_session(active_name);
399 switcher.set_next_session(next_name);
400 switcher.mark_ready(next->corresponding_scene_session());
401
402 EXPECT_THAT(fake_scene.displayed_sessions(),
403 ElementsAre(spinner_name));
404}
405
406TEST_F(ASessionSwitcher,
407 displays_only_spinner_when_booting_if_not_both_active_and_next_are_ready)
408{
409 using namespace testing;
410
411 auto const active = create_stub_session(active_name);
412 auto const next = create_stub_session(next_name);
413 auto const spinner = create_stub_session(spinner_name);
414
415 switcher.add(active, active_pid);
416 switcher.add(next, next_pid);
417 switcher.add(spinner, stub_spinner->pid());
418
419 switcher.set_active_session(active_name);
420 switcher.set_next_session(next_name);
421 switcher.mark_ready(active->corresponding_scene_session());
422
423 EXPECT_THAT(fake_scene.displayed_sessions(),
424 ElementsAre(spinner_name));
425}
426
427TEST_F(ASessionSwitcher,
428 displays_spinner_behind_active_after_boot_if_active_is_ready_but_next_is_not_ready)
429{
430 using namespace testing;
431
432 boot();
433
434 auto const active = create_stub_session(active_name);
435 auto const next = create_stub_session(next_name);
436 auto const spinner = create_stub_session(spinner_name);
437
438 switcher.add(active, active_pid);
439 switcher.add(next, next_pid);
440 switcher.add(spinner, stub_spinner->pid());
441
442 switcher.set_active_session(active_name);
443 switcher.set_next_session(next_name);
444 switcher.mark_ready(active->corresponding_scene_session());
445
446 EXPECT_THAT(fake_scene.displayed_sessions(),
447 ElementsAre(spinner_name, active_name));
448}
449
450TEST_F(ASessionSwitcher, starts_and_stops_spinner_as_needed)
451{
452 using namespace testing;
453
454 auto const active = create_stub_session(active_name);
455
456 switcher.add(active, active_pid);
457 switcher.set_active_session(active_name);
458
459 EXPECT_TRUE(stub_spinner->is_running());
460
461 switcher.mark_ready(active->corresponding_scene_session());
462
463 EXPECT_FALSE(stub_spinner->is_running());
464}
465
466TEST_F(ASessionSwitcher, does_not_display_next_when_active_is_removed)
467{
468 using namespace testing;
469
470 std::string const no_session_name;
471
472 std::string boot_active_name;
473 std::string boot_next_name;
474 std::tie(boot_active_name, boot_next_name) = boot();
475
476 auto const spinner = create_stub_session(spinner_name);
477 switcher.add(spinner, stub_spinner->pid());
478
479 switcher.remove(boot_active_name);
480 switcher.set_active_session(no_session_name);
481
482 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
483}
484
485TEST_F(ASessionSwitcher, displays_only_active_not_spinner_when_next_is_removed)
486{
487 using namespace testing;
488
489 std::string const no_session_name;
490
491 std::string boot_active_name;
492 std::string boot_next_name;
493 std::tie(boot_active_name, boot_next_name) = boot();
494
495 auto const spinner = create_stub_session(spinner_name);
496 switcher.add(spinner, stub_spinner->pid());
497
498 switcher.remove(boot_next_name);
499 switcher.set_next_session(no_session_name);
500
501 EXPECT_THAT(fake_scene.displayed_sessions(),
502 ElementsAre(boot_active_name));
503}
504
505TEST_F(ASessionSwitcher, displays_spinner_when_active_is_removed_unexpectedly)
506{
507 using namespace testing;
508
509 std::string const no_session_name;
510
511 std::string boot_active_name;
512 std::string boot_next_name;
513 std::tie(boot_active_name, boot_next_name) = boot();
514
515 auto const spinner = create_stub_session(spinner_name);
516 switcher.add(spinner, stub_spinner->pid());
517
518 switcher.remove(boot_active_name);
519
520 EXPECT_THAT(fake_scene.displayed_sessions(),
521 ElementsAre(spinner_name));
522}
523
524TEST_F(ASessionSwitcher, displays_spinner_under_active_if_next_is_removed_unexpectedly)
525{
526 using namespace testing;
527
528 std::string const no_session_name;
529
530 std::string boot_active_name;
531 std::string boot_next_name;
532 std::tie(boot_active_name, boot_next_name) = boot();
533
534 auto const spinner = create_stub_session(spinner_name);
535 switcher.add(spinner, stub_spinner->pid());
536
537 switcher.remove(boot_next_name);
538
539 EXPECT_THAT(fake_scene.displayed_sessions(),
540 ElementsAre(spinner_name, boot_active_name));
541}
542
543TEST_F(ASessionSwitcher,
544 does_not_display_any_session_when_spinner_is_removed_and_no_sessions_are_ready)
545{
546 using namespace testing;
547
548 auto const active = create_stub_session(active_name);
549 auto spinner = create_stub_session(spinner_name);
550
551 switcher.add(active, active_pid);
552 switcher.add(spinner, stub_spinner->pid());
553
554 switcher.set_active_session(active_name);
555
556 EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name));
557
558 spinner.reset();
559 switcher.remove(spinner_name);
560
561 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
562}
563
564TEST_F(ASessionSwitcher, can_handle_spinner_resurrection_under_different_name)
565{
566 using namespace testing;
567
568 auto const active = create_stub_session(active_name);
569 auto spinner = create_stub_session(spinner_name);
570
571 switcher.add(active, active_pid);
572 switcher.add(spinner, stub_spinner->pid());
573
574 switcher.set_active_session(active_name);
575
576 EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name));
577
578 spinner.reset();
579 switcher.remove(spinner_name);
580
581 std::string const new_spinner_name{"new_spinner_name"};
582 spinner = create_stub_session(new_spinner_name);
583 switcher.add(spinner, stub_spinner->pid());
584
585 EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(new_spinner_name));
586}
587
588TEST_F(ASessionSwitcher, can_handle_spinner_resurrection_under_different_pid)
589{
590 using namespace testing;
591
592 auto const active = create_stub_session(active_name);
593 auto spinner = create_stub_session(spinner_name);
594
595 switcher.add(active, active_pid);
596 switcher.add(spinner, stub_spinner->pid());
597 switcher.set_active_session(active_name);
598
599 EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name));
600
601 spinner.reset();
602 switcher.remove(spinner_name);
603
604 pid_t const new_pid{1234};
605 stub_spinner->set_pid(new_pid);
606
607 spinner = create_stub_session(spinner_name);
608 switcher.add(spinner, stub_spinner->pid());
609
610 EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name));
611}
612
613TEST_F(ASessionSwitcher, is_not_confused_by_other_session_with_name_of_dead_spinner)
614{
615 using namespace testing;
616
617 auto const active = create_stub_session(active_name);
618 auto spinner = create_stub_session(spinner_name);
619
620 switcher.add(active, active_pid);
621 switcher.add(spinner, stub_spinner->pid());
622
623 switcher.set_active_session(active_name);
624
625 spinner.reset();
626 switcher.remove(spinner_name);
627 stub_spinner->set_pid(invalid_pid);
628
629 auto const other = create_stub_session(spinner_name);
630 switcher.add(other, other_pid);
631
632 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
633}
634
635TEST_F(ASessionSwitcher, is_not_confused_by_other_session_with_pid_of_dead_spinner)
636{
637 using namespace testing;
638
639 auto const active = create_stub_session(active_name);
640 auto spinner = create_stub_session(spinner_name);
641
642 switcher.add(active, active_pid);
643 switcher.add(spinner, stub_spinner->pid());
644
645 switcher.set_active_session(active_name);
646
647 auto const old_spinner_pid = stub_spinner->pid();
648 stub_spinner->set_pid(invalid_pid);
649 spinner.reset();
650 switcher.remove(spinner_name);
651
652 auto const other = create_stub_session(spinner_name);
653 switcher.add(other, old_spinner_pid);
654
655 EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
656}

Subscribers

People subscribed via source and target branches