You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1860 lines
58 KiB
1860 lines
58 KiB
/* |
|
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 "xdgshellwindow.h" |
|
#include "core/output.h" |
|
#include "effect/globals.h" |
|
#include "utils/common.h" |
|
#if KWIN_BUILD_ACTIVITIES |
|
#include "activities.h" |
|
#endif |
|
#include "decorations/decorationbridge.h" |
|
#include "killprompt.h" |
|
#include "placement.h" |
|
#include "pointer_input.h" |
|
#include "tablet_input.h" |
|
#include "tiles/tilemanager.h" |
|
#include "touch_input.h" |
|
#include "utils/subsurfacemonitor.h" |
|
#include "virtualdesktops.h" |
|
#include "wayland/appmenu.h" |
|
#include "wayland/output.h" |
|
#include "wayland/plasmashell.h" |
|
#include "wayland/seat.h" |
|
#include "wayland/server_decoration.h" |
|
#include "wayland/server_decoration_palette.h" |
|
#include "wayland/surface.h" |
|
#include "wayland/tablet_v2.h" |
|
#include "wayland/xdgdecoration_v1.h" |
|
#include "wayland/xdgdialog_v1.h" |
|
#include "wayland_server.h" |
|
#include "workspace.h" |
|
|
|
#include <KDecoration2/DecoratedClient> |
|
#include <KDecoration2/Decoration> |
|
|
|
namespace KWin |
|
{ |
|
|
|
XdgSurfaceWindow::XdgSurfaceWindow(XdgSurfaceInterface *shellSurface) |
|
: WaylandWindow(shellSurface->surface()) |
|
, m_shellSurface(shellSurface) |
|
, m_configureTimer(new QTimer(this)) |
|
{ |
|
connect(shellSurface, &XdgSurfaceInterface::configureAcknowledged, |
|
this, &XdgSurfaceWindow::handleConfigureAcknowledged); |
|
connect(shellSurface, &XdgSurfaceInterface::resetOccurred, |
|
this, &XdgSurfaceWindow::destroyWindow); |
|
connect(shellSurface->surface(), &SurfaceInterface::committed, |
|
this, &XdgSurfaceWindow::handleCommit); |
|
connect(shellSurface, &XdgSurfaceInterface::aboutToBeDestroyed, |
|
this, &XdgSurfaceWindow::destroyWindow); |
|
connect(shellSurface->surface(), &SurfaceInterface::aboutToBeDestroyed, |
|
this, &XdgSurfaceWindow::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, &XdgSurfaceWindow::setHaveNextWindowGeometry); |
|
connect(treeMonitor, &SubSurfaceMonitor::subSurfaceRemoved, |
|
this, &XdgSurfaceWindow::setHaveNextWindowGeometry); |
|
connect(treeMonitor, &SubSurfaceMonitor::subSurfaceMoved, |
|
this, &XdgSurfaceWindow::setHaveNextWindowGeometry); |
|
connect(treeMonitor, &SubSurfaceMonitor::subSurfaceResized, |
|
this, &XdgSurfaceWindow::setHaveNextWindowGeometry); |
|
connect(shellSurface, &XdgSurfaceInterface::windowGeometryChanged, |
|
this, &XdgSurfaceWindow::setHaveNextWindowGeometry); |
|
connect(surface(), &SurfaceInterface::sizeChanged, |
|
this, &XdgSurfaceWindow::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, &XdgSurfaceWindow::sendConfigure); |
|
} |
|
|
|
XdgSurfaceWindow::~XdgSurfaceWindow() |
|
{ |
|
} |
|
|
|
WindowType XdgSurfaceWindow::windowType() const |
|
{ |
|
return m_windowType; |
|
} |
|
|
|
XdgSurfaceConfigure *XdgSurfaceWindow::lastAcknowledgedConfigure() const |
|
{ |
|
return m_lastAcknowledgedConfigure.get(); |
|
} |
|
|
|
void XdgSurfaceWindow::scheduleConfigure() |
|
{ |
|
if (!isDeleted()) { |
|
m_configureTimer->start(); |
|
} |
|
} |
|
|
|
void XdgSurfaceWindow::sendConfigure() |
|
{ |
|
XdgSurfaceConfigure *configureEvent = sendRoleConfigure(); |
|
|
|
// The configure event inherits configure flags from the previous event. |
|
if (!m_configureEvents.isEmpty()) { |
|
const XdgSurfaceConfigure *previousEvent = m_configureEvents.constLast(); |
|
configureEvent->flags = previousEvent->flags; |
|
} |
|
|
|
configureEvent->gravity = m_nextGravity; |
|
configureEvent->flags |= m_configureFlags; |
|
m_configureFlags = {}; |
|
|
|
m_configureEvents.append(configureEvent); |
|
} |
|
|
|
void XdgSurfaceWindow::handleConfigureAcknowledged(quint32 serial) |
|
{ |
|
m_lastAcknowledgedConfigureSerial = serial; |
|
} |
|
|
|
void XdgSurfaceWindow::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(); |
|
|
|
markAsMapped(); |
|
} |
|
|
|
void XdgSurfaceWindow::handleRolePrecommit() |
|
{ |
|
} |
|
|
|
void XdgSurfaceWindow::handleRoleCommit() |
|
{ |
|
} |
|
|
|
void XdgSurfaceWindow::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 & XdgSurfaceConfigure::ConfigurePosition) { |
|
return; |
|
} |
|
} |
|
|
|
setMoveResizeGeometry(rect); |
|
} |
|
|
|
void XdgSurfaceWindow::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 XdgSurfaceConfigure *configureEvent = lastAcknowledgedConfigure()) { |
|
if (configureEvent->flags & XdgSurfaceConfigure::ConfigurePosition) { |
|
frameGeometry = gravitateGeometry(frameGeometry, configureEvent->bounds, configureEvent->gravity); |
|
} |
|
} |
|
|
|
if (isInteractiveMove()) { |
|
bool fullscreen = isFullScreen(); |
|
if (const auto configureEvent = static_cast<XdgToplevelConfigure *>(lastAcknowledgedConfigure())) { |
|
fullscreen = configureEvent->states & XdgToplevelInterface::State::FullScreen; |
|
} |
|
if (!fullscreen) { |
|
const QPointF anchor = interactiveMoveResizeAnchor(); |
|
const QPointF offset = interactiveMoveOffset(); |
|
frameGeometry.moveTopLeft(QPointF(anchor.x() - offset.x() * frameGeometry.width(), |
|
anchor.y() - offset.y() * frameGeometry.height())); |
|
} |
|
} |
|
|
|
maybeUpdateMoveResizeGeometry(frameGeometry); |
|
updateGeometry(frameGeometry); |
|
} |
|
|
|
bool XdgSurfaceWindow::haveNextWindowGeometry() const |
|
{ |
|
return m_haveNextWindowGeometry || m_lastAcknowledgedConfigure; |
|
} |
|
|
|
void XdgSurfaceWindow::setHaveNextWindowGeometry() |
|
{ |
|
m_haveNextWindowGeometry = true; |
|
} |
|
|
|
void XdgSurfaceWindow::resetHaveNextWindowGeometry() |
|
{ |
|
m_haveNextWindowGeometry = false; |
|
} |
|
|
|
void XdgSurfaceWindow::moveResizeInternal(const QRectF &rect, MoveResizeMode mode) |
|
{ |
|
Q_EMIT frameGeometryAboutToChange(); |
|
|
|
if (mode != MoveResizeMode::Move) { |
|
const QSizeF requestedClientSize = frameSizeToClientSize(rect.size()); |
|
if (requestedClientSize == clientSize()) { |
|
updateGeometry(rect); |
|
} else { |
|
m_configureFlags |= XdgSurfaceConfigure::ConfigurePosition; |
|
scheduleConfigure(); |
|
} |
|
} else { |
|
// If the window is moved, cancel any queued window position updates. |
|
for (XdgSurfaceConfigure *configureEvent : std::as_const(m_configureEvents)) { |
|
configureEvent->flags.setFlag(XdgSurfaceConfigure::ConfigurePosition, false); |
|
} |
|
m_configureFlags.setFlag(XdgSurfaceConfigure::ConfigurePosition, false); |
|
updateGeometry(QRectF(rect.topLeft(), size())); |
|
} |
|
} |
|
|
|
QRectF XdgSurfaceWindow::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 XdgSurfaceWindow::handleRoleDestroyed() |
|
{ |
|
if (m_plasmaShellSurface) { |
|
m_plasmaShellSurface->disconnect(this); |
|
} |
|
m_shellSurface->disconnect(this); |
|
m_shellSurface->surface()->disconnect(this); |
|
} |
|
|
|
void XdgSurfaceWindow::destroyWindow() |
|
{ |
|
handleRoleDestroyed(); |
|
markAsDeleted(); |
|
Q_EMIT closed(); |
|
|
|
stopDelayedInteractiveMoveResize(); |
|
if (isInteractiveMoveResize()) { |
|
leaveInteractiveMoveResize(); |
|
Q_EMIT interactiveMoveResizeFinished(); |
|
} |
|
setTile(nullptr); |
|
m_configureTimer->stop(); |
|
qDeleteAll(m_configureEvents); |
|
m_configureEvents.clear(); |
|
cleanTabBox(); |
|
StackingUpdatesBlocker blocker(workspace()); |
|
workspace()->rulebook()->discardUsed(this, true); |
|
cleanGrouping(); |
|
waylandServer()->removeWindow(this); |
|
|
|
unref(); |
|
} |
|
|
|
/** |
|
* \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 XdgSurfaceWindow::installPlasmaShellSurface(PlasmaShellSurfaceInterface *shellSurface) |
|
{ |
|
m_plasmaShellSurface = shellSurface; |
|
|
|
auto updatePosition = [this, shellSurface] { |
|
move(shellSurface->position()); |
|
}; |
|
auto showUnderCursor = [this] { |
|
// Wait for the first commit |
|
auto moveUnderCursor = [this] { |
|
if (input()->hasPointer()) { |
|
move(input()->globalPointer()); |
|
moveResize(keepInArea(moveResizeGeometry(), workspace()->clientArea(PlacementArea, this))); |
|
} |
|
}; |
|
connect(this, &Window::readyForPaintingChanged, this, moveUnderCursor, Qt::SingleShotConnection); |
|
}; |
|
auto updateRole = [this, shellSurface] { |
|
WindowType type = WindowType::Unknown; |
|
switch (shellSurface->role()) { |
|
case PlasmaShellSurfaceInterface::Role::Desktop: |
|
type = WindowType::Desktop; |
|
break; |
|
case PlasmaShellSurfaceInterface::Role::Panel: |
|
type = WindowType::Dock; |
|
break; |
|
case PlasmaShellSurfaceInterface::Role::OnScreenDisplay: |
|
type = WindowType::OnScreenDisplay; |
|
break; |
|
case PlasmaShellSurfaceInterface::Role::Notification: |
|
type = WindowType::Notification; |
|
break; |
|
case PlasmaShellSurfaceInterface::Role::ToolTip: |
|
type = WindowType::Tooltip; |
|
break; |
|
case PlasmaShellSurfaceInterface::Role::CriticalNotification: |
|
type = WindowType::CriticalNotification; |
|
break; |
|
case PlasmaShellSurfaceInterface::Role::AppletPopup: |
|
type = WindowType::AppletPopup; |
|
break; |
|
case PlasmaShellSurfaceInterface::Role::Normal: |
|
default: |
|
type = WindowType::Normal; |
|
break; |
|
} |
|
if (m_windowType == type) { |
|
return; |
|
} |
|
m_windowType = type; |
|
switch (m_windowType) { |
|
case WindowType::Desktop: |
|
case WindowType::Dock: |
|
case WindowType::OnScreenDisplay: |
|
case WindowType::Notification: |
|
case WindowType::CriticalNotification: |
|
case WindowType::Tooltip: |
|
case WindowType::AppletPopup: |
|
setOnAllDesktops(true); |
|
#if KWIN_BUILD_ACTIVITIES |
|
setOnAllActivities(true); |
|
#endif |
|
break; |
|
default: |
|
break; |
|
} |
|
}; |
|
connect(shellSurface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); |
|
connect(shellSurface, &PlasmaShellSurfaceInterface::openUnderCursorRequested, this, showUnderCursor); |
|
connect(shellSurface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); |
|
connect(shellSurface, &PlasmaShellSurfaceInterface::panelTakesFocusChanged, this, [this] { |
|
if (m_plasmaShellSurface->panelTakesFocus()) { |
|
workspace()->activateWindow(this); |
|
} |
|
}); |
|
if (shellSurface->isPositionSet()) { |
|
updatePosition(); |
|
} |
|
if (shellSurface->wantsOpenUnderCursor()) { |
|
showUnderCursor(); |
|
} |
|
updateRole(); |
|
|
|
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()); |
|
}); |
|
} |
|
|
|
XdgToplevelWindow::XdgToplevelWindow(XdgToplevelInterface *shellSurface) |
|
: XdgSurfaceWindow(shellSurface->xdgSurface()) |
|
, m_shellSurface(shellSurface) |
|
{ |
|
setOutput(workspace()->activeOutput()); |
|
setMoveResizeOutput(workspace()->activeOutput()); |
|
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, &XdgToplevelInterface::windowTitleChanged, |
|
this, &XdgToplevelWindow::handleWindowTitleChanged); |
|
connect(shellSurface, &XdgToplevelInterface::windowClassChanged, |
|
this, &XdgToplevelWindow::handleWindowClassChanged); |
|
connect(shellSurface, &XdgToplevelInterface::windowMenuRequested, |
|
this, &XdgToplevelWindow::handleWindowMenuRequested); |
|
connect(shellSurface, &XdgToplevelInterface::moveRequested, |
|
this, &XdgToplevelWindow::handleMoveRequested); |
|
connect(shellSurface, &XdgToplevelInterface::resizeRequested, |
|
this, &XdgToplevelWindow::handleResizeRequested); |
|
connect(shellSurface, &XdgToplevelInterface::maximizeRequested, |
|
this, &XdgToplevelWindow::handleMaximizeRequested); |
|
connect(shellSurface, &XdgToplevelInterface::unmaximizeRequested, |
|
this, &XdgToplevelWindow::handleUnmaximizeRequested); |
|
connect(shellSurface, &XdgToplevelInterface::fullscreenRequested, |
|
this, &XdgToplevelWindow::handleFullscreenRequested); |
|
connect(shellSurface, &XdgToplevelInterface::unfullscreenRequested, |
|
this, &XdgToplevelWindow::handleUnfullscreenRequested); |
|
connect(shellSurface, &XdgToplevelInterface::minimizeRequested, |
|
this, &XdgToplevelWindow::handleMinimizeRequested); |
|
connect(shellSurface, &XdgToplevelInterface::parentXdgToplevelChanged, |
|
this, &XdgToplevelWindow::handleTransientForChanged); |
|
connect(shellSurface, &XdgToplevelInterface::initializeRequested, |
|
this, &XdgToplevelWindow::initialize); |
|
connect(shellSurface, &XdgToplevelInterface::aboutToBeDestroyed, |
|
this, &XdgToplevelWindow::destroyWindow); |
|
connect(shellSurface, &XdgToplevelInterface::maximumSizeChanged, |
|
this, &XdgToplevelWindow::handleMaximumSizeChanged); |
|
connect(shellSurface, &XdgToplevelInterface::minimumSizeChanged, |
|
this, &XdgToplevelWindow::handleMinimumSizeChanged); |
|
connect(shellSurface->shell(), &XdgShellInterface::pingTimeout, |
|
this, &XdgToplevelWindow::handlePingTimeout); |
|
connect(shellSurface->shell(), &XdgShellInterface::pingDelayed, |
|
this, &XdgToplevelWindow::handlePingDelayed); |
|
connect(shellSurface->shell(), &XdgShellInterface::pongReceived, |
|
this, &XdgToplevelWindow::handlePongReceived); |
|
|
|
connect(waylandServer(), &WaylandServer::foreignTransientChanged, |
|
this, &XdgToplevelWindow::handleForeignTransientForChanged); |
|
} |
|
|
|
XdgToplevelWindow::~XdgToplevelWindow() |
|
{ |
|
if (m_killPrompt) { |
|
m_killPrompt->quit(); |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::handleRoleDestroyed() |
|
{ |
|
destroyWindowManagementInterface(); |
|
|
|
if (m_appMenuInterface) { |
|
m_appMenuInterface->disconnect(this); |
|
} |
|
if (m_paletteInterface) { |
|
m_paletteInterface->disconnect(this); |
|
} |
|
if (m_xdgDecoration) { |
|
m_xdgDecoration->disconnect(this); |
|
} |
|
if (m_serverDecoration) { |
|
m_serverDecoration->disconnect(this); |
|
} |
|
if (m_xdgDialog) { |
|
m_xdgDialog->disconnect(this); |
|
} |
|
|
|
m_shellSurface->disconnect(this); |
|
|
|
disconnect(waylandServer(), &WaylandServer::foreignTransientChanged, |
|
this, &XdgToplevelWindow::handleForeignTransientForChanged); |
|
|
|
XdgSurfaceWindow::handleRoleDestroyed(); |
|
} |
|
|
|
XdgToplevelInterface *XdgToplevelWindow::shellSurface() const |
|
{ |
|
return m_shellSurface; |
|
} |
|
|
|
MaximizeMode XdgToplevelWindow::maximizeMode() const |
|
{ |
|
return m_maximizeMode; |
|
} |
|
|
|
MaximizeMode XdgToplevelWindow::requestedMaximizeMode() const |
|
{ |
|
return m_requestedMaximizeMode; |
|
} |
|
|
|
QSizeF XdgToplevelWindow::minSize() const |
|
{ |
|
const int enforcedMinimum = m_nextDecoration ? 150 : 20; |
|
return rules()->checkMinSize(m_shellSurface->minimumSize()).expandedTo(QSizeF(enforcedMinimum, enforcedMinimum)); |
|
} |
|
|
|
QSizeF XdgToplevelWindow::maxSize() const |
|
{ |
|
// enforce the same minimum as for minSize, so that maxSize is always bigger than minSize |
|
const int enforcedMinimum = m_nextDecoration ? 150 : 20; |
|
return rules()->checkMaxSize(m_shellSurface->maximumSize()).expandedTo(QSizeF(enforcedMinimum, enforcedMinimum)); |
|
} |
|
|
|
bool XdgToplevelWindow::isFullScreen() const |
|
{ |
|
return m_isFullScreen; |
|
} |
|
|
|
bool XdgToplevelWindow::isRequestedFullScreen() const |
|
{ |
|
return m_isRequestedFullScreen; |
|
} |
|
|
|
bool XdgToplevelWindow::isMovable() const |
|
{ |
|
if (isRequestedFullScreen()) { |
|
return false; |
|
} |
|
if ((isSpecialWindow() && !isSplash() && !isToolbar()) || isAppletPopup()) { |
|
return false; |
|
} |
|
if (rules()->checkPosition(invalidPoint) != invalidPoint) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool XdgToplevelWindow::isMovableAcrossScreens() const |
|
{ |
|
if ((isSpecialWindow() && !isSplash() && !isToolbar()) || isAppletPopup()) { |
|
return false; |
|
} |
|
if (rules()->checkPosition(invalidPoint) != invalidPoint) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool XdgToplevelWindow::isResizable() const |
|
{ |
|
if (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 XdgToplevelWindow::isCloseable() const |
|
{ |
|
return rules()->checkCloseable(!isDesktop() && !isDock()); |
|
} |
|
|
|
bool XdgToplevelWindow::isFullScreenable() const |
|
{ |
|
if (!rules()->checkFullScreen(true)) { |
|
return false; |
|
} |
|
return !isSpecialWindow(); |
|
} |
|
|
|
bool XdgToplevelWindow::isMaximizable() const |
|
{ |
|
if (!isResizable() || isAppletPopup()) { |
|
return false; |
|
} |
|
if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || rules()->checkMaximize(MaximizeFull) != MaximizeFull) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool XdgToplevelWindow::isMinimizable() const |
|
{ |
|
if ((isSpecialWindow() && !isTransient()) || isAppletPopup()) { |
|
return false; |
|
} |
|
if (!rules()->checkMinimize(true)) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool XdgToplevelWindow::isPlaceable() const |
|
{ |
|
if (m_plasmaShellSurface) { |
|
return !m_plasmaShellSurface->isPositionSet() && !m_plasmaShellSurface->wantsOpenUnderCursor(); |
|
} |
|
return true; |
|
} |
|
|
|
bool XdgToplevelWindow::isTransient() const |
|
{ |
|
return m_isTransient; |
|
} |
|
|
|
bool XdgToplevelWindow::userCanSetNoBorder() const |
|
{ |
|
return (m_serverDecoration || m_xdgDecoration) && !isFullScreen() && !isShade(); |
|
} |
|
|
|
bool XdgToplevelWindow::noBorder() const |
|
{ |
|
return m_userNoBorder; |
|
} |
|
|
|
void XdgToplevelWindow::setNoBorder(bool set) |
|
{ |
|
set = rules()->checkNoBorder(set); |
|
if (m_userNoBorder == set) { |
|
return; |
|
} |
|
m_userNoBorder = set; |
|
configureDecoration(); |
|
updateWindowRules(Rules::NoBorder); |
|
} |
|
|
|
void XdgToplevelWindow::invalidateDecoration() |
|
{ |
|
clearDecoration(); |
|
configureDecoration(); |
|
} |
|
|
|
bool XdgToplevelWindow::supportsWindowRules() const |
|
{ |
|
return true; |
|
} |
|
|
|
void XdgToplevelWindow::applyWindowRules() |
|
{ |
|
WaylandWindow::applyWindowRules(); |
|
updateCapabilities(); |
|
} |
|
|
|
void XdgToplevelWindow::closeWindow() |
|
{ |
|
if (isCloseable()) { |
|
sendPing(PingReason::CloseWindow); |
|
m_shellSurface->sendClose(); |
|
} |
|
} |
|
|
|
XdgSurfaceConfigure *XdgToplevelWindow::sendRoleConfigure() const |
|
{ |
|
surface()->setPreferredBufferScale(preferredBufferScale()); |
|
surface()->setPreferredBufferTransform(preferredBufferTransform()); |
|
surface()->setPreferredColorDescription(preferredColorDescription()); |
|
|
|
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); |
|
|
|
XdgToplevelConfigure *configureEvent = new XdgToplevelConfigure(); |
|
configureEvent->bounds = moveResizeGeometry(); |
|
configureEvent->states = m_nextStates; |
|
configureEvent->decoration = m_nextDecoration; |
|
configureEvent->serial = serial; |
|
|
|
return configureEvent; |
|
} |
|
|
|
void XdgToplevelWindow::handleRolePrecommit() |
|
{ |
|
auto configureEvent = static_cast<XdgToplevelConfigure *>(lastAcknowledgedConfigure()); |
|
if (configureEvent && decoration() != configureEvent->decoration.get()) { |
|
if (configureEvent->decoration) { |
|
connect(configureEvent->decoration.get(), &KDecoration2::Decoration::bordersChanged, this, [this]() { |
|
if (!isDeleted()) { |
|
scheduleConfigure(); |
|
} |
|
}); |
|
} |
|
|
|
setDecoration(configureEvent->decoration); |
|
updateShadow(); |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::handleRoleCommit() |
|
{ |
|
auto configureEvent = static_cast<XdgToplevelConfigure *>(lastAcknowledgedConfigure()); |
|
if (configureEvent) { |
|
handleStatesAcknowledged(configureEvent->states); |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::doMinimize() |
|
{ |
|
if (m_isInitialized) { |
|
if (isMinimized()) { |
|
workspace()->activateNextWindow(this); |
|
} |
|
} |
|
workspace()->updateMinimizedOfTransients(this); |
|
} |
|
|
|
void XdgToplevelWindow::doInteractiveResizeSync(const QRectF &rect) |
|
{ |
|
moveResize(rect); |
|
} |
|
|
|
void XdgToplevelWindow::doSetActive() |
|
{ |
|
WaylandWindow::doSetActive(); |
|
|
|
if (isActive()) { |
|
m_nextStates |= XdgToplevelInterface::State::Activated; |
|
} else { |
|
m_nextStates &= ~XdgToplevelInterface::State::Activated; |
|
} |
|
|
|
scheduleConfigure(); |
|
} |
|
|
|
void XdgToplevelWindow::doSetFullScreen() |
|
{ |
|
if (isRequestedFullScreen()) { |
|
m_nextStates |= XdgToplevelInterface::State::FullScreen; |
|
} else { |
|
m_nextStates &= ~XdgToplevelInterface::State::FullScreen; |
|
} |
|
|
|
scheduleConfigure(); |
|
} |
|
|
|
void XdgToplevelWindow::doSetMaximized() |
|
{ |
|
if (requestedMaximizeMode() & MaximizeHorizontal) { |
|
m_nextStates |= XdgToplevelInterface::State::MaximizedHorizontal; |
|
} else { |
|
m_nextStates &= ~XdgToplevelInterface::State::MaximizedHorizontal; |
|
} |
|
|
|
if (requestedMaximizeMode() & MaximizeVertical) { |
|
m_nextStates |= XdgToplevelInterface::State::MaximizedVertical; |
|
} else { |
|
m_nextStates &= ~XdgToplevelInterface::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 XdgToplevelWindow::doSetQuickTileMode() |
|
{ |
|
const Qt::Edges anchors = anchorsForQuickTileMode(m_requestedQuickTileMode); |
|
|
|
if (anchors & Qt::LeftEdge) { |
|
m_nextStates |= XdgToplevelInterface::State::TiledLeft; |
|
} else { |
|
m_nextStates &= ~XdgToplevelInterface::State::TiledLeft; |
|
} |
|
|
|
if (anchors & Qt::RightEdge) { |
|
m_nextStates |= XdgToplevelInterface::State::TiledRight; |
|
} else { |
|
m_nextStates &= ~XdgToplevelInterface::State::TiledRight; |
|
} |
|
|
|
if (anchors & Qt::TopEdge) { |
|
m_nextStates |= XdgToplevelInterface::State::TiledTop; |
|
} else { |
|
m_nextStates &= ~XdgToplevelInterface::State::TiledTop; |
|
} |
|
|
|
if (anchors & Qt::BottomEdge) { |
|
m_nextStates |= XdgToplevelInterface::State::TiledBottom; |
|
} else { |
|
m_nextStates &= ~XdgToplevelInterface::State::TiledBottom; |
|
} |
|
|
|
scheduleConfigure(); |
|
} |
|
|
|
bool XdgToplevelWindow::doStartInteractiveMoveResize() |
|
{ |
|
if (interactiveMoveResizeGravity() != Gravity::None) { |
|
m_nextGravity = interactiveMoveResizeGravity(); |
|
m_nextStates |= XdgToplevelInterface::State::Resizing; |
|
scheduleConfigure(); |
|
} |
|
return true; |
|
} |
|
|
|
void XdgToplevelWindow::doFinishInteractiveMoveResize() |
|
{ |
|
if (m_nextStates & XdgToplevelInterface::State::Resizing) { |
|
m_nextStates &= ~XdgToplevelInterface::State::Resizing; |
|
scheduleConfigure(); |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::doSetSuspended() |
|
{ |
|
if (isSuspended()) { |
|
m_nextStates |= XdgToplevelInterface::State::Suspended; |
|
} else { |
|
m_nextStates &= ~XdgToplevelInterface::State::Suspended; |
|
} |
|
|
|
scheduleConfigure(); |
|
} |
|
|
|
void XdgToplevelWindow::doSetPreferredBufferScale() |
|
{ |
|
if (isDeleted()) { |
|
return; |
|
} |
|
if (m_isInitialized) { |
|
scheduleConfigure(); |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::doSetPreferredBufferTransform() |
|
{ |
|
if (isDeleted()) { |
|
return; |
|
} |
|
if (m_isInitialized) { |
|
scheduleConfigure(); |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::doSetPreferredColorDescription() |
|
{ |
|
if (isDeleted()) { |
|
return; |
|
} |
|
if (m_isInitialized) { |
|
scheduleConfigure(); |
|
} |
|
} |
|
|
|
bool XdgToplevelWindow::takeFocus() |
|
{ |
|
if (wantsInput()) { |
|
sendPing(PingReason::FocusWindow); |
|
setActive(true); |
|
} |
|
return true; |
|
} |
|
|
|
bool XdgToplevelWindow::wantsInput() const |
|
{ |
|
return rules()->checkAcceptFocus(acceptsFocus()); |
|
} |
|
|
|
bool XdgToplevelWindow::dockWantsInput() const |
|
{ |
|
if (m_plasmaShellSurface) { |
|
if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) { |
|
return m_plasmaShellSurface->panelTakesFocus(); |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
bool XdgToplevelWindow::acceptsFocus() const |
|
{ |
|
if (m_plasmaShellSurface) { |
|
if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || 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; |
|
} |
|
} |
|
return !isDeleted() && readyForPainting(); |
|
} |
|
|
|
void XdgToplevelWindow::handleWindowTitleChanged() |
|
{ |
|
setCaption(m_shellSurface->windowTitle()); |
|
} |
|
|
|
void XdgToplevelWindow::handleWindowClassChanged() |
|
{ |
|
const QString applicationId = m_shellSurface->windowClass(); |
|
setResourceClass(resourceName(), applicationId); |
|
if (shellSurface()->isConfigured()) { |
|
evaluateWindowRules(); |
|
} |
|
setDesktopFileName(applicationId); |
|
} |
|
|
|
void XdgToplevelWindow::handleWindowMenuRequested(SeatInterface *seat, const QPoint &surfacePos, |
|
quint32 serial) |
|
{ |
|
performMouseCommand(Options::MouseOperationsMenu, mapFromLocal(surfacePos)); |
|
} |
|
|
|
void XdgToplevelWindow::handleMoveRequested(SeatInterface *seat, quint32 serial) |
|
{ |
|
if (!seat->hasImplicitPointerGrab(serial) && !seat->hasImplicitTouchGrab(serial) |
|
&& !waylandServer()->tabletManagerV2()->seat(seat)->hasImplicitGrab(serial)) { |
|
return; |
|
} |
|
if (isMovable()) { |
|
QPointF cursorPos; |
|
if (seat->hasImplicitPointerGrab(serial)) { |
|
cursorPos = input()->pointer()->pos(); |
|
} else if (seat->hasImplicitTouchGrab(serial)) { |
|
cursorPos = input()->touch()->position(); |
|
} else { |
|
cursorPos = input()->tablet()->position(); |
|
} |
|
performMouseCommand(Options::MouseMove, cursorPos); |
|
} else { |
|
qCDebug(KWIN_CORE) << this << "is immovable, ignoring the move request"; |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::handleResizeRequested(SeatInterface *seat, XdgToplevelInterface::ResizeAnchor anchor, quint32 serial) |
|
{ |
|
if (!seat->hasImplicitPointerGrab(serial) && !seat->hasImplicitTouchGrab(serial) |
|
&& !waylandServer()->tabletManagerV2()->seat(seat)->hasImplicitGrab(serial)) { |
|
return; |
|
} |
|
if (!isResizable() || isShade()) { |
|
return; |
|
} |
|
if (isInteractiveMoveResize()) { |
|
finishInteractiveMoveResize(false); |
|
} |
|
setInteractiveMoveResizePointerButtonDown(true); |
|
QPointF cursorPos; |
|
if (seat->hasImplicitPointerGrab(serial)) { |
|
cursorPos = input()->pointer()->pos(); |
|
} else if (seat->hasImplicitTouchGrab(serial)) { |
|
cursorPos = input()->touch()->position(); |
|
} else { |
|
cursorPos = input()->tablet()->position(); |
|
} |
|
setInteractiveMoveResizeAnchor(cursorPos); |
|
setInteractiveMoveOffset(QPointF((cursorPos.x() - x()) / width(), (cursorPos.y() - y()) / height())); // map from global |
|
setUnrestrictedInteractiveMoveResize(false); |
|
Gravity gravity; |
|
switch (anchor) { |
|
case XdgToplevelInterface::ResizeAnchor::TopLeft: |
|
gravity = Gravity::TopLeft; |
|
break; |
|
case XdgToplevelInterface::ResizeAnchor::Top: |
|
gravity = Gravity::Top; |
|
break; |
|
case XdgToplevelInterface::ResizeAnchor::TopRight: |
|
gravity = Gravity::TopRight; |
|
break; |
|
case XdgToplevelInterface::ResizeAnchor::Right: |
|
gravity = Gravity::Right; |
|
break; |
|
case XdgToplevelInterface::ResizeAnchor::BottomRight: |
|
gravity = Gravity::BottomRight; |
|
break; |
|
case XdgToplevelInterface::ResizeAnchor::Bottom: |
|
gravity = Gravity::Bottom; |
|
break; |
|
case XdgToplevelInterface::ResizeAnchor::BottomLeft: |
|
gravity = Gravity::BottomLeft; |
|
break; |
|
case XdgToplevelInterface::ResizeAnchor::Left: |
|
gravity = Gravity::Left; |
|
break; |
|
default: |
|
gravity = Gravity::None; |
|
break; |
|
} |
|
setInteractiveMoveResizeGravity(gravity); |
|
if (!startInteractiveMoveResize()) { |
|
setInteractiveMoveResizePointerButtonDown(false); |
|
} |
|
updateCursor(); |
|
} |
|
|
|
void XdgToplevelWindow::handleStatesAcknowledged(const XdgToplevelInterface::States &states) |
|
{ |
|
const XdgToplevelInterface::States delta = m_acknowledgedStates ^ states; |
|
|
|
if (delta & XdgToplevelInterface::State::Maximized) { |
|
MaximizeMode maximizeMode = MaximizeRestore; |
|
if (states & XdgToplevelInterface::State::MaximizedHorizontal) { |
|
maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal); |
|
} |
|
if (states & XdgToplevelInterface::State::MaximizedVertical) { |
|
maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical); |
|
} |
|
updateMaximizeMode(maximizeMode); |
|
} |
|
if (delta & XdgToplevelInterface::State::FullScreen) { |
|
updateFullScreenMode(states & XdgToplevelInterface::State::FullScreen); |
|
} |
|
|
|
if (delta & (XdgToplevelInterface::State::TiledLeft | XdgToplevelInterface::State::TiledTop | XdgToplevelInterface::State::TiledRight | XdgToplevelInterface::State::TiledBottom)) { |
|
QuickTileMode newTileMode = QuickTileFlag::None; |
|
if (states & XdgToplevelInterface::State::TiledLeft) { |
|
newTileMode |= QuickTileFlag::Left; |
|
} |
|
if (states & XdgToplevelInterface::State::TiledTop) { |
|
newTileMode |= QuickTileFlag::Top; |
|
} |
|
if (states & XdgToplevelInterface::State::TiledRight) { |
|
newTileMode |= QuickTileFlag::Right; |
|
} |
|
if (states & XdgToplevelInterface::State::TiledBottom) { |
|
newTileMode |= QuickTileFlag::Bottom; |
|
} |
|
|
|
if (newTileMode != quickTileMode()) { |
|
setTile(workspace()->tileManager(output())->quickTile(newTileMode)); |
|
} |
|
} |
|
|
|
m_acknowledgedStates = states; |
|
} |
|
|
|
void XdgToplevelWindow::handleMaximizeRequested() |
|
{ |
|
if (m_isInitialized) { |
|
maximize(MaximizeFull); |
|
scheduleConfigure(); |
|
} else { |
|
m_initialStates |= XdgToplevelInterface::State::Maximized; |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::handleUnmaximizeRequested() |
|
{ |
|
if (m_isInitialized) { |
|
maximize(MaximizeRestore); |
|
scheduleConfigure(); |
|
} else { |
|
m_initialStates &= ~XdgToplevelInterface::State::Maximized; |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::handleFullscreenRequested(OutputInterface *output) |
|
{ |
|
m_fullScreenRequestedOutput = output ? output->handle() : nullptr; |
|
if (m_isInitialized) { |
|
setFullScreen(true); |
|
scheduleConfigure(); |
|
} else { |
|
m_initialStates |= XdgToplevelInterface::State::FullScreen; |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::handleUnfullscreenRequested() |
|
{ |
|
m_fullScreenRequestedOutput.clear(); |
|
if (m_isInitialized) { |
|
setFullScreen(false); |
|
scheduleConfigure(); |
|
} else { |
|
m_initialStates &= ~XdgToplevelInterface::State::FullScreen; |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::handleMinimizeRequested() |
|
{ |
|
setMinimized(true); |
|
} |
|
|
|
void XdgToplevelWindow::handleTransientForChanged() |
|
{ |
|
SurfaceInterface *transientForSurface = nullptr; |
|
if (XdgToplevelInterface *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 XdgToplevelWindow::handleForeignTransientForChanged(SurfaceInterface *child) |
|
{ |
|
if (surface() == child) { |
|
handleTransientForChanged(); |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::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(); |
|
|
|
if (!m_killPrompt) { |
|
m_killPrompt = std::make_unique<KillPrompt>(this); |
|
} |
|
if (!m_killPrompt->isRunning()) { |
|
m_killPrompt->start(); |
|
} |
|
} |
|
m_pings.erase(pingIt); |
|
} |
|
|
|
void XdgToplevelWindow::handlePingDelayed(quint32 serial) |
|
{ |
|
auto it = m_pings.find(serial); |
|
if (it != m_pings.end()) { |
|
qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); |
|
setUnresponsive(true); |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::handlePongReceived(quint32 serial) |
|
{ |
|
if (m_pings.remove(serial)) { |
|
setUnresponsive(false); |
|
if (m_killPrompt) { |
|
m_killPrompt->quit(); |
|
} |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::handleMaximumSizeChanged() |
|
{ |
|
updateCapabilities(); |
|
Q_EMIT maximizeableChanged(isMaximizable()); |
|
} |
|
|
|
void XdgToplevelWindow::handleMinimumSizeChanged() |
|
{ |
|
updateCapabilities(); |
|
Q_EMIT maximizeableChanged(isMaximizable()); |
|
} |
|
|
|
void XdgToplevelWindow::sendPing(PingReason reason) |
|
{ |
|
XdgShellInterface *shell = m_shellSurface->shell(); |
|
XdgSurfaceInterface *surface = m_shellSurface->xdgSurface(); |
|
|
|
const quint32 serial = shell->ping(surface); |
|
m_pings.insert(serial, reason); |
|
} |
|
|
|
MaximizeMode XdgToplevelWindow::initialMaximizeMode() const |
|
{ |
|
MaximizeMode maximizeMode = MaximizeRestore; |
|
if (m_initialStates & XdgToplevelInterface::State::MaximizedHorizontal) { |
|
maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal); |
|
} |
|
if (m_initialStates & XdgToplevelInterface::State::MaximizedVertical) { |
|
maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical); |
|
} |
|
return maximizeMode; |
|
} |
|
|
|
bool XdgToplevelWindow::initialFullScreenMode() const |
|
{ |
|
return m_initialStates & XdgToplevelInterface::State::FullScreen; |
|
} |
|
|
|
void XdgToplevelWindow::initialize() |
|
{ |
|
bool needsPlacement = isPlaceable(); |
|
setupWindowRules(); |
|
|
|
// 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)); |
|
setOnActivities(rules()->checkActivity(activities(), true)); |
|
setDesktops(rules()->checkDesktops(desktops(), true)); |
|
setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true)); |
|
setMinimized(rules()->checkMinimize(isMinimized(), true)); |
|
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; |
|
} |
|
|
|
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); |
|
} |
|
|
|
configureDecoration(); |
|
scheduleConfigure(); |
|
updateColorScheme(); |
|
updateCapabilities(); |
|
setupWindowManagementInterface(); |
|
|
|
m_isInitialized = true; |
|
} |
|
|
|
void XdgToplevelWindow::updateMaximizeMode(MaximizeMode maximizeMode) |
|
{ |
|
if (m_maximizeMode == maximizeMode) { |
|
return; |
|
} |
|
m_maximizeMode = maximizeMode; |
|
updateWindowRules(Rules::MaximizeVert | Rules::MaximizeHoriz); |
|
Q_EMIT maximizedChanged(); |
|
} |
|
|
|
void XdgToplevelWindow::updateFullScreenMode(bool set) |
|
{ |
|
if (m_isFullScreen == set) { |
|
return; |
|
} |
|
StackingUpdatesBlocker blocker1(workspace()); |
|
m_isFullScreen = set; |
|
updateLayer(); |
|
updateWindowRules(Rules::Fullscreen); |
|
Q_EMIT fullScreenChanged(); |
|
} |
|
|
|
void XdgToplevelWindow::updateCapabilities() |
|
{ |
|
XdgToplevelInterface::Capabilities caps = XdgToplevelInterface::Capability::WindowMenu; |
|
|
|
if (isMaximizable()) { |
|
caps.setFlag(XdgToplevelInterface::Capability::Maximize); |
|
} |
|
if (isFullScreenable()) { |
|
caps.setFlag(XdgToplevelInterface::Capability::FullScreen); |
|
} |
|
if (isMinimizable()) { |
|
caps.setFlag(XdgToplevelInterface::Capability::Minimize); |
|
} |
|
|
|
if (m_capabilities != caps) { |
|
m_capabilities = caps; |
|
m_shellSurface->sendWmCapabilities(caps); |
|
} |
|
} |
|
|
|
QString XdgToplevelWindow::preferredColorScheme() const |
|
{ |
|
if (m_paletteInterface) { |
|
return rules()->checkDecoColor(m_paletteInterface->palette()); |
|
} |
|
return rules()->checkDecoColor(QString()); |
|
} |
|
|
|
void XdgToplevelWindow::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()); |
|
} |
|
|
|
XdgToplevelWindow::DecorationMode XdgToplevelWindow::preferredDecorationMode() const |
|
{ |
|
if (!Decoration::DecorationBridge::hasPlugin()) { |
|
return DecorationMode::Client; |
|
} else if (m_userNoBorder || isRequestedFullScreen()) { |
|
return DecorationMode::None; |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
return DecorationMode::Client; |
|
} |
|
|
|
void XdgToplevelWindow::clearDecoration() |
|
{ |
|
m_nextDecoration = nullptr; |
|
} |
|
|
|
void XdgToplevelWindow::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; |
|
} |
|
|
|
// All decoration updates are synchronized to toplevel configure events. |
|
if (m_xdgDecoration) { |
|
configureXdgDecoration(decorationMode); |
|
} else if (m_serverDecoration) { |
|
configureServerDecoration(decorationMode); |
|
} |
|
} |
|
|
|
void XdgToplevelWindow::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 XdgToplevelWindow::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 XdgToplevelWindow::installXdgDecoration(XdgToplevelDecorationV1Interface *decoration) |
|
{ |
|
m_xdgDecoration = decoration; |
|
|
|
connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::destroyed, |
|
this, &XdgToplevelWindow::clearDecoration); |
|
connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::preferredModeChanged, this, [this] { |
|
if (m_isInitialized) { |
|
configureDecoration(); |
|
} |
|
}); |
|
} |
|
|
|
void XdgToplevelWindow::installServerDecoration(ServerSideDecorationInterface *decoration) |
|
{ |
|
m_serverDecoration = decoration; |
|
if (m_isInitialized) { |
|
configureDecoration(); |
|
} |
|
|
|
connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, |
|
this, &XdgToplevelWindow::clearDecoration); |
|
connect(m_serverDecoration, &ServerSideDecorationInterface::preferredModeChanged, this, [this]() { |
|
if (m_isInitialized) { |
|
configureDecoration(); |
|
} |
|
}); |
|
} |
|
|
|
void XdgToplevelWindow::installPalette(ServerSideDecorationPaletteInterface *palette) |
|
{ |
|
m_paletteInterface = palette; |
|
|
|
connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged, |
|
this, &XdgToplevelWindow::updateColorScheme); |
|
connect(m_paletteInterface, &QObject::destroyed, |
|
this, &XdgToplevelWindow::updateColorScheme); |
|
updateColorScheme(); |
|
} |
|
|
|
void XdgToplevelWindow::installXdgDialogV1(XdgDialogV1Interface *dialog) |
|
{ |
|
m_xdgDialog = dialog; |
|
|
|
connect(dialog, &XdgDialogV1Interface::modalChanged, this, &Window::setModal); |
|
connect(dialog, &QObject::destroyed, this, [this] { |
|
setModal(false); |
|
}); |
|
setModal(dialog->isModal()); |
|
} |
|
|
|
void XdgToplevelWindow::setFullScreen(bool set) |
|
{ |
|
if (!isFullScreenable()) { |
|
return; |
|
} |
|
|
|
set = rules()->checkFullScreen(set); |
|
if (m_isRequestedFullScreen == set) { |
|
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 XdgToplevelWindow::maximize(MaximizeMode mode) |
|
{ |
|
if (changeMaximizeRecursion) { |
|
return; |
|
} |
|
|
|
if (!isResizable() || isAppletPopup()) { |
|
return; |
|
} |
|
|
|
const QRectF clientArea = isElectricBorderMaximizing() ? workspace()->clientArea(MaximizeArea, this, interactiveMoveResizeAnchor()) : workspace()->clientArea(MaximizeArea, this, moveResizeOutput()); |
|
|
|
const MaximizeMode oldMode = m_requestedMaximizeMode; |
|
const QRectF oldGeometry = moveResizeGeometry(); |
|
|
|
mode = rules()->checkMaximize(mode); |
|
if (m_requestedMaximizeMode == mode) { |
|
return; |
|
} |
|
|
|
const auto oldQuickTileMode = requestedQuickTileMode(); |
|
Q_EMIT maximizedAboutToChange(mode); |
|
m_requestedMaximizeMode = mode; |
|
|
|
// call into decoration update borders |
|
if (m_nextDecoration && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == MaximizeFull)) { |
|
changeMaximizeRecursion = true; |
|
const auto c = m_nextDecoration->client(); |
|
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 (oldQuickTileMode == 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); |
|
} |
|
|
|
QRectF geometry = oldGeometry; |
|
|
|
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 (oldMode & MaximizeHorizontal) { |
|
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 (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 (oldMode & MaximizeVertical) { |
|
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); |
|
} |
|
} |
|
|
|
if (m_requestedMaximizeMode == MaximizeFull) { |
|
if (options->electricBorderMaximize()) { |
|
updateQuickTileMode(QuickTileFlag::Maximize); |
|
} else { |
|
updateQuickTileMode(QuickTileFlag::None); |
|
} |
|
} else { |
|
updateQuickTileMode(QuickTileFlag::None); |
|
} |
|
|
|
moveResize(geometry); |
|
|
|
doSetMaximized(); |
|
} |
|
|
|
XdgPopupWindow::XdgPopupWindow(XdgPopupInterface *shellSurface) |
|
: XdgSurfaceWindow(shellSurface->xdgSurface()) |
|
, m_shellSurface(shellSurface) |
|
{ |
|
setOutput(workspace()->activeOutput()); |
|
setMoveResizeOutput(workspace()->activeOutput()); |
|
|
|
m_windowType = WindowType::Unknown; |
|
|
|
connect(shellSurface, &XdgPopupInterface::grabRequested, |
|
this, &XdgPopupWindow::handleGrabRequested); |
|
connect(shellSurface, &XdgPopupInterface::initializeRequested, |
|
this, &XdgPopupWindow::initialize); |
|
connect(shellSurface, &XdgPopupInterface::repositionRequested, |
|
this, &XdgPopupWindow::handleRepositionRequested); |
|
connect(shellSurface, &XdgPopupInterface::aboutToBeDestroyed, |
|
this, &XdgPopupWindow::destroyWindow); |
|
} |
|
|
|
void XdgPopupWindow::handleRoleDestroyed() |
|
{ |
|
disconnect(transientFor(), &Window::frameGeometryChanged, |
|
this, &XdgPopupWindow::relayout); |
|
m_shellSurface->disconnect(this); |
|
|
|
XdgSurfaceWindow::handleRoleDestroyed(); |
|
} |
|
|
|
void XdgPopupWindow::handleRepositionRequested(quint32 token) |
|
{ |
|
updateRelativePlacement(); |
|
m_shellSurface->sendRepositioned(token); |
|
relayout(); |
|
} |
|
|
|
void XdgPopupWindow::updateRelativePlacement() |
|
{ |
|
const QPointF parentPosition = transientFor()->framePosToClientPos(transientFor()->pos()); |
|
const QRectF bounds = workspace()->clientArea(transientFor()->isFullScreen() ? FullScreenArea : PlacementArea, transientFor()).translated(-parentPosition); |
|
const XdgPositioner positioner = m_shellSurface->positioner(); |
|
|
|
if (m_plasmaShellSurface && m_plasmaShellSurface->isPositionSet()) { |
|
m_relativePlacement = QRectF(m_plasmaShellSurface->position(), positioner.size()).translated(-parentPosition); |
|
} else { |
|
m_relativePlacement = positioner.placement(bounds); |
|
} |
|
} |
|
|
|
void XdgPopupWindow::relayout() |
|
{ |
|
if (m_shellSurface->positioner().isReactive()) { |
|
updateRelativePlacement(); |
|
} |
|
workspace()->placement()->place(this, QRectF()); |
|
scheduleConfigure(); |
|
} |
|
|
|
XdgPopupWindow::~XdgPopupWindow() |
|
{ |
|
} |
|
|
|
bool XdgPopupWindow::hasPopupGrab() const |
|
{ |
|
return m_haveExplicitGrab; |
|
} |
|
|
|
void XdgPopupWindow::popupDone() |
|
{ |
|
m_shellSurface->sendPopupDone(); |
|
} |
|
|
|
bool XdgPopupWindow::isPopupWindow() const |
|
{ |
|
return true; |
|
} |
|
|
|
bool XdgPopupWindow::isTransient() const |
|
{ |
|
return true; |
|
} |
|
|
|
bool XdgPopupWindow::isResizable() const |
|
{ |
|
return false; |
|
} |
|
|
|
bool XdgPopupWindow::isMovable() const |
|
{ |
|
return false; |
|
} |
|
|
|
bool XdgPopupWindow::isMovableAcrossScreens() const |
|
{ |
|
return false; |
|
} |
|
|
|
bool XdgPopupWindow::hasTransientPlacementHint() const |
|
{ |
|
return true; |
|
} |
|
|
|
QRectF XdgPopupWindow::transientPlacement() const |
|
{ |
|
const QPointF parentPosition = transientFor()->framePosToClientPos(transientFor()->pos()); |
|
return m_relativePlacement.translated(parentPosition); |
|
} |
|
|
|
bool XdgPopupWindow::isCloseable() const |
|
{ |
|
return false; |
|
} |
|
|
|
void XdgPopupWindow::closeWindow() |
|
{ |
|
} |
|
|
|
bool XdgPopupWindow::wantsInput() const |
|
{ |
|
return false; |
|
} |
|
|
|
bool XdgPopupWindow::takeFocus() |
|
{ |
|
return false; |
|
} |
|
|
|
bool XdgPopupWindow::acceptsFocus() const |
|
{ |
|
return false; |
|
} |
|
|
|
XdgSurfaceConfigure *XdgPopupWindow::sendRoleConfigure() const |
|
{ |
|
surface()->setPreferredBufferScale(preferredBufferScale()); |
|
surface()->setPreferredBufferTransform(preferredBufferTransform()); |
|
surface()->setPreferredColorDescription(preferredColorDescription()); |
|
|
|
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())); |
|
|
|
XdgSurfaceConfigure *configureEvent = new XdgSurfaceConfigure(); |
|
configureEvent->bounds = moveResizeGeometry(); |
|
configureEvent->serial = serial; |
|
|
|
return configureEvent; |
|
} |
|
|
|
void XdgPopupWindow::handleGrabRequested(SeatInterface *seat, quint32 serial) |
|
{ |
|
m_haveExplicitGrab = true; |
|
} |
|
|
|
void XdgPopupWindow::initialize() |
|
{ |
|
Window *parent = waylandServer()->findWindow(m_shellSurface->parentSurface()); |
|
parent->addTransient(this); |
|
setTransientFor(parent); |
|
setDesktops(parent->desktops()); |
|
#if KWIN_BUILD_ACTIVITIES |
|
setOnActivities(parent->activities()); |
|
#endif |
|
|
|
updateRelativePlacement(); |
|
connect(parent, &Window::frameGeometryChanged, this, &XdgPopupWindow::relayout); |
|
|
|
workspace()->placement()->place(this, QRectF()); |
|
scheduleConfigure(); |
|
} |
|
|
|
void XdgPopupWindow::doSetPreferredBufferScale() |
|
{ |
|
if (isDeleted()) { |
|
return; |
|
} |
|
if (m_shellSurface->isConfigured()) { |
|
scheduleConfigure(); |
|
} |
|
} |
|
|
|
void XdgPopupWindow::doSetPreferredBufferTransform() |
|
{ |
|
if (isDeleted()) { |
|
return; |
|
} |
|
if (m_shellSurface->isConfigured()) { |
|
scheduleConfigure(); |
|
} |
|
} |
|
|
|
void XdgPopupWindow::doSetPreferredColorDescription() |
|
{ |
|
if (isDeleted()) { |
|
return; |
|
} |
|
if (m_shellSurface->isConfigured()) { |
|
scheduleConfigure(); |
|
} |
|
} |
|
|
|
} // namespace KWin |
|
|
|
#include "moc_xdgshellwindow.cpp"
|
|
|