1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwaylandscreen_p.h"
5
6#include "qwaylanddisplay_p.h"
7#include "qwaylandintegration_p.h"
8#include "qwaylandcursor_p.h"
9#include "qwaylandwindow_p.h"
10
11#include <QtGui/QGuiApplication>
12
13#include <qpa/qwindowsysteminterface.h>
14#include <qpa/qplatformwindow.h>
15
16QT_BEGIN_NAMESPACE
17
18namespace QtWaylandClient {
19
20QWaylandXdgOutputManagerV1::QWaylandXdgOutputManagerV1(QWaylandDisplay* display, uint id, uint version)
21 : QtWayland::zxdg_output_manager_v1(display->wl_registry(), id, qMin(3u, version))
22{
23}
24
25QWaylandXdgOutputManagerV1::~QWaylandXdgOutputManagerV1()
26{
27 destroy();
28}
29
30QWaylandScreen::QWaylandScreen(QWaylandDisplay *waylandDisplay, int version, uint32_t id)
31 : QtWayland::wl_output(waylandDisplay->wl_registry(), id, qMin(version, 4))
32 , m_outputId(id)
33 , mWaylandDisplay(waylandDisplay)
34 , mOutputName(QStringLiteral("Screen%1").arg(a: id))
35{
36 if (auto *xdgOutputManager = waylandDisplay->xdgOutputManager())
37 initXdgOutput(xdgOutputManager);
38
39 if (version < WL_OUTPUT_DONE_SINCE_VERSION) {
40 qCWarning(lcQpaWayland) << "wl_output done event not supported by compositor,"
41 << "QScreen may not work correctly";
42 mWaylandDisplay->forceRoundTrip(); // Give the compositor a chance to send geometry etc.
43 mProcessedEvents |= OutputDoneEvent; // Fake the done event
44 maybeInitialize();
45 }
46}
47
48QWaylandScreen::~QWaylandScreen()
49{
50 if (zxdg_output_v1::isInitialized())
51 zxdg_output_v1::destroy();
52 if (wl_output::version() >= WL_OUTPUT_RELEASE_SINCE_VERSION)
53 wl_output::release();
54 else
55 wl_output_destroy(wl_output::object());
56}
57
58uint QWaylandScreen::requiredEvents() const
59{
60 uint ret = OutputDoneEvent;
61
62 if (mWaylandDisplay->xdgOutputManager()) {
63 if (mWaylandDisplay->xdgOutputManager()->version() >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)
64 ret |= XdgOutputNameEvent;
65
66 // For objects version 3 onwards, zxdg_output_v1.done is deprecated.
67 if (mWaylandDisplay->xdgOutputManager()->version() < 3)
68 ret |= XdgOutputDoneEvent;
69 }
70 return ret;
71}
72
73void QWaylandScreen::maybeInitialize()
74{
75 Q_ASSERT(!mInitialized);
76
77 const uint requiredEvents = this->requiredEvents();
78 if ((mProcessedEvents & requiredEvents) != requiredEvents)
79 return;
80
81 mInitialized = true;
82 mWaylandDisplay->handleScreenInitialized(screen: this);
83
84 updateOutputProperties();
85 if (zxdg_output_v1::isInitialized())
86 updateXdgOutputProperties();
87}
88
89void QWaylandScreen::initXdgOutput(QWaylandXdgOutputManagerV1 *xdgOutputManager)
90{
91 Q_ASSERT(xdgOutputManager);
92 if (zxdg_output_v1::isInitialized())
93 return;
94
95 zxdg_output_v1::init(xdgOutputManager->get_xdg_output(wl_output::object()));
96}
97
98QWaylandDisplay * QWaylandScreen::display() const
99{
100 return mWaylandDisplay;
101}
102
103QString QWaylandScreen::manufacturer() const
104{
105 return mManufacturer;
106}
107
108QString QWaylandScreen::model() const
109{
110 return mModel;
111}
112
113QRect QWaylandScreen::geometry() const
114{
115 if (zxdg_output_v1::isInitialized()) {
116
117 // Workaround for Gnome bug
118 // https://gitlab.gnome.org/GNOME/mutter/-/issues/2631
119 // which sends an incorrect xdg geometry
120 const bool xdgGeometryIsBogus = mScale > 1 && mXdgGeometry.size() == mGeometry.size();
121
122 if (!xdgGeometryIsBogus) {
123 return mXdgGeometry;
124 }
125 }
126 // Scale geometry for QScreen. This makes window and screen
127 // geometry be in the same coordinate system.
128 return QRect(mGeometry.topLeft(), mGeometry.size() / mScale);
129}
130
131int QWaylandScreen::depth() const
132{
133 return mDepth;
134}
135
136QImage::Format QWaylandScreen::format() const
137{
138 return mFormat;
139}
140
141QSizeF QWaylandScreen::physicalSize() const
142{
143 if (mPhysicalSize.isEmpty())
144 return QPlatformScreen::physicalSize();
145 else
146 return mPhysicalSize;
147}
148
149QDpi QWaylandScreen::logicalDpi() const
150{
151 static bool physicalDpi = qEnvironmentVariable(varName: "QT_WAYLAND_FORCE_DPI") == QStringLiteral("physical");
152 if (physicalDpi)
153 return QPlatformScreen::logicalDpi();
154
155 static int forceDpi = qgetenv(varName: "QT_WAYLAND_FORCE_DPI").toInt();
156 if (forceDpi)
157 return QDpi(forceDpi, forceDpi);
158
159 return QDpi(96, 96);
160}
161
162QList<QPlatformScreen *> QWaylandScreen::virtualSiblings() const
163{
164 QList<QPlatformScreen *> list;
165 const QList<QWaylandScreen*> screens = mWaylandDisplay->screens();
166 auto *placeholder = mWaylandDisplay->placeholderScreen();
167
168 list.reserve(asize: screens.size() + (placeholder ? 1 : 0));
169
170 for (QWaylandScreen *screen : std::as_const(t: screens)) {
171 if (screen->screen())
172 list << screen;
173 }
174
175 if (placeholder)
176 list << placeholder;
177
178 return list;
179}
180
181Qt::ScreenOrientation QWaylandScreen::orientation() const
182{
183 return m_orientation;
184}
185
186int QWaylandScreen::scale() const
187{
188 return mScale;
189}
190
191qreal QWaylandScreen::devicePixelRatio() const
192{
193 return qreal(mScale);
194}
195
196qreal QWaylandScreen::refreshRate() const
197{
198 return mRefreshRate / 1000.f;
199}
200
201#if QT_CONFIG(cursor)
202QPlatformCursor *QWaylandScreen::cursor() const
203{
204 return mWaylandDisplay->waylandCursor();
205}
206#endif // QT_CONFIG(cursor)
207
208QPlatformScreen::SubpixelAntialiasingType QWaylandScreen::subpixelAntialiasingTypeHint() const
209{
210 QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint();
211 if (type == QPlatformScreen::Subpixel_None) {
212 switch (mSubpixel) {
213 case wl_output::subpixel_unknown:
214 case wl_output::subpixel_none:
215 type = QPlatformScreen::Subpixel_None;
216 break;
217 case wl_output::subpixel_horizontal_rgb:
218 type = QPlatformScreen::Subpixel_RGB;
219 break;
220 case wl_output::subpixel_horizontal_bgr:
221 type = QPlatformScreen::Subpixel_BGR;
222 break;
223 case wl_output::subpixel_vertical_rgb:
224 type = QPlatformScreen::Subpixel_VRGB;
225 break;
226 case wl_output::subpixel_vertical_bgr:
227 type = QPlatformScreen::Subpixel_VBGR;
228 break;
229 }
230 }
231 return type;
232}
233
234QWaylandScreen *QWaylandScreen::waylandScreenFromWindow(QWindow *window)
235{
236 QPlatformScreen *platformScreen = QPlatformScreen::platformScreenForWindow(window);
237 if (platformScreen->isPlaceholder())
238 return nullptr;
239 return static_cast<QWaylandScreen *>(platformScreen);
240}
241
242QWaylandScreen *QWaylandScreen::fromWlOutput(::wl_output *output)
243{
244 if (auto *o = QtWayland::wl_output::fromObject(output))
245 return static_cast<QWaylandScreen *>(o);
246 return nullptr;
247}
248
249Qt::ScreenOrientation QWaylandScreen::toScreenOrientation(int wlTransform,
250 Qt::ScreenOrientation fallback) const
251{
252 auto orientation = fallback;
253 bool isPortrait = mGeometry.height() > mGeometry.width();
254 switch (wlTransform) {
255 case WL_OUTPUT_TRANSFORM_NORMAL:
256 orientation = isPortrait ? Qt::PortraitOrientation : Qt::LandscapeOrientation;
257 break;
258 case WL_OUTPUT_TRANSFORM_90:
259 orientation = isPortrait ? Qt::InvertedLandscapeOrientation : Qt::PortraitOrientation;
260 break;
261 case WL_OUTPUT_TRANSFORM_180:
262 orientation = isPortrait ? Qt::InvertedPortraitOrientation : Qt::InvertedLandscapeOrientation;
263 break;
264 case WL_OUTPUT_TRANSFORM_270:
265 orientation = isPortrait ? Qt::LandscapeOrientation : Qt::InvertedPortraitOrientation;
266 break;
267 // Ignore these ones, at least for now
268 case WL_OUTPUT_TRANSFORM_FLIPPED:
269 case WL_OUTPUT_TRANSFORM_FLIPPED_90:
270 case WL_OUTPUT_TRANSFORM_FLIPPED_180:
271 case WL_OUTPUT_TRANSFORM_FLIPPED_270:
272 break;
273 }
274
275 return orientation;
276}
277
278void QWaylandScreen::output_mode(uint32_t flags, int width, int height, int refresh)
279{
280 if (!(flags & WL_OUTPUT_MODE_CURRENT))
281 return;
282
283 QSize size(width, height);
284 if (size != mGeometry.size())
285 mGeometry.setSize(size);
286
287 if (refresh != mRefreshRate)
288 mRefreshRate = refresh;
289}
290
291void QWaylandScreen::output_geometry(int32_t x, int32_t y,
292 int32_t width, int32_t height,
293 int subpixel,
294 const QString &make,
295 const QString &model,
296 int32_t transform)
297{
298 mManufacturer = make;
299 mModel = model;
300
301 mSubpixel = subpixel;
302 mTransform = transform;
303
304 mPhysicalSize = QSize(width, height);
305 mGeometry.moveTopLeft(p: QPoint(x, y));
306}
307
308void QWaylandScreen::output_scale(int32_t factor)
309{
310 mScale = factor;
311}
312
313void QWaylandScreen::output_done()
314{
315 mProcessedEvents |= OutputDoneEvent;
316
317 if (mInitialized) {
318 updateOutputProperties();
319 if (zxdg_output_v1::isInitialized())
320 updateXdgOutputProperties();
321 } else {
322 maybeInitialize();
323 }
324}
325
326void QWaylandScreen::output_name(const QString &name)
327{
328 if (Q_UNLIKELY(mInitialized)) {
329 qCWarning(lcQpaWayland) << "wl_output.name received after output has been initialized, this is most likely a bug in the compositor";
330 return;
331 }
332
333 if (Q_UNLIKELY(mProcessedEvents & OutputNameEvent)) {
334 qCWarning(lcQpaWayland) << "wl_output.name received more than once, this is most likely a bug in the compositor";
335 return;
336 }
337
338 if (!name.isEmpty())
339 mOutputName = name;
340
341 mProcessedEvents |= OutputNameEvent;
342}
343
344void QWaylandScreen::updateOutputProperties()
345{
346 Q_ASSERT(mInitialized);
347
348 if (mTransform >= 0) {
349 auto newOrientation = toScreenOrientation(wlTransform: mTransform, fallback: m_orientation);
350 if (m_orientation != newOrientation) {
351 m_orientation = newOrientation;
352 QWindowSystemInterface::handleScreenOrientationChange(screen: screen(), newOrientation: m_orientation);
353 }
354 mTransform = -1;
355 }
356
357 QWindowSystemInterface::handleScreenRefreshRateChange(screen: screen(), newRefreshRate: refreshRate());
358
359 if (!zxdg_output_v1::isInitialized())
360 QWindowSystemInterface::handleScreenGeometryChange(screen: screen(), newGeometry: geometry(), newAvailableGeometry: geometry());
361}
362
363
364void QWaylandScreen::zxdg_output_v1_logical_position(int32_t x, int32_t y)
365{
366 mXdgGeometry.moveTopLeft(p: QPoint(x, y));
367}
368
369void QWaylandScreen::zxdg_output_v1_logical_size(int32_t width, int32_t height)
370{
371 mXdgGeometry.setSize(QSize(width, height));
372}
373
374void QWaylandScreen::zxdg_output_v1_done()
375{
376 if (Q_UNLIKELY(mWaylandDisplay->xdgOutputManager()->version() >= 3))
377 qCWarning(lcQpaWayland) << "zxdg_output_v1.done received on version 3 or newer, this is most likely a bug in the compositor";
378
379 mProcessedEvents |= XdgOutputDoneEvent;
380 if (mInitialized)
381 updateXdgOutputProperties();
382 else
383 maybeInitialize();
384}
385
386void QWaylandScreen::zxdg_output_v1_name(const QString &name)
387{
388 if (Q_UNLIKELY(mInitialized))
389 qCWarning(lcQpaWayland) << "zxdg_output_v1.name received after output has been initialized, this is most likely a bug in the compositor";
390
391 if (Q_UNLIKELY(mProcessedEvents & XdgOutputNameEvent)) {
392 qCWarning(lcQpaWayland) << "zxdg_output_v1.name received more than once, this is most likely a bug in the compositor";
393 return;
394 }
395
396 // This event is deprecated, instead clients should use wl_output.name.
397 if (!(mProcessedEvents & OutputNameEvent)) {
398 if (!name.isEmpty())
399 mOutputName = name;
400 }
401
402 mProcessedEvents |= XdgOutputNameEvent;
403}
404
405void QWaylandScreen::updateXdgOutputProperties()
406{
407 Q_ASSERT(mInitialized);
408 Q_ASSERT(zxdg_output_v1::isInitialized());
409 QWindowSystemInterface::handleScreenGeometryChange(screen: screen(), newGeometry: geometry(), newAvailableGeometry: geometry());
410}
411
412} // namespace QtWaylandClient
413
414QT_END_NAMESPACE
415

source code of qtbase/src/plugins/platforms/wayland/qwaylandscreen.cpp