Branch data Line data Source code
1 : : // Copyright (C) 2021 The Qt Company Ltd.
2 : : // Copyright (C) 2019 Luxoft Sweden AB
3 : : // Copyright (C) 2018 Pelagicore AG
4 : : // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
5 : :
6 : : #include <QGuiApplication>
7 : : #include <QRegularExpression>
8 : : #include <QQuickView>
9 : : #include <QQuickItem>
10 : : #include <QQuickItemGrabResult>
11 : : #include <QQmlEngine>
12 : : #include <QVariant>
13 : : #include <QMetaObject>
14 : : #include <QThread>
15 : : #include <QQmlComponent>
16 : : #include <private/qabstractanimation_p.h>
17 : : #include <QLocalServer>
18 : : #include "global.h"
19 : :
20 : : #if QT_CONFIG(am_multi_process)
21 : : # include "waylandcompositor.h"
22 : : # include <private/qwaylandcompositor_p.h>
23 : : #endif
24 : :
25 : : #include "logging.h"
26 : : #include "application.h"
27 : : #include "applicationmanager.h"
28 : : #include "abstractruntime.h"
29 : : #include "runtimefactory.h"
30 : : #include "window.h"
31 : : #include "windowitem.h"
32 : : #include "windowmanager.h"
33 : : #include "windowmanager_p.h"
34 : : #include "waylandwindow.h"
35 : : #include "inprocesswindow.h"
36 : : #include "qml-utilities.h"
37 : : #include "qmlinprocapplicationmanagerwindowimpl.h"
38 : : #include "systemframetimerimpl.h"
39 : :
40 : : using namespace Qt::StringLiterals;
41 : :
42 : :
43 : : /*!
44 : : \qmltype WindowManager
45 : : \inqmlmodule QtApplicationManager.SystemUI
46 : : \ingroup system-ui-singletons
47 : : \brief The window model and controller.
48 : :
49 : : The WindowManager singleton type is the window managing part of the application manager.
50 : : It provides a QML API only.
51 : :
52 : : The type is derived from QAbstractListModel, and can be directly used as a model in window views.
53 : :
54 : : Each item in this model corresponds to an actual window surface. Note that a single
55 : : application can have multiple surfaces; therefore, the \c applicationId role is not unique
56 : : within this model.
57 : :
58 : : \target WindowManager Roles
59 : :
60 : : The following roles are available in this model:
61 : :
62 : : \table
63 : : \header
64 : : \li Role name
65 : : \li Type
66 : : \li Description
67 : : \row
68 : : \li \c applicationId
69 : : \li string
70 : : \li The unique id of an application represented as a string. This can be used to look up
71 : : information about the application in the ApplicationManager model.
72 : : \row
73 : : \li \c window
74 : : \li WindowObject
75 : : \target windowmanager-window-role
76 : : \li The \l WindowObject containing the client surface. To display it you have to put it in a
77 : : WindowItem
78 : : \row
79 : : \li \c windowObject
80 : : \li WindowObject
81 : : \li Exactly the same as \c window. This was added to keep the role names between the
82 : : PackageManager and WindowManager models as similar as possible.
83 : : This role was introduced in Qt version 6.6.
84 : : \row
85 : : \li \c contentState
86 : : \li WindowObject::ContentState
87 : : \target windowmanager-contentState-role
88 : : \li The content state of the WindowObject. See WindowObject::contentState
89 : : \endtable
90 : :
91 : : \target Multi-process Wayland caveats
92 : :
93 : : \note Please be aware that Wayland is essentially an asynchronous IPC protocol, resulting in
94 : : different local states in the client and server processes during state changes. A prime example
95 : : for this is window property changes on the client side: in addition to being changed
96 : : asynchronously on the server side, the windowPropertyChanged signal will not be emitted while
97 : : the window object is not yet made available on the server side via the windowAdded signal. All
98 : : those changes are not lost however, but the last change before emitting the windowAdded signal
99 : : will be the initial state of the window object on the System UI side.
100 : :
101 : : \target Minimal compositor
102 : :
103 : : After importing, the WindowManager singleton can be used as in the example below.
104 : : It demonstrates how to implement a basic, fullscreen window compositor with support
105 : : for window show and hide animations:
106 : :
107 : : \qml
108 : : import QtQuick
109 : : import QtApplicationManager.SystemUI
110 : :
111 : : // Simple solution for a full-screen setup
112 : : Item {
113 : : width: 1024
114 : : height: 640
115 : :
116 : : Connections {
117 : : target: WindowManager
118 : : // Send windows to a separate model so that we have control
119 : : // over removals and ordering
120 : : function onWindowAdded(window) {
121 : : windowsModel.append({"window":window});
122 : : }
123 : : }
124 : :
125 : : Repeater {
126 : : model: ListModel { id: windowsModel }
127 : : delegate: WindowItem {
128 : : id: windowItem
129 : : anchors.fill: parent
130 : : z: model.index
131 : :
132 : : window: model.window
133 : :
134 : : states: [
135 : : State {
136 : : name: "open"
137 : : when: model.window.contentState === WindowObject.SurfaceWithContent
138 : : PropertyChanges { target: windowItem; scale: 1; visible: true }
139 : : }
140 : : ]
141 : :
142 : : scale: 0.50
143 : : visible: false
144 : :
145 : : transitions: [
146 : : Transition {
147 : : to: "open"
148 : : NumberAnimation {
149 : : target: windowItem; property: "scale"
150 : : duration: 500; easing.type: Easing.OutQuad
151 : : }
152 : : },
153 : : Transition {
154 : : from: "open"
155 : : SequentialAnimation {
156 : : // we wanna see the window during the closing animation
157 : : PropertyAction { target: windowItem; property: "visible"; value: true }
158 : : NumberAnimation {
159 : : target: windowItem; property: "scale"
160 : : duration: 500; easing.type: Easing.InQuad
161 : : }
162 : : ScriptAction { script: {
163 : : // It's important to destroy our WindowItem once it's no longer needed in
164 : : // order to free up resources
165 : : if (model.window.contentState === WindowObject.NoSurface)
166 : : windowsModel.remove(model.index, 1);
167 : : } }
168 : : }
169 : : }
170 : : ]
171 : : }
172 : : }
173 : : }
174 : : \endqml
175 : :
176 : : */
177 : :
178 : : /*!
179 : : \qmlsignal WindowManager::windowAdded(WindowObject window)
180 : :
181 : : This signal is emitted when a new WindowObject is added to the model. This happens in response
182 : : to an application creating a new window surface, which usually occurs during that application's
183 : : startup.
184 : :
185 : : To display that \a window on your QML scene you need to assign it to a WindowItem.
186 : :
187 : : \note Please be aware that the windowAdded signal is not emitted immediately when the client
188 : : sets a window to visible. This is due to the \l
189 : : {Multi-process Wayland caveats}{asynchronous nature} of the underlying Wayland protocol.
190 : : */
191 : :
192 : : /*!
193 : : \qmlsignal WindowManager::windowAboutToBeRemoved(WindowObject window)
194 : :
195 : : This signal is emitted before the \a window is removed from the model. This happens for
196 : : instance, when the window \c visible property is set to \c false on the application side.
197 : : */
198 : :
199 : : /*!
200 : : \qmlsignal WindowManager::windowContentStateChanged(WindowObject window)
201 : :
202 : : This signal is emitted when the WindowObject::contentState of the given \a window changes.
203 : :
204 : : \sa WindowObject::contentState
205 : : */
206 : :
207 : : /*!
208 : : \qmlsignal WindowManager::raiseApplicationWindow(string applicationId)
209 : :
210 : : This signal is emitted when an application start is triggered for the already running application
211 : : identified by \a applicationId via the ApplicationManager.
212 : : */
213 : :
214 : : QT_BEGIN_NAMESPACE_AM
215 : :
216 : : enum WMRoles
217 : : {
218 : : Id = Qt::UserRole + 1000,
219 : : WindowRole,
220 : : WindowObjectRole,
221 : : ContentState
222 : : };
223 : :
224 : : WindowManager *WindowManager::s_instance = nullptr;
225 : :
226 : 50 : WindowManager *WindowManager::createInstance(QQmlEngine *qmlEngine, const QString &waylandSocketName)
227 : : {
228 [ - + ]: 50 : if (s_instance)
229 : 0 : qFatal("WindowManager::createInstance() was called a second time.");
230 : :
231 [ + - ]: 50 : ApplicationManagerWindowImpl::setFactory([](ApplicationManagerWindow *window) {
232 [ + - ]: 72 : return new QmlInProcApplicationManagerWindowImpl(window);
233 : : });
234 [ + - ]: 50 : ApplicationManagerWindowAttachedImpl::setFactory([](ApplicationManagerWindowAttached *windowAttached,
235 : : QQuickItem *attacheeItem) {
236 [ + - ]: 7 : return new QmlInProcApplicationManagerWindowAttachedImpl(windowAttached, attacheeItem);
237 : : });
238 [ + - ]: 50 : SystemFrameTimerImpl::setFactory([](FrameTimer *frameTimer) {
239 [ + - ]: 2 : return new SystemFrameTimerImpl(frameTimer);
240 : : });
241 : :
242 : 50 : qRegisterMetaType<Window*>("Window*");
243 : 50 : qRegisterMetaType<InProcessSurfaceItem*>("InProcessSurfaceItem*");
244 : 50 : qRegisterMetaType<QSharedPointer<InProcessSurfaceItem>>("QSharedPointer<InProcessSurfaceItem>");
245 : :
246 [ + - ]: 50 : return s_instance = new WindowManager(qmlEngine, waylandSocketName);
247 : : }
248 : :
249 : 156 : WindowManager *WindowManager::instance()
250 : : {
251 [ - + ]: 156 : if (!s_instance)
252 : 0 : qFatal("WindowManager::instance() was called before createInstance().");
253 : 156 : return s_instance;
254 : : }
255 : :
256 : 50 : void WindowManager::shutDown()
257 : : {
258 : 50 : d->shuttingDown = true;
259 : :
260 [ + + ]: 50 : if (d->allWindows.isEmpty())
261 : 46 : QMetaObject::invokeMethod(&internalSignals, &WindowManagerInternalSignals::shutDownFinished, Qt::QueuedConnection);
262 : 50 : }
263 : :
264 : : /*!
265 : : \qmlproperty bool WindowManager::runningOnDesktop
266 : : \readonly
267 : :
268 : : Holds \c true if running on a classic desktop window manager (Windows, X11, or macOS),
269 : : \c false otherwise.
270 : : */
271 : 0 : bool WindowManager::isRunningOnDesktop() const
272 : : {
273 : : #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
274 : : return true;
275 : : #else
276 : 0 : return qApp->platformName() == u"xcb";
277 : : #endif
278 : : }
279 : :
280 : : /*!
281 : : \qmlproperty bool WindowManager::slowAnimations
282 : :
283 : : Whether animations are in slow mode.
284 : :
285 : : It's false by default and might be initialized to true using the command line option
286 : : \c --slow-animations.
287 : :
288 : : Also useful to check this value if you need to adjust timings for the slow animation
289 : : mode.
290 : : */
291 : 0 : bool WindowManager::slowAnimations() const
292 : : {
293 : 0 : return d->slowAnimations;
294 : : }
295 : :
296 : 50 : void WindowManager::setSlowAnimations(bool slowAnimations)
297 : : {
298 [ - + ]: 50 : if (slowAnimations != d->slowAnimations) {
299 : 0 : d->slowAnimations = slowAnimations;
300 : :
301 [ # # ]: 0 : for (auto *view : std::as_const(d->views))
302 : 0 : updateViewSlowMode(view);
303 : :
304 : : // Update timer of the main, GUI, thread
305 : 0 : QUnifiedTimer::instance()->setSlowModeEnabled(d->slowAnimations);
306 : :
307 : : // For new applications to start with the correct value
308 : 0 : RuntimeFactory::instance()->setSlowAnimations(d->slowAnimations);
309 : :
310 : : // Update already running applications
311 : 0 : const auto allApplications = ApplicationManager::instance()->applications();
312 [ # # ]: 0 : for (Application *application : allApplications) {
313 [ # # ]: 0 : auto runtime = application->currentRuntime();
314 [ # # ]: 0 : if (runtime)
315 [ # # ]: 0 : runtime->setSlowAnimations(d->slowAnimations);
316 : : }
317 : :
318 [ # # # # : 0 : qCDebug(LogSystem) << "WindowManager::slowAnimations =" << d->slowAnimations;
# # # # #
# ]
319 : :
320 [ # # ]: 0 : emit slowAnimationsChanged(d->slowAnimations);
321 : 0 : }
322 : 50 : }
323 : :
324 : 0 : bool WindowManager::allowUnknownUiClients() const
325 : : {
326 : 0 : return d->allowUnknownUiClients;
327 : : }
328 : :
329 : 50 : void WindowManager::setAllowUnknownUiClients(bool enable)
330 : : {
331 : 50 : d->allowUnknownUiClients = enable;
332 : 50 : }
333 : :
334 : 40 : void WindowManager::updateViewSlowMode(QQuickWindow *view)
335 : : {
336 : : // QUnifiedTimer are thread-local. To also slow down animations running in the SG thread
337 : : // we need to enable the slow mode in this timer as well.
338 [ + - + - ]: 40 : static QHash<QQuickWindow *, QMetaObject::Connection> conns;
339 : 40 : bool isSlow = d->slowAnimations;
340 [ + - ]: 40 : conns.insert(view, connect(view, &QQuickWindow::beforeRendering, this, [view, isSlow] {
341 : 38 : QMetaObject::Connection con = conns[view];
342 [ + - + - ]: 38 : if (con) {
343 : : #if defined(Q_CC_MSVC)
344 : : qApp->disconnect(con); // MSVC cannot distinguish between static and non-static overloads in lambdas
345 : : #else
346 [ + - ]: 38 : QObject::disconnect(con);
347 : : #endif
348 [ + - ]: 38 : QUnifiedTimer::instance()->setSlowModeEnabled(isSlow);
349 : : }
350 : 38 : }, Qt::DirectConnection));
351 : 40 : }
352 : :
353 : 50 : WindowManager::WindowManager(QQmlEngine *qmlEngine, const QString &waylandSocketName)
354 : : : QAbstractListModel()
355 [ + - + - ]: 50 : , d(new WindowManagerPrivate())
356 : : {
357 : : #if QT_CONFIG(am_multi_process)
358 : 50 : d->waylandSocketName = waylandSocketName;
359 : : #else
360 : : Q_UNUSED(waylandSocketName)
361 : : #endif
362 : :
363 [ + - + - ]: 50 : d->roleNames.insert(WMRoles::Id, "applicationId");
364 [ + - + - ]: 50 : d->roleNames.insert(WMRoles::WindowRole, "window");
365 [ + - + - ]: 50 : d->roleNames.insert(WMRoles::WindowObjectRole, "windowObject");
366 [ + - + - ]: 50 : d->roleNames.insert(WMRoles::ContentState, "contentState");
367 : :
368 : 50 : d->qmlEngine = qmlEngine;
369 : :
370 [ + - ]: 50 : qApp->installEventFilter(this);
371 : :
372 [ + - ]: 50 : connect(AbstractRuntime::signaler(), &RuntimeSignaler::inProcessSurfaceItemReady,
373 [ + - ]: 50 : this, &WindowManager::inProcessSurfaceItemCreated);
374 : 50 : }
375 : :
376 : 100 : WindowManager::~WindowManager()
377 : : {
378 : 50 : qApp->removeEventFilter(this);
379 : :
380 : : #if QT_CONFIG(am_multi_process)
381 [ + + ]: 50 : delete d->waylandCompositor;
382 : : #endif
383 [ + - ]: 50 : delete d;
384 : 50 : s_instance = nullptr;
385 : 100 : }
386 : :
387 : 50 : void WindowManager::enableWatchdog(bool enable)
388 : : {
389 : : #if QT_CONFIG(am_multi_process)
390 : 50 : WaylandWindow::m_watchdogEnabled = enable;
391 : : #else
392 : : Q_UNUSED(enable);
393 : : #endif
394 : 50 : }
395 : :
396 : 0 : bool WindowManager::addWaylandSocket(QLocalServer *waylandSocket)
397 : : {
398 : : #if QT_CONFIG(am_multi_process)
399 [ # # ]: 0 : if (d->waylandCompositor) {
400 [ # # ]: 0 : qCWarning(LogGraphics) << "Cannot add extra Wayland sockets after the compositor has been created"
401 [ # # # # : 0 : " (tried to add:" << waylandSocket->fullServerName() << ").";
# # # # ]
402 [ # # ]: 0 : delete waylandSocket;
403 : 0 : return false;
404 : : }
405 : :
406 [ # # # # ]: 0 : if (!waylandSocket || (waylandSocket->socketDescriptor() < 0)) {
407 [ # # ]: 0 : delete waylandSocket;
408 : 0 : return false;
409 : : }
410 : :
411 : 0 : waylandSocket->setParent(this);
412 : 0 : d->extraWaylandSockets << int(waylandSocket->socketDescriptor());
413 : 0 : return true;
414 : : #else
415 : : Q_UNUSED(waylandSocket)
416 : : return true;
417 : : #endif
418 : : }
419 : :
420 : 1126 : int WindowManager::rowCount(const QModelIndex &parent) const
421 : : {
422 [ - + ]: 1126 : if (parent.isValid())
423 : : return 0;
424 : 1126 : return int(d->windowsInModel.count());
425 : : }
426 : :
427 : 10 : QVariant WindowManager::data(const QModelIndex &index, int role) const
428 : : {
429 [ + - ]: 20 : if (index.parent().isValid() || !index.isValid())
430 : 0 : return QVariant();
431 : :
432 [ + - ]: 10 : if (index.row() < 0 || index.row() >= d->windowsInModel.count())
433 : 0 : return QVariant();
434 : :
435 [ + + + - ]: 10 : Window *win = d->windowsInModel.at(index.row());
436 : :
437 [ + + + - ]: 10 : switch (role) {
438 : 2 : case WMRoles::Id:
439 [ + - ]: 2 : if (win->application()) {
440 : 2 : return win->application()->nonAliased()->id();
441 : : } else {
442 : 0 : return QString();
443 : : }
444 : 6 : case WMRoles::WindowRole:
445 : 6 : case WMRoles::WindowObjectRole:
446 : 6 : return QVariant::fromValue(win);
447 : 2 : case WMRoles::ContentState:
448 : 2 : return win->contentState();
449 : : }
450 : 0 : return QVariant();
451 : : }
452 : :
453 : 3 : QHash<int, QByteArray> WindowManager::roleNames() const
454 : : {
455 [ + - ]: 3 : return d->roleNames;
456 : : }
457 : :
458 : : /*!
459 : : \qmlproperty int WindowManager::count
460 : : \readonly
461 : :
462 : : This property holds the number of applications available.
463 : : */
464 : 823 : int WindowManager::count() const
465 : : {
466 : 823 : return rowCount();
467 : : }
468 : :
469 : : /*!
470 : : \qmlmethod object WindowManager::get(int index) const
471 : :
472 : : Retrieves the model data at \a index as a JavaScript object. See the \l {WindowManager
473 : : Roles}{role names} for the expected object fields.
474 : :
475 : : Returns an empty object if the specified \a index is invalid.
476 : : */
477 : 2 : QVariantMap WindowManager::get(int index) const
478 : : {
479 [ + - - + ]: 2 : if (index < 0 || index >= count()) {
480 [ # # # # : 0 : qCWarning(LogSystem) << "WindowManager::get(index): invalid index:" << index;
# # ]
481 : 0 : return QVariantMap();
482 : : }
483 : :
484 : 2 : QVariantMap map;
485 [ + - ]: 2 : QHash<int, QByteArray> roles = roleNames();
486 [ + - + + ]: 10 : for (auto it = roles.begin(); it != roles.end(); ++it)
487 [ + - + - : 16 : map.insert(QString::fromLatin1(it.value()), data(QAbstractListModel::index(index), it.key()));
+ - + - ]
488 : 2 : return map;
489 : 4 : }
490 : :
491 : : /*!
492 : : \qmlmethod WindowObject WindowManager::window(int index)
493 : :
494 : : Returns the \l{WindowObject}{window} corresponding to the given \a index in the
495 : : model, or \c null if the index is invalid.
496 : :
497 : : \note The object ownership of the returned Window object stays with the application manager.
498 : : If you want to store this pointer, you can use the WindowManager's QAbstractListModel
499 : : signals or the windowAboutToBeRemoved signal to get notified if the object is about
500 : : to be deleted on the C++ side.
501 : : */
502 : 0 : Window *WindowManager::window(int index) const
503 : : {
504 [ # # # # ]: 0 : if (index < 0 || index >= count()) {
505 [ # # # # : 0 : qCWarning(LogSystem) << "WindowManager::window(index): invalid index:" << index;
# # ]
506 : 0 : return nullptr;
507 : : }
508 : 0 : return d->windowsInModel.at(index);
509 : : }
510 : :
511 : : /*!
512 : : \qmlmethod list<WindowObject> WindowManager::windowsOfApplication(string applicationId)
513 : :
514 : : Returns a list of \l{WindowObject}{windows} belonging to the given \a applicationId in the
515 : : model, or an empty list if the applicationId is invalid.
516 : :
517 : : \note The object ownership of the returned Window objects stays with the application manager.
518 : : If you want to store these pointers, you can use the WindowManager's QAbstractListModel
519 : : signals or the windowAboutToBeRemoved signal to get notified if the objects are about
520 : : to be deleted on the C++ side.
521 : : */
522 : 14 : QList<Window *> WindowManager::windowsOfApplication(const QString &id) const
523 : : {
524 : 14 : QList<Window *> result;
525 [ + + ]: 28 : for (Window *window : std::as_const(d->windowsInModel)) {
526 [ + - + - : 28 : if (window->application() && window->application()->id() == id)
+ - + - -
+ - - +
- ]
527 [ + - ]: 28 : result << window;
528 : : }
529 : 14 : return result;
530 : 0 : }
531 : :
532 : : /*!
533 : : \qmlmethod int WindowManager::indexOfWindow(WindowObject window)
534 : :
535 : : Returns the index of the \a window within the WindowManager model, or \c -1 if the window item is
536 : : not a managed window.
537 : : */
538 : 288 : int WindowManager::indexOfWindow(Window *window) const
539 : : {
540 [ # # ]: 0 : return int(d->windowsInModel.indexOf(window));
541 : : }
542 : :
543 : : /*!
544 : : \qmlmethod object WindowManager::addExtension(Component component)
545 : :
546 : : Creates a Wayland compositor extension from \a component and adds it to the System UI's
547 : : underlying WaylandCompositor. The \a component must hold a valid Wayland compositor extension.
548 : : On success, in multi-process mode, the function returns the created extension. Extensions can
549 : : only be added, once ApplicationManager::windowManagerCompositorReady is true, for example:
550 : :
551 : : \code
552 : : import QtWayland.Compositor.TextureSharingExtension
553 : :
554 : : Component {
555 : : id: texshare
556 : : TextureSharingExtension {}
557 : : }
558 : :
559 : : Connections {
560 : : target: ApplicationManager
561 : : function onWindowManagerCompositorReadyChanged() {
562 : : WindowManager.addExtension(texshare);
563 : : }
564 : : }
565 : : \endcode
566 : :
567 : : \sa ApplicationManager::windowManagerCompositorReady
568 : :
569 : : */
570 : 3 : QObject *WindowManager::addExtension(QQmlComponent *component) const
571 : : {
572 : : #if QT_CONFIG(am_multi_process)
573 [ + - + + ]: 3 : if (!component || ApplicationManager::instance()->isSingleProcess())
574 : 1 : return nullptr;
575 : :
576 [ - + ]: 2 : if (!d->waylandCompositor) {
577 [ # # # # ]: 0 : qCWarning(LogSystem) << "Extensions can only be added after ApplicationManager::windowManagerCompositorReady";
578 : 0 : return nullptr;
579 : : }
580 : :
581 : 2 : QObject *obj = component->beginCreate(qmlContext(component));
582 : 2 : auto extension = qobject_cast<QWaylandCompositorExtension*>(obj);
583 : :
584 [ + + ]: 2 : if (extension)
585 : 1 : extension->setParent(d->waylandCompositor);
586 : :
587 : 2 : component->completeCreate();
588 : :
589 [ + + ]: 2 : if (!extension)
590 [ + - ]: 1 : delete obj;
591 : :
592 : : return extension;
593 : : #else
594 : : Q_UNUSED(component)
595 : : return nullptr;
596 : : #endif
597 : : }
598 : :
599 : 140 : void WindowManager::setupInProcessRuntime(AbstractRuntime *runtime)
600 : : {
601 : : // special hacks to get in-process mode working transparently
602 [ + + ]: 140 : if (runtime->manager()->inProcess())
603 : 57 : runtime->setInProcessQmlEngine(d->qmlEngine);
604 : 140 : }
605 : :
606 : : /*
607 : : Releases all resources of the window and destroys it.
608 : : */
609 : 344 : void WindowManager::releaseWindow(Window *window)
610 : : {
611 [ + + ]: 344 : auto index = d->allWindows.indexOf(window);
612 [ + + ]: 344 : if (index == -1)
613 : : return;
614 : :
615 : 144 : d->allWindows.removeAt(index);
616 : :
617 : 144 : disconnect(window, nullptr, this, nullptr);
618 : :
619 : 144 : window->deleteLater();
620 : :
621 [ + + + + ]: 144 : if (d->shuttingDown && (count() == 0))
622 : 4 : QMetaObject::invokeMethod(&internalSignals, &WindowManagerInternalSignals::shutDownFinished, Qt::QueuedConnection);
623 : : }
624 : :
625 : : /*
626 : : Adds the given window to the model.
627 : : */
628 : 144 : void WindowManager::addWindow(Window *window)
629 : : {
630 : 144 : beginInsertRows(QModelIndex(), count(), count());
631 : 144 : d->windowsInModel << window;
632 : 144 : endInsertRows();
633 : 144 : emit countChanged();
634 : 144 : emit windowAdded(window);
635 : 144 : }
636 : :
637 : : /*
638 : : Removes the given window from the model.
639 : : */
640 : 384 : void WindowManager::removeWindow(Window *window)
641 : : {
642 [ + + ]: 384 : qsizetype index = d->windowsInModel.indexOf(window);
643 [ + + ]: 384 : if (index == -1)
644 : : return;
645 : :
646 : 144 : emit windowAboutToBeRemoved(window);
647 : :
648 : 144 : beginRemoveRows(QModelIndex(), int(index), int(index));
649 : 144 : d->windowsInModel.removeAt(index);
650 : 144 : endRemoveRows();
651 : 144 : emit countChanged();
652 : : }
653 : :
654 : : /*
655 : : Registers the given \a view as a possible destination for Wayland window composition.
656 : :
657 : : Wayland window items can only be rendered within top-level windows that have been registered
658 : : via this function.
659 : : */
660 : 40 : void WindowManager::registerCompositorView(QQuickWindow *view)
661 : : {
662 : 40 : static bool once = false;
663 : :
664 [ + - ]: 40 : if (d->views.contains(view))
665 : : return;
666 : 40 : d->views << view;
667 : :
668 : 40 : updateViewSlowMode(view);
669 : :
670 : : #if QT_CONFIG(am_multi_process)
671 [ + + ]: 40 : if (!ApplicationManager::instance()->isSingleProcess()) {
672 [ + - ]: 24 : if (!d->waylandCompositor) {
673 : : // this will trigger the creation of extra sockets in main.cpp
674 : 24 : emit internalSignals.compositorAboutToBeCreated();
675 : :
676 [ + - ]: 24 : d->waylandCompositor = new WaylandCompositor(view, d->waylandSocketName);
677 [ - + ]: 24 : for (const auto &extraSocket : std::as_const(d->extraWaylandSockets))
678 : 0 : d->waylandCompositor->addSocketDescriptor(extraSocket);
679 : :
680 : 24 : connect(d->waylandCompositor, &QWaylandCompositor::surfaceCreated,
681 : 24 : this, &WindowManager::waylandSurfaceCreated);
682 : 24 : connect(d->waylandCompositor, &WaylandCompositor::surfaceMapped,
683 : 24 : this, &WindowManager::waylandSurfaceMapped);
684 : :
685 : : // export the actual socket name for our child processes.
686 [ + - + - ]: 48 : qputenv("WAYLAND_DISPLAY", d->waylandCompositor->socketName());
687 [ + - + + ]: 48 : qCInfo(LogGraphics).nospace() << "WindowManager: running in Wayland mode [socket: "
688 [ + - + - : 48 : << d->waylandCompositor->socketName() << "]";
+ - ]
689 : : // SHM is always loaded
690 [ - + ]: 24 : if (QWaylandCompositorPrivate::get(d->waylandCompositor)->clientBufferIntegrations().size() <= 1) {
691 [ # # ]: 0 : qCCritical(LogGraphics) << "No OpenGL capable Wayland client buffer integration available: "
692 [ # # ]: 0 : "Wayland client applications can only use software rendering";
693 : : }
694 : 24 : ApplicationManager::instance()->setWindowManagerCompositorReady(true);
695 : : } else {
696 : 0 : d->waylandCompositor->registerOutputWindow(view);
697 : : }
698 : : } else {
699 [ + - ]: 16 : if (!once)
700 [ + - + + ]: 32 : qCInfo(LogGraphics) << "WindowManager: running in single-process mode [forced at run-time]";
701 : : }
702 : : #else
703 : : if (!once)
704 : : qCInfo(LogGraphics) << "WindowManager: running in single-process mode [forced at compile-time]";
705 : : #endif
706 : :
707 [ + - ]: 40 : if (!once)
708 : 40 : once = true;
709 : : }
710 : :
711 : : /*! \internal
712 : : Only used for in-process surfaces
713 : : */
714 : 67 : void WindowManager::inProcessSurfaceItemCreated(AbstractRuntime *runtime,
715 : : QSharedPointer<InProcessSurfaceItem> surfaceItem)
716 : : {
717 [ - + ]: 67 : if (!runtime) {
718 [ # # # # ]: 0 : qCCritical(LogSystem) << "This function must be called by a signal of Runtime!";
719 : 0 : return;
720 : : }
721 [ + - ]: 67 : Application *app = runtime->application() ? runtime->application()->nonAliased() : nullptr;
722 [ - + ]: 67 : if (!app) {
723 [ # # # # ]: 0 : qCCritical(LogSystem) << "This function must be called by a signal of Runtime which actually has an application attached!";
724 : 0 : return;
725 : : }
726 : :
727 : : //Only create a new Window if we don't have it already in the window list, as the user controls whether windows are removed or not
728 : 67 : int index = d->findWindowBySurfaceItem(surfaceItem.data());
729 [ + - ]: 67 : if (index == -1) {
730 [ + - ]: 67 : setupWindow(new InProcessWindow(app, surfaceItem));
731 : : } else {
732 : 0 : auto window = qobject_cast<InProcessWindow*>(d->allWindows.at(index));
733 : 0 : window->setContentState(Window::SurfaceWithContent);
734 : : }
735 : : }
736 : :
737 : : /*! \internal
738 : : Used to create the Window objects for a surface
739 : : This is called for both wayland and in-process surfaces
740 : : */
741 : 144 : void WindowManager::setupWindow(Window *window)
742 : : {
743 [ - + ]: 144 : if (!window)
744 : 0 : return;
745 : :
746 : 144 : QQmlEngine::setObjectOwnership(window, QQmlEngine::CppOwnership);
747 : :
748 : 144 : connect(window, &Window::windowPropertyChanged,
749 : 216 : this, [this](const QString &name, const QVariant &value) {
750 [ + - ]: 108 : if (Window *win = qobject_cast<Window *>(sender()))
751 : 108 : emit windowPropertyChanged(win, name, value);
752 : 108 : });
753 : :
754 : : // In very rare cases window->deleteLater() is processed before the isBeingDisplayedChanged
755 : : // handler below, i.e. the window destructor runs before the handler.
756 : 144 : QPointer<Window> guardedWindow = window;
757 [ + - + - ]: 432 : connect(window, &Window::isBeingDisplayedChanged, this, [this, guardedWindow]() {
758 [ + + ]: 416 : if (!guardedWindow.isNull() && guardedWindow->contentState() == Window::NoSurface
759 [ + - + - ]: 304 : && !guardedWindow->isBeingDisplayed()) {
760 : 96 : removeWindow(guardedWindow);
761 : 96 : releaseWindow(guardedWindow);
762 : : }
763 : 208 : }, Qt::QueuedConnection);
764 : :
765 [ + - - - ]: 144 : connect(window, &Window::contentStateChanged, this, [this, window]() {
766 : 288 : auto contentState = window->contentState();
767 [ + + ]: 288 : auto index = indexOfWindow(window);
768 [ + + ]: 288 : if (index != -1) {
769 : 144 : emit windowContentStateChanged(window);
770 : :
771 : 144 : QModelIndex modelIndex = QAbstractListModel::index(index);
772 [ - - - - : 144 : qCDebug(LogGraphics).nospace() << "emitting dataChanged, index: " << modelIndex.row()
- + ]
773 [ # # # # : 0 : << ", contentState: " << window->contentState();
# # ]
774 [ + - + - ]: 288 : emit dataChanged(modelIndex, modelIndex, QVector<int>() << WMRoles::ContentState);
775 : : }
776 : :
777 [ + - ]: 288 : if (contentState == Window::NoSurface) {
778 : 288 : removeWindow(window);
779 [ + + ]: 288 : if (!window->isBeingDisplayed())
780 : 248 : releaseWindow(window);
781 : : }
782 : 288 : }, Qt::QueuedConnection);
783 : :
784 [ + - ]: 144 : d->allWindows << window;
785 [ + - ]: 144 : addWindow(window);
786 : 144 : }
787 : :
788 : :
789 : : #if QT_CONFIG(am_multi_process)
790 : :
791 : 290 : void WindowManager::waylandSurfaceCreated(QWaylandSurface *surface)
792 : : {
793 : 290 : Q_UNUSED(surface)
794 : : // this function is still useful for Wayland debugging
795 : : //qCDebug(LogGraphics) << "New Wayland surface:" << surface->surface() << "pid:" << surface->processId();
796 : 290 : }
797 : :
798 : 77 : void WindowManager::waylandSurfaceMapped(WindowSurface *surface)
799 : : {
800 : 77 : qint64 processId = surface->processId();
801 : 77 : const auto apps = ApplicationManager::instance()->fromProcessId(processId);
802 : 77 : Application *app = nullptr;
803 : :
804 [ + - ]: 77 : if (apps.size() == 1) {
805 : 77 : app = apps.constFirst();
806 [ # # ]: 0 : } else if (apps.size() > 1) {
807 : : // if there is more than one app within the same process, check the XDG surface appId
808 [ # # ]: 0 : const QString xdgAppId = surface->applicationId();
809 [ # # ]: 0 : if (!xdgAppId.isEmpty()) {
810 [ # # ]: 0 : for (const auto &checkApp : apps) {
811 [ # # # # ]: 0 : if (checkApp->id() == xdgAppId) {
812 : 0 : app = checkApp;
813 : 0 : break;
814 : : }
815 : : }
816 : : }
817 : 0 : }
818 : :
819 [ - + - - ]: 77 : if (!app && !d->allowUnknownUiClients) {
820 [ # # # # : 0 : qCCritical(LogGraphics) << "SECURITY ALERT: an unknown application with pid" << processId
# # # # #
# ]
821 [ # # ]: 0 : << "tried to map a Wayland surface!"
822 : : << "\n You can disable this check by using the commandline option "
823 : : "'--no-security' or by adding 'flags/noSecurity: yes' to the "
824 [ # # ]: 0 : "configuration.";
825 : 0 : return;
826 : : }
827 : :
828 : 77 : Q_ASSERT(surface);
829 : :
830 [ + - - - : 77 : qCDebug(LogGraphics) << "Mapping Wayland surface" << surface << "of" << d->applicationId(app, surface);
- - - - -
- - - - -
- + ]
831 : :
832 : : // Only create a new Window if we don't have it already in the window list, as the user controls
833 : : // whether windows are removed or not
834 [ + - + - ]: 77 : int index = d->findWindowByWaylandSurface(surface->surface());
835 [ + - ]: 77 : if (index == -1) {
836 [ + - + - ]: 77 : WaylandWindow *w = new WaylandWindow(app, surface);
837 [ + - ]: 77 : setupWindow(w);
838 : : }
839 : 77 : }
840 : :
841 : : #endif // QT_CONFIG(am_multi_process)
842 : :
843 : : /*!
844 : : \qmlsignal WindowManager::windowPropertyChanged(WindowObject window, string name, var value)
845 : :
846 : : Reports a change of an application \a window's property identified by \a name to the given
847 : : \a value.
848 : :
849 : : \note When listening to property changes of Wayland clients, be aware of the \l
850 : : {Multi-process Wayland caveats}{asynchronous nature} of the underlying Wayland protocol.
851 : :
852 : : \sa ApplicationManagerWindow::setWindowProperty()
853 : : */
854 : :
855 : : /*!
856 : : \qmlmethod bool WindowManager::makeScreenshot(string filename, string selector)
857 : :
858 : : Creates one or several screenshots depending on the \a selector, saving them to the files
859 : : specified by \a filename.
860 : :
861 : : The \a filename argument can either be a plain file name for single screenshots, or it can
862 : : contain format sequences that will be replaced accordingly, if multiple screenshots are
863 : : requested:
864 : :
865 : : \table
866 : : \header
867 : : \li Format
868 : : \li Description
869 : : \row
870 : : \li \c{%s}
871 : : \li Will be replaced with the screen-id of this particular screenshot.
872 : : \row
873 : : \li \c{%i}
874 : : \li Will be replaced with the application id, when making screenshots of application
875 : : windows.
876 : : \row
877 : : \li \c{%%}
878 : : \li Will be replaced by a single \c{%} character.
879 : : \endtable
880 : :
881 : : The \a selector argument is a string which is parsed according to this pattern:
882 : : \badcode
883 : : <application-id>[window-property=value]:<screen-id>
884 : : \endcode
885 : :
886 : : All parts are optional, so if you specify an empty string, the call will create a screenshot
887 : : of every screen.
888 : : If you specify an \c application-id (which can also contain wildcards for matching multiple
889 : : applications), a screenshot will be made for each window of this (these) application(s).
890 : : If only specific windows of one or more applications should be used to create screenshots, you
891 : : can specify a \c window-property selector, which will only select windows that have a matching
892 : : WindowManager::windowProperty.
893 : : Adding a \c screen-id will restrict the creation of screenshots to the the specified screen.
894 : :
895 : : Here is an example, creating screenshots of all windows on the second screen, that have the
896 : : window-property \c type set to \c cluster and written by Pelagicore:
897 : : \badcode
898 : : com.pelagicore.*[type=cluster]:1
899 : : \endcode
900 : :
901 : : Returns \c true on success and \c false otherwise.
902 : :
903 : : \note This call will be handled asynchronously, so even a positive return value does not mean
904 : : that all screenshot images have been created already.
905 : : */
906 : : //TODO: either change return value to list of to-be-created filenames or add a 'finished' signal
907 : 0 : bool WindowManager::makeScreenshot(const QString &filename, const QString &selector)
908 : : {
909 : : // filename:
910 : : // %s -> screenId
911 : : // %i -> appId
912 : : // %% -> %
913 : :
914 : : // selector:
915 : : // <appid>[attribute=value]:<screenid>
916 : : // e.g. com.pelagicore.music[windowType=widget]:1
917 : : // com.pelagicore.*[windowType=]
918 : :
919 : 0 : auto substituteFilename = [filename](const QString &screenId, const QString &appId) -> QString {
920 : 0 : QString result;
921 : 0 : bool percent = false;
922 [ # # ]: 0 : for (int i = 0; i < filename.size(); ++i) {
923 [ # # ]: 0 : QChar c = filename.at(i);
924 [ # # ]: 0 : if (!percent) {
925 [ # # ]: 0 : if (c != u'%')
926 [ # # ]: 0 : result.append(c);
927 : : else
928 : : percent = true;
929 : : } else {
930 [ # # # # ]: 0 : switch (c.unicode()) {
931 : 0 : case '%':
932 [ # # ]: 0 : result.append(c);
933 : : break;
934 : 0 : case 's':
935 [ # # ]: 0 : result.append(screenId);
936 : : break;
937 : 0 : case 'i':
938 [ # # ]: 0 : result.append(appId);
939 : : break;
940 : : }
941 : : percent = false;
942 : : }
943 : : }
944 : 0 : return result;
945 : 0 : };
946 : :
947 [ # # # # : 0 : static const QRegularExpression re(u"^([a-z.-]+)?(\\[([a-zA-Z0-9_.]+)=([^\\]]*)\\])?(:([0-9]+))?"_s);
# # ]
948 [ # # ]: 0 : auto match = re.match(selector);
949 [ # # ]: 0 : QString screenId = match.captured(6);
950 [ # # ]: 0 : QString appId = match.captured(1);
951 [ # # ]: 0 : QString attributeName = match.captured(3);
952 [ # # ]: 0 : QString attributeValue = match.captured(4);
953 : :
954 : : // qWarning() << "Matching result:" << match.isValid();
955 : : // qWarning() << " screen ...... :" << screenId;
956 : : // qWarning() << " app ......... :" << appId;
957 : : // qWarning() << " attributeName :" << attributeName;
958 : : // qWarning() << " attributeValue:" << attributeValue;
959 : :
960 : 0 : bool result = true;
961 : 0 : bool foundAtLeastOne = false;
962 : :
963 [ # # # # ]: 0 : if (appId.isEmpty() && attributeName.isEmpty()) {
964 : : // fullscreen screenshot
965 : :
966 [ # # ]: 0 : for (int i = 0; i < d->views.count(); ++i) {
967 [ # # # # : 0 : if (screenId.isEmpty() || screenId.toInt() == i) {
# # ]
968 [ # # ]: 0 : QImage img = d->views.at(i)->grabWindow();
969 : :
970 : 0 : foundAtLeastOne = true;
971 [ # # # # : 0 : result &= img.save(substituteFilename(QString::number(i), QString()));
# # ]
972 : 0 : }
973 : : }
974 : : } else {
975 : : // app without System UI
976 : :
977 : : // filter out alias and apps not matching appId (if set)
978 [ # # # # ]: 0 : QVector<Application *> apps = ApplicationManager::instance()->applications();
979 [ # # # # : 0 : apps.removeIf([appId](const Application *app) {
# # # # ]
980 [ # # # # : 0 : return app->isAlias() || (!appId.isEmpty() && (appId != app->id()));
# # ]
981 : : });
982 : :
983 [ # # ]: 0 : auto grabbers = new QList<QSharedPointer<const QQuickItemGrabResult>>;
984 : :
985 [ # # ]: 0 : for (const Window *w : std::as_const(d->windowsInModel)) {
986 [ # # # # ]: 0 : if (apps.contains(w->application())) {
987 : 0 : if (attributeName.isEmpty()
988 [ # # # # : 0 : || (w->windowProperty(attributeName).toString() == attributeValue)) {
# # # # #
# # # # #
# # ]
989 [ # # ]: 0 : for (int i = 0; i < d->views.count(); ++i) {
990 [ # # # # : 0 : if (screenId.isEmpty() || screenId.toInt() == i) {
# # ]
991 [ # # ]: 0 : QQuickWindow *view = d->views.at(i);
992 : :
993 : 0 : bool onScreen = false;
994 : :
995 [ # # ]: 0 : auto itemList = w->items().values();
996 [ # # ]: 0 : if (itemList.count() == 0)
997 : 0 : continue;
998 : :
999 : : // grab the first view - they all have the same content anyway
1000 [ # # ]: 0 : WindowItem *windowItem = itemList.first();
1001 : :
1002 [ # # ]: 0 : onScreen = windowItem->QQuickItem::window() == view;
1003 : :
1004 [ # # ]: 0 : if (onScreen) {
1005 : 0 : foundAtLeastOne = true;
1006 [ # # ]: 0 : QSharedPointer<const QQuickItemGrabResult> grabber = windowItem->grabToImage();
1007 : :
1008 [ # # ]: 0 : if (!grabber) {
1009 : 0 : result = false;
1010 : 0 : continue;
1011 : : }
1012 : :
1013 [ # # # # : 0 : QString saveTo = substituteFilename(QString::number(i), w->application()->id());
# # # # ]
1014 [ # # ]: 0 : grabbers->append(grabber);
1015 [ # # # # ]: 0 : connect(grabber.data(), &QQuickItemGrabResult::ready, this, [grabbers, grabber, saveTo]() {
1016 : 0 : grabber->saveToFile(saveTo);
1017 : 0 : grabbers->removeOne(grabber);
1018 [ # # ]: 0 : if (grabbers->isEmpty())
1019 : 0 : delete grabbers;
1020 : 0 : });
1021 : :
1022 : 0 : }
1023 : 0 : }
1024 : : }
1025 : : }
1026 : : }
1027 : : }
1028 : 0 : }
1029 : 0 : return foundAtLeastOne && result;
1030 : 0 : }
1031 : :
1032 : 67 : int WindowManagerPrivate::findWindowBySurfaceItem(QQuickItem *quickItem) const
1033 : : {
1034 [ + + ]: 87 : for (int i = 0; i < allWindows.count(); ++i) {
1035 : 20 : auto *window = allWindows.at(i);
1036 [ + - - + ]: 20 : if (window->isInProcess() && static_cast<InProcessWindow*>(window)->rootItem() == quickItem)
1037 : 0 : return i;
1038 : : }
1039 : : return -1;
1040 : : }
1041 : :
1042 : 0 : QList<QQuickWindow *> WindowManager::compositorViews() const
1043 : : {
1044 : 0 : return d->views;
1045 : : }
1046 : :
1047 : : #if QT_CONFIG(am_multi_process)
1048 : :
1049 : 77 : int WindowManagerPrivate::findWindowByWaylandSurface(QWaylandSurface *waylandSurface) const
1050 : : {
1051 [ + + ]: 96 : for (int i = 0; i < allWindows.count(); ++i) {
1052 : 19 : auto *window = allWindows.at(i);
1053 : :
1054 [ - + ]: 19 : if (window->isInProcess())
1055 : 0 : continue;
1056 : :
1057 [ + - ]: 19 : auto windowSurface = static_cast<WaylandWindow*>(window)->surface();
1058 [ + - - + ]: 19 : if (windowSurface && windowSurface->surface() == waylandSurface)
1059 : 0 : return i;
1060 : : }
1061 : : return -1;
1062 : : }
1063 : :
1064 : 0 : QString WindowManagerPrivate::applicationId(Application *app, WindowSurface *windowSurface)
1065 : : {
1066 [ # # ]: 0 : if (app)
1067 : 0 : return app->id();
1068 [ # # # # : 0 : else if (windowSurface && windowSurface->surface() && windowSurface->surface()->client())
# # ]
1069 [ # # ]: 0 : return u"pid: "_s + QString::number(windowSurface->surface()->client()->processId());
1070 : : else
1071 : 0 : return u"<unknown client>"_s;
1072 : : }
1073 : :
1074 : : #endif // QT_CONFIG(am_multi_process)
1075 : :
1076 : 75655 : bool WindowManager::eventFilter(QObject *watched, QEvent *event)
1077 : : {
1078 [ + + ]: 75655 : if (event->type() == QEvent::PlatformSurface) {
1079 [ + + ]: 162 : if (auto *window = qobject_cast<QQuickWindow *>(watched)) {
1080 [ + - ]: 40 : auto surfaceEventType = static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType();
1081 [ + - ]: 40 : if (surfaceEventType == QPlatformSurfaceEvent::SurfaceCreated)
1082 : 40 : registerCompositorView(window);
1083 : : }
1084 : : }
1085 : 75655 : return QAbstractListModel::eventFilter(watched, event);
1086 : : }
1087 : :
1088 : : QT_END_NAMESPACE_AM
1089 : :
1090 : : #include "moc_windowmanager.cpp"
|