doctor.cpp 18.1 KB
Newer Older
1
/*************************************************************************************
Sebastian Kügler's avatar
Sebastian Kügler committed
2
 *  Copyright 2014-2016 Sebastian Kügler <sebas@kde.org>                             *
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *                                                                                   *
 *  This library is free software; you can redistribute it and/or                    *
 *  modify it under the terms of the GNU Lesser General Public                       *
 *  License as published by the Free Software Foundation; either                     *
 *  version 2.1 of the License, or (at your option) any later version.               *
 *                                                                                   *
 *  This library is distributed in the hope that it will be useful,                  *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                *
 *  Lesser General Public License for more details.                                  *
 *                                                                                   *
 *  You should have received a copy of the GNU Lesser General Public                 *
 *  License along with this library; if not, write to the Free Software              *
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA       *
 *************************************************************************************/

#include "doctor.h"
20
#include "dpmsclient.h"
21

Sebastian Kügler's avatar
Sebastian Kügler committed
22
#include <QCoreApplication>
23
24
#include <QCommandLineParser>
#include <QDateTime>
25
#include <QGuiApplication>
26
27
28
29
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QFile>
Sebastian Kügler's avatar
Sebastian Kügler committed
30
#include <QLoggingCategory>
Sebastian Kügler's avatar
Sebastian Kügler committed
31
#include <QRect>
32
#include <QStandardPaths>
33

34
#include "../backendmanager_p.h"
35
36
37
#include "../config.h"
#include "../configoperation.h"
#include "../getconfigoperation.h"
38
#include "../setconfigoperation.h"
39
#include "../edid.h"
40
#include "../log.h"
41
#include "../output.h"
42

Allen Winter's avatar
Allen Winter committed
43
Q_LOGGING_CATEGORY(KSCREEN_DOCTOR, "kscreen.doctor")
Sebastian Kügler's avatar
Sebastian Kügler committed
44
45


46
47
48
static QTextStream cout(stdout);
static QTextStream cerr(stderr);

Sebastian Kügler's avatar
Sebastian Kügler committed
49
50
51
52
53
54
55
const static QString green = QStringLiteral("\033[01;32m");
const static QString red = QStringLiteral("\033[01;31m");
const static QString yellow = QStringLiteral("\033[01;33m");
const static QString blue = QStringLiteral("\033[01;34m");
const static QString bold = QStringLiteral("\033[01;39m");
const static QString cr = QStringLiteral("\033[0;0m");

56
57
58
59
60
61
62
63
64
65
66
67
68
69
namespace KScreen
{
namespace ConfigSerializer
{
// Exported private symbol in configserializer_p.h in KScreen
extern QJsonObject serializeConfig(const KScreen::ConfigPtr &config);
}
}

using namespace KScreen;

Doctor::Doctor(QObject *parent)
    : QObject(parent)
    , m_config(nullptr)
70
    , m_changed(false)
71
    , m_dpmsClient(nullptr)
72
73
74
75
76
77
78
79
80
81
{
}

Doctor::~Doctor()
{
}

void Doctor::start(QCommandLineParser *parser)
{
    m_parser = parser;
82
    if (m_parser->isSet("info")) {
83
84
        showBackends();
    }
85
    if (parser->isSet("json") || parser->isSet("outputs") || !m_positionalArgs.isEmpty()) {
86
87

        KScreen::GetConfigOperation *op = new KScreen::GetConfigOperation();
88
89
90
91
        connect(op, &KScreen::GetConfigOperation::finished, this,
                [this](KScreen::ConfigOperation *op) {
                    configReceived(op);
                });
92
93
        return;
    }
94
    if (m_parser->isSet("dpms")) {
95
96
97
98
99
100
101
        if (!QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
            cerr << "DPMS is only supported on Wayland." << endl;
            // We need to kick the event loop, otherwise .quit() hangs
            QTimer::singleShot(0, qApp->quit);
            return;

        }
Sebastian Kügler's avatar
Sebastian Kügler committed
102
103
104
105
106
107
108
109
110
        m_dpmsClient = new DpmsClient(this);
        connect(m_dpmsClient, &DpmsClient::finished, qApp, &QCoreApplication::quit);

        const QString dpmsArg = m_parser->value(QStringLiteral("dpms"));
        if (dpmsArg == QStringLiteral("show")) {
            showDpms();
        } else {
            setDpms(dpmsArg);
        }
111
112
        return;
    }
113
114
115
116
117
118
119
120
121
122

    if (m_parser->isSet("log")) {

        const QString logmsg = m_parser->value(QStringLiteral("log"));
        if (!Log::instance()->enabled()) {
            qCWarning(KSCREEN_DOCTOR) << "Logging is disabled, unset KSCREEN_LOGGING in your environment.";
        } else {
            Log::log(logmsg);
        }
    }
123
    // We need to kick the event loop, otherwise .quit() hangs
124
    QTimer::singleShot(0, qApp->quit);
Sebastian Kügler's avatar
Sebastian Kügler committed
125
126
127
128
129
}

