/*
    KWin - the KDE window manager
    This file is part of the KDE project.

    SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
    SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
    SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "xdgshellv6window.h"
#include "core/output.h"
#if KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
#include "composite.h"
#include "decorations/decorationbridge.h"
#include "deleted.h"
#include "placement.h"
#include "pointer_input.h"
#include "screenedge.h"
#include "touch_input.h"
#include "utils/subsurfacemonitor.h"
#include "virtualdesktops.h"
#include "wayland/appmenu_interface.h"
#include "wayland/output_interface.h"
#include "wayland/plasmashell_interface.h"
#include "wayland/seat_interface.h"
#include "wayland/server_decoration_interface.h"
#include "wayland/server_decoration_palette_interface.h"
#include "wayland/surface_interface.h"
#include "wayland/xdgdecoration_v1_interface.h"
#include "wayland/ddeshell_interface.h"
#include "wayland_server.h"
#include "workspace.h"
#include "useractions.h"
#include "wayland/dderestrict_interface.h"
#include "scene/surfaceitem.h"

#include <KDecoration2/DecoratedClient>
#include <KDecoration2/Decoration>

using namespace KWaylandServer;

namespace KWin
{

XdgSurfaceV6Window::XdgSurfaceV6Window(XdgSurfaceV6Interface *shellSurface)
    : WaylandWindow(shellSurface->surface())
    , m_shellSurface(shellSurface)
    , m_configureTimer(new QTimer(this))
{
    setupPlasmaShellIntegration();
    connect(shellSurface, &XdgSurfaceV6Interface::configureAcknowledged,
            this, &XdgSurfaceV6Window::handleConfigureAcknowledged);
    connect(shellSurface, &XdgSurfaceV6Interface::resetOccurred,
            this, &XdgSurfaceV6Window::destroyWindow);
    connect(shellSurface->surface(), &SurfaceInterface::committed,
            this, &XdgSurfaceV6Window::handleCommit);
#if 0 // TODO: Refactor kwin core in order to uncomment this code.
    connect(shellSurface->surface(), &SurfaceInterface::mapped,
            this, &XdgSurfaceV6Window::setReadyForPainting);
#endif
    connect(shellSurface, &XdgSurfaceV6Interface::aboutToBeDestroyed,
            this, &XdgSurfaceV6Window::destroyWindow);
    connect(shellSurface->surface(), &SurfaceInterface::aboutToBeDestroyed,
            this, &XdgSurfaceV6Window::destroyWindow);

    // The effective window geometry is determined by two things: (a) the rectangle that bounds
    // the main surface and all of its sub-surfaces, (b) the client-specified window geometry, if
    // any. If the client hasn't provided the window geometry, we fallback to the bounding sub-
    // surface rectangle. If the client has provided the window geometry, we intersect it with
    // the bounding rectangle and that will be the effective window geometry. It's worth to point
    // out that geometry updates do not occur that frequently, so we don't need to recompute the
    // bounding geometry every time the client commits the surface.

    SubSurfaceMonitor *treeMonitor = new SubSurfaceMonitor(surface(), this);

    connect(treeMonitor, &SubSurfaceMonitor::subSurfaceAdded,
            this, &XdgSurfaceV6Window::setHaveNextWindowGeometry);
    connect(treeMonitor, &SubSurfaceMonitor::subSurfaceRemoved,
            this, &XdgSurfaceV6Window::setHaveNextWindowGeometry);
    connect(treeMonitor, &SubSurfaceMonitor::subSurfaceMoved,
            this, &XdgSurfaceV6Window::setHaveNextWindowGeometry);
    connect(treeMonitor, &SubSurfaceMonitor::subSurfaceResized,
            this, &XdgSurfaceV6Window::setHaveNextWindowGeometry);
    connect(shellSurface, &XdgSurfaceV6Interface::windowGeometryChanged,
            this, &XdgSurfaceV6Window::setHaveNextWindowGeometry);
    connect(surface(), &SurfaceInterface::sizeChanged,
            this, &XdgSurfaceV6Window::setHaveNextWindowGeometry);

    // Configure events are not sent immediately, but rather scheduled to be sent when the event
    // loop is about to be idle. By doing this, we can avoid sending configure events that do
    // nothing, and implementation-wise, it's simpler.

    m_configureTimer->setSingleShot(true);
    connect(m_configureTimer, &QTimer::timeout, this, &XdgSurfaceV6Window::sendConfigure);
}

XdgSurfaceV6Window::~XdgSurfaceV6Window()
{
    qDeleteAll(m_configureEvents);
}

NET::WindowType XdgSurfaceV6Window::windowType(bool direct, int supported_types) const
{
    return m_windowType;
}

QRectF XdgSurfaceV6Window::inputGeometry() const
{
    return isDecorated() ? Window::inputGeometry() : bufferGeometry();
}

QMatrix4x4 XdgSurfaceV6Window::inputTransformation() const
{
    QMatrix4x4 transformation;
    transformation.translate(-bufferGeometry().x(), -bufferGeometry().y());
    return transformation;
}

XdgSurfaceV6Configure *XdgSurfaceV6Window::lastAcknowledgedConfigure() const
{
    return m_lastAcknowledgedConfigure.get();
}

void XdgSurfaceV6Window::scheduleConfigure()
{
    if (!isZombie()) {
        m_configureTimer->start();
    }
}

void XdgSurfaceV6Window::sendConfigure()
{
    XdgSurfaceV6Configure *configureEvent = sendRoleConfigure();

    // The configure event inherits configure flags from the previous event.
    if (!m_configureEvents.isEmpty()) {
        const XdgSurfaceV6Configure *previousEvent = m_configureEvents.constLast();
        configureEvent->flags = previousEvent->flags;
    }

    configureEvent->gravity = m_nextGravity;
    configureEvent->flags |= m_configureFlags;
    m_configureFlags = {};

    m_configureEvents.append(configureEvent);
}

void XdgSurfaceV6Window::handleConfigureAcknowledged(quint32 serial)
{
    m_lastAcknowledgedConfigureSerial = serial;

    // The xdg protocol does not have a minimized state, and this state needs to be supplemented through the dde shell protocol
    if (m_ddeShellSurface && isMinimized()) {
        m_ddeShellSurface->setMinimized(false);
        m_ddeShellSurface->setMinimized(isMinimized());
    }
}

void XdgSurfaceV6Window::leaveInteractiveMoveResize()
{
    Window::leaveInteractiveMoveResize();
    if (m_plasmaShellSurface) {
        m_plasmaShellSurface->resetPositionSet();
    }
}

void XdgSurfaceV6Window::handleCommit()
{
    if (!surface()->buffer()) {
        return;
    }

    if (m_lastAcknowledgedConfigureSerial.has_value()) {
        const quint32 serial = m_lastAcknowledgedConfigureSerial.value();
        while (!m_configureEvents.isEmpty()) {
            if (serial < m_configureEvents.constFirst()->serial) {
                break;
            }
            m_lastAcknowledgedConfigure.reset(m_configureEvents.takeFirst());
        }
    }

    handleRolePrecommit();
    if (haveNextWindowGeometry()) {
        handleNextWindowGeometry();
        resetHaveNextWindowGeometry();
    }

    handleRoleCommit();
    m_lastAcknowledgedConfigure.reset();
    m_lastAcknowledgedConfigureSerial.reset();

    setReadyForPainting();
    updateDepth();
}

void XdgSurfaceV6Window::handleRolePrecommit()
{
}

void XdgSurfaceV6Window::handleRoleCommit()
{
}

void XdgSurfaceV6Window::maybeUpdateMoveResizeGeometry(const QRectF &rect)
{
    // We are about to send a configure event, ignore the committed window geometry.
    if (m_configureTimer->isActive()) {
        return;
    }

    // If there are unacknowledged configure events that change the geometry, don't sync
    // the move resize geometry in order to avoid rolling back to old state. When the last
    // configure event is acknowledged, the move resize geometry will be synchronized.
    for (int i = m_configureEvents.count() - 1; i >= 0; --i) {
        if (m_configureEvents[i]->flags & XdgSurfaceV6Configure::ConfigurePosition) {
            return;
        }
    }

    setMoveResizeGeometry(rect);
}

static QRectF gravitateGeometry(const QRectF &rect, const QRectF &bounds, Gravity gravity)
{
    QRectF geometry = rect;

    switch (gravity) {
    case Gravity::TopLeft:
        geometry.moveRight(bounds.right());
        geometry.moveBottom(bounds.bottom());
        break;
    case Gravity::Top:
    case Gravity::TopRight:
        geometry.moveLeft(bounds.left());
        geometry.moveBottom(bounds.bottom());
        break;
    case Gravity::Right:
    case Gravity::BottomRight:
    case Gravity::Bottom:
    case Gravity::None:
        geometry.moveLeft(bounds.left());
        geometry.moveTop(bounds.top());
        break;
    case Gravity::BottomLeft:
    case Gravity::Left:
        geometry.moveRight(bounds.right());
        geometry.moveTop(bounds.top());
        break;
    }

    return geometry;
}

void XdgSurfaceV6Window::handleNextWindowGeometry()
{
    const QRectF boundingGeometry = surface()->boundingRect();

    // The effective window geometry is defined as the intersection of the window geometry
    // and the rectangle that bounds the main surface and all of its sub-surfaces. If the
    // client hasn't specified the window geometry, we must fallback to the bounding geometry.
    // Note that the xdg-shell spec is not clear about when exactly we have to clamp the
    // window geometry.

    m_windowGeometry = m_shellSurface->windowGeometry();
    if (m_windowGeometry.isValid()) {
        m_windowGeometry &= boundingGeometry;
    } else {
        m_windowGeometry = boundingGeometry;
    }

    if (m_windowGeometry.isEmpty()) {
        qCWarning(KWIN_CORE) << "Committed empty window geometry, dealing with a buggy client!";
    }

    QRectF frameGeometry(pos(), clientSizeToFrameSize(m_windowGeometry.size()));
    if (const XdgSurfaceV6Configure *configureEvent = lastAcknowledgedConfigure()) {
        if (configureEvent->flags & XdgSurfaceV6Configure::ConfigurePosition) {
            frameGeometry = gravitateGeometry(frameGeometry, configureEvent->bounds, configureEvent->gravity);
        }
    }

    if (!isInteractiveMoveResize()) {
        // Both the compositor and the client can change the window geometry. If the client
        // sets a new window geometry, the compositor's move-resize geometry will be invalid.
        maybeUpdateMoveResizeGeometry(frameGeometry);
    }

    updateGeometry(frameGeometry);
}

bool XdgSurfaceV6Window::haveNextWindowGeometry() const
{
    return m_haveNextWindowGeometry || m_lastAcknowledgedConfigure;
}

void XdgSurfaceV6Window::setHaveNextWindowGeometry()
{
    m_haveNextWindowGeometry = true;
}

void XdgSurfaceV6Window::resetHaveNextWindowGeometry()
{
    m_haveNextWindowGeometry = false;
}

void XdgSurfaceV6Window::moveResizeInternal(const QRectF &rect, MoveResizeMode mode)
{
    if (areGeometryUpdatesBlocked()) {
        setPendingMoveResizeMode(mode);
        return;
    }

    Q_EMIT frameGeometryAboutToChange(this);

    if (mode != MoveResizeMode::Move) {
        const QSizeF requestedClientSize = frameSizeToClientSize(rect.size());
        if (requestedClientSize == clientSize()) {
            updateGeometry(rect);
        } else {
            m_configureFlags |= XdgSurfaceV6Configure::ConfigurePosition;
            scheduleConfigure();
        }
    } else {
        // If the window is moved, cancel any queued window position updates.
        for (XdgSurfaceV6Configure *configureEvent : std::as_const(m_configureEvents)) {
            configureEvent->flags.setFlag(XdgSurfaceV6Configure::ConfigurePosition, false);
        }
        m_configureFlags.setFlag(XdgSurfaceV6Configure::ConfigurePosition, false);
        updateGeometry(QRectF(rect.topLeft(), size()));
    }
}

QRectF XdgSurfaceV6Window::frameRectToBufferRect(const QRectF &rect) const
{
    const qreal left = rect.left() + borderLeft() - m_windowGeometry.left();
    const qreal top = rect.top() + borderTop() - m_windowGeometry.top();
    return QRectF(QPoint(left, top), surface()->size());
}

void XdgSurfaceV6Window::destroyWindow()
{
    FUNC_DEBUG_LOG(Q_FUNC_INFO, window());
    markAsZombie();
    if (isInteractiveMoveResize()) {
        leaveInteractiveMoveResize();
        Q_EMIT clientFinishUserMovedResized(this);
    }
    m_configureTimer->stop();
    cleanTabBox();

    auto dde_restrict = waylandServer()->ddeRestrict();
    if (dde_restrict) {
         auto protectedWindowIdLists = dde_restrict->protectedWindowIdLists();
        if (protectedWindowIdLists.contains(window())) {
            broadcastDbusDestroySignal(window());
        }
    }

    Deleted *deleted = Deleted::create(this);
    Q_EMIT windowClosed(this, deleted);
    StackingUpdatesBlocker blocker(workspace());
    workspace()->rulebook()->discardUsed(this, true);
    setDecoration(nullptr);
    cleanGrouping();
    waylandServer()->removeWindow(this);
    deleted->unrefWindow();
    delete this;
}

void XdgSurfaceV6Window::updateClientArea()
{
    if (hasStrut()) {
        if (isMaximized() && m_plasmaShellSurface) {
            m_plasmaShellSurface->resetPositionSet();
        }
        workspace()->updateClientArea();
    }
}

void XdgSurfaceV6Window::updateShowOnScreenEdge()
{
    if (!workspace()->screenEdges()) {
        return;
    }
    if (!readyForPainting() || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) {
        workspace()->screenEdges()->reserve(this, ElectricNone);
        return;
    }
    const PlasmaShellSurfaceInterface::PanelBehavior panelBehavior = m_plasmaShellSurface->panelBehavior();
    if ((panelBehavior == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && isHidden()) || panelBehavior == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) {
        // Screen edge API requires an edge, thus we need to figure out which edge the window borders.
        const QRect clientGeometry = frameGeometry().toRect(); // converted here to match output checks
        Qt::Edges edges;

        const auto outputs = workspace()->outputs();
        for (const Output *output : outputs) {
            const QRect screenGeometry = output->geometry();
            if (screenGeometry.left() == clientGeometry.left()) {
                edges |= Qt::LeftEdge;
            }
            if (screenGeometry.right() == clientGeometry.right()) {
                edges |= Qt::RightEdge;
            }
            if (screenGeometry.top() == clientGeometry.top()) {
                edges |= Qt::TopEdge;
            }
            if (screenGeometry.bottom() == clientGeometry.bottom()) {
                edges |= Qt::BottomEdge;
            }
        }

        // A panel might border multiple screen edges. E.g. a horizontal panel at the bottom will
        // also border the left and right edge. Let's remove such cases.
        if (edges & Qt::LeftEdge && edges & Qt::RightEdge) {
            edges = edges & (~(Qt::LeftEdge | Qt::RightEdge));
        }
        if (edges & Qt::TopEdge && edges & Qt::BottomEdge) {
            edges = edges & (~(Qt::TopEdge | Qt::BottomEdge));
        }

        // It's still possible that a panel borders two edges, e.g. bottom and left
        // in that case the one which is sharing more with the edge wins.
        auto check = [clientGeometry](Qt::Edges edges, Qt::Edge horizontal, Qt::Edge vertical) {
            if (edges & horizontal && edges & vertical) {
                if (clientGeometry.width() >= clientGeometry.height()) {
                    return edges & ~horizontal;
                } else {
                    return edges & ~vertical;
                }
            }
            return edges;
        };
        edges = check(edges, Qt::LeftEdge, Qt::TopEdge);
        edges = check(edges, Qt::LeftEdge, Qt::BottomEdge);
        edges = check(edges, Qt::RightEdge, Qt::TopEdge);
        edges = check(edges, Qt::RightEdge, Qt::BottomEdge);

        ElectricBorder border = ElectricNone;
        if (edges & Qt::LeftEdge) {
            border = ElectricLeft;
        }
        if (edges & Qt::RightEdge) {
            border = ElectricRight;
        }
        if (edges & Qt::TopEdge) {
            border = ElectricTop;
        }
        if (edges & Qt::BottomEdge) {
            border = ElectricBottom;
        }
        workspace()->screenEdges()->reserve(this, border);
    } else {
        workspace()->screenEdges()->reserve(this, ElectricNone);
    }
}

// if we want to do some special action only for Role::StandAlone surface
// we first call isStandAlone
bool XdgSurfaceV6Window::isStandAlone() const
{
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::StandAlone;
    }
    return false;
}

bool XdgSurfaceV6Window::isOverride() const
{
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Override;
    }
    return false;
}

bool XdgSurfaceV6Window::isActiveFullScreenRole() const
{
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ActiveFullScreen;
    }
    return false;
}

Window *XdgSurfaceV6Window::findModal(bool allow_itself)
{
    for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) {
        if (Window* ret = (*it)->findModal(true)) {
            return ret;
        }
    }

    if (isModal() && allow_itself) {
        return this;
    }
    return nullptr;
}

/**
 * \todo This whole plasma shell surface thing doesn't seem right. It turns xdg-toplevel into
 * something completely different! Perhaps plasmashell surfaces need to be implemented via a
 * proprietary protocol that doesn't piggyback on existing shell surface protocols. It'll lead
 * to cleaner code and will be technically correct, but I'm not sure whether this is do-able.
 */
