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"