GCC Code Coverage Report


Directory: ./
File: lib/generator.cpp
Date: 2023-04-20 22:59:23
Exec Total Coverage
Lines: 160 266 60.2%
Branches: 92 216 42.6%

Line Branch Exec Source
1 /*************************************************************************
2 Copyright © 2020 Roman Gilg <subdiff@gmail.com>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 **************************************************************************/
18 #include "generator.h"
19
20 #include "output_p.h"
21
22 #include "disman_debug.h"
23
24 #include <QRectF>
25
26 namespace Disman
27 {
28
29 26 Generator::Generator(ConfigPtr const& config)
30 26 : m_config{config->clone()}
31 26 , m_predecessor_config{config}
32 {
33 26 prepare_config();
34 26 }
35
36 26 void Generator::prepare_config()
37 {
38
2/2
✓ Branch 9 taken 63 times.
✓ Branch 10 taken 26 times.
89 for (auto const& [key, output] : m_config->outputs()) {
39
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 58 times.
63 if (output->d->global.valid) {
40 // We have global data for the output. We fall back to these values if necessary.
41 5 continue;
42 }
43
44 // The scale will generally be independent no matter where the output is
45 // scale will affect geometry, so do this first.
46
2/2
✓ Branch 3 taken 57 times.
✓ Branch 4 taken 1 times.
58 if (m_config->supported_features().testFlag(Disman::Config::Feature::PerOutputScaling)) {
47 57 output->set_scale(best_scale(output));
48 }
49 58 output->set_auto_resolution(true);
50 58 output->set_auto_refresh_rate(true);
51 58 output->set_enabled(true);
52 26 }
53 26 }
54
55 void Generator::set_validities(Config::ValidityFlags validities)
56 {
57 m_validities = validities;
58 }
59
60 25 ConfigPtr Generator::config() const
61 {
62 25 return m_config;
63 }
64
65 23 bool Generator::optimize()
66 {
67
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
23 assert(m_config);
68
69 23 auto config = optimize_impl();
70
71
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
23 if (!check_config(config)) {
72 qCDebug(DISMAN) << "Config could not be optimized. Returning unaltered original config.";
73 return false;
74 }
75 23 config->set_cause(Config::Cause::generated);
76
77
2/2
✓ Branch 11 taken 23 times.
✓ Branch 12 taken 23 times.
46 qCDebug(DISMAN) << "Config optimized:" << config;
78 23 m_config->apply(config);
79
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
23 assert(check_config(m_config));
80 23 return true;
81 23 }
82
83 void normalize_positions(ConfigPtr& config)
84 {
85 double min_x = 0;
86 double min_y = 0;
87 bool is_set = false;
88
89 for (auto const& [key, output] : config->outputs()) {
90 if (!output->positionable()) {
91 continue;
92 }
93
94 auto const x = output->position().x();
95 auto const y = output->position().y();
96 if (!is_set) {
97 min_x = x;
98 min_y = y;
99 is_set = true;
100 }
101
102 if (x < min_x) {
103 min_x = x;
104 }
105 if (y < min_y) {
106 min_y = y;
107 }
108 }
109
110 for (auto& [key, output] : config->outputs()) {
111 auto const pos = output->position();
112 output->set_position(QPointF(pos.x() - min_x, pos.y() - min_y));
113 }
114 }
115
116 bool Generator::extend(Extend_direction direction)
117 {
118 return extend(nullptr, direction);
119 }
120
121 bool Generator::extend(OutputPtr const& first, Extend_direction direction)
122 {
123 assert(m_config);
124
125 auto config = m_config->clone();
126 extend_impl(config, first, direction);
127
128 if (!check_config(config)) {
129 qCDebug(DISMAN) << "Could not extend. Config unchanged.";
130 return false;
131 }
132 config->set_cause(Config::Cause::unknown);
133
134 qCDebug(DISMAN) << "Generated extension configuration:" << config;
135 m_config->apply(config);
136 return true;
137 }
138
139 2 bool Generator::replicate()
140 {
141
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 assert(m_config);
142
143 2 auto config = m_config->clone();
144 2 replicate_impl(config);
145
146
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (!check_config(config)) {
147 qCDebug(DISMAN) << "Could not replicate output. Config unchanged.";
148 return false;
149 }
150 2 config->set_cause(Config::Cause::unknown);
151
152
2/2
✓ Branch 11 taken 2 times.
✓ Branch 12 taken 2 times.
4 qCDebug(DISMAN) << "Generated replica configuration:" << config;
153 2 m_config->apply(config);
154 2 return true;
155 2 }
156
157 bool Generator::disable_embedded()
158 {
159 assert(m_config);
160 auto config = m_config->clone();
161
162 auto embedded = embedded_impl(config->outputs(), OutputMap());
163 if (!embedded) {
164 qCWarning(DISMAN) << "No embedded output found to disable. Config unchanged.";
165 return false;
166 }
167
168 auto biggest_external = biggest_impl(config->outputs(), false, {{embedded->id(), embedded}});
169 if (!biggest_external) {
170 qCWarning(DISMAN) << "No external output found when disabling embedded. Config unchanged.";
171 return false;
172 }
173
174 qCDebug(DISMAN) << "Disable embedded:" << embedded;
175 qCDebug(DISMAN) << "Enable external:" << biggest_external;
176
177 embedded->set_enabled(false);
178 biggest_external->set_enabled(true);
179
180 // TODO: reorder positions.
181 normalize_positions(config);
182
183 if (!check_config(config)) {
184 qCWarning(DISMAN) << "Could not disable embedded output. Config unchanged.";
185 return false;
186 }
187
188 m_config->apply(config);
189 return true;
190 }
191
192 23 ConfigPtr Generator::optimize_impl()
193 {
194
2/2
✓ Branch 13 taken 23 times.
✓ Branch 14 taken 23 times.
46 qCDebug(DISMAN) << "Generates ideal config for" << m_config->outputs().size() << "displays.";
195
196
1/2
✗ Branch 4 not taken.
✓ Branch 5 taken 23 times.
23 if (m_config->outputs().empty()) {
197 qCDebug(DISMAN) << "No displays connected. Nothing to generate.";
198 return m_config;
199 }
200
201 23 auto config = m_config->clone();
202 23 auto outputs = config->outputs();
203
204
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 22 times.
23 if (outputs.size() == 1) {
205 1 single_output(config);
206 1 return config;
207 }
208
209 22 extend_impl(config, nullptr, Extend_direction::right);
210
211 22 return multi_output_fallback(config);
212 23 }
213
214 58 double Generator::best_scale(OutputPtr const& output)
215 {
216 // If we have no physical size, we can't determine the DPI properly. Fallback to scale 1.
217
2/2
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 49 times.
58 if (output->physical_size().height() <= 0) {
218 9 return 1.0;
219 }
220
221 49 const auto mode = output->auto_mode();
222 49 const qreal dpi = mode->size().height() / (output->physical_size().height() / 25.4);
223
224 // We see 110 DPI as a good standard. That corresponds to 1440p at 23" and 2160p/UHD at 34".
225 // This is smaller than usual but with high DPI screens this is often easily possible and
226 // otherwise we just don't scale at the moment.
227 49 auto scale_factor = dpi / 130;
228
229 // We only auto-scale displays up.
230
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 11 times.
49 if (scale_factor < 1) {
231 38 return 1.;
232 }
233
234 // We only auto-scale with one digit.
235 11 scale_factor = static_cast<int>(scale_factor * 10 + 0.5) / 10.;
236
237 // And only up to maximal 3 times.
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (scale_factor > 3) {
239 return 3.;
240 }
241
242 11 return scale_factor;
243 49 }
244
245 1 void Generator::single_output(ConfigPtr const& config)
246 {
247 1 auto outputs = config->outputs();
248
249
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (outputs.empty()) {
250 return;
251 }
252
253 1 auto output = outputs.begin()->second;
254
1/2
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
1 if (output->modes().empty()) {
255 return;
256 }
257
258
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 if (config->supported_features().testFlag(Config::Feature::PrimaryDisplay)) {
259 1 config->set_primary_output(output);
260 }
261
262 1 output->set_position(QPointF(0, 0));
263
264 1 output->d->apply_global();
265
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 }
266
267 22 void Generator::extend_impl(ConfigPtr const& config,
268 OutputPtr const& first,
269 Extend_direction direction)
270 {
271
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
22 assert(!first || first->enabled());
272
273 22 auto outputs = config->outputs();
274
275
2/2
✓ Branch 7 taken 22 times.
✓ Branch 8 taken 22 times.
44 qCDebug(DISMAN) << "Generate config by extending to the"
276
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 << (direction == Extend_direction::left ? "left" : "right");
277
278
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
22 if (outputs.empty()) {
279 qCDebug(DISMAN) << "No displays found. Nothing to generate.";
280 return;
281 }
282
283 22 auto start_output = first;
284
5/6
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 20 times.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 20 times.
22 if (!start_output && config->supported_features().testFlag(Config::Feature::PrimaryDisplay)) {
285
2/6
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
2 if (auto primary = config->primary_output(); primary && primary->enabled()) {
286 start_output = primary;
287 2 }
288 }
289
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 if (!start_output) {
290 22 start_output = primary_impl(outputs, OutputMap());
291 }
292
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
22 if (!start_output) {
293 qCDebug(DISMAN) << "No displays enabled. Nothing to generate.";
294 return;
295 }
296
297
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 20 times.
22 if (config->supported_features().testFlag(Config::Feature::PrimaryDisplay)) {
298
2/6
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
2 if (auto primary = config->primary_output(); !primary || !primary->enabled()) {
299 2 config->set_primary_output(start_output);
300 2 }
301 }
302
303 22 line_up(start_output, OutputMap(), outputs, direction);
304
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
22 }
305
306 void Generator::get_outputs_division(OutputPtr const& first,
307 ConfigPtr const& config,
308 OutputMap& old_outputs,
309 OutputMap& new_outputs)
310 {
311 OutputPtr recent_output;
312
313 for (auto const& [key, output] : config->outputs()) {
314 if (output == first) {
315 continue;
316 }
317 if (m_predecessor_config->output(output->id())) {
318 old_outputs[output->id()] = output;
319 } else {
320 new_outputs[output->id()] = output;
321 }
322 if (!recent_output || recent_output->id() < output->id()) {
323 recent_output = output;
324 }
325 }
326
327 if (!new_outputs.size()) {
328 // If we have no new outputs we assume the last one added (not the one designated as being
329 // first) should be extended in the given direction.
330 new_outputs[recent_output->id()] = recent_output;
331 old_outputs.erase(recent_output->id());
332 }
333 }
334
335 22 void Generator::line_up(OutputPtr const& first,
336 OutputMap const& old_outputs,
337 OutputMap const& new_outputs,
338 Extend_direction direction)
339 {
340 22 first->set_position(QPointF(0, 0));
341 22 first->d->apply_global();
342
343 double globalWidth
344
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 = direction == Extend_direction::right ? first->geometry().width() : first->position().x();
345
346
1/2
✗ Branch 7 not taken.
✓ Branch 8 taken 22 times.
22 for (auto const& [key, output] : old_outputs) {
347 if (direction == Extend_direction::left) {
348 auto const left = output->position().x();
349 if (left < globalWidth) {
350 globalWidth = left;
351 }
352 } else if (direction == Extend_direction::right) {
353 auto const right = output->position().x() + output->geometry().width();
354 if (right > globalWidth) {
355 globalWidth = right;
356 }
357 } else {
358 // We only have two directions at the moment.
359 assert(false);
360 }
361 }
362
363
2/2
✓ Branch 7 taken 55 times.
✓ Branch 8 taken 22 times.
77 for (auto& [key, output] : new_outputs) {
364 55 output->set_replication_source(0);
365
2/2
✓ Branch 4 taken 22 times.
✓ Branch 5 taken 33 times.
55 if (output->id() == first->id()) {
366 22 continue;
367 }
368
369 33 output->d->apply_global();
370
371
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
33 if (direction == Extend_direction::left) {
372 globalWidth -= output->geometry().width();
373 output->set_position(QPointF(globalWidth, 0));
374
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 } else if (direction == Extend_direction::right) {
375 33 output->set_position(QPointF(globalWidth, 0));
376 33 globalWidth += output->geometry().width();
377 } else {
378 // We only have two directions at the moment.
379 assert(false);
380 }
381 }
382 22 }
383
384 2 void Generator::replicate_impl(const ConfigPtr& config)
385 {
386 2 auto outputs = config->outputs();
387 2 auto source = primary_impl(outputs, OutputMap());
388
389
1/2
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 if (config->supported_features().testFlag(Config::Feature::PrimaryDisplay)) {
390
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
2 if (auto primary = config->primary_output()) {
391 source = primary;
392 } else {
393 2 config->set_primary_output(source);
394 2 }
395 }
396
397 2 source->d->apply_global();
398
399
2/2
✓ Branch 12 taken 2 times.
✓ Branch 13 taken 2 times.
4 qCDebug(DISMAN) << "Generate multi-output config by replicating" << source << "on"
400 2 << outputs.size() - 1 << "other outputs.";
401
402
2/2
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 2 times.
6 for (auto& [key, output] : outputs) {
403
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4 if (output == source) {
404 2 continue;
405 }
406
407 2 output->d->apply_global();
408 2 output->set_replication_source(source->id());
409 }
410 2 }
411
412 22 ConfigPtr Generator::multi_output_fallback(ConfigPtr const& config)
413 {
414
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 if (check_config(config)) {
415 22 return config;
416 }
417
418 qCDebug(DISMAN) << "Ideal config can not be applied. Fallback to replicating outputs.";
419 replicate_impl(config);
420
421 return config;
422 }
423
424 70 bool Generator::check_config(ConfigPtr const& config)
425 {
426 70 int enabled = 0;
427
2/2
✓ Branch 8 taken 171 times.
✓ Branch 9 taken 70 times.
241 for (auto const& [key, output] : config->outputs()) {
428 171 enabled += output->enabled();
429 70 }
430
2/6
✗ Branch 2 not taken.
✓ Branch 3 taken 70 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 70 times.
70 if (m_validities & Config::ValidityFlag::RequireAtLeastOneEnabledScreen && enabled == 0) {
431 qCDebug(DISMAN) << "Generator check failed: no enabled display, but required by flag.";
432 return false;
433 }
434
435 // TODO: check for primary output being set when primary output supported.
436 70 return true;
437 }
438
439 OutputPtr Generator::primary(OutputMap const& exclusions) const
440 {
441 return primary_impl(m_config->outputs(), exclusions);
442 }
443
444 2 OutputPtr Generator::embedded() const
445 {
446 2 return embedded_impl(m_config->outputs(), OutputMap());
447 }
448
449 4 OutputPtr Generator::biggest(OutputMap const& exclusions) const
450 {
451 4 return biggest_impl(m_config->outputs(), false, exclusions);
452 }
453
454 24 OutputPtr Generator::primary_impl(OutputMap const& outputs, OutputMap const& exclusions) const
455 {
456 // If one of the outputs is a embedded (panel) display, then we take this one as primary.
457
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 22 times.
24 if (auto output = embedded_impl(outputs, exclusions)) {
458
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (output->enabled()) {
459 2 return output;
460 }
461
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 2 times.
24 }
462 22 return biggest_impl(outputs, true, exclusions);
463 }
464
465 26 OutputPtr Generator::embedded_impl(OutputMap const& outputs, OutputMap const& exclusions) const
466 {
467 26 auto it = std::find_if(outputs.cbegin(), outputs.cend(), [&exclusions](auto const& output) {
468 59 return output.second->type() == Output::Panel
469
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 55 times.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
59 && exclusions.find(output.second->id()) == exclusions.end();
470 });
471
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 22 times.
52 return it != outputs.cend() ? it->second : OutputPtr();
472 }
473
474 26 OutputPtr Generator::biggest_impl(OutputMap const& outputs,
475 bool only_enabled,
476 const OutputMap& exclusions) const
477 {
478 26 auto max_area = 0;
479 26 OutputPtr biggest;
480
481
2/2
✓ Branch 7 taken 63 times.
✓ Branch 8 taken 26 times.
89 for (auto const& [key, output] : outputs) {
482
1/2
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
63 if (exclusions.find(output->id()) != exclusions.end()) {
483 continue;
484 }
485 63 auto const mode = output->best_mode();
486
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
63 if (!mode) {
487 continue;
488 }
489
4/6
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 55 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 63 times.
63 if (only_enabled && !output->enabled()) {
490 continue;
491 }
492 63 auto const area = mode->size().width() * mode->size().height();
493
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 21 times.
63 if (area > max_area) {
494 42 max_area = area;
495 42 biggest = output;
496 }
497
1/2
✓ Branch 1 taken 63 times.
✗ Branch 2 not taken.
63 }
498
499 26 return biggest;
500 }
501
502 }
503