void XdgSurfaceV6Window::installPlasmaShellSurface(PlasmaShellSurfaceInterface *shellSurface)
{
    m_plasmaShellSurface = shellSurface;

    auto updatePosition = [this, shellSurface] {
        move(shellSurface->position());
    };
    auto moveUnderCursor = [this] {
        // Wait for the first commit
        auto connection = new QMetaObject::Connection;
        *connection = connect(this, &Window::windowShown,  [this, connection] () {
            disconnect(*connection);
            if (input()->hasPointer()) {
                move(input()->globalPointer());
                keepInArea(workspace()->clientArea(PlacementArea, this));
            }
        });
    };
    auto updateRole = [this, shellSurface] {
        NET::WindowType type = NET::Unknown;
        switch (shellSurface->role()) {
        case PlasmaShellSurfaceInterface::Role::Desktop:
            type = NET::Desktop;
            break;
        case PlasmaShellSurfaceInterface::Role::Panel:
            type = NET::Dock;
            break;
        case PlasmaShellSurfaceInterface::Role::OnScreenDisplay:
            type = NET::OnScreenDisplay;
            break;
        case PlasmaShellSurfaceInterface::Role::Notification:
            type = NET::Notification;
            break;
        case PlasmaShellSurfaceInterface::Role::ToolTip:
            type = NET::Tooltip;
            break;
        case PlasmaShellSurfaceInterface::Role::CriticalNotification:
            type = NET::CriticalNotification;
            break;
        case PlasmaShellSurfaceInterface::Role::AppletPopup:
            type = NET::AppletPopup;
            break;
        case PlasmaShellSurfaceInterface::Role::Override:
            type = NET::Override;
            break;
        case PlasmaShellSurfaceInterface::Role::Normal:
        case PlasmaShellSurfaceInterface::Role::StandAlone:
        default:
            type = NET::Normal;
            break;
        }
        if (m_windowType == type) {
            return;
        }
        m_windowType = type;
        switch (m_windowType) {
        case NET::Desktop:
        case NET::Dock:
        case NET::OnScreenDisplay:
        case NET::Notification:
        case NET::CriticalNotification:
        case NET::Tooltip:
        case NET::AppletPopup:
            setOnAllDesktops(true);
#if KWIN_BUILD_ACTIVITIES
            setOnAllActivities(true);
#endif
            break;
        default:
            break;
        }
        workspace()->updateClientArea();
    };
    connect(shellSurface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition);
    connect(shellSurface, &PlasmaShellSurfaceInterface::openUnderCursorRequested, this, moveUnderCursor);
    connect(shellSurface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole);
    connect(shellSurface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [this] {
        updateShowOnScreenEdge();
        workspace()->updateClientArea();
    });
    connect(shellSurface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, [this] {
        if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) {
            hideClient();
            m_plasmaShellSurface->hideAutoHidingPanel();
        }
        updateShowOnScreenEdge();
    });
    connect(shellSurface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, [this] {
        showClient();
        workspace()->screenEdges()->reserve(this, ElectricNone);
        m_plasmaShellSurface->showAutoHidingPanel();
    });
    connect(shellSurface, &PlasmaShellSurfaceInterface::panelTakesFocusChanged, this, [this] {
        if (m_plasmaShellSurface->panelTakesFocus()) {
            workspace()->activateWindow(this);
        }
    });
    if (shellSurface->isPositionSet()) {
        updatePosition();
    }
    if (shellSurface->wantsOpenUnderCursor()) {
        moveUnderCursor();
    }
    updateRole();
    updateShowOnScreenEdge();
    connect(this, &XdgSurfaceV6Window::frameGeometryChanged,
            this, &XdgSurfaceV6Window::updateShowOnScreenEdge);
    connect(this, &XdgSurfaceV6Window::windowShown,
            this, &XdgSurfaceV6Window::updateShowOnScreenEdge);

    setSkipTaskbar(shellSurface->skipTaskbar());
    connect(shellSurface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] {
        setSkipTaskbar(m_plasmaShellSurface->skipTaskbar());
    });

    setSkipSwitcher(shellSurface->skipSwitcher());
    connect(shellSurface, &PlasmaShellSurfaceInterface::skipSwitcherChanged, this, [this] {
        setSkipSwitcher(m_plasmaShellSurface->skipSwitcher());
    });
}

