Merge lp:~alan-griffiths/miral/monitor-outputs into lp:miral
- monitor-outputs
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gerry Boland (community) | Approve | ||
Review via email: mp+300740@code.launchpad.net |
Commit message
Introducing ActiveOutputsMo
Description of the change
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::
> 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.
> + 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
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. DisplayConfigur
Preview Diff
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([¤t_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 | +} |
+++ include/ miral/active_ outputs. h stener update_ output( Output const& updated, Output const& original);
+class ActiveOutputsLi
+ virtual void advise_
this is great to have, thank you
"Outputs" is replacing "Display"? /me ambivalent, but your team may have stronger opinions :)
+class ActiveOutputsMo nitor outputs( std::function< void(std: :vector< Output> const& outputs)> const& functor) const; output( functor) style usually, has that been changing?
+ void process_
Mir uses the for_each_
+++ 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' miral/active_ outputs. h
+ active_outputs.cpp ../include/
use ${CMAKE_SOURCE_DIR} for consistency
+++ miral/output.cpp equivalent_ display_ area(Output const& lhs, Output const& rhs) -> bool
+auto miral::
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?