xrandrconfig.cpp 25.1 KB
Newer Older
1
2
/*************************************************************************************
 *  Copyright (C) 2012 by Alejandro Fiestas Olivares <afiestas@kde.org>              *
3
 *  Copyright (C) 2012 - 2015 by Daniel Vrátil <dvratil@redhat.com>                  *
4
 *                                                                                   *
5
6
7
8
 *  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.               *
9
 *                                                                                   *
10
 *  This library is distributed in the hope that it will be useful,                  *
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
12
13
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                *
 *  Lesser General Public License for more details.                                  *
14
 *                                                                                   *
15
16
17
 *  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       *
18
 *************************************************************************************/
Daniel Vrátil's avatar
Daniel Vrátil committed
19
20
21
22
23
#include "xrandrconfig.h"
#include "xrandrscreen.h"
#include "xrandr.h"
#include "xrandrmode.h"
#include "xrandroutput.h"
24
#include "xrandrcrtc.h"
Daniel Vrátil's avatar
Daniel Vrátil committed
25
26
#include "config.h"
#include "output.h"
27
#include "edid.h"
Daniel Vrátil's avatar
Daniel Vrátil committed
28

29
30
#include "../xcbwrapper.h"

Daniel Vrátil's avatar
Daniel Vrátil committed
31
#include <QX11Info>
Daniel Vrátil's avatar
Daniel Vrátil committed
32
#include <QRect>
33
#include <QScopedPointer>
Daniel Vrátil's avatar
Daniel Vrátil committed
34
35
36

using namespace KScreen;

37
38
XRandRConfig::XRandRConfig()
    : QObject()
39
    , m_screen(nullptr)
