Revision control

++ // Copyright (C) 2017 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qwaylandquickshellsurfaceitem.h"
++ #include "qwaylandquickshellsurfaceitem_p.h"
++
++ #include <QtWaylandCompositor/QWaylandShellSurface>
++ #include <QGuiApplication>
++
++ QT_BEGIN_NAMESPACE
++
++ void QWaylandQuickShellSurfaceItemPrivate::setShellSurface(QWaylandShellSurface *shellSurface)
++ {
++ Q_Q(QWaylandQuickShellSurfaceItem);
++
++ if (m_shellSurface) {
++ QObject::disconnect(m_shellSurface, &QObject::destroyed, q, nullptr);
++ QObject::disconnect(m_shellSurface, &QWaylandShellSurface::modalChanged, q, nullptr);
++ }
++
++ m_shellSurface = shellSurface;
++
++ if (m_shellIntegration) {
++ q->removeEventFilter(m_shellIntegration);
++ delete m_shellIntegration;
++ m_shellIntegration = nullptr;
++ }
++
++ if (m_shellSurface) {
++ m_shellIntegration = m_shellSurface->createIntegration(q);
++ q->installEventFilter(m_shellIntegration);
++
++ QObject::connect(m_shellSurface, &QObject::destroyed,
++ q, [this]() {
++ setShellSurface(nullptr);
++ emit q_func()->shellSurfaceChanged();
++ });
++ QObject::connect(m_shellSurface, &QWaylandShellSurface::modalChanged,
++ q, [this]() {
++ if (m_shellSurface && m_shellSurface->isModal())
++ raise();
++ });
++ }
++ }
++
++ QWaylandQuickShellSurfaceItem *QWaylandQuickShellSurfaceItemPrivate::maybeCreateAutoPopup(QWaylandShellSurface* shellSurface)
++ {
++ if (!m_autoCreatePopupItems)
++ return nullptr;
++
++ Q_Q(QWaylandQuickShellSurfaceItem);
++ auto *popupItem = new QWaylandQuickShellSurfaceItem(q);
++ popupItem->setShellSurface(shellSurface);
++ popupItem->setAutoCreatePopupItems(true);
++ QObject::connect(popupItem, &QWaylandQuickShellSurfaceItem::surfaceDestroyed,
++ popupItem, &QObject::deleteLater);
++ return popupItem;
++ }
++
++ /*!
++ * \qmltype ShellSurfaceItem
++ * \nativetype QWaylandQuickShellSurfaceItem
++ * \inherits WaylandQuickItem
++ * \inqmlmodule QtWayland.Compositor
++ * \since 5.8
++ * \brief A Qt Quick item type for displaying and interacting with a ShellSurface.
++ *
++ * This type is used to render \c xdg_shell, \c ivi_application, \c wl_shell or \c qt_shell
++ * surfaces as part of a Qt Quick scene.
++ *
++ * \sa WaylandQuickItem, XdgSurface, IviSurface, QtShellSurface, WlShellSurface
++ */
++
++ /*!
++ * \class QWaylandQuickShellSurfaceItem
++ * \inmodule QtWaylandCompositor
++ * \since 5.8
++ * \brief The QWaylandQuickShellSurfaceItem class provides a Qt Quick item that represents a QWaylandShellSurface.
++ *
++ * This type is used to render \c xdg_shell, \c ivi_application, \c wl_shell or \c qt_shell
++ * surfaces as part of a Qt Quick scene.
++ *
++ * \sa QWaylandQuickItem, QWaylandXdgSurface, QWaylandIviSurface, QWaylandWlShellSurface
++ */
++
++ /*!
++ * Constructs a QWaylandQuickWlShellSurfaceItem with the given \a parent.
++ */
++ QWaylandQuickShellSurfaceItem::QWaylandQuickShellSurfaceItem(QQuickItem *parent)
++ : QWaylandQuickItem(*new QWaylandQuickShellSurfaceItemPrivate(), parent)
++ {
++ }
++
++ QWaylandQuickShellSurfaceItem::~QWaylandQuickShellSurfaceItem()
++ {
++ Q_D(QWaylandQuickShellSurfaceItem);
++
++ d->setShellSurface(nullptr);
++ }
++
++ /*!
++ * \internal
++ */
++ QWaylandQuickShellSurfaceItem::QWaylandQuickShellSurfaceItem(QWaylandQuickShellSurfaceItemPrivate &dd, QQuickItem *parent)
++ : QWaylandQuickItem(dd, parent)
++ {
++ }
++
++ /*!
++ * \qmlproperty ShellSurface QtWayland.Compositor::ShellSurfaceItem::shellSurface
++ *
++ * This property holds the ShellSurface rendered by this ShellSurfaceItem.
++ * It may either be an XdgSurfaceV5, WlShellSurface or IviSurface depending on which shell protocol
++ * is in use.
++ */
++
++ /*!
++ * \property QWaylandQuickShellSurfaceItem::shellSurface
++ *
++ * This property holds the QWaylandShellSurface rendered by this QWaylandQuickShellSurfaceItem.
++ * It may either be a QWaylandXdgSurfaceV5, QWaylandWlShellSurface or QWaylandIviSurface depending
++ * on which shell protocol is in use.
++ */
++ QWaylandShellSurface *QWaylandQuickShellSurfaceItem::shellSurface() const
++ {
++ Q_D(const QWaylandQuickShellSurfaceItem);
++ return d->m_shellSurface;
++ }
++
++ void QWaylandQuickShellSurfaceItem::setShellSurface(QWaylandShellSurface *shellSurface)
++ {
++ Q_D(QWaylandQuickShellSurfaceItem);
++ if (d->m_shellSurface == shellSurface)
++ return;
++
++ d->setShellSurface(shellSurface);
++
++ emit shellSurfaceChanged();
++ }
++
++ /*!
++ * \qmlproperty Item QtWayland.Compositor::ShellSurfaceItem::moveItem
++ *
++ * This property holds the move item for this ShellSurfaceItem. This is the item that will be moved
++ * when the clients request the ShellSurface to be moved, maximized, resized etc. This property is
++ * useful when implementing server-side decorations.
++ */
++
++ /*!
++ * \property QWaylandQuickShellSurfaceItem::moveItem
++ *
++ * This property holds the move item for this QWaylandQuickShellSurfaceItem. This is the item that
++ * will be moved when the clients request the QWaylandShellSurface to be moved, maximized, resized
++ * etc. This property is useful when implementing server-side decorations.
++ */
++ QQuickItem *QWaylandQuickShellSurfaceItem::moveItem() const
++ {
++ Q_D(const QWaylandQuickShellSurfaceItem);
++ return d->m_moveItem ? d->m_moveItem : const_cast<QWaylandQuickShellSurfaceItem *>(this);
++ }
++
++ void QWaylandQuickShellSurfaceItem::setMoveItem(QQuickItem *moveItem)
++ {
++ Q_D(QWaylandQuickShellSurfaceItem);
++ moveItem = moveItem ? moveItem : this;
++ if (this->moveItem() == moveItem)
++ return;
++ d->m_moveItem = moveItem;
++ moveItemChanged();
++ }
++
++ /*!
++ * \qmlproperty bool QtWayland.Compositor::ShellSurfaceItem::autoCreatePopupItems
++ * \default true
++ *
++ * This property holds whether ShellSurfaceItems for popups parented to the shell
++ * surface managed by this item should automatically be created.
++ */
++
++ /*!
++ * \property QWaylandQuickShellSurfaceItem::autoCreatePopupItems
++ *
++ * This property holds whether QWaylandQuickShellSurfaceItems for popups
++ * parented to the shell surface managed by this item should automatically be created.
++ */
++ bool QWaylandQuickShellSurfaceItem::autoCreatePopupItems()
++ {
++ Q_D(const QWaylandQuickShellSurfaceItem);
++ return d->m_autoCreatePopupItems;
++ }
++
++ void QWaylandQuickShellSurfaceItem::setAutoCreatePopupItems(bool enabled)
++ {
++ Q_D(QWaylandQuickShellSurfaceItem);
++
++ if (enabled == d->m_autoCreatePopupItems)
++ return;
++
++ d->m_autoCreatePopupItems = enabled;
++ emit autoCreatePopupItemsChanged();
++ }
++
++ /*!
++ \class QWaylandQuickShellEventFilter
++ \brief QWaylandQuickShellEventFilter implements a Wayland popup grab
++ \internal
++ */
++
++ void QWaylandQuickShellEventFilter::startFilter(QWaylandClient *client, CallbackFunction closePopups)
++ {
++ if (!self)
++ self = new QWaylandQuickShellEventFilter(qGuiApp);
++ if (!self->eventFilterInstalled) {
++ qGuiApp->installEventFilter(self);
++ self->eventFilterInstalled = true;
++ self->client = client;
++ self->closePopups = closePopups;
++ }
++ }
++
++ void QWaylandQuickShellEventFilter::cancelFilter()
++ {
++ if (!self)
++ return;
++ if (self->eventFilterInstalled && !self->waitForRelease)
++ self->stopFilter();
++ }
++
++ void QWaylandQuickShellEventFilter::stopFilter()
++ {
++ if (eventFilterInstalled) {
++ qGuiApp->removeEventFilter(this);
++ eventFilterInstalled = false;
++ }
++ }
++ QWaylandQuickShellEventFilter *QWaylandQuickShellEventFilter::self = nullptr;
++
++ QWaylandQuickShellEventFilter::QWaylandQuickShellEventFilter(QObject *parent)
++ : QObject(parent)
++ {
++ }
++
++ bool QWaylandQuickShellEventFilter::eventFilter(QObject *receiver, QEvent *e)
++ {
++ if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease) {
++ bool press = e->type() == QEvent::MouseButtonPress;
++ if (press && !waitForRelease) {
++ // The user clicked something: we need to close popups unless this press is caught later
++ if (!mousePressTimeout.isActive())
++ mousePressTimeout.start(0, this);
++ }
++
++ QQuickItem *item = qobject_cast<QQuickItem*>(receiver);
++ if (!item)
++ return false;
++
++ QMouseEvent *event = static_cast<QMouseEvent*>(e);
++ QWaylandQuickShellSurfaceItem *shellSurfaceItem = qobject_cast<QWaylandQuickShellSurfaceItem*>(item);
++ bool finalRelease = (event->type() == QEvent::MouseButtonRelease) && (event->buttons() == Qt::NoButton);
++ bool popupClient = shellSurfaceItem && shellSurfaceItem->surface() && shellSurfaceItem->surface()->client() == client;
++
++ if (waitForRelease) {
++ // We are eating events until all mouse buttons are released
++ if (finalRelease) {
++ waitForRelease = false;
++ stopFilter();
++ }
++ return true;
++ }
++
++ if (finalRelease && mousePressTimeout.isActive()) {
++ // the user somehow managed to press and release the mouse button in 0 milliseconds
++ qWarning("Badly written autotest detected");
++ mousePressTimeout.stop();
++ stopFilter();
++ }
++
++ if (press && !shellSurfaceItem && !QQmlProperty(item, QStringLiteral("qtwayland_blocking_overlay")).isValid()) {
++ // the user clicked on something that's not blocking mouse events
++ e->ignore(); //propagate the event to items below
++ return true; // don't give the event to the item
++ }
++
++ mousePressTimeout.stop(); // we've got this
++
++ if (press && !popupClient) {
++ // The user clicked outside the active popup's client. The popups should
++ // be closed, but the event filter will stay to catch the release-
++ // event before removing itself.
++ waitForRelease = true;
++ closePopups();
++ return true;
++ }
++ }
++
++ return false;
++ }
++
++ void QWaylandQuickShellEventFilter::timerEvent(QTimerEvent *event)
++ {
++ if (event->timerId() == mousePressTimeout.timerId()) {
++ mousePressTimeout.stop();
++ closePopups();
++ stopFilter();
++ // Don't wait for release: Since the press wasn't accepted,
++ // the release won't be delivered.
++ }
++ }
++
++ static QWaylandQuickShellSurfaceItem *findSurfaceItemFromMoveItem(QQuickItem *moveItem)
++ {
++ if (Q_UNLIKELY(!moveItem))
++ return nullptr;
++ if (auto *surf = qobject_cast<QWaylandQuickShellSurfaceItem *>(moveItem))
++ return surf;
++
++ const auto childItems = moveItem->childItems();
++ for (auto *item : childItems) {
++ if (auto *surf = findSurfaceItemFromMoveItem(item))
++ return surf;
++ }
++ return nullptr;
++ }
++
++ static inline bool onTop(QWaylandQuickShellSurfaceItem *surf)
++ {
++ return surf->staysOnTop() || (surf->shellSurface() && surf->shellSurface()->isModal());
++ }
++
++ static inline bool onBottom(QWaylandQuickShellSurfaceItem *surf)
++ {
++ return surf->staysOnBottom() && !(surf->shellSurface() && surf->shellSurface()->isModal());
++ }
++
++ /*
++ To raise a surface, find the topmost suitable surface and place above that.
++ We start from the top and:
++ If we don't have staysOnTop, skip all surfaces with staysOnTop
++ If we have staysOnBottom, skip all surfaces that don't have staysOnBottom
++ A modal dialog is handled as if it had staysOnTop
++ */
++ void QWaylandQuickShellSurfaceItemPrivate::raise()
++ {
++ Q_Q(QWaylandQuickShellSurfaceItem);
++ auto *moveItem = q->moveItem();
++ QQuickItem *parent = moveItem->parentItem();
++ if (!parent || !m_shellSurface)
++ return;
++ const bool putOnTop = staysOnTop || m_shellSurface->isModal();
++ const bool putOnBottom = staysOnBottom && !m_shellSurface->isModal();
++
++ const QList<QQuickItem *> childItems = parent->childItems();
++ auto it = childItems.crbegin();
++ const auto end = childItems.crend();
++ auto skip = [putOnTop, putOnBottom](QQuickItem *item) {
++ if (auto *surf = findSurfaceItemFromMoveItem(item))
++ return (!putOnTop && onTop(surf)) || (putOnBottom && !onBottom(surf));
++ return true; // ignore any other Quick items that may be there
++ };
++ while (it != end && skip(*it))
++ ++it;
++ if (it != end) {
++ QQuickItem *top = *it;
++ if (moveItem != top)
++ moveItem->stackAfter(top);
++ }
++ }
++
++ /*
++ To lower a surface, find the lowest suitable surface and place below that.
++ We start from the bottom and:
++ If we don't have staysOnBottom, skip all surfaces with staysOnBottom
++ If we have staysOnTop, skip all surfaces that don't have staysOnTop
++ A modal dialog is handled as if it had staysOnTop
++ */
++ void QWaylandQuickShellSurfaceItemPrivate::lower()
++ {
++ Q_Q(QWaylandQuickShellSurfaceItem);
++ auto *moveItem = q->moveItem();
++ QQuickItem *parent = moveItem->parentItem();
++ if (!parent || !m_shellSurface)
++ return;
++ const bool putOnTop = staysOnTop || m_shellSurface->isModal();
++ const bool putOnBottom = staysOnBottom && !m_shellSurface->isModal();
++
++ const QList<QQuickItem *> childItems = parent->childItems();
++ auto it = childItems.cbegin();
++ auto skip = [putOnTop, putOnBottom](QQuickItem *item) {
++ if (auto *surf = findSurfaceItemFromMoveItem(item))
++ return (!putOnBottom && onBottom(surf)) || (putOnTop && !onTop(surf));
++ return true; // ignore any other Quick items that may be there
++ };
++ while (skip(*it))
++ ++it;
++
++ QQuickItem *bottom = *it;
++ if (moveItem != bottom)
++ moveItem->stackBefore(bottom);
++ }
++
++ /*!
++ * \property QWaylandQuickShellSurfaceItem::staysOnTop
++ *
++ * Keep this item above other Wayland surfaces
++ */
++ bool QWaylandQuickShellSurfaceItem::staysOnTop() const
++ {
++ Q_D(const QWaylandQuickShellSurfaceItem);
++ return d->staysOnTop;
++ }
++
++ void QWaylandQuickShellSurfaceItem::setStaysOnTop(bool onTop)
++ {
++ Q_D(QWaylandQuickShellSurfaceItem);
++ if (d->staysOnTop == onTop)
++ return;
++ d->staysOnTop = onTop;
++ if (d->staysOnBottom) {
++ d->staysOnBottom = false;
++ emit staysOnBottomChanged();
++ }
++ // We need to call raise() even if onTop is false, since we need to stack under any other
++ // staysOnTop surfaces in that case
++ raise();
++ emit staysOnTopChanged();
++ Q_ASSERT(!(d->staysOnTop && d->staysOnBottom));
++ }
++
++ /*!
++ * \property QWaylandQuickShellSurfaceItem::staysOnBottom
++ *
++ * Keep this item above other Wayland surfaces
++ */
++ bool QWaylandQuickShellSurfaceItem::staysOnBottom() const
++ {
++ Q_D(const QWaylandQuickShellSurfaceItem);
++ return d->staysOnBottom;
++ }
++
++ void QWaylandQuickShellSurfaceItem::setStaysOnBottom(bool onBottom)
++ {
++ Q_D(QWaylandQuickShellSurfaceItem);
++ if (d->staysOnBottom == onBottom)
++ return;
++ d->staysOnBottom = onBottom;
++ if (d->staysOnTop) {
++ d->staysOnTop = false;
++ emit staysOnTopChanged();
++ }
++ // We need to call lower() even if onBottom is false, since we need to stack over any other
++ // staysOnBottom surfaces in that case
++ lower();
++ emit staysOnBottomChanged();
++ Q_ASSERT(!(d->staysOnTop && d->staysOnBottom));
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qwaylandquickshellsurfaceitem_p.cpp"
++
++ #include "moc_qwaylandquickshellsurfaceitem.cpp"