File indexing completed on 2024-11-10 04:56:34

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "wayland_output.h"
0010 #include "compositor.h"
0011 #include "core/outputlayer.h"
0012 #include "core/renderbackend.h"
0013 #include "core/renderloop_p.h"
0014 #include "wayland_backend.h"
0015 #include "wayland_display.h"
0016 
0017 #include <KWayland/Client/compositor.h>
0018 #include <KWayland/Client/pointer.h>
0019 #include <KWayland/Client/pointerconstraints.h>
0020 #include <KWayland/Client/surface.h>
0021 #include <KWayland/Client/xdgdecoration.h>
0022 
0023 #include <KLocalizedString>
0024 
0025 #include <QPainter>
0026 
0027 #include <cmath>
0028 
0029 namespace KWin
0030 {
0031 namespace Wayland
0032 {
0033 
0034 using namespace KWayland::Client;
0035 static const int s_refreshRate = 60000; // TODO: can we get refresh rate data from Wayland host?
0036 
0037 WaylandCursor::WaylandCursor(WaylandBackend *backend)
0038     : m_surface(backend->display()->compositor()->createSurface())
0039 {
0040 }
0041 
0042 WaylandCursor::~WaylandCursor() = default;
0043 
0044 KWayland::Client::Pointer *WaylandCursor::pointer() const
0045 {
0046     return m_pointer;
0047 }
0048 
0049 void WaylandCursor::setPointer(KWayland::Client::Pointer *pointer)
0050 {
0051     if (m_pointer == pointer) {
0052         return;
0053     }
0054     m_pointer = pointer;
0055     if (m_pointer) {
0056         m_pointer->setCursor(m_surface.get(), m_hotspot);
0057     }
0058 }
0059 
0060 void WaylandCursor::setEnabled(bool enable)
0061 {
0062     if (m_enabled != enable) {
0063         m_enabled = enable;
0064         sync();
0065     }
0066 }
0067 
0068 void WaylandCursor::update(wl_buffer *buffer, qreal scale, const QPoint &hotspot)
0069 {
0070     if (m_buffer != buffer || m_scale != scale || m_hotspot != hotspot) {
0071         m_buffer = buffer;
0072         m_scale = scale;
0073         m_hotspot = hotspot;
0074 
0075         sync();
0076     }
0077 }
0078 
0079 void WaylandCursor::sync()
0080 {
0081     if (!m_enabled) {
0082         m_surface->attachBuffer(KWayland::Client::Buffer::Ptr());
0083         m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
0084     } else {
0085         m_surface->attachBuffer(m_buffer);
0086         m_surface->setScale(std::ceil(m_scale));
0087         m_surface->damageBuffer(QRect(0, 0, INT32_MAX, INT32_MAX));
0088         m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
0089     }
0090 
0091     if (m_pointer) {
0092         m_pointer->setCursor(m_surface.get(), m_hotspot);
0093     }
0094 }
0095 
0096 WaylandOutput::WaylandOutput(const QString &name, WaylandBackend *backend)
0097     : Output(backend)
0098     , m_renderLoop(std::make_unique<RenderLoop>(this))
0099     , m_surface(backend->display()->compositor()->createSurface())
0100     , m_xdgShellSurface(backend->display()->xdgShell()->createSurface(m_surface.get()))
0101     , m_backend(backend)
0102     , m_cursor(std::make_unique<WaylandCursor>(backend))
0103 {
0104     if (KWayland::Client::XdgDecorationManager *manager = m_backend->display()->xdgDecorationManager()) {
0105         m_xdgDecoration.reset(manager->getToplevelDecoration(m_xdgShellSurface.get()));
0106         m_xdgDecoration->setMode(KWayland::Client::XdgDecoration::Mode::ServerSide);
0107     }
0108 
0109     setInformation(Information{
0110         .name = name,
0111         .model = name,
0112         .capabilities = Capability::Dpms,
0113     });
0114 
0115     m_turnOffTimer.setSingleShot(true);
0116     m_turnOffTimer.setInterval(dimAnimationTime());
0117     connect(&m_turnOffTimer, &QTimer::timeout, this, [this] {
0118         updateDpmsMode(DpmsMode::Off);
0119     });
0120 
0121     m_configureThrottleTimer.setSingleShot(true);
0122     connect(&m_configureThrottleTimer, &QTimer::timeout, this, [this]() {
0123         applyConfigure(m_pendingConfigureSize, m_pendingConfigureSerial);
0124     });
0125 
0126     connect(m_surface.get(), &KWayland::Client::Surface::frameRendered, this, [this]() {
0127         Q_ASSERT(m_frame);
0128         const auto primary = Compositor::self()->backend()->primaryLayer(this);
0129         m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), std::chrono::steady_clock::now().time_since_epoch(), primary ? primary->queryRenderTime() : std::chrono::nanoseconds::zero(), PresentationMode::VSync);
0130         m_frame.reset();
0131     });
0132 
0133     updateWindowTitle();
0134 
0135     connect(m_xdgShellSurface.get(), &XdgShellSurface::configureRequested, this, &WaylandOutput::handleConfigure);
0136     connect(m_xdgShellSurface.get(), &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit);
0137     connect(this, &WaylandOutput::enabledChanged, this, &WaylandOutput::updateWindowTitle);
0138     connect(this, &WaylandOutput::dpmsModeChanged, this, &WaylandOutput::updateWindowTitle);
0139 }
0140 
0141 WaylandOutput::~WaylandOutput()
0142 {
0143     m_xdgDecoration.reset();
0144     m_xdgShellSurface.reset();
0145     m_surface.reset();
0146 }
0147 
0148 void WaylandOutput::framePending(const std::shared_ptr<OutputFrame> &frame)
0149 {
0150     m_frame = frame;
0151 }
0152 
0153 bool WaylandOutput::isReady() const
0154 {
0155     return m_ready;
0156 }
0157 
0158 KWayland::Client::Surface *WaylandOutput::surface() const
0159 {
0160     return m_surface.get();
0161 }
0162 
0163 WaylandCursor *WaylandOutput::cursor() const
0164 {
0165     return m_cursor.get();
0166 }
0167 
0168 WaylandBackend *WaylandOutput::backend() const
0169 {
0170     return m_backend;
0171 }
0172 
0173 RenderLoop *WaylandOutput::renderLoop() const
0174 {
0175     return m_renderLoop.get();
0176 }
0177 
0178 bool WaylandOutput::updateCursorLayer()
0179 {
0180     if (m_hasPointerLock) {
0181         m_cursor->setEnabled(false);
0182         return false;
0183     } else {
0184         m_cursor->setEnabled(Compositor::self()->backend()->cursorLayer(this)->isEnabled());
0185         // the layer already takes care of updating the image
0186         return true;
0187     }
0188 }
0189 
0190 void WaylandOutput::init(const QSize &pixelSize, qreal scale)
0191 {
0192     m_renderLoop->setRefreshRate(s_refreshRate);
0193 
0194     auto mode = std::make_shared<OutputMode>(pixelSize, s_refreshRate);
0195 
0196     State initialState;
0197     initialState.modes = {mode};
0198     initialState.currentMode = mode;
0199     initialState.scale = scale;
0200     setState(initialState);
0201 
0202     m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
0203 }
0204 
0205 void WaylandOutput::resize(const QSize &pixelSize)
0206 {
0207     auto mode = std::make_shared<OutputMode>(pixelSize, s_refreshRate);
0208 
0209     State next = m_state;
0210     next.modes = {mode};
0211     next.currentMode = mode;
0212     setState(next);
0213 
0214     Q_EMIT m_backend->outputsQueried();
0215 }
0216 
0217 void WaylandOutput::setDpmsMode(DpmsMode mode)
0218 {
0219     if (mode == DpmsMode::Off) {
0220         if (!m_turnOffTimer.isActive()) {
0221             Q_EMIT aboutToTurnOff(std::chrono::milliseconds(m_turnOffTimer.interval()));
0222             m_turnOffTimer.start();
0223         }
0224     } else {
0225         m_turnOffTimer.stop();
0226         if (mode != dpmsMode()) {
0227             updateDpmsMode(mode);
0228             Q_EMIT wakeUp();
0229         }
0230     }
0231 }
0232 
0233 void WaylandOutput::updateDpmsMode(DpmsMode dpmsMode)
0234 {
0235     State next = m_state;
0236     next.dpmsMode = dpmsMode;
0237     setState(next);
0238 }
0239 
0240 void WaylandOutput::updateEnabled(bool enabled)
0241 {
0242     State next = m_state;
0243     next.enabled = enabled;
0244     setState(next);
0245 }
0246 
0247 void WaylandOutput::handleConfigure(const QSize &size, XdgShellSurface::States states, quint32 serial)
0248 {
0249     if (!m_ready) {
0250         m_ready = true;
0251 
0252         applyConfigure(size, serial);
0253     } else {
0254         // Output resizing is a resource intensive task, so the configure events are throttled.
0255         m_pendingConfigureSerial = serial;
0256         m_pendingConfigureSize = size;
0257 
0258         if (!m_configureThrottleTimer.isActive()) {
0259             m_configureThrottleTimer.start(1000000 / m_state.currentMode->refreshRate());
0260         }
0261     }
0262 }
0263 
0264 void WaylandOutput::applyConfigure(const QSize &size, quint32 serial)
0265 {
0266     m_xdgShellSurface->ackConfigure(serial);
0267     if (!size.isEmpty()) {
0268         resize(size * scale());
0269     }
0270 }
0271 
0272 void WaylandOutput::updateWindowTitle()
0273 {
0274     QString grab;
0275     if (m_hasPointerLock) {
0276         grab = i18n("Press right control to ungrab pointer");
0277     } else if (m_backend->display()->pointerConstraints()) {
0278         grab = i18n("Press right control key to grab pointer");
0279     }
0280 
0281     QString title = i18nc("Title of nested KWin Wayland with Wayland socket identifier as argument",
0282                           "KDE Wayland Compositor %1", name());
0283 
0284     if (!isEnabled()) {
0285         title += i18n("- Output disabled");
0286     } else if (dpmsMode() != DpmsMode::On) {
0287         title += i18n("- Output dimmed");
0288     } else if (!grab.isEmpty()) {
0289         title += QStringLiteral(" — ") + grab;
0290     }
0291     m_xdgShellSurface->setTitle(title);
0292 }
0293 
0294 void WaylandOutput::lockPointer(Pointer *pointer, bool lock)
0295 {
0296     if (!lock) {
0297         const bool surfaceWasLocked = m_pointerLock && m_hasPointerLock;
0298         m_pointerLock.reset();
0299         m_hasPointerLock = false;
0300         if (surfaceWasLocked) {
0301             updateWindowTitle();
0302             updateCursorLayer();
0303             Q_EMIT m_backend->pointerLockChanged(false);
0304         }
0305         return;
0306     }
0307 
0308     Q_ASSERT(!m_pointerLock);
0309     m_pointerLock.reset(m_backend->display()->pointerConstraints()->lockPointer(surface(), pointer, nullptr, PointerConstraints::LifeTime::OneShot));
0310     if (!m_pointerLock->isValid()) {
0311         m_pointerLock.reset();
0312         return;
0313     }
0314     connect(m_pointerLock.get(), &LockedPointer::locked, this, [this]() {
0315         m_hasPointerLock = true;
0316         updateWindowTitle();
0317         updateCursorLayer();
0318         Q_EMIT m_backend->pointerLockChanged(true);
0319     });
0320     connect(m_pointerLock.get(), &LockedPointer::unlocked, this, [this]() {
0321         m_pointerLock.reset();
0322         m_hasPointerLock = false;
0323         updateWindowTitle();
0324         updateCursorLayer();
0325         Q_EMIT m_backend->pointerLockChanged(false);
0326     });
0327 }
0328 
0329 }
0330 }
0331 
0332 #include "moc_wayland_output.cpp"