1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 #include "wayland_output.h"
10 #include "renderloop.h"
11 #include "wayland_backend.h"
12 #include "wayland_server.h"
13 
14 #include <KWayland/Client/pointerconstraints.h>
15 #include <KWayland/Client/surface.h>
16 
17 #include <KWaylandServer/display.h>
18 
19 #include <KLocalizedString>
20 
21 namespace KWin
22 {
23 namespace Wayland
24 {
25 
26 using namespace KWayland::Client;
27 
WaylandOutput(Surface * surface,WaylandBackend * backend)28 WaylandOutput::WaylandOutput(Surface *surface, WaylandBackend *backend)
29     : AbstractWaylandOutput(backend)
30     , m_renderLoop(new RenderLoop(this))
31     , m_surface(surface)
32     , m_backend(backend)
33 {
34     static int identifier = -1;
35     identifier++;
36     setName("WL-" + QString::number(identifier));
37 
38     setCapabilityInternal(Capability::Dpms);
39     connect(surface, &Surface::frameRendered, this, [this] {
40         m_rendered = true;
41         Q_EMIT frameRendered();
42     });
43     m_turnOffTimer.setSingleShot(true);
44     m_turnOffTimer.setInterval(dimAnimationTime());
45     connect(&m_turnOffTimer, &QTimer::timeout, this, [this] {
46         setDpmsModeInternal(DpmsMode::Off);
47     });
48 }
49 
~WaylandOutput()50 WaylandOutput::~WaylandOutput()
51 {
52     m_surface->destroy();
53     delete m_surface;
54 }
55 
renderLoop() const56 RenderLoop *WaylandOutput::renderLoop() const
57 {
58     return m_renderLoop;
59 }
60 
init(const QPoint & logicalPosition,const QSize & pixelSize)61 void WaylandOutput::init(const QPoint &logicalPosition, const QSize &pixelSize)
62 {
63     const int refreshRate = 60000; // TODO: can we get refresh rate data from Wayland host?
64     m_renderLoop->setRefreshRate(refreshRate);
65 
66     Mode mode;
67     mode.id = 0;
68     mode.size = pixelSize;
69     mode.flags = ModeFlag::Current;
70     mode.refreshRate = refreshRate;
71     static uint i = 0;
72     initialize(QStringLiteral("model_%1").arg(i++), "manufacturer_TODO", "eisa_TODO", "serial_TODO", pixelSize, { mode }, {});
73     setGeometry(logicalPosition, pixelSize);
74     setScale(backend()->initialOutputScale());
75 }
76 
setGeometry(const QPoint & logicalPosition,const QSize & pixelSize)77 void WaylandOutput::setGeometry(const QPoint &logicalPosition, const QSize &pixelSize)
78 {
79     // TODO: set mode to have updated pixelSize
80     Q_UNUSED(pixelSize)
81 
82     moveTo(logicalPosition);
83 }
84 
updateTransform(Transform transform)85 void WaylandOutput::updateTransform(Transform transform)
86 {
87     setTransformInternal(transform);
88 }
89 
updateEnablement(bool enable)90 void WaylandOutput::updateEnablement(bool enable)
91 {
92     setDpmsMode(enable ? DpmsMode::On : DpmsMode::Off);
93 }
94 
setDpmsMode(KWin::AbstractWaylandOutput::DpmsMode mode)95 void WaylandOutput::setDpmsMode(KWin::AbstractWaylandOutput::DpmsMode mode)
96 {
97     if (mode == DpmsMode::Off) {
98         if (!m_turnOffTimer.isActive()) {
99             Q_EMIT aboutToTurnOff(std::chrono::milliseconds(m_turnOffTimer.interval()));
100             m_turnOffTimer.start();
101         }
102         m_backend->createDpmsFilter();
103     } else {
104         m_turnOffTimer.stop();
105         m_backend->clearDpmsFilter();
106 
107         if (mode != dpmsMode()) {
108             setDpmsModeInternal(mode);
109             Q_EMIT wakeUp();
110         }
111     }
112 }
113 
XdgShellOutput(Surface * surface,XdgShell * xdgShell,WaylandBackend * backend,int number)114 XdgShellOutput::XdgShellOutput(Surface *surface, XdgShell *xdgShell, WaylandBackend *backend, int number)
115     : WaylandOutput(surface, backend)
116     , m_number(number)
117 {
118     m_xdgShellSurface = xdgShell->createSurface(surface, this);
119     updateWindowTitle();
120 
121     connect(m_xdgShellSurface, &XdgShellSurface::configureRequested, this, &XdgShellOutput::handleConfigure);
122     connect(m_xdgShellSurface, &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit);
123 
124     connect(backend, &WaylandBackend::pointerLockSupportedChanged, this, &XdgShellOutput::updateWindowTitle);
125     connect(backend, &WaylandBackend::pointerLockChanged, this, [this](bool locked) {
126         if (locked) {
127             if (!m_hasPointerLock) {
128                 // some other output has locked the pointer
129                 // this surface can stop trying to lock the pointer
130                 lockPointer(nullptr, false);
131                 // set it true for the other surface
132                 m_hasPointerLock = true;
133             }
134         } else {
135             // just try unlocking
136             lockPointer(nullptr, false);
137         }
138         updateWindowTitle();
139     });
140 
141     surface->commit(KWayland::Client::Surface::CommitFlag::None);
142 }
143 
~XdgShellOutput()144 XdgShellOutput::~XdgShellOutput()
145 {
146     m_xdgShellSurface->destroy();
147     delete m_xdgShellSurface;
148 }
149 
handleConfigure(const QSize & size,XdgShellSurface::States states,quint32 serial)150 void XdgShellOutput::handleConfigure(const QSize &size, XdgShellSurface::States states, quint32 serial)
151 {
152     Q_UNUSED(states);
153     m_xdgShellSurface->ackConfigure(serial);
154     if (size.width() > 0 && size.height() > 0) {
155         setGeometry(geometry().topLeft(), size);
156         if (m_hasBeenConfigured) {
157             Q_EMIT sizeChanged(size);
158         }
159     }
160 
161     if (!m_hasBeenConfigured) {
162         m_hasBeenConfigured = true;
163         backend()->addConfiguredOutput(this);
164     }
165 }
166 
updateWindowTitle()167 void XdgShellOutput::updateWindowTitle()
168 {
169     QString grab;
170     if (m_hasPointerLock) {
171         grab = i18n("Press right control to ungrab pointer");
172     } else if (backend()->pointerConstraints()) {
173         grab = i18n("Press right control key to grab pointer");
174     }
175     const QString title = i18nc("Title of nested KWin Wayland with Wayland socket identifier as argument",
176                                 "KDE Wayland Compositor #%1 (%2)", m_number, waylandServer()->socketName());
177 
178     if (grab.isEmpty()) {
179         m_xdgShellSurface->setTitle(title);
180     } else {
181         m_xdgShellSurface->setTitle(title + QStringLiteral(" — ") + grab);
182     }
183 }
184 
lockPointer(Pointer * pointer,bool lock)185 void XdgShellOutput::lockPointer(Pointer *pointer, bool lock)
186 {
187     if (!lock) {
188         const bool surfaceWasLocked = m_pointerLock && m_hasPointerLock;
189         delete m_pointerLock;
190         m_pointerLock = nullptr;
191         m_hasPointerLock = false;
192         if (surfaceWasLocked) {
193             Q_EMIT backend()->pointerLockChanged(false);
194         }
195         return;
196     }
197 
198     Q_ASSERT(!m_pointerLock);
199     m_pointerLock = backend()->pointerConstraints()->lockPointer(surface(), pointer, nullptr,
200                                                                  PointerConstraints::LifeTime::OneShot,
201                                                                  this);
202     if (!m_pointerLock->isValid()) {
203         delete m_pointerLock;
204         m_pointerLock = nullptr;
205         return;
206     }
207     connect(m_pointerLock, &LockedPointer::locked, this,
208         [this] {
209             m_hasPointerLock = true;
210             Q_EMIT backend()->pointerLockChanged(true);
211         }
212     );
213     connect(m_pointerLock, &LockedPointer::unlocked, this,
214         [this] {
215             delete m_pointerLock;
216             m_pointerLock = nullptr;
217             m_hasPointerLock = false;
218             Q_EMIT backend()->pointerLockChanged(false);
219         }
220     );
221 }
222 
223 }
224 }
225