void XdgSurfaceV6Window::installDDEShellSurface(DDEShellSurfaceInterface *shellSurface)
{
    m_ddeShellSurface = shellSurface;

    connect(this, &XdgSurfaceV6Window::frameGeometryChanged, this,
        [this] {
            // porting: to confirm frameGeometry?
            if (isDecorated()) {
                QRectF clientGeom(frameGeometry().topLeft() + clientPos(), clientSize());
                m_ddeShellSurface->sendGeometry(clientGeom.toAlignedRect());
            } else {
                m_ddeShellSurface->sendGeometry(frameGeometry().toAlignedRect());
            }
        }
    );

    connect(this, &Window::activeChanged, this,
            [this] {
                m_ddeShellSurface->setActive(isActive());
                Q_EMIT workspace()->windowStateChanged();
                }
            );
    connect(this, &Window::fullScreenChanged, this,
            [this] {
                m_ddeShellSurface->setFullscreen(isFullScreen());
                Q_EMIT workspace()->windowStateChanged();
                }
            );
    connect(this, &Window::keepAboveChanged, m_ddeShellSurface, &DDEShellSurfaceInterface::setKeepAbove);
    connect(this, &Window::keepBelowChanged, m_ddeShellSurface, &DDEShellSurfaceInterface::setKeepBelow);
    connect(this, &Window::minimizedChanged, this,
            [this] {
                m_ddeShellSurface->setMinimized(isMinimized());
                Q_EMIT workspace()->windowStateChanged();
                }
            );
    connect(this, static_cast<void (Window::*)(Window *, MaximizeMode, bool)>(&Window::clientMaximizedStateChanged), this,
        [this] (KWin::Window *c, MaximizeMode mode, bool animated) {
            Q_UNUSED(c);
            m_ddeShellSurface->setMaximized(mode == KWin::MaximizeFull);
            Q_EMIT workspace()->windowStateChanged();
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::activationRequested, this,
        [this] {
            workspace()->activateWindow(this);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::minimizedRequested, this,
        [this] (bool set) {
            if (set) {
                minimize();
            } else {
                unminimize();
            }
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::maximizedRequested, this,
        [this] (bool set) {
            maximize(set ? MaximizeFull : MaximizeRestore);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::keepAboveRequested, this,
        [this] (bool set) {
            setKeepAbove(set);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::keepBelowRequested, this,
        [this] (bool set) {
            setKeepBelow(set);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::activeRequested, this,
        [this] (bool set) {
            if (set) {
                workspace()->activateWindow(this, true);
            }
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::onAllDesktopsRequested, this,
        [this] (bool set) {
            if (set) {
                setOnAllDesktops(set);
            }
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::minimizeableRequested, this,
        [this] (bool set) {
            setMinimizeable(set);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::maximizeableRequested, this,
        [this] (bool set) {
            setMaximizeable(set);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::resizableRequested, this,
        [this] (bool set) {
            setResizable(set);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::closeableRequested, this,
        [this] (bool set) {
            setCloseable(set);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::acceptFocusRequested, this,
        [this] (bool set) {
            setAcceptFocus(set);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::modalityRequested, this,
        [this] (bool set) {
            setModal(set);
        }
    );
    // porting to do: send the value to dde-kwin?
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::noTitleBarPropertyRequested, this,
        [this] (qint32 value) {
            m_noTitleBar = value;
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::windowRadiusPropertyRequested, this,
        [this] (QPointF windowRadius) {
            m_windowRadius = windowRadius;
            Q_EMIT waylandWindowRadiusChanged(windowRadius);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::shadowColorPropertyRequested  , this,
        [this] (QString shadowColor) {
            Q_EMIT waylandShadowColorChanged(shadowColor);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::borderWidthPropertyRequested, this,
        [this] (qint32 width) {
            Q_EMIT waylandBorderWidthChanged(width);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::borderColorPropertyRequested, this,
        [this] (QString borderColor) {
            Q_EMIT waylandBorderColorChanged(borderColor);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::splitWindowRequested, this,
        [this] (KWaylandServer::SplitType type) {
            Window::setQuickTileFromMenu(QuickTileMode(int(type)));

        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::windowEffectRequested, this,
        [this] (uint32_t type) {
            Q_EMIT waylandWindowEffectChanged(type);
        }
    );
    connect(m_ddeShellSurface, &DDEShellSurfaceInterface::windowStartUpEffectRequested, this,
        [this] (uint32_t type) {
            Q_EMIT waylandWindowStartUpEffectChanged(type);
        }
    );
}

KWaylandServer::DDEShellSurfaceInterface *XdgSurfaceV6Window::ddeShellSurface() const {
    return m_ddeShellSurface.data();
}

void XdgSurfaceV6Window::setupPlasmaShellIntegration()
{
    connect(surface(), &SurfaceInterface::mapped,
            this, &XdgSurfaceV6Window::updateShowOnScreenEdge);
    connect(this, &XdgSurfaceV6Window::frameGeometryChanged,
            this, &XdgSurfaceV6Window::updateClientArea);
}

XdgToplevelV6Window::XdgToplevelV6Window(XdgToplevelV6Interface *shellSurface)
    : XdgSurfaceV6Window(shellSurface->xdgSurface())
    , m_shellSurface(shellSurface)
{
    setDesktops({VirtualDesktopManager::self()->currentDesktop()});
#if KWIN_BUILD_ACTIVITIES
    if (auto a = Workspace::self()->activities()) {
        setOnActivities({a->current()});
    }
#endif
    move(workspace()->activeOutput()->geometry().center());

    connect(shellSurface, &XdgToplevelV6Interface::windowTitleChanged,
            this, &XdgToplevelV6Window::handleWindowTitleChanged);
    connect(shellSurface, &XdgToplevelV6Interface::windowClassChanged,
            this, &XdgToplevelV6Window::handleWindowClassChanged);
    connect(shellSurface, &XdgToplevelV6Interface::windowMenuRequested,
            this, &XdgToplevelV6Window::handleWindowMenuRequested);
    connect(shellSurface, &XdgToplevelV6Interface::moveRequested,
            this, &XdgToplevelV6Window::handleMoveRequested);
    connect(shellSurface, &XdgToplevelV6Interface::resizeRequested,
            this, &XdgToplevelV6Window::handleResizeRequested);
    connect(shellSurface, &XdgToplevelV6Interface::maximizeRequested,
            this, &XdgToplevelV6Window::handleMaximizeRequested);
    connect(shellSurface, &XdgToplevelV6Interface::unmaximizeRequested,
            this, &XdgToplevelV6Window::handleUnmaximizeRequested);
    connect(shellSurface, &XdgToplevelV6Interface::fullscreenRequested,
            this, &XdgToplevelV6Window::handleFullscreenRequested);
    connect(shellSurface, &XdgToplevelV6Interface::unfullscreenRequested,
            this, &XdgToplevelV6Window::handleUnfullscreenRequested);
    connect(shellSurface, &XdgToplevelV6Interface::minimizeRequested,
            this, &XdgToplevelV6Window::handleMinimizeRequested);
    connect(shellSurface, &XdgToplevelV6Interface::parentXdgToplevelChanged,
            this, &XdgToplevelV6Window::handleTransientForChanged);
    connect(shellSurface, &XdgToplevelV6Interface::initializeRequested,
            this, &XdgToplevelV6Window::initialize);
    connect(shellSurface, &XdgToplevelV6Interface::aboutToBeDestroyed,
            this, &XdgToplevelV6Window::destroyWindow);
    connect(shellSurface, &XdgToplevelV6Interface::maximumSizeChanged,
            this, &XdgToplevelV6Window::handleMaximumSizeChanged);
    connect(shellSurface, &XdgToplevelV6Interface::minimumSizeChanged,
            this, &XdgToplevelV6Window::handleMinimumSizeChanged);
    connect(shellSurface->shell(), &XdgShellV6Interface::pingTimeout,
            this, &XdgToplevelV6Window::handlePingTimeout);
    connect(shellSurface->shell(), &XdgShellV6Interface::pingDelayed,
            this, &XdgToplevelV6Window::handlePingDelayed);
    connect(shellSurface->shell(), &XdgShellV6Interface::pongReceived,
            this, &XdgToplevelV6Window::handlePongReceived);

    connect(waylandServer(), &WaylandServer::foreignTransientChanged,
            this, &XdgToplevelV6Window::handleForeignTransientForChanged);
}

XdgToplevelV6Window::~XdgToplevelV6Window()
{
    if (m_isBenchWindow) {
        Compositor::self()->decrementBenchWindow();
    }
}

XdgToplevelV6Interface *XdgToplevelV6Window::shellSurface() const
{
    return m_shellSurface;
}

MaximizeMode XdgToplevelV6Window::maximizeMode() const
{
    return m_maximizeMode;
}

MaximizeMode XdgToplevelV6Window::requestedMaximizeMode() const
{
    return m_requestedMaximizeMode;
}

QSizeF XdgToplevelV6Window::minSize() const
{
    return rules()->checkMinSize(m_shellSurface->minimumSize());
}

QSizeF XdgToplevelV6Window::maxSize() const
{
    return rules()->checkMaxSize(m_shellSurface->maximumSize());
}

bool XdgToplevelV6Window::isFullScreen() const
{
    return m_isFullScreen;
}

bool XdgToplevelV6Window::isRequestedFullScreen() const
{
    return m_isRequestedFullScreen;
}

bool XdgToplevelV6Window::isMovable() const
{
    if (isRequestedFullScreen()) {
        return false;
    }
    if ((isSpecialWindow() && !isSplash() && !isToolbar()) || isAppletPopup()) {
        return false;
    }
    if (rules()->checkPosition(invalidPoint) != invalidPoint) {
        return false;
    }
    return true;
}

bool XdgToplevelV6Window::isMovableAcrossScreens() const
{
    if ((isSpecialWindow() && !isSplash() && !isToolbar()) || isAppletPopup()) {
        return false;
    }
    if (rules()->checkPosition(invalidPoint) != invalidPoint) {
        return false;
    }
    if (m_plasmaShellSurface) {
        return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
    }
    return true;
}

bool XdgToplevelV6Window::isResizable() const
{
    if (!m_resizable || isRequestedFullScreen()) {
        return false;
    }
    if (isSpecialWindow() || isSplash() || isToolbar()) {
        return false;
    }
    if (rules()->checkSize(QSize()).isValid()) {
        return false;
    }
    const QSizeF min = minSize();
    const QSizeF max = maxSize();
    return min.width() < max.width() || min.height() < max.height();
}

bool XdgToplevelV6Window::isCloseable() const
{
    return !isDesktop() && !isDock() && m_closeable;
}

bool XdgToplevelV6Window::isFullScreenable() const
{
    if (!rules()->checkFullScreen(true)) {
        return false;
    }
    return !isSpecialWindow();
}

bool XdgToplevelV6Window::isMaximizable() const
{
    if (!isResizable() || !m_maxmizable || isAppletPopup()) {
        return false;
    }
    if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || rules()->checkMaximize(MaximizeFull) != MaximizeFull) {
        return false;
    }
    return true;
}

bool XdgToplevelV6Window::isMinimizable() const
{
    if ((isSpecialWindow() && !isTransient()) || isAppletPopup()) {
        return false;
    }
    if (!rules()->checkMinimize(true) || !m_minimizable) {
        return false;
    }
    return true;
}

bool XdgToplevelV6Window::isPlaceable() const
{
    if (m_plasmaShellSurface) {
        return !m_plasmaShellSurface->isPositionSet() && !m_plasmaShellSurface->wantsOpenUnderCursor();
    }
    return true;
}

bool XdgToplevelV6Window::isTransient() const
{
    return m_isTransient;
}

bool XdgToplevelV6Window::userCanSetFullScreen() const
{
    return true;
}

bool XdgToplevelV6Window::userCanSetNoBorder() const
{
    return (m_serverDecoration || m_xdgDecoration) && !isFullScreen() && !isShade();
}

bool XdgToplevelV6Window::noBorder() const
{
    return m_userNoBorder;
}

void XdgToplevelV6Window::setNoBorder(bool set)
{
    set = rules()->checkNoBorder(set);
    if (m_userNoBorder == set) {
        return;
    }
    m_userNoBorder = set;
    configureDecoration();
    updateWindowRules(Rules::NoBorder);
}

void XdgToplevelV6Window::invalidateDecoration()
{
    clearDecoration();
    configureDecoration();
}

bool XdgToplevelV6Window::supportsWindowRules() const
{
    return true;
}

StrutRect XdgToplevelV6Window::strutRect(StrutArea area) const
{
    if (!hasStrut()) {
        return StrutRect();
    }

    const QRect windowRect = frameGeometry().toRect();
    const QRect outputRect = output()->geometry();

    const bool left = windowRect.left() == outputRect.left();
    const bool right = windowRect.right() == outputRect.right();
    const bool top = windowRect.top() == outputRect.top();
    const bool bottom = windowRect.bottom() == outputRect.bottom();
    const bool horizontal = width() >= height();

    KWaylandServer::deepinKwinStrut strutArea = strut();
    if (strutArea.left != 0 || strutArea.right != 0 || strutArea.top != 0 || strutArea.bottom != 0) {
        switch (area) {
        case StrutAreaTop: {
            QRect strutTop = windowRect;
            if (strutArea.top != 0) {
                strutTop.setHeight(strutArea.top);
                strutTop.setLeft(strutArea.top_start_x);
                strutTop.setWidth(strutArea.top_end_x - strutArea.top_start_x);
                strutTop &= outputRect;
                return StrutRect(strutTop, StrutAreaTop);
            }
            return StrutRect();
        }
        case StrutAreaRight: {
            QRect strutRight = windowRect;
            if (strutArea.right != 0) {
                strutRight.setLeft(outputRect.right() - strutArea.right);
                strutRight.setWidth(strutArea.right);
                strutRight.setTop(strutArea.right_start_y);
                strutRight.setHeight(strutArea.right_end_y - strutArea.right_start_y);
                strutRight &= outputRect;
                return StrutRect(strutRight, StrutAreaRight);
            }
            return StrutRect();
        }
        case StrutAreaBottom: {
            QRect strutBottom = windowRect;
            if (strutArea.bottom != 0) {
                strutBottom.setTop(outputRect.bottom() - strutArea.bottom);
                strutBottom.setHeight(strutArea.bottom);
                strutBottom.setLeft(strutArea.bottom_start_x);
                strutBottom.setWidth(strutArea.bottom_end_x - strutArea.bottom_start_x);
                strutBottom &= outputRect;
                return StrutRect(strutBottom, StrutAreaBottom);
            }
            return StrutRect();
        }
        case StrutAreaLeft: {
            QRect strutLeft = windowRect;
            if (strutArea.left != 0) {
                strutLeft.setWidth(strutArea.left);
                strutLeft.setTop(strutArea.left_start_y);
                strutLeft.setHeight(strutArea.left_end_y - strutArea.left_start_y);
                strutLeft &= outputRect;
                return StrutRect(strutLeft, StrutAreaLeft);
            }
            return StrutRect();
        }
        default:
            return StrutRect();
        }
    }

    switch (area) {
    case StrutAreaTop:
        if (top && horizontal) {
            return StrutRect(windowRect, StrutAreaTop);
        }
        return StrutRect();
    case StrutAreaRight:
        if (right && !horizontal) {
            return StrutRect(windowRect, StrutAreaRight);
        }
        return StrutRect();
    case StrutAreaBottom:
        if (bottom && horizontal) {
            return StrutRect(windowRect, StrutAreaBottom);
        }
        return StrutRect();
    case StrutAreaLeft:
        if (left && !horizontal) {
            return StrutRect(windowRect, StrutAreaLeft);
        }
        return StrutRect();
    default:
        return StrutRect();
    }
}

bool XdgToplevelV6Window::hasStrut() const
{
    if (!isShown()) {
        return false;
    }
    if (!m_plasmaShellSurface) {
        return false;
    }
    if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) {
        return false;
    }
    KWaylandServer::deepinKwinStrut strutArea = strut();
    if (strutArea.top == 0 && strutArea.left == 0 && strutArea.bottom == 0 && strutArea.right == 0) {
        return false;
    }
    return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible;
}

void XdgToplevelV6Window::showOnScreenEdge()
{
    // ShowOnScreenEdge can be called by an Edge, and hideClient could destroy the Edge
    // Use the singleshot to avoid use-after-free
    QTimer::singleShot(0, this, [this]() {
        showClient();
        workspace()->raiseWindow(this);
        if (m_plasmaShellSurface && m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) {
            m_plasmaShellSurface->showAutoHidingPanel();
        }
    });
}

void XdgToplevelV6Window::closeWindow()
{
    if (isCloseable()) {
        sendPing(PingReason::CloseWindow);
        m_shellSurface->sendClose();
    }
    broadcastDbusDestroySignal(window());
}

XdgSurfaceV6Configure *XdgToplevelV6Window::sendRoleConfigure() const
{
    QSize framePadding(0, 0);
    if (m_nextDecoration) {
        framePadding.setWidth(m_nextDecoration->borderLeft() + m_nextDecoration->borderRight());
        framePadding.setHeight(m_nextDecoration->borderTop() + m_nextDecoration->borderBottom());
    }

    QSizeF nextClientSize = moveResizeGeometry().size();
    if (!nextClientSize.isEmpty()) {
        nextClientSize.rwidth() -= framePadding.width();
        nextClientSize.rheight() -= framePadding.height();
    }

    if (nextClientSize.isEmpty()) {
        QSizeF bounds = workspace()->clientArea(PlacementArea, this, moveResizeOutput()).size();
        bounds.rwidth() -= framePadding.width();
        bounds.rheight() -= framePadding.height();
        m_shellSurface->sendConfigureBounds(bounds.toSize());
    }

    const quint32 serial = m_shellSurface->sendConfigure(nextClientSize.toSize(), m_nextStates);

    XdgToplevelV6Configure *configureEvent = new XdgToplevelV6Configure();
    configureEvent->bounds = moveResizeGeometry();
    configureEvent->states = m_nextStates;
    configureEvent->decoration = m_nextDecoration;
    configureEvent->serial = serial;

    return configureEvent;
}

void XdgToplevelV6Window::handleRolePrecommit()
{
    auto configureEvent = static_cast<XdgToplevelV6Configure *>(lastAcknowledgedConfigure());
    if (configureEvent && decoration() != configureEvent->decoration.get()) {
        setDecoration(configureEvent->decoration);
        updateShadow();
    }
}

void XdgToplevelV6Window::handleRoleCommit()
{
    auto configureEvent = static_cast<XdgToplevelV6Configure *>(lastAcknowledgedConfigure());
    if (configureEvent) {
        handleStatesAcknowledged(configureEvent->states);
    }
}

void XdgToplevelV6Window::doMinimize()
{
    if (isMinimized()) {
        workspace()->windowHidden(this);
    } else {
        Q_EMIT windowShown(this);
    }
    workspace()->updateMinimizedOfTransients(this);
}

void XdgToplevelV6Window::doInteractiveResizeSync(const QRectF &rect)
{
    moveResize(rect);
}

void XdgToplevelV6Window::doSetActive()
{
    WaylandWindow::doSetActive();

    if (isActive()) {
        m_nextStates |= XdgToplevelV6Interface::State::Activated;
    } else {
        m_nextStates &= ~XdgToplevelV6Interface::State::Activated;
    }

    scheduleConfigure();
}

void XdgToplevelV6Window::doSetFullScreen()
{
    if (isRequestedFullScreen()) {
        m_nextStates |= XdgToplevelV6Interface::State::FullScreen;
    } else {
        m_nextStates &= ~XdgToplevelV6Interface::State::FullScreen;
    }

    scheduleConfigure();
}

void XdgToplevelV6Window::doSetMaximized()
{
    if (requestedMaximizeMode() & MaximizeHorizontal) {
        m_nextStates |= XdgToplevelV6Interface::State::MaximizedHorizontal;
    } else {
        m_nextStates &= ~XdgToplevelV6Interface::State::MaximizedHorizontal;
    }

    if (requestedMaximizeMode() & MaximizeVertical) {
        m_nextStates |= XdgToplevelV6Interface::State::MaximizedVertical;
    } else {
        m_nextStates &= ~XdgToplevelV6Interface::State::MaximizedVertical;
    }

    scheduleConfigure();
}

static Qt::Edges anchorsForQuickTileMode(QuickTileMode mode)
{
    if (mode == QuickTileMode(QuickTileFlag::None)) {
        return Qt::Edges();
    }

    Qt::Edges anchors = Qt::LeftEdge | Qt::TopEdge | Qt::RightEdge | Qt::BottomEdge;

    if ((mode & QuickTileFlag::Left) && !(mode & QuickTileFlag::Right)) {
        anchors &= ~Qt::RightEdge;
    }
    if ((mode & QuickTileFlag::Right) && !(mode & QuickTileFlag::Left)) {
        anchors &= ~Qt::LeftEdge;
    }

    if ((mode & QuickTileFlag::Top) && !(mode & QuickTileFlag::Bottom)) {
        anchors &= ~Qt::BottomEdge;
    }
    if ((mode & QuickTileFlag::Bottom) && !(mode & QuickTileFlag::Top)) {
        anchors &= ~Qt::TopEdge;
    }

    return anchors;
}

void XdgToplevelV6Window::doSetQuickTileMode()
{
    const Qt::Edges anchors = anchorsForQuickTileMode(quickTileMode());

    if (anchors & Qt::LeftEdge) {
        m_nextStates |= XdgToplevelV6Interface::State::TiledLeft;
    } else {
        m_nextStates &= ~XdgToplevelV6Interface::State::TiledLeft;
    }

    if (anchors & Qt::RightEdge) {
        m_nextStates |= XdgToplevelV6Interface::State::TiledRight;
    } else {
        m_nextStates &= ~XdgToplevelV6Interface::State::TiledRight;
    }

    if (anchors & Qt::TopEdge) {
        m_nextStates |= XdgToplevelV6Interface::State::TiledTop;
    } else {
        m_nextStates &= ~XdgToplevelV6Interface::State::TiledTop;
    }

    if (anchors & Qt::BottomEdge) {
        m_nextStates |= XdgToplevelV6Interface::State::TiledBottom;
    } else {
        m_nextStates &= ~XdgToplevelV6Interface::State::TiledBottom;
    }

    scheduleConfigure();
}

bool XdgToplevelV6Window::doStartInteractiveMoveResize()
{
    if (interactiveMoveResizeGravity() != Gravity::None) {
        m_nextGravity = interactiveMoveResizeGravity();
        m_nextStates |= XdgToplevelV6Interface::State::Resizing;
        scheduleConfigure();
    }
    return true;
}

void XdgToplevelV6Window::doFinishInteractiveMoveResize()
{
    if (m_nextStates & XdgToplevelV6Interface::State::Resizing) {
        m_nextStates &= ~XdgToplevelV6Interface::State::Resizing;
        scheduleConfigure();
    }
}

bool XdgToplevelV6Window::takeFocus()
{
    if (wantsInput()) {
        sendPing(PingReason::FocusWindow);
        setActive(true);
    }
    if (!isOnScreenDisplay() && !belongsToDesktop()) {
        if (workspace()->showingDesktop()) {
            // minimize all other windows
            for (Window *c : workspace()->allClientList()) {
                if (this == c || c->isDock() || c->isDesktop() || skipTaskbar()) {
                    continue;
                }

                //if 'this' is dialog , it's parent cannot minimize
                if (this->transientFor() == c)
                    continue;
                // c is dialog and it's child of this ,cannot minimize
                if (c->transientFor() != this) {
                    c->minimize(true);
                }
            }
            workspace()->setShowingDesktop(false);
        }
    }
    return true;
}

bool XdgToplevelV6Window::wantsInput() const
{
    return rules()->checkAcceptFocus(acceptsFocus());
}

bool XdgToplevelV6Window::dockWantsInput() const
{
    if (m_ddeShellSurface && !m_acceptFocus) {
        return m_acceptFocus;
    }

    if (m_plasmaShellSurface) {
        if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) {
            return m_plasmaShellSurface->panelTakesFocus();
        }
    }
    return false;
}

bool XdgToplevelV6Window::acceptsFocus() const
{
    if (m_plasmaShellSurface) {
        if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip) {
            return false;
        }
        switch (m_plasmaShellSurface->role()) {
        case PlasmaShellSurfaceInterface::Role::Notification:
        case PlasmaShellSurfaceInterface::Role::CriticalNotification:
            return m_plasmaShellSurface->panelTakesFocus();
        default:
            break;
        }
    }
    // Just return false. In case return false finally if m_acceptFoucus is true.
    if (!m_ddeShellSurface.isNull() && !m_acceptFocus) {
        return m_acceptFocus;
    }
    return !isZombie() && readyForPainting();
}

Layer XdgToplevelV6Window::layerForDock() const
{
    if (m_plasmaShellSurface) {
        switch (m_plasmaShellSurface->panelBehavior()) {
        case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover:
            return NormalLayer;
        case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide:
        case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow:
            return AboveLayer;
        case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible:
            return DockLayer;
        default:
            Q_UNREACHABLE();
            break;
        }
    }
    return Window::layerForDock();
}

void XdgToplevelV6Window::handleWindowTitleChanged()
{
    setCaption(m_shellSurface->windowTitle());
}

void XdgToplevelV6Window::handleWindowClassChanged()
{
    const QString applicationId = m_shellSurface->windowClass();
    setResourceClass(resourceName(), applicationId);
    if (shellSurface()->isConfigured()) {
        evaluateWindowRules();
    }
    setDesktopFileName(applicationId);
}

void XdgToplevelV6Window::handleWindowMenuRequested(SeatInterface *seat, const QPoint &surfacePos,
                                                  quint32 serial)
{
    performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos);
}

void XdgToplevelV6Window::handleMoveRequested(SeatInterface *seat, quint32 serial)
{
    if (!seat->hasImplicitPointerGrab(serial) && !seat->hasImplicitTouchGrab(serial)) {
        return;
    }
    if (isMovable()) {
        QPointF cursorPos;
        if (seat->hasImplicitPointerGrab(serial)) {
            cursorPos = input()->pointer()->pos();
        } else {
            cursorPos = input()->touch()->position();
        }
        performMouseCommand(Options::MouseMove, cursorPos);
    } else {
        qCDebug(KWIN_CORE) << this << "is immovable, ignoring the move request";
    }
}

void XdgToplevelV6Window::handleResizeRequested(SeatInterface *seat, XdgToplevelV6Interface::ResizeAnchor anchor, quint32 serial)
{
    if (!seat->hasImplicitPointerGrab(serial) && !seat->hasImplicitTouchGrab(serial)) {
        return;
    }
    if (!isResizable() || isShade()) {
        return;
    }
    if (isInteractiveMoveResize()) {
        finishInteractiveMoveResize(false);
    }
    setInteractiveMoveResizePointerButtonDown(true);
    QPointF cursorPos;
    if (seat->hasImplicitPointerGrab(serial)) {
        cursorPos = input()->pointer()->pos();
    } else {
        cursorPos = input()->touch()->position();
    }
    setInteractiveMoveOffset(cursorPos - pos()); // map from global
    setInvertedInteractiveMoveOffset(rect().bottomRight() - interactiveMoveOffset());
    setUnrestrictedInteractiveMoveResize(false);
    Gravity gravity;
    switch (anchor) {
    case XdgToplevelV6Interface::ResizeAnchor::TopLeft:
        gravity = Gravity::TopLeft;
        break;
    case XdgToplevelV6Interface::ResizeAnchor::Top:
        gravity = Gravity::Top;
        break;
    case XdgToplevelV6Interface::ResizeAnchor::TopRight:
        gravity = Gravity::TopRight;
        break;
    case XdgToplevelV6Interface::ResizeAnchor::Right:
        gravity = Gravity::Right;
        break;
    case XdgToplevelV6Interface::ResizeAnchor::BottomRight:
        gravity = Gravity::BottomRight;
        break;
    case XdgToplevelV6Interface::ResizeAnchor::Bottom:
        gravity = Gravity::Bottom;
        break;
    case XdgToplevelV6Interface::ResizeAnchor::BottomLeft:
        gravity = Gravity::BottomLeft;
        break;
    case XdgToplevelV6Interface::ResizeAnchor::Left:
        gravity = Gravity::Left;
        break;
    default:
        gravity = Gravity::None;
        break;
    }
    setInteractiveMoveResizeGravity(gravity);
    if (!startInteractiveMoveResize()) {
        setInteractiveMoveResizePointerButtonDown(false);
    }
    updateCursor();
}

void XdgToplevelV6Window::handleStatesAcknowledged(const XdgToplevelV6Interface::States &states)
{
    const XdgToplevelV6Interface::States delta = m_acknowledgedStates ^ states;

    if (delta & XdgToplevelV6Interface::State::Maximized) {
        MaximizeMode maximizeMode = MaximizeRestore;
        if (states & XdgToplevelV6Interface::State::MaximizedHorizontal) {
            maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal);
        }
        if (states & XdgToplevelV6Interface::State::MaximizedVertical) {
            maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical);
        }
        updateMaximizeMode(maximizeMode);
    }
    if (delta & XdgToplevelV6Interface::State::FullScreen) {
        updateFullScreenMode(states & XdgToplevelV6Interface::State::FullScreen);
    }

    m_acknowledgedStates = states;
}

void XdgToplevelV6Window::handleMaximizeRequested()
{
    if (m_isInitialized) {
        maximize(MaximizeFull);
        scheduleConfigure();
        setTile(nullptr);
    } else {
        m_initialStates |= XdgToplevelV6Interface::State::Maximized;
    }
}

void XdgToplevelV6Window::handleUnmaximizeRequested()
{
    if (m_isInitialized) {
        maximize(MaximizeRestore);
        scheduleConfigure();
    } else {
        m_initialStates &= ~XdgToplevelV6Interface::State::Maximized;
    }
}

void XdgToplevelV6Window::handleFullscreenRequested(OutputInterface *output)
{
    m_fullScreenRequestedOutput = output ? output->handle() : nullptr;
    if (m_isInitialized) {
        setFullScreen(/* set */ true, /* user */ false);
        scheduleConfigure();
    } else {
        m_initialStates |= XdgToplevelV6Interface::State::FullScreen;
    }
}

void XdgToplevelV6Window::handleUnfullscreenRequested()
{
    m_fullScreenRequestedOutput.clear();
    if (m_isInitialized) {
        setFullScreen(/* set */ false, /* user */ false);
        scheduleConfigure();
    } else {
        m_initialStates &= ~XdgToplevelV6Interface::State::FullScreen;
    }
}

void XdgToplevelV6Window::handleMinimizeRequested()
{
    minimize();
}

void XdgToplevelV6Window::handleTransientForChanged()
{
    SurfaceInterface *transientForSurface = nullptr;
    if (XdgToplevelV6Interface *parentToplevel = m_shellSurface->parentXdgToplevel()) {
        transientForSurface = parentToplevel->surface();
    }
    if (!transientForSurface) {
        transientForSurface = waylandServer()->findForeignTransientForSurface(surface());
    }
    Window *transientForWindow = waylandServer()->findWindow(transientForSurface);
    if (transientForWindow != transientFor()) {
        if (transientFor()) {
            transientFor()->removeTransient(this);
        }
        if (transientForWindow) {
            transientForWindow->addTransient(this);
        }
        setTransientFor(transientForWindow);
    }
    m_isTransient = transientForWindow;
}

void XdgToplevelV6Window::handleForeignTransientForChanged(SurfaceInterface *child)
{
    if (surface() == child) {
        handleTransientForChanged();
    }
}

void XdgToplevelV6Window::handlePingTimeout(quint32 serial)
{
    auto pingIt = m_pings.find(serial);
    if (pingIt == m_pings.end()) {
        return;
    }
    if (pingIt.value() == PingReason::CloseWindow) {
        qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption();

        // for internal windows, killing the window will delete this
        QPointer<QObject> guard(this);
        killWindow();
        if (!guard) {
            return;
        }
    }
    m_pings.erase(pingIt);
}

void XdgToplevelV6Window::handlePingDelayed(quint32 serial)
{
    auto it = m_pings.find(serial);
    if (it != m_pings.end()) {
        qCDebug(KWIN_CORE) << "First ping timeout:" << caption();
        setUnresponsive(true);
    }
}

void XdgToplevelV6Window::handlePongReceived(quint32 serial)
{
    m_pings.remove(serial);
    setUnresponsive(false);
}

void XdgToplevelV6Window::handleMaximumSizeChanged()
{
    Q_EMIT maximizeableChanged(isMaximizable());
}

void XdgToplevelV6Window::handleMinimumSizeChanged()
{
    Q_EMIT maximizeableChanged(isMaximizable());
}

void XdgToplevelV6Window::sendPing(PingReason reason)
{
    XdgShellV6Interface *shell = m_shellSurface->shell();
    XdgSurfaceV6Interface *surface = m_shellSurface->xdgSurface();

    const quint32 serial = shell->ping(surface);
    m_pings.insert(serial, reason);
}

MaximizeMode XdgToplevelV6Window::initialMaximizeMode() const
{
    MaximizeMode maximizeMode = MaximizeRestore;
    if (m_initialStates & XdgToplevelV6Interface::State::MaximizedHorizontal) {
        maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal);
    }
    if (m_initialStates & XdgToplevelV6Interface::State::MaximizedVertical) {
        maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical);
    }
    return maximizeMode;
}

bool XdgToplevelV6Window::initialFullScreenMode() const
{
    return m_initialStates & XdgToplevelV6Interface::State::FullScreen;
}

void XdgToplevelV6Window::initialize()
{
    bool needsPlacement = isPlaceable();
    setupWindowRules(false);

    // Move or resize the window only if enforced by a window rule.
    const QPointF forcedPosition = rules()->checkPositionSafe(invalidPoint, true);
    if (forcedPosition != invalidPoint) {
        move(forcedPosition);
    }
    const QSizeF forcedSize = rules()->checkSize(QSize(), true);
    if (forcedSize.isValid()) {
        resize(forcedSize);
    }

    maximize(rules()->checkMaximize(initialMaximizeMode(), true));
    setFullScreen(rules()->checkFullScreen(initialFullScreenMode(), true), false);
    setOnActivities(rules()->checkActivity(activities(), true));
    setDesktops(rules()->checkDesktops(desktops(), true));
    setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true));
    if (rules()->checkMinimize(isMinimized(), true)) {
        minimize(true); // No animation.
    }
    setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true));
    setSkipPager(rules()->checkSkipPager(skipPager(), true));
    setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true));
    setKeepAbove(rules()->checkKeepAbove(keepAbove(), true));
    setKeepBelow(rules()->checkKeepBelow(keepBelow(), true));
    setShortcut(rules()->checkShortcut(shortcut().toString(), true));
    setNoBorder(rules()->checkNoBorder(noBorder(), true));

    // Don't place the client if its position is set by a rule.
    if (rules()->checkPosition(invalidPoint, true) != invalidPoint) {
        needsPlacement = false;
    }

    // Don't place the client if the maximize state is set by a rule.
    if (requestedMaximizeMode() != MaximizeRestore) {
        needsPlacement = false;
    }

    discardTemporaryRules();
    workspace()->rulebook()->discardUsed(this, false); // Remove Apply Now rules.
    updateWindowRules(Rules::All);

    if (isRequestedFullScreen()) {
        needsPlacement = false;
    }
    if (needsPlacement) {
        const QRectF area = workspace()->clientArea(PlacementArea, this, workspace()->activeOutput());
        workspace()->placement()->place(this, area);
        Q_EMIT workspace()->windowStateChanged();
    }

    configureDecoration();
    scheduleConfigure();
    updateColorScheme();
    setupWindowManagementInterface();

    m_isInitialized = true;

    if (m_ddeShellSurface && !m_isSendT && isResizable()) {
        m_ddeShellSurface->sendSplitable(true);
        m_isSendT = true;
    }

    if (!qEnvironmentVariableIsSet("KWIN_DISABLE_SKIP_BUFFER")) {
        static const QStringList benchApps = [] {
            const QString envApps = qEnvironmentVariable("KWIN_SKIP_BUFFER_APPS");
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
            return envApps.isEmpty() ? QStringList{"glmark2"} : envApps.split(',', QString::SkipEmptyParts);
#else
            return envApps.isEmpty() ? QStringList{"glmark2"} : envApps.split(',', Qt::SkipEmptyParts);
#endif
        }();

        const QString &resName = resourceName();
        for (const QString &benchApp : benchApps) {
            if (resName.contains(benchApp)) {
                Compositor::self()->incrementBenchWindow();
                surface()->skipBuffer();
                m_isBenchWindow = true;
                break;
            }
        }
    }
}

void XdgToplevelV6Window::updateMaximizeMode(MaximizeMode maximizeMode)
{
    if (m_maximizeMode == maximizeMode) {
        return;
    }
    m_maximizeMode = maximizeMode;
    updateWindowRules(Rules::MaximizeVert | Rules::MaximizeHoriz);
    setMaximized(maximizeMode & MaximizeHorizontal && maximizeMode & MaximizeVertical);
    Q_EMIT clientMaximizedStateChanged(this, maximizeMode);
    Q_EMIT clientMaximizedStateChanged(this, maximizeMode & MaximizeHorizontal, maximizeMode & MaximizeVertical);
    if (maximizeMode == MaximizeFull || maximizeMode == MaximizeRestore)
        Q_EMIT clientMaximizedChanged(this, m_restoreMaxiArea, frameGeometry(), maximizeMode);

}

void XdgToplevelV6Window::updateFullScreenMode(bool set)
{
    if (m_isFullScreen == set) {
        return;
    }
    StackingUpdatesBlocker blocker1(workspace());
    m_isFullScreen = set;
    updateLayer();
    updateWindowRules(Rules::Fullscreen);
    Q_EMIT fullScreenChanged();
    Q_EMIT workspace()->windowStateChanged();
}

QString XdgToplevelV6Window::preferredColorScheme() const
{
    if (m_paletteInterface) {
        return rules()->checkDecoColor(m_paletteInterface->palette());
    }
    return rules()->checkDecoColor(QString());
}

void XdgToplevelV6Window::installAppMenu(AppMenuInterface *appMenu)
{
    m_appMenuInterface = appMenu;

    auto updateMenu = [this](const AppMenuInterface::InterfaceAddress &address) {
        updateApplicationMenuServiceName(address.serviceName);
        updateApplicationMenuObjectPath(address.objectPath);
    };
    connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, updateMenu);
    updateMenu(appMenu->address());
}

XdgToplevelV6Window::DecorationMode XdgToplevelV6Window::preferredDecorationMode() const
{
    if (!Decoration::DecorationBridge::hasPlugin()) {
        return DecorationMode::Client;
    } else if (m_userNoBorder || isRequestedFullScreen()) {
        return DecorationMode::None;
    }

    if (m_serverDecoration) {
        switch (m_serverDecoration->preferredMode()) {
        case ServerSideDecorationManagerInterface::Mode::None:
            return DecorationMode::None;
        case ServerSideDecorationManagerInterface::Mode::Client:
            return DecorationMode::Client;
        case ServerSideDecorationManagerInterface::Mode::Server:
            return DecorationMode::Server;
        }
    }

    // if (m_xdgDecoration) {
    //     switch (m_xdgDecoration->preferredMode()) {
    //     case XdgToplevelDecorationV1Interface::Mode::Undefined:
    //         return DecorationMode::Server;
    //     case XdgToplevelDecorationV1Interface::Mode::None:
    //         return DecorationMode::None;
    //     case XdgToplevelDecorationV1Interface::Mode::Client:
    //         return DecorationMode::Client;
    //     case XdgToplevelDecorationV1Interface::Mode::Server:
    //         return DecorationMode::Server;
    //     }
    // }

    return DecorationMode::Client;
}

void XdgToplevelV6Window::clearDecoration()
{
    m_nextDecoration = nullptr;
}

void XdgToplevelV6Window::configureDecoration()
{
    const DecorationMode decorationMode = preferredDecorationMode();
    switch (decorationMode) {
    case DecorationMode::None:
    case DecorationMode::Client:
        clearDecoration();
        break;
    case DecorationMode::Server:
        if (!m_nextDecoration) {
            m_nextDecoration.reset(Workspace::self()->decorationBridge()->createDecoration(this));
        }
        break;
    }

    if (m_noTitleBar != -1) {
        disconnect(m_ddeShellSurface, &DDEShellSurfaceInterface::noTitleBarPropertyRequested, this, nullptr);
        Q_EMIT m_ddeShellSurface->noTitleBarPropertyRequested(m_noTitleBar);
    }
    if (!m_windowRadius.isNull()) {
        // disconnect(m_ddeShellSurface, &KWaylandServer::DDEShellSurfaceInterface::windowRadiusPropertyRequested, this, nullptr);
        Q_EMIT m_ddeShellSurface->windowRadiusPropertyRequested(m_windowRadius);
        Q_EMIT waylandWindowRadiusChanged(m_windowRadius);
    }

    // All decoration updates are synchronized to toplevel configure events.
    if (m_xdgDecoration) {
        configureXdgDecoration(decorationMode);
    } else if (m_serverDecoration) {
        configureServerDecoration(decorationMode);
    }
}

void XdgToplevelV6Window::configureXdgDecoration(DecorationMode decorationMode)
{
    // switch (decorationMode) {
    // case DecorationMode::None: // Faked as server side mode under the hood.
    //     m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::None);
    //     break;
    // case DecorationMode::Client:
    //     m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Client);
    //     break;
    // case DecorationMode::Server:
    //     m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Server);
    //     break;
    // }
    // scheduleConfigure();
}

void XdgToplevelV6Window::configureServerDecoration(DecorationMode decorationMode)
{
    switch (decorationMode) {
    case DecorationMode::None:
        m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::None);
        break;
    case DecorationMode::Client:
        m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::Client);
        break;
    case DecorationMode::Server:
        m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::Server);
        break;
    }
    scheduleConfigure();
}

