/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtWaylandCompositor module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPLv3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qwaylandxdgshell.h" #include "qwaylandxdgshell_p.h" #if QT_CONFIG(wayland_compositor_quick) #include "qwaylandxdgshellintegration_p.h" #endif #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE QWaylandXdgShellPrivate::QWaylandXdgShellPrivate() { } void QWaylandXdgShellPrivate::ping(QtWaylandServer::xdg_wm_base::Resource *resource, uint32_t serial) { m_pings.insert(serial); send_ping(resource->handle, serial); } void QWaylandXdgShellPrivate::registerXdgSurface(QWaylandXdgSurface *xdgSurface) { m_xdgSurfaces.insert(xdgSurface->surface()->client()->client(), xdgSurface); } void QWaylandXdgShellPrivate::unregisterXdgSurface(QWaylandXdgSurface *xdgSurface) { auto xdgSurfacePrivate = QWaylandXdgSurfacePrivate::get(xdgSurface); if (!m_xdgSurfaces.remove(xdgSurfacePrivate->resource()->client(), xdgSurface)) qWarning("%s Unexpected state. Can't find registered xdg surface\n", Q_FUNC_INFO); } QWaylandXdgSurface *QWaylandXdgShellPrivate::xdgSurfaceFromSurface(QWaylandSurface *surface) { for (QWaylandXdgSurface *xdgSurface : qAsConst(m_xdgSurfaces)) { if (surface == xdgSurface->surface()) return xdgSurface; } return nullptr; } void QWaylandXdgShellPrivate::xdg_wm_base_destroy(Resource *resource) { if (!m_xdgSurfaces.values(resource->client()).empty()) wl_resource_post_error(resource->handle, XDG_WM_BASE_ERROR_DEFUNCT_SURFACES, "xdg_shell was destroyed before children"); wl_resource_destroy(resource->handle); } void QWaylandXdgShellPrivate::xdg_wm_base_create_positioner(QtWaylandServer::xdg_wm_base::Resource *resource, uint32_t id) { QWaylandResource positionerResource(wl_resource_create(resource->client(), &xdg_positioner_interface, wl_resource_get_version(resource->handle), id)); new QWaylandXdgPositioner(positionerResource); } void QWaylandXdgShellPrivate::xdg_wm_base_get_xdg_surface(Resource *resource, uint32_t id, wl_resource *surfaceResource) { Q_Q(QWaylandXdgShell); QWaylandSurface *surface = QWaylandSurface::fromResource(surfaceResource); if (surface->role() != nullptr) { wl_resource_post_error(resource->handle, XDG_WM_BASE_ERROR_ROLE, "wl_surface@%d, already has role %s\n", wl_resource_get_id(surface->resource()), surface->role()->name().constData()); return; } if (surface->hasContent()) { //TODO: According to the spec, this is a client error, but there's no appropriate error code qWarning() << "get_xdg_surface requested on a xdg_surface with content"; } QWaylandResource xdgSurfaceResource(wl_resource_create(resource->client(), &xdg_surface_interface, wl_resource_get_version(resource->handle), id)); QWaylandXdgSurface *xdgSurface = new QWaylandXdgSurface(q, surface, xdgSurfaceResource); registerXdgSurface(xdgSurface); emit q->xdgSurfaceCreated(xdgSurface); } void QWaylandXdgShellPrivate::xdg_wm_base_pong(Resource *resource, uint32_t serial) { Q_UNUSED(resource); Q_Q(QWaylandXdgShell); if (m_pings.remove(serial)) emit q->pong(serial); else qWarning("Received an unexpected pong!"); } /*! * \qmltype XdgShell * \inqmlmodule QtWayland.Compositor * \since 5.12 * \brief Provides an extension for desktop-style user interfaces. * * The XdgShell extension provides a way to associate a XdgToplevel or XdgPopup * with a regular Wayland surface. Using the XdgToplevel interface, the client * can request that the surface is resized, moved, and so on. * * XdgShell corresponds to the Wayland interface, \c xdg_shell. * * To provide the functionality of the shell extension in a compositor, create * an instance of the XdgShell component and add it to the list of extensions * supported by the compositor: * * \qml \QtMinorVersion * import QtWayland.Compositor 1.\1 * * WaylandCompositor { * XdgShell { * // ... * } * } * \endqml */ /*! * \class QWaylandXdgShell * \inmodule QtWaylandCompositor * \since 5.12 * \brief The QWaylandXdgShell class is an extension for desktop-style user interfaces. * * The QWaylandXdgShell extension provides a way to associate a QWaylandXdgToplevel or * QWaylandXdgPopup with a regular Wayland surface. Using the QWaylandXdgToplevel interface, * the client can request that the surface is resized, moved, and so on. * * QWaylandXdgShell corresponds to the Wayland interface, \c xdg_shell. */ /*! * Constructs a QWaylandXdgShell object. */ QWaylandXdgShell::QWaylandXdgShell() : QWaylandShellTemplate(*new QWaylandXdgShellPrivate()) { } /*! * Constructs a QWaylandXdgShell object for the provided \a compositor. */ QWaylandXdgShell::QWaylandXdgShell(QWaylandCompositor *compositor) : QWaylandShellTemplate(compositor, *new QWaylandXdgShellPrivate()) { } /*! * Initializes the shell extension. */ void QWaylandXdgShell::initialize() { Q_D(QWaylandXdgShell); QWaylandShellTemplate::initialize(); QWaylandCompositor *compositor = static_cast(extensionContainer()); if (!compositor) { qWarning() << "Failed to find QWaylandCompositor when initializing QWaylandXdgShell"; return; } d->init(compositor->display(), 1); handleSeatChanged(compositor->defaultSeat(), nullptr); connect(compositor, &QWaylandCompositor::defaultSeatChanged, this, &QWaylandXdgShell::handleSeatChanged); } /*! * Returns the Wayland interface for the QWaylandXdgShell. */ const struct wl_interface *QWaylandXdgShell::interface() { return QWaylandXdgShellPrivate::interface(); } QByteArray QWaylandXdgShell::interfaceName() { return QWaylandXdgShellPrivate::interfaceName(); } /*! * \qmlmethod void QtWaylandCompositor::XdgShell::ping(WaylandClient client) * * Sends a ping event to \a client. If the client replies to the event the * \l pong signal will be emitted. */ /*! * Sends a ping event to \a client. If the client replies to the event the * \l pong signal will be emitted. */ uint QWaylandXdgShell::ping(QWaylandClient *client) { Q_D(QWaylandXdgShell); QWaylandCompositor *compositor = static_cast(extensionContainer()); Q_ASSERT(compositor); uint32_t serial = compositor->nextSerial(); QWaylandXdgShellPrivate::Resource *clientResource = d->resourceMap().value(client->client(), nullptr); Q_ASSERT(clientResource); d->ping(clientResource, serial); return serial; } void QWaylandXdgShell::handleSeatChanged(QWaylandSeat *newSeat, QWaylandSeat *oldSeat) { if (oldSeat != nullptr) { disconnect(oldSeat, &QWaylandSeat::keyboardFocusChanged, this, &QWaylandXdgShell::handleFocusChanged); } if (newSeat != nullptr) { connect(newSeat, &QWaylandSeat::keyboardFocusChanged, this, &QWaylandXdgShell::handleFocusChanged); } } void QWaylandXdgShell::handleFocusChanged(QWaylandSurface *newSurface, QWaylandSurface *oldSurface) { Q_D(QWaylandXdgShell); QWaylandXdgSurface *newXdgSurface = d->xdgSurfaceFromSurface(newSurface); QWaylandXdgSurface *oldXdgSurface = d->xdgSurfaceFromSurface(oldSurface); if (newXdgSurface) QWaylandXdgSurfacePrivate::get(newXdgSurface)->handleFocusReceived(); if (oldXdgSurface) QWaylandXdgSurfacePrivate::get(oldXdgSurface)->handleFocusLost(); } QWaylandXdgSurfacePrivate::QWaylandXdgSurfacePrivate() { } void QWaylandXdgSurfacePrivate::setWindowType(Qt::WindowType windowType) { if (m_windowType == windowType) return; m_windowType = windowType; Q_Q(QWaylandXdgSurface); emit q->windowTypeChanged(); } void QWaylandXdgSurfacePrivate::handleFocusLost() { if (m_toplevel) QWaylandXdgToplevelPrivate::get(m_toplevel)->handleFocusLost(); } void QWaylandXdgSurfacePrivate::handleFocusReceived() { if (m_toplevel) QWaylandXdgToplevelPrivate::get(m_toplevel)->handleFocusReceived(); } QRect QWaylandXdgSurfacePrivate::calculateFallbackWindowGeometry() const { // TODO: The unset window geometry should include subsurfaces as well, so this solution // won't work too well on those kinds of clients. return QRect(QPoint(), m_surface->destinationSize()); } void QWaylandXdgSurfacePrivate::updateFallbackWindowGeometry() { Q_Q(QWaylandXdgSurface); if (!m_unsetWindowGeometry) return; const QRect unsetGeometry = calculateFallbackWindowGeometry(); if (unsetGeometry == m_windowGeometry) return; m_windowGeometry = unsetGeometry; emit q->windowGeometryChanged(); } void QWaylandXdgSurfacePrivate::xdg_surface_destroy_resource(QtWaylandServer::xdg_surface::Resource *resource) { Q_UNUSED(resource); Q_Q(QWaylandXdgSurface); QWaylandXdgShellPrivate::get(m_xdgShell)->unregisterXdgSurface(q); delete q; } void QWaylandXdgSurfacePrivate::xdg_surface_destroy(QtWaylandServer::xdg_surface::Resource *resource) { wl_resource_destroy(resource->handle); } void QWaylandXdgSurfacePrivate::xdg_surface_get_toplevel(QtWaylandServer::xdg_surface::Resource *resource, uint32_t id) { Q_Q(QWaylandXdgSurface); if (m_toplevel || m_popup) { wl_resource_post_error(resource->handle, XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED, "xdg_surface already has a role object"); return; } if (!m_surface->setRole(QWaylandXdgToplevel::role(), resource->handle, XDG_WM_BASE_ERROR_ROLE)) return; QWaylandResource topLevelResource(wl_resource_create(resource->client(), &xdg_toplevel_interface, wl_resource_get_version(resource->handle), id)); m_toplevel = new QWaylandXdgToplevel(q, topLevelResource); emit q->toplevelCreated(); emit m_xdgShell->toplevelCreated(m_toplevel, q); } void QWaylandXdgSurfacePrivate::xdg_surface_get_popup(QtWaylandServer::xdg_surface::Resource *resource, uint32_t id, wl_resource *parentResource, wl_resource *positionerResource) { Q_Q(QWaylandXdgSurface); if (m_toplevel || m_popup) { wl_resource_post_error(resource->handle, XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED, "xdg_surface already has a role object"); return; } QWaylandXdgSurface *parent = QWaylandXdgSurface::fromResource(parentResource); if (!parent) { wl_resource_post_error(resource->handle, XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT, "xdg_surface.get_popup with invalid popup parent"); return; } QWaylandXdgPositioner *positioner = QWaylandXdgPositioner::fromResource(positionerResource); if (!positioner) { wl_resource_post_error(resource->handle, XDG_WM_BASE_ERROR_INVALID_POSITIONER, "xdg_surface.get_popup without positioner"); return; } if (!positioner->m_data.isComplete()) { QWaylandXdgPositionerData p = positioner->m_data; wl_resource_post_error(resource->handle, XDG_WM_BASE_ERROR_INVALID_POSITIONER, "xdg_surface.get_popup with invalid positioner (size: %dx%d, anchorRect: %dx%d)", p.size.width(), p.size.height(), p.anchorRect.width(), p.anchorRect.height()); return; } QRect anchorBounds(QPoint(0, 0), parent->windowGeometry().size()); if (!anchorBounds.contains(positioner->m_data.anchorRect)) { // TODO: this is a protocol error and should ideally be handled like this: //wl_resource_post_error(resource->handle, XDG_WM_BASE_ERROR_INVALID_POSITIONER, // "xdg_positioner anchor rect extends beyound its parent's window geometry"); //return; // However, our own clients currently do this, so we'll settle for a gentle warning instead. qCWarning(qLcWaylandCompositor) << "Ignoring client protocol error: xdg_positioner anchor" << "rect extends beyond its parent's window geometry"; } if (!m_surface->setRole(QWaylandXdgPopup::role(), resource->handle, XDG_WM_BASE_ERROR_ROLE)) return; QWaylandResource popupResource(wl_resource_create(resource->client(), &xdg_popup_interface, wl_resource_get_version(resource->handle), id)); m_popup = new QWaylandXdgPopup(q, parent, positioner, popupResource); emit q->popupCreated(); emit m_xdgShell->popupCreated(m_popup, q); } void QWaylandXdgSurfacePrivate::xdg_surface_ack_configure(QtWaylandServer::xdg_surface::Resource *resource, uint32_t serial) { if (m_toplevel) { QWaylandXdgToplevelPrivate::get(m_toplevel)->handleAckConfigure(serial); } else if (m_popup) { QWaylandXdgPopupPrivate::get(m_popup)->handleAckConfigure(serial); } else { wl_resource_post_error(resource->handle, XDG_SURFACE_ERROR_NOT_CONSTRUCTED, "ack_configure requested on an unconstructed xdg_surface"); } } void QWaylandXdgSurfacePrivate::xdg_surface_set_window_geometry(QtWaylandServer::xdg_surface::Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { Q_Q(QWaylandXdgSurface); if (!q->surface()->role()) { wl_resource_post_error(resource->handle, XDG_SURFACE_ERROR_NOT_CONSTRUCTED, "set_window_geometry requested on an unconstructed xdg_surface"); return; } if (width <= 0 || height <= 0) { // The protocol spec says "setting an invalid size will raise an error". But doesn't tell // which error to raise, and there's no fitting error in the xdg_surface_error enum. // So until this is fixed, just output a warning and return. qWarning() << "Invalid (non-positive) dimensions received in set_window_geometry"; return; } m_unsetWindowGeometry = false; QRect geometry(x, y, width, height); if (m_windowGeometry == geometry) return; m_windowGeometry = geometry; emit q->windowGeometryChanged(); } /*! * \qmltype XdgSurface * \inqmlmodule QtWayland.Compositor * \since 5.12 * \brief XdgSurface provides desktop-style compositor-specific features to an xdg surface. * * This type is part of the \l{XdgShell} extension and provides a way to * extend the functionality of an existing \l{WaylandSurface} with features * specific to desktop-style compositors, such as resizing and moving the * surface. * * It corresponds to the Wayland interface \c xdg_surface. */ /*! * \class QWaylandXdgSurface * \inmodule QtWaylandCompositor * \since 5.12 * \brief The QWaylandXdgSurface class provides desktop-style compositor-specific features to an xdg surface. * * This class is part of the QWaylandXdgShell extension and provides a way to * extend the functionality of an existing QWaylandSurface with features * specific to desktop-style compositors, such as resizing and moving the * surface. * * It corresponds to the Wayland interface \c xdg_surface. */ /*! * Constructs a QWaylandXdgSurface. */ QWaylandXdgSurface::QWaylandXdgSurface() : QWaylandShellSurfaceTemplate(*new QWaylandXdgSurfacePrivate) { } /*! * Constructs a QWaylandXdgSurface for \a surface and initializes it with the * given \a xdgShell, \a surface, and resource \a res. */ QWaylandXdgSurface::QWaylandXdgSurface(QWaylandXdgShell *xdgShell, QWaylandSurface *surface, const QWaylandResource &res) : QWaylandShellSurfaceTemplate(*new QWaylandXdgSurfacePrivate) { initialize(xdgShell, surface, res); } /*! * \qmlmethod void QtWaylandCompositor::XdgSurface::initialize(object xdgShell, object surface, object client, int id) * * Initializes the XdgSurface, associating it with the given \a xdgShell, \a surface, * \a client, and \a id. */ /*! * Initializes the QWaylandXdgSurface, associating it with the given \a xdgShell, \a surface * and \a resource. */ void QWaylandXdgSurface::initialize(QWaylandXdgShell *xdgShell, QWaylandSurface *surface, const QWaylandResource &resource) { Q_D(QWaylandXdgSurface); d->m_xdgShell = xdgShell; d->m_surface = surface; d->init(resource.resource()); setExtensionContainer(surface); d->m_windowGeometry = d->calculateFallbackWindowGeometry(); connect(surface, &QWaylandSurface::destinationSizeChanged, this, &QWaylandXdgSurface::handleSurfaceSizeChanged); connect(surface, &QWaylandSurface::bufferScaleChanged, this, &QWaylandXdgSurface::handleBufferScaleChanged); emit shellChanged(); emit surfaceChanged(); QWaylandCompositorExtension::initialize(); } /*! * \qmlproperty enum QtWaylandCompositor::XdgSurface::windowType * * This property holds the window type of the XdgSurface. */ Qt::WindowType QWaylandXdgSurface::windowType() const { Q_D(const QWaylandXdgSurface); return d->m_windowType; } /*! * \qmlproperty rect QtWaylandCompositor::XdgSurface::windowGeometry * * This property holds the window geometry of the QWaylandXdgSurface. The window * geometry describes the window's visible bounds from the user's perspective. * The geometry includes title bars and borders if drawn by the client, but * excludes drop shadows. It is meant to be used for aligning and tiling * windows. */ /*! * \property QWaylandXdgSurface::windowGeometry * * This property holds the window geometry of the QWaylandXdgSurface. The window * geometry describes the window's visible bounds from the user's perspective. * The geometry includes title bars and borders if drawn by the client, but * excludes drop shadows. It is meant to be used for aligning and tiling * windows. */ QRect QWaylandXdgSurface::windowGeometry() const { Q_D(const QWaylandXdgSurface); return d->m_windowGeometry; } /*! * \internal */ void QWaylandXdgSurface::initialize() { QWaylandCompositorExtension::initialize(); } void QWaylandXdgSurface::handleSurfaceSizeChanged() { Q_D(QWaylandXdgSurface); d->updateFallbackWindowGeometry(); } void QWaylandXdgSurface::handleBufferScaleChanged() { Q_D(QWaylandXdgSurface); d->updateFallbackWindowGeometry(); } /*! * \qmlproperty XdgShell QtWaylandCompositor::XdgSurface::shell * * This property holds the shell associated with this XdgSurface. */ /*! * \property QWaylandXdgSurface::shell * * This property holds the shell associated with this QWaylandXdgSurface. */ QWaylandXdgShell *QWaylandXdgSurface::shell() const { Q_D(const QWaylandXdgSurface); return d->m_xdgShell; } /*! * \qmlproperty WaylandSurface QtWaylandCompositor::XdgSurface::surface * * This property holds the surface associated with this XdgSurface. */ /*! * \property QWaylandXdgSurface::surface * * This property holds the surface associated with this QWaylandXdgSurface. */ QWaylandSurface *QWaylandXdgSurface::surface() const { Q_D(const QWaylandXdgSurface); return d->m_surface; } /*! * \qmlproperty XdgToplevel QtWaylandCompositor::XdgSurface::toplevel * * This property holds the properties and methods that are specific to the * toplevel XdgSurface. * * \sa popup, XdgShell::toplevelCreated */ /*! * \property QWaylandXdgSurface::toplevel * * This property holds the properties and methods that are specific to the * toplevel QWaylandXdgSurface. * * \sa QWaylandXdgSurface::popup, QWaylandXdgShell::toplevelCreated */ QWaylandXdgToplevel *QWaylandXdgSurface::toplevel() const { Q_D(const QWaylandXdgSurface); return d->m_toplevel; } /*! * \qmlproperty XdgPopup QtWaylandCompositor::XdgSurface::popup * * This property holds the properties and methods that are specific to the * popup XdgSurface. * * \sa toplevel, XdgShell::popupCreated */ /*! * \property QWaylandXdgSurface::popup * * This property holds the properties and methods that are specific to the * popup QWaylandXdgSurface. * * \sa QWaylandXdgSurface::toplevel, QWaylandXdgShell::popupCreated */ QWaylandXdgPopup *QWaylandXdgSurface::popup() const { Q_D(const QWaylandXdgSurface); return d->m_popup; } /*! * Returns the Wayland interface for the QWaylandXdgSurface. */ const wl_interface *QWaylandXdgSurface::interface() { return QWaylandXdgSurfacePrivate::interface(); } /*! * \internal */ QByteArray QWaylandXdgSurface::interfaceName() { return QWaylandXdgSurfacePrivate::interfaceName(); } /*! * Returns the QWaylandXdgSurface corresponding to the \a resource. */ QWaylandXdgSurface *QWaylandXdgSurface::fromResource(wl_resource *resource) { if (auto p = QtWayland::fromResource(resource)) return p->q_func(); return nullptr; } #if QT_CONFIG(wayland_compositor_quick) QWaylandQuickShellIntegration *QWaylandXdgSurface::createIntegration(QWaylandQuickShellSurfaceItem *item) { Q_D(const QWaylandXdgSurface); if (d->m_toplevel) return new QtWayland::XdgToplevelIntegration(item); if (d->m_popup) return new QtWayland::XdgPopupIntegration(item); return nullptr; } #endif /*! * \qmltype XdgToplevel * \inqmlmodule QtWayland.Compositor * \since 5.12 * \brief XdgToplevel represents the toplevel window specific parts of an xdg surface. * * This type is part of the \l{XdgShell} extension and provides a way to * extend the functionality of an XdgSurface with features * specific to desktop-style windows. * * It corresponds to the Wayland interface \c xdg_toplevel. */ /*! * \class QWaylandXdgToplevel * \inmodule QtWaylandCompositor * \since 5.12 * \brief The QWaylandXdgToplevel class represents the toplevel window specific parts of an xdg surface. * * This class is part of the QWaylandXdgShell extension and provides a way to * extend the functionality of an QWaylandXdgSurface with features * specific to desktop-style windows. * * It corresponds to the Wayland interface \c xdg_toplevel. */ /*! * Constructs a QWaylandXdgToplevel for the given \a xdgSurface and \a resource. */ QWaylandXdgToplevel::QWaylandXdgToplevel(QWaylandXdgSurface *xdgSurface, QWaylandResource &resource) : QObject(*new QWaylandXdgToplevelPrivate(xdgSurface, resource)) { QVector states; sendConfigure({0, 0}, states); } QWaylandXdgToplevel::~QWaylandXdgToplevel() { Q_D(QWaylandXdgToplevel); // Usually, the decoration is destroyed by the client (according to the protocol), // but if the client misbehaves, or is shut down, we need to clean up here. if (Q_UNLIKELY(d->m_decoration)) wl_resource_destroy(d->m_decoration->resource()->handle); Q_ASSERT(!d->m_decoration); } /*! * \qmlproperty XdgSurface QtWaylandCompositor::XdgToplevel::xdgSurface * * This property holds the XdgSurface for this XdgToplevel. */ /*! * \property QWaylandXdgToplevel::xdgSurface * * This property holds the QWaylandXdgSurface for this QWaylandXdgToplevel. */ QWaylandXdgSurface *QWaylandXdgToplevel::xdgSurface() const { Q_D(const QWaylandXdgToplevel); return d->m_xdgSurface; } /*! * \qmlproperty XdgToplevel QtWaylandCompositor::XdgToplevel::parentToplevel * * This property holds the XdgToplevel parent of this XdgToplevel. */ /*! * \property QWaylandXdgToplevel::parentToplevel * * This property holds the XdgToplevel parent of this XdgToplevel. * */ QWaylandXdgToplevel *QWaylandXdgToplevel::parentToplevel() const { Q_D(const QWaylandXdgToplevel); return d->m_parentToplevel; } /*! * \qmlproperty string QtWaylandCompositor::XdgToplevel::title * * This property holds the title of the XdgToplevel. */ /*! * \property QWaylandXdgToplevel::title * * This property holds the title of the QWaylandXdgToplevel. */ QString QWaylandXdgToplevel::title() const { Q_D(const QWaylandXdgToplevel); return d->m_title; } /*! * \qmlproperty string QtWaylandCompositor::XdgToplevel::appId * * This property holds the app id of the XdgToplevel. */ /*! * \property QWaylandXdgToplevel::appId * * This property holds the app id of the QWaylandXdgToplevel. */ QString QWaylandXdgToplevel::appId() const { Q_D(const QWaylandXdgToplevel); return d->m_appId; } /*! * \qmlproperty size QtWaylandCompositor::XdgToplevel::maxSize * * This property holds the maximum size of the XdgToplevel as requested by the client. * * The compositor is free to ignore this value and request a larger size. */ /*! * \property QWaylandXdgToplevel::maxSize * * This property holds the maximum size of the QWaylandXdgToplevel. * * The compositor is free to ignore this value and request a larger size. */ QSize QWaylandXdgToplevel::maxSize() const { Q_D(const QWaylandXdgToplevel); return d->m_maxSize; } /*! * \qmlproperty size QtWaylandCompositor::XdgToplevel::minSize * * This property holds the minimum size of the XdgToplevel as requested by the client. * * The compositor is free to ignore this value and request a smaller size. */ /*! * \property QWaylandXdgToplevel::minSize * * This property holds the minimum size of the QWaylandXdgToplevel. * * The compositor is free to ignore this value and request a smaller size. */ QSize QWaylandXdgToplevel::minSize() const { Q_D(const QWaylandXdgToplevel); return d->m_minSize; } /*! * \property QWaylandXdgToplevel::states * * This property holds the last states the client acknowledged for this QWaylandToplevel. */ QVector QWaylandXdgToplevel::states() const { Q_D(const QWaylandXdgToplevel); return d->m_lastAckedConfigure.states; } /*! * \qmlproperty bool QtWaylandCompositor::XdgToplevel::maximized * * This property holds whether the client has acknowledged that it should be maximized. */ /*! * \property QWaylandXdgToplevel::maximized * * This property holds whether the client has acknowledged that it should be maximized. */ bool QWaylandXdgToplevel::maximized() const { Q_D(const QWaylandXdgToplevel); return d->m_lastAckedConfigure.states.contains(QWaylandXdgToplevel::State::MaximizedState); } /*! * \qmlproperty bool QtWaylandCompositor::XdgToplevel::fullscreen * * This property holds whether the client has acknowledged that it should be fullscreen. */ /*! * \property QWaylandXdgToplevel::fullscreen * * This property holds whether the client has acknowledged that it should be fullscreen. */ bool QWaylandXdgToplevel::fullscreen() const { Q_D(const QWaylandXdgToplevel); return d->m_lastAckedConfigure.states.contains(QWaylandXdgToplevel::State::FullscreenState); } /*! * \qmlproperty bool QtWaylandCompositor::XdgToplevel::resizing * * This property holds whether the client has acknowledged that it is being resized. */ /*! * \property QWaylandXdgToplevel::resizing * * This property holds whether the client has acknowledged that it is being resized. */ bool QWaylandXdgToplevel::resizing() const { Q_D(const QWaylandXdgToplevel); return d->m_lastAckedConfigure.states.contains(QWaylandXdgToplevel::State::ResizingState); } /*! * \qmlproperty bool QtWaylandCompositor::XdgToplevel::activated * * This property holds whether toplevel is drawing itself as having input focus. */ /*! * \property QWaylandXdgToplevel::activated * * This property holds whether toplevel is drawing itself as having input focus. */ bool QWaylandXdgToplevel::activated() const { Q_D(const QWaylandXdgToplevel); return d->m_lastAckedConfigure.states.contains(QWaylandXdgToplevel::State::ActivatedState); } /*! * \enum QWaylandXdgToplevel::DecorationMode * * This enum type is used to specify the window decoration mode for toplevel windows. * * \value ServerSideDecoration The compositor should draw window decorations. * \value ClientSideDecoration The client should draw window decorations. */ /*! * \qmlproperty enumeration QtWaylandCompositor::XdgToplevel::decorationMode * * This property holds the current window decoration mode for this toplevel. * * The possible values are: * \value XdgToplevel.ServerSideDecoration The compositor should draw window decorations. * \value XdgToplevel.ClientSideDecoration The client should draw window decorations. * * \sa XdgDecorationManagerV1 */ /*! * \property QWaylandXdgToplevel::decorationMode * * This property holds the current window decoration mode for this toplevel. * * \sa QWaylandXdgDecorationManagerV1 */ QWaylandXdgToplevel::DecorationMode QWaylandXdgToplevel::decorationMode() const { Q_D(const QWaylandXdgToplevel); return d->m_decoration ? d->m_decoration->configuredMode() : DecorationMode::ClientSideDecoration; } /*! * \qmlmethod size QtWaylandCompositor::XdgToplevel::sizeForResize(size size, point delta, uint edges) * * Convenience for computing the new size given the current \a size, a \a delta, and * the \a edges active in the drag. */ /*! * Convenience for computing the new size given the current \a size, a \a delta, and * the \a edges active in the drag. */ QSize QWaylandXdgToplevel::sizeForResize(const QSizeF &size, const QPointF &delta, Qt::Edges edges) const { qreal width = size.width(); qreal height = size.height(); if (edges & Qt::LeftEdge) width -= delta.x(); else if (edges & Qt::RightEdge) width += delta.x(); if (edges & Qt::TopEdge) height -= delta.y(); else if (edges & Qt::BottomEdge) height += delta.y(); QSize newSize = QSize(width, height) .expandedTo(minSize()) .expandedTo({1, 1}); // We don't want to send a size of (0,0) as that means that the client decides if (maxSize().isValid()) newSize = newSize.boundedTo(maxSize()); return newSize; } /*! * Sends a configure event to the client. Parameter \a size contains the pixel size * of the surface. A size of zero means the client is free to decide the size. * Known \a states are enumerated in QWaylandXdgToplevel::State. */ uint QWaylandXdgToplevel::sendConfigure(const QSize &size, const QVector &states) { if (!size.isValid()) { qWarning() << "Can't configure xdg_toplevel with an invalid size" << size; return 0; } Q_D(QWaylandXdgToplevel); auto statesBytes = QByteArray::fromRawData(reinterpret_cast(states.data()), states.size() * static_cast(sizeof(State))); uint32_t serial = d->m_xdgSurface->surface()->compositor()->nextSerial(); d->m_pendingConfigures.append(QWaylandXdgToplevelPrivate::ConfigureEvent{states, size, serial}); d->send_configure(size.width(), size.height(), statesBytes); QWaylandXdgSurfacePrivate::get(d->m_xdgSurface)->send_configure(serial); return serial; } /*! * \qmlmethod int QtWaylandCompositor::XdgToplevel::sendConfigure(size size, list states) * * Sends a configure event to the client. \a size contains the pixel size of the surface. * A size of zero means the client is free to decide the size. * Known \a states are enumerated in XdgToplevel::State. */ uint QWaylandXdgToplevel::sendConfigure(const QSize &size, const QVector &states) { QVector s; for (auto state : states) s << State(state); return sendConfigure(size, s); } /*! * \qmlmethod void QtWaylandCompositor::XdgToplevel::sendClose() * * Sends a close event to the client. The client may choose to ignore the event. */ /*! * Sends a close event to the client. The client may choose to ignore the event. */ void QWaylandXdgToplevel::sendClose() { Q_D(QWaylandXdgToplevel); d->send_close(); } /*! * \qmlmethod void QtWaylandCompositor::XdgToplevel::sendMaximized(size size) * * Convenience for sending a configure event with the maximized state set, and * fullscreen and resizing removed. The activated state is left in its current state. * * \a size is the new size of the window. */ /*! * Convenience for sending a configure event with the maximized state set, and * fullscreen and resizing removed. The activated state is left in its current state. * * \a size is the new size of the window. */ uint QWaylandXdgToplevel::sendMaximized(const QSize &size) { Q_D(QWaylandXdgToplevel); QWaylandXdgToplevelPrivate::ConfigureEvent conf = d->lastSentConfigure(); if (!conf.states.contains(QWaylandXdgToplevel::State::MaximizedState)) conf.states.append(QWaylandXdgToplevel::State::MaximizedState); conf.states.removeOne(QWaylandXdgToplevel::State::FullscreenState); conf.states.removeOne(QWaylandXdgToplevel::State::ResizingState); return sendConfigure(size, conf.states); } /*! * \qmlmethod void QtWaylandCompositor::XdgToplevel::sendUnmaximized(size size) * * Convenience for sending a configure event with the maximized, fullscreen and * resizing states removed, and fullscreen and resizing removed. The activated * state is left in its current state. * * \a size is the new size of the window. If \a size is zero, the client decides the size. */ /*! * Convenience for sending a configure event with the maximized, fullscreen and * resizing states removed, and fullscreen and resizing removed. The activated * state is left in its current state. * * \a size is the new size of the window. If \a size is zero, the client decides the size. */ uint QWaylandXdgToplevel::sendUnmaximized(const QSize &size) { Q_D(QWaylandXdgToplevel); QWaylandXdgToplevelPrivate::ConfigureEvent conf = d->lastSentConfigure(); conf.states.removeOne(QWaylandXdgToplevel::State::MaximizedState); conf.states.removeOne(QWaylandXdgToplevel::State::FullscreenState); conf.states.removeOne(QWaylandXdgToplevel::State::ResizingState); return sendConfigure(size, conf.states); } /*! * \qmlmethod void QtWaylandCompositor::XdgToplevel::sendFullscreen(size size) * * Convenience for sending a configure event with the fullscreen state set, and * maximized and resizing removed. The activated state is left in its current state. * * \sa sendUnmaximized * * \a size is the new size of the window. */ /*! * Convenience for sending a configure event with the fullscreen state set, and * maximized and resizing removed. The activated state is left in its current state. * * \sa sendUnmaximized * * \a size is the new size of the window. */ uint QWaylandXdgToplevel::sendFullscreen(const QSize &size) { Q_D(QWaylandXdgToplevel); QWaylandXdgToplevelPrivate::ConfigureEvent conf = d->lastSentConfigure(); if (!conf.states.contains(QWaylandXdgToplevel::State::FullscreenState)) conf.states.append(QWaylandXdgToplevel::State::FullscreenState); conf.states.removeOne(QWaylandXdgToplevel::State::MaximizedState); conf.states.removeOne(QWaylandXdgToplevel::State::ResizingState); return sendConfigure(size, conf.states); } /*! * \qmlmethod void QtWaylandCompositor::XdgToplevel::sendResizing(size maxSize) * * Convenience for sending a configure event with the resizing state set, and * maximized and fullscreen removed. The activated state is left in its current state. * * \a maxSize is the new size of the window. */ /*! * Convenience for sending a configure event with the resizing state set, and * maximized and fullscreen removed. The activated state is left in its current state. * * \a maxSize is the new size of the window. */ uint QWaylandXdgToplevel::sendResizing(const QSize &maxSize) { Q_D(QWaylandXdgToplevel); QWaylandXdgToplevelPrivate::ConfigureEvent conf = d->lastSentConfigure(); if (!conf.states.contains(QWaylandXdgToplevel::State::ResizingState)) conf.states.append(QWaylandXdgToplevel::State::ResizingState); conf.states.removeOne(QWaylandXdgToplevel::State::MaximizedState); conf.states.removeOne(QWaylandXdgToplevel::State::FullscreenState); return sendConfigure(maxSize, conf.states); } /*! * Returns the surface role for the QWaylandToplevel. */ QWaylandSurfaceRole *QWaylandXdgToplevel::role() { return &QWaylandXdgToplevelPrivate::s_role; } /*! * Returns the QWaylandXdgToplevel corresponding to the \a resource. */ QWaylandXdgToplevel *QWaylandXdgToplevel::fromResource(wl_resource *resource) { if (auto p = QtWayland::fromResource(resource)) return p->q_func(); return nullptr; } /*! * \qmlsignal QtWaylandCompositor::XdgShell::xdgSurfaceCreated(XdgSurface xdgSurface) * * This signal is emitted when the client has created a \c xdg_surface. * Note that \a xdgSurface is not mapped, i.e. according to the \c xdg-shell * protocol it should not be displayed, until it has received a role object. * * \sa toplevelCreated(), popupCreated() */ /*! * \fn void QWaylandXdgShell::xdgSurfaceCreated(QWaylandXdgSurface *xdgSurface) * * This signal is emitted when the client has created a \c xdg_surface. * Note that \a xdgSurface is not mapped, i.e. according to the \c xdg-shell * protocol it should not be displayed, until it has received a role object. * * \sa toplevelCreated(), popupCreated() */ /*! * \qmlsignal QtWaylandCompositor::XdgShell::toplevelCreated(XdgToplevel toplevel, XdgSurface xdgSurface) * * This signal is emitted when the client has created a \c xdg_toplevel. * A common use case is to let the handler of this signal instantiate a ShellSurfaceItem or * WaylandQuickItem for displaying \a toplevel in a QtQuick scene. * * \a xdgSurface is the XdgSurface \a toplevel is the role object for. */ /*! * \fn void QWaylandXdgShell::toplevelCreated(QWaylandXdgToplevel *toplevel, QWaylandXdgSurface *xdgSurface) * * This signal is emitted when the client has created a \c xdg_toplevel. * A common use case is to let the handler of this signal instantiate a QWaylandShellSurfaceItem or * QWaylandQuickItem for displaying \a toplevel in a QtQuick scene. * * \a xdgSurface is the XdgSurface \a toplevel is the role object for. */ /*! * \qmlsignal QtWaylandCompositor::XdgShell::popupCreated(XdgPopup popup, XdgSurface xdgSurface) * * This signal is emitted when the client has created a \c xdg_popup. * A common use case is to let the handler of this signal instantiate a ShellSurfaceItem or * WaylandQuickItem for displaying \a popup in a QtQuick scene. * * \a xdgSurface is the XdgSurface \a popup is the role object for. */ /*! * \fn void QWaylandXdgShell::popupCreated(QWaylandXdgPopup *popup, QWaylandXdgSurface *xdgSurface) * * This signal is emitted when the client has created a \c xdg_popup. * A common use case is to let the handler of this signal instantiate a QWaylandShellSurfaceItem or * QWaylandQuickItem for displaying \a popup in a QtQuick scene. * * \a xdgSurface is the XdgSurface \a popup is the role object for. */ /*! * \qmlsignal QtWaylandCompositor::XdgShell::pong(int serial) * * This signal is emitted when the client has responded to a ping event with serial, \a serial. * * \sa ping() */ /*! * \fn void QWaylandXdgShell::pong(uint serial) * * This signal is emitted when the client has responded to a ping event with serial, \a serial. * * \sa QWaylandXdgShell::ping() */ QList QWaylandXdgToplevel::statesAsInts() const { QList list; const auto s = states(); list.reserve(s.size()); for (auto state : s) { list << static_cast(state); } return list; } QWaylandSurfaceRole QWaylandXdgToplevelPrivate::s_role("xdg_toplevel"); QWaylandXdgToplevelPrivate::QWaylandXdgToplevelPrivate(QWaylandXdgSurface *xdgSurface, const QWaylandResource &resource) : m_xdgSurface(xdgSurface) { init(resource.resource()); } void QWaylandXdgToplevelPrivate::handleAckConfigure(uint serial) { Q_Q(QWaylandXdgToplevel); ConfigureEvent config; Q_FOREVER { if (m_pendingConfigures.empty()) { qWarning("Toplevel received an unexpected ack_configure!"); return; } // This won't work unless there always is a toplevel.configure for each xdgsurface.configure config = m_pendingConfigures.takeFirst(); if (config.serial == serial) break; } QVector changedStates; std::set_symmetric_difference( m_lastAckedConfigure.states.begin(), m_lastAckedConfigure.states.end(), config.states.begin(), config.states.end(), std::back_inserter(changedStates)); m_lastAckedConfigure = config; for (uint state : changedStates) { switch (state) { case state_maximized: emit q->maximizedChanged(); break; case state_fullscreen: emit q->fullscreenChanged(); break; case state_resizing: emit q->resizingChanged(); break; case state_activated: emit q->activatedChanged(); break; } } if (!changedStates.empty()) emit q->statesChanged(); } void QWaylandXdgToplevelPrivate::handleFocusLost() { Q_Q(QWaylandXdgToplevel); QWaylandXdgToplevelPrivate::ConfigureEvent current = lastSentConfigure(); current.states.removeOne(QWaylandXdgToplevel::State::ActivatedState); q->sendConfigure(current.size, current.states); } void QWaylandXdgToplevelPrivate::handleFocusReceived() { Q_Q(QWaylandXdgToplevel); QWaylandXdgToplevelPrivate::ConfigureEvent current = lastSentConfigure(); if (!current.states.contains(QWaylandXdgToplevel::State::ActivatedState)) { current.states.push_back(QWaylandXdgToplevel::State::ActivatedState); q->sendConfigure(current.size, current.states); } } Qt::Edges QWaylandXdgToplevelPrivate::convertToEdges(resize_edge edge) { return Qt::Edges(((edge & 0b1100) >> 1) | ((edge & 0b0010) << 2) | (edge & 0b0001)); } void QWaylandXdgToplevelPrivate::xdg_toplevel_destroy_resource(QtWaylandServer::xdg_toplevel::Resource *resource) { Q_UNUSED(resource); Q_Q(QWaylandXdgToplevel); delete q; } void QWaylandXdgToplevelPrivate::xdg_toplevel_destroy(QtWaylandServer::xdg_toplevel::Resource *resource) { if (Q_UNLIKELY(m_decoration)) qWarning() << "Client error: xdg_toplevel destroyed before its decoration object"; wl_resource_destroy(resource->handle); //TODO: Should the xdg surface be desroyed as well? Or is it allowed to recreate a new toplevel for it? } void QWaylandXdgToplevelPrivate::xdg_toplevel_set_parent(QtWaylandServer::xdg_toplevel::Resource *resource, wl_resource *parent) { Q_UNUSED(resource); QWaylandXdgToplevel *parentToplevel = QWaylandXdgToplevel::fromResource(parent); Q_Q(QWaylandXdgToplevel); if (m_parentToplevel != parentToplevel) { m_parentToplevel = parentToplevel; emit q->parentToplevelChanged(); } if (m_parentToplevel && m_xdgSurface->windowType() != Qt::WindowType::SubWindow) { // There's a parent now, which means the surface is transient QWaylandXdgSurfacePrivate::get(m_xdgSurface)->setWindowType(Qt::WindowType::SubWindow); } else if (!m_parentToplevel && m_xdgSurface->windowType() != Qt::WindowType::Window) { // When the surface has no parent it is toplevel QWaylandXdgSurfacePrivate::get(m_xdgSurface)->setWindowType(Qt::WindowType::Window); } } void QWaylandXdgToplevelPrivate::xdg_toplevel_set_title(QtWaylandServer::xdg_toplevel::Resource *resource, const QString &title) { Q_UNUSED(resource); if (title == m_title) return; Q_Q(QWaylandXdgToplevel); m_title = title; emit q->titleChanged(); } void QWaylandXdgToplevelPrivate::xdg_toplevel_set_app_id(QtWaylandServer::xdg_toplevel::Resource *resource, const QString &app_id) { Q_UNUSED(resource); if (app_id == m_appId) return; Q_Q(QWaylandXdgToplevel); m_appId = app_id; emit q->appIdChanged(); } void QWaylandXdgToplevelPrivate::xdg_toplevel_show_window_menu(QtWaylandServer::xdg_toplevel::Resource *resource, wl_resource *seatResource, uint32_t serial, int32_t x, int32_t y) { Q_UNUSED(resource); Q_UNUSED(serial); QPoint position(x, y); auto seat = QWaylandSeat::fromSeatResource(seatResource); Q_Q(QWaylandXdgToplevel); emit q->showWindowMenu(seat, position); } void QWaylandXdgToplevelPrivate::xdg_toplevel_move(Resource *resource, wl_resource *seatResource, uint32_t serial) { Q_UNUSED(resource); Q_UNUSED(serial); Q_Q(QWaylandXdgToplevel); QWaylandSeat *seat = QWaylandSeat::fromSeatResource(seatResource); emit q->startMove(seat); } void QWaylandXdgToplevelPrivate::xdg_toplevel_resize(QtWaylandServer::xdg_toplevel::Resource *resource, wl_resource *seatResource, uint32_t serial, uint32_t edges) { Q_UNUSED(resource); Q_UNUSED(serial); Q_Q(QWaylandXdgToplevel); QWaylandSeat *seat = QWaylandSeat::fromSeatResource(seatResource); emit q->startResize(seat, convertToEdges(resize_edge(edges))); } void QWaylandXdgToplevelPrivate::xdg_toplevel_set_max_size(QtWaylandServer::xdg_toplevel::Resource *resource, int32_t width, int32_t height) { Q_UNUSED(resource); QSize maxSize(width, height); if (width == 0 && height == 0) maxSize = QSize(); // Wayland size of zero means unspecified which best translates to invalid if (m_maxSize == maxSize) return; if (width < 0 || height < 0) { // The spec says raise a protocol error, but there's no matching error defined qWarning() << "Received a xdg_toplevel.set_max_size request with a negative size"; return; } if (m_minSize.isValid() && maxSize.isValid() && (maxSize.width() < m_minSize.width() || maxSize.height() < m_minSize.height())) { // The spec says raise a protocol error, but there's no matching error defined qWarning() << "Received a xdg_toplevel.set_max_size request with a size smaller than the minimium size"; return; } m_maxSize = maxSize; Q_Q(QWaylandXdgToplevel); emit q->maxSizeChanged(); } void QWaylandXdgToplevelPrivate::xdg_toplevel_set_min_size(QtWaylandServer::xdg_toplevel::Resource *resource, int32_t width, int32_t height) { Q_UNUSED(resource); QSize minSize(width, height); if (width == 0 && height == 0) minSize = QSize(); // Wayland size of zero means unspecified if (m_minSize == minSize) return; if (width < 0 || height < 0) { // The spec says raise a protocol error, but there's no matching error defined qWarning() << "Received a xdg_toplevel.set_min_size request with a negative size"; return; } if (m_maxSize.isValid() && minSize.isValid() && (minSize.width() > m_maxSize.width() || minSize.height() > m_maxSize.height())) { // The spec says raise a protocol error, but there's no matching error defined qWarning() << "Received a xdg_toplevel.set_min_size request with a size larger than the maximum size"; return; } m_minSize = minSize; Q_Q(QWaylandXdgToplevel); emit q->minSizeChanged(); } void QWaylandXdgToplevelPrivate::xdg_toplevel_set_maximized(QtWaylandServer::xdg_toplevel::Resource *resource) { Q_UNUSED(resource); Q_Q(QWaylandXdgToplevel); emit q->setMaximized(); } void QWaylandXdgToplevelPrivate::xdg_toplevel_unset_maximized(QtWaylandServer::xdg_toplevel::Resource *resource) { Q_UNUSED(resource); Q_Q(QWaylandXdgToplevel); emit q->unsetMaximized(); } void QWaylandXdgToplevelPrivate::xdg_toplevel_set_fullscreen(QtWaylandServer::xdg_toplevel::Resource *resource, wl_resource *output_res) { Q_UNUSED(resource); Q_Q(QWaylandXdgToplevel); QWaylandOutput *output = output_res ? QWaylandOutput::fromResource(output_res) : nullptr; emit q->setFullscreen(output); } void QWaylandXdgToplevelPrivate::xdg_toplevel_unset_fullscreen(QtWaylandServer::xdg_toplevel::Resource *resource) { Q_UNUSED(resource); Q_Q(QWaylandXdgToplevel); emit q->unsetFullscreen(); } void QWaylandXdgToplevelPrivate::xdg_toplevel_set_minimized(QtWaylandServer::xdg_toplevel::Resource *resource) { Q_UNUSED(resource); Q_Q(QWaylandXdgToplevel); emit q->setMinimized(); } /*! * \qmltype XdgPopup * \inqmlmodule QtWayland.Compositor * \since 5.12 * \brief XdgPopup represents the popup specific parts of and xdg surface. * * This type is part of the \l{XdgShell} extension and provides a way to extend * extend the functionality of an \l{XdgSurface} with features * specific to desktop-style menus for an xdg surface. * * It corresponds to the Wayland interface \c xdg_popup. */ /*! * \class QWaylandXdgPopup * \inmodule QtWaylandCompositor * \since 5.12 * \brief The QWaylandXdgPopup class represents the popup specific parts of an xdg surface. * * This class is part of the QWaylandXdgShell extension and provides a way to * extend the functionality of a QWaylandXdgSurface with features * specific to desktop-style menus for an xdg surface. * * It corresponds to the Wayland interface \c xdg_popup. */ /*! * Constructs a QWaylandXdgPopup. */ QWaylandXdgPopup::QWaylandXdgPopup(QWaylandXdgSurface *xdgSurface, QWaylandXdgSurface *parentXdgSurface, QWaylandXdgPositioner *positioner, QWaylandResource &resource) : QObject(*new QWaylandXdgPopupPrivate(xdgSurface, parentXdgSurface, positioner, resource)) { } /*! * \qmlproperty XdgSurface QtWaylandCompositor::XdgPopup::xdgSurface * * This property holds the XdgSurface associated with this XdgPopup. */ /*! * \property QWaylandXdgPopup::xdgSurface * * This property holds the QWaylandXdgSurface associated with this QWaylandXdgPopup. */ QWaylandXdgSurface *QWaylandXdgPopup::xdgSurface() const { Q_D(const QWaylandXdgPopup); return d->m_xdgSurface; } /*! * \qmlproperty XdgSurface QtWaylandCompositor::XdgPopup::parentXdgSurface * * This property holds the XdgSurface associated with the parent of this XdgPopup. */ /*! * \property QWaylandXdgPopup::parentXdgSurface * * This property holds the QWaylandXdgSurface associated with the parent of this * QWaylandXdgPopup. */ QWaylandXdgSurface *QWaylandXdgPopup::parentXdgSurface() const { Q_D(const QWaylandXdgPopup); return d->m_parentXdgSurface; } /*! * \qmlproperty rect QtWaylandCompositor::XdgPopup::configuredGeometry * * The window geometry the popup received in the configure event. Relative to the * upper left corner of the parent surface. */ /*! * \property QWaylandXdgPopup::configuredGeometry * * The window geometry the popup received in the configure event. Relative to the * upper left corner of the parent surface. */ QRect QWaylandXdgPopup::configuredGeometry() const { Q_D(const QWaylandXdgPopup); return d->m_geometry; } /*! * \qmlproperty rect QtWaylandCompositor::XdgPopup::anchorRect * * The anchor rectangle relative to the parent window geometry that the child * surface should be placed relative to. */ /*! * \property QWaylandXdgPopup::anchorRect * * Returns the anchor rectangle relative to the parent window geometry that the child * surface should be placed relative to. */ QRect QWaylandXdgPopup::anchorRect() const { Q_D(const QWaylandXdgPopup); return d->m_positionerData.anchorRect; } /*! * \qmlproperty enumeration QtWaylandCompositor::XdgPopup::anchorEdges * * This property holds the set of edges on the anchor rect that the child surface should be placed * relative to. If no edges are specified in a direction, the anchor point should be * centered between the edges. * * The possible values are: * \value Qt.TopEdge The top edge of the rectangle. * \value Qt.LeftEdge The left edge of the rectangle. * \value Qt.RightEdge The right edge of the rectangle. * \value Qt.BottomEdge The bottom edge of the rectangle. */ /*! * \property QWaylandXdgPopup::anchorEdges * * Returns the set of edges on the anchor rect that the child surface should be placed * relative to. If no edges are specified in a direction, the anchor point should be * centered between the edges. */ Qt::Edges QWaylandXdgPopup::anchorEdges() const { Q_D(const QWaylandXdgPopup); return d->m_positionerData.anchorEdges; } /*! * \qmlproperty rect QtWaylandCompositor::XdgPopup::gravityEdges * * Specifies in what direction the surface should be positioned, relative to the anchor * point. * * The possible values are: * \value Qt.TopEdge The surface should slide towards the top of the screen. * \value Qt.LeftEdge The surface should slide towards the left of the screen. * \value Qt.RightEdge The surface should slide towards the right of the screen. * \value Qt.BottomEdge The surface should slide towards the bottom of the screen. */ /*! * \property QWaylandXdgPopup::gravityEdges * * Specifies in what direction the surface should be positioned, relative to the anchor * point. */ Qt::Edges QWaylandXdgPopup::gravityEdges() const { Q_D(const QWaylandXdgPopup); return d->m_positionerData.gravityEdges; } /*! * \qmlproperty enumeration QtWaylandCompositor::XdgPopup::slideConstraints * * This property holds the orientations in which the child should slide to fit within the screen. * * Possible values: * \value Qt.Horizontal Horizontal * \value Qt.Vertical Vertical */ /*! * \property QWaylandXdgPopup::slideConstraints * * This property holds the orientations in which the child should slide to fit within the screen. */ Qt::Orientations QWaylandXdgPopup::slideConstraints() const { Q_D(const QWaylandXdgPopup); const uint flags = d->m_positionerData.constraintAdjustments; Qt::Orientations constraints = 0; if (flags & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X) constraints |= Qt::Horizontal; if (flags & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y) constraints |= Qt::Vertical; return constraints; } /*! * \qmlproperty enumeration QtWaylandCompositor::XdgPopup::flipConstraints * * This property holds the orientations in which the child should flip to fit within the screen. * * Possible values: * \value Qt.Horizontal Horizontal * \value Qt.Vertical Vertical */ /*! * \property QWaylandXdgPopup::flipConstraints * * This property holds the orientations in which the child should flip to fit within the screen. */ Qt::Orientations QWaylandXdgPopup::flipConstraints() const { Q_D(const QWaylandXdgPopup); const uint flags = d->m_positionerData.constraintAdjustments; Qt::Orientations constraints = 0; if (flags & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X) constraints |= Qt::Horizontal; if (flags & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y) constraints |= Qt::Vertical; return constraints; } /*! * \qmlproperty enumeration QtWaylandCompositor::XdgPopup::resizeConstraints * * This property holds the orientations in which the child should resize to fit within the screen. * * Possible values: * \value Qt.Horizontal Horizontal * \value Qt.Vertical Vertical */ /*! * \property QWaylandXdgPopup::resizeConstraints * * This property holds the orientations in which the child should resize to fit within the screen. */ Qt::Orientations QWaylandXdgPopup::resizeConstraints() const { Q_D(const QWaylandXdgPopup); const uint flags = d->m_positionerData.constraintAdjustments; Qt::Orientations constraints = 0; if (flags & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X) constraints |= Qt::Horizontal; if (flags & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y) constraints |= Qt::Vertical; return constraints; } /*! * \qmlproperty point QtWaylandCompositor::XdgPopup::offset * * The position relative to the position of the anchor on the anchor rectangle and * the anchor on the surface. */ /*! * \property QWaylandXdgPopup::offset * * Returns the surface position relative to the position of the anchor on the anchor * rectangle and the anchor on the surface. */ QPoint QWaylandXdgPopup::offset() const { Q_D(const QWaylandXdgPopup); return d->m_positionerData.offset; } /*! * \qmlproperty size QtWaylandCompositor::XdgPopup::positionerSize * * The size requested for the window geometry by the positioner object. */ /*! * \property QWaylandXdgPopup::positionerSize * * Returns the size requested for the window geometry by the positioner object. */ QSize QWaylandXdgPopup::positionerSize() const { Q_D(const QWaylandXdgPopup); return d->m_positionerData.size; } /*! * \qmlproperty point QtWaylandCompositor::XdgPopup::unconstrainedPosition * * The position of the surface relative to the parent window geometry if the surface * is not constrained. I.e. when not moved to fit inside the screen or similar. */ /*! * \property QWaylandXdgPopup::unconstrainedPosition * * The position of the surface relative to the parent window geometry if the surface * is not constrained. I.e. when not moved to fit inside the screen or similar. */ QPoint QWaylandXdgPopup::unconstrainedPosition() const { Q_D(const QWaylandXdgPopup); return d->m_positionerData.unconstrainedPosition(); } /*! * \qmlmethod int QtWaylandCompositor::XdgPopup::sendConfigure(rect geometry) * * Sends a configure event to the client. \a geometry contains the window geometry * relative to the upper left corner of the window geometry of the parent surface. * * This implicitly sends a configure event to the corresponding XdgSurface as well. */ /*! * Sends a configure event to the client. \a geometry contains the window geometry * relative to the upper left corner of the window geometry of the parent surface. * * This implicitly sends a configure event to the corresponding QWaylandXdgSurface * as well. */ uint QWaylandXdgPopup::sendConfigure(const QRect &geometry) { Q_D(QWaylandXdgPopup); return d->sendConfigure(geometry); } /*! * \qmlmethod void QtWaylandCompositor::XdgPopup::sendPopupDone() * \since 5.14 * * Dismiss the popup. According to the \c xdg-shell protocol this should make the * client destroy the popup. */ /*! * \since 5.14 * * Dismiss the popup. According to the \c xdg-shell protocol this should make the * client destroy the popup. */ void QWaylandXdgPopup::sendPopupDone() { Q_D(QWaylandXdgPopup); d->send_popup_done(); } /*! * Returns the surface role for the QWaylandPopup. */ QWaylandSurfaceRole *QWaylandXdgPopup::role() { return &QWaylandXdgPopupPrivate::s_role; } QWaylandXdgPopupPrivate::QWaylandXdgPopupPrivate(QWaylandXdgSurface *xdgSurface, QWaylandXdgSurface *parentXdgSurface, QWaylandXdgPositioner *positioner, const QWaylandResource &resource) : m_xdgSurface(xdgSurface) , m_parentXdgSurface(parentXdgSurface) , m_positionerData(positioner->m_data) { Q_ASSERT(m_positionerData.isComplete()); init(resource.resource()); QWaylandXdgSurfacePrivate::get(m_xdgSurface)->setWindowType(Qt::WindowType::Popup); //TODO: Need an API for sending a different initial configure sendConfigure(QRect(m_positionerData.unconstrainedPosition(), m_positionerData.size)); } void QWaylandXdgPopupPrivate::handleAckConfigure(uint serial) { Q_Q(QWaylandXdgPopup); ConfigureEvent config; Q_FOREVER { if (m_pendingConfigures.empty()) { qWarning("Popup received an unexpected ack_configure!"); return; } // This won't work unless there always is a popup.configure for each xdgsurface.configure config = m_pendingConfigures.takeFirst(); if (config.serial == serial) break; } if (m_geometry == config.geometry) return; m_geometry = config.geometry; emit q->configuredGeometryChanged(); } uint QWaylandXdgPopupPrivate::sendConfigure(const QRect &geometry) { uint32_t serial = m_xdgSurface->surface()->compositor()->nextSerial(); m_pendingConfigures.append(QWaylandXdgPopupPrivate::ConfigureEvent{geometry, serial}); send_configure(geometry.x(), geometry.y(), geometry.width(), geometry.height()); QWaylandXdgSurfacePrivate::get(m_xdgSurface)->send_configure(serial); return serial; } void QWaylandXdgPopupPrivate::xdg_popup_destroy(QtWaylandServer::xdg_popup::Resource *resource) { Q_UNUSED(resource); qWarning() << Q_FUNC_INFO << "Not implemented"; //TODO } void QWaylandXdgPopupPrivate::xdg_popup_grab(QtWaylandServer::xdg_popup::Resource *resource, wl_resource *seat, uint32_t serial) { Q_UNUSED(resource); Q_UNUSED(serial); Q_UNUSED(seat); qWarning() << Q_FUNC_INFO << "Not implemented"; //TODO //switch keyboard focus //eventually send configure with activated. } QWaylandSurfaceRole QWaylandXdgPopupPrivate::s_role("xdg_popup"); QWaylandXdgPositionerData::QWaylandXdgPositionerData() : offset(0, 0) {} bool QWaylandXdgPositionerData::isComplete() const { return size.width() > 0 && size.height() > 0 && anchorRect.size().width() > 0 && anchorRect.size().height() > 0; } QPoint QWaylandXdgPositionerData::anchorPoint() const { int yPosition = 0; if (anchorEdges & Qt::TopEdge) yPosition = anchorRect.top(); else if (anchorEdges & Qt::BottomEdge) yPosition = anchorRect.bottom() + 1; else yPosition = anchorRect.top() + anchorRect.height() / 2; int xPosition = 0; if (anchorEdges & Qt::LeftEdge) xPosition = anchorRect.left(); else if (anchorEdges & Qt::RightEdge) xPosition = anchorRect.right() + 1; else xPosition = anchorRect.left() + anchorRect.width() / 2; return QPoint(xPosition, yPosition); } QPoint QWaylandXdgPositionerData::unconstrainedPosition() const { int gravityOffsetY = 0; if (gravityEdges & Qt::TopEdge) gravityOffsetY = -size.height(); else if (!(gravityEdges & Qt::BottomEdge)) gravityOffsetY = -size.height() / 2; int gravityOffsetX = 0; if (gravityEdges & Qt::LeftEdge) gravityOffsetX = -size.width(); else if (!(gravityEdges & Qt::RightEdge)) gravityOffsetX = -size.width() / 2; QPoint gravityOffset(gravityOffsetX, gravityOffsetY); return anchorPoint() + gravityOffset + offset; } QWaylandXdgPositioner::QWaylandXdgPositioner(const QWaylandResource &resource) { init(resource.resource()); } void QWaylandXdgPositioner::xdg_positioner_destroy_resource(QtWaylandServer::xdg_positioner::Resource *resource) { Q_UNUSED(resource); delete this; } void QWaylandXdgPositioner::xdg_positioner_destroy(QtWaylandServer::xdg_positioner::Resource *resource) { wl_resource_destroy(resource->handle); } void QWaylandXdgPositioner::xdg_positioner_set_size(QtWaylandServer::xdg_positioner::Resource *resource, int32_t width, int32_t height) { if (width <= 0 || height <= 0) { wl_resource_post_error(resource->handle, XDG_POSITIONER_ERROR_INVALID_INPUT, "xdg_positioner.set_size requested with non-positive dimensions"); return; } QSize size(width, height); m_data.size = size; } void QWaylandXdgPositioner::xdg_positioner_set_anchor_rect(QtWaylandServer::xdg_positioner::Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { if (width <= 0 || height <= 0) { wl_resource_post_error(resource->handle, XDG_POSITIONER_ERROR_INVALID_INPUT, "xdg_positioner.set_anchor_rect requested with non-positive dimensions"); return; } QRect anchorRect(x, y, width, height); m_data.anchorRect = anchorRect; } void QWaylandXdgPositioner::xdg_positioner_set_anchor(QtWaylandServer::xdg_positioner::Resource *resource, uint32_t anchor) { Qt::Edges anchorEdges = convertToEdges(xdg_positioner::anchor(anchor)); if ((anchorEdges & Qt::BottomEdge && anchorEdges & Qt::TopEdge) || (anchorEdges & Qt::LeftEdge && anchorEdges & Qt::RightEdge)) { wl_resource_post_error(resource->handle, XDG_POSITIONER_ERROR_INVALID_INPUT, "xdg_positioner.set_anchor requested with parallel edges"); return; } m_data.anchorEdges = anchorEdges; } void QWaylandXdgPositioner::xdg_positioner_set_gravity(QtWaylandServer::xdg_positioner::Resource *resource, uint32_t gravity) { Qt::Edges gravityEdges = convertToEdges(xdg_positioner::gravity(gravity)); if ((gravityEdges & Qt::BottomEdge && gravityEdges & Qt::TopEdge) || (gravityEdges & Qt::LeftEdge && gravityEdges & Qt::RightEdge)) { wl_resource_post_error(resource->handle, XDG_POSITIONER_ERROR_INVALID_INPUT, "xdg_positioner.set_gravity requested with parallel edges"); return; } m_data.gravityEdges = gravityEdges; } void QWaylandXdgPositioner::xdg_positioner_set_constraint_adjustment(QtWaylandServer::xdg_positioner::Resource *resource, uint32_t constraint_adjustment) { Q_UNUSED(resource); m_data.constraintAdjustments = constraint_adjustment; } void QWaylandXdgPositioner::xdg_positioner_set_offset(QtWaylandServer::xdg_positioner::Resource *resource, int32_t x, int32_t y) { Q_UNUSED(resource); m_data.offset = QPoint(x, y); } QWaylandXdgPositioner *QWaylandXdgPositioner::fromResource(wl_resource *resource) { return QtWayland::fromResource(resource); } Qt::Edges QWaylandXdgPositioner::convertToEdges(anchor anchor) { switch (anchor) { case anchor_none: return Qt::Edges(); case anchor_top: return Qt::TopEdge; case anchor_bottom: return Qt::BottomEdge; case anchor_left: return Qt::LeftEdge; case anchor_right: return Qt::RightEdge; case anchor_top_left: return Qt::TopEdge | Qt::LeftEdge; case anchor_bottom_left: return Qt::BottomEdge | Qt::LeftEdge; case anchor_top_right: return Qt::TopEdge | Qt::RightEdge; case anchor_bottom_right: return Qt::BottomEdge | Qt::RightEdge; default: qWarning() << "Unknown Wayland xdg edge" << anchor; return Qt::Edges(); } } Qt::Edges QWaylandXdgPositioner::convertToEdges(QWaylandXdgPositioner::gravity gravity) { return convertToEdges(anchor(gravity)); } QT_END_NAMESPACE