Merge lp:~alan-griffiths/miral/monitor-outputs into lp:miral

Proposed by Alan Griffiths
Status: Merged
Approved by: Gerry Boland
Approved revision: 245
Merged at revision: 230
Proposed branch: lp:~alan-griffiths/miral/monitor-outputs
Merge into: lp:miral
Diff against target: 708 lines (+644/-1)
8 files modified
include/miral/active_outputs.h (+73/-0)
include/miral/output.h (+113/-0)
include/miral/window.h (+1/-1)
miral/CMakeLists.txt (+2/-0)
miral/active_outputs.cpp (+131/-0)
miral/output.cpp (+118/-0)
test/CMakeLists.txt (+1/-0)
test/active_outputs.cpp (+205/-0)
To merge this branch: bzr merge lp:~alan-griffiths/miral/monitor-outputs
Reviewer Review Type Date Requested Status
Gerry Boland (community) Approve
Review via email: mp+300740@code.launchpad.net

Commit message

Introducing ActiveOutputsMonitor to track the active outputs

To post a comment you must log in.
Revision history for this message
Gerry Boland (gerboland) wrote :

+++ include/miral/active_outputs.h
+class ActiveOutputsListener
+ virtual void advise_update_output(Output const& updated, Output const& original);
this is great to have, thank you

"Outputs" is replacing "Display"? /me ambivalent, but your team may have stronger opinions :)

+class ActiveOutputsMonitor
+ void process_outputs(std::function<void(std::vector<Output> const& outputs)> const& functor) const;
Mir uses the for_each_output(functor) style usually, has that been changing?

+++ include/miral/output.h
+ /// The physical size of the output.
+ auto physical_size() const -> PhysicalSize;
What units? mm?

+ auto orientaton() const -> MirOrientation;
Typo

=== modified file 'miral/CMakeLists.txt'
+ active_outputs.cpp ../include/miral/active_outputs.h
use ${CMAKE_SOURCE_DIR} for consistency

+++ miral/output.cpp
+auto miral::equivalent_display_area(Output const& lhs, Output const& rhs) -> bool
I'm fine with this for a first cut. For later I'd prefer operations like orientation or resolution change just cause an output update, not an add/remove.

+ if (lhs_bad || rhs_bad) return lhs_bad == rhs_bad;
if both outputs bad, this returns true. Are 2 invalid outputs equivalent?

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

> "Outputs" is replacing "Display"? /me ambivalent, but your team may have
> stronger opinions :)

I guess I'll ask.
...
> +++ miral/output.cpp
> +auto miral::equivalent_display_area(Output const& lhs, Output const& rhs) ->
> bool
> I'm fine with this for a first cut. For later I'd prefer operations like
> orientation or resolution change just cause an output update, not an
> add/remove.

I don't see the connection between the code and your comment. For the latter, see
ActiveOutputs.when_base_configuration_is_updated_listener_is_advised which uses an orientation change to test for an output update.

> + if (lhs_bad || rhs_bad) return lhs_bad == rhs_bad;
> if both outputs bad, this returns true. Are 2 invalid outputs equivalent?

IMO They have equivalent display areas.

240. By Alan Griffiths

merge lp:miral

241. By Alan Griffiths

for_each_output() is a more consistent name

242. By Alan Griffiths

Oh for a standard Units library

243. By Alan Griffiths

Fix typo

244. By Alan Griffiths

Consistency

245. By Alan Griffiths

re-sync working copy

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

> > "Outputs" is replacing "Display"? /me ambivalent, but your team may have
> > stronger opinions :)
>
> I guess I'll ask.

Actually, the Mir codebase uses output too. E.g. DisplayConfiguration::for_each_output()