// void XdgToplevelV6Window::installXdgDecoration(XdgToplevelDecorationV1Interface *decoration)
// {
//     m_xdgDecoration = decoration;

//     connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::destroyed,
//             this, &XdgToplevelV6Window::clearDecoration);
//     connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::preferredModeChanged, this, [this] {
//         if (m_isInitialized) {
//             configureDecoration();
//         }
//     });
// }

void XdgToplevelV6Window::installServerDecoration(ServerSideDecorationInterface *decoration)
{
    m_serverDecoration = decoration;
    if (m_isInitialized) {
        configureDecoration();
    }

    connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed,
            this, &XdgToplevelV6Window::clearDecoration);
    connect(m_serverDecoration, &ServerSideDecorationInterface::preferredModeChanged, this, [this]() {
        if (m_isInitialized) {
            configureDecoration();
        }
    });
}

void XdgToplevelV6Window::installPalette(ServerSideDecorationPaletteInterface *palette)
{
    m_paletteInterface = palette;

    connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged,
            this, &XdgToplevelV6Window::updateColorScheme);
    connect(m_paletteInterface, &QObject::destroyed,
            this, &XdgToplevelV6Window::updateColorScheme);
    updateColorScheme();
}

void XdgToplevelV6Window::setFullScreen(bool set, bool user)
{
    set = rules()->checkFullScreen(set);
    if (m_isRequestedFullScreen == set) {
        return;
    }
    if (isSpecialWindow()) {
        return;
    }
    if (user && !userCanSetFullScreen()) {
        return;
    }

    m_isRequestedFullScreen = set;
    configureDecoration();

    if (set) {
        const Output *output = m_fullScreenRequestedOutput ? m_fullScreenRequestedOutput.data() : moveResizeOutput();
        setFullscreenGeometryRestore(moveResizeGeometry());
        moveResize(workspace()->clientArea(FullScreenArea, this, output));
    } else {
        m_fullScreenRequestedOutput.clear();
        if (fullscreenGeometryRestore().isValid()) {
            moveResize(QRectF(fullscreenGeometryRestore().topLeft(),
                              constrainFrameSize(fullscreenGeometryRestore().size())));
        } else {
            // this can happen when the window was first shown already fullscreen,
            // so let the client set the size by itself
            moveResize(QRectF(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0)));
        }
    }

    doSetFullScreen();
}

