GCC Code Coverage Report


Directory: ./
File: ctl/doctor.cpp
Date: 2023-04-20 22:59:23
Exec Total Coverage
Lines: 0 272 0.0%
Branches: 0 296 0.0%

Line Branch Exec Source
1 /*
2 SPDX-FileCopyrightText: 2014-2016 by Sebastian K├╝gler <sebas@kde.org>
3 SPDX-FileCopyrightText: 2020 Roman Gilg <subdiff@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only
6 */
7 #include "doctor.h"
8 #include "watcher.h"
9
10 #include <QCommandLineParser>
11 #include <QJsonDocument>
12 #include <QJsonObject>
13 #include <QRectF>
14
15 #include "backendmanager_p.h"
16 #include "config.h"
17 #include "configoperation.h"
18 #include "getconfigoperation.h"
19 #include "log.h"
20 #include "setconfigoperation.h"
21
22 Q_LOGGING_CATEGORY(DISMAN_CTL, "disman.ctl")
23
24 static QTextStream cout(stdout);
25 static QTextStream cerr(stderr);
26
27 const static QString green = QStringLiteral("\033[01;32m");
28 const static QString red = QStringLiteral("\033[01;31m");
29 const static QString yellow = QStringLiteral("\033[01;33m");
30 const static QString blue = QStringLiteral("\033[01;34m");
31 const static QString bold = QStringLiteral("\033[01;39m");
32 const static QString cr = QStringLiteral("\033[0;0m");
33
34 namespace Disman::ConfigSerializer
35 {
36 // Exported private symbol in configserializer_p.h in Disman
37 extern QJsonObject serialize_config(const Disman::ConfigPtr& config);
38 }
39
40 namespace Disman::Ctl
41 {
42
43 Doctor::Doctor(QCommandLineParser* parser, QObject* parent)
44 : QObject(parent)
45 , m_config(nullptr)
46 , m_parser(parser)
47 , m_changed(false)
48 {
49 if (m_parser->optionNames().isEmpty() && m_parser->positionalArguments().isEmpty()) {
50 // When dismanctl was launched without any parameter show help and quit.
51 m_parser->showHelp(1);
52 }
53 if (m_parser->isSet(QStringLiteral("info"))) {
54 showBackends();
55 }
56 if (parser->isSet(QStringLiteral("json")) || parser->isSet(QStringLiteral("outputs"))
57 || parser->isSet(QStringLiteral("watch")) || !m_parser->positionalArguments().isEmpty()) {
58
59 Disman::GetConfigOperation* op = new Disman::GetConfigOperation();
60 connect(op,
61 &Disman::GetConfigOperation::finished,
62 this,
63 [this](Disman::ConfigOperation* op) { configReceived(op); });
64 return;
65 }
66
67 if (m_parser->isSet(QStringLiteral("log"))) {
68
69 const QString logmsg = m_parser->value(QStringLiteral("log"));
70 if (!Log::instance()->enabled()) {
71 qCWarning(DISMAN_CTL)
72 << "Logging is disabled, unset DISMAN_LOGGING in your environment.";
73 } else {
74 Log::log(logmsg);
75 }
76 }
77 // We need to kick the event loop, otherwise .quit() hangs
78 QTimer::singleShot(0, qApp, &QCoreApplication::quit);
79 }
80
81 void Doctor::showBackends() const
82 {
83 cout << "Environment: " << Qt::endl;
84 auto env_disman_backend = (qEnvironmentVariableIsEmpty("DISMAN_BACKEND"))
85 ? QStringLiteral("[not set]")
86 : QString::fromUtf8(qgetenv("DISMAN_BACKEND"));
87 cout << " * DISMAN_BACKEND : " << env_disman_backend << Qt::endl;
88 auto env_disman_backend_inprocess = (qEnvironmentVariableIsEmpty("DISMAN_IN_PROCESS"))
89 ? QStringLiteral("[not set]")
90 : QString::fromUtf8(qgetenv("DISMAN_IN_PROCESS"));
91 cout << " * DISMAN_IN_PROCESS : " << env_disman_backend_inprocess << Qt::endl;
92 auto env_disman_logging = (qEnvironmentVariableIsEmpty("DISMAN_LOGGING"))
93 ? QStringLiteral("[not set]")
94 : QString::fromUtf8(qgetenv("DISMAN_LOGGING"));
95 cout << " * DISMAN_LOGGING : " << env_disman_logging << Qt::endl;
96
97 cout << "Logging to : "
98 << (Log::instance()->enabled() ? Log::instance()->file()
99 : QStringLiteral("[logging disabled]"))
100 << Qt::endl;
101 auto backends = BackendManager::instance()->list_backends();
102 auto preferred = BackendManager::instance()->preferred_backend();
103 cout << "Preferred Disman backend : " << green << preferred.fileName() << cr << Qt::endl;
104 cout << "Available Disman backends:" << Qt::endl;
105 for (auto const& file_info : qAsConst(backends)) {
106 auto c = blue;
107 if (preferred == file_info) {
108 c = green;
109 }
110 cout << " * " << c << file_info.fileName() << cr << ": " << file_info.absoluteFilePath()
111 << Qt::endl;
112 }
113 cout << Qt::endl;
114 }
115
116 void Doctor::parsePositionalArgs()
117 {
118 auto const& args = m_parser->positionalArguments();
119 for (auto const& op : args) {
120 auto ops = op.split(QLatin1Char('.'));
121 if (ops.count() > 2) {
122 bool ok;
123 int output_id = -1;
124 if (ops[0] == QLatin1String("output")) {
125 for (auto const& [key, output] : m_config->outputs()) {
126 if (output->name() == ops[1].toStdString()) {
127 output_id = output->id();
128 }
129 }
130 if (output_id == -1) {
131 output_id = ops[1].toInt(&ok);
132 if (!ok) {
133 cerr << "Unable to parse output id: " << ops[1] << Qt::endl;
134 qApp->exit(3);
135 return;
136 }
137 }
138 if (ops.count() == 3 && ops[2] == QLatin1String("enable")) {
139 if (!setEnabled(output_id, true)) {
140 qApp->exit(1);
141 return;
142 };
143 } else if (ops.count() == 3 && ops[2] == QLatin1String("disable")) {
144 if (!setEnabled(output_id, false)) {
145 qApp->exit(1);
146 return;
147 };
148 } else if (ops.count() == 4 && ops[2] == QLatin1String("mode")) {
149 QString mode_id = ops[3];
150 // set mode
151 if (!setMode(output_id, mode_id.toStdString())) {
152 qApp->exit(9);
153 return;
154 }
155 qCDebug(DISMAN_CTL) << "Output" << output_id << "set mode" << mode_id;
156
157 } else if (ops.count() == 4 && ops[2] == QLatin1String("position")) {
158 QStringList _pos = ops[3].split(QLatin1Char(','));
159 if (_pos.count() != 2) {
160 qCWarning(DISMAN_CTL) << "Invalid position:" << ops[3];
161 qApp->exit(5);
162 return;
163 }
164 int x = _pos[0].toInt(&ok);
165 int y = _pos[1].toInt(&ok);
166 if (!ok) {
167 cerr << "Unable to parse position: " << ops[3] << Qt::endl;
168 qApp->exit(5);
169 return;
170 }
171
172 QPoint p(x, y);
173 qCDebug(DISMAN_CTL) << "Output position" << p;
174 if (!setPosition(output_id, p)) {
175 qApp->exit(1);
176 return;
177 }
178 } else if ((ops.count() == 4 || ops.count() == 5)
179 && ops[2] == QLatin1String("scale")) {
180 // be lenient about . vs. comma as separator
181 qreal scale = ops[3].replace(QLatin1Char(','), QLatin1Char('.')).toDouble(&ok);
182 if (ops.count() == 5) {
183 const QString dbl = ops[3] + QLatin1String(".") + ops[4];
184 scale = dbl.toDouble(&ok);
185 };
186 // set scale
187 if (!ok || qFuzzyCompare(scale, 0.0) || !setScale(output_id, scale)) {
188 qCDebug(DISMAN_CTL)
189 << "Could not set scale " << scale << " to output " << output_id;
190 qApp->exit(9);
191 return;
192 }
193 } else if ((ops.count() == 4)
194 && (ops[2] == QLatin1String("orientation")
195 || ops[2] == QStringLiteral("rotation"))) {
196 const QString _rotation = ops[3].toLower();
197 bool ok = false;
198 const QHash<QString, Disman::Output::Rotation> rotationMap(
199 {{QStringLiteral("none"), Disman::Output::None},
200 {QStringLiteral("normal"), Disman::Output::None},
201 {QStringLiteral("left"), Disman::Output::Left},
202 {QStringLiteral("right"), Disman::Output::Right},
203 {QStringLiteral("inverted"), Disman::Output::Inverted}});
204 Disman::Output::Rotation rot = Disman::Output::None;
205 // set orientation
206 if (rotationMap.contains(_rotation)) {
207 ok = true;
208 rot = rotationMap[_rotation];
209 }
210 if (!ok || !setRotation(output_id, rot)) {
211 qCDebug(DISMAN_CTL) << "Could not set orientation " << _rotation
212 << " to output " << output_id;
213 qApp->exit(9);
214 return;
215 }
216 } else {
217 cerr << "Unable to parse arguments: " << op << Qt::endl;
218 qApp->exit(2);
219 return;
220 }
221 }
222 }
223 }
224 }
225
226 void Doctor::configReceived(Disman::ConfigOperation* op)
227 {
228 if (op->has_error()) {
229 qCWarning(DISMAN_CTL) << "Received initial config has error.";
230 qApp->exit(1);
231 }
232
233 if (m_parser->isSet(QStringLiteral("watch"))) {
234 m_watcher.reset(new Watcher(op->config()));
235 return;
236 }
237
238 m_config = op->config();
239
240 if (m_parser->isSet(QStringLiteral("json"))) {
241 showJson();
242 qApp->quit();
243 }
244 if (m_parser->isSet(QStringLiteral("outputs"))) {
245 showOutputs(m_config);
246 qApp->quit();
247 }
248
249 parsePositionalArgs();
250
251 if (m_changed) {
252 applyConfig();
253 m_changed = false;
254 }
255 }
256
257 void Doctor::showOutputs(const Disman::ConfigPtr& config)
258 {
259 if (!config) {
260 qCWarning(DISMAN_CTL) << "Invalid config.";
261 return;
262 }
263
264 QHash<Disman::Output::Type, QString> typeString;
265 typeString[Disman::Output::Unknown] = QStringLiteral("Unknown");
266 typeString[Disman::Output::VGA] = QStringLiteral("VGA");
267 typeString[Disman::Output::DVI] = QStringLiteral("DVI");
268 typeString[Disman::Output::DVII] = QStringLiteral("DVII");
269 typeString[Disman::Output::DVIA] = QStringLiteral("DVIA");
270 typeString[Disman::Output::DVID] = QStringLiteral("DVID");
271 typeString[Disman::Output::HDMI] = QStringLiteral("HDMI");
272 typeString[Disman::Output::Panel] = QStringLiteral("Panel");
273 typeString[Disman::Output::TV] = QStringLiteral("TV");
274 typeString[Disman::Output::TVComposite] = QStringLiteral("TVComposite");
275 typeString[Disman::Output::TVSVideo] = QStringLiteral("TVSVideo");
276 typeString[Disman::Output::TVComponent] = QStringLiteral("TVComponent");
277 typeString[Disman::Output::TVSCART] = QStringLiteral("TVSCART");
278 typeString[Disman::Output::TVC4] = QStringLiteral("TVC4");
279 typeString[Disman::Output::DisplayPort] = QStringLiteral("DisplayPort");
280
281 for (auto const& [key, output] : config->outputs()) {
282 cout << green << "Output: " << cr << output->id() << " " << output->name().c_str();
283 cout << " "
284 << (output->enabled() ? green + QLatin1String("enabled")
285 : red + QLatin1String("disabled"));
286 auto _type = typeString[output->type()];
287 cout << " " << yellow << (_type.isEmpty() ? QStringLiteral("UnmappedOutputType") : _type);
288 cout << blue << " Modes: " << cr;
289 for (auto const& [key, mode] : output->modes()) {
290 auto name = QStringLiteral("%1x%2@%3")
291 .arg(QString::number(mode->size().width()),
292 QString::number(mode->size().height()),
293 QString::number(mode->refresh()));
294 if (mode == output->auto_mode()) {
295 name = green + name + QLatin1Char('*') + cr;
296 }
297 if (mode == output->preferred_mode()) {
298 name = name + QLatin1Char('!');
299 }
300 cout << mode->id().c_str() << ":" << name << " ";
301 }
302 const auto g = output->geometry();
303 cout << yellow << "Geometry: " << cr << g.x() << "," << g.y() << " " << g.width() << "x"
304 << g.height() << " ";
305 cout << yellow << "Scale: " << cr << output->scale() << " ";
306 cout << yellow << "Rotation: " << cr << output->rotation() << " ";
307 cout << cr << Qt::endl;
308 }
309
310 if (auto primary = config->primary_output()) {
311 cout << blue << "Primary:" << cr << primary->name().c_str() << Qt::endl;
312 }
313 }
314
315 void Doctor::showJson() const
316 {
317 QJsonDocument doc(Disman::ConfigSerializer::serialize_config(m_config));
318 cout << doc.toJson(QJsonDocument::Indented);
319 }
320
321 bool Doctor::setEnabled(int id, bool enabled = true)
322 {
323 if (!m_config) {
324 qCWarning(DISMAN_CTL) << "Invalid config.";
325 return false;
326 }
327
328 for (auto& [key, output] : m_config->outputs()) {
329 if (output->id() == id) {
330 cout << (enabled ? "Enabling " : "Disabling ") << "output " << id << Qt::endl;
331 output->set_enabled(enabled);
332 m_changed = true;
333 return true;
334 }
335 }
336 cerr << "Output with id " << id << " not found." << Qt::endl;
337 qApp->exit(8);
338 return false;
339 }
340
341 bool Doctor::setPosition(int id, const QPoint& pos)
342 {
343 if (!m_config) {
344 qCWarning(DISMAN_CTL) << "Invalid config.";
345 return false;
346 }
347
348 for (auto& [key, output] : m_config->outputs()) {
349 if (output->id() == id) {
350 qCDebug(DISMAN_CTL) << "Set output position" << pos;
351 output->set_position(pos);
352 m_changed = true;
353 return true;
354 }
355 }
356 cout << "Output with id " << id << " not found." << Qt::endl;
357 return false;
358 }
359
360 bool Doctor::setMode(int id, std::string const& mode_id)
361 {
362 if (!m_config) {
363 qCWarning(DISMAN_CTL) << "Invalid config.";
364 return false;
365 }
366
367 for (auto& [key, output] : m_config->outputs()) {
368 if (output->id() == id) {
369 // find mode
370 for (auto const& [key, mode] : output->modes()) {
371 auto name = QStringLiteral("%1x%2@%3")
372 .arg(QString::number(mode->size().width()),
373 QString::number(mode->size().height()),
374 QString::number(mode->refresh()));
375 if (mode->id() == mode_id || name.toStdString() == mode_id) {
376 qCDebug(DISMAN_CTL) << "Taddaaa! Found mode" << mode->id().c_str() << name;
377 output->set_mode(mode);
378 m_changed = true;
379 return true;
380 }
381 }
382 }
383 }
384 cout << "Output mode " << mode_id.c_str() << " not found." << Qt::endl;
385 return false;
386 }
387
388 bool Doctor::setScale(int id, qreal scale)
389 {
390 if (!m_config) {
391 qCWarning(DISMAN_CTL) << "Invalid config.";
392 return false;
393 }
394
395 for (auto& [key, output] : m_config->outputs()) {
396 if (output->id() == id) {
397 output->set_scale(scale);
398 m_changed = true;
399 return true;
400 }
401 }
402 cout << "Output scale " << id << " invalid." << Qt::endl;
403 return false;
404 }
405
406 bool Doctor::setRotation(int id, Disman::Output::Rotation rot)
407 {
408 if (!m_config) {
409 qCWarning(DISMAN_CTL) << "Invalid config.";
410 return false;
411 }
412
413 for (auto& [key, output] : m_config->outputs()) {
414 if (output->id() == id) {
415 output->set_rotation(rot);
416 m_changed = true;
417 return true;
418 }
419 }
420 cout << "Output rotation " << id << " invalid." << Qt::endl;
421 return false;
422 }
423
424 void Doctor::applyConfig()
425 {
426 if (!m_changed) {
427 return;
428 }
429 auto setop = new SetConfigOperation(m_config, this);
430 setop->exec();
431 qCDebug(DISMAN_CTL) << "setop exec returned" << m_config;
432 qApp->exit(0);
433 }
434
435 }
436