Revision history for this message
Gerry Boland (gerboland) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'include/miral/active_outputs.h'
2--- include/miral/active_outputs.h 1970-01-01 00:00:00 +0000
3+++ include/miral/active_outputs.h 2016-07-22 11:21:29 +0000
4@@ -0,0 +1,73 @@
5+/*
6+ * Copyright © 2016 Canonical Ltd.
7+ *
8+ * This program is free software: you can redistribute it and/or modify it
9+ * under the terms of the GNU General Public License version 3,
10+ * as published by the Free Software Foundation.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU General Public License
18+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ *
20+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
21+ */
22+
23+#ifndef MIRAL_ACTIVE_OUTPUTS_H
24+#define MIRAL_ACTIVE_OUTPUTS_H
25+
26+#include <memory>
27+#include <vector>
28+
29+namespace mir { class Server; }
30+
31+namespace miral
32+{
33+class Output;
34+
35+class ActiveOutputsListener
36+{
37+public:
38+ ActiveOutputsListener() = default;
39+
40+ /// before any related calls begin
41+ virtual void advise_begin();
42+
43+ /// after any related calls end
44+ virtual void advise_end();
45+
46+ virtual void advise_create_output(Output const& output);
47+ virtual void advise_update_output(Output const& updated, Output const& original);
48+ virtual void advise_delete_output(Output const& output);
49+
50+protected:
51+ virtual ~ActiveOutputsListener() = default;
52+ ActiveOutputsListener(ActiveOutputsListener const&) = delete;
53+ ActiveOutputsListener operator=(ActiveOutputsListener const&) = delete;
54+};
55+
56+class ActiveOutputsMonitor
57+{
58+public:
59+ ActiveOutputsMonitor();
60+ ~ActiveOutputsMonitor();
61+ ActiveOutputsMonitor(ActiveOutputsMonitor const&);
62+ ActiveOutputsMonitor& operator=(ActiveOutputsMonitor const&);
63+
64+ void add_listener(ActiveOutputsListener* listener);
65+ void delete_listener(ActiveOutputsListener* listener);
66+
67+ void operator()(mir::Server& server);
68+
69+ void for_each_output(std::function<void(std::vector<Output> const& outputs)> const& functor) const;
70+
71+private:
72+ struct Self;
73+ std::shared_ptr<Self> self;
74+};
75+}
76+
77+#endif //MIRAL_ACTIVE_OUTPUTS_H
78
79=== added file 'include/miral/output.h'
80--- include/miral/output.h 1970-01-01 00:00:00 +0000
81+++ include/miral/output.h 2016-07-22 11:21:29 +0000
82@@ -0,0 +1,113 @@
83+/*
84+ * Copyright © 2016 Canonical Ltd.
85+ *
86+ * This program is free software: you can redistribute it and/or modify it
87+ * under the terms of the GNU General Public License version 3,
88+ * as published by the Free Software Foundation.
89+ *
90+ * This program is distributed in the hope that it will be useful,
91+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
92+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
93+ * GNU General Public License for more details.
94+ *
95+ * You should have received a copy of the GNU General Public License
96+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
97+ *
98+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
99+ */
100+
101+#ifndef MIRAL_OUTPUT_H
102+#define MIRAL_OUTPUT_H
103+
104+#include <mir_toolkit/common.h>
105+
106+#include <mir/geometry/rectangle.h>
107+#include <mir/int_wrapper.h>
108+
109+#include <memory>
110+
111+namespace mir { namespace graphics { struct DisplayConfigurationOutput; } }
112+
113+namespace miral
114+{
115+using namespace mir::geometry;
116+
117+class Output
118+{
119+public:
120+
121+ struct PhysicalSizeMM { int width; int height; };
122+
123+ enum class Type
124+ {
125+ unknown,
126+ vga,
127+ dvii,
128+ dvid,
129+ dvia,
130+ composite,
131+ svideo,
132+ lvds,
133+ component,
134+ ninepindin,
135+ displayport,
136+ hdmia,
137+ hdmib,
138+ tv,
139+ edp
140+ };
141+
142+ explicit Output(const mir::graphics::DisplayConfigurationOutput &output);
143+ Output(Output const&);
144+ Output& operator=(Output const&);
145+ ~Output();
146+
147+ /// The type of the output.
148+ auto type() const -> Type;
149+
150+ /// The physical size of the output.
151+ auto physical_size_mm() const -> PhysicalSizeMM;
152+
153+ /// Whether the output is connected.
154+ auto connected() const -> bool;
155+
156+ /// Whether the output is used in the configuration.
157+ auto used() const -> bool;
158+
159+ /// The current output pixel format.
160+ auto pixel_format() const -> MirPixelFormat;
161+
162+ /// refresh_rate in Hz
163+ auto refresh_rate() const -> double;
164+
165+ /// Current power mode
166+ auto power_mode() const -> MirPowerMode;
167+
168+ auto orientation() const -> MirOrientation;
169+
170+ /// Requested scale factor for this output, for HiDPI support
171+ auto scale() const -> float;
172+
173+ /// Form factor of this output; phone display, tablet, monitor, TV, projector...
174+ auto form_factor() const -> MirFormFactor;
175+
176+ /// The logical rectangle occupied by the output, based on its position,
177+ /// current mode and orientation (rotation)
178+ auto extents() const -> Rectangle;
179+
180+ auto valid() const -> bool;
181+
182+ auto is_same_output(Output const& other) const -> bool;
183+
184+private:
185+ std::shared_ptr<mir::graphics::DisplayConfigurationOutput> self;
186+};
187+
188+bool operator==(Output::PhysicalSizeMM const& lhs, Output::PhysicalSizeMM const& rhs);
189+inline bool operator!=(Output::PhysicalSizeMM const& lhs, Output::PhysicalSizeMM const& rhs)
190+{ return !(lhs == rhs); }
191+
192+auto equivalent_display_area(Output const& lhs, Output const& rhs) -> bool;
193+}
194+
195+#endif //MIRAL_OUTPUT_H
196
197=== modified file 'include/miral/window.h'
198--- include/miral/window.h 2016-06-09 02:09:01 +0000
199+++ include/miral/window.h 2016-07-22 11:21:29 +0000
200@@ -35,7 +35,7 @@
201 namespace mir
202 {
203 namespace scene { class Surface; }
204-namespace shell { class StreamSpecification; }
205+namespace shell { struct StreamSpecification; }
206 }
207
208 namespace miral
209
210=== modified file 'miral/CMakeLists.txt'
211--- miral/CMakeLists.txt 2016-06-16 10:21:28 +0000
212+++ miral/CMakeLists.txt 2016-07-22 11:21:29 +0000
213@@ -3,12 +3,14 @@
214 include_directories(include SYSTEM ${MIRSERVER_INCLUDE_DIRS})
215
216 add_library(miral SHARED
217+ active_outputs.cpp ${CMAKE_SOURCE_DIR}/include/miral/active_outputs.h
218 application.cpp ${CMAKE_SOURCE_DIR}/include/miral/application.h
219 application_authorizer.cpp ${CMAKE_SOURCE_DIR}/include/miral/application_authorizer.h
220 application_info.cpp ${CMAKE_SOURCE_DIR}/include/miral/application_info.h
221 canonical_window_manager.cpp ${CMAKE_SOURCE_DIR}/include/miral/canonical_window_manager.h
222 runner.cpp ${CMAKE_SOURCE_DIR}/include/miral/runner.h
223 display_configuration_option.cpp ${CMAKE_SOURCE_DIR}/include/miral/display_configuration_option.h
224+ output.cpp ${CMAKE_SOURCE_DIR}/include/miral/output.h
225 quit_on_ctrl_alt_bksp.cpp ${CMAKE_SOURCE_DIR}/include/miral/quit_on_ctrl_alt_bksp.h
226 window.cpp ${CMAKE_SOURCE_DIR}/include/miral/window.h
227 window_info.cpp ${CMAKE_SOURCE_DIR}/include/miral/window_info.h
228
229=== added file 'miral/active_outputs.cpp'
230--- miral/active_outputs.cpp 1970-01-01 00:00:00 +0000
231+++ miral/active_outputs.cpp 2016-07-22 11:21:29 +0000
232@@ -0,0 +1,131 @@
233+/*
234+ * Copyright © 2016 Canonical Ltd.
235+ *
236+ * This program is free software: you can redistribute it and/or modify it
237+ * under the terms of the GNU General Public License version 3,
238+ * as published by the Free Software Foundation.
239+ *
240+ * This program is distributed in the hope that it will be useful,
241+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
242+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
243+ * GNU General Public License for more details.
244+ *
245+ * You should have received a copy of the GNU General Public License
246+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
247+ *
248+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
249+ */
250+
251+#include "miral/active_outputs.h"
252+#include "miral/output.h"
253+
254+#include <mir/graphics/display_configuration_report.h>
255+#include <mir/graphics/display_configuration.h>
256+#include <mir/server.h>
257+
258+#include <algorithm>
259+#include <mutex>
260+#include <vector>
261+
262+void miral::ActiveOutputsListener::advise_begin() {}
263+void miral::ActiveOutputsListener::advise_end() {}
264+void miral::ActiveOutputsListener::advise_create_output(Output const& /*output*/) {}
265+void miral::ActiveOutputsListener::advise_update_output(Output const& /*updated*/, Output const& /*original*/) {}
266+void miral::ActiveOutputsListener::advise_delete_output(Output const& /*output*/) {}
267+
268+struct miral::ActiveOutputsMonitor::Self : mir::graphics::DisplayConfigurationReport
269+{
270+ virtual void initial_configuration(mir::graphics::DisplayConfiguration const& configuration) override;
271+ virtual void new_configuration(mir::graphics::DisplayConfiguration const& configuration) override;
272+
273+ std::mutex mutex;
274+ std::vector<ActiveOutputsListener*> listeners;
275+ std::vector<Output> outputs;
276+};
277+
278+miral::ActiveOutputsMonitor::ActiveOutputsMonitor() :
279+ self{std::make_shared<Self>()}
280+{
281+}
282+
283+miral::ActiveOutputsMonitor::~ActiveOutputsMonitor() = default;
284+miral::ActiveOutputsMonitor::ActiveOutputsMonitor(ActiveOutputsMonitor const&) = default;
285+miral::ActiveOutputsMonitor& miral::ActiveOutputsMonitor::operator=(ActiveOutputsMonitor const&) = default;
286+
287+void miral::ActiveOutputsMonitor::add_listener(ActiveOutputsListener* listener)
288+{
289+ std::lock_guard<decltype(self->mutex)> lock{self->mutex};
290+
291+ self->listeners.push_back(listener);
292+}
293+
294+void miral::ActiveOutputsMonitor::delete_listener(ActiveOutputsListener* listener)
295+{
296+ std::lock_guard<decltype(self->mutex)> lock{self->mutex};
297+
298+ auto const new_end = std::remove(self->listeners.begin(), self->listeners.end(), listener);
299+ self->listeners.erase(new_end, self->listeners.end());
300+}
301+
302+void miral::ActiveOutputsMonitor::operator()(mir::Server& server)
303+{
304+ std::lock_guard<decltype(self->mutex)> lock{self->mutex};
305+
306+ server.override_the_display_configuration_report([this]{ return self; });
307+}
308+
309+void miral::ActiveOutputsMonitor::for_each_output(
310+ std::function<void(std::vector<Output> const& outputs)> const& functor) const
311+{
312+ std::lock_guard<decltype(self->mutex)> lock{self->mutex};
313+ functor(self->outputs);
314+}
315+
316+void miral::ActiveOutputsMonitor::Self::initial_configuration(mir::graphics::DisplayConfiguration const& configuration)
317+{
318+ new_configuration(configuration);
319+}
320+
321+void miral::ActiveOutputsMonitor::Self::new_configuration(mir::graphics::DisplayConfiguration const& configuration)
322+{
323+ std::lock_guard<decltype(mutex)> lock{mutex};
324+
325+ decltype(outputs) current_outputs;
326+
327+ for (auto const l : listeners)
328+ l->advise_begin();
329+
330+ configuration.for_each_output([&current_outputs, this](mir::graphics::DisplayConfigurationOutput const& output)
331+ {
332+ Output o{output};
333+ auto op = find_if(begin(outputs), end(outputs), [&](Output const& oo) { return oo.is_same_output(o); });
334+
335+
336+ if (op == end(outputs))
337+ {
338+ for (auto const l : listeners)
339+ l->advise_create_output(o);
340+ }
341+ else if (!equivalent_display_area(o, *op))
342+ {
343+ for (auto const l : listeners)
344+ l->advise_update_output(o, *op);
345+ }
346+
347+ current_outputs.push_back(o);
348+ });
349+
350+ for (auto const& o : outputs)
351+ {
352+ auto op = find_if(begin(current_outputs), end(current_outputs), [&](Output const& oo)
353+ { return oo.is_same_output(o); });
354+
355+ if (op == end(current_outputs))
356+ for (auto const l : listeners)
357+ l->advise_delete_output(o);
358+ }
359+
360+ current_outputs.swap(outputs);
361+ for (auto const l : listeners)
362+ l->advise_end();
363+}
364
365=== added file 'miral/output.cpp'
366--- miral/output.cpp 1970-01-01 00:00:00 +0000
367+++ miral/output.cpp 2016-07-22 11:21:29 +0000
368@@ -0,0 +1,118 @@
369+/*
370+ * Copyright © 2016 Canonical Ltd.
371+ *
372+ * This program is free software: you can redistribute it and/or modify it
373+ * under the terms of the GNU General Public License version 3,
374+ * as published by the Free Software Foundation.
375+ *
376+ * This program is distributed in the hope that it will be useful,
377+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
378+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
379+ * GNU General Public License for more details.
380+ *
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: Alan Griffiths <alan@octopull.co.uk>
385+ */
386+
387+#include "miral/output.h"
388+
389+#include <mir/graphics/display_configuration.h>
390+
391+miral::Output::Output(const mir::graphics::DisplayConfigurationOutput& output) :
392+ self{std::make_shared<mir::graphics::DisplayConfigurationOutput>(output)}
393+{
394+}
395+
396+miral::Output::Output(Output const&) = default;
397+miral::Output& miral::Output::operator=(Output const&) = default;
398+miral::Output::~Output() = default;
399+
400+auto miral::Output::type() const -> Type
401+{
402+ return Type(self->type);
403+}
404+
405+auto miral::Output::physical_size_mm() const -> PhysicalSizeMM
406+{
407+ auto const& size = self->physical_size_mm;
408+ return PhysicalSizeMM{size.width.as_int(), size.height.as_int()};
409+}
410+
411+auto miral::Output::connected() const -> bool
412+{
413+ return self->connected;
414+}
415+
416+auto miral::Output::used() const -> bool
417+{
418+ return self->used;
419+}
420+
421+auto miral::Output::pixel_format() const -> MirPixelFormat
422+{
423+ return self->current_format;
424+}
425+
426+auto miral::Output::refresh_rate() const -> double
427+{
428+ return self->modes[self->current_mode_index].vrefresh_hz;
429+}
430+
431+auto miral::Output::power_mode() const -> MirPowerMode
432+{
433+ return self->power_mode;
434+}
435+
436+auto miral::Output::orientation() const -> MirOrientation
437+{
438+ return self->orientation;
439+}
440+
441+auto miral::Output::scale() const -> float
442+{
443+ return self->scale;
444+}
445+
446+auto miral::Output::form_factor() const -> MirFormFactor
447+{
448+ return self->form_factor;
449+}
450+
451+auto miral::Output::extents() const -> Rectangle
452+{
453+ return Rectangle{self->top_left, self->modes[self->current_mode_index].size};
454+
455+}
456+
457+auto miral::Output::valid() const -> bool
458+{
459+ return self->valid();
460+}
461+
462+auto miral::Output::is_same_output(Output const& other) const -> bool
463+{
464+ return self->card_id == other.self->card_id && self->id == other.self->id;
465+}
466+
467+bool miral::operator==(Output::PhysicalSizeMM const& lhs, Output::PhysicalSizeMM const& rhs)
468+{
469+ return lhs.width == rhs.width && lhs.height == rhs.height;
470+}
471+
472+auto miral::equivalent_display_area(Output const& lhs, Output const& rhs) -> bool
473+{
474+ // Eliminate the cases where one or both output isn't available
475+ auto const lhs_bad = !lhs.valid() || !lhs.used() || !lhs.connected() || lhs.power_mode() != mir_power_mode_on;
476+ auto const rhs_bad = !rhs.valid() || !rhs.used() || !rhs.connected() || rhs.power_mode() != mir_power_mode_on;
477+ if (lhs_bad || rhs_bad) return lhs_bad == rhs_bad;
478+
479+ return lhs.extents() == rhs.extents() &&
480+ lhs.form_factor() == rhs.form_factor() &&
481+ lhs.orientation() == rhs.orientation() &&
482+ lhs.pixel_format() == rhs.pixel_format() &&
483+ lhs.physical_size_mm() == rhs.physical_size_mm() &&
484+ lhs.scale() == rhs.scale() &&
485+ lhs.type() == rhs.type();
486+}
487
488=== modified file 'test/CMakeLists.txt'
489--- test/CMakeLists.txt 2016-07-21 14:21:51 +0000
490+++ test/CMakeLists.txt 2016-07-22 11:21:29 +0000
491@@ -8,6 +8,7 @@
492
493 add_executable(miral-test
494 mru_window_list.cpp
495+ active_outputs.cpp
496 )
497
498 target_link_libraries(miral-test
499
500=== added file 'test/active_outputs.cpp'
501--- test/active_outputs.cpp 1970-01-01 00:00:00 +0000
502+++ test/active_outputs.cpp 2016-07-22 11:21:29 +0000
503@@ -0,0 +1,205 @@
504+/*
505+ * Copyright © 2016 Canonical Ltd.
506+ *
507+ * This program is free software: you can redistribute it and/or modify it
508+ * under the terms of the GNU General Public License version 3,
509+ * as published by the Free Software Foundation.
510+ *
511+ * This program is distributed in the hope that it will be useful,
512+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
513+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
514+ * GNU General Public License for more details.
515+ *
516+ * You should have received a copy of the GNU General Public License
517+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
518+ *
519+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
520+ */
521+
522+#include "miral/active_outputs.h"
523+#include "miral/output.h"
524+
525+#include <mir/shell/display_configuration_controller.h>
526+
527+#include <mir_test_framework/headless_test.h>
528+
529+#include <mir/test/doubles/fake_display.h>
530+#include <mir/test/doubles/stub_display_configuration.h>
531+#include <mir/test/fake_shared.h>
532+#include <mir/test/signal.h>
533+
534+#include <gtest/gtest.h>
535+#include <gmock/gmock.h>
536+
537+namespace mg = mir::graphics;
538+namespace mt = mir::test;
539+namespace mtd = mir::test::doubles;
540+namespace mtf = mir_test_framework;
541+
542+using namespace miral;
543+using namespace testing;
544+
545+namespace
546+{
547+struct MockActiveOutputsListener : ActiveOutputsListener
548+{
549+ MOCK_METHOD0(advise_begin, void());
550+ MOCK_METHOD0(advise_end, void());
551+
552+ MOCK_METHOD1(advise_create_output, void(Output const&));
553+ MOCK_METHOD2(advise_update_output, void(Output const&, Output const&));
554+ MOCK_METHOD1(advise_delete_output, void(Output const&));
555+};
556+
557+std::vector<Rectangle> const output_rects{
558+ {{0,0}, {640,480}},
559+ {{640,0}, {640,480}}
560+};
561+
562+struct ActiveOutputs : mtf::HeadlessTest
563+{
564+ ActiveOutputs()
565+ {
566+ add_to_environment("MIR_SERVER_NO_FILE", "");
567+ }
568+
569+ void SetUp() override
570+ {
571+ mtf::HeadlessTest::SetUp();
572+ preset_display(mt::fake_shared(display));
573+ active_outputs_monitor(server);
574+ active_outputs_monitor.add_listener(&active_outputs_listener);
575+ }
576+
577+ void TearDown() override
578+ {
579+ active_outputs_monitor.delete_listener(&active_outputs_listener);
580+ mtf::HeadlessTest::TearDown();
581+ }
582+
583+ mtd::FakeDisplay display{output_rects};
584+ ActiveOutputsMonitor active_outputs_monitor;
585+ NiceMock<MockActiveOutputsListener> active_outputs_listener;
586+
587+ void update_outputs(std::vector<Rectangle> const& displays)
588+ {
589+ mt::Signal signal;
590+ EXPECT_CALL(active_outputs_listener, advise_end()).WillOnce(Invoke([&]{signal.raise(); }));
591+
592+ mtd::StubDisplayConfig changed_stub_display_config{displays};
593+ display.emit_configuration_change_event(mt::fake_shared(changed_stub_display_config));
594+
595+ signal.wait_for(std::chrono::seconds(10));
596+ ASSERT_TRUE(signal.raised());
597+ }
598+
599+ void invert_outputs_in_base_configuration()
600+ {
601+ mt::Signal signal;
602+ EXPECT_CALL(active_outputs_listener, advise_end()).WillOnce(Invoke([&]{signal.raise(); }));
603+
604+ auto configuration = server.the_display()->configuration();
605+ configuration->for_each_output([](mg::UserDisplayConfigurationOutput& output)
606+ {
607+ output.orientation = mir_orientation_inverted;
608+ });
609+
610+ server.the_display_configuration_controller()->set_base_configuration(std::move(configuration));
611+
612+ signal.wait_for(std::chrono::seconds(10));
613+ ASSERT_TRUE(signal.raised());
614+ }
615+};
616+
617+struct RunServer
618+{
619+ RunServer(mtf::HeadlessTest* self) : self{self} { self->start_server(); }
620+ ~RunServer() { self->stop_server(); }
621+
622+ mtf::HeadlessTest* const self;
623+};
624+}
625+
626+TEST_F(ActiveOutputs, on_startup_listener_is_advised)
627+{
628+ InSequence seq;
629+ EXPECT_CALL(active_outputs_listener, advise_begin());
630+ EXPECT_CALL(active_outputs_listener, advise_create_output(_)).Times(2);
631+ RunServer runner{this};
632+
633+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
634+}
635+
636+TEST_F(ActiveOutputs, when_output_unplugged_listener_is_advised)
637+{
638+ RunServer runner{this};
639+
640+ InSequence seq;
641+ EXPECT_CALL(active_outputs_listener, advise_begin());
642+ EXPECT_CALL(active_outputs_listener, advise_delete_output(_)).Times(1);
643+ update_outputs({{{0,0}, {640,480}}});
644+
645+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
646+}
647+
648+TEST_F(ActiveOutputs, when_output_added_listener_is_advised)
649+{
650+ RunServer runner{this};
651+
652+ auto new_output_rects = output_rects;
653+ new_output_rects.emplace_back(Point{1280,0}, Size{640,480});
654+
655+ InSequence seq;
656+ EXPECT_CALL(active_outputs_listener, advise_begin());
657+ EXPECT_CALL(active_outputs_listener, advise_create_output(_)).Times(1);
658+ update_outputs(new_output_rects);
659+
660+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
661+}
662+
663+TEST_F(ActiveOutputs, when_output_resized_listener_is_advised)
664+{
665+ RunServer runner{this};
666+
667+ auto new_output_rects = output_rects;
668+ new_output_rects[1] = {Point{640,0}, Size{1080,768}};
669+
670+ InSequence seq;
671+ EXPECT_CALL(active_outputs_listener, advise_begin());
672+ EXPECT_CALL(active_outputs_listener, advise_update_output(_, _)).Times(1);
673+ update_outputs(new_output_rects);
674+
675+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
676+}
677+
678+TEST_F(ActiveOutputs, when_base_configuration_is_updated_listener_is_advised)
679+{
680+ RunServer runner{this};
681+
682+ InSequence seq;
683+ EXPECT_CALL(active_outputs_listener, advise_begin());
684+ EXPECT_CALL(active_outputs_listener, advise_update_output(_, _)).Times(2);
685+ invert_outputs_in_base_configuration();
686+
687+ Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
688+}
689+
690+TEST_F(ActiveOutputs, available_to_process)
691+{
692+ RunServer runner{this};
693+
694+ active_outputs_monitor.for_each_output([](std::vector<Output> const& outputs)
695+ { EXPECT_THAT(outputs.size(), Eq(output_rects.size())); });
696+}
697+
698+TEST_F(ActiveOutputs, updates_are_available_to_process)
699+{
700+ RunServer runner{this};
701+
702+ auto new_output_rects = output_rects;
703+ new_output_rects.emplace_back(Point{1280,0}, Size{640,480});
704+ update_outputs(new_output_rects);
705+
706+ active_outputs_monitor.for_each_output([&](std::vector<Output> const& outputs)
707+ { EXPECT_THAT(outputs.size(), Eq(new_output_rects.size())); });
708+}

Subscribers

People subscribed via source and target branches