void KScreen::Doctor::setDpms(const QString& dpmsArg)
{
    qDebug() << "SetDpms: " << dpmsArg;
130
    connect(m_dpmsClient, &DpmsClient::ready, this, [this, dpmsArg]() {
Sebastian Kügler's avatar
Sebastian Kügler committed
131
132
133
134
135
136
137
138
139
140
141
        cout << "DPMS.ready()";
        if (dpmsArg == QStringLiteral("off")) {
            m_dpmsClient->off();
        } else if (dpmsArg == QStringLiteral("on")) {
            m_dpmsClient->on();
        } else {
            cout << "--dpms argument not understood (" << dpmsArg << ")";
        }
    });

    m_dpmsClient->connect();
142
143
}

Sebastian Kügler's avatar
Sebastian Kügler committed
144

145
146
147
148
void Doctor::showDpms()
{
    m_dpmsClient = new DpmsClient(this);

149
    connect(m_dpmsClient, &DpmsClient::ready, this, []() {
150
151
152
153
154
155
        cout << "DPMS.ready()";
    });

    m_dpmsClient->connect();
}

156
157
158
159
void Doctor::showBackends() const
{
    cout << "Environment: " << endl;
    auto env_kscreen_backend = (qgetenv("KSCREEN_BACKEND").isEmpty()) ? QStringLiteral("[not set]") : qgetenv("KSCREEN_BACKEND");
160
    cout << "  * KSCREEN_BACKEND           : " << env_kscreen_backend << endl;
161
162
    auto env_kscreen_backend_inprocess = (qgetenv("KSCREEN_BACKEND_INPROCESS").isEmpty()) ? QStringLiteral("[not set]") : qgetenv("KSCREEN_BACKEND_INPROCESS");
    cout << "  * KSCREEN_BACKEND_INPROCESS : " << env_kscreen_backend_inprocess << endl;
163
164
165
166
    auto env_kscreen_logging = (qgetenv("KSCREEN_LOGGING").isEmpty()) ? QStringLiteral("[not set]") : qgetenv("KSCREEN_LOGGING");
    cout << "  * KSCREEN_LOGGING           : " << env_kscreen_logging << endl;

    cout << "Logging to                : " << (Log::instance()->enabled() ? Log::instance()->logFile() : "[logging disabled]") << endl;
167
168
    auto backends = BackendManager::instance()->listBackends();
    auto preferred = BackendManager::instance()->preferredBackend();
169
    cout << "Preferred KScreen backend : " << green << preferred.fileName() << cr << endl;
170
171
172
173
174
175
176
177
178
    cout << "Available KScreen backends:" << endl;
    Q_FOREACH(const QFileInfo f, backends) {
        auto c = blue;
        if (preferred == f) {
            c = green;
        }
        cout << "  * " << c << f.fileName() << cr << ": " << f.absoluteFilePath() << endl;
    }
    cout << endl;
179
180
}

Sebastian Kügler's avatar
Sebastian Kügler committed
181
182
183
184
185
186
187
void Doctor::setOptionList(const QStringList &positionalArgs)
{
    m_positionalArgs = positionalArgs;
}