static bool changeMaximizeRecursion = false;
void XdgToplevelV6Window::maximize(MaximizeMode mode, bool animated)
{
    if (workspace()->userActionsMenu()->isShown()) {
        return;
    }

    if (changeMaximizeRecursion) {
        return;
    }

    if (!isResizable() || isAppletPopup()) {
        return;
    }

    const QRectF clientArea = isElectricBorderMaximizing() ? workspace()->clientArea(MaximizeArea, this, Cursors::self()->mouse()->pos()) : workspace()->clientArea(MaximizeArea, this, moveResizeOutput());

    const MaximizeMode oldMode = m_requestedMaximizeMode;
    const QRectF oldGeometry = moveResizeGeometry();

    mode = rules()->checkMaximize(mode);
    if (m_requestedMaximizeMode == mode) {
        return;
    }
    if (auto item = surfaceItem()) {
        item->discardPixmap();
    }
    m_restoreMaxiArea = frameGeometry();
    Q_EMIT clientMaximizedStateAboutToChange(this, mode);
    m_requestedMaximizeMode = mode;

    // call into decoration update borders
    if (m_nextDecoration && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) {
        changeMaximizeRecursion = true;
        const auto c = m_nextDecoration->client().toStrongRef();
        if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) {
            Q_EMIT c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical);
        }
        if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) {
            Q_EMIT c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal);
        }
        if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) {
            Q_EMIT c->maximizedChanged(m_requestedMaximizeMode == MaximizeFull);
        }
        changeMaximizeRecursion = false;
    }

    if (options->borderlessMaximizedWindows()) {
        setNoBorder(m_requestedMaximizeMode == MaximizeFull);
    }

    if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) {
        QRectF savedGeometry = geometryRestore();
        if (!(oldMode & MaximizeVertical)) {
            savedGeometry.setTop(oldGeometry.top());
            savedGeometry.setBottom(oldGeometry.bottom());
        }
        if (!(oldMode & MaximizeHorizontal)) {
            savedGeometry.setLeft(oldGeometry.left());
            savedGeometry.setRight(oldGeometry.right());
        }
        setGeometryRestore(savedGeometry);
    }

    const MaximizeMode delta = m_requestedMaximizeMode ^ oldMode;
    QRectF geometry = oldGeometry;

    if (delta & MaximizeHorizontal) {
        if (m_requestedMaximizeMode & MaximizeHorizontal) {
            // Stretch the window vertically to fit the size of the maximize area.
            geometry.setX(clientArea.x());
            geometry.setWidth(clientArea.width());
        } else if (geometryRestore().isValid()) {
            // The window is no longer maximized horizontally and the saved geometry is valid.
            geometry.setX(geometryRestore().x());
            geometry.setWidth(geometryRestore().width());
        } else {
            // The window is no longer maximized horizontally and the saved geometry is
            // invalid. This would happen if the window had been mapped in the maximized state.
            // We ask the client to resize the window horizontally to its preferred size.
            geometry.setX(clientArea.x());
            geometry.setWidth(0);
        }
    }

    if (delta & MaximizeVertical) {
        if (m_requestedMaximizeMode & MaximizeVertical) {
            // Stretch the window horizontally to fit the size of the maximize area.
            geometry.setY(clientArea.y());
            geometry.setHeight(clientArea.height());
        } else if (geometryRestore().isValid()) {
            // The window is no longer maximized vertically and the saved geometry is valid.
            geometry.setY(geometryRestore().y());
            geometry.setHeight(geometryRestore().height());
        } else {
            // The window is no longer maximized vertically and the saved geometry is
            // invalid. This would happen if the window had been mapped in the maximized state.
            // We ask the client to resize the window vertically to its preferred size.
            geometry.setY(clientArea.y());
            geometry.setHeight(0);
        }
    }

    const auto oldQuickTileMode = quickTileMode();
    if (m_requestedMaximizeMode == MaximizeFull) {
        if (options->electricBorderMaximize()) {
            updateQuickTileMode(QuickTileFlag::Maximize);
        } else {
            updateQuickTileMode(QuickTileFlag::None);
        }
        // When maximizing the window, it is necessary to reset the positionset.
        if (m_plasmaShellSurface) {
            m_plasmaShellSurface->resetPositionSet();
        }
    } else {
        updateQuickTileMode(QuickTileFlag::None);
    }

    moveResize(geometry);

    if (oldQuickTileMode != quickTileMode()) {
        doSetQuickTileMode();
        Q_EMIT quickTileModeChanged();
    }

    doSetMaximized();
}

