1 /*
2     SPDX-FileCopyrightText: 2020 Roman Gilg <subdiff@gmail.com>
3 
4     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only
5 */
6 #include "backend_impl.h"
7 
8 #include "device.h"
9 #include "filer_controller.h"
10 #include "generator.h"
11 #include "logging.h"
12 #include "output.h"
13 
14 #include <QRectF>
15 
16 namespace Disman
17 {
18 
BackendImpl()19 BackendImpl::BackendImpl()
20     : Backend()
21     , m_device{new Device}
22     , m_filer_controller{new Filer_controller(m_device.get())}
23 {
24     connect(m_device.get(), &Device::lid_open_changed, this, &BackendImpl::load_lid_config);
25 }
26 
27 BackendImpl::~BackendImpl() = default;
28 
init(QVariantMap const & arguments)29 void BackendImpl::init([[maybe_unused]] QVariantMap const& arguments)
30 {
31     // noop, maybe overridden in individual backends.
32 }
33 
config() const34 Disman::ConfigPtr BackendImpl::config() const
35 {
36     m_config_initialized = true;
37 
38     auto config = config_impl();
39 
40     if (config->cause() == Config::Cause::unknown && m_config) {
41         config->set_cause(m_config->cause());
42     }
43 
44     return config;
45 }
46 
config_impl() const47 Disman::ConfigPtr BackendImpl::config_impl() const
48 {
49     auto config = std::make_shared<Config>();
50 
51     // We update from the windowing system first so the controller knows about the current
52     // configuration and then update one more time so the windowing system can override values
53     // it provides itself.
54     update_config(config);
55     m_filer_controller->read(config);
56     update_config(config);
57 
58     return config;
59 }
60 
set_config(Disman::ConfigPtr const & config)61 void BackendImpl::set_config(Disman::ConfigPtr const& config)
62 {
63     if (!config || config->compare(m_config)) {
64         // No change by new config. Do nothing.
65         return;
66     }
67 
68     if (!set_config_impl(config)) {
69         // No change to the system but other changes that need to be synced with other Disman
70         // clients so emit a config_changed signal directly.
71         m_config = config;
72         Q_EMIT config_changed(config);
73     }
74 }
75 
set_config_impl(Disman::ConfigPtr const & config)76 bool BackendImpl::set_config_impl(Disman::ConfigPtr const& config)
77 {
78     if (QLoggingCategory category("disman.backend"); category.isEnabled(QtDebugMsg)) {
79         qCDebug(DISMAN_BACKEND) << "About to set config."
80                                 << "\nPrevious config:" << this->config()
81                                 << "\nNew config:" << config;
82     }
83 
84     m_filer_controller->write(config);
85 
86     if (config->supported_features().testFlag(Config::Feature::OutputReplication)) {
87         for (auto const& [key, output] : config->outputs()) {
88             if (auto source_id = output->replication_source()) {
89                 auto source = config->output(source_id);
90                 output->set_position(source->position());
91                 output->force_geometry(source->geometry());
92             }
93         }
94     }
95 
96     return set_config_system(config);
97 }
98 
handle_config_change()99 bool BackendImpl::handle_config_change()
100 {
101     // We need the config with its own cause, so we call config_impl here.
102     auto cfg = config_impl();
103 
104     if (!m_config || m_config->hash() != cfg->hash()) {
105         qCDebug(DISMAN_BACKEND) << "Config with new output pattern received:" << cfg;
106 
107         if (cfg->cause() == Config::Cause::unknown) {
108             qCDebug(DISMAN_BACKEND)
109                 << "Config received that is unknown. Creating an optimized config now.";
110             Generator generator(cfg);
111             generator.optimize();
112             cfg = generator.config();
113         } else {
114             // We set the windowing system to our saved values. They were overriden before so
115             // re-read them.
116             m_filer_controller->read(cfg);
117         }
118 
119         m_config = cfg;
120 
121         if (set_config_impl(cfg)) {
122             qCDebug(DISMAN_BACKEND) << "Config for new output pattern sent.";
123             return false;
124         }
125     }
126 
127     Q_EMIT config_changed(cfg);
128     return true;
129 }
130 
load_lid_config()131 void BackendImpl::load_lid_config()
132 {
133     if (!m_config_initialized) {
134         qCWarning(DISMAN_BACKEND) << "Lid open state changed but first config has not yet been "
135                                      "initialized. Doing nothing.";
136         return;
137     }
138 
139     auto cfg = config();
140     if (cfg->outputs().size() == 1) {
141         // Open-lid configuration is only relevant with more than one output.
142         return;
143     }
144 
145     if (m_device->lid_open()) {
146         // The lid has been opnened. Try to load the open-lid file.
147         if (!m_filer_controller->load_lid_file(cfg)) {
148             qCWarning(DISMAN_BACKEND)
149                 << "Loading open-lid file failed. Generating an optimal config instead.";
150             return;
151         }
152         qCDebug(DISMAN_BACKEND) << "Loaded lid-open file on lid being opened.";
153     } else {
154         // The lid has been closed, we write the current config as open-lid-config and then generate
155         // an optimized one with the embedded display disabled that gets applied.
156         Generator generator(cfg);
157         qCDebug(DISMAN_BACKEND) << "Lid closed, trying to disable embedded display.";
158 
159         if (!generator.disable_embedded()) {
160             // Alternative config could not be generated.
161             qCWarning(DISMAN_BACKEND) << "Embedded display could not be disabled.";
162             return;
163         }
164         if (!m_filer_controller->save_lid_file(cfg)) {
165             qCWarning(DISMAN_BACKEND) << "Failed to save open-lid file.";
166             return;
167         }
168         cfg = generator.config();
169     }
170 
171     set_config_impl(cfg);
172 }
173 
174 }
175