| 1 | // Copyright (C) 2016 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include "qwaylandwlshellsurface_p.h" |
| 5 | |
| 6 | #include <QtWaylandClient/private/qwaylanddisplay_p.h> |
| 7 | #include <QtWaylandClient/private/qwaylandwindow_p.h> |
| 8 | #include <QtWaylandClient/private/qwaylandinputdevice_p.h> |
| 9 | #include <QtWaylandClient/private/qwaylandabstractdecoration_p.h> |
| 10 | #include <QtWaylandClient/private/qwaylandscreen_p.h> |
| 11 | #include <QtWaylandClient/private/qwaylandextendedsurface_p.h> |
| 12 | |
| 13 | #include <QtCore/QDebug> |
| 14 | |
| 15 | QT_BEGIN_NAMESPACE |
| 16 | |
| 17 | namespace QtWaylandClient { |
| 18 | |
| 19 | QWaylandWlShellSurface::QWaylandWlShellSurface(struct ::wl_shell_surface *shell_surface, QWaylandWindow *window) |
| 20 | : QWaylandShellSurface(window) |
| 21 | , QtWayland::wl_shell_surface(shell_surface) |
| 22 | , m_window(window) |
| 23 | { |
| 24 | if (window->display()->windowExtension()) |
| 25 | m_extendedWindow = new QWaylandExtendedSurface(window); |
| 26 | |
| 27 | Qt::WindowType type = window->window()->type(); |
| 28 | auto *transientParent = window->transientParent(); |
| 29 | if (type == Qt::Popup && transientParent && transientParent->wlSurface()) |
| 30 | setPopup(parent: transientParent, device: m_window->display()->lastInputDevice(), serial: m_window->display()->lastInputSerial()); |
| 31 | else if (transientParent && transientParent->wlSurface()) |
| 32 | updateTransientParent(parent: transientParent->window()); |
| 33 | else |
| 34 | setTopLevel(); |
| 35 | } |
| 36 | |
| 37 | QWaylandWlShellSurface::~QWaylandWlShellSurface() |
| 38 | { |
| 39 | wl_shell_surface_destroy(object()); |
| 40 | delete m_extendedWindow; |
| 41 | } |
| 42 | |
| 43 | bool QWaylandWlShellSurface::resize(QWaylandInputDevice *inputDevice, Qt::Edges edges) |
| 44 | { |
| 45 | enum resize resizeEdges = convertToResizeEdges(edges); |
| 46 | resize(inputDevice: inputDevice->wl_seat(), edges: inputDevice->serial(), resizeEdges); |
| 47 | return true; |
| 48 | } |
| 49 | |
| 50 | bool QWaylandWlShellSurface::move(QWaylandInputDevice *inputDevice) |
| 51 | { |
| 52 | move(inputDevice->wl_seat(), |
| 53 | inputDevice->serial()); |
| 54 | return true; |
| 55 | } |
| 56 | |
| 57 | void QWaylandWlShellSurface::setTitle(const QString & title) |
| 58 | { |
| 59 | return QtWayland::wl_shell_surface::set_title(title); |
| 60 | } |
| 61 | |
| 62 | void QWaylandWlShellSurface::setAppId(const QString & appId) |
| 63 | { |
| 64 | return QtWayland::wl_shell_surface::set_class(appId); |
| 65 | } |
| 66 | |
| 67 | void QWaylandWlShellSurface::raise() |
| 68 | { |
| 69 | if (m_extendedWindow) |
| 70 | m_extendedWindow->raise(); |
| 71 | } |
| 72 | |
| 73 | void QWaylandWlShellSurface::lower() |
| 74 | { |
| 75 | if (m_extendedWindow) |
| 76 | m_extendedWindow->lower(); |
| 77 | } |
| 78 | |
| 79 | void QWaylandWlShellSurface::setContentOrientationMask(Qt::ScreenOrientations orientation) |
| 80 | { |
| 81 | if (m_extendedWindow) |
| 82 | m_extendedWindow->setContentOrientationMask(orientation); |
| 83 | } |
| 84 | |
| 85 | void QWaylandWlShellSurface::setWindowFlags(Qt::WindowFlags flags) |
| 86 | { |
| 87 | if (m_extendedWindow) |
| 88 | m_extendedWindow->setWindowFlags(flags); |
| 89 | } |
| 90 | |
| 91 | void QWaylandWlShellSurface::sendProperty(const QString &name, const QVariant &value) |
| 92 | { |
| 93 | if (m_extendedWindow) |
| 94 | m_extendedWindow->updateGenericProperty(name, value); |
| 95 | } |
| 96 | |
| 97 | void QWaylandWlShellSurface::applyConfigure() |
| 98 | { |
| 99 | if ((m_pending.states & (Qt::WindowMaximized|Qt::WindowFullScreen)) |
| 100 | && !(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen))) { |
| 101 | m_normalSize = m_window->windowFrameGeometry().size(); |
| 102 | } |
| 103 | |
| 104 | if (m_pending.states != m_applied.states) |
| 105 | m_window->handleWindowStatesChanged(m_pending.states); |
| 106 | |
| 107 | if (!m_pending.size.isEmpty()) { |
| 108 | int x = 0; |
| 109 | int y = 0; |
| 110 | if (m_pending.edges & resize_left) |
| 111 | x = m_applied.size.width() - m_pending.size.width(); |
| 112 | if (m_pending.edges & resize_top) |
| 113 | y = m_applied.size.height() - m_pending.size.height(); |
| 114 | QPoint offset(x, y); |
| 115 | m_window->resizeFromApplyConfigure(m_pending.size, offset); |
| 116 | } else if (m_pending.size.isValid() && !m_normalSize.isEmpty()) { |
| 117 | m_window->resizeFromApplyConfigure(sizeWithMargins: m_normalSize); |
| 118 | } |
| 119 | |
| 120 | m_applied = m_pending; |
| 121 | } |
| 122 | |
| 123 | bool QWaylandWlShellSurface::wantsDecorations() const |
| 124 | { |
| 125 | return !(m_pending.states & Qt::WindowFullScreen); |
| 126 | } |
| 127 | |
| 128 | void QWaylandWlShellSurface::requestWindowStates(Qt::WindowStates states) |
| 129 | { |
| 130 | // On wl-shell the client is in charge of states, so diff from the pending state |
| 131 | Qt::WindowStates changedStates = m_pending.states ^ states; |
| 132 | Qt::WindowStates addedStates = changedStates & states; |
| 133 | |
| 134 | if (addedStates & Qt::WindowMinimized) |
| 135 | qCWarning(lcQpaWayland) << "Minimizing is not supported on wl-shell. Consider using xdg-shell instead." ; |
| 136 | |
| 137 | if (addedStates & Qt::WindowMaximized) { |
| 138 | set_maximized(nullptr); |
| 139 | m_window->applyConfigureWhenPossible(); |
| 140 | } |
| 141 | |
| 142 | if (addedStates & Qt::WindowFullScreen) { |
| 143 | set_fullscreen(WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0, nullptr); |
| 144 | m_window->applyConfigureWhenPossible(); |
| 145 | } |
| 146 | |
| 147 | bool isNormal = !(states & Qt::WindowMaximized) && !(states & Qt::WindowFullScreen); |
| 148 | if (isNormal && (changedStates & (Qt::WindowMaximized | Qt::WindowFullScreen))) { |
| 149 | setTopLevel(); // set normal window |
| 150 | // There's usually no configure event after this, so just clear the rest of the pending |
| 151 | // configure here and queue the applyConfigure call |
| 152 | m_pending.size = {0, 0}; |
| 153 | m_pending.edges = resize_none; |
| 154 | m_window->applyConfigureWhenPossible(); |
| 155 | } |
| 156 | |
| 157 | m_pending.states = states & ~Qt::WindowMinimized; |
| 158 | } |
| 159 | |
| 160 | enum QWaylandWlShellSurface::resize QWaylandWlShellSurface::convertToResizeEdges(Qt::Edges edges) |
| 161 | { |
| 162 | return static_cast<enum resize>( |
| 163 | ((edges & Qt::TopEdge) ? resize_top : 0) |
| 164 | | ((edges & Qt::BottomEdge) ? resize_bottom : 0) |
| 165 | | ((edges & Qt::LeftEdge) ? resize_left : 0) |
| 166 | | ((edges & Qt::RightEdge) ? resize_right : 0)); |
| 167 | } |
| 168 | |
| 169 | void QWaylandWlShellSurface::setTopLevel() |
| 170 | { |
| 171 | set_toplevel(); |
| 172 | } |
| 173 | |
| 174 | static inline bool testShowWithoutActivating(const QWindow *window) |
| 175 | { |
| 176 | // QWidget-attribute Qt::WA_ShowWithoutActivating. |
| 177 | const QVariant showWithoutActivating = window->property(name: "_q_showWithoutActivating" ); |
| 178 | return showWithoutActivating.isValid() && showWithoutActivating.toBool(); |
| 179 | } |
| 180 | |
| 181 | void QWaylandWlShellSurface::updateTransientParent(QWindow *parent) |
| 182 | { |
| 183 | QWaylandWindow *parent_wayland_window = static_cast<QWaylandWindow *>(parent->handle()); |
| 184 | if (!parent_wayland_window) |
| 185 | return; |
| 186 | |
| 187 | // set_transient expects a position relative to the parent |
| 188 | QPoint transientPos = m_window->geometry().topLeft(); // this is absolute |
| 189 | transientPos -= parent->geometry().topLeft(); |
| 190 | if (parent_wayland_window->decoration()) { |
| 191 | transientPos.setX(transientPos.x() + parent_wayland_window->decoration()->margins().left()); |
| 192 | transientPos.setY(transientPos.y() + parent_wayland_window->decoration()->margins().top()); |
| 193 | } |
| 194 | |
| 195 | uint32_t flags = 0; |
| 196 | Qt::WindowFlags wf = m_window->window()->flags(); |
| 197 | if (wf.testFlag(Qt::ToolTip) |
| 198 | || wf.testFlag(Qt::WindowTransparentForInput) |
| 199 | || testShowWithoutActivating(m_window->window())) |
| 200 | flags |= WL_SHELL_SURFACE_TRANSIENT_INACTIVE; |
| 201 | |
| 202 | auto *parentSurface = parent_wayland_window->wlSurface(); |
| 203 | Q_ASSERT(parentSurface); |
| 204 | set_transient(parentSurface, transientPos.x(), transientPos.y(), flags); |
| 205 | } |
| 206 | |
| 207 | void QWaylandWlShellSurface::setPopup(QWaylandWindow *parent, QWaylandInputDevice *device, uint serial) |
| 208 | { |
| 209 | QWaylandWindow *parent_wayland_window = parent; |
| 210 | if (!parent_wayland_window) { |
| 211 | qCWarning(lcQpaWayland) << "setPopup called without a parent window" ; |
| 212 | return; |
| 213 | } |
| 214 | if (!device) { |
| 215 | qCWarning(lcQpaWayland) << "setPopup called without an input device" ; |
| 216 | return; |
| 217 | } |
| 218 | |
| 219 | // set_popup expects a position relative to the parent |
| 220 | QPoint transientPos = m_window->geometry().topLeft(); // this is absolute |
| 221 | transientPos -= parent_wayland_window->geometry().topLeft(); |
| 222 | if (parent_wayland_window->decoration()) { |
| 223 | transientPos.setX(transientPos.x() + parent_wayland_window->decoration()->margins().left()); |
| 224 | transientPos.setY(transientPos.y() + parent_wayland_window->decoration()->margins().top()); |
| 225 | } |
| 226 | |
| 227 | auto *parentSurface = parent_wayland_window->wlSurface(); |
| 228 | Q_ASSERT(parentSurface); |
| 229 | uint flags = 0; |
| 230 | set_popup(device->wl_seat(), serial, parentSurface, transientPos.x(), transientPos.y(), flags); |
| 231 | } |
| 232 | |
| 233 | void QWaylandWlShellSurface::shell_surface_ping(uint32_t serial) |
| 234 | { |
| 235 | pong(serial); |
| 236 | } |
| 237 | |
| 238 | void QWaylandWlShellSurface::shell_surface_configure(uint32_t edges, int32_t width, int32_t height) |
| 239 | { |
| 240 | m_pending.size = QSize(width, height); |
| 241 | m_pending.edges = static_cast<enum resize>(edges); |
| 242 | if (m_pending.edges && !m_pending.size.isEmpty()) |
| 243 | m_normalSize = m_pending.size; |
| 244 | m_window->applyConfigureWhenPossible(); |
| 245 | } |
| 246 | |
| 247 | void QWaylandWlShellSurface::shell_surface_popup_done() |
| 248 | { |
| 249 | QCoreApplication::postEvent(receiver: m_window->window(), event: new QCloseEvent()); |
| 250 | } |
| 251 | |
| 252 | } |
| 253 | |
| 254 | QT_END_NAMESPACE |
| 255 | |
| 256 | #include "moc_qwaylandwlshellsurface_p.cpp" |
| 257 | |