XdgPopupV6Window::XdgPopupV6Window(XdgPopupV6Interface *shellSurface)
    : XdgSurfaceV6Window(shellSurface->xdgSurface())
    , m_shellSurface(shellSurface)
{
    m_windowType = NET::Unknown;
    setDesktops({VirtualDesktopManager::self()->currentDesktop()});
#if KWIN_BUILD_ACTIVITIES
    if (auto a = Workspace::self()->activities()) {
        setOnActivities({a->current()});
    }
#endif

    connect(shellSurface, &XdgPopupV6Interface::grabRequested,
            this, &XdgPopupV6Window::handleGrabRequested);
    connect(shellSurface, &XdgPopupV6Interface::initializeRequested,
            this, &XdgPopupV6Window::initialize);
    connect(shellSurface, &XdgPopupV6Interface::repositionRequested,
            this, &XdgPopupV6Window::handleRepositionRequested);
    connect(shellSurface, &XdgPopupV6Interface::aboutToBeDestroyed,
            this, &XdgPopupV6Window::destroyWindow);
}

void XdgPopupV6Window::updateReactive()
{
    if (m_shellSurface->positioner().isReactive()) {
        connect(transientFor(), &Window::frameGeometryChanged,
                this, &XdgPopupV6Window::relayout, Qt::UniqueConnection);
    } else {
        disconnect(transientFor(), &Window::frameGeometryChanged,
                   this, &XdgPopupV6Window::relayout);
    }
}

