waylandconfig.cpp 12.3 KB
Newer Older
Sebastian Kügler's avatar
Sebastian Kügler committed
1
/*************************************************************************************
Sebastian Kügler's avatar
Sebastian Kügler committed
2
 *  Copyright 2014-2015 Sebastian Kügler <sebas@kde.org>                             *
Sebastian Kügler's avatar
Sebastian Kügler committed
3
 *  Copyright 2013 Martin Gräßlin <mgraesslin@kde.org>                               *
Sebastian Kügler's avatar
Sebastian Kügler committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *                                                                                   *
 *  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 "waylandconfig.h"
#include "waylandoutput.h"
22
#include "waylandscreen.h"
Sebastian Kügler's avatar
Sebastian Kügler committed
23 24
#include "waylandbackend.h"

25 26
// KWayland
#include <KWayland/Client/connection_thread.h>
27
#include <KWayland/Client/event_queue.h>
28
#include <KWayland/Client/registry.h>
29
#include <KWayland/Client/outputconfiguration.h>
30 31
#include <KWayland/Client/outputdevice.h>
#include <KWayland/Client/outputmanagement.h>
32

33 34 35 36

// Qt
#include <QTimer>

Sebastian Kügler's avatar
Sebastian Kügler committed
37 38 39 40 41 42 43 44 45
#include <configmonitor.h>
#include <mode.h>


using namespace KScreen;


WaylandConfig::WaylandConfig(QObject *parent)
    : QObject(parent)
46
    , m_outputManagement(nullptr)
47
    , m_registryInitialized(false)
Sebastian Kügler's avatar
Sebastian Kügler committed
48
    , m_blockSignals(true)
49
    , m_newOutputId(0)
Sebastian Kügler's avatar
Sebastian Kügler committed
50
    , m_kscreenConfig(nullptr)
51
    , m_kscreenPendingConfig(nullptr)
Sebastian Kügler's avatar
Sebastian Kügler committed
52
    , m_screen(new WaylandScreen(this))
Sebastian Kügler's avatar
Sebastian Kügler committed
53
{
54
    connect(this, &WaylandConfig::initialized, &m_syncLoop, &QEventLoop::quit);
55
    QTimer::singleShot(1000, this, [this] {
Sebastian Kügler's avatar
Sebastian Kügler committed
56
        if (m_syncLoop.isRunning()) {
Sebastian Kügler's avatar
Sebastian Kügler committed
57
            qCWarning(KSCREEN_WAYLAND) << "Connection to Wayland server at socket:" << m_connection->socketName() << "timed out.";
Sebastian Kügler's avatar
Sebastian Kügler committed
58 59 60 61
            m_syncLoop.quit();
            m_thread->quit();
            m_thread->wait();
        }
62
    });
Sebastian Kügler's avatar
Sebastian Kügler committed
63
    initConnection();
64
    m_syncLoop.exec();
Sebastian Kügler's avatar
Sebastian Kügler committed
65 66 67 68
}

WaylandConfig::~WaylandConfig()
{
69 70
    m_thread->quit();
    m_thread->wait();
Sebastian Kügler's avatar
Sebastian Kügler committed
71
    m_syncLoop.quit();
Sebastian Kügler's avatar
Sebastian Kügler committed
72 73 74 75
}

void WaylandConfig::initConnection()
{
76
    m_thread = new QThread(this);
Sebastian Kügler's avatar
Sebastian Kügler committed
77
    //m_queue = new KWayland::Client::EventQueue(this);
78 79 80 81
    m_connection = new KWayland::Client::ConnectionThread;

    connect(m_connection, &KWayland::Client::ConnectionThread::connected,
            this, &WaylandConfig::setupRegistry, Qt::QueuedConnection);
Sebastian Kügler's avatar
Sebastian Kügler committed
82

83 84
    connect(m_connection, &KWayland::Client::ConnectionThread::connectionDied,
            this, &WaylandConfig::disconnected, Qt::QueuedConnection);
85
    connect(m_connection, &KWayland::Client::ConnectionThread::failed, this, [this] {
86
        qCWarning(KSCREEN_WAYLAND) << "Failed to connect to Wayland server at socket:" << m_connection->socketName();
87 88 89 90
        m_syncLoop.quit();
        m_thread->quit();
        m_thread->wait();
    });
91 92

    m_thread->start();
93
    m_connection->moveToThread(m_thread);
94 95
    m_connection->initConnection();

Sebastian Kügler's avatar
Sebastian Kügler committed
96 97
}

98 99 100 101 102 103 104 105 106 107 108 109
void WaylandConfig::blockSignals()
{
    Q_ASSERT(m_blockSignals == false);
    m_blockSignals = true;
}

void WaylandConfig::unblockSignals()
{
    Q_ASSERT(m_blockSignals == true);
    m_blockSignals = false;
}

Sebastian Kügler's avatar
Sebastian Kügler committed
110 111
void WaylandConfig::disconnected()
{
112
    qCWarning(KSCREEN_WAYLAND) << "Wayland disconnected, cleaning up.";
113
    qDeleteAll(m_outputMap);
Sebastian Kügler's avatar
Sebastian Kügler committed
114
    m_outputMap.clear();
Sebastian Kügler's avatar
Sebastian Kügler committed
115

Sebastian Kügler's avatar
Sebastian Kügler committed
116
    // Clean up
Sebastian Kügler's avatar
Sebastian Kügler committed
117 118 119 120
    if (m_queue) {
        delete m_queue;
        m_queue = nullptr;
    }
Sebastian Kügler's avatar
Sebastian Kügler committed
121 122 123 124

    m_connection->deleteLater();
    m_connection = nullptr;

Sebastian Kügler's avatar
Sebastian Kügler committed
125 126
    if (m_thread) {
        m_thread->quit();
127 128
        if (!m_thread->wait(3000)) {
            m_thread->terminate();
Sebastian Kügler's avatar
Sebastian Kügler committed
129
            m_thread->wait();
130
        }
Sebastian Kügler's avatar
Sebastian Kügler committed
131 132 133 134
        delete m_thread;
        m_thread = nullptr;
    }

135
    Q_EMIT configChanged(toKScreenConfig());
Sebastian Kügler's avatar
Sebastian Kügler committed
136
    Q_EMIT gone();
Sebastian Kügler's avatar
Sebastian Kügler committed
137 138
}

139 140 141 142 143 144 145
void WaylandConfig::setupRegistry()
{
    m_queue = new KWayland::Client::EventQueue(this);
    m_queue->setup(m_connection);

    m_registry = new KWayland::Client::Registry(this);

146
    connect(m_registry, &KWayland::Client::Registry::outputDeviceAnnounced,
Sebastian Kügler's avatar
Sebastian Kügler committed
147
            this, &WaylandConfig::addOutput);
148
    connect(m_registry, &KWayland::Client::Registry::outputDeviceRemoved,
Sebastian Kügler's avatar
Sebastian Kügler committed
149
            this, &WaylandConfig::removeOutput);
150

Sebastian Kügler's avatar
Sebastian Kügler committed
151 152 153 154 155 156
    connect(m_registry, &KWayland::Client::Registry::outputManagementAnnounced,
            this, [this](quint32 name, quint32 version) {
                m_outputManagement = m_registry->createOutputManagement(name, version, m_registry);
                checkInitialized();
            }
    );
157

Sebastian Kügler's avatar
Sebastian Kügler committed
158 159 160
    connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced,
            this, [this] {
                m_registryInitialized = true;
161
                unblockSignals();
Sebastian Kügler's avatar
Sebastian Kügler committed
162 163 164
                checkInitialized();
            }
    );
165 166

    m_registry->create(m_connection);
Sebastian Kügler's avatar
Sebastian Kügler committed
167
    m_registry->setEventQueue(m_queue);
168 169 170 171
    m_registry->setup();
}

void WaylandConfig::addOutput(quint32 name, quint32 version)
Sebastian Kügler's avatar
Sebastian Kügler committed
172
{
173
    ++m_newOutputId;
174
    quint32 new_id = m_newOutputId;
175
    m_outputIds[name] = new_id;
176
    if (m_outputMap.contains(new_id)) {
Sebastian Kügler's avatar
Sebastian Kügler committed
177 178
        return;
    }
179 180 181
    if (!m_initializingOutputs.contains(name)) {
        m_initializingOutputs << name;
    }
182

183
    auto op = new KWayland::Client::OutputDevice(this);
184 185
    WaylandOutput *waylandoutput = new WaylandOutput(new_id, this);
    waylandoutput->bindOutputDevice(m_registry, op, name, version);
186

187 188 189
    // finalize: when the output is done, we put it in the known outputs map,
    // remove if from the list of initializing outputs, and emit configChanged()
    connect(waylandoutput, &WaylandOutput::complete, this, [this, waylandoutput, name]{
190

Sebastian Kügler's avatar
Sebastian Kügler committed
191
        m_outputMap.insert(waylandoutput->id(), waylandoutput);
192 193
        m_initializingOutputs.removeAll(name);
        checkInitialized();
194

195
        if (!m_blockSignals && m_initializingOutputs.empty()) {
196
            m_screen->setOutputs(m_outputMap.values());
197
            Q_EMIT configChanged(toKScreenConfig());
198
        }
Sebastian Kügler's avatar
Sebastian Kügler committed
199
        connect(waylandoutput, &WaylandOutput::changed, this, [this]() {
200 201 202
            if (!m_blockSignals) {
                Q_EMIT configChanged(toKScreenConfig());
            }
203
        });
204
    });
Sebastian Kügler's avatar
Sebastian Kügler committed
205 206
}

207 208
void WaylandConfig::checkInitialized()
{
209
    if (!m_blockSignals && m_registryInitialized &&
210
        m_initializingOutputs.isEmpty() && m_outputMap.count() && m_outputManagement != nullptr) {
211
        m_screen->setOutputs(m_outputMap.values());
Sebastian Kügler's avatar
Sebastian Kügler committed
212
        Q_EMIT initialized();
213
    }
214 215
}

Sebastian Kügler's avatar
Sebastian Kügler committed
216
KScreen::ConfigPtr WaylandConfig::toKScreenConfig()
Sebastian Kügler's avatar
Sebastian Kügler committed
217
{
Sebastian Kügler's avatar
Sebastian Kügler committed
218 219 220 221 222 223 224
    if (m_kscreenConfig == nullptr) {
        m_kscreenConfig = KScreen::ConfigPtr(new Config);
    }
    m_kscreenConfig->setScreen(m_screen->toKScreenScreen(m_kscreenConfig));

    updateKScreenConfig(m_kscreenConfig);
    return m_kscreenConfig;
Sebastian Kügler's avatar
Sebastian Kügler committed
225 226
}

227
void WaylandConfig::removeOutput(quint32 name)
Sebastian Kügler's avatar
Sebastian Kügler committed
228
{
Sebastian Kügler's avatar
Sebastian Kügler committed
229
    const int kscreen_id = m_outputIds[name];
230
    auto output = m_outputMap.take(kscreen_id);
Sebastian Kügler's avatar
noise--  
Sebastian Kügler committed
231
    m_screen->setOutputs(m_outputMap.values());
232
    delete output;
Sebastian Kügler's avatar
Sebastian Kügler committed
233
    if (!m_blockSignals) {
234
        Q_EMIT configChanged(toKScreenConfig());
Sebastian Kügler's avatar
Sebastian Kügler committed
235
    }
Sebastian Kügler's avatar
Sebastian Kügler committed
236 237
}

238
void WaylandConfig::updateKScreenConfig(KScreen::ConfigPtr &config) const
Sebastian Kügler's avatar
Sebastian Kügler committed
239
{
240
    auto features = Config::Feature::Writable | Config::Feature::PerOutputScaling;
241
    config->setSupportedFeatures(features);
242
    config->setValid(m_connection->display());
243 244
    KScreen::ScreenPtr screen = config->screen();
    m_screen->updateKScreenScreen(screen);
Sebastian Kügler's avatar
Sebastian Kügler committed
245 246

    //Removing removed outputs
247
    const KScreen::OutputList outputs = config->outputs();
Sebastian Kügler's avatar
Sebastian Kügler committed
248
    Q_FOREACH (const KScreen::OutputPtr &output, outputs) {
249
        if (!m_outputMap.contains(output->id())) {
Sebastian Kügler's avatar
Sebastian Kügler committed
250 251 252
            config->removeOutput(output->id());
        }
    }
253

Sebastian Kügler's avatar
Sebastian Kügler committed
254
    // Add KScreen::Outputs that aren't in the list yet, handle primaryOutput
255
    KScreen::OutputList kscreenOutputs = config->outputs();
256
    Q_FOREACH (const auto &output, m_outputMap) {
257 258 259 260 261
        KScreen::OutputPtr kscreenOutput = kscreenOutputs[output->id()];
        if (!kscreenOutput) {
            kscreenOutput = output->toKScreenOutput();
            kscreenOutputs.insert(kscreenOutput->id(), kscreenOutput);
        }
262 263 264
        if (kscreenOutput && m_outputMap.count() == 1) {
            kscreenOutput->setPrimary(true);
        } else if (m_outputMap.count() > 1) {
Sebastian Kügler's avatar
noise--  
Sebastian Kügler committed
265
            // primaryScreen concept doesn't exist in kwayland, so we don't set one
266
            //qCWarning(KSCREEN_WAYLAND) << "Multiple outputs, but no way to figure out the primary one. :/";
267
        }
Sebastian Kügler's avatar
Sebastian Kügler committed
268 269
        output->updateKScreenOutput(kscreenOutput);
    }
270
    config->setOutputs(kscreenOutputs);
Sebastian Kügler's avatar
Sebastian Kügler committed
271 272
}

273
QMap<int, WaylandOutput*> WaylandConfig::outputMap() const
Sebastian Kügler's avatar
Sebastian Kügler committed
274
{
275
    return m_outputMap;
Sebastian Kügler's avatar
Sebastian Kügler committed
276 277
}

278 279 280 281 282 283 284 285 286
void WaylandConfig::tryPendingConfig()
{
    if (!m_kscreenPendingConfig) {
        return;
    }
    applyConfig(m_kscreenPendingConfig);
    m_kscreenPendingConfig = nullptr;
}

287
void WaylandConfig::applyConfig(const KScreen::ConfigPtr &newConfig)
288 289 290
{
    using namespace KWayland::Client;
    // Create a new configuration object
291
    auto wlOutputConfiguration = m_outputManagement->createConfiguration();
292
    bool changed = false;
293

294 295 296 297 298 299
    if (m_blockSignals) {
        /* Last apply still pending, remember new changes and apply afterwards */
        m_kscreenPendingConfig = newConfig;
        return;
    }

