File indexing completed on 2024-12-08 13:21:55

0001 /*
0002     SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "layershellv1integration.h"
0008 #include "core/output.h"
0009 #include "layershellv1window.h"
0010 #include "wayland/display.h"
0011 #include "wayland/layershell_v1_interface.h"
0012 #include "wayland/output_interface.h"
0013 #include "wayland_server.h"
0014 #include "workspace.h"
0015 
0016 #include <QTimer>
0017 
0018 using namespace KWaylandServer;
0019 
0020 namespace KWin
0021 {
0022 
0023 static const Qt::Edges AnchorHorizontal = Qt::LeftEdge | Qt::RightEdge;
0024 static const Qt::Edges AnchorVertical = Qt::TopEdge | Qt::BottomEdge;
0025 
0026 LayerShellV1Integration::LayerShellV1Integration(QObject *parent)
0027     : WaylandShellIntegration(parent)
0028 {
0029     LayerShellV1Interface *shell = new LayerShellV1Interface(waylandServer()->display(), this);
0030     connect(shell, &KWaylandServer::LayerShellV1Interface::surfaceCreated,
0031             this, &LayerShellV1Integration::createWindow);
0032 
0033     m_rearrangeTimer = new QTimer(this);
0034     m_rearrangeTimer->setSingleShot(true);
0035     connect(m_rearrangeTimer, &QTimer::timeout, this, &LayerShellV1Integration::rearrange);
0036 }
0037 
0038 void LayerShellV1Integration::createWindow(LayerSurfaceV1Interface *shellSurface)
0039 {
0040     Output *output = shellSurface->output() ? shellSurface->output()->handle() : workspace()->activeOutput();
0041     if (!output) {
0042         qCWarning(KWIN_CORE) << "Could not find any suitable output for a layer surface";
0043         shellSurface->sendClosed();
0044         return;
0045     }
0046 
0047     Q_EMIT windowCreated(new LayerShellV1Window(shellSurface, output, this));
0048 }
0049 
0050 void LayerShellV1Integration::recreateWindow(LayerSurfaceV1Interface *shellSurface)
0051 {
0052     destroyWindow(shellSurface);
0053     createWindow(shellSurface);
0054 }
0055 
0056 void LayerShellV1Integration::destroyWindow(LayerSurfaceV1Interface *shellSurface)
0057 {
0058     const QList<Window *> windows = waylandServer()->windows();
0059     for (Window *window : windows) {
0060         LayerShellV1Window *layerShellWindow = qobject_cast<LayerShellV1Window *>(window);
0061         if (layerShellWindow && layerShellWindow->shellSurface() == shellSurface) {
0062             layerShellWindow->destroyWindow();
0063             break;
0064         }
0065     }
0066 }
0067 
0068 static void adjustWorkArea(const LayerSurfaceV1Interface *shellSurface, QRect *workArea)
0069 {
0070     switch (shellSurface->exclusiveEdge()) {
0071     case Qt::LeftEdge:
0072         workArea->adjust(shellSurface->leftMargin() + shellSurface->exclusiveZone(), 0, 0, 0);
0073         break;
0074     case Qt::RightEdge:
0075         workArea->adjust(0, 0, -shellSurface->rightMargin() - shellSurface->exclusiveZone(), 0);
0076         break;
0077     case Qt::TopEdge:
0078         workArea->adjust(0, shellSurface->topMargin() + shellSurface->exclusiveZone(), 0, 0);
0079         break;
0080     case Qt::BottomEdge:
0081         workArea->adjust(0, 0, 0, -shellSurface->bottomMargin() - shellSurface->exclusiveZone());
0082         break;
0083     }
0084 }
0085 
0086 static void rearrangeLayer(const QList<LayerShellV1Window *> &windows, QRect *workArea,
0087                            LayerSurfaceV1Interface::Layer layer, bool exclusive)
0088 {
0089     for (LayerShellV1Window *window : windows) {
0090         LayerSurfaceV1Interface *shellSurface = window->shellSurface();
0091 
0092         if (shellSurface->layer() != layer) {
0093             continue;
0094         }
0095         if (exclusive != (shellSurface->exclusiveZone() > 0)) {
0096             continue;
0097         }
0098 
0099         QRect bounds;
0100         if (shellSurface->exclusiveZone() == -1) {
0101             bounds = window->desiredOutput()->geometry();
0102         } else {
0103             bounds = *workArea;
0104         }
0105 
0106         QRect geometry(QPoint(0, 0), shellSurface->desiredSize());
0107 
0108         if ((shellSurface->anchor() & AnchorHorizontal) && geometry.width() == 0) {
0109             geometry.setLeft(bounds.left());
0110             geometry.setWidth(bounds.width());
0111         } else if (shellSurface->anchor() & Qt::LeftEdge) {
0112             geometry.moveLeft(bounds.left());
0113         } else if (shellSurface->anchor() & Qt::RightEdge) {
0114             geometry.moveRight(bounds.right());
0115         } else {
0116             geometry.moveLeft(bounds.left() + (bounds.width() - geometry.width()) / 2);
0117         }
0118 
0119         if ((shellSurface->anchor() & AnchorVertical) && geometry.height() == 0) {
0120             geometry.setTop(bounds.top());
0121             geometry.setHeight(bounds.height());
0122         } else if (shellSurface->anchor() & Qt::TopEdge) {
0123             geometry.moveTop(bounds.top());
0124         } else if (shellSurface->anchor() & Qt::BottomEdge) {
0125             geometry.moveBottom(bounds.bottom());
0126         } else {
0127             geometry.moveTop(bounds.top() + (bounds.height() - geometry.height()) / 2);
0128         }
0129 
0130         if ((shellSurface->anchor() & AnchorHorizontal) == AnchorHorizontal) {
0131             geometry.adjust(shellSurface->leftMargin(), 0, -shellSurface->rightMargin(), 0);
0132         } else if (shellSurface->anchor() & Qt::LeftEdge) {
0133             geometry.translate(shellSurface->leftMargin(), 0);
0134         } else if (shellSurface->anchor() & Qt::RightEdge) {
0135             geometry.translate(-shellSurface->rightMargin(), 0);
0136         }
0137 
0138         if ((shellSurface->anchor() & AnchorVertical) == AnchorVertical) {
0139             geometry.adjust(0, shellSurface->topMargin(), 0, -shellSurface->bottomMargin());
0140         } else if (shellSurface->anchor() & Qt::TopEdge) {
0141             geometry.translate(0, shellSurface->topMargin());
0142         } else if (shellSurface->anchor() & Qt::BottomEdge) {
0143             geometry.translate(0, -shellSurface->bottomMargin());
0144         }
0145 
0146         // Move the window's bottom if its virtual keyboard is overlapping it
0147         if (shellSurface->exclusiveZone() >= 0 && !window->virtualKeyboardGeometry().isEmpty() && geometry.bottom() > window->virtualKeyboardGeometry().top()) {
0148             geometry.setBottom(window->virtualKeyboardGeometry().top());
0149         }
0150 
0151         if (geometry.isValid()) {
0152             window->moveResize(geometry);
0153         } else {
0154             qCWarning(KWIN_CORE) << "Closing a layer shell window due to invalid geometry";
0155             window->closeWindow();
0156             continue;
0157         }
0158 
0159         if (exclusive && shellSurface->exclusiveZone() > 0) {
0160             adjustWorkArea(shellSurface, workArea);
0161         }
0162     }
0163 }
0164 
0165 static QList<LayerShellV1Window *> windowsForOutput(Output *output)
0166 {
0167     QList<LayerShellV1Window *> result;
0168     const QList<Window *> windows = waylandServer()->windows();
0169     for (Window *window : windows) {
0170         LayerShellV1Window *layerShellWindow = qobject_cast<LayerShellV1Window *>(window);
0171         if (!layerShellWindow || layerShellWindow->desiredOutput() != output) {
0172             continue;
0173         }
0174         if (layerShellWindow->shellSurface()->isCommitted()) {
0175             result.append(layerShellWindow);
0176         }
0177     }
0178     return result;
0179 }
0180 
0181 static void rearrangeOutput(Output *output)
0182 {
0183     const QList<LayerShellV1Window *> windows = windowsForOutput(output);
0184     if (!windows.isEmpty()) {
0185         QRect workArea = output->geometry();
0186 
0187         rearrangeLayer(windows, &workArea, LayerSurfaceV1Interface::OverlayLayer, true);
0188         rearrangeLayer(windows, &workArea, LayerSurfaceV1Interface::TopLayer, true);
0189         rearrangeLayer(windows, &workArea, LayerSurfaceV1Interface::BottomLayer, true);
0190         rearrangeLayer(windows, &workArea, LayerSurfaceV1Interface::BackgroundLayer, true);
0191 
0192         rearrangeLayer(windows, &workArea, LayerSurfaceV1Interface::OverlayLayer, false);
0193         rearrangeLayer(windows, &workArea, LayerSurfaceV1Interface::TopLayer, false);
0194         rearrangeLayer(windows, &workArea, LayerSurfaceV1Interface::BottomLayer, false);
0195         rearrangeLayer(windows, &workArea, LayerSurfaceV1Interface::BackgroundLayer, false);
0196     }
0197 }
0198 
0199 void LayerShellV1Integration::rearrange()
0200 {
0201     m_rearrangeTimer->stop();
0202 
0203     const QList<Output *> outputs = workspace()->outputs();
0204     for (Output *output : outputs) {
0205         rearrangeOutput(output);
0206     }
0207 
0208     if (workspace()) {
0209         workspace()->updateClientArea();
0210     }
0211 }
0212 
0213 void LayerShellV1Integration::scheduleRearrange()
0214 {
0215     m_rearrangeTimer->start();
0216 }
0217 
0218 } // namespace KWin