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