File indexing completed on 2024-04-28 05:30:23

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.h"
0012 #include "wayland/output.h"
0013 #include "wayland_server.h"
0014 #include "workspace.h"
0015 
0016 #include <QTimer>
0017 
0018 namespace KWin
0019 {
0020 
0021 static const Qt::Edges AnchorHorizontal = Qt::LeftEdge | Qt::RightEdge;
0022 static const Qt::Edges AnchorVertical = Qt::TopEdge | Qt::BottomEdge;
0023 
0024 LayerShellV1Integration::LayerShellV1Integration(QObject *parent)
0025     : WaylandShellIntegration(parent)
0026 {
0027     LayerShellV1Interface *shell = new LayerShellV1Interface(waylandServer()->display(), this);
0028     connect(shell, &LayerShellV1Interface::surfaceCreated,
0029             this, &LayerShellV1Integration::createWindow);
0030 
0031     m_rearrangeTimer = new QTimer(this);
0032     m_rearrangeTimer->setSingleShot(true);
0033     connect(m_rearrangeTimer, &QTimer::timeout, this, &LayerShellV1Integration::rearrange);
0034 }
0035 
0036 void LayerShellV1Integration::createWindow(LayerSurfaceV1Interface *shellSurface)
0037 {
0038     Output *output = shellSurface->output() ? shellSurface->output()->handle() : workspace()->activeOutput();
0039     if (!output) {
0040         qCWarning(KWIN_CORE) << "Could not find any suitable output for a layer surface";
0041         shellSurface->sendClosed();
0042         return;
0043     }
0044 
0045     Q_EMIT windowCreated(new LayerShellV1Window(shellSurface, output, this));
0046 }
0047 
0048 void LayerShellV1Integration::recreateWindow(LayerSurfaceV1Interface *shellSurface)
0049 {
0050     destroyWindow(shellSurface);
0051     createWindow(shellSurface);
0052 }
0053 
0054 void LayerShellV1Integration::destroyWindow(LayerSurfaceV1Interface *shellSurface)
0055 {
0056     const QList<Window *> windows = waylandServer()->windows();
0057     for (Window *window : windows) {
0058         LayerShellV1Window *layerShellWindow = qobject_cast<LayerShellV1Window *>(window);
0059         if (layerShellWindow && layerShellWindow->shellSurface() == shellSurface) {
0060             layerShellWindow->destroyWindow();
0061             break;
0062         }
0063     }
0064 }
0065 
0066 static void adjustWorkArea(const LayerSurfaceV1Interface *shellSurface, QRect *workArea)
0067 {
0068     switch (shellSurface->exclusiveEdge()) {
0069     case Qt::LeftEdge:
0070         workArea->adjust(shellSurface->leftMargin() + shellSurface->exclusiveZone(), 0, 0, 0);
0071         break;
0072     case Qt::RightEdge:
0073         workArea->adjust(0, 0, -shellSurface->rightMargin() - shellSurface->exclusiveZone(), 0);
0074         break;
0075     case Qt::TopEdge:
0076         workArea->adjust(0, shellSurface->topMargin() + shellSurface->exclusiveZone(), 0, 0);
0077         break;
0078     case Qt::BottomEdge:
0079         workArea->adjust(0, 0, 0, -shellSurface->bottomMargin() - shellSurface->exclusiveZone());
0080         break;
0081     }
0082 }
0083 
0084 static void rearrangeLayer(const QList<LayerShellV1Window *> &windows, QRect *workArea,
0085                            LayerSurfaceV1Interface::Layer layer, bool exclusive)
0086 {
0087     for (LayerShellV1Window *window : windows) {
0088         LayerSurfaceV1Interface *shellSurface = window->shellSurface();
0089 
0090         if (shellSurface->layer() != layer) {
0091             continue;
0092         }
0093         if (exclusive != (shellSurface->exclusiveZone() > 0)) {
0094             continue;
0095         }
0096 
0097         QRect bounds;
0098         if (shellSurface->exclusiveZone() == -1) {
0099             bounds = window->desiredOutput()->geometry();
0100         } else {
0101             bounds = *workArea;
0102         }
0103 
0104         QRect geometry(QPoint(0, 0), shellSurface->desiredSize());
0105 
0106         if ((shellSurface->anchor() & AnchorHorizontal) && geometry.width() == 0) {
0107             geometry.setLeft(bounds.left());
0108             geometry.setWidth(bounds.width());
0109         } else if (shellSurface->anchor() & Qt::LeftEdge) {
0110             geometry.moveLeft(bounds.left());
0111         } else if (shellSurface->anchor() & Qt::RightEdge) {
0112             geometry.moveRight(bounds.right());
0113         } else {
0114             geometry.moveLeft(bounds.left() + (bounds.width() - geometry.width()) / 2);
0115         }
0116 
0117         if ((shellSurface->anchor() & AnchorVertical) && geometry.height() == 0) {
0118             geometry.setTop(bounds.top());
0119             geometry.setHeight(bounds.height());
0120         } else if (shellSurface->anchor() & Qt::TopEdge) {
0121             geometry.moveTop(bounds.top());
0122         } else if (shellSurface->anchor() & Qt::BottomEdge) {
0123             geometry.moveBottom(bounds.bottom());
0124         } else {
0125             geometry.moveTop(bounds.top() + (bounds.height() - geometry.height()) / 2);
0126         }
0127 
0128         if ((shellSurface->anchor() & AnchorHorizontal) == AnchorHorizontal) {
0129             geometry.adjust(shellSurface->leftMargin(), 0, -shellSurface->rightMargin(), 0);
0130         } else if (shellSurface->anchor() & Qt::LeftEdge) {
0131             geometry.translate(shellSurface->leftMargin(), 0);
0132         } else if (shellSurface->anchor() & Qt::RightEdge) {
0133             geometry.translate(-shellSurface->rightMargin(), 0);
0134         }
0135 
0136         if ((shellSurface->anchor() & AnchorVertical) == AnchorVertical) {
0137             geometry.adjust(0, shellSurface->topMargin(), 0, -shellSurface->bottomMargin());
0138         } else if (shellSurface->anchor() & Qt::TopEdge) {
0139             geometry.translate(0, shellSurface->topMargin());
0140         } else if (shellSurface->anchor() & Qt::BottomEdge) {
0141             geometry.translate(0, -shellSurface->bottomMargin());
0142         }
0143 
0144         // Move the window's bottom if its virtual keyboard is overlapping it
0145         if (shellSurface->exclusiveZone() >= 0 && !window->virtualKeyboardGeometry().isEmpty() && geometry.bottom() > window->virtualKeyboardGeometry().top()) {
0146             geometry.setBottom(window->virtualKeyboardGeometry().top());
0147         }
0148 
0149         window->updateLayer();
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
0219 
0220 #include "moc_layershellv1integration.cpp"