void XdgPopupV6Window::handleRepositionRequested(quint32 token)
{
    updateReactive();
    m_shellSurface->sendRepositioned(token);
    relayout();
}

void XdgPopupV6Window::relayout()
{
    workspace()->placement()->place(this, QRect());
    Q_EMIT workspace()->windowStateChanged();
    scheduleConfigure();
}

XdgPopupV6Window::~XdgPopupV6Window()
{
}

bool XdgPopupV6Window::hasPopupGrab() const
{
    return m_haveExplicitGrab;
}

void XdgPopupV6Window::popupDone()
{
    m_shellSurface->sendPopupDone();
}

bool XdgPopupV6Window::isPopupWindow() const
{
    return true;
}

bool XdgPopupV6Window::isTransient() const
{
    return true;
}

bool XdgPopupV6Window::isResizable() const
{
    return false;
}

bool XdgPopupV6Window::isMovable() const
{
    return false;
}

bool XdgPopupV6Window::isMovableAcrossScreens() const
{
    return false;
}

bool XdgPopupV6Window::hasTransientPlacementHint() const
{
    return true;
}

QRectF XdgPopupV6Window::transientPlacement(const QRectF &bounds) const
{
    const XdgPositionerV6 positioner = m_shellSurface->positioner();

    const QSize desiredSize = positioner.size();

    const QPointF parentPosition = transientFor()->framePosToClientPos(transientFor()->pos());

    // returns if a target is within the supplied bounds, optional edges argument states which side to check
    auto inBounds = [bounds](const QRectF &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool {
        if (edges & Qt::LeftEdge && target.left() < bounds.left()) {
            return false;
        }
        if (edges & Qt::TopEdge && target.top() < bounds.top()) {
            return false;
        }
        if (edges & Qt::RightEdge && target.right() > bounds.right()) {
            // normal QRect::right issue cancels out
            return false;
        }
        if (edges & Qt::BottomEdge && target.bottom() > bounds.bottom()) {
            return false;
        }
        return true;
    };

    QRectF popupRect(popupOffset(positioner.anchorRect(), positioner.anchorEdges(), positioner.gravityEdges(), desiredSize) + positioner.offset() + parentPosition, desiredSize);

    // if that fits, we don't need to do anything
    if (inBounds(popupRect)) {
        return popupRect;
    }
    // otherwise apply constraint adjustment per axis in order XDG Shell Popup states

    if (positioner.flipConstraintAdjustments() & Qt::Horizontal) {
        if (!inBounds(popupRect, Qt::LeftEdge | Qt::RightEdge)) {
            // flip both edges (if either bit is set, XOR both)
            auto flippedAnchorEdge = positioner.anchorEdges();
            if (flippedAnchorEdge & (Qt::LeftEdge | Qt::RightEdge)) {
                flippedAnchorEdge ^= (Qt::LeftEdge | Qt::RightEdge);
            }
            auto flippedGravity = positioner.gravityEdges();
            if (flippedGravity & (Qt::LeftEdge | Qt::RightEdge)) {
                flippedGravity ^= (Qt::LeftEdge | Qt::RightEdge);
            }
            auto flippedPopupRect = QRectF(popupOffset(positioner.anchorRect(), flippedAnchorEdge, flippedGravity, desiredSize) + positioner.offset() + parentPosition, desiredSize);

            // if it still doesn't fit we should continue with the unflipped version
            if (inBounds(flippedPopupRect, Qt::LeftEdge | Qt::RightEdge)) {
                popupRect.moveLeft(flippedPopupRect.left());
            }
        }
    }
    if (positioner.slideConstraintAdjustments() & Qt::Horizontal) {
        if (!inBounds(popupRect, Qt::LeftEdge)) {
            popupRect.moveLeft(bounds.left());
        }
        if (!inBounds(popupRect, Qt::RightEdge)) {
            popupRect.moveRight(bounds.right());
        }
    }
    if (positioner.resizeConstraintAdjustments() & Qt::Horizontal) {
        QRectF unconstrainedRect = popupRect;

        if (!inBounds(unconstrainedRect, Qt::LeftEdge)) {
            unconstrainedRect.setLeft(bounds.left());
        }
        if (!inBounds(unconstrainedRect, Qt::RightEdge)) {
            unconstrainedRect.setRight(bounds.right());
        }

        if (unconstrainedRect.isValid()) {
            popupRect = unconstrainedRect;
        }
    }

    if (positioner.flipConstraintAdjustments() & Qt::Vertical) {
        if (!inBounds(popupRect, Qt::TopEdge | Qt::BottomEdge)) {
            // flip both edges (if either bit is set, XOR both)
            auto flippedAnchorEdge = positioner.anchorEdges();
            if (flippedAnchorEdge & (Qt::TopEdge | Qt::BottomEdge)) {
                flippedAnchorEdge ^= (Qt::TopEdge | Qt::BottomEdge);
            }
            auto flippedGravity = positioner.gravityEdges();
            if (flippedGravity & (Qt::TopEdge | Qt::BottomEdge)) {
                flippedGravity ^= (Qt::TopEdge | Qt::BottomEdge);
            }
            auto flippedPopupRect = QRectF(popupOffset(positioner.anchorRect(), flippedAnchorEdge, flippedGravity, desiredSize) + positioner.offset() + parentPosition, desiredSize);

            // if it still doesn't fit we should continue with the unflipped version
            if (inBounds(flippedPopupRect, Qt::TopEdge | Qt::BottomEdge)) {
                popupRect.moveTop(flippedPopupRect.top());
            }
        }
    }
    if (positioner.slideConstraintAdjustments() & Qt::Vertical) {
        if (!inBounds(popupRect, Qt::TopEdge)) {
            popupRect.moveTop(bounds.top());
        }
        if (!inBounds(popupRect, Qt::BottomEdge)) {
            popupRect.moveBottom(bounds.bottom());
        }
    }
    if (positioner.resizeConstraintAdjustments() & Qt::Vertical) {
        QRectF unconstrainedRect = popupRect;

        if (!inBounds(unconstrainedRect, Qt::TopEdge)) {
            unconstrainedRect.setTop(bounds.top());
        }
        if (!inBounds(unconstrainedRect, Qt::BottomEdge)) {
            unconstrainedRect.setBottom(bounds.bottom());
        }

        if (unconstrainedRect.isValid()) {
            popupRect = unconstrainedRect;
        }
    }

    return popupRect;
}

bool XdgPopupV6Window::isCloseable() const
{
    return false;
}

void XdgPopupV6Window::closeWindow()
{
}

bool XdgPopupV6Window::wantsInput() const
{
    return false;
}

bool XdgPopupV6Window::takeFocus()
{
    return false;
}

bool XdgPopupV6Window::acceptsFocus() const
{
    return false;
}

XdgSurfaceV6Configure *XdgPopupV6Window::sendRoleConfigure() const
{
    const QPointF parentPosition = transientFor()->framePosToClientPos(transientFor()->pos());
    const QPointF popupPosition = moveResizeGeometry().topLeft() - parentPosition;

    const quint32 serial = m_shellSurface->sendConfigure(QRect(popupPosition.toPoint(), moveResizeGeometry().size().toSize()));

    XdgSurfaceV6Configure *configureEvent = new XdgSurfaceV6Configure();
    configureEvent->bounds = moveResizeGeometry();
    configureEvent->serial = serial;

    return configureEvent;
}

void XdgPopupV6Window::handleGrabRequested(SeatInterface *seat, quint32 serial)
{
    m_haveExplicitGrab = true;
}

void XdgPopupV6Window::initialize()
{
    Window *parent = waylandServer()->findWindow(m_shellSurface->parentSurface());
    parent->addTransient(this);
    setTransientFor(parent);
    setupWindowRules(false);

    updateReactive();

    const QRectF area = workspace()->clientArea(PlacementArea, this, workspace()->activeOutput());
    workspace()->placement()->place(this, area);
    Q_EMIT workspace()->windowStateChanged();
    scheduleConfigure();
}

} // namespace KWin