void Doctor::parsePositionalArgs()
{
188
    //qCDebug(KSCREEN_DOCTOR) << "POSARGS" << m_positionalArgs;
Sebastian Kügler's avatar
Sebastian Kügler committed
189
190
191
    Q_FOREACH(const QString &op, m_positionalArgs) {
        auto ops = op.split('.');
        if (ops.count() > 2) {
192
            bool ok;
193
            int output_id = -1;
Sebastian Kügler's avatar
Sebastian Kügler committed
194
            if (ops[0] == QStringLiteral("output")) {
195
196
197
198
199
200
                Q_FOREACH (const auto &output, m_config->outputs()) {
                    if (output->name() == ops[1]) {
                        output_id = output->id();
                    }
                }
                if (output_id == -1) {
201
                    output_id = ops[1].toInt(&ok);
202
203
204
205
206
                    if (!ok) {
                        cerr << "Unable to parse output id" << ops[1] << endl;
                        qApp->exit(3);
                        return;
                    }
207
                }
208
                if (ops.count() == 3 && ops[2] == QStringLiteral("enable")) {
Sebastian Kügler's avatar
Sebastian Kügler committed
209
210
211
212
                    if (!setEnabled(output_id, true)) {
                        qApp->exit(1);
                        return;
                    };
Sebastian Kügler's avatar
Sebastian Kügler committed
213
                } else if (ops.count() == 3 && ops[2] == QStringLiteral("disable")) {
Sebastian Kügler's avatar
Sebastian Kügler committed
214
215
216
217
                    if (!setEnabled(output_id, false)) {
                        qApp->exit(1);
                        return;
                    };
Sebastian Kügler's avatar
Sebastian Kügler committed
218
                } else if (ops.count() == 4 && ops[2] == QStringLiteral("mode")) {
219
220
221
222
                    QString mode_id = ops[3];
                    // set mode
                    if (!setMode(output_id, mode_id)) {
                        qApp->exit(9);
223
224
                        return;
                    }
Sebastian Kügler's avatar
Sebastian Kügler committed
225
                    qCDebug(KSCREEN_DOCTOR) << "Output" << output_id << "set mode" << mode_id;
Sebastian Kügler's avatar
Sebastian Kügler committed
226

Sebastian Kügler's avatar
Sebastian Kügler committed
227
228
229
230
                } else if (ops.count() == 4 && ops[2] == QStringLiteral("position")) {
                    QStringList _pos = ops[3].split(',');
                    if (_pos.count() != 2) {
                        qCWarning(KSCREEN_DOCTOR) << "Invalid position:" << ops[3];
231
232
233
                        qApp->exit(5);
                        return;
                    }
234
235
                    int x = _pos[0].toInt(&ok);
                    int y = _pos[1].toInt(&ok);
236
237
238
239
                    if (!ok) {
                        cerr << "Unable to parse position" << ops[3] << endl;
                        qApp->exit(5);
                        return;
Sebastian Kügler's avatar
Sebastian Kügler committed
240
241
242
243
                    }

                    QPoint p(x, y);
                    qCDebug(KSCREEN_DOCTOR) << "Output position" << p;
Sebastian Kügler's avatar
Sebastian Kügler committed
244
245
246
247
                    if (!setPosition(output_id, p)) {
                        qApp->exit(1);
                        return;
                    }
248
249
250
251
252
253
254
255
256
257
258
259
260
                } else if ((ops.count() == 4 || ops.count() == 5) && ops[2] == QStringLiteral("scale")) {
                    // be lenient about . vs. comma as separator
                    qreal scale = ops[3].replace(QStringLiteral(","), QStringLiteral(".")).toDouble(&ok);
                    if (ops.count() == 5) {
                        const auto dbl = ops[3] + QStringLiteral(".") + ops[4];
                        scale = dbl.toDouble(&ok);
                    };
                    // set scale
                    if (!ok || scale == 0 || !setScale(output_id, scale)) {
                        qCDebug(KSCREEN_DOCTOR) << "Could not set scale " << scale << " to output " << output_id;
                        qApp->exit(9);
                        return;
                    }
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
                } else if ((ops.count() == 4) && (ops[2] == QStringLiteral("orientation") || ops[2] == QStringLiteral("rotation"))) {
                    const QString _rotation = ops[3].toLower();
                    bool ok = false;
                    const QHash<QString, KScreen::Output::Rotation> rotationMap({
                                                                            {QStringLiteral("none"), KScreen::Output::None},
                                                                            {QStringLiteral("normal"), KScreen::Output::None},
                                                                            {QStringLiteral("left"), KScreen::Output::Left},
                                                                            {QStringLiteral("right"), KScreen::Output::Right},
                                                                            {QStringLiteral("inverted"), KScreen::Output::Inverted}
                                                                        });
                    KScreen::Output::Rotation rot = KScreen::Output::None;
                    // set orientation
                    if (rotationMap.contains(_rotation)) {
                        ok = true;
                        rot = rotationMap[_rotation];
                    }
                    if (!ok || !setRotation(output_id, rot)) {
                        qCDebug(KSCREEN_DOCTOR) << "Could not set orientation " << _rotation << " to output " << output_id;
                        qApp->exit(9);
                        return;
                    }
282
283
284
285
286
                } else {
                    cerr << "Unable to parse arguments" << op << endl;
                    qApp->exit(2);
                    return;
                }
Sebastian Kügler's avatar
Sebastian Kügler committed
287
288
289
290
291
            }
        }
    }
}