300
    Q_FOREACH (auto output, newConfig->outputs()) {
301
        auto o_old = m_outputMap[output->id()];
302
        auto device = o_old->outputDevice();
303 304
        Q_ASSERT(o_old != nullptr);

305
        // enabled?
306
        bool old_enabled = (o_old->outputDevice()->enabled() == OutputDevice::Enablement::Enabled);
307
        if (old_enabled != output->isEnabled()) {
308
            changed = true;
309
            auto _enablement = output->isEnabled() ? OutputDevice::Enablement::Enabled : OutputDevice::Enablement::Disabled;
310
            wlOutputConfiguration->setEnabled(o_old->outputDevice(), _enablement);
311
        }
312 313

        // position
314
        if (device->globalPosition() != output->pos()) {
315
            changed = true;
316
            wlOutputConfiguration->setPosition(o_old->outputDevice(), output->pos());
317 318
        }

319
        if (!qFuzzyCompare(device->scaleF(), output->scale())) {
320
            changed = true;
321
            wlOutputConfiguration->setScaleF(o_old->outputDevice(), output->scale());
322 323
        }

324 325
        // rotation
        auto r_current = o_old->toKScreenRotation(device->transform());
326
        auto r_new = output->rotation();
327
        if (r_current != r_new) {
328
            changed = true;
329
            wlOutputConfiguration->setTransform(device, o_old->toKWaylandTransform(r_new));
330
        }
Sebastian Kügler's avatar
Sebastian Kügler committed
331

Sebastian Kügler's avatar
Sebastian Kügler committed
332
        // mode
333 334 335 336
        int w_currentmodeid = device->currentMode().id;
        QString l_newmodeid = output->currentModeId();
        int w_newmodeid = o_old->toKWaylandModeId(l_newmodeid);
        if (w_newmodeid != w_currentmodeid) {
337
            changed = true;
338
            wlOutputConfiguration->setMode(device, w_newmodeid);
339
        }
340
    }
341

342 343 344 345
    if (!changed) {
        return;
    }

346 347 348 349 350
    // We now block changes in order to compress events while the compositor is doing its thing
    // once it's done or failed, we'll trigger configChanged() only once, and not per individual
    // property change.
    connect(wlOutputConfiguration, &OutputConfiguration::applied, this, [this, wlOutputConfiguration] {
        wlOutputConfiguration->deleteLater();
351
        unblockSignals();
352
        Q_EMIT configChanged(toKScreenConfig());
353
        tryPendingConfig();
354 355 356
    });
    connect(wlOutputConfiguration, &OutputConfiguration::failed, this, [this, wlOutputConfiguration] {
        wlOutputConfiguration->deleteLater();
357
        unblockSignals();
358
        Q_EMIT configChanged(toKScreenConfig());
359
        tryPendingConfig();
360
    });
361
    blockSignals();
362
    // Now ask the compositor to apply the changes
363
    wlOutputConfiguration->apply();
Sebastian Kügler's avatar
Sebastian Kügler committed
364
}