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
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-07-03 17:52:36 +0000
3+++ CMakeLists.txt 2014-07-18 09:49:03 +0000
4@@ -52,3 +52,13 @@
5
6 add_subdirectory(spinner/)
7 add_subdirectory(src/)
8+
9+enable_testing()
10+
11+option(MIR_ENABLE_TESTS "Build tests" ON)
12+
13+if (MIR_ENABLE_TESTS)
14+ find_package(Gtest REQUIRED)
15+ include_directories(${GMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIR})
16+ add_subdirectory(tests/)
17+endif ()
18
19=== added file 'cmake/FindGtest.cmake'
20--- cmake/FindGtest.cmake 1970-01-01 00:00:00 +0000
21+++ cmake/FindGtest.cmake 2014-07-18 09:49:03 +0000
22@@ -0,0 +1,53 @@
23+include(ExternalProject)
24+include(FindPackageHandleStandardArgs)
25+
26+#gtest
27+set(GTEST_INSTALL_DIR /usr/src/gmock/gtest/include)
28+find_path(GTEST_INCLUDE_DIR gtest/gtest.h
29+ HINTS ${GTEST_INSTALL_DIR})
30+
31+#gmock
32+find_path(GMOCK_INSTALL_DIR gmock/CMakeLists.txt
33+ HINTS /usr/src)
34+if(${GMOCK_INSTALL_DIR} STREQUAL "GMOCK_INSTALL_DIR-NOTFOUND")
35+ message(FATAL_ERROR "google-mock package not found")
36+endif()
37+
38+set(GMOCK_INSTALL_DIR ${GMOCK_INSTALL_DIR}/gmock)
39+find_path(GMOCK_INCLUDE_DIR gmock/gmock.h)
40+
41+set(GMOCK_PREFIX gmock)
42+set(GMOCK_BINARY_DIR ${CMAKE_BINARY_DIR}/${GMOCK_PREFIX}/libs)
43+set(GTEST_BINARY_DIR ${GMOCK_BINARY_DIR}/gtest)
44+
45+set(GTEST_CMAKE_ARGS "")
46+if (${CMAKE_CROSSCOMPILING})
47+ set(GTEST_CMAKE_ARGS
48+ -DCMAKE_TOOLCHAIN_FILE=${CMAKE_MODULE_PATH}/LinuxCrossCompile.cmake)
49+endif()
50+
51+ExternalProject_Add(
52+ GMock
53+ #where to build in source tree
54+ PREFIX ${GMOCK_PREFIX}
55+ #where the source is external to the project
56+ SOURCE_DIR ${GMOCK_INSTALL_DIR}
57+ #forward the compilers to the subproject so cross-arch builds work
58+ CMAKE_ARGS ${GTEST_CMAKE_ARGS}
59+ BINARY_DIR ${GMOCK_BINARY_DIR}
60+
61+ #we don't need to install, so skip
62+ INSTALL_COMMAND ""
63+)
64+
65+set(GMOCK_LIBRARY ${GMOCK_BINARY_DIR}/libgmock.a)
66+set(GMOCK_MAIN_LIBRARY ${GMOCK_BINARY_DIR}/libgmock_main.a)
67+set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARY} ${GMOCK_MAIN_LIBRARY})
68+set(GTEST_LIBRARY ${GTEST_BINARY_DIR}/libgtest.a)
69+set(GTEST_MAIN_LIBRARY ${GTEST_BINARY_DIR}/libgtest_main.a)
70+set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY})
71+set(GTEST_ALL_LIBRARIES ${GTEST_BOTH_LIBRARIES} ${GMOCK_BOTH_LIBRARIES})
72+
73+find_package_handle_standard_args(GTest DEFAULT_MSG
74+ GMOCK_INCLUDE_DIR
75+ GTEST_INCLUDE_DIR)
76
77=== modified file 'debian/control'
78--- debian/control 2014-07-15 13:50:07 +0000
79+++ debian/control 2014-07-18 09:49:03 +0000
80@@ -5,6 +5,7 @@
81 Build-Depends: cmake,
82 cmake-data,
83 debhelper (>= 9),
84+ google-mock,
85 libandroid-properties-dev [i386 amd64 armhf],
86 libboost-chrono-dev,
87 libboost-date-time-dev,
88
89=== modified file 'src/CMakeLists.txt'
90--- src/CMakeLists.txt 2014-06-16 16:53:55 +0000
91+++ src/CMakeLists.txt 2014-07-18 09:49:03 +0000
92@@ -15,18 +15,17 @@
93 # Authored by: Robert Ancell <robert.ancell@canonical.com>
94
95 set(USC_SRCS
96- main.cpp
97+ asio_dm_connection.cpp
98 dbus_screen.cpp
99- dbus_screen.h
100+ external_spinner.cpp
101 powerd_mediator.cpp
102- dm_connection.cpp
103- dm_connection.h
104+ powerkey_handler.cpp
105+ screen_state_handler.cpp
106+ server_configuration.cpp
107+ session_coordinator.cpp
108+ session_switcher.cpp
109+ surface_coordinator.cpp
110 system_compositor.cpp
111- system_compositor.h
112- screen_state_handler.cpp
113- screen_state_handler.h
114- powerkey_handler.cpp
115- powerkey_handler.h
116 )
117
118 qt5_generate_dbus_interface(dbus_screen.h com.canonical.Unity.Screen.xml)
119@@ -36,9 +35,14 @@
120 dbus_screen_adaptor DBusScreenAdaptor)
121
122 # Compile system compositor
123+add_library(
124+ usc STATIC
125+ ${USC_SRCS}
126+)
127+
128 add_executable(
129 unity-system-compositor
130- ${USC_SRCS}
131+ main.cpp
132 )
133
134 include_directories(
135@@ -52,15 +56,20 @@
136 -DDEFAULT_SPINNER="${CMAKE_INSTALL_FULL_BINDIR}/unity-system-compositor-spinner"
137 )
138
139-# Link against libmirserver
140+qt5_use_modules(usc Core DBus)
141+
142 link_directories(${MIRSERVER_LIBRARY_DIRS})
143-target_link_libraries(unity-system-compositor
144+
145+target_link_libraries(usc
146 ${MIRSERVER_LDFLAGS}
147 pthread
148 ${Boost_LIBRARIES}
149 ${GLESv2_LIBRARIES}
150 )
151-qt5_use_modules(unity-system-compositor Core DBus)
152+
153+target_link_libraries(unity-system-compositor
154+ usc
155+)
156
157 # Install into bin directory
158 install(TARGETS unity-system-compositor
159
160=== renamed file 'src/dm_connection.cpp' => 'src/asio_dm_connection.cpp'
161--- src/dm_connection.cpp 2013-07-09 19:18:59 +0000
162+++ src/asio_dm_connection.cpp 2014-07-18 09:49:03 +0000
163@@ -1,5 +1,5 @@
164 /*
165- * Copyright © 2013 Canonical Ltd.
166+ * Copyright © 2013-2014 Canonical Ltd.
167 *
168 * This program is free software: you can redistribute it and/or modify
169 * it under the terms of the GNU General Public License version 3 as
170@@ -14,37 +14,62 @@
171 * along with this program. If not, see <http://www.gnu.org/licenses/>.
172 *
173 * Authored by: Robert Ancell <robert.ancell@canonical.com>
174+ * Alexandros Frantzis <alexandros.frantzis@canonical.com>
175 */
176
177-#include "dm_connection.h"
178+#include "asio_dm_connection.h"
179
180-#include <boost/signals2.hpp>
181 #include <iostream>
182+#include <thread>
183
184 namespace ba = boost::asio;
185 namespace bs = boost::system;
186
187-void DMConnection::start()
188+usc::AsioDMConnection::AsioDMConnection(
189+ int from_dm_fd, int to_dm_fd,
190+ std::shared_ptr<DMMessageHandler> const& dm_message_handler)
191+ : from_dm_pipe{io_service, from_dm_fd},
192+ to_dm_pipe{io_service, to_dm_fd},
193+ dm_message_handler{dm_message_handler}
194+{
195+}
196+
197+usc::AsioDMConnection::~AsioDMConnection()
198+{
199+ io_service.stop();
200+ if (io_thread.joinable())
201+ io_thread.join();
202+}
203+
204+void usc::AsioDMConnection::start()
205 {
206 std::cerr << "dm_connection_start" << std::endl;
207+
208+ send_ready();
209 read_header();
210+
211+ io_thread = std::thread{
212+ [this]
213+ {
214+ io_service.run();
215+ }};
216 }
217
218-void DMConnection::send_ready()
219+void usc::AsioDMConnection::send_ready()
220 {
221 send(USCMessageID::ready, "");
222 }
223
224-void DMConnection::read_header()
225+void usc::AsioDMConnection::read_header()
226 {
227 ba::async_read(from_dm_pipe,
228 ba::buffer(message_header_bytes),
229- boost::bind(&DMConnection::on_read_header,
230- this,
231- ba::placeholders::error));
232+ std::bind(&AsioDMConnection::on_read_header,
233+ this,
234+ std::placeholders::_1));
235 }
236
237-void DMConnection::on_read_header(const bs::error_code& ec)
238+void usc::AsioDMConnection::on_read_header(bs::error_code const& ec)
239 {
240 if (!ec)
241 {
242@@ -52,15 +77,15 @@
243 ba::async_read(from_dm_pipe,
244 message_payload_buffer,
245 ba::transfer_exactly(payload_length),
246- boost::bind(&DMConnection::on_read_payload,
247- this,
248- ba::placeholders::error));
249+ std::bind(&AsioDMConnection::on_read_payload,
250+ this,
251+ std::placeholders::_1));
252 }
253 else
254 std::cerr << "Failed to read header" << std::endl;
255 }
256
257-void DMConnection::on_read_payload(const bs::error_code& ec)
258+void usc::AsioDMConnection::on_read_payload(const bs::error_code& ec)
259 {
260 if (!ec)
261 {
262@@ -86,8 +111,7 @@
263 ss << &message_payload_buffer;
264 auto client_name = ss.str();
265 std::cerr << "set_active_session '" << client_name << "'" << std::endl;
266- if (handler)
267- handler->set_active_session(client_name);
268+ dm_message_handler->set_active_session(client_name);
269 break;
270 }
271 case USCMessageID::set_next_session:
272@@ -96,8 +120,7 @@
273 ss << &message_payload_buffer;
274 auto client_name = ss.str();
275 std::cerr << "set_next_session '" << client_name << "'" << std::endl;
276- if (handler)
277- handler->set_next_session(client_name);
278+ dm_message_handler->set_next_session(client_name);
279 break;
280 }
281 default:
282@@ -111,7 +134,7 @@
283 read_header();
284 }
285
286-void DMConnection::send(USCMessageID id, std::string const& body)
287+void usc::AsioDMConnection::send(USCMessageID id, std::string const& body)
288 {
289 const size_t size = body.size();
290 const uint16_t _id = (uint16_t) id;
291
292=== added file 'src/asio_dm_connection.h'
293--- src/asio_dm_connection.h 1970-01-01 00:00:00 +0000
294+++ src/asio_dm_connection.h 2014-07-18 09:49:03 +0000
295@@ -0,0 +1,73 @@
296+/*
297+ * Copyright © 2013-2014 Canonical Ltd.
298+ *
299+ * This program is free software: you can redistribute it and/or modify
300+ * it under the terms of the GNU General Public License version 3 as
301+ * published by the Free Software Foundation.
302+ *
303+ * This program is distributed in the hope that it will be useful,
304+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
305+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
306+ * GNU General Public License for more details.
307+ *
308+ * You should have received a copy of the GNU General Public License
309+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
310+ *
311+ * Authored by: Robert Ancell <robert.ancell@canonical.com>
312+ * Alexandros Frantzis <alexandros.frantzis@canonical.com>
313+ */
314+
315+#ifndef USC_ASIO_DM_CONNECTION_H_
316+#define USC_ASIO_DM_CONNECTION_H_
317+
318+#include "dm_connection.h"
319+
320+#include <boost/asio.hpp>
321+#include <thread>
322+
323+namespace usc
324+{
325+
326+class AsioDMConnection : public DMConnection
327+{
328+public:
329+ AsioDMConnection(
330+ int from_dm_fd, int to_dm_fd,
331+ std::shared_ptr<DMMessageHandler> const& dm_message_handler);
332+ ~AsioDMConnection();
333+
334+ void start() override;
335+
336+private:
337+ enum class USCMessageID
338+ {
339+ ping = 0,
340+ pong = 1,
341+ ready = 2,
342+ session_connected = 3,
343+ set_active_session = 4,
344+ set_next_session = 5,
345+ };
346+
347+ void send_ready();
348+ void read_header();
349+ void on_read_header(const boost::system::error_code& ec);
350+ void on_read_payload(const boost::system::error_code& ec);
351+ void send(USCMessageID id, std::string const& body);
352+
353+ boost::asio::io_service io_service;
354+ boost::asio::posix::stream_descriptor from_dm_pipe;
355+ boost::asio::posix::stream_descriptor to_dm_pipe;
356+ std::thread io_thread;
357+ std::shared_ptr<DMMessageHandler> const dm_message_handler;
358+
359+ static size_t const size_of_header = 4;
360+ unsigned char message_header_bytes[size_of_header];
361+ boost::asio::streambuf message_payload_buffer;
362+ std::vector<char> write_buffer;
363+
364+};
365+
366+}
367+
368+#endif /* USC_ASIO_DM_CONNECTION_H_ */
369
370=== modified file 'src/dm_connection.h'
371--- src/dm_connection.h 2013-11-13 13:53:51 +0000
372+++ src/dm_connection.h 2014-07-18 09:49:03 +0000
373@@ -1,5 +1,5 @@
374 /*
375- * Copyright © 2013 Canonical Ltd.
376+ * Copyright © 2014 Canonical Ltd.
377 *
378 * This program is free software: you can redistribute it and/or modify
379 * it under the terms of the GNU General Public License version 3 as
380@@ -13,60 +13,37 @@
381 * You should have received a copy of the GNU General Public License
382 * along with this program. If not, see <http://www.gnu.org/licenses/>.
383 *
384- * Authored by: Robert Ancell <robert.ancell@canonical.com>
385+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
386 */
387
388-#ifndef DM_CONNECTION_H_
389-#define DM_CONNECTION_H_
390-
391-#include <boost/asio.hpp>
392+#ifndef USC_DM_CONNECTION_H_
393+#define USC_DM_CONNECTION_H_
394+
395+#include <string>
396+
397+namespace usc
398+{
399
400 class DMMessageHandler
401 {
402 public:
403- virtual void set_active_session(std::string client_name) = 0;
404- virtual void set_next_session(std::string client_name) = 0;
405-};
406-
407-enum class USCMessageID
408-{
409- ping = 0,
410- pong = 1,
411- ready = 2,
412- session_connected = 3,
413- set_active_session = 4,
414- set_next_session = 5,
415+ virtual void set_active_session(std::string const& client_name) = 0;
416+ virtual void set_next_session(std::string const& client_name) = 0;
417 };
418
419 class DMConnection
420 {
421 public:
422- DMConnection(boost::asio::io_service& io_service, int from_dm_fd, int to_dm_fd) :
423- from_dm_pipe(io_service, from_dm_fd),
424- to_dm_pipe(io_service, to_dm_fd) {};
425-
426- void set_handler(DMMessageHandler *handler)
427- {
428- this->handler = handler;
429- }
430-
431- void start();
432-
433- void send_ready();
434-
435-private:
436- DMMessageHandler *handler;
437- boost::asio::posix::stream_descriptor from_dm_pipe;
438- boost::asio::posix::stream_descriptor to_dm_pipe;
439- static size_t const size_of_header = 4;
440- unsigned char message_header_bytes[size_of_header];
441- boost::asio::streambuf message_payload_buffer;
442- std::vector<char> write_buffer;
443-
444- void read_header();
445- void on_read_header(const boost::system::error_code& ec);
446- void on_read_payload(const boost::system::error_code& ec);
447- void send(USCMessageID id, std::string const& body);
448+ virtual ~DMConnection() = default;
449+
450+ virtual void start() = 0;
451+
452+protected:
453+ DMConnection() = default;
454+ DMConnection(DMConnection const&) = delete;
455+ DMConnection& operator=(DMConnection const&) = delete;
456 };
457
458-#endif /* DM_CONNECTION_H_ */
459+}
460+
461+#endif /* USC_DM_CONNECTION_H_ */
462
463=== added file 'src/external_spinner.cpp'
464--- src/external_spinner.cpp 1970-01-01 00:00:00 +0000
465+++ src/external_spinner.cpp 2014-07-18 09:49:03 +0000
466@@ -0,0 +1,54 @@
467+/*
468+ * Copyright © 2014 Canonical Ltd.
469+ *
470+ * This program is free software: you can redistribute it and/or modify
471+ * it under the terms of the GNU General Public License version 3 as
472+ * published by the Free Software Foundation.
473+ *
474+ * This program is distributed in the hope that it will be useful,
475+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
476+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
477+ * GNU General Public License for more details.
478+ *
479+ * You should have received a copy of the GNU General Public License
480+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
481+ *
482+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
483+ */
484+
485+#include "external_spinner.h"
486+
487+usc::ExternalSpinner::ExternalSpinner(
488+ std::string const& executable,
489+ std::string const& mir_socket)
490+ : executable{executable},
491+ mir_socket{mir_socket}
492+{
493+}
494+
495+usc::ExternalSpinner::~ExternalSpinner()
496+{
497+ kill();
498+}
499+
500+void usc::ExternalSpinner::ensure_running()
501+{
502+ if (executable.empty() || process.state() != QProcess::NotRunning)
503+ return;
504+
505+ // Launch spinner process to provide default background when a session isn't ready
506+ QStringList env = QProcess::systemEnvironment();
507+ env << "MIR_SOCKET=" + QString::fromStdString(mir_socket);
508+ process.setEnvironment(env);
509+ process.start(executable.c_str());
510+}
511+
512+void usc::ExternalSpinner::kill()
513+{
514+ process.close();
515+}
516+
517+pid_t usc::ExternalSpinner::pid()
518+{
519+ return process.processId();
520+}
521
522=== added file 'src/external_spinner.h'
523--- src/external_spinner.h 1970-01-01 00:00:00 +0000
524+++ src/external_spinner.h 2014-07-18 09:49:03 +0000
525@@ -0,0 +1,49 @@
526+/*
527+ * Copyright © 2014 Canonical Ltd.
528+ *
529+ * This program is free software: you can redistribute it and/or modify
530+ * it under the terms of the GNU General Public License version 3 as
531+ * published by the Free Software Foundation.
532+ *
533+ * This program is distributed in the hope that it will be useful,
534+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
535+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
536+ * GNU General Public License for more details.
537+ *
538+ * You should have received a copy of the GNU General Public License
539+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
540+ *
541+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
542+ */
543+
544+#ifndef USC_EXTERNAL_SPINNER_H_
545+#define USC_EXTERNAL_SPINNER_H_
546+
547+#include "spinner.h"
548+
549+#include <QProcess>
550+#include <string>
551+
552+namespace usc
553+{
554+
555+class ExternalSpinner : public Spinner
556+{
557+public:
558+ ExternalSpinner(std::string const& executable,
559+ std::string const& mir_socket);
560+ ~ExternalSpinner();
561+
562+ void ensure_running() override;
563+ void kill() override;
564+ pid_t pid() override;
565+
566+private:
567+ std::string const executable;
568+ std::string const mir_socket;
569+ QProcess process;
570+};
571+
572+}
573+
574+#endif
575
576=== modified file 'src/main.cpp'
577--- src/main.cpp 2013-10-31 22:00:19 +0000
578+++ src/main.cpp 2014-07-18 09:49:03 +0000
579@@ -1,5 +1,5 @@
580 /*
581- * Copyright © 2013 Canonical Ltd.
582+ * Copyright © 2013-2014 Canonical Ltd.
583 *
584 * This program is free software: you can redistribute it and/or modify
585 * it under the terms of the GNU General Public License version 3 as
586@@ -14,17 +14,22 @@
587 * along with this program. If not, see <http://www.gnu.org/licenses/>.
588 *
589 * Authored by: Robert Ancell <robert.ancell@canonical.com>
590+ * Alexandros Frantzis <alexandros.frantzis@canonical.com>
591 */
592
593 #include "system_compositor.h"
594+#include "server_configuration.h"
595+
596 #include <mir/report_exception.h>
597 #include <iostream>
598
599 int main(int argc, char *argv[])
600 try
601 {
602- SystemCompositor system_compositor;
603- system_compositor.run(argc, argv);
604+ auto const config = std::make_shared<usc::ServerConfiguration>(argc, argv);
605+
606+ usc::SystemCompositor system_compositor{config};
607+ system_compositor.run();
608
609 return 0;
610 }
611
612=== added file 'src/server_configuration.cpp'
613--- src/server_configuration.cpp 1970-01-01 00:00:00 +0000
614+++ src/server_configuration.cpp 2014-07-18 09:49:03 +0000
615@@ -0,0 +1,196 @@
616+/*
617+ * Copyright © 2014 Canonical Ltd.
618+ *
619+ * This program is free software: you can redistribute it and/or modify
620+ * it under the terms of the GNU General Public License version 3 as
621+ * published by the Free Software Foundation.
622+ *
623+ * This program is distributed in the hope that it will be useful,
624+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
625+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
626+ * GNU General Public License for more details.
627+ *
628+ * You should have received a copy of the GNU General Public License
629+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
630+ *
631+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
632+ */
633+
634+#include "server_configuration.h"
635+#include "external_spinner.h"
636+#include "session_coordinator.h"
637+#include "surface_coordinator.h"
638+#include "asio_dm_connection.h"
639+#include "session_switcher.h"
640+
641+#include <mir/options/default_configuration.h>
642+#include <mir/input/cursor_listener.h>
643+#include <mir/server_status_listener.h>
644+#include <mir/shell/focus_controller.h>
645+#include <mir/scene/session.h>
646+
647+#include <boost/program_options.hpp>
648+
649+namespace msh = mir::shell;
650+namespace ms = mir::scene;
651+namespace mf = mir::frontend;
652+namespace mi = mir::input;
653+namespace mo = mir::options;
654+namespace po = boost::program_options;
655+
656+namespace
657+{
658+
659+class ConfigurationOptions : public mo::DefaultConfiguration
660+{
661+public:
662+ ConfigurationOptions(int argc, char const* argv[]) :
663+ DefaultConfiguration(argc, argv)
664+ {
665+ add_options()
666+ ("from-dm-fd", po::value<int>(), "File descriptor of read end of pipe from display manager [int]")
667+ ("to-dm-fd", po::value<int>(), "File descriptor of write end of pipe to display manager [int]")
668+ ("blacklist", po::value<std::string>(), "Video blacklist regex to use")
669+ ("version", "Show version of Unity System Compositor")
670+ ("spinner", po::value<std::string>(), "Path to spinner executable")
671+ ("public-socket", po::value<bool>(), "Make the socket file publicly writable")
672+ ("enable-hardware-cursor", po::value<bool>(), "Enable the hardware cursor (disabled by default)")
673+ ("inactivity-display-off-timeout", po::value<int>(), "The time in seconds before the screen is turned off when there are no active sessions")
674+ ("inactivity-display-dim-timeout", po::value<int>(), "The time in seconds before the screen is dimmed when there are no active sessions")
675+ ("shutdown-timeout", po::value<int>(), "The time in milli-seconds the power key must be held to initiate a clean system shutdown")
676+ ("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")
677+ ("disable-inactivity-policy", po::value<bool>(), "Disables handling user inactivity and power key");
678+ }
679+
680+ void parse_config_file(
681+ boost::program_options::options_description& options_description,
682+ mo::ProgramOption& options) const override
683+ {
684+ options.parse_file(options_description, "unity-system-compositor.conf");
685+ }
686+};
687+
688+}
689+
690+usc::ServerConfiguration::ServerConfiguration(int argc, char** argv)
691+ : mir::DefaultServerConfiguration(
692+ std::make_shared<ConfigurationOptions>(argc, const_cast<char const **>(argv)))
693+{
694+}
695+
696+std::shared_ptr<mi::CursorListener>
697+usc::ServerConfiguration::the_cursor_listener()
698+{
699+ struct NullCursorListener : public mi::CursorListener
700+ {
701+ void cursor_moved_to(float, float) override
702+ {
703+ }
704+ };
705+
706+ // This is a workaround for u8 desktop preview in 14.04 for the lack of client cursor API.
707+ // We need to disable the cursor for XMir but leave it on for the desktop preview.
708+ // Luckily as it stands they run inside seperate instances of USC. ~racarr
709+ if (enable_hardware_cursor())
710+ return mir::DefaultServerConfiguration::the_cursor_listener();
711+ else
712+ return std::make_shared<NullCursorListener>();
713+}
714+
715+std::shared_ptr<mir::ServerStatusListener>
716+usc::ServerConfiguration::the_server_status_listener()
717+{
718+ struct ServerStatusListener : public mir::ServerStatusListener
719+ {
720+ ServerStatusListener(
721+ std::shared_ptr<msh::FocusController> const& focus_controller)
722+ : focus_controller{focus_controller}
723+ {
724+ }
725+
726+ void paused() override
727+ {
728+ std::cerr << "pause" << std::endl;
729+
730+ if (auto active_session = weak_active_session().lock())
731+ active_session->set_lifecycle_state(mir_lifecycle_state_will_suspend);
732+ }
733+
734+ void resumed() override
735+ {
736+ std::cerr << "resume" << std::endl;
737+
738+ if (auto active_session = weak_active_session().lock())
739+ active_session->set_lifecycle_state(mir_lifecycle_state_resumed);
740+ }
741+
742+ void started() override
743+ {
744+ }
745+
746+ std::weak_ptr<ms::Session> weak_active_session()
747+ {
748+ return focus_controller->focussed_application();
749+ }
750+
751+ std::shared_ptr<msh::FocusController> const focus_controller;
752+ };
753+
754+ return std::make_shared<ServerStatusListener>(the_focus_controller());
755+}
756+
757+std::shared_ptr<mir::scene::SessionCoordinator>
758+usc::ServerConfiguration::wrap_session_coordinator(
759+ std::shared_ptr<ms::SessionCoordinator> const& wrapped)
760+{
761+ return std::make_shared<SessionCoordinator>(
762+ wrapped,
763+ the_session_switcher());
764+}
765+
766+std::shared_ptr<mir::scene::SurfaceCoordinator>
767+usc::ServerConfiguration::wrap_surface_coordinator(
768+ std::shared_ptr<ms::SurfaceCoordinator> const& wrapped)
769+{
770+ return std::make_shared<SurfaceCoordinator>(
771+ wrapped,
772+ the_session_switcher());
773+}
774+
775+std::shared_ptr<usc::Spinner> usc::ServerConfiguration::the_spinner()
776+{
777+ return spinner(
778+ [this]
779+ {
780+ return std::make_shared<ExternalSpinner>(
781+ spinner_executable(),
782+ get_socket_file());
783+ });
784+}
785+
786+std::shared_ptr<usc::SessionSwitcher> usc::ServerConfiguration::the_session_switcher()
787+{
788+ return session_switcher(
789+ [this]
790+ {
791+ return std::make_shared<SessionSwitcher>(
792+ the_spinner());
793+ });
794+}
795+
796+std::shared_ptr<usc::DMMessageHandler> usc::ServerConfiguration::the_dm_message_handler()
797+{
798+ return the_session_switcher();
799+}
800+
801+std::shared_ptr<usc::DMConnection> usc::ServerConfiguration::the_dm_connection()
802+{
803+ return dm_connection(
804+ [this]
805+ {
806+ return std::make_shared<AsioDMConnection>(
807+ the_options()->get("from-dm-fd", -1),
808+ the_options()->get("to-dm-fd", -1),
809+ the_dm_message_handler());
810+ });
811+}
812
813=== added file 'src/server_configuration.h'
814--- src/server_configuration.h 1970-01-01 00:00:00 +0000
815+++ src/server_configuration.h 2014-07-18 09:49:03 +0000
816@@ -0,0 +1,119 @@
817+/*
818+ * Copyright © 2014 Canonical Ltd.
819+ *
820+ * This program is free software: you can redistribute it and/or modify
821+ * it under the terms of the GNU General Public License version 3 as
822+ * published by the Free Software Foundation.
823+ *
824+ * This program is distributed in the hope that it will be useful,
825+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
826+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
827+ * GNU General Public License for more details.
828+ *
829+ * You should have received a copy of the GNU General Public License
830+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
831+ *
832+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
833+ */
834+
835+#ifndef USC_SERVER_CONFIGURATION_H_
836+#define USC_SERVER_CONFIGURATION_H_
837+
838+#include <mir/default_server_configuration.h>
839+#include <mir/options/option.h>
840+
841+namespace usc
842+{
843+class Spinner;
844+class SessionSwitcher;
845+class DMMessageHandler;
846+class DMConnection;
847+
848+class ServerConfiguration : public mir::DefaultServerConfiguration
849+{
850+public:
851+ ServerConfiguration(int argc, char** argv);
852+
853+ virtual std::shared_ptr<Spinner> the_spinner();
854+ virtual std::shared_ptr<DMMessageHandler> the_dm_message_handler();
855+ virtual std::shared_ptr<DMConnection> the_dm_connection();
856+
857+ bool show_version()
858+ {
859+ return the_options()->is_set("version");
860+ }
861+
862+ int inactivity_display_off_timeout()
863+ {
864+ return the_options()->get("inactivity-display-off-timeout", 60);
865+ }
866+
867+ int inactivity_display_dim_timeout()
868+ {
869+ return the_options()->get("inactivity-display-dim-timeout", 45);
870+ }
871+
872+ int shutdown_timeout()
873+ {
874+ return the_options()->get("shutdown-timeout", 5000);
875+ }
876+
877+ int power_key_ignore_timeout()
878+ {
879+ return the_options()->get("power-key-ignore-timeout", 1500);
880+ }
881+
882+ bool enable_hardware_cursor()
883+ {
884+ return the_options()->get("enable-hardware-cursor", false);
885+ }
886+
887+ bool disable_inactivity_policy()
888+ {
889+ return the_options()->get("disable-inactivity-policy", false);
890+ }
891+
892+ std::string blacklist()
893+ {
894+ auto x = the_options()->get("blacklist", "");
895+ //boost::trim(x);
896+ return x;
897+ }
898+
899+ std::string spinner_executable()
900+ {
901+ // TODO: once our default spinner is ready for use everywhere, replace
902+ // default value with DEFAULT_SPINNER instead of the empty string.
903+ auto x = the_options()->get("spinner", "");
904+ //boost::trim(x);
905+ return x;
906+ }
907+
908+ bool public_socket()
909+ {
910+ return !the_options()->is_set("no-file") && the_options()->get("public-socket", true);
911+ }
912+
913+ std::string get_socket_file()
914+ {
915+ // the_socket_file is private, so we have to re-implement it here
916+ return the_options()->get("file", "/tmp/mir_socket");
917+ }
918+
919+protected:
920+ virtual std::shared_ptr<SessionSwitcher> the_session_switcher();
921+ std::shared_ptr<mir::input::CursorListener> the_cursor_listener() override;
922+ std::shared_ptr<mir::ServerStatusListener> the_server_status_listener() override;
923+ std::shared_ptr<mir::scene::SessionCoordinator> wrap_session_coordinator(
924+ std::shared_ptr<mir::scene::SessionCoordinator> const& wrapped) override;
925+ std::shared_ptr<mir::scene::SurfaceCoordinator> wrap_surface_coordinator(
926+ std::shared_ptr<mir::scene::SurfaceCoordinator> const& wrapped) override;
927+
928+ mir::CachedPtr<Spinner> spinner;
929+ mir::CachedPtr<DMConnection> dm_connection;
930+ mir::CachedPtr<SessionSwitcher> session_switcher;
931+};
932+
933+}
934+
935+#endif
936
937=== added file 'src/session_coordinator.cpp'
938--- src/session_coordinator.cpp 1970-01-01 00:00:00 +0000
939+++ src/session_coordinator.cpp 2014-07-18 09:49:03 +0000
940@@ -0,0 +1,117 @@
941+/*
942+ * Copyright © 2014 Canonical Ltd.
943+ *
944+ * This program is free software: you can redistribute it and/or modify
945+ * it under the terms of the GNU General Public License version 3 as
946+ * published by the Free Software Foundation.
947+ *
948+ * This program is distributed in the hope that it will be useful,
949+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
950+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
951+ * GNU General Public License for more details.
952+ *
953+ * You should have received a copy of the GNU General Public License
954+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
955+ *
956+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
957+ */
958+
959+#include "session_coordinator.h"
960+#include "session_switcher.h"
961+
962+#include <mir/scene/session.h>
963+
964+#include <iostream>
965+
966+namespace msh = mir::shell;
967+namespace ms = mir::scene;
968+namespace mf = mir::frontend;
969+
970+namespace
971+{
972+
973+class UscSession : public usc::Session
974+{
975+public:
976+ UscSession(
977+ std::shared_ptr<ms::Session> const& scene_session,
978+ msh::FocusController& focus_controller)
979+ : scene_session{scene_session},
980+ focus_controller(focus_controller)
981+ {
982+ }
983+
984+ std::string name()
985+ {
986+ return scene_session->name();
987+ }
988+
989+ void show() override
990+ {
991+ scene_session->show();
992+ }
993+
994+ void hide() override
995+ {
996+ scene_session->hide();
997+ }
998+
999+ void raise_and_focus() override
1000+ {
1001+ focus_controller.set_focus_to(scene_session);
1002+ }
1003+
1004+ bool corresponds_to(mir::scene::Session const* s) override
1005+ {
1006+ return scene_session.get() == s;
1007+ }
1008+
1009+ std::shared_ptr<ms::Session> const scene_session;
1010+ msh::FocusController& focus_controller;
1011+};
1012+
1013+}
1014+
1015+usc::SessionCoordinator::SessionCoordinator(
1016+ std::shared_ptr<ms::SessionCoordinator> const& wrapped,
1017+ std::shared_ptr<SessionSwitcher> const& session_switcher)
1018+ : msh::SessionCoordinatorWrapper{wrapped},
1019+ session_switcher{session_switcher}
1020+{
1021+}
1022+
1023+std::shared_ptr<mf::Session>
1024+usc::SessionCoordinator::open_session(
1025+ pid_t client_pid,
1026+ std::string const& name,
1027+ std::shared_ptr<mf::EventSink> const& sink)
1028+{
1029+ std::cerr << "Opening session " << name << std::endl;
1030+
1031+ // We need ms::Session objects because that is what the focus controller
1032+ // works with. But the mf::SessionCoordinator interface deals with mf::Session objects.
1033+ // So we cast here since in practice, these objects are also ms::Sessions.
1034+ auto orig = std::dynamic_pointer_cast<ms::Session>(
1035+ msh::SessionCoordinatorWrapper::open_session(client_pid, name, sink));
1036+ if (!orig)
1037+ {
1038+ std::cerr << "Unexpected non-shell session" << std::endl;
1039+ return std::shared_ptr<mf::Session>();
1040+ }
1041+
1042+ auto const usc_session = std::make_shared<UscSession>(orig, *this);
1043+
1044+ session_switcher->add(usc_session, client_pid);
1045+
1046+ return orig;
1047+}
1048+
1049+void usc::SessionCoordinator::close_session(
1050+ std::shared_ptr<mf::Session> const& session)
1051+{
1052+ std::cerr << "Closing session " << session->name() << std::endl;
1053+
1054+ msh::SessionCoordinatorWrapper::close_session(session);
1055+
1056+ session_switcher->remove(session->name());
1057+}
1058
1059=== added file 'src/session_coordinator.h'
1060--- src/session_coordinator.h 1970-01-01 00:00:00 +0000
1061+++ src/session_coordinator.h 2014-07-18 09:49:03 +0000
1062@@ -0,0 +1,54 @@
1063+/*
1064+ * Copyright © 2014 Canonical Ltd.
1065+ *
1066+ * This program is free software: you can redistribute it and/or modify
1067+ * it under the terms of the GNU General Public License version 3 as
1068+ * published by the Free Software Foundation.
1069+ *
1070+ * This program is distributed in the hope that it will be useful,
1071+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1072+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1073+ * GNU General Public License for more details.
1074+ *
1075+ * You should have received a copy of the GNU General Public License
1076+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1077+ *
1078+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
1079+ */
1080+
1081+#ifndef USC_SESSION_COORDINATOR_H_
1082+#define USC_SESSION_COORDINATOR_H_
1083+
1084+#include <mir/shell/session_coordinator_wrapper.h>
1085+
1086+#include <memory>
1087+
1088+namespace mir
1089+{
1090+namespace scene { class SurfaceCoordinator; }
1091+}
1092+
1093+namespace usc
1094+{
1095+class SessionSwitcher;
1096+
1097+class SessionCoordinator : public mir::shell::SessionCoordinatorWrapper
1098+{
1099+public:
1100+ SessionCoordinator(
1101+ std::shared_ptr<mir::scene::SessionCoordinator> const& wrapped,
1102+ std::shared_ptr<SessionSwitcher> const& session_switcher);
1103+
1104+private:
1105+ std::shared_ptr<mir::frontend::Session> open_session(
1106+ pid_t client_pid,
1107+ std::string const& name,
1108+ std::shared_ptr<mir::frontend::EventSink> const& sink) override;
1109+ void close_session(std::shared_ptr<mir::frontend::Session> const& session) override;
1110+
1111+ std::shared_ptr<SessionSwitcher> const session_switcher;
1112+};
1113+
1114+}
1115+
1116+#endif
1117
1118=== added file 'src/session_switcher.cpp'
1119--- src/session_switcher.cpp 1970-01-01 00:00:00 +0000
1120+++ src/session_switcher.cpp 2014-07-18 09:49:03 +0000
1121@@ -0,0 +1,190 @@
1122+/*
1123+ * Copyright © 2014 Canonical Ltd.
1124+ *
1125+ * This program is free software: you can redistribute it and/or modify
1126+ * it under the terms of the GNU General Public License version 3 as
1127+ * published by the Free Software Foundation.
1128+ *
1129+ * This program is distributed in the hope that it will be useful,
1130+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1131+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1132+ * GNU General Public License for more details.
1133+ *
1134+ * You should have received a copy of the GNU General Public License
1135+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1136+ *
1137+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
1138+ */
1139+
1140+#include "session_switcher.h"
1141+#include "spinner.h"
1142+
1143+usc::SessionSwitcher::SessionSwitcher(std::shared_ptr<Spinner> const& spinner)
1144+ : spinner_process{spinner},
1145+ booting{true}
1146+{
1147+}
1148+
1149+void usc::SessionSwitcher::add(std::shared_ptr<Session> const& session, pid_t pid)
1150+{
1151+ std::lock_guard<std::mutex> lock{mutex};
1152+
1153+ if (pid == spinner_process->pid())
1154+ spinner_name = session->name();
1155+
1156+ sessions[session->name()] = SessionInfo(session);
1157+ update_displayed_sessions();
1158+}
1159+
1160+void usc::SessionSwitcher::remove(std::string const& name)
1161+{
1162+ std::lock_guard<std::mutex> lock{mutex};
1163+
1164+ if (name == spinner_name)
1165+ spinner_name = "";
1166+
1167+ sessions.erase(name);
1168+ update_displayed_sessions();
1169+}
1170+
1171+void usc::SessionSwitcher::set_active_session(std::string const& name)
1172+{
1173+ std::lock_guard<std::mutex> lock{mutex};
1174+
1175+ active_name = name;
1176+ update_displayed_sessions();
1177+}
1178+
1179+void usc::SessionSwitcher::set_next_session(std::string const& name)
1180+{
1181+ std::lock_guard<std::mutex> lock{mutex};
1182+
1183+ next_name = name;
1184+ update_displayed_sessions();
1185+}
1186+
1187+void usc::SessionSwitcher::mark_ready(mir::scene::Session const* scene_session)
1188+{
1189+ std::lock_guard<std::mutex> lock{mutex};
1190+
1191+ for (auto& pair : sessions)
1192+ {
1193+ if (pair.second.session && pair.second.session->corresponds_to(scene_session))
1194+ pair.second.ready = true;
1195+ }
1196+
1197+ update_displayed_sessions();
1198+}
1199+
1200+void usc::SessionSwitcher::update_displayed_sessions()
1201+{
1202+ hide_uninteresting_sessions();
1203+
1204+ bool show_spinner = false;
1205+ ShowMode show_spinner_mode{ShowMode::as_next};
1206+ bool const allowed_to_display_active =
1207+ is_session_ready_for_display(next_name) ||
1208+ !is_session_expected_to_become_ready(next_name) ||
1209+ !booting;
1210+ bool show_active = false;
1211+
1212+ if (allowed_to_display_active && is_session_ready_for_display(active_name))
1213+ {
1214+ show_session(active_name, ShowMode::as_active);
1215+ show_active = true;
1216+ booting = false;
1217+ }
1218+ else if (is_session_expected_to_become_ready(active_name))
1219+ {
1220+ show_spinner = true;
1221+ show_spinner_mode = ShowMode::as_active;
1222+ }
1223+
1224+ bool const allowed_to_display_next = !show_spinner && show_active;
1225+
1226+ if (allowed_to_display_next)
1227+ {
1228+ if (is_session_ready_for_display(next_name))
1229+ {
1230+ show_session(next_name, ShowMode::as_next);
1231+ }
1232+ else if (is_session_expected_to_become_ready(next_name))
1233+ {
1234+ show_spinner = true;
1235+ show_spinner_mode = ShowMode::as_next;
1236+ }
1237+ }
1238+ else if (is_session_ready_for_display(next_name))
1239+ {
1240+ hide_session(next_name);
1241+ }
1242+
1243+ if (show_spinner)
1244+ ensure_spinner_will_be_shown(show_spinner_mode);
1245+ else
1246+ ensure_spinner_is_not_running();
1247+}
1248+
1249+void usc::SessionSwitcher::hide_uninteresting_sessions()
1250+{
1251+ for (auto const& pair : sessions)
1252+ {
1253+ if (pair.second.session->name() != active_name &&
1254+ pair.second.session->name() != next_name)
1255+ {
1256+ pair.second.session->hide();
1257+ }
1258+ }
1259+}
1260+
1261+bool usc::SessionSwitcher::is_session_ready_for_display(std::string const& name)
1262+{
1263+ auto const iter = sessions.find(name);
1264+ if (iter == sessions.end())
1265+ return false;
1266+
1267+ return iter->second.session && iter->second.ready;
1268+}
1269+
1270+bool usc::SessionSwitcher::is_session_expected_to_become_ready(std::string const& name)
1271+{
1272+ return !name.empty();
1273+}
1274+
1275+void usc::SessionSwitcher::show_session(
1276+ std::string const& name,
1277+ ShowMode show_mode)
1278+{
1279+ auto& session = sessions[name].session;
1280+
1281+ if (show_mode == ShowMode::as_active)
1282+ session->raise_and_focus();
1283+
1284+ session->show();
1285+}
1286+
1287+void usc::SessionSwitcher::hide_session(std::string const& name)
1288+{
1289+ auto& session = sessions[name].session;
1290+ session->hide();
1291+}
1292+
1293+void usc::SessionSwitcher::ensure_spinner_will_be_shown(ShowMode show_mode)
1294+{
1295+ auto const iter = sessions.find(spinner_name);
1296+ if (iter == sessions.end())
1297+ {
1298+ spinner_process->ensure_running();
1299+ }
1300+ else
1301+ {
1302+ if (show_mode == ShowMode::as_active)
1303+ iter->second.session->raise_and_focus();
1304+ iter->second.session->show();
1305+ }
1306+}
1307+
1308+void usc::SessionSwitcher::ensure_spinner_is_not_running()
1309+{
1310+ spinner_process->kill();
1311+}
1312
1313=== added file 'src/session_switcher.h'
1314--- src/session_switcher.h 1970-01-01 00:00:00 +0000
1315+++ src/session_switcher.h 2014-07-18 09:49:03 +0000
1316@@ -0,0 +1,98 @@
1317+/*
1318+ * Copyright © 2014 Canonical Ltd.
1319+ *
1320+ * This program is free software: you can redistribute it and/or modify
1321+ * it under the terms of the GNU General Public License version 3 as
1322+ * published by the Free Software Foundation.
1323+ *
1324+ * This program is distributed in the hope that it will be useful,
1325+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1326+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1327+ * GNU General Public License for more details.
1328+ *
1329+ * You should have received a copy of the GNU General Public License
1330+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1331+ *
1332+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
1333+ */
1334+
1335+#ifndef USC_SESSION_SWITCHER_H_
1336+#define USC_SESSION_SWITCHER_H_
1337+
1338+#include "dm_connection.h"
1339+
1340+#include <map>
1341+#include <memory>
1342+#include <mutex>
1343+
1344+namespace mir { namespace scene { class Session; }};
1345+namespace usc
1346+{
1347+class Spinner;
1348+
1349+class Session
1350+{
1351+public:
1352+ virtual ~Session() = default;
1353+
1354+ virtual std::string name() = 0;
1355+ virtual void show() = 0;
1356+ virtual void hide() = 0;
1357+ virtual void raise_and_focus() = 0;
1358+ virtual bool corresponds_to(mir::scene::Session const*) = 0;
1359+
1360+protected:
1361+ Session() = default;
1362+ Session(Session const&) = delete;
1363+ Session& operator=(Session const&) = delete;
1364+};
1365+
1366+class SessionSwitcher : public DMMessageHandler
1367+{
1368+public:
1369+ SessionSwitcher(std::shared_ptr<Spinner> const& spinner);
1370+
1371+ void add(std::shared_ptr<Session> const& session, pid_t pid);
1372+ void remove(std::string const& name);
1373+ void mark_ready(mir::scene::Session const*);
1374+
1375+ /* From DMMessageHandler */
1376+ void set_active_session(std::string const& name) override;
1377+ void set_next_session(std::string const& name) override;
1378+
1379+private:
1380+ enum class ShowMode { as_active, as_next };
1381+
1382+ void update_displayed_sessions();
1383+ void hide_uninteresting_sessions();
1384+ bool is_session_ready_for_display(std::string const& name);
1385+ bool is_session_expected_to_become_ready(std::string const& name);
1386+ void show_session(std::string const& name, ShowMode show_mode);
1387+ void hide_session(std::string const& name);
1388+ void ensure_spinner_will_be_shown(ShowMode show_mode);
1389+ void ensure_spinner_is_not_running();
1390+
1391+ struct SessionInfo
1392+ {
1393+ SessionInfo() = default;
1394+ SessionInfo(std::shared_ptr<Session> session)
1395+ : session{session}
1396+ {
1397+ }
1398+ std::shared_ptr<Session> session;
1399+ bool ready = false;
1400+ };
1401+
1402+ std::mutex mutex;
1403+ std::shared_ptr<Spinner> const spinner_process;
1404+ std::map<std::string, SessionInfo> sessions;
1405+ std::string active_name;
1406+ std::string next_name;
1407+ std::string spinner_name;
1408+ bool booting;
1409+};
1410+
1411+}
1412+
1413+#endif
1414+
1415
1416=== added file 'src/spinner.h'
1417--- src/spinner.h 1970-01-01 00:00:00 +0000
1418+++ src/spinner.h 2014-07-18 09:49:03 +0000
1419@@ -0,0 +1,44 @@
1420+/*
1421+ * Copyright © 2014 Canonical Ltd.
1422+ *
1423+ * This program is free software: you can redistribute it and/or modify
1424+ * it under the terms of the GNU General Public License version 3 as
1425+ * published by the Free Software Foundation.
1426+ *
1427+ * This program is distributed in the hope that it will be useful,
1428+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1429+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1430+ * GNU General Public License for more details.
1431+ *
1432+ * You should have received a copy of the GNU General Public License
1433+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1434+ *
1435+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
1436+ */
1437+
1438+#ifndef USC_SPINNER_H_
1439+#define USC_SPINNER_H_
1440+
1441+#include <sys/types.h>
1442+
1443+namespace usc
1444+{
1445+
1446+class Spinner
1447+{
1448+public:
1449+ virtual ~Spinner() = default;
1450+
1451+ virtual void ensure_running() = 0;
1452+ virtual void kill() = 0;
1453+ virtual pid_t pid() = 0;
1454+
1455+protected:
1456+ Spinner() = default;
1457+ Spinner(Spinner const&) = delete;
1458+ Spinner& operator=(Spinner const&) = delete;
1459+};
1460+
1461+}
1462+
1463+#endif
1464
1465=== added file 'src/surface_coordinator.cpp'
1466--- src/surface_coordinator.cpp 1970-01-01 00:00:00 +0000
1467+++ src/surface_coordinator.cpp 2014-07-18 09:49:03 +0000
1468@@ -0,0 +1,86 @@
1469+/*
1470+ * Copyright © 2014 Canonical Ltd.
1471+ *
1472+ * This program is free software: you can redistribute it and/or modify
1473+ * it under the terms of the GNU General Public License version 3 as
1474+ * published by the Free Software Foundation.
1475+ *
1476+ * This program is distributed in the hope that it will be useful,
1477+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1478+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1479+ * GNU General Public License for more details.
1480+ *
1481+ * You should have received a copy of the GNU General Public License
1482+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1483+ *
1484+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
1485+ */
1486+
1487+#include "surface_coordinator.h"
1488+#include "session_switcher.h"
1489+
1490+#include <mir/scene/null_surface_observer.h>
1491+#include <mir/scene/surface.h>
1492+
1493+namespace ms = mir::scene;
1494+namespace msh = mir::shell;
1495+
1496+namespace
1497+{
1498+
1499+struct SessionReadyObserver : ms::NullSurfaceObserver,
1500+ std::enable_shared_from_this<SessionReadyObserver>
1501+{
1502+ SessionReadyObserver(
1503+ std::shared_ptr<usc::SessionSwitcher> const& switcher,
1504+ std::shared_ptr<ms::Surface> const& surface,
1505+ ms::Session const* session)
1506+ : switcher{switcher},
1507+ surface{surface},
1508+ session{session}
1509+ {
1510+ }
1511+
1512+ void frame_posted(int) override
1513+ {
1514+ ++num_frames_posted;
1515+ if (num_frames_posted == num_frames_for_session_ready)
1516+ {
1517+ switcher->mark_ready(session);
1518+ surface->remove_observer(shared_from_this());
1519+ }
1520+ }
1521+
1522+ std::shared_ptr<usc::SessionSwitcher> const switcher;
1523+ std::shared_ptr<ms::Surface> const surface;
1524+ ms::Session const* const session;
1525+ // We need to wait for the second frame before marking the session
1526+ // as ready. The first frame posted from sessions is a blank frame.
1527+ // TODO: Solve this issue at its root and remove this workaround
1528+ int const num_frames_for_session_ready{2};
1529+ int num_frames_posted{0};
1530+};
1531+
1532+}
1533+
1534+usc::SurfaceCoordinator::SurfaceCoordinator(
1535+ std::shared_ptr<ms::SurfaceCoordinator> const& wrapped,
1536+ std::shared_ptr<SessionSwitcher> const& session_switcher)
1537+ : msh::SurfaceCoordinatorWrapper{wrapped},
1538+ session_switcher{session_switcher}
1539+{
1540+}
1541+
1542+std::shared_ptr<ms::Surface> usc::SurfaceCoordinator::add_surface(
1543+ ms::SurfaceCreationParameters const& params,
1544+ ms::Session* session)
1545+{
1546+ auto const surface = msh::SurfaceCoordinatorWrapper::add_surface(params, session);
1547+
1548+ auto const session_ready_observer = std::make_shared<SessionReadyObserver>(
1549+ session_switcher, surface, session);
1550+
1551+ surface->add_observer(session_ready_observer);
1552+
1553+ return surface;
1554+}
1555
1556=== added file 'src/surface_coordinator.h'
1557--- src/surface_coordinator.h 1970-01-01 00:00:00 +0000
1558+++ src/surface_coordinator.h 2014-07-18 09:49:03 +0000
1559@@ -0,0 +1,47 @@
1560+/*
1561+ * Copyright © 2014 Canonical Ltd.
1562+ *
1563+ * This program is free software: you can redistribute it and/or modify
1564+ * it under the terms of the GNU General Public License version 3 as
1565+ * published by the Free Software Foundation.
1566+ *
1567+ * This program is distributed in the hope that it will be useful,
1568+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1569+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1570+ * GNU General Public License for more details.
1571+ *
1572+ * You should have received a copy of the GNU General Public License
1573+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1574+ *
1575+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
1576+ */
1577+
1578+#ifndef USC_SURFACE_COORDINATOR_H_
1579+#define USC_SURFACE_COORDINATOR_H_
1580+
1581+#include <mir/shell/surface_coordinator_wrapper.h>
1582+
1583+#include <memory>
1584+
1585+namespace usc
1586+{
1587+class SessionSwitcher;
1588+
1589+class SurfaceCoordinator : public mir::shell::SurfaceCoordinatorWrapper
1590+{
1591+public:
1592+ SurfaceCoordinator(
1593+ std::shared_ptr<mir::scene::SurfaceCoordinator> const& wrapped,
1594+ std::shared_ptr<SessionSwitcher> const& session_switcher);
1595+
1596+private:
1597+ std::shared_ptr<mir::scene::Surface> add_surface(
1598+ mir::scene::SurfaceCreationParameters const& params,
1599+ mir::scene::Session* session) override;
1600+
1601+ std::shared_ptr<SessionSwitcher> const session_switcher;
1602+};
1603+
1604+}
1605+
1606+#endif
1607
1608=== modified file 'src/system_compositor.cpp'
1609--- src/system_compositor.cpp 2014-07-15 19:26:10 +0000
1610+++ src/system_compositor.cpp 2014-07-18 09:49:03 +0000
1611@@ -1,5 +1,5 @@
1612 /*
1613- * Copyright © 2013 Canonical Ltd.
1614+ * Copyright © 2013-2014 Canonical Ltd.
1615 *
1616 * This program is free software: you can redistribute it and/or modify
1617 * it under the terms of the GNU General Public License version 3 as
1618@@ -14,10 +14,14 @@
1619 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1620 *
1621 * Authored by: Robert Ancell <robert.ancell@canonical.com>
1622+ * Alexandros Frantzis <alexandros.frantzis@canonical.com>
1623 */
1624
1625
1626 #include "system_compositor.h"
1627+#include "server_configuration.h"
1628+#include "dm_connection.h"
1629+#include "spinner.h"
1630 #include "screen_state_handler.h"
1631 #include "powerkey_handler.h"
1632
1633@@ -26,21 +30,9 @@
1634 // method declarations
1635 #undef signals
1636
1637+#include <mir/input/composite_event_filter.h>
1638 #include <mir/run_mir.h>
1639 #include <mir/abnormal_exit.h>
1640-#include <mir/compositor/compositor_id.h>
1641-#include <mir/compositor/scene.h>
1642-#include <mir/compositor/scene_element.h>
1643-#include <mir/default_server_configuration.h>
1644-#include <mir/options/default_configuration.h>
1645-#include <mir/frontend/shell.h>
1646-#include <mir/scene/surface.h>
1647-#include <mir/scene/surface_coordinator.h>
1648-#include <mir/scene/session.h>
1649-#include <mir/server_status_listener.h>
1650-#include <mir/shell/focus_controller.h>
1651-#include <mir/input/cursor_listener.h>
1652-#include <mir/input/composite_event_filter.h>
1653 #include <mir/main_loop.h>
1654
1655 #include <cerrno>
1656@@ -49,664 +41,16 @@
1657 #include <thread>
1658 #include <regex.h>
1659 #include <GLES2/gl2.h>
1660-#include <boost/algorithm/string.hpp>
1661 #include <QCoreApplication>
1662
1663-namespace geom = mir::geometry;
1664-namespace mc = mir::compositor;
1665-namespace msh = mir::shell;
1666-namespace msc = mir::scene;
1667-namespace mf = mir::frontend;
1668-namespace mg = mir::graphics;
1669-namespace mi = mir::input;
1670-namespace mo = mir::options;
1671-namespace po = boost::program_options;
1672-
1673-class SystemCompositorSurface;
1674-
1675-class SystemCompositorSession : public msc::Session
1676-{
1677-public:
1678- SystemCompositorSession(std::shared_ptr<msc::Session> const& self,
1679- SystemCompositorShell *shell)
1680- : self{self}, shell{shell}, ready{false} {}
1681-
1682- // These are defined below, since they reference methods defined in other classes
1683- void mark_ready();
1684- void raise(std::shared_ptr<msc::SurfaceCoordinator> const& coordinator);
1685- std::shared_ptr<mf::Surface> get_surface(mf::SurfaceId surface) const override;
1686- mf::SurfaceId create_surface(msc::SurfaceCreationParameters const& params) override;
1687-
1688- bool is_ready() const
1689- {
1690- return ready;
1691- }
1692-
1693- std::shared_ptr<msc::Session> get_orig()
1694- {
1695- return self;
1696- }
1697-
1698- void destroy_surface(mf::SurfaceId surface) override
1699- {
1700- surfaces.erase(surface);
1701- self->destroy_surface(surface);
1702- }
1703-
1704- // This is just for convience of USC
1705- std::vector<std::shared_ptr<SystemCompositorSurface>> get_surfaces()
1706- {
1707- std::vector<std::shared_ptr<SystemCompositorSurface>> vector;
1708- for(auto surface: surfaces)
1709- vector.push_back(surface.second);
1710- return vector;
1711- }
1712-
1713- std::string name() const override {return self->name();}
1714- void hide() override {self->hide();}
1715- void show() override {self->show();}
1716- void send_display_config(mg::DisplayConfiguration const&config) override {self->send_display_config(config);}
1717- pid_t process_id() const override {return self->process_id();}
1718- void force_requests_to_complete() override {self->force_requests_to_complete();}
1719- void take_snapshot(msc::SnapshotCallback const& snapshot_taken) override {self->take_snapshot(snapshot_taken);}
1720- std::shared_ptr<msc::Surface> default_surface() const override {return self->default_surface();}
1721- void set_lifecycle_state(MirLifecycleState state) override {self->set_lifecycle_state(state);}
1722-
1723- void start_prompt_session() override { self->start_prompt_session(); }
1724- void stop_prompt_session() override { self->stop_prompt_session(); }
1725-
1726-private:
1727- std::shared_ptr<msc::Session> const self;
1728- SystemCompositorShell *shell;
1729- std::map<mf::SurfaceId, std::shared_ptr<SystemCompositorSurface>> surfaces;
1730- bool ready;
1731-};
1732-
1733-class SystemCompositorSurface : public msc::Surface
1734-{
1735-public:
1736- SystemCompositorSurface(std::shared_ptr<msc::Surface> const& self,
1737- SystemCompositorSession *session)
1738- : self{self}, session{session}, buffer_count{0} {}
1739-
1740- std::shared_ptr<msc::Surface> get_orig()
1741- {
1742- return self;
1743- }
1744-
1745- void swap_buffers(mg::Buffer* old_buffer, std::function<void(mg::Buffer* new_buffer)> complete) override
1746- {
1747- self->swap_buffers(old_buffer, complete);
1748- // Mark it available only after some content has been rendered
1749- if (old_buffer != NULL && !session->is_ready() && buffer_count++ == 1)
1750- session->mark_ready();
1751- }
1752-
1753- // mf::Surface methods
1754- void force_requests_to_complete() override {self->force_requests_to_complete();}
1755- geom::Size size() const override {return self->size();}
1756- MirPixelFormat pixel_format() const override {return self->pixel_format();}
1757- bool supports_input() const override {return self->supports_input();}
1758- int client_input_fd() const override {return self->client_input_fd();}
1759- int configure(MirSurfaceAttrib attrib, int value) override {return self->configure(attrib, value);}
1760-
1761- // msc::Surface methods
1762- std::string name() const override {return self->name();}
1763- geom::Size client_size() const override { return self->client_size(); };
1764- geom::Rectangle input_bounds() const override { return self->input_bounds(); };
1765-
1766- MirSurfaceType type() const override {return self->type();}
1767- MirSurfaceState state() const override {return self->state();}
1768- void hide() override {self->hide();}
1769- void show() override {self->show();}
1770- void move_to(geom::Point const& top_left) override {self->move_to(top_left);}
1771- geom::Point top_left() const override {return self->top_left();}
1772- void take_input_focus(std::shared_ptr<msh::InputTargeter> const& targeter) override {self->take_input_focus(targeter);}
1773- void set_input_region(std::vector<geom::Rectangle> const& region) override {self->set_input_region(region);}
1774- void allow_framedropping(bool allow) override {self->allow_framedropping(allow);}
1775- void resize(geom::Size const& size) override {self->resize(size);}
1776- void set_transformation(glm::mat4 const& t) override {self->set_transformation(t);}
1777- float alpha() const override {return self->alpha();}
1778- void set_alpha(float alpha) override {self->set_alpha(alpha);}
1779- void with_most_recent_buffer_do(std::function<void(mg::Buffer&)> const& exec) override {self->with_most_recent_buffer_do(exec);}
1780-
1781- // msc::Surface methods
1782- std::shared_ptr<mi::InputChannel> input_channel() const override {return self->input_channel();}
1783- void set_reception_mode(mi::InputReceptionMode mode) override { self->set_reception_mode(mode); }
1784- void add_observer(std::shared_ptr<msc::SurfaceObserver> const& observer) override {self->add_observer(observer);}
1785- void remove_observer(std::weak_ptr<msc::SurfaceObserver> const& observer) override {self->remove_observer(observer);}
1786- void consume(MirEvent const& event) override {self->consume(event);}
1787- void set_orientation(MirOrientation orientation) override {self->set_orientation(orientation);}
1788-
1789- // mi::Surface methods
1790- bool input_area_contains(geom::Point const& point) const override {return self->input_area_contains(point);}
1791- mi::InputReceptionMode reception_mode() const override { return self->reception_mode(); }
1792-
1793- // mg::Renderable methods
1794- std::unique_ptr<mg::Renderable> compositor_snapshot(void const* compositor_id) const override { return self->compositor_snapshot(compositor_id); }
1795-
1796- void set_cursor_image(std::shared_ptr<mg::CursorImage> const& image) override { self->set_cursor_image(image); }
1797- std::shared_ptr<mg::CursorImage> cursor_image() const override { return self->cursor_image(); }
1798-
1799-private:
1800- std::shared_ptr<msc::Surface> const self;
1801- SystemCompositorSession *session;
1802- int buffer_count;
1803-};
1804-
1805-class SystemCompositorShell : public mf::Shell
1806-{
1807-public:
1808- SystemCompositorShell(SystemCompositor *compositor,
1809- std::shared_ptr<mf::Shell> const& self,
1810- std::shared_ptr<msh::FocusController> const& focus_controller,
1811- std::shared_ptr<msc::SurfaceCoordinator> const& surface_coordinator)
1812- : compositor{compositor}, self(self), focus_controller{focus_controller}, surface_coordinator{surface_coordinator}, active_ever_used{false} {}
1813-
1814- std::shared_ptr<mf::Session> session_named(std::string const& name)
1815- {
1816- return sessions[name];
1817- }
1818-
1819- void set_active_session(std::string const& name)
1820- {
1821- active_name = name;
1822- update_session_focus();
1823- }
1824-
1825- void set_next_session(std::string const& name)
1826- {
1827- next_name = name;
1828- update_session_focus();
1829- }
1830-
1831- std::shared_ptr<SystemCompositorSession> get_active_session()
1832- {
1833- return active_session;
1834- }
1835-
1836- std::shared_ptr<SystemCompositorSession> get_next_session()
1837- {
1838- return next_session;
1839- }
1840-
1841- void update_session_focus()
1842- {
1843- auto spinner = sessions[spinner_name];
1844- auto next = sessions[next_name];
1845- auto active = sessions[active_name];
1846- bool need_spinner = false;
1847-
1848- if (spinner)
1849- spinner->hide();
1850-
1851- if (next && next->is_ready())
1852- {
1853- std::cerr << "Setting next focus to session " << next_name;
1854- next->hide();
1855- next->raise(surface_coordinator);
1856- next_session = next;
1857- }
1858- else if (!next_name.empty() && spinner)
1859- {
1860- std::cerr << "Setting next focus to spinner";
1861- spinner->raise(surface_coordinator);
1862- next_session = spinner;
1863- need_spinner = true;
1864- }
1865- else
1866- {
1867- need_spinner = !next_name.empty();
1868- std::cerr << "Setting no next focus";
1869- next_session.reset();
1870- }
1871-
1872- // If we are booting, we want to wait for next session to be ready to
1873- // go (it's a smoother experience if user is able to immediately swipe
1874- // greeter out of way -- enough that it's worth the tiny wait). So
1875- // check here to see if next is all ready for us (or we've already
1876- // focused the active before in which case we're not booting anymore).
1877- bool next_all_set = next_name.empty() || (next && next->is_ready());
1878- if (active && active->is_ready() && (next_all_set || active_ever_used))
1879- {
1880- std::cerr << "; active focus to session " << active_name;
1881- focus_controller->set_focus_to(active); // raises and focuses
1882- active_ever_used = true;
1883- active_session = active;
1884- if (active_session == next_session)
1885- next_session.reset();
1886- }
1887- else if (!active_name.empty() && spinner)
1888- {
1889- std::cerr << "; active focus to spinner";
1890- focus_controller->set_focus_to(spinner); // raises and focuses
1891- active_session = spinner;
1892- next_session.reset();
1893- need_spinner = true;
1894- }
1895- else
1896- {
1897- need_spinner = need_spinner || !active_name.empty();
1898- std::cerr << "; no active focus";
1899- active_session.reset();
1900- next_session.reset();
1901- }
1902-
1903- if (active_session)
1904- active_session->show();
1905- if (next_session)
1906- next_session->show();
1907-
1908- if (need_spinner)
1909- compositor->ensure_spinner();
1910- else
1911- compositor->kill_spinner();
1912-
1913- std::cerr << std::endl;
1914- }
1915-
1916- std::shared_ptr<mf::PromptSession> start_prompt_session_for(
1917- std::shared_ptr<mf::Session> const& session,
1918- msc::PromptSessionCreationParameters const& params) override
1919- {
1920- return self->start_prompt_session_for(session, params);
1921- }
1922-
1923- void add_prompt_provider_for(
1924- std::shared_ptr<mf::PromptSession> const& prompt_session,
1925- std::shared_ptr<mf::Session> const& session) override
1926- {
1927- self->add_prompt_provider_for(prompt_session, session);
1928- }
1929-
1930- void stop_prompt_session(std::shared_ptr<mf::PromptSession> const& prompt_session) override
1931- {
1932- self->stop_prompt_session(prompt_session);
1933- }
1934-
1935-private:
1936- std::shared_ptr<mf::Session> open_session(
1937- pid_t client_pid,
1938- std::string const& name,
1939- std::shared_ptr<mf::EventSink> const& sink) override
1940- {
1941- std::cerr << "Opening session " << name << std::endl;
1942-
1943- // We need msc::Session objects because that is what the focus controller
1944- // works with. But the mf::Shell interface deals with mf::Session objects.
1945- // So we cast here since in practice, these objects are also msc::Sessions.
1946- auto orig = std::dynamic_pointer_cast<msc::Session>(self->open_session(client_pid, name, sink));
1947- if (!orig)
1948- {
1949- std::cerr << "Unexpected non-shell session" << std::endl;
1950- return std::shared_ptr<mf::Session>();
1951- }
1952-
1953- auto result = std::make_shared<SystemCompositorSession>(orig, this);
1954- sessions[name] = result;
1955-
1956- if (client_pid == compositor->get_spinner_pid())
1957- spinner_name = name;
1958-
1959- return result;
1960- }
1961-
1962- void close_session(std::shared_ptr<mf::Session> const& session_in) override
1963- {
1964- std::cerr << "Closing session " << session_in->name() << std::endl;
1965-
1966- auto session = std::dynamic_pointer_cast<SystemCompositorSession>(session_in);
1967- if (!session)
1968- return; // shouldn't happen
1969-
1970- if (session->name() == spinner_name)
1971- spinner_name = "";
1972-
1973- self->close_session(session->get_orig());
1974- sessions.erase(session->name());
1975- }
1976-
1977- mf::SurfaceId create_surface_for(
1978- std::shared_ptr<mf::Session> const& session,
1979- msc::SurfaceCreationParameters const& params) override
1980- {
1981- return self->create_surface_for(session, params);
1982- }
1983-
1984- void handle_surface_created(std::shared_ptr<mf::Session> const& session) override
1985- {
1986- self->handle_surface_created(session);
1987-
1988- // Opening a new surface will steal focus from our active surface, so
1989- // restore the focus if needed.
1990- update_session_focus();
1991- }
1992-
1993- SystemCompositor *compositor;
1994- std::shared_ptr<mf::Shell> const self;
1995- std::shared_ptr<msh::FocusController> const focus_controller;
1996- std::shared_ptr<msc::SurfaceCoordinator> const surface_coordinator;
1997- std::map<std::string, std::shared_ptr<SystemCompositorSession>> sessions;
1998- std::string active_name;
1999- std::string next_name;
2000- std::string spinner_name;
2001- std::shared_ptr<SystemCompositorSession> active_session;
2002- std::shared_ptr<SystemCompositorSession> next_session;
2003- bool active_ever_used;
2004-};
2005-
2006-class SurfaceSceneElement : public mc::SceneElement
2007-{
2008-public:
2009- SurfaceSceneElement(std::shared_ptr<mg::Renderable> renderable)
2010- : renderable_{renderable}
2011- {
2012- }
2013-
2014- std::shared_ptr<mg::Renderable> renderable() const override
2015- {
2016- return renderable_;
2017- }
2018-
2019- void rendered_in(mc::CompositorID) override {}
2020- void occluded_in(mc::CompositorID) override {}
2021-
2022-private:
2023- std::shared_ptr<mg::Renderable> const renderable_;
2024-};
2025-
2026-class SystemCompositorScene : public mc::Scene
2027-{
2028-public:
2029- SystemCompositorScene(std::shared_ptr<mc::Scene> const& self)
2030- : self{self}, shell{nullptr} {}
2031-
2032- void set_shell(std::shared_ptr<SystemCompositorShell> const& the_shell)
2033- {
2034- shell = the_shell;
2035- }
2036-
2037- mc::SceneElementSequence scene_elements_for(mc::CompositorID id) override
2038- {
2039- if (shell == nullptr)
2040- return self->scene_elements_for(id);
2041-
2042- mc::SceneElementSequence elements;
2043- std::shared_ptr<SystemCompositorSession> session;
2044-
2045- session = shell->get_next_session();
2046- if (session)
2047- {
2048- for (auto const& surface : session->get_surfaces()) {
2049- // An open question is whether this memory allocation overhead (as this method is
2050- // called for every rendered frame) is perceivable/significant at all to warrant
2051- // an optimization here.
2052- auto element = std::make_shared<SurfaceSceneElement>(surface->compositor_snapshot(id));
2053- elements.emplace_back(element);
2054- }
2055- }
2056-
2057- session = shell->get_active_session();
2058- if (session)
2059- {
2060- for (auto const& surface : session->get_surfaces()) {
2061- // see comment previous comment
2062- auto element = std::make_shared<SurfaceSceneElement>(surface->compositor_snapshot(id));
2063- elements.emplace_back(element);
2064- }
2065- }
2066-
2067- return elements;
2068- }
2069-
2070- void add_observer(std::shared_ptr<msc::Observer> const& observer) override { self->add_observer(observer); }
2071- void remove_observer(std::weak_ptr<msc::Observer> const& observer) override { self->remove_observer(observer); }
2072-
2073- void register_compositor(mc::CompositorID id) override { self->register_compositor(id); }
2074- void unregister_compositor(mc::CompositorID id) override { self->unregister_compositor(id); }
2075-
2076-
2077-private:
2078- std::shared_ptr<mc::Scene> const self;
2079- std::shared_ptr<SystemCompositorShell> shell;
2080-};
2081-
2082-
2083-void SystemCompositorSession::mark_ready()
2084-{
2085- if (!ready)
2086- {
2087- ready = true;
2088- shell->update_session_focus();
2089- }
2090-}
2091-
2092-void SystemCompositorSession::raise(std::shared_ptr<msc::SurfaceCoordinator> const& coordinator)
2093-{
2094- std::map<mf::SurfaceId, std::shared_ptr<SystemCompositorSurface>>::iterator iter;
2095- for (iter = surfaces.begin(); iter != surfaces.end(); ++iter)
2096- {
2097- // This will iterate by creation order, which is fine. New surfaces on top
2098- coordinator->raise(iter->second->get_orig());
2099- }
2100-}
2101-
2102-std::shared_ptr<mf::Surface> SystemCompositorSession::get_surface(mf::SurfaceId surface) const
2103-{
2104- return surfaces.at(surface);
2105-}
2106-
2107-mf::SurfaceId SystemCompositorSession::create_surface(msc::SurfaceCreationParameters const& params)
2108-{
2109- mf::SurfaceId id = self->create_surface(params);
2110- std::shared_ptr<mf::Surface> mf_surface = self->get_surface(id);
2111-
2112- auto surface = std::dynamic_pointer_cast<msc::Surface>(mf_surface);
2113- if (!surface)
2114- {
2115- std::cerr << "Unexpected non-scene surface" << std::endl;
2116- self->destroy_surface(id);
2117- return mf::SurfaceId(0);
2118- }
2119-
2120- surfaces[id] = std::make_shared<SystemCompositorSurface>(surface, this);
2121- return id;
2122-}
2123-
2124-class SystemCompositorServerConfiguration : public mir::DefaultServerConfiguration
2125-{
2126-public:
2127- SystemCompositorServerConfiguration(SystemCompositor *compositor, std::shared_ptr<mo::Configuration> options)
2128- : mir::DefaultServerConfiguration(options), compositor{compositor}
2129- {
2130- }
2131-
2132- int from_dm_fd()
2133- {
2134- return the_options()->get("from-dm-fd", -1);
2135- }
2136-
2137- int to_dm_fd()
2138- {
2139- return the_options()->get("to-dm-fd", -1);
2140- }
2141-
2142- bool show_version()
2143- {
2144- return the_options()->is_set("version");
2145- }
2146-
2147- int inactivity_display_off_timeout()
2148- {
2149- return the_options()->get("inactivity-display-off-timeout", 60);
2150- }
2151-
2152- int inactivity_display_dim_timeout()
2153- {
2154- return the_options()->get("inactivity-display-dim-timeout", 45);
2155- }
2156-
2157- int shutdown_timeout()
2158- {
2159- return the_options()->get("shutdown-timeout", 5000);
2160- }
2161-
2162- int power_key_ignore_timeout()
2163- {
2164- return the_options()->get("power-key-ignore-timeout", 1500);
2165- }
2166-
2167- bool enable_hardware_cursor()
2168- {
2169- return the_options()->get("enable-hardware-cursor", false);
2170- }
2171-
2172- bool disable_inactivity_policy()
2173- {
2174- return the_options()->get("disable-inactivity-policy", false);
2175- }
2176-
2177- std::string blacklist()
2178- {
2179- auto x = the_options()->get("blacklist", "");
2180- boost::trim(x);
2181- return x;
2182- }
2183-
2184- std::string spinner()
2185- {
2186- // TODO: once our default spinner is ready for use everywhere, replace
2187- // default value with DEFAULT_SPINNER instead of the empty string.
2188- auto x = the_options()->get("spinner", "");
2189- boost::trim(x);
2190- return x;
2191- }
2192-
2193- bool public_socket()
2194- {
2195- return !the_options()->is_set("no-file") && the_options()->get("public-socket", true);
2196- }
2197-
2198- std::shared_ptr<mi::CursorListener> the_cursor_listener() override
2199- {
2200- struct NullCursorListener : public mi::CursorListener
2201- {
2202- void cursor_moved_to(float, float) override
2203- {
2204- }
2205- };
2206-
2207- // This is a workaround for u8 desktop preview in 14.04 for the lack of client cursor API.
2208- // We need to disable the cursor for XMir but leave it on for the desktop preview.
2209- // Luckily as it stands they run inside seperate instances of USC. ~racarr
2210- if (enable_hardware_cursor())
2211- return mir::DefaultServerConfiguration::the_cursor_listener();
2212- else
2213- return std::make_shared<NullCursorListener>();
2214- }
2215-
2216- std::shared_ptr<mir::ServerStatusListener> the_server_status_listener() override
2217- {
2218- struct ServerStatusListener : public mir::ServerStatusListener
2219- {
2220- ServerStatusListener (SystemCompositor *compositor) : compositor{compositor} {}
2221-
2222- void paused() override
2223- {
2224- compositor->pause();
2225- }
2226-
2227- void resumed() override
2228- {
2229- compositor->resume();
2230- }
2231-
2232- void started() override
2233- {
2234- }
2235-
2236- SystemCompositor *compositor;
2237- };
2238- return std::make_shared<ServerStatusListener>(compositor);
2239- }
2240-
2241- std::string get_socket_file()
2242- {
2243- // the_socket_file is private, so we have to re-implement it here
2244- return the_options()->get("file", "/tmp/mir_socket");
2245- }
2246-
2247- std::shared_ptr<SystemCompositorShell> the_system_compositor_shell()
2248- {
2249- auto shell = sc_shell([this]
2250- {
2251- return std::make_shared<SystemCompositorShell>(
2252- compositor,
2253- mir::DefaultServerConfiguration::the_frontend_shell(),
2254- the_focus_controller(),
2255- the_surface_coordinator());
2256- });
2257-
2258- the_system_compositor_scene()->set_shell(shell);
2259- return shell;
2260- }
2261-
2262- std::shared_ptr<SystemCompositorScene> the_system_compositor_scene()
2263- {
2264- return sc_scene([this]
2265- {
2266- return std::make_shared<SystemCompositorScene>(
2267- mir::DefaultServerConfiguration::the_scene());
2268- });
2269- }
2270-
2271- std::shared_ptr<mc::Scene> the_scene()
2272- {
2273- return the_system_compositor_scene();
2274- }
2275-
2276-private:
2277- mir::CachedPtr<SystemCompositorShell> sc_shell;
2278- mir::CachedPtr<SystemCompositorScene> sc_scene;
2279-
2280- std::shared_ptr<mf::Shell> the_frontend_shell() override
2281- {
2282- return the_system_compositor_shell();
2283- }
2284-
2285- SystemCompositor *compositor;
2286-};
2287-
2288-class SystemCompositorConfigurationOptions : public mo::DefaultConfiguration
2289-{
2290-public:
2291- SystemCompositorConfigurationOptions(int argc, char const* argv[]) :
2292- DefaultConfiguration(argc, argv)
2293- {
2294- add_options()
2295- ("from-dm-fd", po::value<int>(), "File descriptor of read end of pipe from display manager [int]")
2296- ("to-dm-fd", po::value<int>(), "File descriptor of write end of pipe to display manager [int]")
2297- ("blacklist", po::value<std::string>(), "Video blacklist regex to use")
2298- ("version", "Show version of Unity System Compositor")
2299- ("spinner", po::value<std::string>(), "Path to spinner executable")
2300- ("public-socket", po::value<bool>(), "Make the socket file publicly writable")
2301- ("enable-hardware-cursor", po::value<bool>(), "Enable the hardware cursor (disabled by default)")
2302- ("inactivity-display-off-timeout", po::value<int>(), "The time in seconds before the screen is turned off when there are no active sessions")
2303- ("inactivity-display-dim-timeout", po::value<int>(), "The time in seconds before the screen is dimmed when there are no active sessions")
2304- ("shutdown-timeout", po::value<int>(), "The time in milli-seconds the power key must be held to initiate a clean system shutdown")
2305- ("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")
2306- ("disable-inactivity-policy", po::value<bool>(), "Disables handling user inactivity and power key");
2307- }
2308-
2309- void parse_config_file(
2310- boost::program_options::options_description& options_description,
2311- mo::ProgramOption& options) const override
2312- {
2313- options.parse_file(options_description, "unity-system-compositor.conf");
2314- }
2315-};
2316-
2317-bool check_blacklist(std::string blacklist, const char *vendor, const char *renderer, const char *version)
2318+namespace
2319+{
2320+
2321+bool check_blacklist(
2322+ std::string const& blacklist,
2323+ const char *vendor,
2324+ const char *renderer,
2325+ const char *version)
2326 {
2327 if (blacklist.empty())
2328 return true;
2329@@ -739,35 +83,37 @@
2330 return true;
2331 }
2332
2333-void SystemCompositor::run(int argc, char **argv)
2334-{
2335- auto const options = std::make_shared<SystemCompositorConfigurationOptions>(argc, const_cast<char const **>(argv));
2336- config = std::make_shared<SystemCompositorServerConfiguration>(this, options);
2337-
2338+}
2339+
2340+usc::SystemCompositor::SystemCompositor(
2341+ std::shared_ptr<ServerConfiguration> const& config)
2342+ : config{config},
2343+ dm_connection{config->the_dm_connection()},
2344+ spinner{config->the_spinner()}
2345+{
2346+}
2347+
2348+void usc::SystemCompositor::run()
2349+{
2350 if (config->show_version())
2351 {
2352 std::cerr << "unity-system-compositor " << USC_VERSION << std::endl;
2353 return;
2354 }
2355
2356- dm_connection = std::make_shared<DMConnection>(io_service, config->from_dm_fd(), config->to_dm_fd());
2357-
2358 struct ScopeGuard
2359 {
2360- explicit ScopeGuard(boost::asio::io_service& io_service) : io_service(io_service) {}
2361 ~ScopeGuard()
2362 {
2363- io_service.stop();
2364- if (io_thread.joinable())
2365- io_thread.join();
2366 if (qt_thread.joinable())
2367+ {
2368+ QCoreApplication::quit();
2369 qt_thread.join();
2370+ }
2371 }
2372
2373- boost::asio::io_service& io_service;
2374- std::thread io_thread;
2375 std::thread qt_thread;
2376- } guard(io_service);
2377+ } guard;
2378
2379 mir::run_mir(*config, [&](mir::DisplayServer&)
2380 {
2381@@ -781,46 +127,12 @@
2382 if (!check_blacklist(config->blacklist(), vendor, renderer, version))
2383 throw mir::AbnormalExit ("Video driver is blacklisted, exiting");
2384
2385- shell = config->the_system_compositor_shell();
2386- guard.io_thread = std::thread(&SystemCompositor::main, this);
2387- guard.qt_thread = std::thread(&SystemCompositor::qt_main, this, argc, argv);
2388+ main();
2389+ guard.qt_thread = std::thread(&SystemCompositor::qt_main, this);
2390 });
2391 }
2392
2393-void SystemCompositor::pause()
2394-{
2395- std::cerr << "pause" << std::endl;
2396-
2397- if (auto active_session = config->the_focus_controller()->focussed_application().lock())
2398- active_session->set_lifecycle_state(mir_lifecycle_state_will_suspend);
2399-}
2400-
2401-void SystemCompositor::resume()
2402-{
2403- std::cerr << "resume" << std::endl;
2404-
2405- if (auto active_session = config->the_focus_controller()->focussed_application().lock())
2406- active_session->set_lifecycle_state(mir_lifecycle_state_resumed);
2407-}
2408-
2409-pid_t SystemCompositor::get_spinner_pid() const
2410-{
2411- return spinner_process.pid();
2412-}
2413-
2414-void SystemCompositor::set_active_session(std::string client_name)
2415-{
2416- std::cerr << "set_active_session" << std::endl;
2417- shell->set_active_session(client_name);
2418-}
2419-
2420-void SystemCompositor::set_next_session(std::string client_name)
2421-{
2422- std::cerr << "set_next_session" << std::endl;
2423- shell->set_next_session(client_name);
2424-}
2425-
2426-void SystemCompositor::main()
2427+void usc::SystemCompositor::main()
2428 {
2429 // Make socket world-writable, since users need to talk to us. No worries
2430 // about race condition, since we are adding permissions, not restricting
2431@@ -828,33 +140,13 @@
2432 if (config->public_socket() && chmod(config->get_socket_file().c_str(), 0777) == -1)
2433 std::cerr << "Unable to chmod socket file " << config->get_socket_file() << ": " << strerror(errno) << std::endl;
2434
2435- dm_connection->set_handler(this);
2436 dm_connection->start();
2437- dm_connection->send_ready();
2438-
2439- io_service.run();
2440-}
2441-
2442-void SystemCompositor::ensure_spinner()
2443-{
2444- if (config->spinner().empty() || spinner_process.state() != QProcess::NotRunning)
2445- return;
2446-
2447- // Launch spinner process to provide default background when a session isn't ready
2448- QStringList env = QProcess::systemEnvironment();
2449- env << "MIR_SOCKET=" + QString(config->get_socket_file().c_str());
2450- spinner_process.setEnvironment(env);
2451- spinner_process.start(config->spinner().c_str());
2452-}
2453-
2454-void SystemCompositor::kill_spinner()
2455-{
2456- spinner_process.close();
2457-}
2458-
2459-void SystemCompositor::qt_main(int argc, char **argv)
2460-{
2461- QCoreApplication app(argc, argv);
2462+}
2463+
2464+void usc::SystemCompositor::qt_main()
2465+{
2466+ int argc{0};
2467+ QCoreApplication app(argc, nullptr);
2468
2469 if (!config->disable_inactivity_policy())
2470 {
2471@@ -877,6 +169,11 @@
2472 composite_filter->append(power_key_handler);
2473 }
2474
2475- ensure_spinner();
2476 app.exec();
2477+
2478+ // Destroy components that depend on Qt event handling inside the Qt thread,
2479+ // to silence warnings during shutdown
2480+
2481+ // ScreenStateHandler uses the Qt DBus infrastructure
2482+ screen_state_handler.reset();
2483 }
2484
2485=== modified file 'src/system_compositor.h'
2486--- src/system_compositor.h 2014-06-20 16:35:15 +0000
2487+++ src/system_compositor.h 2014-07-18 09:49:03 +0000
2488@@ -1,5 +1,5 @@
2489 /*
2490- * Copyright © 2013 Canonical Ltd.
2491+ * Copyright © 2013-2014 Canonical Ltd.
2492 *
2493 * This program is free software: you can redistribute it and/or modify
2494 * it under the terms of the GNU General Public License version 3 as
2495@@ -14,44 +14,41 @@
2496 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2497 *
2498 * Authored by: Robert Ancell <robert.ancell@canonical.com>
2499+ * Alexandros Frantzis <alexandros.frantzis@canonical.com>
2500 */
2501
2502-#ifndef SYSTEM_COMPOSITOR_H_
2503-#define SYSTEM_COMPOSITOR_H_
2504-
2505-#include "dm_connection.h"
2506-#include <QProcess>
2507-
2508-namespace mir { namespace scene { class Session; } }
2509-
2510-class SystemCompositorShell;
2511-class SystemCompositorServerConfiguration;
2512+#ifndef USC_SYSTEM_COMPOSITOR_H_
2513+#define USC_SYSTEM_COMPOSITOR_H_
2514+
2515+#include <memory>
2516+
2517 class ScreenStateHandler;
2518 class PowerKeyHandler;
2519
2520-class SystemCompositor : public DMMessageHandler
2521+namespace usc
2522+{
2523+
2524+class ServerConfiguration;
2525+class DMConnection;
2526+class Spinner;
2527+
2528+class SystemCompositor
2529 {
2530 public:
2531- void run(int argc, char **argv);
2532- void pause();
2533- void resume();
2534- pid_t get_spinner_pid() const;
2535- void ensure_spinner();
2536- void kill_spinner();
2537+ SystemCompositor(std::shared_ptr<ServerConfiguration> const& config);
2538+ void run();
2539
2540 private:
2541- std::shared_ptr<SystemCompositorServerConfiguration> config;
2542- std::shared_ptr<SystemCompositorShell> shell;
2543- boost::asio::io_service io_service;
2544- std::shared_ptr<DMConnection> dm_connection;
2545+ void main();
2546+ void qt_main();
2547+
2548+ std::shared_ptr<ServerConfiguration> const config;
2549+ std::shared_ptr<DMConnection> const dm_connection;
2550+ std::shared_ptr<Spinner> const spinner;
2551 std::shared_ptr<ScreenStateHandler> screen_state_handler;
2552 std::shared_ptr<PowerKeyHandler> power_key_handler;
2553- QProcess spinner_process;
2554-
2555- void set_active_session(std::string client_name);
2556- void set_next_session(std::string client_name);
2557- void main();
2558- void qt_main(int argc, char **argv);
2559 };
2560
2561-#endif /* SYSTEM_COMPOSITOR_H_ */
2562+}
2563+
2564+#endif /* USC_SYSTEM_COMPOSITOR_H_ */
2565
2566=== added file 'tests/CMakeLists.txt'
2567--- tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
2568+++ tests/CMakeLists.txt 2014-07-18 09:49:03 +0000
2569@@ -0,0 +1,17 @@
2570+# Copyright © 2014 Canonical Ltd.
2571+#
2572+# This program is free software: you can redistribute it and/or modify
2573+# it under the terms of the GNU General Public License version 3 as
2574+# published by the Free Software Foundation.
2575+#
2576+# This program is distributed in the hope that it will be useful,
2577+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2578+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2579+# GNU General Public License for more details.
2580+#
2581+# You should have received a copy of the GNU General Public License
2582+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2583+#
2584+# Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
2585+
2586+add_subdirectory(unit-tests/)
2587
2588=== added directory 'tests/unit-tests'
2589=== added file 'tests/unit-tests/CMakeLists.txt'
2590--- tests/unit-tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
2591+++ tests/unit-tests/CMakeLists.txt 2014-07-18 09:49:03 +0000
2592@@ -0,0 +1,39 @@
2593+# Copyright © 2014 Canonical Ltd.
2594+#
2595+# This program is free software: you can redistribute it and/or modify
2596+# it under the terms of the GNU General Public License version 3 as
2597+# published by the Free Software Foundation.
2598+#
2599+# This program is distributed in the hope that it will be useful,
2600+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2601+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2602+# GNU General Public License for more details.
2603+#
2604+# You should have received a copy of the GNU General Public License
2605+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2606+#
2607+# Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
2608+
2609+include_directories(
2610+ ${CMAKE_SOURCE_DIR}
2611+ ${MIRSERVER_INCLUDE_DIRS}
2612+)
2613+
2614+add_executable(
2615+ usc_unit_tests
2616+
2617+ test_session_switcher.cpp
2618+)
2619+
2620+target_link_libraries(
2621+ usc_unit_tests
2622+
2623+ usc
2624+ ${GTEST_BOTH_LIBRARIES}
2625+ ${GMOCK_LIBRARY}
2626+ ${GMOCK_MAIN_LIBRARY}
2627+)
2628+
2629+add_test(usc_unit_tests ${EXECUTABLE_OUTPUT_PATH}/usc_unit_tests)
2630+
2631+add_dependencies(usc_unit_tests GMock)
2632
2633=== added file 'tests/unit-tests/test_session_switcher.cpp'
2634--- tests/unit-tests/test_session_switcher.cpp 1970-01-01 00:00:00 +0000
2635+++ tests/unit-tests/test_session_switcher.cpp 2014-07-18 09:49:03 +0000
2636@@ -0,0 +1,656 @@
2637+/*
2638+ * Copyright © 2014 Canonical Ltd.
2639+ *
2640+ * This program is free software: you can redistribute it and/or modify
2641+ * it under the terms of the GNU General Public License version 3 as
2642+ * published by the Free Software Foundation.
2643+ *
2644+ * This program is distributed in the hope that it will be useful,
2645+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2646+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2647+ * GNU General Public License for more details.
2648+ *
2649+ * You should have received a copy of the GNU General Public License
2650+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2651+ *
2652+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
2653+ */
2654+
2655+#include "src/session_switcher.h"
2656+#include "src/spinner.h"
2657+
2658+#include <gtest/gtest.h>
2659+#include <gmock/gmock.h>
2660+
2661+#include <memory>
2662+#include <vector>
2663+#include <tuple>
2664+
2665+namespace
2666+{
2667+
2668+class FakeScene
2669+{
2670+public:
2671+ void add(usc::Session* session)
2672+ {
2673+ sessions.emplace_back(session, true);
2674+ }
2675+
2676+ void remove(usc::Session* session)
2677+ {
2678+ sessions.erase(find(session));
2679+ }
2680+
2681+ void show(usc::Session* session)
2682+ {
2683+ find(session)->second = true;
2684+ }
2685+
2686+ void hide(usc::Session* session)
2687+ {
2688+ find(session)->second = false;
2689+ }
2690+
2691+ void raise(usc::Session* session)
2692+ {
2693+ auto const iter = find(session);
2694+ auto const pair = *iter;
2695+ sessions.erase(iter);
2696+ sessions.push_back(pair);
2697+ }
2698+
2699+ std::vector<std::string> displayed_sessions()
2700+ {
2701+ std::vector<std::string> ret;
2702+ for (auto const& pair : sessions)
2703+ {
2704+ bool session_visible = pair.second;
2705+ if (session_visible)
2706+ ret.push_back(pair.first->name());
2707+ }
2708+
2709+ return ret;
2710+ }
2711+
2712+private:
2713+ std::vector<std::pair<usc::Session*,bool>> sessions;
2714+
2715+ decltype(sessions)::iterator find(usc::Session* session)
2716+ {
2717+ return std::find_if(
2718+ sessions.begin(), sessions.end(),
2719+ [session] (decltype(sessions)::value_type const& p)
2720+ {
2721+ return p.first == session;
2722+ });
2723+ }
2724+};
2725+
2726+class StubSession : public usc::Session
2727+{
2728+public:
2729+ StubSession(FakeScene& fake_scene, std::string const& name)
2730+ : fake_scene(fake_scene),
2731+ name_{name}
2732+ {
2733+ fake_scene.add(this);
2734+ }
2735+
2736+ ~StubSession()
2737+ {
2738+ fake_scene.remove(this);
2739+ }
2740+
2741+ std::string name() override
2742+ {
2743+ return name_;
2744+ }
2745+
2746+ void show() override
2747+ {
2748+ fake_scene.show(this);
2749+ }
2750+
2751+ void hide() override
2752+ {
2753+ fake_scene.hide(this);
2754+ }
2755+
2756+ void raise_and_focus() override
2757+ {
2758+ fake_scene.raise(this);
2759+ }
2760+
2761+ bool corresponds_to(mir::scene::Session const* s) override
2762+ {
2763+ return s == corresponding_scene_session();
2764+ }
2765+
2766+ mir::scene::Session const* corresponding_scene_session()
2767+ {
2768+ return reinterpret_cast<mir::scene::Session const*>(this);
2769+ }
2770+
2771+private:
2772+ FakeScene& fake_scene;
2773+ std::string const name_;
2774+};
2775+
2776+struct StubSpinner : usc::Spinner
2777+{
2778+ void ensure_running() override { is_running_ = true; }
2779+ void kill() override { is_running_ = false; }
2780+ pid_t pid() override { return pid_; }
2781+
2782+ void set_pid(pid_t new_pid) { pid_ = new_pid; }
2783+ bool is_running() { return is_running_; }
2784+
2785+private:
2786+ bool is_running_ = false;
2787+ pid_t pid_ = 666;
2788+};
2789+
2790+struct ASessionSwitcher : testing::Test
2791+{
2792+ std::shared_ptr<StubSession> create_stub_session(std::string const& name)
2793+ {
2794+ return std::make_shared<StubSession>(fake_scene, name);
2795+ }
2796+
2797+ std::tuple<std::string,std::string> boot()
2798+ {
2799+ std::string const boot_active_name{"boot_active"};
2800+ std::string const boot_next_name{"boot_next"};
2801+
2802+ auto const boot_active = create_stub_session(boot_active_name);
2803+ auto const boot_next = create_stub_session(boot_next_name);
2804+
2805+ switcher.add(boot_active, active_pid);
2806+ switcher.add(boot_next, next_pid);
2807+
2808+ switcher.set_next_session(boot_next_name);
2809+ switcher.set_active_session(boot_active_name);
2810+ switcher.mark_ready(boot_active->corresponding_scene_session());
2811+ switcher.mark_ready(boot_next->corresponding_scene_session());
2812+
2813+ return std::make_tuple(boot_active_name, boot_next_name);
2814+ }
2815+
2816+ FakeScene fake_scene;
2817+ std::shared_ptr<StubSpinner> const stub_spinner{std::make_shared<StubSpinner>()};
2818+ usc::SessionSwitcher switcher{stub_spinner};
2819+ std::string const active_name{"active"};
2820+ std::string const next_name{"next"};
2821+ std::string const spinner_name{"spinner"};
2822+ pid_t const invalid_pid{0};
2823+ pid_t const active_pid{1000};
2824+ pid_t const next_pid{1001};
2825+ pid_t const other_pid{1002};
2826+};
2827+
2828+}
2829+
2830+TEST_F(ASessionSwitcher, does_not_display_any_session_if_active_and_next_not_set)
2831+{
2832+ using namespace testing;
2833+
2834+ switcher.add(create_stub_session("s1"), invalid_pid);
2835+ switcher.add(create_stub_session("s2"), invalid_pid);
2836+
2837+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
2838+}
2839+
2840+TEST_F(ASessionSwitcher, does_not_display_not_ready_active_session)
2841+{
2842+ using namespace testing;
2843+
2844+ auto const active = create_stub_session(active_name);
2845+
2846+ switcher.add(active, active_pid);
2847+ switcher.set_active_session(active_name);
2848+
2849+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
2850+}
2851+
2852+TEST_F(ASessionSwitcher, does_not_display_not_ready_next_session)
2853+{
2854+ using namespace testing;
2855+
2856+ auto const next = create_stub_session(next_name);
2857+
2858+ switcher.add(next, next_pid);
2859+ switcher.set_next_session(next_name);
2860+
2861+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
2862+}
2863+
2864+TEST_F(ASessionSwitcher, does_not_display_ready_next_session_without_ready_active_session)
2865+{
2866+ using namespace testing;
2867+
2868+ auto const next = create_stub_session(next_name);
2869+
2870+ switcher.add(next, next_pid);
2871+ switcher.set_next_session(next_name);
2872+ switcher.mark_ready(next->corresponding_scene_session());
2873+
2874+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
2875+}
2876+
2877+TEST_F(ASessionSwitcher,
2878+ does_not_display_any_session_on_boot_if_not_both_active_and_next_are_ready)
2879+{
2880+ using namespace testing;
2881+
2882+ auto const active = create_stub_session(active_name);
2883+ auto const next = create_stub_session(next_name);
2884+
2885+ switcher.add(active, active_pid);
2886+ switcher.add(next, next_pid);
2887+
2888+ switcher.set_active_session(active_name);
2889+ switcher.set_next_session(next_name);
2890+ switcher.mark_ready(active->corresponding_scene_session());
2891+
2892+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
2893+}
2894+
2895+TEST_F(ASessionSwitcher,
2896+ displays_the_active_session_on_boot_if_it_is_ready_and_there_is_no_next_session)
2897+{
2898+ using namespace testing;
2899+
2900+ auto const active = create_stub_session(active_name);
2901+ auto const spinner = create_stub_session(spinner_name);
2902+
2903+ switcher.add(active, active_pid);
2904+ switcher.add(spinner, stub_spinner->pid());
2905+
2906+ switcher.set_active_session(active_name);
2907+ switcher.mark_ready(active->corresponding_scene_session());
2908+
2909+ EXPECT_THAT(fake_scene.displayed_sessions(),
2910+ ElementsAre(active_name));
2911+}
2912+
2913+TEST_F(ASessionSwitcher, displays_the_active_session_after_boot_if_it_is_ready)
2914+{
2915+ using namespace testing;
2916+
2917+ boot();
2918+
2919+ auto const active = create_stub_session(active_name);
2920+ auto const next = create_stub_session(next_name);
2921+
2922+ switcher.add(active, active_pid);
2923+ switcher.add(next, next_pid);
2924+
2925+ switcher.set_next_session(next_name);
2926+ switcher.set_active_session(active_name);
2927+ switcher.mark_ready(active->corresponding_scene_session());
2928+
2929+ EXPECT_THAT(fake_scene.displayed_sessions(),
2930+ ElementsAre(active_name));
2931+}
2932+
2933+TEST_F(ASessionSwitcher, displays_active_over_next_if_both_are_ready)
2934+{
2935+ using namespace testing;
2936+
2937+ auto const active = create_stub_session(active_name);
2938+ auto const next = create_stub_session(next_name);
2939+
2940+ switcher.add(active, active_pid);
2941+ switcher.add(next, next_pid);
2942+
2943+ switcher.set_next_session(next_name);
2944+ switcher.set_active_session(active_name);
2945+ switcher.mark_ready(active->corresponding_scene_session());
2946+ switcher.mark_ready(next->corresponding_scene_session());
2947+
2948+ EXPECT_THAT(fake_scene.displayed_sessions(),
2949+ ElementsAre(next_name, active_name));
2950+}
2951+
2952+
2953+TEST_F(ASessionSwitcher, displays_only_active_if_next_equals_active)
2954+{
2955+ using namespace testing;
2956+
2957+ auto const active = create_stub_session(active_name);
2958+
2959+ switcher.add(active, active_pid);
2960+
2961+ switcher.set_next_session(active_name);
2962+ switcher.set_active_session(active_name);
2963+ switcher.mark_ready(active->corresponding_scene_session());
2964+
2965+ EXPECT_THAT(fake_scene.displayed_sessions(),
2966+ ElementsAre(active_name));
2967+}
2968+
2969+TEST_F(ASessionSwitcher, displays_only_active_and_next_sessions)
2970+{
2971+ using namespace testing;
2972+
2973+ auto const active = create_stub_session(active_name);
2974+ auto const next = create_stub_session(next_name);
2975+ auto const other = create_stub_session("other");
2976+
2977+ switcher.add(active, active_pid);
2978+ switcher.add(next, next_pid);
2979+ switcher.add(other, other_pid);
2980+
2981+ switcher.set_next_session(next_name);
2982+ switcher.set_active_session(active_name);
2983+ switcher.mark_ready(active->corresponding_scene_session());
2984+ switcher.mark_ready(next->corresponding_scene_session());
2985+ switcher.mark_ready(other->corresponding_scene_session());
2986+
2987+ EXPECT_THAT(fake_scene.displayed_sessions(),
2988+ ElementsAre(next_name, active_name));
2989+}
2990+
2991+TEST_F(ASessionSwitcher, displays_spinner_if_active_is_not_ready)
2992+{
2993+ using namespace testing;
2994+
2995+ auto const active = create_stub_session(active_name);
2996+ auto const spinner = create_stub_session(spinner_name);
2997+
2998+ switcher.add(active, active_pid);
2999+ switcher.add(spinner, stub_spinner->pid());
3000+
3001+ switcher.set_active_session(active_name);
3002+
3003+ EXPECT_THAT(fake_scene.displayed_sessions(),
3004+ ElementsAre(spinner_name));
3005+}
3006+
3007+TEST_F(ASessionSwitcher, does_not_display_spinner_if_next_is_not_ready)
3008+{
3009+ using namespace testing;
3010+
3011+ auto const next = create_stub_session(next_name);
3012+ auto const spinner = create_stub_session(spinner_name);
3013+
3014+ switcher.add(next, next_pid);
3015+ switcher.add(spinner, stub_spinner->pid());
3016+
3017+ switcher.set_next_session(next_name);
3018+
3019+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
3020+}
3021+
3022+TEST_F(ASessionSwitcher, displays_only_spinner_when_active_is_not_ready_but_next_is_ready)
3023+{
3024+ using namespace testing;
3025+
3026+ auto const active = create_stub_session(active_name);
3027+ auto const next = create_stub_session(next_name);
3028+ auto const spinner = create_stub_session(spinner_name);
3029+
3030+ switcher.add(active, active_pid);
3031+ switcher.add(next, next_pid);
3032+ switcher.add(spinner, stub_spinner->pid());
3033+
3034+ switcher.set_active_session(active_name);
3035+ switcher.set_next_session(next_name);
3036+ switcher.mark_ready(next->corresponding_scene_session());
3037+
3038+ EXPECT_THAT(fake_scene.displayed_sessions(),
3039+ ElementsAre(spinner_name));
3040+}
3041+
3042+TEST_F(ASessionSwitcher,
3043+ displays_only_spinner_when_booting_if_not_both_active_and_next_are_ready)
3044+{
3045+ using namespace testing;
3046+
3047+ auto const active = create_stub_session(active_name);
3048+ auto const next = create_stub_session(next_name);
3049+ auto const spinner = create_stub_session(spinner_name);
3050+
3051+ switcher.add(active, active_pid);
3052+ switcher.add(next, next_pid);
3053+ switcher.add(spinner, stub_spinner->pid());
3054+
3055+ switcher.set_active_session(active_name);
3056+ switcher.set_next_session(next_name);
3057+ switcher.mark_ready(active->corresponding_scene_session());
3058+
3059+ EXPECT_THAT(fake_scene.displayed_sessions(),
3060+ ElementsAre(spinner_name));
3061+}
3062+
3063+TEST_F(ASessionSwitcher,
3064+ displays_spinner_behind_active_after_boot_if_active_is_ready_but_next_is_not_ready)
3065+{
3066+ using namespace testing;
3067+
3068+ boot();
3069+
3070+ auto const active = create_stub_session(active_name);
3071+ auto const next = create_stub_session(next_name);
3072+ auto const spinner = create_stub_session(spinner_name);
3073+
3074+ switcher.add(active, active_pid);
3075+ switcher.add(next, next_pid);
3076+ switcher.add(spinner, stub_spinner->pid());
3077+
3078+ switcher.set_active_session(active_name);
3079+ switcher.set_next_session(next_name);
3080+ switcher.mark_ready(active->corresponding_scene_session());
3081+
3082+ EXPECT_THAT(fake_scene.displayed_sessions(),
3083+ ElementsAre(spinner_name, active_name));
3084+}
3085+
3086+TEST_F(ASessionSwitcher, starts_and_stops_spinner_as_needed)
3087+{
3088+ using namespace testing;
3089+
3090+ auto const active = create_stub_session(active_name);
3091+
3092+ switcher.add(active, active_pid);
3093+ switcher.set_active_session(active_name);
3094+
3095+ EXPECT_TRUE(stub_spinner->is_running());
3096+
3097+ switcher.mark_ready(active->corresponding_scene_session());
3098+
3099+ EXPECT_FALSE(stub_spinner->is_running());
3100+}
3101+
3102+TEST_F(ASessionSwitcher, does_not_display_next_when_active_is_removed)
3103+{
3104+ using namespace testing;
3105+
3106+ std::string const no_session_name;
3107+
3108+ std::string boot_active_name;
3109+ std::string boot_next_name;
3110+ std::tie(boot_active_name, boot_next_name) = boot();
3111+
3112+ auto const spinner = create_stub_session(spinner_name);
3113+ switcher.add(spinner, stub_spinner->pid());
3114+
3115+ switcher.remove(boot_active_name);
3116+ switcher.set_active_session(no_session_name);
3117+
3118+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
3119+}
3120+
3121+TEST_F(ASessionSwitcher, displays_only_active_not_spinner_when_next_is_removed)
3122+{
3123+ using namespace testing;
3124+
3125+ std::string const no_session_name;
3126+
3127+ std::string boot_active_name;
3128+ std::string boot_next_name;
3129+ std::tie(boot_active_name, boot_next_name) = boot();
3130+
3131+ auto const spinner = create_stub_session(spinner_name);
3132+ switcher.add(spinner, stub_spinner->pid());
3133+
3134+ switcher.remove(boot_next_name);
3135+ switcher.set_next_session(no_session_name);
3136+
3137+ EXPECT_THAT(fake_scene.displayed_sessions(),
3138+ ElementsAre(boot_active_name));
3139+}
3140+
3141+TEST_F(ASessionSwitcher, displays_spinner_when_active_is_removed_unexpectedly)
3142+{
3143+ using namespace testing;
3144+
3145+ std::string const no_session_name;
3146+
3147+ std::string boot_active_name;
3148+ std::string boot_next_name;
3149+ std::tie(boot_active_name, boot_next_name) = boot();
3150+
3151+ auto const spinner = create_stub_session(spinner_name);
3152+ switcher.add(spinner, stub_spinner->pid());
3153+
3154+ switcher.remove(boot_active_name);
3155+
3156+ EXPECT_THAT(fake_scene.displayed_sessions(),
3157+ ElementsAre(spinner_name));
3158+}
3159+
3160+TEST_F(ASessionSwitcher, displays_spinner_under_active_if_next_is_removed_unexpectedly)
3161+{
3162+ using namespace testing;
3163+
3164+ std::string const no_session_name;
3165+
3166+ std::string boot_active_name;
3167+ std::string boot_next_name;
3168+ std::tie(boot_active_name, boot_next_name) = boot();
3169+
3170+ auto const spinner = create_stub_session(spinner_name);
3171+ switcher.add(spinner, stub_spinner->pid());
3172+
3173+ switcher.remove(boot_next_name);
3174+
3175+ EXPECT_THAT(fake_scene.displayed_sessions(),
3176+ ElementsAre(spinner_name, boot_active_name));
3177+}
3178+
3179+TEST_F(ASessionSwitcher,
3180+ does_not_display_any_session_when_spinner_is_removed_and_no_sessions_are_ready)
3181+{
3182+ using namespace testing;
3183+
3184+ auto const active = create_stub_session(active_name);
3185+ auto spinner = create_stub_session(spinner_name);
3186+
3187+ switcher.add(active, active_pid);
3188+ switcher.add(spinner, stub_spinner->pid());
3189+
3190+ switcher.set_active_session(active_name);
3191+
3192+ EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name));
3193+
3194+ spinner.reset();
3195+ switcher.remove(spinner_name);
3196+
3197+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
3198+}
3199+
3200+TEST_F(ASessionSwitcher, can_handle_spinner_resurrection_under_different_name)
3201+{
3202+ using namespace testing;
3203+
3204+ auto const active = create_stub_session(active_name);
3205+ auto spinner = create_stub_session(spinner_name);
3206+
3207+ switcher.add(active, active_pid);
3208+ switcher.add(spinner, stub_spinner->pid());
3209+
3210+ switcher.set_active_session(active_name);
3211+
3212+ EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name));
3213+
3214+ spinner.reset();
3215+ switcher.remove(spinner_name);
3216+
3217+ std::string const new_spinner_name{"new_spinner_name"};
3218+ spinner = create_stub_session(new_spinner_name);
3219+ switcher.add(spinner, stub_spinner->pid());
3220+
3221+ EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(new_spinner_name));
3222+}
3223+
3224+TEST_F(ASessionSwitcher, can_handle_spinner_resurrection_under_different_pid)
3225+{
3226+ using namespace testing;
3227+
3228+ auto const active = create_stub_session(active_name);
3229+ auto spinner = create_stub_session(spinner_name);
3230+
3231+ switcher.add(active, active_pid);
3232+ switcher.add(spinner, stub_spinner->pid());
3233+ switcher.set_active_session(active_name);
3234+
3235+ EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name));
3236+
3237+ spinner.reset();
3238+ switcher.remove(spinner_name);
3239+
3240+ pid_t const new_pid{1234};
3241+ stub_spinner->set_pid(new_pid);
3242+
3243+ spinner = create_stub_session(spinner_name);
3244+ switcher.add(spinner, stub_spinner->pid());
3245+
3246+ EXPECT_THAT(fake_scene.displayed_sessions(), ElementsAre(spinner_name));
3247+}
3248+
3249+TEST_F(ASessionSwitcher, is_not_confused_by_other_session_with_name_of_dead_spinner)
3250+{
3251+ using namespace testing;
3252+
3253+ auto const active = create_stub_session(active_name);
3254+ auto spinner = create_stub_session(spinner_name);
3255+
3256+ switcher.add(active, active_pid);
3257+ switcher.add(spinner, stub_spinner->pid());
3258+
3259+ switcher.set_active_session(active_name);
3260+
3261+ spinner.reset();
3262+ switcher.remove(spinner_name);
3263+ stub_spinner->set_pid(invalid_pid);
3264+
3265+ auto const other = create_stub_session(spinner_name);
3266+ switcher.add(other, other_pid);
3267+
3268+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
3269+}
3270+
3271+TEST_F(ASessionSwitcher, is_not_confused_by_other_session_with_pid_of_dead_spinner)
3272+{
3273+ using namespace testing;
3274+
3275+ auto const active = create_stub_session(active_name);
3276+ auto spinner = create_stub_session(spinner_name);
3277+
3278+ switcher.add(active, active_pid);
3279+ switcher.add(spinner, stub_spinner->pid());
3280+
3281+ switcher.set_active_session(active_name);
3282+
3283+ auto const old_spinner_pid = stub_spinner->pid();
3284+ stub_spinner->set_pid(invalid_pid);
3285+ spinner.reset();
3286+ switcher.remove(spinner_name);
3287+
3288+ auto const other = create_stub_session(spinner_name);
3289+ switcher.add(other, old_spinner_pid);
3290+
3291+ EXPECT_THAT(fake_scene.displayed_sessions(), IsEmpty());
3292+}

Subscribers

People subscribed via source and target branches