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 "qwaylanddisplay_p.h" |
5 | |
6 | #include "qwaylandintegration_p.h" |
7 | #include "qwaylandwindow_p.h" |
8 | #include "qwaylandsurface_p.h" |
9 | #include "qwaylandabstractdecoration_p.h" |
10 | #include "qwaylandscreen_p.h" |
11 | #include "qwaylandcursor_p.h" |
12 | #include "qwaylandinputdevice_p.h" |
13 | #if QT_CONFIG(clipboard) |
14 | #include "qwaylandclipboard_p.h" |
15 | #endif |
16 | #if QT_CONFIG(wayland_datadevice) |
17 | #include "qwaylanddatadevicemanager_p.h" |
18 | #include "qwaylanddatadevice_p.h" |
19 | #endif // QT_CONFIG(wayland_datadevice) |
20 | #if QT_CONFIG(wayland_client_primary_selection) |
21 | #include "qwaylandprimaryselectionv1_p.h" |
22 | #endif // QT_CONFIG(wayland_client_primary_selection) |
23 | #if QT_CONFIG(cursor) |
24 | #include <wayland-cursor.h> |
25 | #endif |
26 | #include "qwaylandhardwareintegration_p.h" |
27 | #include "qwaylandtextinputv1_p.h" |
28 | #include "qwaylandtextinputv2_p.h" |
29 | #if QT_WAYLAND_TEXT_INPUT_V4_WIP |
30 | #include "qwaylandtextinputv4_p.h" |
31 | #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP |
32 | #include "qwaylandinputcontext_p.h" |
33 | #include "qwaylandinputmethodcontext_p.h" |
34 | |
35 | #include "qwaylandwindowmanagerintegration_p.h" |
36 | #include "qwaylandshellintegration_p.h" |
37 | #include "qwaylandclientbufferintegration_p.h" |
38 | |
39 | #include "qwaylandextendedsurface_p.h" |
40 | #include "qwaylandpointergestures_p.h" |
41 | #include "qwaylandsubsurface_p.h" |
42 | #include "qwaylandtouch_p.h" |
43 | #if QT_CONFIG(tabletevent) |
44 | #include "qwaylandtabletv2_p.h" |
45 | #endif |
46 | #include "qwaylandqtkey_p.h" |
47 | |
48 | #include <QtWaylandClient/private/qwayland-text-input-unstable-v1.h> |
49 | #include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h> |
50 | #include <QtWaylandClient/private/qwayland-text-input-unstable-v4-wip.h> |
51 | #include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h> |
52 | #include <QtWaylandClient/private/qwayland-qt-text-input-method-unstable-v1.h> |
53 | #include <QtWaylandClient/private/qwayland-fractional-scale-v1.h> |
54 | #include <QtWaylandClient/private/qwayland-viewporter.h> |
55 | #include <QtWaylandClient/private/qwayland-cursor-shape-v1.h> |
56 | #include <QtWaylandClient/private/qwayland-qt-toplevel-drag-v1.h> |
57 | |
58 | #include <QtCore/private/qcore_unix_p.h> |
59 | |
60 | #include <QtCore/QAbstractEventDispatcher> |
61 | #include <QtGui/qpa/qwindowsysteminterface.h> |
62 | #include <QtGui/private/qguiapplication_p.h> |
63 | |
64 | #include <QtCore/QDebug> |
65 | |
66 | #include <errno.h> |
67 | |
68 | #include <tuple> // for std::tie |
69 | |
70 | QT_BEGIN_NAMESPACE |
71 | |
72 | namespace QtWaylandClient { |
73 | |
74 | class EventThread : public QThread |
75 | { |
76 | Q_OBJECT |
77 | public: |
78 | enum OperatingMode { |
79 | EmitToDispatch, // Emit the signal, allow dispatching in a differnt thread. |
80 | SelfDispatch, // Dispatch the events inside this thread. |
81 | }; |
82 | |
83 | EventThread(struct wl_display * wl, struct wl_event_queue * ev_queue, |
84 | OperatingMode mode) |
85 | : m_fd(wl_display_get_fd(wl)) |
86 | , m_pipefd{ -1, -1 } |
87 | , m_wldisplay(wl) |
88 | , m_wlevqueue(ev_queue) |
89 | , m_mode(mode) |
90 | , m_reading(true) |
91 | , m_quitting(false) |
92 | { |
93 | setObjectName(QStringLiteral("WaylandEventThread" )); |
94 | } |
95 | |
96 | void readAndDispatchEvents() |
97 | { |
98 | /* |
99 | * Dispatch pending events and flush the requests at least once. If the event thread |
100 | * is not reading, try to call _prepare_read() to allow the event thread to poll(). |
101 | * If that fails, re-try dispatch & flush again until _prepare_read() is successful. |
102 | * |
103 | * This allow any call to readAndDispatchEvents() to start event thread's polling, |
104 | * not only the one issued from event thread's waitForReading(), which means functions |
105 | * called from dispatch_pending() can safely spin an event loop. |
106 | */ |
107 | if (m_quitting) |
108 | return; |
109 | |
110 | for (;;) { |
111 | if (dispatchQueuePending() < 0) { |
112 | Q_EMIT waylandError(); |
113 | m_quitting = true; |
114 | return; |
115 | } |
116 | |
117 | wl_display_flush(m_wldisplay); |
118 | |
119 | // We have to check if event thread is reading every time we dispatch |
120 | // something, as that may recursively call this function. |
121 | if (m_reading.loadAcquire()) |
122 | break; |
123 | |
124 | if (prepareReadQueue() == 0) { |
125 | QMutexLocker l(&m_mutex); |
126 | m_reading.storeRelease(newValue: true); |
127 | m_cond.wakeOne(); |
128 | break; |
129 | } |
130 | } |
131 | } |
132 | |
133 | void stop() |
134 | { |
135 | // We have to both write to the pipe and set the flag, as the thread may be |
136 | // either in the poll() or waiting for _prepare_read(). |
137 | if (m_pipefd[1] != -1 && write(fd: m_pipefd[1], buf: "\0" , n: 1) == -1) |
138 | qWarning(msg: "Failed to write to the pipe: %s." , strerror(errno)); |
139 | |
140 | { |
141 | QMutexLocker l(&m_mutex); |
142 | m_quitting = true; |
143 | m_cond.wakeOne(); |
144 | } |
145 | |
146 | wait(); |
147 | } |
148 | |
149 | Q_SIGNALS: |
150 | void needReadAndDispatch(); |
151 | void waylandError(); |
152 | |
153 | protected: |
154 | void run() override |
155 | { |
156 | // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets |
157 | // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore. |
158 | struct Pipe |
159 | { |
160 | Pipe(int *fds) |
161 | : fds(fds) |
162 | { |
163 | if (qt_safe_pipe(pipefd: fds) != 0) |
164 | qWarning(msg: "Pipe creation failed. Quitting may hang." ); |
165 | } |
166 | ~Pipe() |
167 | { |
168 | if (fds[0] != -1) { |
169 | close(fd: fds[0]); |
170 | close(fd: fds[1]); |
171 | } |
172 | } |
173 | |
174 | int *fds; |
175 | } pipe(m_pipefd); |
176 | |
177 | // Make the main thread call wl_prepare_read(), dispatch the pending messages and flush the |
178 | // outbound ones. Wait until it's done before proceeding, unless we're told to quit. |
179 | while (waitForReading()) { |
180 | if (!m_reading.loadRelaxed()) |
181 | break; |
182 | |
183 | pollfd fds[2] = { { .fd: m_fd, POLLIN, .revents: 0 }, { .fd: m_pipefd[0], POLLIN, .revents: 0 } }; |
184 | poll(fds: fds, nfds: 2, timeout: -1); |
185 | |
186 | if (fds[1].revents & POLLIN) { |
187 | // we don't really care to read the byte that was written here since we're closing down |
188 | wl_display_cancel_read(m_wldisplay); |
189 | break; |
190 | } |
191 | |
192 | if (fds[0].revents & POLLIN) |
193 | wl_display_read_events(m_wldisplay); |
194 | // The polll was succesfull and the event thread did the wl_display_read_events(). On the next iteration of the loop |
195 | // the event sent to the main thread will cause it to dispatch the messages just read, unless the loop exits in which |
196 | // case we don't care anymore about them. |
197 | else |
198 | wl_display_cancel_read(m_wldisplay); |
199 | } |
200 | } |
201 | |
202 | private: |
203 | bool waitForReading() |
204 | { |
205 | Q_ASSERT(QThread::currentThread() == this); |
206 | |
207 | m_reading.storeRelease(newValue: false); |
208 | |
209 | if (m_mode == SelfDispatch) { |
210 | readAndDispatchEvents(); |
211 | } else { |
212 | Q_EMIT needReadAndDispatch(); |
213 | |
214 | QMutexLocker lock(&m_mutex); |
215 | // m_reading might be set from our emit or some other invocation of |
216 | // readAndDispatchEvents(). |
217 | while (!m_reading.loadRelaxed() && !m_quitting) |
218 | m_cond.wait(lockedMutex: &m_mutex); |
219 | } |
220 | |
221 | return !m_quitting; |
222 | } |
223 | |
224 | int dispatchQueuePending() |
225 | { |
226 | if (m_wlevqueue) |
227 | return wl_display_dispatch_queue_pending(m_wldisplay, m_wlevqueue); |
228 | else |
229 | return wl_display_dispatch_pending(m_wldisplay); |
230 | } |
231 | |
232 | int prepareReadQueue() |
233 | { |
234 | if (m_wlevqueue) |
235 | return wl_display_prepare_read_queue(m_wldisplay, m_wlevqueue); |
236 | else |
237 | return wl_display_prepare_read(m_wldisplay); |
238 | } |
239 | |
240 | int m_fd; |
241 | int m_pipefd[2]; |
242 | wl_display *m_wldisplay; |
243 | wl_event_queue *m_wlevqueue; |
244 | OperatingMode m_mode; |
245 | |
246 | /* Concurrency note when operating in EmitToDispatch mode: |
247 | * m_reading is set to false inside event thread's waitForReading(), and is |
248 | * set to true inside main thread's readAndDispatchEvents(). |
249 | * The lock is not taken when setting m_reading to false, as the main thread |
250 | * is not actively waiting for it to turn false. However, the lock is taken |
251 | * inside readAndDispatchEvents() before setting m_reading to true, |
252 | * as the event thread is actively waiting for it under the wait condition. |
253 | */ |
254 | |
255 | QAtomicInteger<bool> m_reading; |
256 | bool m_quitting; |
257 | QMutex m_mutex; |
258 | QWaitCondition m_cond; |
259 | }; |
260 | |
261 | Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland" ); // for general (uncategorized) Wayland platform logging |
262 | |
263 | struct wl_surface *QWaylandDisplay::createSurface(void *handle) |
264 | { |
265 | struct wl_surface *surface = mCompositor.create_surface(); |
266 | wl_surface_set_user_data(surface, handle); |
267 | return surface; |
268 | } |
269 | |
270 | struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) |
271 | { |
272 | struct ::wl_region *region = mCompositor.create_region(); |
273 | |
274 | for (const QRect &rect : qregion) |
275 | wl_region_add(region, rect.x(), rect.y(), rect.width(), rect.height()); |
276 | |
277 | return region; |
278 | } |
279 | |
280 | ::wl_subsurface *QWaylandDisplay::createSubSurface(QWaylandWindow *window, QWaylandWindow *parent) |
281 | { |
282 | if (!mSubCompositor) { |
283 | qCWarning(lcQpaWayland) << "Can't create subsurface, not supported by the compositor." ; |
284 | return nullptr; |
285 | } |
286 | |
287 | // Make sure we don't pass NULL surfaces to libwayland (crashes) |
288 | Q_ASSERT(parent->wlSurface()); |
289 | Q_ASSERT(window->wlSurface()); |
290 | |
291 | return mSubCompositor->get_subsurface(window->wlSurface(), parent->wlSurface()); |
292 | } |
293 | |
294 | ::wp_viewport *QWaylandDisplay::createViewport(QWaylandWindow *window) |
295 | { |
296 | if (!mViewporter) { |
297 | qCWarning(lcQpaWayland) << "Can't create wp_viewport, not supported by the compositor." ; |
298 | return nullptr; |
299 | } |
300 | |
301 | Q_ASSERT(window->wlSurface()); |
302 | return mViewporter->get_viewport(window->wlSurface()); |
303 | } |
304 | |
305 | QWaylandShellIntegration *QWaylandDisplay::shellIntegration() const |
306 | { |
307 | return mWaylandIntegration->shellIntegration(); |
308 | } |
309 | |
310 | QWaylandClientBufferIntegration * QWaylandDisplay::clientBufferIntegration() const |
311 | { |
312 | return mWaylandIntegration->clientBufferIntegration(); |
313 | } |
314 | |
315 | QWaylandWindowManagerIntegration *QWaylandDisplay::windowManagerIntegration() const |
316 | { |
317 | return mWindowManagerIntegration.data(); |
318 | } |
319 | |
320 | QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) |
321 | : mWaylandIntegration(waylandIntegration) |
322 | { |
323 | qRegisterMetaType<uint32_t>(typeName: "uint32_t" ); |
324 | |
325 | mDisplay = wl_display_connect(nullptr); |
326 | if (mDisplay) { |
327 | setupConnection(); |
328 | } else { |
329 | qErrnoWarning(errno, msg: "Failed to create wl_display" ); |
330 | } |
331 | |
332 | mWaylandTryReconnect = qEnvironmentVariableIsSet(varName: "QT_WAYLAND_RECONNECT" ); |
333 | } |
334 | |
335 | void QWaylandDisplay::setupConnection() |
336 | { |
337 | struct ::wl_registry *registry = wl_display_get_registry(mDisplay); |
338 | init(registry); |
339 | |
340 | mWindowManagerIntegration.reset(other: new QWaylandWindowManagerIntegration(this)); |
341 | |
342 | #if QT_CONFIG(xkbcommon) |
343 | mXkbContext.reset(p: xkb_context_new(flags: XKB_CONTEXT_NO_FLAGS)); |
344 | if (!mXkbContext) |
345 | qCWarning(lcQpaWayland, "failed to create xkb context" ); |
346 | #endif |
347 | if (!mClientSideInputContextRequested) |
348 | checkTextInputProtocol(); |
349 | } |
350 | |
351 | QWaylandDisplay::~QWaylandDisplay(void) |
352 | { |
353 | if (m_eventThread) |
354 | m_eventThread->stop(); |
355 | |
356 | if (m_frameEventQueueThread) |
357 | m_frameEventQueueThread->stop(); |
358 | |
359 | if (mSyncCallback) |
360 | wl_callback_destroy(mSyncCallback); |
361 | |
362 | qDeleteAll(c: std::exchange(obj&: mInputDevices, new_val: {})); |
363 | |
364 | for (QWaylandScreen *screen : std::exchange(obj&: mScreens, new_val: {})) { |
365 | QWindowSystemInterface::handleScreenRemoved(screen); |
366 | } |
367 | qDeleteAll(c: mWaitingScreens); |
368 | |
369 | #if QT_CONFIG(wayland_datadevice) |
370 | mDndSelectionHandler.reset(); |
371 | #endif |
372 | #if QT_CONFIG(cursor) |
373 | mCursorThemes.clear(); |
374 | #endif |
375 | |
376 | if (m_frameEventQueue) |
377 | wl_event_queue_destroy(m_frameEventQueue); |
378 | |
379 | if (mDisplay) |
380 | wl_display_disconnect(mDisplay); |
381 | } |
382 | |
383 | // Steps which is called just after constructor. This separates registry_global() out of the constructor |
384 | // so that factory functions in integration can be overridden. |
385 | bool QWaylandDisplay::initialize() |
386 | { |
387 | if (!isInitialized()) |
388 | return false; |
389 | |
390 | forceRoundTrip(); |
391 | |
392 | if (!mWaitingScreens.isEmpty()) { |
393 | // Give wl_output.done and zxdg_output_v1.done events a chance to arrive |
394 | forceRoundTrip(); |
395 | } |
396 | if (!mClientSideInputContextRequested) |
397 | mTextInputManagerIndex = INT_MAX; |
398 | |
399 | return qEnvironmentVariableIntValue(varName: "QT_WAYLAND_DONT_CHECK_SHELL_INTEGRATION" ) || shellIntegration(); |
400 | } |
401 | |
402 | void QWaylandDisplay::ensureScreen() |
403 | { |
404 | if (!mScreens.empty() || mPlaceholderScreen) |
405 | return; // There are real screens or we already have a fake one |
406 | |
407 | qCInfo(lcQpaWayland) << "Creating a fake screen in order for Qt not to crash" ; |
408 | |
409 | mPlaceholderScreen = new QPlatformPlaceholderScreen(); |
410 | QWindowSystemInterface::handleScreenAdded(screen: mPlaceholderScreen); |
411 | Q_ASSERT(!QGuiApplication::screens().empty()); |
412 | } |
413 | |
414 | void QWaylandDisplay::reconnect() |
415 | { |
416 | qCWarning(lcQpaWayland) << "Attempting wayland reconnect" ; |
417 | m_eventThread->stop(); |
418 | m_frameEventQueueThread->stop(); |
419 | m_eventThread->wait(); |
420 | m_frameEventQueueThread->wait(); |
421 | |
422 | qDeleteAll(c: mWaitingScreens); |
423 | mWaitingScreens.clear(); |
424 | |
425 | while (!mScreens.isEmpty()) { |
426 | auto screen = mScreens.takeLast(); |
427 | ensureScreen(); |
428 | QWindowSystemInterface::handleScreenRemoved(screen); |
429 | } |
430 | |
431 | // mCompositor |
432 | mShm.reset(); |
433 | mCursorThemes.clear(); |
434 | mCursor.reset(); |
435 | mDndSelectionHandler.reset(); |
436 | mWindowExtension.reset(); |
437 | mSubCompositor.reset(); |
438 | mTouchExtension.reset(); |
439 | mQtKeyExtension.reset(); |
440 | mWindowManagerIntegration.reset(); |
441 | mTabletManager.reset(); |
442 | mPointerGestures.reset(); |
443 | #if QT_CONFIG(wayland_client_primary_selection) |
444 | mPrimarySelectionManager.reset(); |
445 | #endif |
446 | mTextInputMethodManager.reset(); |
447 | mTextInputManagerv1.reset(); |
448 | mTextInputManagerv2.reset(); |
449 | mTextInputManagerv4.reset(); |
450 | mHardwareIntegration.reset(); |
451 | mXdgOutputManager.reset(); |
452 | mViewporter.reset(); |
453 | mFractionalScaleManager.reset(); |
454 | mCursorShapeManager.reset(); |
455 | mXdgToplevelDragManager.reset(); |
456 | |
457 | mWaylandIntegration->reset(); |
458 | |
459 | qDeleteAll(c: std::exchange(obj&: mInputDevices, new_val: {})); |
460 | mLastInputDevice = nullptr; |
461 | |
462 | for (const RegistryGlobal &global : mGlobals) { |
463 | emit globalRemoved(global); |
464 | } |
465 | mGlobals.clear(); |
466 | |
467 | mLastInputSerial = 0; |
468 | mLastInputWindow.clear(); |
469 | mLastKeyboardFocus.clear(); |
470 | mActiveWindows.clear(); |
471 | |
472 | const auto windows = QGuiApplication::allWindows(); |
473 | for (auto window : windows) { |
474 | if (auto waylandWindow = dynamic_cast<QWaylandWindow *>(window->handle())) |
475 | waylandWindow->closeChildPopups(); |
476 | } |
477 | // Remove windows that do not need to be recreated and now closed popups |
478 | QList<QWaylandWindow *> recreateWindows; |
479 | for (auto window : std::as_const(t: windows)) { |
480 | auto waylandWindow = dynamic_cast<QWaylandWindow*>((window)->handle()); |
481 | if (waylandWindow && waylandWindow->wlSurface()) { |
482 | waylandWindow->reset(); |
483 | recreateWindows.push_back(t: waylandWindow); |
484 | } |
485 | } |
486 | |
487 | if (mSyncCallback) { |
488 | wl_callback_destroy(mSyncCallback); |
489 | mSyncCallback = nullptr; |
490 | } |
491 | |
492 | mDisplay = wl_display_connect(nullptr); |
493 | if (!mDisplay) |
494 | _exit(status: 1); |
495 | |
496 | setupConnection(); |
497 | initialize(); |
498 | |
499 | if (m_frameEventQueue) |
500 | wl_event_queue_destroy(m_frameEventQueue); |
501 | initEventThread(); |
502 | |
503 | emit reconnected(); |
504 | |
505 | auto needsRecreate = [](QPlatformWindow *window) { |
506 | return window && !static_cast<QWaylandWindow *>(window)->wlSurface(); |
507 | }; |
508 | auto window = recreateWindows.begin(); |
509 | while (!recreateWindows.isEmpty()) { |
510 | if (!needsRecreate((*window)->QPlatformWindow::parent()) && !needsRecreate((*window)->transientParent())) { |
511 | (*window)->reinit(); |
512 | window = recreateWindows.erase(pos: window); |
513 | } else { |
514 | ++window; |
515 | } |
516 | if (window == recreateWindows.end()) |
517 | window = recreateWindows.begin(); |
518 | } |
519 | |
520 | mWaylandIntegration->reconfigureInputContext(); |
521 | } |
522 | |
523 | void QWaylandDisplay::flushRequests() |
524 | { |
525 | m_eventThread->readAndDispatchEvents(); |
526 | } |
527 | |
528 | // We have to wait until we have an eventDispatcher before creating the eventThread, |
529 | // otherwise forceRoundTrip() may block inside _events_read() because eventThread is |
530 | // polling. |
531 | void QWaylandDisplay::initEventThread() |
532 | { |
533 | m_eventThread.reset( |
534 | p: new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch)); |
535 | connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this, |
536 | &QWaylandDisplay::flushRequests, Qt::QueuedConnection); |
537 | connect(m_eventThread.get(), &EventThread::waylandError, this, |
538 | &QWaylandDisplay::checkWaylandError, Qt::QueuedConnection); |
539 | m_eventThread->start(); |
540 | |
541 | // wl_display_disconnect() free this. |
542 | m_frameEventQueue = wl_display_create_queue(mDisplay); |
543 | m_frameEventQueueThread.reset( |
544 | new EventThread(mDisplay, m_frameEventQueue, EventThread::SelfDispatch)); |
545 | m_frameEventQueueThread->start(); |
546 | } |
547 | |
548 | void QWaylandDisplay::checkWaylandError() |
549 | { |
550 | int ecode = wl_display_get_error(mDisplay); |
551 | if ((ecode == EPIPE || ecode == ECONNRESET)) { |
552 | qWarning(msg: "The Wayland connection broke. Did the Wayland compositor die?" ); |
553 | if (mWaylandTryReconnect) { |
554 | reconnect(); |
555 | return; |
556 | } |
557 | } else { |
558 | qWarning(msg: "The Wayland connection experienced a fatal error: %s" , strerror(errnum: ecode)); |
559 | } |
560 | _exit(status: -1); |
561 | } |
562 | |
563 | void QWaylandDisplay::blockingReadEvents() |
564 | { |
565 | if (wl_display_dispatch(mDisplay) < 0) { |
566 | int ecode = wl_display_get_error(mDisplay); |
567 | if ((ecode == EPIPE || ecode == ECONNRESET)) |
568 | qWarning(msg: "The Wayland connection broke during blocking read event. Did the Wayland compositor die?" ); |
569 | else |
570 | qWarning(msg: "The Wayland connection experienced a fatal error during blocking read event: %s" , strerror(errnum: ecode)); |
571 | _exit(status: -1); |
572 | } |
573 | } |
574 | |
575 | void QWaylandDisplay::checkTextInputProtocol() |
576 | { |
577 | QStringList tips, timps; // for text input protocols and text input manager protocols |
578 | tips << QLatin1String(QtWayland::qt_text_input_method_v1::interface()->name) |
579 | << QLatin1String(QtWayland::zwp_text_input_v2::interface()->name) |
580 | << QLatin1String(QtWayland::zwp_text_input_v1::interface()->name); |
581 | timps << QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name) |
582 | << QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name) |
583 | << QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name); |
584 | #if QT_WAYLAND_TEXT_INPUT_V4_WIP |
585 | tips << QLatin1String(QtWayland::zwp_text_input_v4::interface()->name); |
586 | timps << QLatin1String(QtWayland::zwp_text_input_manager_v4::interface()->name); |
587 | #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP |
588 | |
589 | QString tiProtocols = QString::fromLocal8Bit(ba: qgetenv(varName: "QT_WAYLAND_TEXT_INPUT_PROTOCOL" )); |
590 | qCDebug(lcQpaWayland) << "QT_WAYLAND_TEXT_INPUT_PROTOCOL=" << tiProtocols; |
591 | QStringList keys; |
592 | if (!tiProtocols.isEmpty()) { |
593 | keys = tiProtocols.split(sep: QLatin1Char(';')); |
594 | QList<QString>::iterator it = keys.begin(); |
595 | while (it != keys.end()) { |
596 | if (tips.contains(str: *it)) |
597 | mTextInputManagerList.append(t: timps.at(i: tips.indexOf(str: *it))); |
598 | else |
599 | qCDebug(lcQpaWayland) << "text input: unknown protocol - " << *it; |
600 | ++it; |
601 | } |
602 | } |
603 | if (mTextInputManagerList.isEmpty()) // fallback |
604 | mTextInputManagerList = timps; |
605 | } |
606 | |
607 | QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const |
608 | { |
609 | for (auto screen : std::as_const(t: mScreens)) { |
610 | if (screen->output() == output) |
611 | return screen; |
612 | } |
613 | return nullptr; |
614 | } |
615 | |
616 | void QWaylandDisplay::handleScreenInitialized(QWaylandScreen *screen) |
617 | { |
618 | if (!mWaitingScreens.removeOne(t: screen)) |
619 | return; |
620 | mScreens.append(t: screen); |
621 | QWindowSystemInterface::handleScreenAdded(screen); |
622 | if (mPlaceholderScreen) { |
623 | QWindowSystemInterface::handleScreenRemoved(screen: mPlaceholderScreen); |
624 | // handleScreenRemoved deletes the platform screen |
625 | mPlaceholderScreen = nullptr; |
626 | } |
627 | } |
628 | |
629 | void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uint32_t version) |
630 | { |
631 | struct ::wl_registry *registry = object(); |
632 | |
633 | static QStringList interfaceBlacklist = qEnvironmentVariable(varName: "QT_WAYLAND_DISABLED_INTERFACES" ).split(sep: u','); |
634 | if (interfaceBlacklist.contains(str: interface)) { |
635 | return; |
636 | } |
637 | |
638 | if (interface == QLatin1String(QtWayland::wl_output::interface()->name)) { |
639 | mWaitingScreens << mWaylandIntegration->createPlatformScreen(waylandDisplay: this, version, id); |
640 | } else if (interface == QLatin1String(QtWayland::wl_compositor::interface()->name)) { |
641 | mCompositor.init(registry, id, qMin((int)version, 4)); |
642 | } else if (interface == QLatin1String(QWaylandShm::interface()->name)) { |
643 | mShm.reset(other: new QWaylandShm(this, version, id)); |
644 | } else if (interface == QLatin1String(QWaylandInputDevice::interface()->name)) { |
645 | QWaylandInputDevice *inputDevice = mWaylandIntegration->createInputDevice(display: this, version, id); |
646 | mInputDevices.append(t: inputDevice); |
647 | #if QT_CONFIG(wayland_datadevice) |
648 | } else if (interface == QLatin1String(QWaylandDataDeviceManager::interface()->name)) { |
649 | mDndSelectionHandler.reset(other: new QWaylandDataDeviceManager(this, version, id)); |
650 | #endif |
651 | } else if (interface == QLatin1String(QtWayland::qt_surface_extension::interface()->name)) { |
652 | mWindowExtension.reset(new QtWayland::qt_surface_extension(registry, id, 1)); |
653 | } else if (interface == QLatin1String(QtWayland::wl_subcompositor::interface()->name)) { |
654 | mSubCompositor.reset(new QtWayland::wl_subcompositor(registry, id, 1)); |
655 | } else if (interface == QLatin1String(QWaylandTouchExtension::interface()->name)) { |
656 | mTouchExtension.reset(other: new QWaylandTouchExtension(this, id)); |
657 | } else if (interface == QLatin1String(QWaylandQtKeyExtension::interface()->name)) { |
658 | mQtKeyExtension.reset(other: new QWaylandQtKeyExtension(this, id)); |
659 | #if QT_CONFIG(tabletevent) |
660 | } else if (interface == QLatin1String(QWaylandTabletManagerV2::interface()->name)) { |
661 | mTabletManager.reset(other: new QWaylandTabletManagerV2(this, id, qMin(a: 1, b: int(version)))); |
662 | #endif |
663 | } else if (interface == QLatin1String(QWaylandPointerGestures::interface()->name)) { |
664 | mPointerGestures.reset(other: new QWaylandPointerGestures(this, id, 1)); |
665 | #if QT_CONFIG(wayland_client_primary_selection) |
666 | } else if (interface == QLatin1String(QWaylandPrimarySelectionDeviceManagerV1::interface()->name)) { |
667 | mPrimarySelectionManager.reset(other: new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); |
668 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
669 | inputDevice->setPrimarySelectionDevice(mPrimarySelectionManager->createDevice(seat: inputDevice)); |
670 | #endif |
671 | } else if (interface == QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name) |
672 | && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { |
673 | qCDebug(lcQpaWayland) << "text input: register qt_text_input_method_manager_v1" ; |
674 | if (mTextInputManagerIndex < INT_MAX) { |
675 | mTextInputManagerv1.reset(); |
676 | mTextInputManagerv2.reset(); |
677 | #if QT_WAYLAND_TEXT_INPUT_V4_WIP |
678 | mTextInputManagerv4.reset(); |
679 | #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP |
680 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
681 | inputDevice->setTextInput(nullptr); |
682 | } |
683 | |
684 | mTextInputMethodManager.reset(new QtWayland::qt_text_input_method_manager_v1(registry, id, 1)); |
685 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
686 | inputDevice->setTextInputMethod(new QWaylandTextInputMethod(this, mTextInputMethodManager->get_text_input_method(inputDevice->wl_seat()))); |
687 | mWaylandIntegration->reconfigureInputContext(); |
688 | mTextInputManagerIndex = mTextInputManagerList.indexOf(str: interface); |
689 | } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name) |
690 | && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { |
691 | qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v1" ; |
692 | if (mTextInputManagerIndex < INT_MAX) { |
693 | mTextInputMethodManager.reset(); |
694 | mTextInputManagerv2.reset(); |
695 | #if QT_WAYLAND_TEXT_INPUT_V4_WIP |
696 | mTextInputManagerv4.reset(); |
697 | #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP |
698 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
699 | inputDevice->setTextInputMethod(nullptr); |
700 | } |
701 | |
702 | mTextInputManagerv1.reset(new QtWayland::zwp_text_input_manager_v1(registry, id, 1)); |
703 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) { |
704 | auto textInput = new QWaylandTextInputv1(this, mTextInputManagerv1->create_text_input()); |
705 | textInput->setSeat(inputDevice->wl_seat()); |
706 | inputDevice->setTextInput(textInput); |
707 | } |
708 | |
709 | mWaylandIntegration->reconfigureInputContext(); |
710 | mTextInputManagerIndex = mTextInputManagerList.indexOf(str: interface); |
711 | } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name) |
712 | && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { |
713 | qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v2" ; |
714 | if (mTextInputManagerIndex < INT_MAX) { |
715 | mTextInputMethodManager.reset(); |
716 | mTextInputManagerv1.reset(); |
717 | #if QT_WAYLAND_TEXT_INPUT_V4_WIP |
718 | mTextInputManagerv4.reset(); |
719 | #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP |
720 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
721 | inputDevice->setTextInputMethod(nullptr); |
722 | } |
723 | |
724 | mTextInputManagerv2.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); |
725 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
726 | inputDevice->setTextInput(new QWaylandTextInputv2(this, mTextInputManagerv2->get_text_input(inputDevice->wl_seat()))); |
727 | mWaylandIntegration->reconfigureInputContext(); |
728 | mTextInputManagerIndex = mTextInputManagerList.indexOf(str: interface); |
729 | #if QT_WAYLAND_TEXT_INPUT_V4_WIP |
730 | } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v4::interface()->name) |
731 | && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { |
732 | qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v4" ; |
733 | if (mTextInputManagerIndex < INT_MAX) { |
734 | mTextInputMethodManager.reset(); |
735 | mTextInputManagerv2.reset(); |
736 | for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) |
737 | inputDevice->setTextInputMethod(nullptr); |
738 | } |
739 | |
740 | mTextInputManagerv4.reset(new QtWayland::zwp_text_input_manager_v4(registry, id, 1)); |
741 | for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) |
742 | inputDevice->setTextInput(new QWaylandTextInputv4(this, mTextInputManagerv4->get_text_input(inputDevice->wl_seat()))); |
743 | mWaylandIntegration->reconfigureInputContext(); |
744 | mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); |
745 | #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP |
746 | } else if (interface == QLatin1String(QWaylandHardwareIntegration::interface()->name)) { |
747 | bool disableHardwareIntegration = qEnvironmentVariableIntValue(varName: "QT_WAYLAND_DISABLE_HW_INTEGRATION" ); |
748 | if (!disableHardwareIntegration) { |
749 | mHardwareIntegration.reset(new QWaylandHardwareIntegration(registry, id)); |
750 | // make a roundtrip here since we need to receive the events sent by |
751 | // qt_hardware_integration before creating windows |
752 | forceRoundTrip(); |
753 | } |
754 | } else if (interface == QLatin1String(QWaylandXdgOutputManagerV1::interface()->name)) { |
755 | mXdgOutputManager.reset(other: new QWaylandXdgOutputManagerV1(this, id, version)); |
756 | for (auto *screen : std::as_const(t&: mWaitingScreens)) |
757 | screen->initXdgOutput(xdgOutputManager: xdgOutputManager()); |
758 | } else if (interface == QLatin1String(QtWayland::wp_fractional_scale_manager_v1::interface()->name)) { |
759 | mFractionalScaleManager.reset(new QtWayland::wp_fractional_scale_manager_v1(registry, id, 1)); |
760 | } else if (interface == QLatin1String("wp_viewporter" )) { |
761 | mViewporter.reset(new QtWayland::wp_viewporter(registry, id, qMin(1u, version))); |
762 | } else if (interface == QLatin1String(QtWayland::wp_cursor_shape_manager_v1::interface()->name)) { |
763 | mCursorShapeManager.reset(new QtWayland::wp_cursor_shape_manager_v1(registry, id, std::min(1u, version))); |
764 | } else if ( |
765 | interface == QLatin1String(QtWayland::qt_toplevel_drag_manager_v1::interface()->name)) { |
766 | mXdgToplevelDragManager.reset(new QtWayland::qt_toplevel_drag_manager_v1(registry, id, 1)); |
767 | } |
768 | |
769 | mGlobals.append(t: RegistryGlobal(id, interface, version, registry)); |
770 | emit globalAdded(global: mGlobals.back()); |
771 | |
772 | const auto copy = mRegistryListeners; // be prepared for listeners unregistering on notification |
773 | for (Listener l : copy) |
774 | (*l.listener)(l.data, registry, id, interface, version); |
775 | } |
776 | |
777 | void QWaylandDisplay::registry_global_remove(uint32_t id) |
778 | { |
779 | for (int i = 0, ie = mGlobals.size(); i != ie; ++i) { |
780 | RegistryGlobal &global = mGlobals[i]; |
781 | if (global.id == id) { |
782 | if (global.interface == QLatin1String(QtWayland::wl_output::interface()->name)) { |
783 | for (auto *screen : mWaitingScreens) { |
784 | if (screen->outputId() == id) { |
785 | mWaitingScreens.removeOne(t: screen); |
786 | delete screen; |
787 | break; |
788 | } |
789 | } |
790 | |
791 | for (QWaylandScreen *screen : std::as_const(t&: mScreens)) { |
792 | if (screen->outputId() == id) { |
793 | mScreens.removeOne(t: screen); |
794 | // If this is the last screen, we have to add a fake screen, or Qt will break. |
795 | ensureScreen(); |
796 | QWindowSystemInterface::handleScreenRemoved(screen); |
797 | break; |
798 | } |
799 | } |
800 | } |
801 | if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name)) { |
802 | mTextInputManagerv1.reset(); |
803 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
804 | inputDevice->setTextInput(nullptr); |
805 | mWaylandIntegration->reconfigureInputContext(); |
806 | } |
807 | if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name)) { |
808 | mTextInputManagerv2.reset(); |
809 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
810 | inputDevice->setTextInput(nullptr); |
811 | mWaylandIntegration->reconfigureInputContext(); |
812 | } |
813 | #if QT_WAYLAND_TEXT_INPUT_V4_WIP |
814 | if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v4::interface()->name)) { |
815 | mTextInputManagerv4.reset(); |
816 | for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) |
817 | inputDevice->setTextInput(nullptr); |
818 | mWaylandIntegration->reconfigureInputContext(); |
819 | } |
820 | #endif // QT_WAYLAND_TEXT_INPUT_V4_WIP |
821 | if (global.interface == QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name)) { |
822 | mTextInputMethodManager.reset(); |
823 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
824 | inputDevice->setTextInputMethod(nullptr); |
825 | mWaylandIntegration->reconfigureInputContext(); |
826 | } |
827 | #if QT_CONFIG(wayland_client_primary_selection) |
828 | if (global.interface == QLatin1String(QtWayland::zwp_primary_selection_device_manager_v1::interface()->name)) { |
829 | mPrimarySelectionManager.reset(); |
830 | for (QWaylandInputDevice *inputDevice : std::as_const(t&: mInputDevices)) |
831 | inputDevice->setPrimarySelectionDevice(nullptr); |
832 | } |
833 | #endif |
834 | emit globalRemoved(global: mGlobals.takeAt(i)); |
835 | break; |
836 | } |
837 | } |
838 | } |
839 | |
840 | bool QWaylandDisplay::hasRegistryGlobal(QStringView interfaceName) const |
841 | { |
842 | for (const RegistryGlobal &global : mGlobals) |
843 | if (global.interface == interfaceName) |
844 | return true; |
845 | |
846 | return false; |
847 | } |
848 | |
849 | void QWaylandDisplay::addRegistryListener(RegistryListener listener, void *data) |
850 | { |
851 | Listener l = { listener, data }; |
852 | mRegistryListeners.append(t: l); |
853 | for (int i = 0, ie = mGlobals.size(); i != ie; ++i) |
854 | (*l.listener)(l.data, mGlobals[i].registry, mGlobals[i].id, mGlobals[i].interface, mGlobals[i].version); |
855 | } |
856 | |
857 | void QWaylandDisplay::removeListener(RegistryListener listener, void *data) |
858 | { |
859 | auto iter = std::remove_if(first: mRegistryListeners.begin(), last: mRegistryListeners.end(), pred: [=](Listener l){ |
860 | return (l.listener == listener && l.data == data); |
861 | }); |
862 | mRegistryListeners.erase(abegin: iter, aend: mRegistryListeners.end()); |
863 | } |
864 | |
865 | uint32_t QWaylandDisplay::currentTimeMillisec() |
866 | { |
867 | //### we throw away the time information |
868 | struct timeval tv; |
869 | int ret = gettimeofday(tv: &tv, tz: nullptr); |
870 | if (ret == 0) |
871 | return tv.tv_sec*1000 + tv.tv_usec/1000; |
872 | return 0; |
873 | } |
874 | |
875 | void QWaylandDisplay::forceRoundTrip() |
876 | { |
877 | wl_display_roundtrip(mDisplay); |
878 | } |
879 | |
880 | bool QWaylandDisplay::supportsWindowDecoration() const |
881 | { |
882 | static bool disabled = qgetenv(varName: "QT_WAYLAND_DISABLE_WINDOWDECORATION" ).toInt(); |
883 | // Stop early when disabled via the environment. Do not try to load the integration in |
884 | // order to play nice with SHM-only, buffer integration-less systems. |
885 | if (disabled) |
886 | return false; |
887 | |
888 | static bool integrationSupport = clientBufferIntegration() && clientBufferIntegration()->supportsWindowDecoration(); |
889 | return integrationSupport; |
890 | } |
891 | |
892 | QWaylandWindow *QWaylandDisplay::lastInputWindow() const |
893 | { |
894 | return mLastInputWindow.data(); |
895 | } |
896 | |
897 | void QWaylandDisplay::setLastInputDevice(QWaylandInputDevice *device, uint32_t serial, QWaylandWindow *win) |
898 | { |
899 | mLastInputDevice = device; |
900 | mLastInputSerial = serial; |
901 | mLastInputWindow = win; |
902 | } |
903 | |
904 | bool QWaylandDisplay::isWindowActivated(const QWaylandWindow *window) |
905 | { |
906 | return mActiveWindows.contains(t: const_cast<QWaylandWindow *>(window)); |
907 | } |
908 | |
909 | void QWaylandDisplay::handleWindowActivated(QWaylandWindow *window) |
910 | { |
911 | if (mActiveWindows.contains(t: window)) |
912 | return; |
913 | |
914 | mActiveWindows.append(t: window); |
915 | requestWaylandSync(); |
916 | |
917 | if (auto *decoration = window->decoration()) |
918 | decoration->update(); |
919 | } |
920 | |
921 | void QWaylandDisplay::handleWindowDeactivated(QWaylandWindow *window) |
922 | { |
923 | Q_ASSERT(!mActiveWindows.empty()); |
924 | |
925 | if (mActiveWindows.last() == window) |
926 | requestWaylandSync(); |
927 | |
928 | mActiveWindows.removeOne(t: window); |
929 | |
930 | if (QCoreApplication::closingDown()) |
931 | return; |
932 | |
933 | if (auto *decoration = window->decoration()) |
934 | decoration->update(); |
935 | } |
936 | |
937 | void QWaylandDisplay::handleKeyboardFocusChanged(QWaylandInputDevice *inputDevice) |
938 | { |
939 | QWaylandWindow *keyboardFocus = inputDevice->keyboardFocus(); |
940 | |
941 | if (mLastKeyboardFocus == keyboardFocus) |
942 | return; |
943 | |
944 | if (keyboardFocus) |
945 | handleWindowActivated(window: keyboardFocus); |
946 | if (mLastKeyboardFocus) |
947 | handleWindowDeactivated(window: mLastKeyboardFocus); |
948 | |
949 | mLastKeyboardFocus = keyboardFocus; |
950 | } |
951 | |
952 | void QWaylandDisplay::handleWindowDestroyed(QWaylandWindow *window) |
953 | { |
954 | if (mActiveWindows.contains(t: window)) |
955 | handleWindowDeactivated(window); |
956 | } |
957 | |
958 | void QWaylandDisplay::handleWaylandSync() |
959 | { |
960 | // This callback is used to set the window activation because we may get an activate/deactivate |
961 | // pair, and the latter one would be lost in the QWindowSystemInterface queue, if we issue the |
962 | // handleWindowActivated() calls immediately. |
963 | QWindow *activeWindow = mActiveWindows.empty() ? nullptr : mActiveWindows.last()->window(); |
964 | if (activeWindow != QGuiApplication::focusWindow()) |
965 | QWindowSystemInterface::handleWindowActivated(window: activeWindow); |
966 | |
967 | if (!activeWindow) { |
968 | if (lastInputDevice()) { |
969 | #if QT_CONFIG(clipboard) |
970 | if (auto *dataDevice = lastInputDevice()->dataDevice()) |
971 | dataDevice->invalidateSelectionOffer(); |
972 | #endif |
973 | #if QT_CONFIG(wayland_client_primary_selection) |
974 | if (auto *device = lastInputDevice()->primarySelectionDevice()) |
975 | device->invalidateSelectionOffer(); |
976 | #endif |
977 | } |
978 | } |
979 | } |
980 | |
981 | const wl_callback_listener QWaylandDisplay::syncCallbackListener = { |
982 | [](void *data, struct wl_callback *callback, uint32_t time){ |
983 | Q_UNUSED(time); |
984 | wl_callback_destroy(callback); |
985 | QWaylandDisplay *display = static_cast<QWaylandDisplay *>(data); |
986 | display->mSyncCallback = nullptr; |
987 | display->handleWaylandSync(); |
988 | } |
989 | }; |
990 | |
991 | void QWaylandDisplay::requestWaylandSync() |
992 | { |
993 | if (mSyncCallback) |
994 | return; |
995 | |
996 | mSyncCallback = wl_display_sync(mDisplay); |
997 | wl_callback_add_listener(mSyncCallback, &syncCallbackListener, this); |
998 | } |
999 | |
1000 | QWaylandInputDevice *QWaylandDisplay::defaultInputDevice() const |
1001 | { |
1002 | return mInputDevices.isEmpty() ? nullptr : mInputDevices.first(); |
1003 | } |
1004 | |
1005 | bool QWaylandDisplay::isKeyboardAvailable() const |
1006 | { |
1007 | return std::any_of( |
1008 | first: mInputDevices.constBegin(), last: mInputDevices.constEnd(), |
1009 | pred: [](const QWaylandInputDevice *device) { return device->keyboard() != nullptr; }); |
1010 | } |
1011 | |
1012 | bool QWaylandDisplay::isClientSideInputContextRequested() const { |
1013 | return mClientSideInputContextRequested; |
1014 | } |
1015 | |
1016 | #if QT_CONFIG(cursor) |
1017 | |
1018 | QWaylandCursor *QWaylandDisplay::waylandCursor() |
1019 | { |
1020 | if (!mCursor) |
1021 | mCursor.reset(other: mWaylandIntegration->createPlatformCursor(display: this)); |
1022 | return mCursor.data(); |
1023 | } |
1024 | |
1025 | auto QWaylandDisplay::findExistingCursorTheme(const QString &name, int pixelSize) const noexcept |
1026 | -> FindExistingCursorThemeResult |
1027 | { |
1028 | const auto byNameAndSize = [](const WaylandCursorTheme &lhs, const WaylandCursorTheme &rhs) { |
1029 | return std::tie(args: lhs.pixelSize, args: lhs.name) < std::tie(args: rhs.pixelSize, args: rhs.name); |
1030 | }; |
1031 | |
1032 | const WaylandCursorTheme prototype = {.name: name, .pixelSize: pixelSize, .theme: nullptr}; |
1033 | |
1034 | const auto it = std::lower_bound(first: mCursorThemes.cbegin(), last: mCursorThemes.cend(), val: prototype, comp: byNameAndSize); |
1035 | if (it != mCursorThemes.cend() && it->name == name && it->pixelSize == pixelSize) |
1036 | return {.position: it, .found: true}; |
1037 | else |
1038 | return {.position: it, .found: false}; |
1039 | } |
1040 | |
1041 | QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(const QString &name, int pixelSize) |
1042 | { |
1043 | const auto result = findExistingCursorTheme(name, pixelSize); |
1044 | if (result.found) |
1045 | return result.theme(); |
1046 | |
1047 | if (auto theme = QWaylandCursorTheme::create(shm: shm(), size: pixelSize, themeName: name)) |
1048 | return mCursorThemes.insert(position: result.position, x: {.name: name, .pixelSize: pixelSize, .theme: std::move(theme)})->theme.get(); |
1049 | |
1050 | return nullptr; |
1051 | } |
1052 | |
1053 | #endif // QT_CONFIG(cursor) |
1054 | |
1055 | } // namespace QtWaylandClient |
1056 | |
1057 | QT_END_NAMESPACE |
1058 | |
1059 | #include "qwaylanddisplay.moc" |
1060 | #include "moc_qwaylanddisplay_p.cpp" |
1061 | |