Daniel Vrátil's avatar
Daniel Vrátil committed
40
{
41
    m_screen = new XRandRScreen(this);
Daniel Vrátil's avatar
Daniel Vrátil committed
42

43
    XCB::ScopedPointer<xcb_randr_get_screen_resources_reply_t> resources(XRandR::screenResources());
Roman Gilg's avatar
Roman Gilg committed
44

45
    xcb_randr_crtc_t *crtcs = xcb_randr_get_screen_resources_crtcs(resources.data());
Roman Gilg's avatar
Roman Gilg committed
46
47
    const int crtcsCount = xcb_randr_get_screen_resources_crtcs_length(resources.data());
    for (int i = 0; i < crtcsCount; ++i) {
48
        addNewCrtc(crtcs[i]);
49
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
50

51
    xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_outputs(resources.data());
Roman Gilg's avatar
Roman Gilg committed
52
53
    const int outputsCount = xcb_randr_get_screen_resources_outputs_length(resources.data());
    for (int i = 0; i < outputsCount; ++i) {
54
        addNewOutput(outputs[i]);
55
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
56
57
58
59
}

XRandRConfig::~XRandRConfig()
{
60
61
62
    qDeleteAll(m_outputs);
    qDeleteAll(m_crtcs);
    delete m_screen;
Daniel Vrátil's avatar
Daniel Vrátil committed
63
64
}

65
XRandROutput::Map XRandRConfig::outputs() const
Daniel Vrátil's avatar
Daniel Vrátil committed
66
{
67
68
    return m_outputs;
}
69

70
XRandROutput* XRandRConfig::output(xcb_randr_output_t output) const
71
72
73
{
    return m_outputs[output];
}
Daniel Vrátil's avatar
Daniel Vrátil committed
74

75
76
77
XRandRCrtc::Map XRandRConfig::crtcs() const
{
    return m_crtcs;
Daniel Vrátil's avatar
Daniel Vrátil committed
78
79
}

80
XRandRCrtc* XRandRConfig::crtc(xcb_randr_crtc_t crtc) const
Daniel Vrátil's avatar
Daniel Vrátil committed
81
{
82
    return m_crtcs[crtc];
Daniel Vrátil's avatar
Daniel Vrátil committed
83
84
}

85
XRandRScreen* XRandRConfig::screen() const
Àlex Fiestas's avatar
Àlex Fiestas committed
86
{
87
    return m_screen;
Àlex Fiestas's avatar
Àlex Fiestas committed
88
89
}

90

91
void XRandRConfig::addNewOutput(xcb_randr_output_t id)
92
{
93
94
95
    XRandROutput *xOutput = new XRandROutput(id, this);
    m_outputs.insert(id, xOutput);
}
96

97
void XRandRConfig::addNewCrtc(xcb_randr_crtc_t crtc)
98
99
{
    m_crtcs.insert(crtc, new XRandRCrtc(crtc, this));
100
101
}

102
void XRandRConfig::removeOutput(xcb_randr_output_t id)
103
{
104
    delete m_outputs.take(id);
105
106
}

107
KScreen::ConfigPtr XRandRConfig::toKScreenConfig() const
Daniel Vrátil's avatar
Daniel Vrátil committed
108
{
109
    KScreen::ConfigPtr config(new KScreen::Config);
Roman Gilg's avatar
Roman Gilg committed
110

111
112
    const Config::Features features = Config::Feature::Writable | Config::Feature::PrimaryDisplay |
                                      Config::Feature::OutputReplication;
113
    config->setSupportedFeatures(features);
Roman Gilg's avatar
Roman Gilg committed
114

Daniel Vrátil's avatar
Daniel Vrátil committed
115
116
    KScreen::OutputList kscreenOutputs;

Daniel Vrátil's avatar
Daniel Vrátil committed
117
    for (auto iter = m_outputs.constBegin(); iter != m_outputs.constEnd(); ++iter) {
118
        KScreen::OutputPtr kscreenOutput = (*iter)->toKScreenOutput();
Daniel Vrátil's avatar
Daniel Vrátil committed
119
120
        kscreenOutputs.insert(kscreenOutput->id(), kscreenOutput);
    }
Roman Gilg's avatar
Roman Gilg committed
121

Daniel Vrátil's avatar
Daniel Vrátil committed
122
    config->setOutputs(kscreenOutputs);
123
    config->setScreen(m_screen->toKScreenScreen());
Daniel Vrátil's avatar
Daniel Vrátil committed
124
125
126
127

    return config;
}

128
void XRandRConfig::applyKScreenConfig(const KScreen::ConfigPtr &config)
Daniel Vrátil's avatar
Daniel Vrátil committed
129
{
130
    const KScreen::OutputList kscreenOutputs = config->outputs();
Roman Gilg's avatar
Roman Gilg committed
131

132
    const QSize newScreenSize = screenSize(config);
133
    const QSize currentScreenSize = m_screen->currentSize();
Roman Gilg's avatar
Roman Gilg committed
134

135
136
137
138
139
140
141
    // When the current screen configuration is bigger than the new size (like
    // when rotating an output), the XSetScreenSize can fail or apply the smaller
    // size only partially, because we apply the size (we have to) before the
    // output changes. To prevent all kinds of weird screen sizes from happening,
    // we initially set such screen size, that it can take the current as well
    // as the new configuration, then we apply the output changes, and finally then
    // (if necessary) we reduce the screen size to fix the new configuration precisely.
Roman Gilg's avatar
Roman Gilg committed
142
143
144
145
146
    const QSize intermediateScreenSize = QSize(qMax(newScreenSize.width(),
                                                    currentScreenSize.width()),
                                               qMax(newScreenSize.height(),
                                                    currentScreenSize.height()));

147
    int neededCrtcs = 0;
148
149
    xcb_randr_output_t primaryOutput = 0;
    xcb_randr_output_t oldPrimaryOutput = 0;
150

Roman Gilg's avatar
Roman Gilg committed
151
    for (const XRandROutput *xrandrOutput : m_outputs) {
152
        if (xrandrOutput->isPrimary()) {
153
            oldPrimaryOutput = xrandrOutput->id();
154
155
156
157
            break;
        }
    }

158
    KScreen::OutputList toDisable, toEnable, toChange;
Roman Gilg's avatar
Roman Gilg committed
159
160

    for (const KScreen::OutputPtr &kscreenOutput : kscreenOutputs) {
161
        xcb_randr_output_t outputId = kscreenOutput->id();
162
        XRandROutput *currentOutput = output(outputId);
163
        //Only set the output as primary if it is enabled.
164
165
        if (kscreenOutput->isPrimary() && kscreenOutput->isEnabled()) {
            primaryOutput = outputId;
Daniel Vrátil's avatar
Daniel Vrátil committed
166
167
        }

168
        const bool currentEnabled = currentOutput->isEnabled();
Roman Gilg's avatar
Roman Gilg committed
169

170
171
        if (!kscreenOutput->isEnabled() && currentEnabled) {
            toDisable.insert(outputId, kscreenOutput);
Daniel Vrátil's avatar
Daniel Vrátil committed
172
            continue;
173
174
175
        } else if (kscreenOutput->isEnabled() && !currentEnabled) {
            toEnable.insert(outputId, kscreenOutput);
            ++neededCrtcs;
Daniel Vrátil's avatar
Daniel Vrátil committed
176
            continue;
177
        } else if (!kscreenOutput->isEnabled() && !currentEnabled) {
Daniel Vrátil's avatar
Daniel Vrátil committed
178
179
180
            continue;
        }

181
        ++neededCrtcs;
Daniel Vrátil's avatar
Daniel Vrátil committed
182

183
184
185
        if (kscreenOutput->currentModeId() != currentOutput->currentModeId()) {
            if (!toChange.contains(outputId)) {
                toChange.insert(outputId, kscreenOutput);
Daniel Vrátil's avatar
Daniel Vrátil committed
186
187
188
            }
        }

189
190
191
        if (kscreenOutput->pos() != currentOutput->position()) {
            if (!toChange.contains(outputId)) {
                toChange.insert(outputId, kscreenOutput);
Daniel Vrátil's avatar
Daniel Vrátil committed
192
193
194
            }
        }

195
196
197
        if (kscreenOutput->rotation() != currentOutput->rotation()) {
            if (!toChange.contains(outputId)) {
                toChange.insert(outputId, kscreenOutput);
198
199
200
            }
        }

201
202
203
204
205
206
        if (kscreenOutput->explicitLogicalSize() != currentOutput->logicalSize()) {
            if (!toChange.contains(outputId)) {
                toChange.insert(outputId, kscreenOutput);
            }
        }

Roman Gilg's avatar
Roman Gilg committed
207
208
        XRandRMode *currentMode = currentOutput->modes().value(
                                    kscreenOutput->currentModeId().toInt());
209
210
211
212
213
        // For some reason, in some environments currentMode is null
        // which doesn't make sense because it is the *current* mode...
        // Since we haven't been able to figure out the reason why
        // this happens, we are adding this debug code to try to
        // figure out how this happened.
214
        if (!currentMode) {
215
            qWarning() << "Current mode is null:"
216
217
218
                       << "ModeId:" << currentOutput->currentModeId()
                       << "Mode: " << currentOutput->currentMode()
                       << "Output: " << currentOutput->id();
219
220
            printConfig(config);
            printInternalCond();
221
            continue;
222
        }
223

Roman Gilg's avatar
Roman Gilg committed
224
        // When the output would not fit into new screen size, we need to disable and reposition it.
225
226
227
        const QRect geom = kscreenOutput->geometry();
        if (geom.right() > newScreenSize.width() || geom.bottom() > newScreenSize.height()) {
            if (!toDisable.contains(outputId)) {
Roman Gilg's avatar
Roman Gilg committed
228
229
230
                qCDebug(KSCREEN_XRANDR)
                        << "The new output would not fit into screen - new geometry: " << geom
                        << ", new screen size:" << newScreenSize;
231
                toDisable.insert(outputId, kscreenOutput);
Daniel Vrátil's avatar
Daniel Vrátil committed
232
233
            }
        }
Roman Gilg's avatar
Roman Gilg committed
234
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
235

236
237
238
    const KScreen::ScreenPtr kscreenScreen = config->screen();
    if (newScreenSize.width() > kscreenScreen->maxSize().width() ||
        newScreenSize.height() > kscreenScreen->maxSize().height()) {
Roman Gilg's avatar
Roman Gilg committed
239
240
        qCDebug(KSCREEN_XRANDR) << "The new screen size is too big - requested: "
                                << newScreenSize << ", maximum: " << kscreenScreen->maxSize();
241
        return;
Daniel Vrátil's avatar
Daniel Vrátil committed
242
243
    }

244
    qCDebug(KSCREEN_XRANDR) << "Needed CRTCs: " << neededCrtcs;
Roman Gilg's avatar
Roman Gilg committed
245
246
247
248

    XCB::ScopedPointer<xcb_randr_get_screen_resources_reply_t>
            screenResources(XRandR::screenResources());

249
    if (neededCrtcs > screenResources->num_crtcs) {
Roman Gilg's avatar
Roman Gilg committed
250
251
        qCDebug(KSCREEN_XRANDR) << "We need more CRTCs than we have available - requested: "
                                << neededCrtcs << ", available: " << screenResources->num_crtcs;
252
            return;
Daniel Vrátil's avatar
Daniel Vrátil committed
253
254
    }

Roman Gilg's avatar
Roman Gilg committed
255
256
    qCDebug(KSCREEN_XRANDR) << "Actions to perform:" << "\n"
                            << "\tPrimary Output:" << (primaryOutput != oldPrimaryOutput);
257
    if (primaryOutput != oldPrimaryOutput) {
Roman Gilg's avatar
Roman Gilg committed
258
259
        qCDebug(KSCREEN_XRANDR) << "\t\tOld:" << oldPrimaryOutput << "\n"
                                << "\t\tNew:" << primaryOutput;
260
    }
Roman Gilg's avatar
Roman Gilg committed
261

262
263
    qCDebug(KSCREEN_XRANDR) << "\tChange Screen Size:" << (newScreenSize != currentScreenSize);
    if (newScreenSize != currentScreenSize) {
Roman Gilg's avatar
Roman Gilg committed
264
265
266
        qCDebug(KSCREEN_XRANDR) << "\t\tOld:" << currentScreenSize << "\n"
                                << "\t\tIntermediate:" << intermediateScreenSize << "\n"
                                << "\t\tNew:" << newScreenSize;
267
    }
Roman Gilg's avatar
Roman Gilg committed
268

269
    qCDebug(KSCREEN_XRANDR) << "\tDisable outputs:" << !toDisable.isEmpty();
270
    if (!toDisable.isEmpty()) {
271
        qCDebug(KSCREEN_XRANDR) << "\t\t" << toDisable.keys();
272
    }
Roman Gilg's avatar
Roman Gilg committed
273

274
    qCDebug(KSCREEN_XRANDR) << "\tChange outputs:" << !toChange.isEmpty();
275
    if (!toChange.isEmpty()) {
276
        qCDebug(KSCREEN_XRANDR) << "\t\t" << toChange.keys();
277
    }
Roman Gilg's avatar
Roman Gilg committed
278

279
    qCDebug(KSCREEN_XRANDR) << "\tEnable outputs:" << !toEnable.isEmpty();
280
    if (!toEnable.isEmpty()) {
281
        qCDebug(KSCREEN_XRANDR) << "\t\t" << toEnable.keys();
282
283
    }

284
285
    // Grab the server so that no-one else can do changes to XRandR and to block
    // change notifications until we are done
286
    XCB::GrabServer grabber;
Daniel Vrátil's avatar
Daniel Vrátil committed
287

288
    //If there is nothing to do, not even bother
Roman Gilg's avatar
Roman Gilg committed
289
    if (oldPrimaryOutput == primaryOutput && toDisable.isEmpty() &&
290
            toEnable.isEmpty() && toChange.isEmpty()) {
291
292
        if (newScreenSize != currentScreenSize) {
            setScreenSize(newScreenSize);
293
        }
294
295
296
        return;
    }

Roman Gilg's avatar
Roman Gilg committed
297
    for (const KScreen::OutputPtr &output : toDisable) {
Daniel Vrátil's avatar
Daniel Vrátil committed
298
299
300
        disableOutput(output);
    }

301
302
    if (intermediateScreenSize != currentScreenSize) {
        setScreenSize(intermediateScreenSize);
303
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
304

305
    bool forceScreenSizeUpdate = false;
Roman Gilg's avatar
Roman Gilg committed
306
307

    for (const KScreen::OutputPtr &output : toChange) {
308
        if (!changeOutput(output)) {
309
310
311
            /* If we disabled the output before changing it and XRandR failed
             * to re-enable it, then update screen size too */
            if (toDisable.contains(output->id())) {
312
                output->setEnabled(false);
313
                qCDebug(KSCREEN_XRANDR) << "Output failed to change: " << output->name();
314
315
316
317
318
                forceScreenSizeUpdate = true;
            }
        }
    }

Roman Gilg's avatar
Roman Gilg committed
319
    for (const KScreen::OutputPtr &output : toEnable) {
320
        if (!enableOutput(output)) {
321
            qCDebug(KSCREEN_XRANDR) << "Output failed to be Enabled: " << output->name();
322
323
324
325
            forceScreenSizeUpdate = true;
        }
    }

326
    if (oldPrimaryOutput != primaryOutput) {
327
328
        setPrimaryOutput(primaryOutput);
    }
329

330
    if (forceScreenSizeUpdate || intermediateScreenSize != newScreenSize) {
331
        QSize newSize = newScreenSize;
332
333
334
335
        if (forceScreenSizeUpdate) {
            newSize = screenSize(config);
            qCDebug(KSCREEN_XRANDR) << "Forced to change screen size: " << newSize;
        }
336
        setScreenSize(newSize);
Daniel Vrátil's avatar
Daniel Vrátil committed
337
338
339
    }
}

340
void XRandRConfig::printConfig(const ConfigPtr &config) const
341
{
342
    qCDebug(KSCREEN_XRANDR) << "KScreen version:" /*<< LIBKSCREEN_VERSION*/;
343
344

    if (!config) {
345
        qCDebug(KSCREEN_XRANDR) << "Config is invalid";
346
347
348
        return;
    }
    if (!config->screen()) {
349
        qCDebug(KSCREEN_XRANDR) << "No screen in the configuration, broken backend";
350
351
352
        return;
    }

Roman Gilg's avatar
Roman Gilg committed
353
354
355
356
357
358
359
360
361
362
363
364
    qCDebug(KSCREEN_XRANDR) << "Screen:" << "\n"
                            << "\tmaxSize:" << config->screen()->maxSize() << "\n"
                            << "\tminSize:" << config->screen()->minSize() << "\n"
                            << "\tcurrentSize:" << config->screen()->currentSize();

    const OutputList outputs = config->outputs();
    for (const OutputPtr &output : outputs) {
        qCDebug(KSCREEN_XRANDR) << "\n-----------------------------------------------------\n" << "\n"
                                << "Id: " << output->id() << "\n"
                                << "Name: " << output->name() << "\n"
                                << "Type: " << output->type() << "\n"
                                << "Connected: " << output->isConnected();
365
366
367
368

        if (!output->isConnected()) {
            continue;
        }
Roman Gilg's avatar
Roman Gilg committed
369
370
371
372
373
374

        qCDebug(KSCREEN_XRANDR) << "Enabled: " << output->isEnabled() << "\n"
                                << "Primary: " << output->isPrimary() << "\n"
                                << "Rotation: " << output->rotation() << "\n"
                                << "Pos: " << output->pos() << "\n"
                                << "MMSize: " << output->sizeMm();
375
        if (output->currentMode()) {
376
            qCDebug(KSCREEN_XRANDR) << "Size: " << output->currentMode()->size();
377
        }
Roman Gilg's avatar
Roman Gilg committed
378
379
380
381
382
383
384
385
386

        qCDebug(KSCREEN_XRANDR) << "Clones: "
                                << (output->clones().isEmpty() ? QStringLiteral("None") :
                                                        QString::number(output->clones().count()))
                                << "\n"
                                << "Mode: " << output->currentModeId() << "\n"
                                << "Preferred Mode: " << output->preferredModeId() << "\n"
                                << "Preferred modes: " << output->preferredModes() << "\n"
                                << "Modes: ";
387
388

        ModeList modes = output->modes();
Roman Gilg's avatar
Roman Gilg committed
389
390
391
        for (const ModePtr &mode : modes) {
            qCDebug(KSCREEN_XRANDR) << "\t" << mode->id() << "  " << mode->name() << " "
                                    << mode->size() << " " << mode->refreshRate();
392
393
394
        }

        Edid* edid = output->edid();
395
        qCDebug(KSCREEN_XRANDR) << "EDID Info: ";
396
        if (edid && edid->isValid()) {
Roman Gilg's avatar
Roman Gilg committed
397
398
399
400
401
402
403
404
405
406
407
408
409
            qCDebug(KSCREEN_XRANDR) << "\tDevice ID: " << edid->deviceId() << "\n"
                                    << "\tName: " << edid->name() << "\n"
                                    << "\tVendor: " << edid->vendor() << "\n"
                                    << "\tSerial: " << edid->serial() << "\n"
                                    << "\tEISA ID: " << edid->eisaId() << "\n"
                                    << "\tHash: " << edid->hash() << "\n"
                                    << "\tWidth: " << edid->width() << "\n"
                                    << "\tHeight: " << edid->height() << "\n"
                                    << "\tGamma: " << edid->gamma() << "\n"
                                    << "\tRed: " << edid->red() << "\n"
                                    << "\tGreen: " << edid->green() << "\n"
                                    << "\tBlue: " << edid->blue() << "\n"
                                    << "\tWhite: " << edid->white();
410
        } else {
411
            qCDebug(KSCREEN_XRANDR) << "\tUnavailable";
412
413
414
415
416
417
        }
    }
}

void XRandRConfig::printInternalCond() const
{
418
    qCDebug(KSCREEN_XRANDR) << "Internal config in xrandr";
Roman Gilg's avatar
Roman Gilg committed
419
420
421
422
423
424
425
    for (const XRandROutput *output : m_outputs) {
        qCDebug(KSCREEN_XRANDR) << "Id: " << output->id() << "\n"
                                << "Current Mode: " << output->currentMode() << "\n"
                                << "Current mode id: " << output->currentModeId() << "\n"
                                << "Connected: " << output->isConnected() << "\n"
                                << "Enabled: " << output->isEnabled() << "\n"
                                << "Primary: " << output->isPrimary();
426
427
428
        if (!output->isEnabled()) {
            continue;
        }
Roman Gilg's avatar
Roman Gilg committed
429

430
        XRandRMode::Map modes = output->modes();
Roman Gilg's avatar
Roman Gilg committed
431
432
433
434
        for (const XRandRMode *mode : modes) {
            qCDebug(KSCREEN_XRANDR) << "\t" << mode->id() << "\n"
                                    << "\t" << mode->name() << "\n"
                                    << "\t" << mode->size() << mode->refreshRate();
435
436
437
        }
    }
}
Daniel Vrátil's avatar
Daniel Vrátil committed
438

439
QSize XRandRConfig::screenSize(const KScreen::ConfigPtr &config) const
Daniel Vrátil's avatar
Daniel Vrátil committed
440
{
Daniel Vrátil's avatar
Daniel Vrátil committed
441
    QRect rect;
Roman Gilg's avatar
Roman Gilg committed
442
    for (const KScreen::OutputPtr &output : config->outputs()) {
443
        if (!output->isConnected() || !output->isEnabled()) {
Daniel Vrátil's avatar
Daniel Vrátil committed
444
445
446
            continue;
        }

Daniel Vrátil's avatar
Daniel Vrátil committed
447
        const ModePtr currentMode = output->currentMode();
Daniel Vrátil's avatar
Daniel Vrátil committed
448
        if (!currentMode) {
449
            qCDebug(KSCREEN_XRANDR) << "Output: " << output->name() << " has no current Mode!";
Daniel Vrátil's avatar
Daniel Vrátil committed
450
451
            continue;
        }
Daniel Vrátil's avatar
Daniel Vrátil committed
452

453
454
        const QRect outputGeom = output->geometry();
        rect = rect.united(outputGeom);
Daniel Vrátil's avatar
Daniel Vrátil committed
455
456
    }

Daniel Vrátil's avatar
Daniel Vrátil committed
457
    const QSize size = QSize(rect.width(), rect.height());
458
    qCDebug(KSCREEN_XRANDR) << "Requested screen size is" << size;
Daniel Vrátil's avatar
Daniel Vrátil committed
459
460
461
    return size;
}

462
bool XRandRConfig::setScreenSize(const QSize &size) const
Daniel Vrátil's avatar
Daniel Vrátil committed
463
{
Roman Gilg's avatar
Roman Gilg committed
464
465
466
467
    const double dpi = 25.4 * XRandR::screen()->height_in_pixels
                            / XRandR::screen()->height_in_millimeters;
    const int widthMM =  (25.4 * size.width()) / dpi;
    const int heightMM = (25.4 * size.height()) / dpi;
Daniel Vrátil's avatar
Daniel Vrátil committed
468

Roman Gilg's avatar
Roman Gilg committed
469
470
471
472
    qCDebug(KSCREEN_XRANDR) << "RRSetScreenSize" << "\n"
                            << "\tDPI:" << dpi << "\n"
                            << "\tSize:" << size << "\n"
                            << "\tSizeMM:" << QSize(widthMM, heightMM);
473
474
475

    xcb_randr_set_screen_size(XCB::connection(), XRandR::rootWindow(),
                              size.width(), size.height(), widthMM, heightMM);
476
    m_screen->update(size);
Daniel Vrátil's avatar
Daniel Vrátil committed
477
478
479
    return true;
}

480
void XRandRConfig::setPrimaryOutput(xcb_randr_output_t outputId) const
Daniel Vrátil's avatar
Daniel Vrátil committed
481
{
Roman Gilg's avatar
Roman Gilg committed
482
483
    qCDebug(KSCREEN_XRANDR) << "RRSetOutputPrimary" << "\n"
                            << "\tNew primary:" << outputId;
484
    xcb_randr_set_output_primary(XCB::connection(), XRandR::rootWindow(), outputId);
485
486
487
488

    for (XRandROutput *output : m_outputs) {
        output->setIsPrimary(output->id() == outputId);
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
489
490
}

491
bool XRandRConfig::disableOutput(const OutputPtr &kscreenOutput) const
Daniel Vrátil's avatar
Daniel Vrátil committed
492
{
493
494
495
    XRandROutput *xOutput = output(kscreenOutput->id());
    Q_ASSERT(xOutput);
    Q_ASSERT(xOutput->crtc());
Roman Gilg's avatar
Roman Gilg committed
496

497
498
499
500
501
    if (!xOutput->crtc()) {
        qCWarning(KSCREEN_XRANDR) << "Attempting to disable output without CRTC, wth?";
        return false;
    }

502
    const xcb_randr_crtc_t crtc = xOutput->crtc()->crtc();
503

Roman Gilg's avatar
Roman Gilg committed
504
505
    qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (disable output)" << "\n"
                            << "\tCRTC:" << crtc;
506

507
508
509
510
511
    auto cookie = xcb_randr_set_crtc_config(XCB::connection(), crtc,
            XCB_CURRENT_TIME, XCB_CURRENT_TIME,
            0, 0,
            XCB_NONE,
            XCB_RANDR_ROTATION_ROTATE_0,
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
512
            0, nullptr);
Roman Gilg's avatar
Roman Gilg committed
513
514
515
516

    XCB::ScopedPointer<xcb_randr_set_crtc_config_reply_t>
            reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, nullptr));

517
518
519
520
521
    if (!reply) {
        qCDebug(KSCREEN_XRANDR) << "\tResult: unknown (error)";
        return false;
    }
    qCDebug(KSCREEN_XRANDR) << "\tResult:" << reply->status;
522
523
524

    // Update the cached output now, otherwise we get RRNotify_CrtcChange notification
    // for an outdated output, which can lead to a crash.
525
    if (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS) {
Roman Gilg's avatar
Roman Gilg committed
526
527
528
        xOutput->update(XCB_NONE, XCB_NONE,
                        xOutput->isConnected() ? XCB_RANDR_CONNECTION_CONNECTED :
                                                 XCB_RANDR_CONNECTION_DISCONNECTED,
529
                        kscreenOutput->isPrimary());
530
    }
531
    return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS);
Daniel Vrátil's avatar
Daniel Vrátil committed
532
533
}

534
bool XRandRConfig::enableOutput(const OutputPtr &kscreenOutput) const
Daniel Vrátil's avatar
Daniel Vrátil committed
535
{
536
    XRandRCrtc *freeCrtc = nullptr;
537
    qCDebug(KSCREEN_XRANDR) << m_crtcs;
Roman Gilg's avatar
Roman Gilg committed
538
539

    for (XRandRCrtc *crtc : m_crtcs) {
540
        crtc->update();
Roman Gilg's avatar
Roman Gilg committed
541
542
543
544
545
546
547
        qCDebug(KSCREEN_XRANDR) << "Testing CRTC" << crtc->crtc() << "\n"
                                << "\tFree:" << crtc->isFree() << "\n"
                                << "\tMode:" << crtc->mode() << "\n"
                                << "\tPossible outputs:" << crtc->possibleOutputs() << "\n"
                                << "\tConnected outputs:" << crtc->outputs() << "\n"
                                << "\tGeometry:" << crtc->geometry();

548
549
550
551
552
        if (crtc->isFree() && crtc->possibleOutputs().contains(kscreenOutput->id())) {
            freeCrtc = crtc;
            break;
        }
    }
Roman Gilg's avatar
Roman Gilg committed
553

554
555
556
557
558
    if (!freeCrtc) {
        qCWarning(KSCREEN_XRANDR) << "Failed to get free CRTC for output" << kscreenOutput->id();
        return false;
    }

559
    XRandROutput *xOutput = output(kscreenOutput->id());
Roman Gilg's avatar
Roman Gilg committed
560
561
    const int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() :
                                                      kscreenOutput->preferredModeId().toInt();
Roman Gilg's avatar
Roman Gilg committed
562
    xOutput->updateLogicalSize(kscreenOutput, freeCrtc);
563

Roman Gilg's avatar
Roman Gilg committed
564
565
566
567
568
569
570
571
    qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (enable output)" << "\n"
                            << "\tOutput:" << kscreenOutput->id() << "(" << kscreenOutput->name()
                            << ")" << "\n"
                            << "\tNew CRTC:" << freeCrtc->crtc() << "\n"
                            << "\tPos:" << kscreenOutput->pos() << "\n"
                            << "\tMode:" << kscreenOutput->currentMode()
                            << "Preferred:" << kscreenOutput->preferredModeId() << "\n"
                            << "\tRotation:" << kscreenOutput->rotation();
572

573
574
575
    if (!sendConfig(kscreenOutput, freeCrtc)) {
        return false;
    }
Roman Gilg's avatar
Roman Gilg committed
576

577
578
    xOutput->update(freeCrtc->crtc(), modeId, XCB_RANDR_CONNECTION_CONNECTED,
                    kscreenOutput->isPrimary());
579
580
    return true;
}
Roman Gilg's avatar
Roman Gilg committed
581

582
583
584
585
586
587
588
589
590
bool XRandRConfig::changeOutput(const KScreen::OutputPtr &kscreenOutput) const
{
    XRandROutput *xOutput = output(kscreenOutput->id());
    Q_ASSERT(xOutput);

    if (!xOutput->crtc()) {
        qCDebug(KSCREEN_XRANDR) << "Output" << kscreenOutput->id()
                                << "has no CRTC, falling back to enableOutput()";
        return enableOutput(kscreenOutput);
591
    }
592

593
594
    int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() :
                                                kscreenOutput->preferredModeId().toInt();
595
    xOutput->updateLogicalSize(kscreenOutput);
596
597
598
599
600
601
602
603
604
605
606

    qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (change output)" << "\n"
                            << "\tOutput:" << kscreenOutput->id() << "(" << kscreenOutput->name()
                            << ")" << "\n"
                            << "\tCRTC:" << xOutput->crtc()->crtc() << "\n"
                            << "\tPos:" << kscreenOutput->pos() << "\n"
                            << "\tMode:" << modeId  << kscreenOutput->currentMode() << "\n"
                            << "\tRotation:" << kscreenOutput->rotation();

    if (!sendConfig(kscreenOutput, xOutput->crtc())) {
        return false;
607
    }
608
609
610
611

    xOutput->update(xOutput->crtc()->crtc(), modeId,
                    XCB_RANDR_CONNECTION_CONNECTED, kscreenOutput->isPrimary());
    return true;
Daniel Vrátil's avatar
Daniel Vrátil committed
612
613
}

614
615
bool XRandRConfig::sendConfig(const KScreen::OutputPtr &kscreenOutput, XRandRCrtc *crtc) const
{
616
    xcb_randr_output_t outputs[1] { static_cast<xcb_randr_output_t>(kscreenOutput->id()) };
617
618
    const int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() :
                                                      kscreenOutput->preferredModeId().toInt();
619

620
621
622
623
624
625
    auto cookie = xcb_randr_set_crtc_config(XCB::connection(), crtc->crtc(),
                                            XCB_CURRENT_TIME, XCB_CURRENT_TIME,
                                            kscreenOutput->pos().rx(), kscreenOutput->pos().ry(),
                                            modeId,
                                            kscreenOutput->rotation(),
                                            1, outputs);
Roman Gilg's avatar
Roman Gilg committed
626
627
628

    XCB::ScopedPointer<xcb_randr_set_crtc_config_reply_t>
            reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, nullptr));
629
630
631
632
633
634
    if (!reply) {
        qCDebug(KSCREEN_XRANDR) << "\tResult: unknown (error)";
        return false;
    }
    qCDebug(KSCREEN_XRANDR) << "\tResult: " << reply->status;
    return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS);
Daniel Vrátil's avatar
Daniel Vrátil committed
635
}