e2a0863843
Hardware constraints limit the number of crtcs and which connector + crtc combinations can work together. The current code is searching for working combinations when a hotplug happens but that's not enough, it also needs to happen when the user enables or disables outputs and when modesets are done, and the configuration change needs to be applied with a single atomic commit. This commit removes the hard dependency of DrmPipeline on crtcs by moving the pending state of outputs from the drm objects to DrmPipeline itself, which ensures that it's independent from the set of drm objects currently used. It also changes requests from KScreen to be applied truly atomically.
672 lines
16 KiB
C++
672 lines
16 KiB
C++
/*
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
#include "platform.h"
|
|
|
|
#include "abstract_output.h"
|
|
#include <config-kwin.h>
|
|
#include "composite.h"
|
|
#include "cursor.h"
|
|
#include "effects.h"
|
|
#include "keyboard_input.h"
|
|
#include <KCoreAddons>
|
|
#include "overlaywindow.h"
|
|
#include "outline.h"
|
|
#include "pointer_input.h"
|
|
#include "scene.h"
|
|
#include "screens.h"
|
|
#include "screenedge.h"
|
|
#include "touch_input.h"
|
|
#include "wayland_server.h"
|
|
#include "waylandoutputconfig.h"
|
|
|
|
#include <KWaylandServer/outputconfiguration_v2_interface.h>
|
|
#include <KWaylandServer/outputchangeset_v2.h>
|
|
|
|
#include <QX11Info>
|
|
|
|
#include <cerrno>
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
Platform::Platform(QObject *parent)
|
|
: QObject(parent)
|
|
, m_eglDisplay(EGL_NO_DISPLAY)
|
|
{
|
|
setSoftwareCursorForced(false);
|
|
connect(Cursors::self(), &Cursors::currentCursorRendered, this, &Platform::cursorRendered);
|
|
|
|
connect(this, &Platform::outputDisabled, this, [this] (AbstractOutput *output) {
|
|
if (m_primaryOutput == output) {
|
|
setPrimaryOutput(enabledOutputs().value(0, nullptr));
|
|
}
|
|
});
|
|
connect(this, &Platform::outputEnabled, this, [this] (AbstractOutput *output) {
|
|
if (!m_primaryOutput) {
|
|
setPrimaryOutput(output);
|
|
}
|
|
});
|
|
}
|
|
|
|
Platform::~Platform()
|
|
{
|
|
}
|
|
|
|
PlatformCursorImage Platform::cursorImage() const
|
|
{
|
|
Cursor* cursor = Cursors::self()->currentCursor();
|
|
return PlatformCursorImage(cursor->image(), cursor->hotspot());
|
|
}
|
|
|
|
void Platform::hideCursor()
|
|
{
|
|
m_hideCursorCounter++;
|
|
if (m_hideCursorCounter == 1) {
|
|
doHideCursor();
|
|
}
|
|
}
|
|
|
|
void Platform::doHideCursor()
|
|
{
|
|
}
|
|
|
|
void Platform::showCursor()
|
|
{
|
|
m_hideCursorCounter--;
|
|
if (m_hideCursorCounter == 0) {
|
|
doShowCursor();
|
|
}
|
|
}
|
|
|
|
void Platform::doShowCursor()
|
|
{
|
|
}
|
|
|
|
InputBackend *Platform::createInputBackend()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
OpenGLBackend *Platform::createOpenGLBackend()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
QPainterBackend *Platform::createQPainterBackend()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
Edge *Platform::createScreenEdge(ScreenEdges *edges)
|
|
{
|
|
return new Edge(edges);
|
|
}
|
|
|
|
void Platform::createPlatformCursor(QObject *parent)
|
|
{
|
|
new InputRedirectionCursor(parent);
|
|
}
|
|
|
|
void Platform::requestOutputsChange(KWaylandServer::OutputConfigurationV2Interface *config)
|
|
{
|
|
if (!m_supportsOutputChanges) {
|
|
qCWarning(KWIN_CORE) << "This backend does not support configuration changes.";
|
|
config->setFailed();
|
|
return;
|
|
}
|
|
|
|
WaylandOutputConfig cfg;
|
|
const auto changes = config->changes();
|
|
for (auto it = changes.begin(); it != changes.end(); it++) {
|
|
const KWaylandServer::OutputChangeSetV2 *changeset = it.value();
|
|
auto output = qobject_cast<AbstractWaylandOutput*>(findOutput(it.key()->uuid()));
|
|
if (!output) {
|
|
qCWarning(KWIN_CORE) << "Could NOT find output matching " << it.key()->uuid();
|
|
continue;
|
|
}
|
|
auto props = cfg.changeSet(output);
|
|
props->enabled = changeset->enabled();
|
|
props->pos = changeset->position();
|
|
props->scale = changeset->scale();
|
|
props->modeSize = changeset->size();
|
|
props->refreshRate = changeset->refreshRate();
|
|
props->transform = static_cast<AbstractWaylandOutput::Transform>(changeset->transform());
|
|
props->overscan = changeset->overscan();
|
|
props->rgbRange = static_cast<AbstractWaylandOutput::RgbRange>(changeset->rgbRange());
|
|
props->vrrPolicy = static_cast<RenderLoop::VrrPolicy>(changeset->vrrPolicy());
|
|
}
|
|
|
|
const auto outputs = enabledOutputs();
|
|
bool allDisabled = !std::any_of(outputs.begin(), outputs.end(), [&cfg](const auto &output){
|
|
auto o = qobject_cast<AbstractWaylandOutput*>(output);
|
|
if (!o) {
|
|
qCWarning(KWIN_CORE) << "Platform::requestOutputsChange should only be called for Wayland platforms!";
|
|
return false;
|
|
}
|
|
return cfg.changeSet(o)->enabled;
|
|
});
|
|
if (allDisabled) {
|
|
qCWarning(KWIN_CORE) << "Disabling all outputs through configuration changes is not allowed";
|
|
config->setFailed();
|
|
return;
|
|
}
|
|
|
|
if (applyOutputChanges(cfg)) {
|
|
if (config->primaryChanged()) {
|
|
setPrimaryOutput(findOutput(config->primary()->uuid()));
|
|
}
|
|
Q_EMIT screens()->changed();
|
|
config->setApplied();
|
|
} else {
|
|
qCDebug(KWIN_CORE) << "Applying config failed";
|
|
config->setFailed();
|
|
}
|
|
}
|
|
|
|
bool Platform::applyOutputChanges(const WaylandOutputConfig &config)
|
|
{
|
|
const auto outputs = enabledOutputs();
|
|
for (const auto &output : outputs) {
|
|
static_cast<AbstractWaylandOutput*>(output)->applyChanges(config);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
AbstractOutput *Platform::findOutput(int screenId) const
|
|
{
|
|
return enabledOutputs().value(screenId);
|
|
}
|
|
|
|
AbstractOutput *Platform::findOutput(const QUuid &uuid) const
|
|
{
|
|
const auto outs = outputs();
|
|
auto it = std::find_if(outs.constBegin(), outs.constEnd(),
|
|
[uuid](AbstractOutput *output) {
|
|
return output->uuid() == uuid; }
|
|
);
|
|
if (it != outs.constEnd()) {
|
|
return *it;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
AbstractOutput *Platform::outputAt(const QPoint &pos) const
|
|
{
|
|
AbstractOutput *bestOutput = nullptr;
|
|
int minDistance = INT_MAX;
|
|
const auto candidates = enabledOutputs();
|
|
for (AbstractOutput *output : candidates) {
|
|
const QRect &geo = output->geometry();
|
|
if (geo.contains(pos)) {
|
|
return output;
|
|
}
|
|
int distance = QPoint(geo.topLeft() - pos).manhattanLength();
|
|
distance = std::min(distance, QPoint(geo.topRight() - pos).manhattanLength());
|
|
distance = std::min(distance, QPoint(geo.bottomRight() - pos).manhattanLength());
|
|
distance = std::min(distance, QPoint(geo.bottomLeft() - pos).manhattanLength());
|
|
if (distance < minDistance) {
|
|
minDistance = distance;
|
|
bestOutput = output;
|
|
}
|
|
}
|
|
return bestOutput;
|
|
}
|
|
|
|
bool Platform::usesSoftwareCursor() const
|
|
{
|
|
return m_softwareCursor;
|
|
}
|
|
|
|
void Platform::setSoftwareCursor(bool set)
|
|
{
|
|
if (m_softwareCursor == set) {
|
|
return;
|
|
}
|
|
m_softwareCursor = set;
|
|
doSetSoftwareCursor();
|
|
if (m_softwareCursor) {
|
|
connect(Cursors::self(), &Cursors::positionChanged, this, &Platform::triggerCursorRepaint);
|
|
connect(Cursors::self(), &Cursors::currentCursorChanged, this, &Platform::triggerCursorRepaint);
|
|
} else {
|
|
disconnect(Cursors::self(), &Cursors::positionChanged, this, &Platform::triggerCursorRepaint);
|
|
disconnect(Cursors::self(), &Cursors::currentCursorChanged, this, &Platform::triggerCursorRepaint);
|
|
}
|
|
triggerCursorRepaint();
|
|
}
|
|
|
|
void Platform::doSetSoftwareCursor()
|
|
{
|
|
}
|
|
|
|
bool Platform::isSoftwareCursorForced() const
|
|
{
|
|
return m_softwareCursorForced;
|
|
}
|
|
|
|
void Platform::setSoftwareCursorForced(bool forced)
|
|
{
|
|
if (qEnvironmentVariableIsSet("KWIN_FORCE_SW_CURSOR")) {
|
|
forced = true;
|
|
}
|
|
if (m_softwareCursorForced == forced) {
|
|
return;
|
|
}
|
|
m_softwareCursorForced = forced;
|
|
if (m_softwareCursorForced) {
|
|
setSoftwareCursor(true);
|
|
} else {
|
|
// Do not unset the software cursor yet, the platform will choose the right
|
|
// moment when it can be done. There is still a chance that we must continue
|
|
// using the software cursor.
|
|
}
|
|
}
|
|
|
|
void Platform::triggerCursorRepaint()
|
|
{
|
|
if (!Compositor::self()) {
|
|
return;
|
|
}
|
|
Compositor::self()->addRepaint(m_cursor.lastRenderedGeometry);
|
|
Compositor::self()->addRepaint(Cursors::self()->currentCursor()->geometry());
|
|
}
|
|
|
|
void Platform::cursorRendered(const QRect &geometry)
|
|
{
|
|
if (m_softwareCursor) {
|
|
m_cursor.lastRenderedGeometry = geometry;
|
|
}
|
|
}
|
|
|
|
void Platform::keyboardKeyPressed(quint32 key, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->keyboard()->processKey(key, InputRedirection::KeyboardKeyPressed, time);
|
|
}
|
|
|
|
void Platform::keyboardKeyReleased(quint32 key, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->keyboard()->processKey(key, InputRedirection::KeyboardKeyReleased, time);
|
|
}
|
|
|
|
void Platform::keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->keyboard()->processModifiers(modsDepressed, modsLatched, modsLocked, group);
|
|
}
|
|
|
|
void Platform::keymapChange(int fd, uint32_t size)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->keyboard()->processKeymapChange(fd, size);
|
|
}
|
|
|
|
void Platform::pointerAxisHorizontal(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processAxis(InputRedirection::PointerAxisHorizontal, delta, discreteDelta, source, time);
|
|
}
|
|
|
|
void Platform::pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processAxis(InputRedirection::PointerAxisVertical, delta, discreteDelta, source, time);
|
|
}
|
|
|
|
void Platform::pointerButtonPressed(quint32 button, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processButton(button, InputRedirection::PointerButtonPressed, time);
|
|
}
|
|
|
|
void Platform::pointerButtonReleased(quint32 button, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processButton(button, InputRedirection::PointerButtonReleased, time);
|
|
}
|
|
|
|
void Platform::pointerMotion(const QPointF &position, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processMotionAbsolute(position, time);
|
|
}
|
|
|
|
void Platform::cancelTouchSequence()
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->touch()->cancel();
|
|
}
|
|
|
|
void Platform::touchCancel()
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->touch()->cancel();
|
|
}
|
|
|
|
void Platform::touchDown(qint32 id, const QPointF &pos, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->touch()->processDown(id, pos, time);
|
|
}
|
|
|
|
void Platform::touchFrame()
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->touch()->frame();
|
|
}
|
|
|
|
void Platform::touchMotion(qint32 id, const QPointF &pos, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->touch()->processMotion(id, pos, time);
|
|
}
|
|
|
|
void Platform::touchUp(qint32 id, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->touch()->processUp(id, time);
|
|
}
|
|
|
|
void Platform::processSwipeGestureBegin(int fingerCount, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processSwipeGestureBegin(fingerCount, time);
|
|
}
|
|
|
|
void Platform::processSwipeGestureUpdate(const QSizeF &delta, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processSwipeGestureUpdate(delta, time);
|
|
}
|
|
|
|
void Platform::processSwipeGestureEnd(quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processSwipeGestureEnd(time);
|
|
}
|
|
|
|
void Platform::processSwipeGestureCancelled(quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processSwipeGestureCancelled(time);
|
|
}
|
|
|
|
void Platform::processPinchGestureBegin(int fingerCount, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processPinchGestureBegin(fingerCount, time);
|
|
}
|
|
|
|
void Platform::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processPinchGestureUpdate(scale, angleDelta, delta, time);
|
|
}
|
|
|
|
void Platform::processPinchGestureEnd(quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processPinchGestureEnd(time);
|
|
}
|
|
|
|
void Platform::processPinchGestureCancelled(quint32 time)
|
|
{
|
|
if (!input()) {
|
|
return;
|
|
}
|
|
input()->pointer()->processPinchGestureCancelled(time);
|
|
}
|
|
|
|
void Platform::repaint(const QRect &rect)
|
|
{
|
|
if (!Compositor::self()) {
|
|
return;
|
|
}
|
|
Compositor::self()->addRepaint(rect);
|
|
}
|
|
|
|
void Platform::setReady(bool ready)
|
|
{
|
|
if (m_ready == ready) {
|
|
return;
|
|
}
|
|
m_ready = ready;
|
|
Q_EMIT readyChanged(m_ready);
|
|
}
|
|
|
|
bool Platform::isPerScreenRenderingEnabled() const
|
|
{
|
|
return m_isPerScreenRenderingEnabled;
|
|
}
|
|
|
|
void Platform::setPerScreenRenderingEnabled(bool enabled)
|
|
{
|
|
m_isPerScreenRenderingEnabled = enabled;
|
|
}
|
|
|
|
RenderLoop *Platform::renderLoop() const
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
AbstractOutput *Platform::createVirtualOutput(const QString &name, const QSize &size, double scale)
|
|
{
|
|
Q_UNUSED(name);
|
|
Q_UNUSED(size);
|
|
Q_UNUSED(scale);
|
|
return nullptr;
|
|
}
|
|
|
|
void Platform::removeVirtualOutput(AbstractOutput *output)
|
|
{
|
|
Q_ASSERT(!output);
|
|
}
|
|
|
|
void Platform::warpPointer(const QPointF &globalPos)
|
|
{
|
|
Q_UNUSED(globalPos)
|
|
}
|
|
|
|
bool Platform::supportsNativeFence() const
|
|
{
|
|
if (Compositor *compositor = Compositor::self()) {
|
|
return compositor->scene()->supportsNativeFence();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
EGLDisplay KWin::Platform::sceneEglDisplay() const
|
|
{
|
|
return m_eglDisplay;
|
|
}
|
|
|
|
void Platform::setSceneEglDisplay(EGLDisplay display)
|
|
{
|
|
m_eglDisplay = display;
|
|
}
|
|
|
|
QSize Platform::screenSize() const
|
|
{
|
|
return QSize();
|
|
}
|
|
|
|
bool Platform::requiresCompositing() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool Platform::compositingPossible() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QString Platform::compositingNotPossibleReason() const
|
|
{
|
|
return QString();
|
|
}
|
|
|
|
bool Platform::openGLCompositingIsBroken() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void Platform::createOpenGLSafePoint(OpenGLSafePoint safePoint)
|
|
{
|
|
Q_UNUSED(safePoint)
|
|
}
|
|
|
|
void Platform::startInteractiveWindowSelection(std::function<void(KWin::Toplevel*)> callback, const QByteArray &cursorName)
|
|
{
|
|
if (!input()) {
|
|
callback(nullptr);
|
|
return;
|
|
}
|
|
input()->startInteractiveWindowSelection(callback, cursorName);
|
|
}
|
|
|
|
void Platform::startInteractivePositionSelection(std::function<void(const QPoint &)> callback)
|
|
{
|
|
if (!input()) {
|
|
callback(QPoint(-1, -1));
|
|
return;
|
|
}
|
|
input()->startInteractivePositionSelection(callback);
|
|
}
|
|
|
|
void Platform::setupActionForGlobalAccel(QAction *action)
|
|
{
|
|
Q_UNUSED(action)
|
|
}
|
|
|
|
OverlayWindow *Platform::createOverlayWindow()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
static quint32 monotonicTime()
|
|
{
|
|
timespec ts;
|
|
|
|
const int result = clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
if (result)
|
|
qCWarning(KWIN_CORE, "Failed to query monotonic time: %s", strerror(errno));
|
|
|
|
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000L;
|
|
}
|
|
|
|
void Platform::updateXTime()
|
|
{
|
|
switch (kwinApp()->operationMode()) {
|
|
case Application::OperationModeX11:
|
|
kwinApp()->setX11Time(QX11Info::getTimestamp(), Application::TimestampUpdate::Always);
|
|
break;
|
|
|
|
case Application::OperationModeXwayland:
|
|
kwinApp()->setX11Time(monotonicTime(), Application::TimestampUpdate::Always);
|
|
break;
|
|
|
|
default:
|
|
// Do not update the current X11 time stamp if it's the Wayland only session.
|
|
break;
|
|
}
|
|
}
|
|
|
|
OutlineVisual *Platform::createOutline(Outline *outline)
|
|
{
|
|
if (Compositor::compositing()) {
|
|
return new CompositedOutlineVisual(outline);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Platform::invertScreen()
|
|
{
|
|
if (effects) {
|
|
if (Effect *inverter = static_cast<EffectsHandlerImpl*>(effects)->provides(Effect::ScreenInversion)) {
|
|
qCDebug(KWIN_CORE) << "inverting screen using Effect plugin";
|
|
QMetaObject::invokeMethod(inverter, "toggleScreenInversion", Qt::DirectConnection);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Platform::createEffectsHandler(Compositor *compositor, Scene *scene)
|
|
{
|
|
new EffectsHandlerImpl(compositor, scene);
|
|
}
|
|
|
|
QString Platform::supportInformation() const
|
|
{
|
|
return QStringLiteral("Name: %1\n").arg(metaObject()->className());
|
|
}
|
|
|
|
EGLContext Platform::sceneEglGlobalShareContext() const
|
|
{
|
|
return m_globalShareContext;
|
|
}
|
|
|
|
void Platform::setSceneEglGlobalShareContext(EGLContext context)
|
|
{
|
|
m_globalShareContext = context;
|
|
}
|
|
|
|
void Platform::setPrimaryOutput(AbstractOutput *primary)
|
|
{
|
|
if (primary == m_primaryOutput) {
|
|
return;
|
|
}
|
|
Q_ASSERT(kwinApp()->isTerminating() || primary->isEnabled());
|
|
m_primaryOutput = primary;
|
|
Q_EMIT primaryOutputChanged(primary);
|
|
}
|
|
|
|
}
|