292
293
294
295
296
297
void Doctor::configReceived(KScreen::ConfigOperation *op)
{
    m_config = op->config();

    if (m_parser->isSet("json")) {
        showJson();
298
299
300
301
302
        qApp->quit();
    }
    if (m_parser->isSet("outputs")) {
        showOutputs();
        qApp->quit();
303
    }
Sebastian Kügler's avatar
Sebastian Kügler committed
304
305

    parsePositionalArgs();
306
307
308
309

    if (m_changed) {
        applyConfig();
        m_changed = false;
Sebastian Kügler's avatar
Sebastian Kügler committed
310
    }
311
312
313
314
}

int Doctor::outputCount() const
{
Sebastian Kügler's avatar
Sebastian Kügler committed
315
    if (!m_config) {
316
        qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
Sebastian Kügler's avatar
Sebastian Kügler committed
317
318
319
        return 0;
    }
    return m_config->outputs().count();
320
321
}

322
void Doctor::showOutputs() const
323
{
324
    if (!m_config) {
325
        qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
326
327
328
        return;
    }

329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
    QHash<KScreen::Output::Type, QString> typeString;
    typeString[KScreen::Output::Unknown] = QStringLiteral("Unknown");
    typeString[KScreen::Output::VGA] = QStringLiteral("VGA");
    typeString[KScreen::Output::DVI] = QStringLiteral("DVI");
    typeString[KScreen::Output::DVII] = QStringLiteral("DVII");
    typeString[KScreen::Output::DVIA] = QStringLiteral("DVIA");
    typeString[KScreen::Output::DVID] = QStringLiteral("DVID");
    typeString[KScreen::Output::HDMI] = QStringLiteral("HDMI");
    typeString[KScreen::Output::Panel] = QStringLiteral("Panel");
    typeString[KScreen::Output::TV] = QStringLiteral("TV");
    typeString[KScreen::Output::TVComposite] = QStringLiteral("TVComposite");
    typeString[KScreen::Output::TVSVideo] = QStringLiteral("TVSVideo");
    typeString[KScreen::Output::TVComponent] = QStringLiteral("TVComponent");
    typeString[KScreen::Output::TVSCART] = QStringLiteral("TVSCART");
    typeString[KScreen::Output::TVC4] = QStringLiteral("TVC4");
    typeString[KScreen::Output::DisplayPort] = QStringLiteral("DisplayPort");

346
    Q_FOREACH (const auto &output, m_config->outputs()) {
Sebastian Kügler's avatar
Sebastian Kügler committed
347
348
        cout << green << "Output: " << cr << output->id() << " " << output->name();
        cout << " " << (output->isEnabled() ? green + "enabled" : red + "disabled");
349
        cout << " " << (output->isConnected() ? green + "connected" : red + "disconnected");
350
        cout << " " << (output->isPrimary() ? green + "primary" : QString());
351
352
        auto _type = typeString[output->type()];
        cout << " " << yellow << (_type.isEmpty() ? "UnmappedOutputType" : _type);
Sebastian Kügler's avatar
Sebastian Kügler committed
353
        cout << blue << " Modes: " << cr;
354
        Q_FOREACH (auto mode, output->modes()) {
355
356
357
358
359
360
361
362
363
364
            auto name = QString("%1x%2@%3").arg(QString::number(mode->size().width()),
                                                QString::number(mode->size().height()),
                                                QString::number(qRound(mode->refreshRate())));
            if (mode == output->currentMode()) {
                name = green + name + "*" + cr;
            }
            if (mode == output->preferredMode()) {
                name = name + "!";
            }
            cout << mode->id() << ":" << name << " ";
365
        }
Sebastian Kügler's avatar
Sebastian Kügler committed
366
        const auto g = output->geometry();
367
368
369
        cout << yellow << "Geometry: " << cr << g.x() << "," << g.y() << " " << g.width() << "x" << g.height() << " ";
        cout << yellow << "Scale: " << cr << output->scale() << " ";
        cout << yellow << "Rotation: " << cr << output->rotation() << " ";
370
371
        cout << endl;
    }
372
373
}

374
void Doctor::showJson() const
375
376
377
378
379
{
    QJsonDocument doc(KScreen::ConfigSerializer::serializeConfig(m_config));
    cout << doc.toJson(QJsonDocument::Indented);
}

