c29d6093ba
Summary: Moves most of the implementation from Client to AbstractClient, so that it can be used for both Client and ShellClient. Only the X11 specific code is kept in Client. Not yet implemented is updating the window caption. Unfortunately the testing of this feature showed that setting a window shortcut is not working on Wayland at all (the Qt widget doesn't properly catch the shortcut). So this feature is currently only of erm theoretical use. Test Plan: Added new test case. No testing in real world as explained. Reviewers: #kwin, #plasma Subscribers: plasma-devel, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D6818
1514 lines
46 KiB
C++
1514 lines
46 KiB
C++
/********************************************************************
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*********************************************************************/
|
|
#include "shell_client.h"
|
|
#include "composite.h"
|
|
#include "cursor.h"
|
|
#include "deleted.h"
|
|
#include "placement.h"
|
|
#include "screenedge.h"
|
|
#include "screens.h"
|
|
#include "wayland_server.h"
|
|
#include "workspace.h"
|
|
#include "virtualdesktops.h"
|
|
#include "workspace.h"
|
|
#include "decorations/decorationbridge.h"
|
|
#include "decorations/decoratedclient.h"
|
|
#include <KDecoration2/Decoration>
|
|
#include <KDecoration2/DecoratedClient>
|
|
|
|
#include <KWayland/Client/surface.h>
|
|
#include <KWayland/Server/clientconnection.h>
|
|
#include <KWayland/Server/seat_interface.h>
|
|
#include <KWayland/Server/shell_interface.h>
|
|
#include <KWayland/Server/surface_interface.h>
|
|
#include <KWayland/Server/buffer_interface.h>
|
|
#include <KWayland/Server/plasmashell_interface.h>
|
|
#include <KWayland/Server/shadow_interface.h>
|
|
#include <KWayland/Server/server_decoration_interface.h>
|
|
#include <KWayland/Server/qtsurfaceextension_interface.h>
|
|
#include <KWayland/Server/plasmawindowmanagement_interface.h>
|
|
#include <KDesktopFile>
|
|
|
|
#include <QOpenGLFramebufferObject>
|
|
#include <QWindow>
|
|
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
|
|
using namespace KWayland::Server;
|
|
|
|
static const QByteArray s_schemePropertyName = QByteArrayLiteral("KDE_COLOR_SCHEME_PATH");
|
|
static const QByteArray s_appMenuServiceNamePropertyName = QByteArrayLiteral("KDE_APPMENU_SERVICE_NAME");
|
|
static const QByteArray s_appMenuObjectPathPropertyName = QByteArrayLiteral("KDE_APPMENU_OBJECT_PATH");
|
|
static const QByteArray s_skipClosePropertyName = QByteArrayLiteral("KWIN_SKIP_CLOSE_ANIMATION");
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
ShellClient::ShellClient(ShellSurfaceInterface *surface)
|
|
: AbstractClient()
|
|
, m_shellSurface(surface)
|
|
, m_xdgShellSurface(nullptr)
|
|
, m_xdgShellPopup(nullptr)
|
|
, m_internal(surface->client() == waylandServer()->internalConnection())
|
|
{
|
|
setSurface(surface->surface());
|
|
init();
|
|
}
|
|
|
|
ShellClient::ShellClient(XdgShellSurfaceInterface *surface)
|
|
: AbstractClient()
|
|
, m_shellSurface(nullptr)
|
|
, m_xdgShellSurface(surface)
|
|
, m_xdgShellPopup(nullptr)
|
|
, m_internal(surface->client() == waylandServer()->internalConnection())
|
|
{
|
|
setSurface(surface->surface());
|
|
init();
|
|
}
|
|
|
|
ShellClient::ShellClient(XdgShellPopupInterface *surface)
|
|
: AbstractClient()
|
|
, m_shellSurface(nullptr)
|
|
, m_xdgShellSurface(nullptr)
|
|
, m_xdgShellPopup(surface)
|
|
, m_internal(surface->client() == waylandServer()->internalConnection())
|
|
{
|
|
setSurface(surface->surface());
|
|
init();
|
|
}
|
|
|
|
ShellClient::~ShellClient() = default;
|
|
|
|
template <class T>
|
|
void ShellClient::initSurface(T *shellSurface)
|
|
{
|
|
m_caption = shellSurface->title().simplified();
|
|
connect(shellSurface, &T::titleChanged, this, &ShellClient::captionChanged);
|
|
connect(shellSurface, &T::destroyed, this, &ShellClient::destroyClient);
|
|
connect(shellSurface, &T::titleChanged, this,
|
|
[this] (const QString &s) {
|
|
m_caption = s.simplified();
|
|
emit captionChanged();
|
|
}
|
|
);
|
|
connect(shellSurface, &T::moveRequested, this,
|
|
[this] {
|
|
// TODO: check the seat and serial
|
|
performMouseCommand(Options::MouseMove, Cursor::pos());
|
|
}
|
|
);
|
|
|
|
setResourceClass(shellSurface->windowClass());
|
|
setDesktopFileName(shellSurface->windowClass());
|
|
connect(shellSurface, &T::windowClassChanged, this,
|
|
[this] (const QByteArray &windowClass) {
|
|
setResourceClass(windowClass);
|
|
setDesktopFileName(windowClass);
|
|
}
|
|
);
|
|
connect(shellSurface, &T::resizeRequested, this,
|
|
[this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) {
|
|
// TODO: check the seat and serial
|
|
Q_UNUSED(seat)
|
|
Q_UNUSED(serial)
|
|
if (!isResizable() || isShade()) {
|
|
return;
|
|
}
|
|
if (isMoveResize()) {
|
|
finishMoveResize(false);
|
|
}
|
|
setMoveResizePointerButtonDown(true);
|
|
setMoveOffset(Cursor::pos() - pos()); // map from global
|
|
setInvertedMoveOffset(rect().bottomRight() - moveOffset());
|
|
setUnrestrictedMoveResize(false);
|
|
auto toPosition = [edges] {
|
|
Position pos = PositionCenter;
|
|
if (edges.testFlag(Qt::TopEdge)) {
|
|
pos = PositionTop;
|
|
} else if (edges.testFlag(Qt::BottomEdge)) {
|
|
pos = PositionBottom;
|
|
}
|
|
if (edges.testFlag(Qt::LeftEdge)) {
|
|
pos = Position(pos | PositionLeft);
|
|
} else if (edges.testFlag(Qt::RightEdge)) {
|
|
pos = Position(pos | PositionRight);
|
|
}
|
|
return pos;
|
|
};
|
|
setMoveResizePointerMode(toPosition());
|
|
if (!startMoveResize())
|
|
setMoveResizePointerButtonDown(false);
|
|
updateCursor();
|
|
}
|
|
);
|
|
connect(shellSurface, &T::maximizedChanged, this,
|
|
[this] (bool maximized) {
|
|
if (m_shellSurface && isFullScreen()) {
|
|
// ignore for wl_shell - there it is mutual exclusive and messes with the geometry
|
|
return;
|
|
}
|
|
maximize(maximized ? MaximizeFull : MaximizeRestore);
|
|
}
|
|
);
|
|
// TODO: consider output!
|
|
connect(shellSurface, &T::fullscreenChanged, this, &ShellClient::clientFullScreenChanged);
|
|
|
|
connect(shellSurface, &T::transientForChanged, this, &ShellClient::setTransient);
|
|
}
|
|
|
|
void ShellClient::init()
|
|
{
|
|
connect(this, &ShellClient::desktopFileNameChanged, this, &ShellClient::updateIcon);
|
|
findInternalWindow();
|
|
createWindowId();
|
|
setupCompositing();
|
|
SurfaceInterface *s = surface();
|
|
Q_ASSERT(s);
|
|
if (s->buffer()) {
|
|
setReadyForPainting();
|
|
if (shouldExposeToWindowManagement()) {
|
|
setupWindowManagementInterface();
|
|
}
|
|
m_unmapped = false;
|
|
m_clientSize = s->size();
|
|
} else {
|
|
ready_for_painting = false;
|
|
}
|
|
if (m_internalWindow) {
|
|
updateInternalWindowGeometry();
|
|
setOnAllDesktops(true);
|
|
updateDecoration(true);
|
|
} else {
|
|
doSetGeometry(QRect(QPoint(0, 0), m_clientSize));
|
|
setDesktop(VirtualDesktopManager::self()->current());
|
|
}
|
|
if (waylandServer()->inputMethodConnection() == s->client()) {
|
|
m_windowType = NET::OnScreenDisplay;
|
|
}
|
|
|
|
connect(s, &SurfaceInterface::sizeChanged, this,
|
|
[this] {
|
|
m_clientSize = surface()->size();
|
|
doSetGeometry(QRect(geom.topLeft(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom())));
|
|
}
|
|
);
|
|
connect(s, &SurfaceInterface::unmapped, this, &ShellClient::unmap);
|
|
connect(s, &SurfaceInterface::unbound, this, &ShellClient::destroyClient);
|
|
connect(s, &SurfaceInterface::destroyed, this, &ShellClient::destroyClient);
|
|
if (m_shellSurface) {
|
|
initSurface(m_shellSurface);
|
|
} else if (m_xdgShellSurface) {
|
|
initSurface(m_xdgShellSurface);
|
|
connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowMenuRequested, this,
|
|
[this] (SeatInterface *seat, quint32 serial, const QPoint &surfacePos) {
|
|
// TODO: check serial on seat
|
|
Q_UNUSED(seat)
|
|
Q_UNUSED(serial)
|
|
performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos);
|
|
}
|
|
);
|
|
connect(m_xdgShellSurface, &XdgShellSurfaceInterface::minimizeRequested, this,
|
|
[this] {
|
|
performMouseCommand(Options::MouseMinimize, Cursor::pos());
|
|
}
|
|
);
|
|
auto configure = [this] {
|
|
if (m_closing) {
|
|
return;
|
|
}
|
|
m_xdgShellSurface->configure(xdgSurfaceStates());
|
|
};
|
|
connect(this, &AbstractClient::activeChanged, this, configure);
|
|
connect(this, &AbstractClient::clientStartUserMovedResized, this, configure);
|
|
connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure);
|
|
} else if (m_xdgShellPopup) {
|
|
connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &ShellClient::destroyClient);
|
|
}
|
|
|
|
// setup shadow integration
|
|
getShadow();
|
|
connect(s, &SurfaceInterface::shadowChanged, this, &Toplevel::getShadow);
|
|
|
|
setTransient();
|
|
// check whether we have a ServerSideDecoration
|
|
if (ServerSideDecorationInterface *deco = ServerSideDecorationInterface::get(s)) {
|
|
installServerSideDecoration(deco);
|
|
}
|
|
|
|
updateColorScheme(QString());
|
|
updateApplicationMenu();
|
|
}
|
|
|
|
void ShellClient::destroyClient()
|
|
{
|
|
m_closing = true;
|
|
Deleted *del = nullptr;
|
|
if (workspace()) {
|
|
del = Deleted::create(this);
|
|
}
|
|
emit windowClosed(this, del);
|
|
destroyWindowManagementInterface();
|
|
destroyDecoration();
|
|
|
|
if (workspace()) {
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
if (transientFor()) {
|
|
transientFor()->removeTransient(this);
|
|
}
|
|
for (auto it = transients().constBegin(); it != transients().constEnd();) {
|
|
if ((*it)->transientFor() == this) {
|
|
removeTransient(*it);
|
|
it = transients().constBegin(); // restart, just in case something more has changed with the list
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
waylandServer()->removeClient(this);
|
|
|
|
if (del) {
|
|
del->unrefWindow();
|
|
}
|
|
m_shellSurface = nullptr;
|
|
m_xdgShellSurface = nullptr;
|
|
m_xdgShellPopup = nullptr;
|
|
deleteClient(this);
|
|
}
|
|
|
|
void ShellClient::deleteClient(ShellClient *c)
|
|
{
|
|
delete c;
|
|
}
|
|
|
|
QStringList ShellClient::activities() const
|
|
{
|
|
// TODO: implement
|
|
return QStringList();
|
|
}
|
|
|
|
QPoint ShellClient::clientContentPos() const
|
|
{
|
|
return -1 * clientPos();
|
|
}
|
|
|
|
QSize ShellClient::clientSize() const
|
|
{
|
|
return m_clientSize;
|
|
}
|
|
|
|
void ShellClient::debug(QDebug &stream) const
|
|
{
|
|
// TODO: implement
|
|
Q_UNUSED(stream)
|
|
}
|
|
|
|
Layer ShellClient::layerForDock() const
|
|
{
|
|
if (m_plasmaShellSurface) {
|
|
switch (m_plasmaShellSurface->panelBehavior()) {
|
|
case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover:
|
|
return NormalLayer;
|
|
case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide:
|
|
return AboveLayer;
|
|
case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow:
|
|
case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible:
|
|
return DockLayer;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
return AbstractClient::layerForDock();
|
|
}
|
|
|
|
QRect ShellClient::transparentRect() const
|
|
{
|
|
// TODO: implement
|
|
return QRect();
|
|
}
|
|
|
|
NET::WindowType ShellClient::windowType(bool direct, int supported_types) const
|
|
{
|
|
// TODO: implement
|
|
Q_UNUSED(direct)
|
|
Q_UNUSED(supported_types)
|
|
return m_windowType;
|
|
}
|
|
|
|
double ShellClient::opacity() const
|
|
{
|
|
return m_opacity;
|
|
}
|
|
|
|
void ShellClient::setOpacity(double opacity)
|
|
{
|
|
const qreal newOpacity = qBound(0.0, opacity, 1.0);
|
|
if (newOpacity == m_opacity) {
|
|
return;
|
|
}
|
|
const qreal oldOpacity = m_opacity;
|
|
m_opacity = newOpacity;
|
|
addRepaintFull();
|
|
emit opacityChanged(this, oldOpacity);
|
|
}
|
|
|
|
void ShellClient::addDamage(const QRegion &damage)
|
|
{
|
|
auto s = surface();
|
|
if (s->size().isValid()) {
|
|
m_clientSize = s->size();
|
|
QPoint position = geom.topLeft();
|
|
if (m_positionAfterResize.isValid()) {
|
|
addLayerRepaint(geometry());
|
|
position = m_positionAfterResize.point();
|
|
m_positionAfterResize.clear();
|
|
}
|
|
doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom())));
|
|
}
|
|
markAsMapped();
|
|
setDepth((s->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24);
|
|
repaints_region += damage.translated(clientPos());
|
|
Toplevel::addDamage(damage);
|
|
}
|
|
|
|
void ShellClient::setInternalFramebufferObject(const QSharedPointer<QOpenGLFramebufferObject> &fbo)
|
|
{
|
|
if (fbo.isNull()) {
|
|
unmap();
|
|
return;
|
|
}
|
|
|
|
//Kwin currently scales internal windows to 1, so this is currently always correct
|
|
//when that changes, this needs adjusting
|
|
m_clientSize = fbo->size();
|
|
markAsMapped();
|
|
doSetGeometry(QRect(geom.topLeft(), m_clientSize));
|
|
Toplevel::setInternalFramebufferObject(fbo);
|
|
Toplevel::addDamage(QRegion(0, 0, width(), height()));
|
|
}
|
|
|
|
void ShellClient::markAsMapped()
|
|
{
|
|
if (!m_unmapped) {
|
|
return;
|
|
}
|
|
|
|
m_unmapped = false;
|
|
if (!ready_for_painting) {
|
|
setReadyForPainting();
|
|
} else {
|
|
addRepaintFull();
|
|
emit windowShown(this);
|
|
}
|
|
if (shouldExposeToWindowManagement()) {
|
|
setupWindowManagementInterface();
|
|
}
|
|
updateShowOnScreenEdge();
|
|
}
|
|
|
|
void ShellClient::createDecoration(const QRect &oldGeom)
|
|
{
|
|
KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this);
|
|
if (decoration) {
|
|
QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection);
|
|
connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow);
|
|
connect(decoration, &KDecoration2::Decoration::bordersChanged, this,
|
|
[this]() {
|
|
GeometryUpdatesBlocker blocker(this);
|
|
RequestGeometryBlocker requestBlocker(this);
|
|
QRect oldgeom = geometry();
|
|
if (!isShade())
|
|
checkWorkspacePosition(oldgeom);
|
|
emit geometryShapeChanged(this, oldgeom);
|
|
}
|
|
);
|
|
}
|
|
setDecoration(decoration);
|
|
// TODO: ensure the new geometry still fits into the client area (e.g. maximized windows)
|
|
doSetGeometry(QRect(oldGeom.topLeft(), m_clientSize + (decoration ? QSize(decoration->borderLeft() + decoration->borderRight(),
|
|
decoration->borderBottom() + decoration->borderTop()) : QSize())));
|
|
|
|
emit geometryShapeChanged(this, oldGeom);
|
|
}
|
|
|
|
void ShellClient::updateDecoration(bool check_workspace_pos, bool force)
|
|
{
|
|
if (!force &&
|
|
((!isDecorated() && noBorder()) || (isDecorated() && !noBorder())))
|
|
return;
|
|
QRect oldgeom = geometry();
|
|
QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom());
|
|
blockGeometryUpdates(true);
|
|
if (force)
|
|
destroyDecoration();
|
|
if (!noBorder()) {
|
|
createDecoration(oldgeom);
|
|
} else
|
|
destroyDecoration();
|
|
if (m_serverDecoration && isDecorated()) {
|
|
m_serverDecoration->setMode(KWayland::Server::ServerSideDecorationManagerInterface::Mode::Server);
|
|
}
|
|
getShadow();
|
|
if (check_workspace_pos)
|
|
checkWorkspacePosition(oldgeom, -2, oldClientGeom);
|
|
blockGeometryUpdates(false);
|
|
}
|
|
|
|
void ShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force)
|
|
{
|
|
Q_UNUSED(force)
|
|
// TODO: better merge with Client's implementation
|
|
if (QSize(w, h) == geom.size()) {
|
|
// size didn't change, update directly
|
|
doSetGeometry(QRect(x, y, w, h));
|
|
} else {
|
|
// size did change, Client needs to provide a new buffer
|
|
requestGeometry(QRect(x, y, w, h));
|
|
}
|
|
}
|
|
|
|
void ShellClient::doSetGeometry(const QRect &rect)
|
|
{
|
|
if (geom == rect && pendingGeometryUpdate() == PendingGeometryNone) {
|
|
return;
|
|
}
|
|
if (!m_unmapped) {
|
|
addWorkspaceRepaint(visibleRect());
|
|
}
|
|
const QRect old = geom;
|
|
geom = rect;
|
|
|
|
if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) {
|
|
// use first valid geometry as restore geometry
|
|
m_geomMaximizeRestore = geom;
|
|
}
|
|
|
|
if (!m_unmapped) {
|
|
addWorkspaceRepaint(visibleRect());
|
|
}
|
|
syncGeometryToInternalWindow();
|
|
if (hasStrut()) {
|
|
workspace()->updateClientArea();
|
|
}
|
|
emit geometryShapeChanged(this, old);
|
|
|
|
if (isResize()) {
|
|
performMoveResize();
|
|
}
|
|
}
|
|
|
|
void ShellClient::doMove(int x, int y)
|
|
{
|
|
Q_UNUSED(x)
|
|
Q_UNUSED(y)
|
|
syncGeometryToInternalWindow();
|
|
}
|
|
|
|
void ShellClient::syncGeometryToInternalWindow()
|
|
{
|
|
if (!m_internalWindow) {
|
|
return;
|
|
}
|
|
const QRect windowRect = QRect(geom.topLeft() + QPoint(borderLeft(), borderTop()),
|
|
geom.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()));
|
|
if (m_internalWindow->geometry() != windowRect) {
|
|
m_internalWindow->setGeometry(windowRect);
|
|
}
|
|
}
|
|
|
|
QByteArray ShellClient::windowRole() const
|
|
{
|
|
return QByteArray();
|
|
}
|
|
|
|
bool ShellClient::belongsToSameApplication(const AbstractClient *other, bool active_hack) const
|
|
{
|
|
Q_UNUSED(active_hack)
|
|
if (auto s = other->surface()) {
|
|
return s->client() == surface()->client();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ShellClient::blockActivityUpdates(bool b)
|
|
{
|
|
Q_UNUSED(b)
|
|
}
|
|
|
|
QString ShellClient::caption(bool full, bool stripped) const
|
|
{
|
|
Q_UNUSED(full)
|
|
Q_UNUSED(stripped)
|
|
return m_caption;
|
|
}
|
|
|
|
void ShellClient::closeWindow()
|
|
{
|
|
if (m_xdgShellSurface && isCloseable()) {
|
|
m_xdgShellSurface->close();
|
|
return;
|
|
}
|
|
if (m_qtExtendedSurface && isCloseable()) {
|
|
m_qtExtendedSurface->close();
|
|
}
|
|
if (m_internalWindow) {
|
|
m_internalWindow->hide();
|
|
}
|
|
}
|
|
|
|
AbstractClient *ShellClient::findModal(bool allow_itself)
|
|
{
|
|
Q_UNUSED(allow_itself)
|
|
return nullptr;
|
|
}
|
|
|
|
bool ShellClient::isCloseable() const
|
|
{
|
|
if (m_windowType == NET::Desktop || m_windowType == NET::Dock) {
|
|
return false;
|
|
}
|
|
if (m_xdgShellSurface) {
|
|
return true;
|
|
}
|
|
if (m_internal) {
|
|
return true;
|
|
}
|
|
return m_qtExtendedSurface ? true : false;
|
|
}
|
|
|
|
bool ShellClient::isFullScreenable() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ShellClient::isFullScreen() const
|
|
{
|
|
return m_fullScreen;
|
|
}
|
|
|
|
bool ShellClient::isMaximizable() const
|
|
{
|
|
if (m_internal) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ShellClient::isMinimizable() const
|
|
{
|
|
if (m_internal) {
|
|
return false;
|
|
}
|
|
return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal);
|
|
}
|
|
|
|
bool ShellClient::isMovable() const
|
|
{
|
|
if (m_plasmaShellSurface) {
|
|
return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
|
|
}
|
|
if (m_xdgShellPopup) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ShellClient::isMovableAcrossScreens() const
|
|
{
|
|
if (m_plasmaShellSurface) {
|
|
return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
|
|
}
|
|
if (m_xdgShellPopup) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ShellClient::isResizable() const
|
|
{
|
|
if (m_plasmaShellSurface) {
|
|
return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal;
|
|
}
|
|
if (m_xdgShellPopup) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ShellClient::isShown(bool shaded_is_shown) const
|
|
{
|
|
Q_UNUSED(shaded_is_shown)
|
|
return !m_closing && !m_unmapped && !isMinimized() && !m_hidden;
|
|
}
|
|
|
|
void ShellClient::hideClient(bool hide)
|
|
{
|
|
if (m_hidden == hide) {
|
|
return;
|
|
}
|
|
m_hidden = hide;
|
|
if (hide) {
|
|
addWorkspaceRepaint(visibleRect());
|
|
workspace()->clientHidden(this);
|
|
emit windowHidden(this);
|
|
} else {
|
|
emit windowShown(this);
|
|
}
|
|
}
|
|
|
|
static bool changeMaximizeRecursion = false;
|
|
void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust)
|
|
{
|
|
if (changeMaximizeRecursion) {
|
|
return;
|
|
}
|
|
|
|
if (!isResizable()) {
|
|
return;
|
|
}
|
|
|
|
const QRect clientArea = isElectricBorderMaximizing() ?
|
|
workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()) :
|
|
workspace()->clientArea(MaximizeArea, this);
|
|
|
|
MaximizeMode oldMode = m_maximizeMode;
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
RequestGeometryBlocker geometryBlocker(this);
|
|
// 'adjust == true' means to update the size only, e.g. after changing workspace size
|
|
if (!adjust) {
|
|
if (vertical)
|
|
m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeVertical);
|
|
if (horizontal)
|
|
m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeHorizontal);
|
|
}
|
|
// TODO: add more checks as in Client
|
|
|
|
// call into decoration update borders
|
|
if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_maximizeMode == KWin::MaximizeFull)) {
|
|
changeMaximizeRecursion = true;
|
|
const auto c = decoration()->client().data();
|
|
if ((m_maximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) {
|
|
emit c->maximizedVerticallyChanged(m_maximizeMode & MaximizeVertical);
|
|
}
|
|
if ((m_maximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) {
|
|
emit c->maximizedHorizontallyChanged(m_maximizeMode & MaximizeHorizontal);
|
|
}
|
|
if ((m_maximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) {
|
|
emit c->maximizedChanged(m_maximizeMode & MaximizeFull);
|
|
}
|
|
changeMaximizeRecursion = false;
|
|
}
|
|
|
|
if (options->borderlessMaximizedWindows()) {
|
|
// triggers a maximize change.
|
|
// The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry
|
|
changeMaximizeRecursion = true;
|
|
setNoBorder(rules()->checkNoBorder(m_maximizeMode == MaximizeFull));
|
|
changeMaximizeRecursion = false;
|
|
}
|
|
|
|
// Conditional quick tiling exit points
|
|
const auto oldQuickTileMode = quickTileMode();
|
|
if (quickTileMode() != QuickTileNone) {
|
|
if (oldMode == MaximizeFull &&
|
|
!clientArea.contains(m_geomMaximizeRestore.center())) {
|
|
// Not restoring on the same screen
|
|
// TODO: The following doesn't work for some reason
|
|
//quick_tile_mode = QuickTileNone; // And exit quick tile mode manually
|
|
} else if ((oldMode == MaximizeVertical && m_maximizeMode == MaximizeRestore) ||
|
|
(oldMode == MaximizeFull && m_maximizeMode == MaximizeHorizontal)) {
|
|
// Modifying geometry of a tiled window
|
|
updateQuickTileMode(QuickTileNone); // Exit quick tile mode without restoring geometry
|
|
}
|
|
}
|
|
|
|
// TODO: check rules
|
|
if (m_maximizeMode == MaximizeFull) {
|
|
m_geomMaximizeRestore = geometry();
|
|
// TODO: Client has more checks
|
|
if (options->electricBorderMaximize()) {
|
|
updateQuickTileMode(QuickTileMaximize);
|
|
} else {
|
|
updateQuickTileMode(QuickTileNone);
|
|
}
|
|
if (quickTileMode() != oldQuickTileMode) {
|
|
emit quickTileModeChanged();
|
|
}
|
|
requestGeometry(workspace()->clientArea(MaximizeArea, this));
|
|
workspace()->raiseClient(this);
|
|
} else {
|
|
if (m_maximizeMode == MaximizeRestore) {
|
|
updateQuickTileMode(QuickTileNone);
|
|
}
|
|
if (quickTileMode() != oldQuickTileMode) {
|
|
emit quickTileModeChanged();
|
|
}
|
|
|
|
if (m_geomMaximizeRestore.isValid()) {
|
|
requestGeometry(m_geomMaximizeRestore);
|
|
} else {
|
|
requestGeometry(workspace()->clientArea(PlacementArea, this));
|
|
}
|
|
}
|
|
}
|
|
|
|
MaximizeMode ShellClient::maximizeMode() const
|
|
{
|
|
return m_maximizeMode;
|
|
}
|
|
|
|
bool ShellClient::noBorder() const
|
|
{
|
|
if (isInternal()) {
|
|
return m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup);
|
|
}
|
|
if (m_serverDecoration) {
|
|
if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) {
|
|
return m_userNoBorder || isFullScreen();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const WindowRules *ShellClient::rules() const
|
|
{
|
|
static WindowRules s_rules;
|
|
return &s_rules;
|
|
}
|
|
|
|
void ShellClient::setFullScreen(bool set, bool user)
|
|
{
|
|
Q_UNUSED(set)
|
|
Q_UNUSED(user)
|
|
}
|
|
|
|
void ShellClient::setNoBorder(bool set)
|
|
{
|
|
if (!userCanSetNoBorder()) {
|
|
return;
|
|
}
|
|
set = rules()->checkNoBorder(set);
|
|
if (m_userNoBorder == set) {
|
|
return;
|
|
}
|
|
m_userNoBorder = set;
|
|
updateDecoration(true, false);
|
|
updateWindowRules(Rules::NoBorder);
|
|
}
|
|
|
|
void ShellClient::setOnAllActivities(bool set)
|
|
{
|
|
Q_UNUSED(set)
|
|
}
|
|
|
|
void ShellClient::takeFocus()
|
|
{
|
|
if (rules()->checkAcceptFocus(wantsInput())) {
|
|
setActive(true);
|
|
}
|
|
|
|
bool breakShowingDesktop = !keepAbove() && !isOnScreenDisplay();
|
|
if (breakShowingDesktop) {
|
|
// check that it doesn't belong to the desktop
|
|
const auto &clients = waylandServer()->clients();
|
|
for (auto c: clients) {
|
|
if (!belongsToSameApplication(c, false)) {
|
|
continue;
|
|
}
|
|
if (c->isDesktop()) {
|
|
breakShowingDesktop = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (breakShowingDesktop)
|
|
workspace()->setShowingDesktop(false);
|
|
}
|
|
|
|
void ShellClient::doSetActive()
|
|
{
|
|
if (!isActive()) {
|
|
return;
|
|
}
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
workspace()->focusToNull();
|
|
}
|
|
|
|
void ShellClient::updateWindowRules(Rules::Types selection)
|
|
{
|
|
Q_UNUSED(selection)
|
|
}
|
|
|
|
bool ShellClient::userCanSetFullScreen() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ShellClient::userCanSetNoBorder() const
|
|
{
|
|
if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) {
|
|
return !isFullScreen() && !isShade() && !tabGroup();
|
|
}
|
|
if (m_internal) {
|
|
return !m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShellClient::wantsInput() const
|
|
{
|
|
return rules()->checkAcceptFocus(acceptsFocus());
|
|
}
|
|
|
|
bool ShellClient::acceptsFocus() const
|
|
{
|
|
if (isInternal()) {
|
|
return false;
|
|
}
|
|
if (waylandServer()->inputMethodConnection() == surface()->client()) {
|
|
return false;
|
|
}
|
|
if (m_plasmaShellSurface) {
|
|
if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay ||
|
|
m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip ||
|
|
m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification) {
|
|
return false;
|
|
}
|
|
}
|
|
if (m_closing) {
|
|
// a closing window does not accept focus
|
|
return false;
|
|
}
|
|
if (m_unmapped) {
|
|
// an unmapped window does not accept focus
|
|
return false;
|
|
}
|
|
if (m_shellSurface) {
|
|
if (m_shellSurface->isPopup()) {
|
|
return false;
|
|
}
|
|
return m_shellSurface->acceptsKeyboardFocus();
|
|
}
|
|
if (m_xdgShellSurface) {
|
|
// TODO: proper
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ShellClient::createWindowId()
|
|
{
|
|
if (m_internalWindow) {
|
|
m_windowId = m_internalWindow->winId();
|
|
} else {
|
|
m_windowId = waylandServer()->createWindowId(surface());
|
|
}
|
|
}
|
|
|
|
void ShellClient::findInternalWindow()
|
|
{
|
|
if (surface()->client() != waylandServer()->internalConnection()) {
|
|
return;
|
|
}
|
|
const QWindowList windows = kwinApp()->topLevelWindows();
|
|
for (QWindow *w: windows) {
|
|
auto s = KWayland::Client::Surface::fromWindow(w);
|
|
if (!s) {
|
|
continue;
|
|
}
|
|
if (s->id() != surface()->id()) {
|
|
continue;
|
|
}
|
|
m_internalWindow = w;
|
|
m_internalWindowFlags = m_internalWindow->flags();
|
|
connect(m_internalWindow, &QWindow::xChanged, this, &ShellClient::updateInternalWindowGeometry);
|
|
connect(m_internalWindow, &QWindow::yChanged, this, &ShellClient::updateInternalWindowGeometry);
|
|
connect(m_internalWindow, &QWindow::destroyed, this, [this] { m_internalWindow = nullptr; });
|
|
connect(m_internalWindow, &QWindow::opacityChanged, this, &ShellClient::setOpacity);
|
|
|
|
// Try reading the window type from the QWindow. PlasmaCore.Dialog provides a dynamic type property
|
|
// let's check whether it exists, if it does it's our window type
|
|
const QVariant windowType = m_internalWindow->property("type");
|
|
if (!windowType.isNull()) {
|
|
m_windowType = static_cast<NET::WindowType>(windowType.toInt());
|
|
}
|
|
setOpacity(m_internalWindow->opacity());
|
|
|
|
// skip close animation support
|
|
setSkipCloseAnimation(m_internalWindow->property(s_skipClosePropertyName).toBool());
|
|
m_internalWindow->installEventFilter(this);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ShellClient::updateInternalWindowGeometry()
|
|
{
|
|
if (!m_internalWindow) {
|
|
return;
|
|
}
|
|
doSetGeometry(QRect(m_internalWindow->geometry().topLeft() - QPoint(borderLeft(), borderTop()),
|
|
m_internalWindow->geometry().size() + QSize(borderLeft() + borderRight(), borderTop() + borderBottom())));
|
|
}
|
|
|
|
pid_t ShellClient::pid() const
|
|
{
|
|
return surface()->client()->processId();
|
|
}
|
|
|
|
bool ShellClient::isInternal() const
|
|
{
|
|
return m_internal;
|
|
}
|
|
|
|
bool ShellClient::isLockScreen() const
|
|
{
|
|
if (m_internalWindow) {
|
|
return m_internalWindow->property("org_kde_ksld_emergency").toBool();
|
|
}
|
|
return surface()->client() == waylandServer()->screenLockerClientConnection();
|
|
}
|
|
|
|
bool ShellClient::isInputMethod() const
|
|
{
|
|
if (m_internal && m_internalWindow) {
|
|
return m_internalWindow->property("__kwin_input_method").toBool();
|
|
}
|
|
return surface()->client() == waylandServer()->inputMethodConnection();
|
|
}
|
|
|
|
void ShellClient::requestGeometry(const QRect &rect)
|
|
{
|
|
if (m_requestGeometryBlockCounter != 0) {
|
|
m_blockedRequestGeometry = rect;
|
|
return;
|
|
}
|
|
m_positionAfterResize.setPoint(rect.topLeft());
|
|
const QSize size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom());
|
|
if (m_shellSurface) {
|
|
m_shellSurface->requestSize(size);
|
|
}
|
|
if (m_xdgShellSurface) {
|
|
m_xdgShellSurface->configure(xdgSurfaceStates(), size);
|
|
}
|
|
m_blockedRequestGeometry = QRect();
|
|
if (m_internal) {
|
|
m_internalWindow->setGeometry(QRect(rect.topLeft() + QPoint(borderLeft(), borderTop()), rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom())));
|
|
}
|
|
}
|
|
|
|
void ShellClient::clientFullScreenChanged(bool fullScreen)
|
|
{
|
|
RequestGeometryBlocker requestBlocker(this);
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
|
|
const bool emitSignal = m_fullScreen != fullScreen;
|
|
m_fullScreen = fullScreen;
|
|
updateDecoration(false, false);
|
|
|
|
workspace()->updateClientLayer(this); // active fullscreens get different layer
|
|
|
|
if (fullScreen) {
|
|
m_geomFsRestore = geometry();
|
|
requestGeometry(workspace()->clientArea(FullScreenArea, this));
|
|
workspace()->raiseClient(this);
|
|
} else {
|
|
if (m_geomFsRestore.isValid()) {
|
|
requestGeometry(m_geomFsRestore);
|
|
} else {
|
|
requestGeometry(workspace()->clientArea(MaximizeArea, this));
|
|
}
|
|
}
|
|
if (emitSignal) {
|
|
emit fullScreenChanged();
|
|
}
|
|
}
|
|
|
|
void ShellClient::resizeWithChecks(int w, int h, ForceGeometry_t force)
|
|
{
|
|
Q_UNUSED(force)
|
|
QRect area = workspace()->clientArea(WorkArea, this);
|
|
// don't allow growing larger than workarea
|
|
if (w > area.width()) {
|
|
w = area.width();
|
|
}
|
|
if (h > area.height()) {
|
|
h = area.height();
|
|
}
|
|
if (m_shellSurface) {
|
|
m_shellSurface->requestSize(QSize(w, h));
|
|
}
|
|
if (m_xdgShellSurface) {
|
|
m_xdgShellSurface->configure(xdgSurfaceStates(), QSize(w, h));
|
|
}
|
|
if (m_internal) {
|
|
m_internalWindow->setGeometry(QRect(pos() + QPoint(borderLeft(), borderTop()), QSize(w, h) - QSize(borderLeft() + borderRight(), borderTop() + borderBottom())));
|
|
}
|
|
}
|
|
|
|
void ShellClient::unmap()
|
|
{
|
|
m_unmapped = true;
|
|
destroyWindowManagementInterface();
|
|
if (Workspace::self()) {
|
|
addWorkspaceRepaint(visibleRect());
|
|
workspace()->clientHidden(this);
|
|
}
|
|
emit windowHidden(this);
|
|
}
|
|
|
|
void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface)
|
|
{
|
|
m_plasmaShellSurface = surface;
|
|
auto updatePosition = [this, surface] {
|
|
doSetGeometry(QRect(surface->position(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom())));
|
|
};
|
|
auto updateRole = [this, surface] {
|
|
NET::WindowType type = NET::Unknown;
|
|
switch (surface->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::Normal:
|
|
default:
|
|
type = NET::Normal;
|
|
break;
|
|
}
|
|
if (type != m_windowType) {
|
|
m_windowType = type;
|
|
if (m_windowType == NET::Desktop || type == NET::Dock || type == NET::OnScreenDisplay || type == NET::Notification || type == NET::Tooltip) {
|
|
setOnAllDesktops(true);
|
|
}
|
|
workspace()->updateClientArea();
|
|
}
|
|
};
|
|
connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition);
|
|
connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole);
|
|
connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this,
|
|
[this] {
|
|
updateShowOnScreenEdge();
|
|
workspace()->updateClientArea();
|
|
}
|
|
);
|
|
connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this,
|
|
[this] {
|
|
hideClient(true);
|
|
m_plasmaShellSurface->hideAutoHidingPanel();
|
|
updateShowOnScreenEdge();
|
|
}
|
|
);
|
|
connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this,
|
|
[this] {
|
|
hideClient(false);
|
|
ScreenEdges::self()->reserve(this, ElectricNone);
|
|
m_plasmaShellSurface->showAutoHidingPanel();
|
|
}
|
|
);
|
|
updatePosition();
|
|
updateRole();
|
|
updateShowOnScreenEdge();
|
|
connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateShowOnScreenEdge);
|
|
|
|
setSkipTaskbar(surface->skipTaskbar());
|
|
connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] {
|
|
setSkipTaskbar(m_plasmaShellSurface->skipTaskbar());
|
|
});
|
|
}
|
|
|
|
void ShellClient::updateShowOnScreenEdge()
|
|
{
|
|
if (!ScreenEdges::self()) {
|
|
return;
|
|
}
|
|
if (m_unmapped || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) {
|
|
ScreenEdges::self()->reserve(this, ElectricNone);
|
|
return;
|
|
}
|
|
if ((m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && m_hidden) ||
|
|
m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) {
|
|
// screen edge API requires an edge, thus we need to figure out which edge the window borders
|
|
Qt::Edges edges;
|
|
for (int i = 0; i < screens()->count(); i++) {
|
|
const auto &screenGeo = screens()->geometry(i);
|
|
if (screenGeo.x() == geom.x()) {
|
|
edges |= Qt::LeftEdge;
|
|
}
|
|
if (screenGeo.x() + screenGeo.width() == geom.x() + geom.width()) {
|
|
edges |= Qt::RightEdge;
|
|
}
|
|
if (screenGeo.y() == geom.y()) {
|
|
edges |= Qt::TopEdge;
|
|
}
|
|
if (screenGeo.y() + screenGeo.height() == geom.y() + geom.height()) {
|
|
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.testFlag(Qt::LeftEdge) && edges.testFlag(Qt::RightEdge)) {
|
|
edges = edges & (~(Qt::LeftEdge | Qt::RightEdge));
|
|
}
|
|
if (edges.testFlag(Qt::TopEdge) && edges.testFlag(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 = [this](Qt::Edges edges, Qt::Edge horiz, Qt::Edge vert) {
|
|
if (edges.testFlag(horiz) && edges.testFlag(vert)) {
|
|
if (geom.width() >= geom.height()) {
|
|
return edges & ~horiz;
|
|
} else {
|
|
return edges & ~vert;
|
|
}
|
|
}
|
|
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.testFlag(Qt::LeftEdge)) {
|
|
border = ElectricLeft;
|
|
}
|
|
if (edges.testFlag(Qt::RightEdge)) {
|
|
border = ElectricRight;
|
|
}
|
|
if (edges.testFlag(Qt::TopEdge)) {
|
|
border = ElectricTop;
|
|
}
|
|
if (edges.testFlag(Qt::BottomEdge)) {
|
|
border = ElectricBottom;
|
|
}
|
|
ScreenEdges::self()->reserve(this, border);
|
|
} else {
|
|
ScreenEdges::self()->reserve(this, ElectricNone);
|
|
}
|
|
}
|
|
|
|
bool ShellClient::isInitialPositionSet() const
|
|
{
|
|
if (m_plasmaShellSurface) {
|
|
return m_plasmaShellSurface->isPositionSet();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ShellClient::installQtExtendedSurface(QtExtendedSurfaceInterface *surface)
|
|
{
|
|
m_qtExtendedSurface = surface;
|
|
|
|
connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::raiseRequested, this, [this]() {
|
|
workspace()->raiseClientRequest(this);
|
|
});
|
|
connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::lowerRequested, this, [this]() {
|
|
workspace()->lowerClientRequest(this);
|
|
});
|
|
m_qtExtendedSurface->installEventFilter(this);
|
|
}
|
|
|
|
bool ShellClient::eventFilter(QObject *watched, QEvent *event)
|
|
{
|
|
if (watched == m_qtExtendedSurface.data() && event->type() == QEvent::DynamicPropertyChange) {
|
|
QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent*>(event);
|
|
if (pe->propertyName() == s_schemePropertyName) {
|
|
updateColorScheme(rules()->checkDecoColor(m_qtExtendedSurface->property(pe->propertyName().constData()).toString()));
|
|
} else if (pe->propertyName() == s_appMenuServiceNamePropertyName) {
|
|
updateApplicationMenuServiceName(m_qtExtendedSurface->property(pe->propertyName().constData()).toString());
|
|
} else if (pe->propertyName() == s_appMenuObjectPathPropertyName) {
|
|
updateApplicationMenuObjectPath(m_qtExtendedSurface->property(pe->propertyName().constData()).toString());
|
|
}
|
|
}
|
|
if (watched == m_internalWindow && event->type() == QEvent::DynamicPropertyChange) {
|
|
QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent*>(event);
|
|
if (pe->propertyName() == s_skipClosePropertyName) {
|
|
setSkipCloseAnimation(m_internalWindow->property(s_skipClosePropertyName).toBool());
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShellClient::hasStrut() const
|
|
{
|
|
if (!isShown(true)) {
|
|
return false;
|
|
}
|
|
if (!m_plasmaShellSurface) {
|
|
return false;
|
|
}
|
|
if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) {
|
|
return false;
|
|
}
|
|
return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible;
|
|
}
|
|
|
|
void ShellClient::updateIcon()
|
|
{
|
|
const QString waylandIconName = QStringLiteral("wayland");
|
|
const QString dfIconName = iconFromDesktopFile();
|
|
const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName;
|
|
if (iconName == icon().name()) {
|
|
return;
|
|
}
|
|
setIcon(QIcon::fromTheme(iconName));
|
|
}
|
|
|
|
bool ShellClient::isTransient() const
|
|
{
|
|
return m_transient;
|
|
}
|
|
|
|
void ShellClient::setTransient()
|
|
{
|
|
SurfaceInterface *s = nullptr;
|
|
if (m_shellSurface) {
|
|
s = m_shellSurface->transientFor().data();
|
|
}
|
|
if (m_xdgShellSurface) {
|
|
if (auto transient = m_xdgShellSurface->transientFor().data()) {
|
|
s = transient->surface();
|
|
}
|
|
}
|
|
if (m_xdgShellPopup) {
|
|
s = m_xdgShellPopup->transientFor().data();
|
|
}
|
|
auto t = waylandServer()->findClient(s);
|
|
if (t != transientFor()) {
|
|
// remove from main client
|
|
if (transientFor())
|
|
transientFor()->removeTransient(this);
|
|
setTransientFor(t);
|
|
if (t) {
|
|
t->addTransient(this);
|
|
}
|
|
}
|
|
m_transient = (s != nullptr);
|
|
}
|
|
|
|
bool ShellClient::hasTransientPlacementHint() const
|
|
{
|
|
return isTransient() && transientFor() != nullptr;
|
|
}
|
|
|
|
QPoint ShellClient::transientPlacementHint() const
|
|
{
|
|
if (m_shellSurface) {
|
|
return m_shellSurface->transientOffset();
|
|
}
|
|
if (m_xdgShellPopup) {
|
|
return m_xdgShellPopup->transientOffset();
|
|
}
|
|
return QPoint();
|
|
}
|
|
|
|
bool ShellClient::isWaitingForMoveResizeSync() const
|
|
{
|
|
return m_positionAfterResize.isValid();
|
|
}
|
|
|
|
void ShellClient::doResizeSync()
|
|
{
|
|
requestGeometry(moveResizeGeometry());
|
|
}
|
|
|
|
QMatrix4x4 ShellClient::inputTransformation() const
|
|
{
|
|
QMatrix4x4 m = Toplevel::inputTransformation();
|
|
m.translate(-borderLeft(), -borderTop());
|
|
return m;
|
|
}
|
|
|
|
void ShellClient::installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *deco)
|
|
{
|
|
if (m_serverDecoration == deco) {
|
|
return;
|
|
}
|
|
m_serverDecoration = deco;
|
|
connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this,
|
|
[this] {
|
|
m_serverDecoration = nullptr;
|
|
if (m_closing || !Workspace::self()) {
|
|
return;
|
|
}
|
|
if (!m_unmapped) {
|
|
// maybe delay to next event cycle in case the ShellClient is getting destroyed, too
|
|
updateDecoration(true);
|
|
}
|
|
}
|
|
);
|
|
if (!m_unmapped) {
|
|
updateDecoration(true);
|
|
}
|
|
connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this,
|
|
[this] (ServerSideDecorationManagerInterface::Mode mode) {
|
|
const bool changed = mode != m_serverDecoration->mode();
|
|
// always acknowledge the requested mode
|
|
m_serverDecoration->setMode(mode);
|
|
if (changed && !m_unmapped) {
|
|
updateDecoration(false);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
bool ShellClient::shouldExposeToWindowManagement()
|
|
{
|
|
if (isInternal()) {
|
|
return false;
|
|
}
|
|
if (isLockScreen()) {
|
|
return false;
|
|
}
|
|
if (m_shellSurface) {
|
|
if (m_shellSurface->isTransient() && !m_shellSurface->acceptsKeyboardFocus()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
KWayland::Server::XdgShellSurfaceInterface::States ShellClient::xdgSurfaceStates() const
|
|
{
|
|
XdgShellSurfaceInterface::States states;
|
|
if (isActive()) {
|
|
states |= XdgShellSurfaceInterface::State::Activated;
|
|
}
|
|
if (isFullScreen()) {
|
|
states |= XdgShellSurfaceInterface::State::Fullscreen;
|
|
}
|
|
if (maximizeMode() == MaximizeMode::MaximizeFull) {
|
|
states |= XdgShellSurfaceInterface::State::Maximized;
|
|
}
|
|
if (isResize()) {
|
|
states |= XdgShellSurfaceInterface::State::Resizing;
|
|
}
|
|
return states;
|
|
}
|
|
|
|
void ShellClient::doMinimize()
|
|
{
|
|
if (isMinimized()) {
|
|
workspace()->clientHidden(this);
|
|
} else {
|
|
emit windowShown(this);
|
|
}
|
|
}
|
|
|
|
bool ShellClient::setupCompositing()
|
|
{
|
|
if (m_compositingSetup) {
|
|
return true;
|
|
}
|
|
m_compositingSetup = Toplevel::setupCompositing();
|
|
return m_compositingSetup;
|
|
}
|
|
|
|
void ShellClient::finishCompositing(ReleaseReason releaseReason)
|
|
{
|
|
m_compositingSetup = false;
|
|
Toplevel::finishCompositing(releaseReason);
|
|
}
|
|
|
|
void ShellClient::placeIn(QRect &area)
|
|
{
|
|
Placement::self()->place(this, area);
|
|
setGeometryRestore(geometry());
|
|
}
|
|
|
|
void ShellClient::showOnScreenEdge()
|
|
{
|
|
if (!m_plasmaShellSurface || m_unmapped) {
|
|
return;
|
|
}
|
|
hideClient(false);
|
|
workspace()->raiseClient(this);
|
|
if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) {
|
|
m_plasmaShellSurface->showAutoHidingPanel();
|
|
}
|
|
}
|
|
|
|
bool ShellClient::dockWantsInput() const
|
|
{
|
|
if (m_plasmaShellSurface) {
|
|
if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) {
|
|
return m_plasmaShellSurface->panelTakesFocus();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ShellClient::killWindow()
|
|
{
|
|
if (isInternal()) {
|
|
return;
|
|
}
|
|
if (!surface()) {
|
|
return;
|
|
}
|
|
auto c = surface()->client();
|
|
if (c->processId() == getpid()) {
|
|
c->destroy();
|
|
return;
|
|
}
|
|
::kill(c->processId(), SIGTERM);
|
|
// give it time to terminate and only if terminate fails, try destroy Wayland connection
|
|
QTimer::singleShot(5000, c, &ClientConnection::destroy);
|
|
}
|
|
|
|
void ShellClient::updateApplicationMenu()
|
|
{
|
|
if (m_qtExtendedSurface) {
|
|
updateApplicationMenuServiceName(m_qtExtendedSurface->property(s_appMenuObjectPathPropertyName).toString());
|
|
updateApplicationMenuObjectPath(m_qtExtendedSurface->property(s_appMenuServiceNamePropertyName).toString());
|
|
}
|
|
}
|
|
|
|
bool ShellClient::hasPopupGrab() const
|
|
{
|
|
if (m_shellSurface) {
|
|
// TODO: verify grab serial
|
|
return m_shellSurface->isPopup();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ShellClient::popupDone()
|
|
{
|
|
if (m_shellSurface) {
|
|
m_shellSurface->popupDone();
|
|
}
|
|
}
|
|
|
|
}
|