Sebastian Kügler's avatar
Sebastian Kügler committed
380
bool Doctor::setEnabled(int id, bool enabled = true)
Sebastian Kügler's avatar
Sebastian Kügler committed
381
{
382
    if (!m_config) {
383
        qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
Sebastian Kügler's avatar
Sebastian Kügler committed
384
        return false;
385
386
387
388
    }

    Q_FOREACH (const auto &output, m_config->outputs()) {
        if (output->id() == id) {
Sebastian Kügler's avatar
Sebastian Kügler committed
389
            cout << (enabled ? "Enabling " : "Disabling ") << "output " << id << endl;
390
391
            output->setEnabled(enabled);
            m_changed = true;
Sebastian Kügler's avatar
Sebastian Kügler committed
392
            return true;
393
394
        }
    }
395
396
    cerr << "Output with id " << id << " not found." << endl;
    qApp->exit(8);
Sebastian Kügler's avatar
Sebastian Kügler committed
397
    return false;
Sebastian Kügler's avatar
Sebastian Kügler committed
398
399
}

Sebastian Kügler's avatar
Sebastian Kügler committed
400
bool Doctor::setPosition(int id, const QPoint &pos)
Sebastian Kügler's avatar
Sebastian Kügler committed
401
402
{
    if (!m_config) {
403
        qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
Sebastian Kügler's avatar
Sebastian Kügler committed
404
        return false;
Sebastian Kügler's avatar
Sebastian Kügler committed
405
406
407
408
    }

    Q_FOREACH (const auto &output, m_config->outputs()) {
        if (output->id() == id) {
409
            qCDebug(KSCREEN_DOCTOR) << "Set output position" << pos;
Sebastian Kügler's avatar
Sebastian Kügler committed
410
411
            output->setPos(pos);
            m_changed = true;
Sebastian Kügler's avatar
Sebastian Kügler committed
412
            return true;
Sebastian Kügler's avatar
Sebastian Kügler committed
413
414
415
        }
    }
    cout << "Output with id " << id << " not found." << endl;
Sebastian Kügler's avatar
Sebastian Kügler committed
416
    return false;
Sebastian Kügler's avatar
Sebastian Kügler committed
417
418
}

419
420
421
bool Doctor::setMode(int id, const QString &mode_id)
{
    if (!m_config) {
422
        qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
423
424
425
426
427
428
429
        return false;
    }

    Q_FOREACH (const auto &output, m_config->outputs()) {
        if (output->id() == id) {
            // find mode
            Q_FOREACH (const KScreen::ModePtr mode, output->modes()) {
430
431
432
433
434
                auto name = QString("%1x%2@%3").arg(QString::number(mode->size().width()),
                                    QString::number(mode->size().height()),
                                    QString::number(qRound(mode->refreshRate())));
                if (mode->id() == mode_id || name == mode_id) {
                    qCDebug(KSCREEN_DOCTOR) << "Taddaaa! Found mode" << mode->id() << name;
435
436
437
                    output->setCurrentModeId(mode->id());
                    m_changed = true;
                    return true;
438
                }
439
440
441
            }
        }
    }
442
    cout << "Output mode " << mode_id << " not found." << endl;
443
444
445
    return false;
}

Sebastian Kügler's avatar
Sebastian Kügler committed
446
bool Doctor::setScale(int id, qreal scale)
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
{
    if (!m_config) {
        qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
        return false;
    }

    Q_FOREACH (const auto &output, m_config->outputs()) {
        if (output->id() == id) {
            output->setScale(scale);
            m_changed = true;
            return true;
        }
    }
    cout << "Output scale " << id << " invalid." << endl;
    return false;
}

464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
bool Doctor::setRotation(int id, KScreen::Output::Rotation rot)
{
    if (!m_config) {
        qCWarning(KSCREEN_DOCTOR) << "Invalid config.";
        return false;
    }

    Q_FOREACH (const auto &output, m_config->outputs()) {
        if (output->id() == id) {
            output->setRotation(rot);
            m_changed = true;
            return true;
        }
    }
    cout << "Output rotation " << id << " invalid." << endl;
    return false;
}

482
void Doctor::applyConfig()
Sebastian Kügler's avatar
Sebastian Kügler committed
483
{
Sebastian Kügler's avatar
Sebastian Kügler committed
484
485
486
    if (!m_changed) {
        return;
    }
487
488
    auto setop = new SetConfigOperation(m_config, this);
    setop->exec();
489
    qCDebug(KSCREEN_DOCTOR) << "setop exec returned" << m_config;
Sebastian Kügler's avatar
Sebastian Kügler committed
490
    qApp->exit(0);
Sebastian Kügler's avatar
Sebastian Kügler committed
491
}