diff --git a/deleted.cpp b/deleted.cpp index 34960bb3e..0df6c3646 100644 --- a/deleted.cpp +++ b/deleted.cpp @@ -1,277 +1,279 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #include "deleted.h" #include "workspace.h" #include "client.h" #include "group.h" #include "netinfo.h" #include "shadow.h" #include "shell_client.h" #include "decorations/decoratedclient.h" #include "decorations/decorationrenderer.h" #include namespace KWin { Deleted::Deleted() : Toplevel() , delete_refcount(1) , m_frame(XCB_WINDOW_NONE) , no_border(true) , m_layer(UnknownLayer) , m_minimized(false) , m_modal(false) , m_wasClient(false) , m_wasCurrentTab(true) , m_decorationRenderer(nullptr) , m_fullscreen(false) , m_keepAbove(false) , m_keepBelow(false) , m_wasActive(false) , m_wasX11Client(false) , m_wasWaylandClient(false) , m_wasGroupTransient(false) + , m_wasPopupWindow(false) { } Deleted::~Deleted() { if (delete_refcount != 0) qCCritical(KWIN_CORE) << "Deleted client has non-zero reference count (" << delete_refcount << ")"; assert(delete_refcount == 0); if (workspace()) { workspace()->removeDeleted(this); } for (Toplevel *toplevel : qAsConst(m_transientFor)) { if (auto *deleted = qobject_cast(toplevel)) { deleted->removeTransient(this); } } for (Deleted *transient : qAsConst(m_transients)) { transient->removeTransientFor(this); } deleteEffectWindow(); } Deleted* Deleted::create(Toplevel* c) { Deleted* d = new Deleted(); d->copyToDeleted(c); workspace()->addDeleted(d, c); return d; } // to be used only from Workspace::finishCompositing() void Deleted::discard() { delete_refcount = 0; delete this; } void Deleted::copyToDeleted(Toplevel* c) { assert(dynamic_cast< Deleted* >(c) == NULL); Toplevel::copyToDeleted(c); desk = c->desktop(); m_desktops = c->desktops(); activityList = c->activities(); contentsRect = QRect(c->clientPos(), c->clientSize()); m_contentPos = c->clientContentPos(); transparent_rect = c->transparentRect(); m_layer = c->layer(); m_frame = c->frameId(); m_opacity = c->opacity(); m_type = c->windowType(true); m_windowRole = c->windowRole(); if (WinInfo* cinfo = dynamic_cast< WinInfo* >(info)) cinfo->disable(); if (AbstractClient *client = dynamic_cast(c)) { no_border = client->noBorder(); if (!no_border) { client->layoutDecorationRects(decoration_left, decoration_top, decoration_right, decoration_bottom); if (client->isDecorated()) { if (Decoration::Renderer *renderer = client->decoratedClient()->renderer()) { m_decorationRenderer = renderer; m_decorationRenderer->reparent(this); } } } m_wasClient = true; m_minimized = client->isMinimized(); m_modal = client->isModal(); m_mainClients = client->mainClients(); foreach (AbstractClient *c, m_mainClients) { addTransientFor(c); connect(c, &AbstractClient::windowClosed, this, &Deleted::mainClientClosed); } m_fullscreen = client->isFullScreen(); m_wasCurrentTab = client->isCurrentTab(); m_keepAbove = client->keepAbove(); m_keepBelow = client->keepBelow(); m_caption = client->caption(); m_wasActive = client->isActive(); const auto *x11Client = qobject_cast(client); m_wasGroupTransient = x11Client && x11Client->groupTransient(); } m_wasWaylandClient = qobject_cast(c) != nullptr; m_wasX11Client = !m_wasWaylandClient; + m_wasPopupWindow = c->isPopupWindow(); } void Deleted::unrefWindow() { if (--delete_refcount > 0) return; // needs to be delayed // a) when calling from effects, otherwise it'd be rather complicated to handle the case of the // window going away during a painting pass // b) to prevent dangeling pointers in the stacking order, see bug #317765 deleteLater(); } int Deleted::desktop() const { return desk; } QStringList Deleted::activities() const { return activityList; } QVector Deleted::desktops() const { return m_desktops; } QPoint Deleted::clientPos() const { return contentsRect.topLeft(); } QSize Deleted::clientSize() const { return contentsRect.size(); } void Deleted::debug(QDebug& stream) const { stream << "\'ID:" << window() << "\' (deleted)"; } void Deleted::layoutDecorationRects(QRect& left, QRect& top, QRect& right, QRect& bottom) const { left = decoration_left; top = decoration_top; right = decoration_right; bottom = decoration_bottom; } QRect Deleted::decorationRect() const { return rect(); } QRect Deleted::transparentRect() const { return transparent_rect; } bool Deleted::isDeleted() const { return true; } NET::WindowType Deleted::windowType(bool direct, int supportedTypes) const { Q_UNUSED(direct) Q_UNUSED(supportedTypes) return m_type; } void Deleted::mainClientClosed(Toplevel *client) { if (AbstractClient *c = dynamic_cast(client)) m_mainClients.removeAll(c); } void Deleted::transientForClosed(Toplevel *toplevel, Deleted *deleted) { if (deleted == nullptr) { m_transientFor.removeAll(toplevel); return; } const int index = m_transientFor.indexOf(toplevel); if (index == -1) { return; } m_transientFor[index] = deleted; deleted->addTransient(this); } xcb_window_t Deleted::frameId() const { return m_frame; } double Deleted::opacity() const { return m_opacity; } QByteArray Deleted::windowRole() const { return m_windowRole; } void Deleted::addTransient(Deleted *transient) { m_transients.append(transient); } void Deleted::removeTransient(Deleted *transient) { m_transients.removeAll(transient); } void Deleted::addTransientFor(AbstractClient *parent) { m_transientFor.append(parent); connect(parent, &AbstractClient::windowClosed, this, &Deleted::transientForClosed); } void Deleted::removeTransientFor(Deleted *parent) { m_transientFor.removeAll(parent); } } // namespace diff --git a/deleted.h b/deleted.h index d7af9f213..a2dd663fe 100644 --- a/deleted.h +++ b/deleted.h @@ -1,237 +1,247 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #ifndef KWIN_DELETED_H #define KWIN_DELETED_H #include "toplevel.h" namespace KWin { class AbstractClient; namespace Decoration { class Renderer; } class KWIN_EXPORT Deleted : public Toplevel { Q_OBJECT Q_PROPERTY(bool minimized READ isMinimized) Q_PROPERTY(bool modal READ isModal) Q_PROPERTY(bool fullScreen READ isFullScreen CONSTANT) Q_PROPERTY(bool isCurrentTab READ isCurrentTab) Q_PROPERTY(bool keepAbove READ keepAbove CONSTANT) Q_PROPERTY(bool keepBelow READ keepBelow CONSTANT) Q_PROPERTY(QString caption READ caption CONSTANT) public: static Deleted* create(Toplevel* c); // used by effects to keep the window around for e.g. fadeout effects when it's destroyed void refWindow(); void unrefWindow(); void discard(); virtual int desktop() const; virtual QStringList activities() const; virtual QVector desktops() const; virtual QPoint clientPos() const; virtual QSize clientSize() const; QPoint clientContentPos() const override { return m_contentPos; } virtual QRect transparentRect() const; virtual bool isDeleted() const; virtual xcb_window_t frameId() const override; bool noBorder() const { return no_border; } void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const; QRect decorationRect() const; virtual Layer layer() const { return m_layer; } bool isMinimized() const { return m_minimized; } bool isModal() const { return m_modal; } QList mainClients() const { return m_mainClients; } NET::WindowType windowType(bool direct = false, int supported_types = 0) const; bool wasClient() const { return m_wasClient; } double opacity() const override; QByteArray windowRole() const override; const Decoration::Renderer *decorationRenderer() const { return m_decorationRenderer; } bool isFullScreen() const { return m_fullscreen; } bool isCurrentTab() const { return m_wasCurrentTab; } bool keepAbove() const { return m_keepAbove; } bool keepBelow() const { return m_keepBelow; } QString caption() const { return m_caption; } /** * Returns whether the client was active. * * @returns @c true if the client was active at the time when it was closed, * @c false otherwise **/ bool wasActive() const { return m_wasActive; } /** * Returns whether this was an X11 client. * * @returns @c true if it was an X11 client, @c false otherwise. **/ bool wasX11Client() const { return m_wasX11Client; } /** * Returns whether this was a Wayland client. * * @returns @c true if it was a Wayland client, @c false otherwise. **/ bool wasWaylandClient() const { return m_wasWaylandClient; } /** * Returns whether the client was a transient. * * @returns @c true if it was a transient, @c false otherwise. **/ bool wasTransient() const { return !m_transientFor.isEmpty(); } /** * Returns whether the client was a group transient. * * @returns @c true if it was a group transient, @c false otherwise. * @note This is relevant only for X11 clients. **/ bool wasGroupTransient() const { return m_wasGroupTransient; } /** * Checks whether this client was a transient for given toplevel. * * @param toplevel Toplevel against which we are testing. * @returns @c true if it was a transient for given toplevel, @c false otherwise. **/ bool wasTransientFor(const Toplevel *toplevel) const { return m_transientFor.contains(const_cast(toplevel)); } /** * Returns the list of transients. * * Because the window is Deleted, it can have only Deleted child transients. **/ DeletedList transients() const { return m_transients; } + /** + * Returns whether the client was a popup. + * + * @returns @c true if the client was a popup, @c false otherwise. + **/ + bool isPopupWindow() const override { + return m_wasPopupWindow; + } + protected: virtual void debug(QDebug& stream) const; private Q_SLOTS: void mainClientClosed(KWin::Toplevel *client); void transientForClosed(Toplevel *toplevel, Deleted *deleted); private: Deleted(); // use create() void copyToDeleted(Toplevel* c); virtual ~Deleted(); // deleted only using unrefWindow() void addTransient(Deleted *transient); void removeTransient(Deleted *transient); void addTransientFor(AbstractClient *parent); void removeTransientFor(Deleted *parent); int delete_refcount; double window_opacity; int desk; QStringList activityList; QRect contentsRect; // for clientPos()/clientSize() QPoint m_contentPos; QRect transparent_rect; xcb_window_t m_frame; QVector m_desktops; bool no_border; QRect decoration_left; QRect decoration_right; QRect decoration_top; QRect decoration_bottom; Layer m_layer; bool m_minimized; bool m_modal; QList m_mainClients; bool m_wasClient; bool m_wasCurrentTab; Decoration::Renderer *m_decorationRenderer; double m_opacity; NET::WindowType m_type = NET::Unknown; QByteArray m_windowRole; bool m_fullscreen; bool m_keepAbove; bool m_keepBelow; QString m_caption; bool m_wasActive; bool m_wasX11Client; bool m_wasWaylandClient; bool m_wasGroupTransient; ToplevelList m_transientFor; DeletedList m_transients; + bool m_wasPopupWindow; }; inline void Deleted::refWindow() { ++delete_refcount; } } // namespace Q_DECLARE_METATYPE(KWin::Deleted*) #endif diff --git a/libkwineffects/kwineffects.cpp b/libkwineffects/kwineffects.cpp index 8a159486a..25644ff7c 100644 --- a/libkwineffects/kwineffects.cpp +++ b/libkwineffects/kwineffects.cpp @@ -1,2104 +1,2098 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray Copyright (C) 2018 Vlad Zagorodniy 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 . *********************************************************************/ #include "kwineffects.h" #include "config-kwin.h" #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include "kwinxrenderutils.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #endif #if defined(__GNUC__) # define KWIN_ALIGN(n) __attribute((aligned(n))) # if defined(__SSE2__) # define HAVE_SSE2 # endif #elif defined(__INTEL_COMPILER) # define KWIN_ALIGN(n) __declspec(align(n)) # define HAVE_SSE2 #else # define KWIN_ALIGN(n) #endif #ifdef HAVE_SSE2 # include #endif namespace KWin { void WindowPrePaintData::setTranslucent() { mask |= Effect::PAINT_WINDOW_TRANSLUCENT; mask &= ~Effect::PAINT_WINDOW_OPAQUE; clip = QRegion(); // cannot clip, will be transparent } void WindowPrePaintData::setTransformed() { mask |= Effect::PAINT_WINDOW_TRANSFORMED; } class PaintDataPrivate { public: QGraphicsScale scale; QVector3D translation; QGraphicsRotation rotation; }; PaintData::PaintData() : d(new PaintDataPrivate()) { } PaintData::~PaintData() { delete d; } qreal PaintData::xScale() const { return d->scale.xScale(); } qreal PaintData::yScale() const { return d->scale.yScale(); } qreal PaintData::zScale() const { return d->scale.zScale(); } void PaintData::setScale(const QVector2D &scale) { d->scale.setXScale(scale.x()); d->scale.setYScale(scale.y()); } void PaintData::setScale(const QVector3D &scale) { d->scale.setXScale(scale.x()); d->scale.setYScale(scale.y()); d->scale.setZScale(scale.z()); } void PaintData::setXScale(qreal scale) { d->scale.setXScale(scale); } void PaintData::setYScale(qreal scale) { d->scale.setYScale(scale); } void PaintData::setZScale(qreal scale) { d->scale.setZScale(scale); } const QGraphicsScale &PaintData::scale() const { return d->scale; } void PaintData::setXTranslation(qreal translate) { d->translation.setX(translate); } void PaintData::setYTranslation(qreal translate) { d->translation.setY(translate); } void PaintData::setZTranslation(qreal translate) { d->translation.setZ(translate); } void PaintData::translate(qreal x, qreal y, qreal z) { translate(QVector3D(x, y, z)); } void PaintData::translate(const QVector3D &t) { d->translation += t; } qreal PaintData::xTranslation() const { return d->translation.x(); } qreal PaintData::yTranslation() const { return d->translation.y(); } qreal PaintData::zTranslation() const { return d->translation.z(); } const QVector3D &PaintData::translation() const { return d->translation; } qreal PaintData::rotationAngle() const { return d->rotation.angle(); } QVector3D PaintData::rotationAxis() const { return d->rotation.axis(); } QVector3D PaintData::rotationOrigin() const { return d->rotation.origin(); } void PaintData::setRotationAngle(qreal angle) { d->rotation.setAngle(angle); } void PaintData::setRotationAxis(Qt::Axis axis) { d->rotation.setAxis(axis); } void PaintData::setRotationAxis(const QVector3D &axis) { d->rotation.setAxis(axis); } void PaintData::setRotationOrigin(const QVector3D &origin) { d->rotation.setOrigin(origin); } class WindowPaintDataPrivate { public: qreal opacity; qreal saturation; qreal brightness; int screen; qreal crossFadeProgress; QMatrix4x4 pMatrix; QMatrix4x4 mvMatrix; QMatrix4x4 screenProjectionMatrix; }; WindowPaintData::WindowPaintData(EffectWindow *w) : WindowPaintData(w, QMatrix4x4()) { } WindowPaintData::WindowPaintData(EffectWindow* w, const QMatrix4x4 &screenProjectionMatrix) : PaintData() , shader(nullptr) , d(new WindowPaintDataPrivate()) { d->screenProjectionMatrix = screenProjectionMatrix; quads = w->buildQuads(); setOpacity(w->opacity()); setSaturation(1.0); setBrightness(1.0); setScreen(0); setCrossFadeProgress(1.0); } WindowPaintData::WindowPaintData(const WindowPaintData &other) : PaintData() , quads(other.quads) , shader(other.shader) , d(new WindowPaintDataPrivate()) { setXScale(other.xScale()); setYScale(other.yScale()); setZScale(other.zScale()); translate(other.translation()); setRotationOrigin(other.rotationOrigin()); setRotationAxis(other.rotationAxis()); setRotationAngle(other.rotationAngle()); setOpacity(other.opacity()); setSaturation(other.saturation()); setBrightness(other.brightness()); setScreen(other.screen()); setCrossFadeProgress(other.crossFadeProgress()); setProjectionMatrix(other.projectionMatrix()); setModelViewMatrix(other.modelViewMatrix()); d->screenProjectionMatrix = other.d->screenProjectionMatrix; } WindowPaintData::~WindowPaintData() { delete d; } qreal WindowPaintData::opacity() const { return d->opacity; } qreal WindowPaintData::saturation() const { return d->saturation; } qreal WindowPaintData::brightness() const { return d->brightness; } int WindowPaintData::screen() const { return d->screen; } void WindowPaintData::setOpacity(qreal opacity) { d->opacity = opacity; } void WindowPaintData::setSaturation(qreal saturation) const { d->saturation = saturation; } void WindowPaintData::setBrightness(qreal brightness) { d->brightness = brightness; } void WindowPaintData::setScreen(int screen) const { d->screen = screen; } qreal WindowPaintData::crossFadeProgress() const { return d->crossFadeProgress; } void WindowPaintData::setCrossFadeProgress(qreal factor) { d->crossFadeProgress = qBound(qreal(0.0), factor, qreal(1.0)); } qreal WindowPaintData::multiplyOpacity(qreal factor) { d->opacity *= factor; return d->opacity; } qreal WindowPaintData::multiplySaturation(qreal factor) { d->saturation *= factor; return d->saturation; } qreal WindowPaintData::multiplyBrightness(qreal factor) { d->brightness *= factor; return d->brightness; } void WindowPaintData::setProjectionMatrix(const QMatrix4x4 &matrix) { d->pMatrix = matrix; } QMatrix4x4 WindowPaintData::projectionMatrix() const { return d->pMatrix; } QMatrix4x4 &WindowPaintData::rprojectionMatrix() { return d->pMatrix; } void WindowPaintData::setModelViewMatrix(const QMatrix4x4 &matrix) { d->mvMatrix = matrix; } QMatrix4x4 WindowPaintData::modelViewMatrix() const { return d->mvMatrix; } QMatrix4x4 &WindowPaintData::rmodelViewMatrix() { return d->mvMatrix; } WindowPaintData &WindowPaintData::operator*=(qreal scale) { this->setXScale(this->xScale() * scale); this->setYScale(this->yScale() * scale); this->setZScale(this->zScale() * scale); return *this; } WindowPaintData &WindowPaintData::operator*=(const QVector2D &scale) { this->setXScale(this->xScale() * scale.x()); this->setYScale(this->yScale() * scale.y()); return *this; } WindowPaintData &WindowPaintData::operator*=(const QVector3D &scale) { this->setXScale(this->xScale() * scale.x()); this->setYScale(this->yScale() * scale.y()); this->setZScale(this->zScale() * scale.z()); return *this; } WindowPaintData &WindowPaintData::operator+=(const QPointF &translation) { return this->operator+=(QVector3D(translation)); } WindowPaintData &WindowPaintData::operator+=(const QPoint &translation) { return this->operator+=(QVector3D(translation)); } WindowPaintData &WindowPaintData::operator+=(const QVector2D &translation) { return this->operator+=(QVector3D(translation)); } WindowPaintData &WindowPaintData::operator+=(const QVector3D &translation) { translate(translation); return *this; } QMatrix4x4 WindowPaintData::screenProjectionMatrix() const { return d->screenProjectionMatrix; } class ScreenPaintData::Private { public: QMatrix4x4 projectionMatrix; QRect outputGeometry; }; ScreenPaintData::ScreenPaintData() : PaintData() , d(new Private()) { } ScreenPaintData::ScreenPaintData(const QMatrix4x4 &projectionMatrix, const QRect &outputGeometry) : PaintData() , d(new Private()) { d->projectionMatrix = projectionMatrix; d->outputGeometry = outputGeometry; } ScreenPaintData::~ScreenPaintData() = default; ScreenPaintData::ScreenPaintData(const ScreenPaintData &other) : PaintData() , d(new Private()) { translate(other.translation()); setXScale(other.xScale()); setYScale(other.yScale()); setZScale(other.zScale()); setRotationOrigin(other.rotationOrigin()); setRotationAxis(other.rotationAxis()); setRotationAngle(other.rotationAngle()); d->projectionMatrix = other.d->projectionMatrix; d->outputGeometry = other.d->outputGeometry; } ScreenPaintData &ScreenPaintData::operator=(const ScreenPaintData &rhs) { setXScale(rhs.xScale()); setYScale(rhs.yScale()); setZScale(rhs.zScale()); setXTranslation(rhs.xTranslation()); setYTranslation(rhs.yTranslation()); setZTranslation(rhs.zTranslation()); setRotationOrigin(rhs.rotationOrigin()); setRotationAxis(rhs.rotationAxis()); setRotationAngle(rhs.rotationAngle()); d->projectionMatrix = rhs.d->projectionMatrix; d->outputGeometry = rhs.d->outputGeometry; return *this; } ScreenPaintData &ScreenPaintData::operator*=(qreal scale) { setXScale(this->xScale() * scale); setYScale(this->yScale() * scale); setZScale(this->zScale() * scale); return *this; } ScreenPaintData &ScreenPaintData::operator*=(const QVector2D &scale) { setXScale(this->xScale() * scale.x()); setYScale(this->yScale() * scale.y()); return *this; } ScreenPaintData &ScreenPaintData::operator*=(const QVector3D &scale) { setXScale(this->xScale() * scale.x()); setYScale(this->yScale() * scale.y()); setZScale(this->zScale() * scale.z()); return *this; } ScreenPaintData &ScreenPaintData::operator+=(const QPointF &translation) { return this->operator+=(QVector3D(translation)); } ScreenPaintData &ScreenPaintData::operator+=(const QPoint &translation) { return this->operator+=(QVector3D(translation)); } ScreenPaintData &ScreenPaintData::operator+=(const QVector2D &translation) { return this->operator+=(QVector3D(translation)); } ScreenPaintData &ScreenPaintData::operator+=(const QVector3D &translation) { translate(translation); return *this; } QMatrix4x4 ScreenPaintData::projectionMatrix() const { return d->projectionMatrix; } QRect ScreenPaintData::outputGeometry() const { return d->outputGeometry; } //**************************************** // Effect //**************************************** Effect::Effect() { } Effect::~Effect() { } void Effect::reconfigure(ReconfigureFlags) { } void* Effect::proxy() { return nullptr; } void Effect::windowInputMouseEvent(QEvent*) { } void Effect::grabbedKeyboardEvent(QKeyEvent*) { } bool Effect::borderActivated(ElectricBorder) { return false; } void Effect::prePaintScreen(ScreenPrePaintData& data, int time) { effects->prePaintScreen(data, time); } void Effect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); } void Effect::postPaintScreen() { effects->postPaintScreen(); } void Effect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { effects->prePaintWindow(w, data, time); } void Effect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->paintWindow(w, mask, region, data); } void Effect::postPaintWindow(EffectWindow* w) { effects->postPaintWindow(w); } void Effect::paintEffectFrame(KWin::EffectFrame* frame, QRegion region, double opacity, double frameOpacity) { effects->paintEffectFrame(frame, region, opacity, frameOpacity); } bool Effect::provides(Feature) { return false; } bool Effect::isActive() const { return true; } QString Effect::debug(const QString &) const { return QString(); } void Effect::drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->drawWindow(w, mask, region, data); } void Effect::buildQuads(EffectWindow* w, WindowQuadList& quadList) { effects->buildQuads(w, quadList); } void Effect::setPositionTransformations(WindowPaintData& data, QRect& region, EffectWindow* w, const QRect& r, Qt::AspectRatioMode aspect) { QSize size = w->size(); size.scale(r.size(), aspect); data.setXScale(size.width() / double(w->width())); data.setYScale(size.height() / double(w->height())); int width = int(w->width() * data.xScale()); int height = int(w->height() * data.yScale()); int x = r.x() + (r.width() - width) / 2; int y = r.y() + (r.height() - height) / 2; region = QRect(x, y, width, height); data.setXTranslation(x - w->x()); data.setYTranslation(y - w->y()); } QPoint Effect::cursorPos() { return effects->cursorPos(); } double Effect::animationTime(const KConfigGroup& cfg, const QString& key, int defaultTime) { int time = cfg.readEntry(key, 0); return time != 0 ? time : qMax(defaultTime * effects->animationTimeFactor(), 1.); } double Effect::animationTime(int defaultTime) { // at least 1ms, otherwise 0ms times can break some things return qMax(defaultTime * effects->animationTimeFactor(), 1.); } int Effect::requestedEffectChainPosition() const { return 0; } xcb_connection_t *Effect::xcbConnection() const { return effects->xcbConnection(); } xcb_window_t Effect::x11RootWindow() const { return effects->x11RootWindow(); } bool Effect::touchDown(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) return false; } bool Effect::touchMotion(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) return false; } bool Effect::touchUp(quint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) return false; } bool Effect::perform(Feature feature, const QVariantList &arguments) { Q_UNUSED(feature) Q_UNUSED(arguments) return false; } //**************************************** // EffectFactory //**************************************** EffectPluginFactory::EffectPluginFactory() { } EffectPluginFactory::~EffectPluginFactory() { } bool EffectPluginFactory::enabledByDefault() const { return true; } bool EffectPluginFactory::isSupported() const { return true; } //**************************************** // EffectsHandler //**************************************** EffectsHandler::EffectsHandler(CompositingType type) : compositing_type(type) { if (compositing_type == NoCompositing) return; KWin::effects = this; } EffectsHandler::~EffectsHandler() { // All effects should already be unloaded by Impl dtor assert(loaded_effects.count() == 0); KWin::effects = nullptr; } CompositingType EffectsHandler::compositingType() const { return compositing_type; } bool EffectsHandler::isOpenGLCompositing() const { return compositing_type & OpenGLCompositing; } void EffectsHandler::removeWindowFromDesktop(KWin::EffectWindow* w, int desktop) { if (w->parent() && !w->isDesktop() && !w->isDock()) { QMetaObject::invokeMethod(w->parent(), "unSetDesktop", Q_ARG(int, desktop)); } } EffectsHandler* effects = nullptr; //**************************************** // EffectWindow //**************************************** class Q_DECL_HIDDEN EffectWindow::Private { public: Private(EffectWindow *q); EffectWindow *q; bool managed = false; bool waylandClient; bool x11Client; - bool popupWindow; }; EffectWindow::Private::Private(EffectWindow *q) : q(q) { } EffectWindow::EffectWindow(QObject *parent) : QObject(parent) , d(new Private(this)) { // Deleted windows are not managed. So, when windowClosed signal is // emitted, effects can't distinguish managed windows from unmanaged // windows(e.g. combo box popups, popup menus, etc). Save value of the // managed property during construction of EffectWindow. At that time, // parent can be Client, ShellClient, or Unmanaged. So, later on, when // an instance of Deleted becomes parent of the EffectWindow, effects // can still figure out whether it is/was a managed window. d->managed = parent->property("managed").value(); d->waylandClient = parent->inherits("KWin::ShellClient"); d->x11Client = !d->waylandClient; - d->popupWindow = parent->property("popupWindow").value(); } EffectWindow::~EffectWindow() { } #define WINDOW_HELPER( rettype, prototype, propertyname ) \ rettype EffectWindow::prototype ( ) const \ { \ return parent()->property( propertyname ).value< rettype >(); \ } WINDOW_HELPER(double, opacity, "opacity") WINDOW_HELPER(bool, hasAlpha, "alpha") WINDOW_HELPER(int, x, "x") WINDOW_HELPER(int, y, "y") WINDOW_HELPER(int, width, "width") WINDOW_HELPER(int, height, "height") WINDOW_HELPER(QPoint, pos, "pos") WINDOW_HELPER(QSize, size, "size") WINDOW_HELPER(int, screen, "screen") WINDOW_HELPER(QRect, geometry, "geometry") WINDOW_HELPER(QRect, expandedGeometry, "visibleRect") WINDOW_HELPER(QRect, rect, "rect") #ifndef KWIN_NO_DEPRECATED WINDOW_HELPER(int, desktop, "desktop") #endif WINDOW_HELPER(bool, isDesktop, "desktopWindow") WINDOW_HELPER(bool, isDock, "dock") WINDOW_HELPER(bool, isToolbar, "toolbar") WINDOW_HELPER(bool, isMenu, "menu") WINDOW_HELPER(bool, isNormalWindow, "normalWindow") WINDOW_HELPER(bool, isDialog, "dialog") WINDOW_HELPER(bool, isSplash, "splash") WINDOW_HELPER(bool, isUtility, "utility") WINDOW_HELPER(bool, isDropdownMenu, "dropdownMenu") WINDOW_HELPER(bool, isPopupMenu, "popupMenu") WINDOW_HELPER(bool, isTooltip, "tooltip") WINDOW_HELPER(bool, isNotification, "notification") WINDOW_HELPER(bool, isOnScreenDisplay, "onScreenDisplay") WINDOW_HELPER(bool, isComboBox, "comboBox") WINDOW_HELPER(bool, isDNDIcon, "dndIcon") WINDOW_HELPER(bool, isDeleted, "deleted") WINDOW_HELPER(bool, hasOwnShape, "shaped") WINDOW_HELPER(QString, windowRole, "windowRole") WINDOW_HELPER(QStringList, activities, "activities") WINDOW_HELPER(bool, skipsCloseAnimation, "skipsCloseAnimation") WINDOW_HELPER(KWayland::Server::SurfaceInterface *, surface, "surface") WINDOW_HELPER(QVector, desktops, "x11DesktopIds") +WINDOW_HELPER(bool, isPopupWindow, "popupWindow") QString EffectWindow::windowClass() const { return parent()->property("resourceName").toString() + QLatin1Char(' ') + parent()->property("resourceClass").toString(); } QRect EffectWindow::contentsRect() const { return QRect(parent()->property("clientPos").toPoint(), parent()->property("clientSize").toSize()); } NET::WindowType EffectWindow::windowType() const { return static_cast(parent()->property("windowType").toInt()); } bool EffectWindow::isOnActivity(QString activity) const { const QStringList activities = parent()->property("activities").toStringList(); return activities.isEmpty() || activities.contains(activity); } bool EffectWindow::isOnAllActivities() const { return parent()->property("activities").toStringList().isEmpty(); } #undef WINDOW_HELPER #define WINDOW_HELPER_DEFAULT( rettype, prototype, propertyname, defaultValue ) \ rettype EffectWindow::prototype ( ) const \ { \ const QVariant variant = parent()->property( propertyname ); \ if (!variant.isValid()) { \ return defaultValue; \ } \ return variant.value< rettype >(); \ } WINDOW_HELPER_DEFAULT(bool, isMinimized, "minimized", false) WINDOW_HELPER_DEFAULT(bool, isMovable, "moveable", false) WINDOW_HELPER_DEFAULT(bool, isMovableAcrossScreens, "moveableAcrossScreens", false) WINDOW_HELPER_DEFAULT(QString, caption, "caption", QString()) WINDOW_HELPER_DEFAULT(bool, keepAbove, "keepAbove", true) WINDOW_HELPER_DEFAULT(bool, keepBelow, "keepBelow", false) WINDOW_HELPER_DEFAULT(bool, isModal, "modal", false) WINDOW_HELPER_DEFAULT(QSize, basicUnit, "basicUnit", QSize(1, 1)) WINDOW_HELPER_DEFAULT(bool, isUserMove, "move", false) WINDOW_HELPER_DEFAULT(bool, isUserResize, "resize", false) WINDOW_HELPER_DEFAULT(QRect, iconGeometry, "iconGeometry", QRect()) WINDOW_HELPER_DEFAULT(bool, isSpecialWindow, "specialWindow", true) WINDOW_HELPER_DEFAULT(bool, acceptsFocus, "wantsInput", true) // We don't actually know... WINDOW_HELPER_DEFAULT(QIcon, icon, "icon", QIcon()) WINDOW_HELPER_DEFAULT(bool, isSkipSwitcher, "skipSwitcher", false) WINDOW_HELPER_DEFAULT(bool, isCurrentTab, "isCurrentTab", true) WINDOW_HELPER_DEFAULT(bool, decorationHasAlpha, "decorationHasAlpha", false) WINDOW_HELPER_DEFAULT(bool, isFullScreen, "fullScreen", false) WINDOW_HELPER_DEFAULT(bool, isUnresponsive, "unresponsive", false) #undef WINDOW_HELPER_DEFAULT #define WINDOW_HELPER_SETTER( prototype, propertyname, args, value ) \ void EffectWindow::prototype ( args ) \ {\ const QVariant variant = parent()->property( propertyname ); \ if (variant.isValid()) { \ parent()->setProperty( propertyname, value ); \ } \ } WINDOW_HELPER_SETTER(minimize, "minimized",,true) WINDOW_HELPER_SETTER(unminimize, "minimized",,false) #undef WINDOW_HELPER_SETTER void EffectWindow::setMinimized(bool min) { if (min) { minimize(); } else { unminimize(); } } void EffectWindow::closeWindow() const { QMetaObject::invokeMethod(parent(), "closeWindow"); } void EffectWindow::addRepaint(int x, int y, int w, int h) { QMetaObject::invokeMethod(parent(), "addRepaint", Q_ARG(int, x), Q_ARG(int, y), Q_ARG(int, w), Q_ARG(int, h)); } void EffectWindow::addRepaint(const QRect &r) { QMetaObject::invokeMethod(parent(), "addRepaint", Q_ARG(const QRect&, r)); } void EffectWindow::addRepaintFull() { QMetaObject::invokeMethod(parent(), "addRepaintFull"); } void EffectWindow::addLayerRepaint(int x, int y, int w, int h) { QMetaObject::invokeMethod(parent(), "addLayerRepaint", Q_ARG(int, x), Q_ARG(int, y), Q_ARG(int, w), Q_ARG(int, h)); } void EffectWindow::addLayerRepaint(const QRect &r) { QMetaObject::invokeMethod(parent(), "addLayerRepaint", Q_ARG(const QRect&, r)); } bool EffectWindow::isOnCurrentActivity() const { return isOnActivity(effects->currentActivity()); } bool EffectWindow::isOnCurrentDesktop() const { return isOnDesktop(effects->currentDesktop()); } bool EffectWindow::isOnDesktop(int d) const { const QVector ds = desktops(); return ds.isEmpty() || ds.contains(d); } bool EffectWindow::isOnAllDesktops() const { return desktops().isEmpty(); } bool EffectWindow::hasDecoration() const { return contentsRect() != QRect(0, 0, width(), height()); } bool EffectWindow::isVisible() const { return !isMinimized() && isOnCurrentDesktop() && isOnCurrentActivity(); } bool EffectWindow::isManaged() const { return d->managed; } bool EffectWindow::isWaylandClient() const { return d->waylandClient; } bool EffectWindow::isX11Client() const { return d->x11Client; } -bool EffectWindow::isPopupWindow() const -{ - return d->popupWindow; -} - //**************************************** // EffectWindowGroup //**************************************** EffectWindowGroup::~EffectWindowGroup() { } /*************************************************************** WindowQuad ***************************************************************/ WindowQuad WindowQuad::makeSubQuad(double x1, double y1, double x2, double y2) const { assert(x1 < x2 && y1 < y2 && x1 >= left() && x2 <= right() && y1 >= top() && y2 <= bottom()); #ifndef NDEBUG if (isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif WindowQuad ret(*this); // vertices are clockwise starting from topleft ret.verts[ 0 ].px = x1; ret.verts[ 3 ].px = x1; ret.verts[ 1 ].px = x2; ret.verts[ 2 ].px = x2; ret.verts[ 0 ].py = y1; ret.verts[ 1 ].py = y1; ret.verts[ 2 ].py = y2; ret.verts[ 3 ].py = y2; // original x/y are supposed to be the same, no transforming is done here ret.verts[ 0 ].ox = x1; ret.verts[ 3 ].ox = x1; ret.verts[ 1 ].ox = x2; ret.verts[ 2 ].ox = x2; ret.verts[ 0 ].oy = y1; ret.verts[ 1 ].oy = y1; ret.verts[ 2 ].oy = y2; ret.verts[ 3 ].oy = y2; const double my_u0 = verts[0].tx; const double my_u1 = verts[2].tx; const double my_v0 = verts[0].ty; const double my_v1 = verts[2].ty; const double width = right() - left(); const double height = bottom() - top(); const double texWidth = my_u1 - my_u0; const double texHeight = my_v1 - my_v0; if (!uvAxisSwapped()) { const double u0 = (x1 - left()) / width * texWidth + my_u0; const double u1 = (x2 - left()) / width * texWidth + my_u0; const double v0 = (y1 - top()) / height * texHeight + my_v0; const double v1 = (y2 - top()) / height * texHeight + my_v0; ret.verts[0].tx = u0; ret.verts[3].tx = u0; ret.verts[1].tx = u1; ret.verts[2].tx = u1; ret.verts[0].ty = v0; ret.verts[1].ty = v0; ret.verts[2].ty = v1; ret.verts[3].ty = v1; } else { const double u0 = (y1 - top()) / height * texWidth + my_u0; const double u1 = (y2 - top()) / height * texWidth + my_u0; const double v0 = (x1 - left()) / width * texHeight + my_v0; const double v1 = (x2 - left()) / width * texHeight + my_v0; ret.verts[0].tx = u0; ret.verts[1].tx = u0; ret.verts[2].tx = u1; ret.verts[3].tx = u1; ret.verts[0].ty = v0; ret.verts[3].ty = v0; ret.verts[1].ty = v1; ret.verts[2].ty = v1; } ret.setUVAxisSwapped(uvAxisSwapped()); return ret; } bool WindowQuad::smoothNeeded() const { // smoothing is needed if the width or height of the quad does not match the original size double width = verts[ 1 ].ox - verts[ 0 ].ox; double height = verts[ 2 ].oy - verts[ 1 ].oy; return(verts[ 1 ].px - verts[ 0 ].px != width || verts[ 2 ].px - verts[ 3 ].px != width || verts[ 2 ].py - verts[ 1 ].py != height || verts[ 3 ].py - verts[ 0 ].py != height); } /*************************************************************** WindowQuadList ***************************************************************/ WindowQuadList WindowQuadList::splitAtX(double x) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif bool wholeleft = true; bool wholeright = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].x() < x) wholeright = false; if (quad[ i ].x() > x) wholeleft = false; } if (wholeleft || wholeright) { // is whole in one split part ret.append(quad); continue; } if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), x, quad.bottom())); ret.append(quad.makeSubQuad(x, quad.top(), quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::splitAtY(double y) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif bool wholetop = true; bool wholebottom = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].y() < y) wholebottom = false; if (quad[ i ].y() > y) wholetop = false; } if (wholetop || wholebottom) { // is whole in one split part ret.append(quad); continue; } if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), quad.right(), y)); ret.append(quad.makeSubQuad(quad.left(), y, quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::makeGrid(int maxQuadSize) const { if (empty()) return *this; // Find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad &quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } WindowQuadList ret; foreach (const WindowQuad &quad, *this) { const double quadLeft = quad.left(); const double quadRight = quad.right(); const double quadTop = quad.top(); const double quadBottom = quad.bottom(); // sanity check, see BUG 390953 if (quadLeft == quadRight || quadTop == quadBottom) { ret.append(quad); continue; } // Compute the top-left corner of the first intersecting grid cell const double xBegin = left + qFloor((quadLeft - left) / maxQuadSize) * maxQuadSize; const double yBegin = top + qFloor((quadTop - top) / maxQuadSize) * maxQuadSize; // Loop over all intersecting cells and add sub-quads for (double y = yBegin; y < quadBottom; y += maxQuadSize) { const double y0 = qMax(y, quadTop); const double y1 = qMin(quadBottom, y + maxQuadSize); for (double x = xBegin; x < quadRight; x += maxQuadSize) { const double x0 = qMax(x, quadLeft); const double x1 = qMin(quadRight, x + maxQuadSize); ret.append(quad.makeSubQuad(x0, y0, x1, y1)); } } } return ret; } WindowQuadList WindowQuadList::makeRegularGrid(int xSubdivisions, int ySubdivisions) const { if (empty()) return *this; // Find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad &quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } double xIncrement = (right - left) / xSubdivisions; double yIncrement = (bottom - top) / ySubdivisions; WindowQuadList ret; foreach (const WindowQuad &quad, *this) { const double quadLeft = quad.left(); const double quadRight = quad.right(); const double quadTop = quad.top(); const double quadBottom = quad.bottom(); // sanity check, see BUG 390953 if (quadLeft == quadRight || quadTop == quadBottom) { ret.append(quad); continue; } // Compute the top-left corner of the first intersecting grid cell const double xBegin = left + qFloor((quadLeft - left) / xIncrement) * xIncrement; const double yBegin = top + qFloor((quadTop - top) / yIncrement) * yIncrement; // Loop over all intersecting cells and add sub-quads for (double y = yBegin; y < quadBottom; y += yIncrement) { const double y0 = qMax(y, quadTop); const double y1 = qMin(quadBottom, y + yIncrement); for (double x = xBegin; x < quadRight; x += xIncrement) { const double x0 = qMax(x, quadLeft); const double x1 = qMin(quadRight, x + xIncrement); ret.append(quad.makeSubQuad(x0, y0, x1, y1)); } } } return ret; } #ifndef GL_TRIANGLES # define GL_TRIANGLES 0x0004 #endif #ifndef GL_QUADS # define GL_QUADS 0x0007 #endif void WindowQuadList::makeInterleavedArrays(unsigned int type, GLVertex2D *vertices, const QMatrix4x4 &textureMatrix) const { // Since we know that the texture matrix just scales and translates // we can use this information to optimize the transformation const QVector2D coeff(textureMatrix(0, 0), textureMatrix(1, 1)); const QVector2D offset(textureMatrix(0, 3), textureMatrix(1, 3)); GLVertex2D *vertex = vertices; assert(type == GL_QUADS || type == GL_TRIANGLES); switch (type) { case GL_QUADS: #ifdef HAVE_SSE2 if (!(intptr_t(vertex) & 0xf)) { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); KWIN_ALIGN(16) GLVertex2D v[4]; for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; v[j].position = QVector2D(wv.x(), wv.y()); v[j].texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; } const __m128i *srcP = (const __m128i *) &v; __m128i *dstP = (__m128i *) vertex; _mm_stream_si128(&dstP[0], _mm_load_si128(&srcP[0])); // Top-left _mm_stream_si128(&dstP[1], _mm_load_si128(&srcP[1])); // Top-right _mm_stream_si128(&dstP[2], _mm_load_si128(&srcP[2])); // Bottom-right _mm_stream_si128(&dstP[3], _mm_load_si128(&srcP[3])); // Bottom-left vertex += 4; } } else #endif // HAVE_SSE2 { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; GLVertex2D v; v.position = QVector2D(wv.x(), wv.y()); v.texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; *(vertex++) = v; } } } break; case GL_TRIANGLES: #ifdef HAVE_SSE2 if (!(intptr_t(vertex) & 0xf)) { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); KWIN_ALIGN(16) GLVertex2D v[4]; for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; v[j].position = QVector2D(wv.x(), wv.y()); v[j].texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; } const __m128i *srcP = (const __m128i *) &v; __m128i *dstP = (__m128i *) vertex; __m128i src[4]; src[0] = _mm_load_si128(&srcP[0]); // Top-left src[1] = _mm_load_si128(&srcP[1]); // Top-right src[2] = _mm_load_si128(&srcP[2]); // Bottom-right src[3] = _mm_load_si128(&srcP[3]); // Bottom-left // First triangle _mm_stream_si128(&dstP[0], src[1]); // Top-right _mm_stream_si128(&dstP[1], src[0]); // Top-left _mm_stream_si128(&dstP[2], src[3]); // Bottom-left // Second triangle _mm_stream_si128(&dstP[3], src[3]); // Bottom-left _mm_stream_si128(&dstP[4], src[2]); // Bottom-right _mm_stream_si128(&dstP[5], src[1]); // Top-right vertex += 6; } } else #endif // HAVE_SSE2 { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); GLVertex2D v[4]; // Four unique vertices / quad for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; v[j].position = QVector2D(wv.x(), wv.y()); v[j].texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; } // First triangle *(vertex++) = v[1]; // Top-right *(vertex++) = v[0]; // Top-left *(vertex++) = v[3]; // Bottom-left // Second triangle *(vertex++) = v[3]; // Bottom-left *(vertex++) = v[2]; // Bottom-right *(vertex++) = v[1]; // Top-right } } break; default: break; } } void WindowQuadList::makeArrays(float **vertices, float **texcoords, const QSizeF &size, bool yInverted) const { *vertices = new float[count() * 6 * 2]; *texcoords = new float[count() * 6 * 2]; float *vpos = *vertices; float *tpos = *texcoords; // Note: The positions in a WindowQuad are stored in clockwise order const int index[] = { 1, 0, 3, 3, 2, 1 }; for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); for (int j = 0; j < 6; j++) { const WindowVertex &wv = quad[index[j]]; *vpos++ = wv.x(); *vpos++ = wv.y(); *tpos++ = wv.u() / size.width(); *tpos++ = yInverted ? (wv.v() / size.height()) : (1.0 - wv.v() / size.height()); } } } WindowQuadList WindowQuadList::select(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() != type) { // something else than ones to select, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() == type) ret.append(q); } return ret; } } return *this; // nothing to filter out } WindowQuadList WindowQuadList::filterOut(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() == type) { // something to filter out, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() != type) ret.append(q); } return ret; } } return *this; // nothing to filter out } bool WindowQuadList::smoothNeeded() const { foreach (const WindowQuad & q, *this) if (q.smoothNeeded()) return true; return false; } bool WindowQuadList::isTransformed() const { foreach (const WindowQuad & q, *this) if (q.isTransformed()) return true; return false; } /*************************************************************** PaintClipper ***************************************************************/ QStack< QRegion >* PaintClipper::areas = nullptr; PaintClipper::PaintClipper(const QRegion& allowed_area) : area(allowed_area) { push(area); } PaintClipper::~PaintClipper() { pop(area); } void PaintClipper::push(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) // don't push these return; if (areas == nullptr) areas = new QStack< QRegion >; areas->push(allowed_area); } void PaintClipper::pop(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) return; Q_ASSERT(areas != nullptr); Q_ASSERT(areas->top() == allowed_area); areas->pop(); if (areas->isEmpty()) { delete areas; areas = nullptr; } } bool PaintClipper::clip() { return areas != nullptr; } QRegion PaintClipper::paintArea() { assert(areas != nullptr); // can be called only with clip() == true const QSize &s = effects->virtualScreenSize(); QRegion ret = QRegion(0, 0, s.width(), s.height()); foreach (const QRegion & r, *areas) ret &= r; return ret; } struct PaintClipper::Iterator::Data { Data() : index(0) {} int index; QRegion region; }; PaintClipper::Iterator::Iterator() : data(new Data) { if (clip() && effects->isOpenGLCompositing()) { data->region = paintArea(); data->index = -1; next(); // move to the first one } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) { XFixesRegion region(paintArea()); xcb_xfixes_set_picture_clip_region(connection(), effects->xrenderBufferPicture(), region, 0, 0); } #endif } PaintClipper::Iterator::~Iterator() { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) xcb_xfixes_set_picture_clip_region(connection(), effects->xrenderBufferPicture(), XCB_XFIXES_REGION_NONE, 0, 0); #endif delete data; } bool PaintClipper::Iterator::isDone() { if (!clip()) return data->index == 1; // run once if (effects->isOpenGLCompositing()) return data->index >= data->region.rectCount(); // run once per each area #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) return data->index == 1; // run once #endif abort(); } void PaintClipper::Iterator::next() { data->index++; } QRect PaintClipper::Iterator::boundingRect() const { if (!clip()) return infiniteRegion(); if (effects->isOpenGLCompositing()) return *(data->region.begin() + data->index); #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) return data->region.boundingRect(); #endif abort(); return infiniteRegion(); } /*************************************************************** Motion1D ***************************************************************/ Motion1D::Motion1D(double initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion1D::Motion1D(const Motion1D &other) : Motion(other) { } Motion1D::~Motion1D() { } /*************************************************************** Motion2D ***************************************************************/ Motion2D::Motion2D(QPointF initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion2D::Motion2D(const Motion2D &other) : Motion(other) { } Motion2D::~Motion2D() { } /*************************************************************** WindowMotionManager ***************************************************************/ WindowMotionManager::WindowMotionManager(bool useGlobalAnimationModifier) : m_useGlobalAnimationModifier(useGlobalAnimationModifier) { // TODO: Allow developer to modify motion attributes } // TODO: What happens when the window moves by an external force? WindowMotionManager::~WindowMotionManager() { } void WindowMotionManager::manage(EffectWindow *w) { if (m_managedWindows.contains(w)) return; double strength = 0.08; double smoothness = 4.0; if (m_useGlobalAnimationModifier && effects->animationTimeFactor()) { // If the factor is == 0 then we just skip the calculation completely strength = 0.08 / effects->animationTimeFactor(); smoothness = effects->animationTimeFactor() * 4.0; } WindowMotion &motion = m_managedWindows[ w ]; motion.translation.setStrength(strength); motion.translation.setSmoothness(smoothness); motion.scale.setStrength(strength * 1.33); motion.scale.setSmoothness(smoothness / 2.0); motion.translation.setValue(w->pos()); motion.scale.setValue(QPointF(1.0, 1.0)); } void WindowMotionManager::unmanage(EffectWindow *w) { m_movingWindowsSet.remove(w); m_managedWindows.remove(w); } void WindowMotionManager::unmanageAll() { m_managedWindows.clear(); m_movingWindowsSet.clear(); } void WindowMotionManager::calculate(int time) { if (!effects->animationTimeFactor()) { // Just skip it completely if the user wants no animation m_movingWindowsSet.clear(); QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); motion->translation.finish(); motion->scale.finish(); } } QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); int stopped = 0; // TODO: What happens when distance() == 0 but we are still moving fast? // TODO: Motion needs to be calculated from the window's center Motion2D *trans = &motion->translation; if (trans->distance().isNull()) ++stopped; else { // Still moving trans->calculate(time); const short fx = trans->target().x() <= trans->startValue().x() ? -1 : 1; const short fy = trans->target().y() <= trans->startValue().y() ? -1 : 1; if (trans->distance().x()*fx/0.5 < 1.0 && trans->velocity().x()*fx/0.2 < 1.0 && trans->distance().y()*fy/0.5 < 1.0 && trans->velocity().y()*fy/0.2 < 1.0) { // Hide tiny oscillations motion->translation.finish(); ++stopped; } } Motion2D *scale = &motion->scale; if (scale->distance().isNull()) ++stopped; else { // Still scaling scale->calculate(time); const short fx = scale->target().x() < 1.0 ? -1 : 1; const short fy = scale->target().y() < 1.0 ? -1 : 1; if (scale->distance().x()*fx/0.001 < 1.0 && scale->velocity().x()*fx/0.05 < 1.0 && scale->distance().y()*fy/0.001 < 1.0 && scale->velocity().y()*fy/0.05 < 1.0) { // Hide tiny oscillations motion->scale.finish(); ++stopped; } } // We just finished this window's motion if (stopped == 2) m_movingWindowsSet.remove(it.key()); } } void WindowMotionManager::reset() { QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); EffectWindow *window = it.key(); motion->translation.setTarget(window->pos()); motion->translation.finish(); motion->scale.setTarget(QPointF(1.0, 1.0)); motion->scale.finish(); } } void WindowMotionManager::reset(EffectWindow *w) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; WindowMotion *motion = &it.value(); motion->translation.setTarget(w->pos()); motion->translation.finish(); motion->scale.setTarget(QPointF(1.0, 1.0)); motion->scale.finish(); } void WindowMotionManager::apply(EffectWindow *w, WindowPaintData &data) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) WindowMotion *motion = &it.value(); data += (motion->translation.value() - QPointF(w->x(), w->y())); data *= QVector2D(motion->scale.value()); } void WindowMotionManager::moveWindow(EffectWindow *w, QPoint target, double scale, double yScale) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) abort(); // Notify the effect author that they did something wrong WindowMotion *motion = &it.value(); if (yScale == 0.0) yScale = scale; QPointF scalePoint(scale, yScale); if (motion->translation.value() == target && motion->scale.value() == scalePoint) return; // Window already at that position motion->translation.setTarget(target); motion->scale.setTarget(scalePoint); m_movingWindowsSet << w; } QRectF WindowMotionManager::transformedGeometry(EffectWindow *w) const { QHash::const_iterator it = m_managedWindows.constFind(w); if (it == m_managedWindows.end()) return w->geometry(); const WindowMotion *motion = &it.value(); QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(motion->translation.value()); geometry.setWidth(geometry.width() * motion->scale.value().x()); geometry.setHeight(geometry.height() * motion->scale.value().y()); return geometry; } void WindowMotionManager::setTransformedGeometry(EffectWindow *w, const QRectF &geometry) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; WindowMotion *motion = &it.value(); motion->translation.setValue(geometry.topLeft()); motion->scale.setValue(QPointF(geometry.width() / qreal(w->width()), geometry.height() / qreal(w->height()))); } QRectF WindowMotionManager::targetGeometry(EffectWindow *w) const { QHash::const_iterator it = m_managedWindows.constFind(w); if (it == m_managedWindows.end()) return w->geometry(); const WindowMotion *motion = &it.value(); QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(motion->translation.target()); geometry.setWidth(geometry.width() * motion->scale.target().x()); geometry.setHeight(geometry.height() * motion->scale.target().y()); return geometry; } EffectWindow* WindowMotionManager::windowAtPoint(QPoint point, bool useStackingOrder) const { Q_UNUSED(useStackingOrder); // TODO: Stacking order uses EffectsHandler::stackingOrder() then filters by m_managedWindows QHash< EffectWindow*, WindowMotion >::ConstIterator it = m_managedWindows.constBegin(); while (it != m_managedWindows.constEnd()) { if (transformedGeometry(it.key()).contains(point)) return it.key(); ++it; } return nullptr; } /*************************************************************** EffectFramePrivate ***************************************************************/ class EffectFramePrivate { public: EffectFramePrivate(); ~EffectFramePrivate(); bool crossFading; qreal crossFadeProgress; QMatrix4x4 screenProjectionMatrix; }; EffectFramePrivate::EffectFramePrivate() : crossFading(false) , crossFadeProgress(1.0) { } EffectFramePrivate::~EffectFramePrivate() { } /*************************************************************** EffectFrame ***************************************************************/ EffectFrame::EffectFrame() : d(new EffectFramePrivate) { } EffectFrame::~EffectFrame() { delete d; } qreal EffectFrame::crossFadeProgress() const { return d->crossFadeProgress; } void EffectFrame::setCrossFadeProgress(qreal progress) { d->crossFadeProgress = progress; } bool EffectFrame::isCrossFade() const { return d->crossFading; } void EffectFrame::enableCrossFade(bool enable) { d->crossFading = enable; } QMatrix4x4 EffectFrame::screenProjectionMatrix() const { return d->screenProjectionMatrix; } void EffectFrame::setScreenProjectionMatrix(const QMatrix4x4 &spm) { d->screenProjectionMatrix = spm; } /*************************************************************** TimeLine ***************************************************************/ class Q_DECL_HIDDEN TimeLine::Data : public QSharedData { public: std::chrono::milliseconds duration; Direction direction; QEasingCurve easingCurve; std::chrono::milliseconds elapsed = std::chrono::milliseconds::zero(); bool done = false; }; TimeLine::TimeLine(std::chrono::milliseconds duration, Direction direction) : d(new Data) { Q_ASSERT(duration > std::chrono::milliseconds::zero()); d->duration = duration; d->direction = direction; } TimeLine::TimeLine(const TimeLine &other) : d(other.d) { } TimeLine::~TimeLine() = default; qreal TimeLine::progress() const { return static_cast(d->elapsed.count()) / d->duration.count(); } qreal TimeLine::value() const { const qreal t = progress(); return d->easingCurve.valueForProgress( d->direction == Backward ? 1.0 - t : t); } void TimeLine::update(std::chrono::milliseconds delta) { Q_ASSERT(delta >= std::chrono::milliseconds::zero()); if (d->done) { return; } d->elapsed += delta; if (d->elapsed >= d->duration) { d->done = true; d->elapsed = d->duration; } } std::chrono::milliseconds TimeLine::elapsed() const { return d->elapsed; } void TimeLine::setElapsed(std::chrono::milliseconds elapsed) { Q_ASSERT(elapsed >= std::chrono::milliseconds::zero()); if (elapsed == d->elapsed) { return; } reset(); update(elapsed); } std::chrono::milliseconds TimeLine::duration() const { return d->duration; } void TimeLine::setDuration(std::chrono::milliseconds duration) { Q_ASSERT(duration > std::chrono::milliseconds::zero()); if (duration == d->duration) { return; } d->elapsed = std::chrono::milliseconds(qRound(progress() * duration.count())); d->duration = duration; if (d->elapsed == d->duration) { d->done = true; } } TimeLine::Direction TimeLine::direction() const { return d->direction; } void TimeLine::setDirection(TimeLine::Direction direction) { if (d->direction == direction) { return; } if (d->elapsed > std::chrono::milliseconds::zero()) { d->elapsed = d->duration - d->elapsed; } d->direction = direction; } void TimeLine::toggleDirection() { setDirection(d->direction == Forward ? Backward : Forward); } QEasingCurve TimeLine::easingCurve() const { return d->easingCurve; } void TimeLine::setEasingCurve(const QEasingCurve &easingCurve) { d->easingCurve = easingCurve; } void TimeLine::setEasingCurve(QEasingCurve::Type type) { d->easingCurve.setType(type); } bool TimeLine::running() const { return d->elapsed != std::chrono::milliseconds::zero() && d->elapsed != d->duration; } bool TimeLine::done() const { return d->done; } void TimeLine::reset() { d->elapsed = std::chrono::milliseconds::zero(); d->done = false; } TimeLine &TimeLine::operator=(const TimeLine &other) { d = other.d; return *this; } } // namespace diff --git a/shell_client.cpp b/shell_client.cpp index f363a0440..83e58eef2 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -1,1947 +1,1950 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin Copyright (C) 2018 David Edmundson 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 . *********************************************************************/ #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 "screens.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KWayland::Server; 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 void ShellClient::initSurface(T *shellSurface) { m_caption = shellSurface->title().simplified(); // delay till end of init QTimer::singleShot(0, this, &ShellClient::updateCaption); connect(shellSurface, &T::destroyed, this, &ShellClient::destroyClient); connect(shellSurface, &T::titleChanged, this, [this] (const QString &s) { const auto oldSuffix = m_captionSuffix; m_caption = s.simplified(); updateCaption(); if (m_captionSuffix == oldSuffix) { // don't emit caption change twice // it already got emitted by the changing suffix emit captionChanged(); } } ); connect(shellSurface, &T::moveRequested, this, [this] { // TODO: check the seat and serial performMouseCommand(Options::MouseMove, Cursor::pos()); } ); // determine the resource name, this is inspired from ICCCM 4.1.2.5 // the binary name of the invoked client QFileInfo info{shellSurface->client()->executablePath()}; QByteArray resourceName; if (info.exists()) { resourceName = info.fileName().toUtf8(); } setResourceClass(resourceName, shellSurface->windowClass()); connect(shellSurface, &T::windowClassChanged, this, [this, resourceName] (const QByteArray &windowClass) { setResourceClass(resourceName, windowClass); if (!m_internal) { setupWindowRules(true); applyWindowRules(); } 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); connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateClientOutputs); connect(screens(), &Screens::changed, this, &ShellClient::updateClientOutputs); if (!m_internal) { setupWindowRules(false); } setDesktopFileName(rules()->checkDesktopFile(shellSurface->windowClass(), true).toUtf8()); } void ShellClient::init() { connect(this, &ShellClient::desktopFileNameChanged, this, &ShellClient::updateIcon); findInternalWindow(); createWindowId(); setupCompositing(); updateIcon(); 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(); updateDecoration(true); } else { doSetGeometry(QRect(QPoint(0, 0), m_clientSize)); } 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); auto setPopup = [this] { // TODO: verify grab serial m_hasPopupGrab = m_shellSurface->isPopup(); }; connect(m_shellSurface, &ShellSurfaceInterface::popupChanged, this, setPopup); setPopup(); } else if (m_xdgShellSurface) { initSurface(m_xdgShellSurface); auto global = static_cast(m_xdgShellSurface->global()); connect(global, &XdgShellInterface::pingDelayed, this, [this](qint32 serial) { auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); setUnresponsive(true); } }); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::configureAcknowledged, this, [this](int serial) { m_lastAckedConfigureRequest = serial; }); connect(global, &XdgShellInterface::pingTimeout, this, [this](qint32 serial) { auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { if (it.value() == PingReason::CloseWindow) { qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption(); //for internal windows, killing the window will delete this QPointer guard(this); killWindow(); if (!guard) { return; } } m_pingSerials.erase(it); } }); connect(global, &XdgShellInterface::pongReceived, this, [this](qint32 serial){ auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { setUnresponsive(false); m_pingSerials.erase(it); } }); 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; } if (m_requestGeometryBlockCounter != 0 || areGeometryUpdatesBlocked()) { return; } m_xdgShellSurface->configure(xdgSurfaceStates(), m_requestedClientSize); }; configure(); 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::grabRequested, this, [this](SeatInterface *seat, quint32 serial) { Q_UNUSED(seat) Q_UNUSED(serial) //TODO - should check the parent had focus m_hasPopupGrab = true; }); connect(m_xdgShellPopup, &XdgShellPopupInterface::configureAcknowledged, this, [this](int serial) { m_lastAckedConfigureRequest = serial; }); QRect position = QRect(m_xdgShellPopup->transientOffset(), m_xdgShellPopup->initialSize()); m_xdgShellPopup->configure(position); connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &ShellClient::destroyClient); } // set initial desktop setDesktop(rules()->checkDesktop(m_internal ? int(NET::OnAllDesktops) : VirtualDesktopManager::self()->current(), true)); // TODO: merge in checks from Client::manage? if (rules()->checkMinimize(false, true)) { minimize(true); // No animation } setSkipTaskbar(rules()->checkSkipTaskbar(m_plasmaShellSurface ? m_plasmaShellSurface->skipTaskbar() : false, true)); setSkipPager(rules()->checkSkipPager(false, true)); setSkipSwitcher(rules()->checkSkipSwitcher(false, true)); setKeepAbove(rules()->checkKeepAbove(false, true)); setKeepBelow(rules()->checkKeepBelow(false, true)); setShortcut(rules()->checkShortcut(QString(), true)); // setup shadow integration getShadow(); connect(s, &SurfaceInterface::shadowChanged, this, &Toplevel::getShadow); connect(waylandServer(), &WaylandServer::foreignTransientChanged, this, [this](KWayland::Server::SurfaceInterface *child) { if (child == surface()) { setTransient(); } }); setTransient(); // check whether we have a ServerSideDecoration if (ServerSideDecorationInterface *deco = ServerSideDecorationInterface::get(s)) { installServerSideDecoration(deco); } AbstractClient::updateColorScheme(QString()); if (!m_internal) { discardTemporaryRules(); applyWindowRules(); // Just in case RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules updateWindowRules(Rules::All); // Was blocked while !isManaged() } } 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 { stream.nospace(); stream << "\'ShellClient:" << surface() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } 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(); updatePendingGeometry(); } markAsMapped(); setDepth((s->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24); repaints_region += damage.translated(clientPos()); Toplevel::addDamage(damage); } void ShellClient::setInternalFramebufferObject(const QSharedPointer &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) { if (areGeometryUpdatesBlocked()) { // when the GeometryUpdateBlocker exits the current geom is passed to setGeometry // thus we need to set it here. geom = QRect(x, y, w, h); if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } if (pendingGeometryUpdate() != PendingGeometryNone) { // reset geometry to the one before blocking, so that we can compare properly geom = geometryBeforeUpdateBlocking(); } // TODO: better merge with Client's implementation if (QSize(w, h) == geom.size() && !isWaitingForMoveResizeSync()) { // size didn't change, update directly doSetGeometry(QRect(x, y, w, h)); updateMaximizeMode(m_requestedMaximizeMode); } 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()); } 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(); } const auto old = geometryBeforeUpdateBlocking(); updateGeometryBeforeUpdateBlocking(); 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) { // delay to end of cycle to prevent freeze, see BUG 384441 QTimer::singleShot(0, m_internalWindow, std::bind(static_cast(&QWindow::setGeometry), m_internalWindow, windowRect)); } } QByteArray ShellClient::windowRole() const { return QByteArray(); } bool ShellClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const { if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) { if (other->desktopFileName() == desktopFileName()) { return true; } } if (auto s = other->surface()) { return s->client() == surface()->client(); } return false; } void ShellClient::blockActivityUpdates(bool b) { Q_UNUSED(b) } void ShellClient::updateCaption() { const QString oldSuffix = m_captionSuffix; const auto shortcut = shortcutCaptionSuffix(); m_captionSuffix = shortcut; if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { int i = 2; do { m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>'); i++; } while (findClientWithSameCaption()); } if (m_captionSuffix != oldSuffix) { emit captionChanged(); } } void ShellClient::closeWindow() { if (m_xdgShellSurface && isCloseable()) { m_xdgShellSurface->close(); const qint32 pingSerial = static_cast(m_xdgShellSurface->global())->ping(m_xdgShellSurface); m_pingSerials.insert(pingSerial, PingReason::CloseWindow); } else if (m_qtExtendedSurface && isCloseable()) { m_qtExtendedSurface->close(); } else 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::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); const MaximizeMode oldMode = m_requestedMaximizeMode; const QRect oldGeometry = geometry(); 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_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeVertical); if (horizontal) m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal); } // TODO: add more checks as in Client // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical); } if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal); } if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { emit c->maximizedChanged(m_requestedMaximizeMode & 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_requestedMaximizeMode == MaximizeFull)); changeMaximizeRecursion = false; } // Conditional quick tiling exit points const auto oldQuickTileMode = quickTileMode(); if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { 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_requestedMaximizeMode == MaximizeRestore) || (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry } } // TODO: check rules if (m_requestedMaximizeMode == MaximizeFull) { m_geomMaximizeRestore = oldGeometry; // TODO: Client has more checks if (options->electricBorderMaximize()) { updateQuickTileMode(QuickTileFlag::Maximize); } else { updateQuickTileMode(QuickTileFlag::None); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } setGeometry(workspace()->clientArea(MaximizeArea, this)); workspace()->raiseClient(this); } else { if (m_requestedMaximizeMode == MaximizeRestore) { updateQuickTileMode(QuickTileFlag::None); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } if (m_geomMaximizeRestore.isValid()) { setGeometry(m_geomMaximizeRestore); } else { setGeometry(workspace()->clientArea(PlacementArea, this)); } } } MaximizeMode ShellClient::maximizeMode() const { return m_maximizeMode; } MaximizeMode ShellClient::requestedMaximizeMode() const { return m_requestedMaximizeMode; } 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; } void ShellClient::setFullScreen(bool set, bool user) { if (!isFullScreen() && !set) return; if (user && !userCanSetFullScreen()) return; set = rules()->checkFullScreen(set && !isSpecialWindow()); setShade(ShadeNone); bool was_fs = isFullScreen(); if (was_fs) workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event else m_geomFsRestore = geometry(); m_fullScreen = set; if (was_fs == isFullScreen()) return; if (set) { untab(); workspace()->raiseClient(this); } RequestGeometryBlocker requestBlocker(this); StackingUpdatesBlocker blocker1(workspace()); GeometryUpdatesBlocker blocker2(this); workspace()->updateClientLayer(this); // active fullscreens get different layer updateDecoration(false, false); if (isFullScreen()) { setGeometry(workspace()->clientArea(FullScreenArea, this)); } else { if (!m_geomFsRestore.isNull()) { int currentScreen = screen(); setGeometry(QRect(m_geomFsRestore.topLeft(), adjustedSize(m_geomFsRestore.size()))); if( currentScreen != screen()) workspace()->sendClientToScreen( this, currentScreen ); } else { // does this ever happen? setGeometry(workspace()->clientArea(MaximizeArea, this)); } } updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); if (was_fs != isFullScreen()) { emit fullScreenChanged(); } } 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())) { if (m_xdgShellSurface) { const qint32 pingSerial = static_cast(m_xdgShellSurface->global())->ping(m_xdgShellSurface); m_pingSerials.insert(pingSerial, PingReason::FocusWindow); } 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, SameApplicationChecks())) { continue; } if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } void ShellClient::doSetActive() { if (!isActive()) { return; } StackingUpdatesBlocker blocker(workspace()); workspace()->focusToNull(); } bool ShellClient::userCanSetFullScreen() const { if (m_xdgShellSurface) { return true; } 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(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; } PendingConfigureRequest configureRequest; configureRequest.positionAfterResize = rect.topLeft(); configureRequest.maximizeMode = m_requestedMaximizeMode; const QSize size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); m_requestedClientSize = QSize(0, 0); if (m_shellSurface) { m_shellSurface->requestSize(size); } if (m_xdgShellSurface) { configureRequest.serialId = m_xdgShellSurface->configure(xdgSurfaceStates(), size); } if (m_xdgShellPopup) { auto parent = transientFor(); if (parent) { const QPoint globalClientContentPos = parent->geometry().topLeft() + parent->clientPos(); const QPoint relativeOffset = rect.topLeft() -globalClientContentPos; configureRequest.serialId = m_xdgShellPopup->configure(QRect(relativeOffset, rect.size())); } } m_pendingConfigureRequests.append(configureRequest); m_blockedRequestGeometry = QRect(); if (m_internal) { m_internalWindow->setGeometry(QRect(rect.topLeft() + QPoint(borderLeft(), borderTop()), rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } } void ShellClient::updatePendingGeometry() { QPoint position = geom.topLeft(); MaximizeMode maximizeMode = m_maximizeMode; for (auto it = m_pendingConfigureRequests.begin(); it != m_pendingConfigureRequests.end(); it++) { if (it->serialId > m_lastAckedConfigureRequest) { //this serial is not acked yet, therefore we know all future serials are not break; } if (it->serialId == m_lastAckedConfigureRequest) { if (position != it->positionAfterResize) { addLayerRepaint(geometry()); } position = it->positionAfterResize; maximizeMode = it->maximizeMode; m_pendingConfigureRequests.erase(m_pendingConfigureRequests.begin(), ++it); break; } //else serialId < m_lastAckedConfigureRequest and the state is now irrelevant and can be ignored } doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); updateMaximizeMode(maximizeMode); } void ShellClient::clientFullScreenChanged(bool fullScreen) { setFullScreen(fullScreen, false); } 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; m_requestedClientSize = QSize(); destroyWindowManagementInterface(); if (Workspace::self()) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } emit windowHidden(this); } void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface) { m_plasmaShellSurface = surface; auto updatePosition = [this, surface] { QRect rect = QRect(surface->position(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom())); // Shell surfaces of internal windows are sometimes desync to current value. // Make sure to not set window geometry of internal windows to invalid values (bug 386304) if (!m_internal || rect.isValid()) { doSetGeometry(rect); } }; 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()); }); setSkipSwitcher(surface->skipSwitcher()); connect(surface, &PlasmaShellSurfaceInterface::skipSwitcherChanged, this, [this] { setSkipSwitcher(m_plasmaShellSurface->skipSwitcher()); }); } 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); } void ShellClient::installAppMenu(AppMenuInterface *menu) { m_appMenuInterface = menu; auto updateMenu = [this](AppMenuInterface::InterfaceAddress address) { updateApplicationMenuServiceName(address.serviceName); updateApplicationMenuObjectPath(address.objectPath); }; connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, [=](AppMenuInterface::InterfaceAddress address) { updateMenu(address); }); updateMenu(menu->address()); } void ShellClient::installPalette(ServerSideDecorationPaletteInterface *palette) { m_paletteInterface = palette; auto updatePalette = [this](const QString &palette) { AbstractClient::updateColorScheme(rules()->checkDecoColor(palette)); }; connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged, this, [=](const QString &palette) { updatePalette(palette); }); connect(m_paletteInterface, &QObject::destroyed, this, [=]() { updatePalette(QString()); }); updatePalette(palette->palette()); } bool ShellClient::eventFilter(QObject *watched, QEvent *event) { if (watched == m_internalWindow && event->type() == QEvent::DynamicPropertyChange) { QDynamicPropertyChangeEvent *pe = static_cast(event); if (pe->propertyName() == s_skipClosePropertyName) { setSkipCloseAnimation(m_internalWindow->property(s_skipClosePropertyName).toBool()); } } return false; } void ShellClient::updateColorScheme() { if (m_paletteInterface) { AbstractClient::updateColorScheme(rules()->checkDecoColor(m_paletteInterface->palette())); } else { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString())); } } void ShellClient::updateMaximizeMode(MaximizeMode maximizeMode) { if (maximizeMode == m_maximizeMode) { return; } m_maximizeMode = maximizeMode; emit clientMaximizedStateChanged(this, m_maximizeMode); emit clientMaximizedStateChanged(this, m_maximizeMode & MaximizeHorizontal, m_maximizeMode & MaximizeVertical); } 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(); } if (!s) { s = waylandServer()->findForeignTransientForSurface(surface()); } 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 && (m_shellSurface || m_xdgShellPopup); } QRect ShellClient::transientPlacement(const QRect &bounds) const { QRect anchorRect; Qt::Edges anchorEdge; Qt::Edges gravity; QPoint offset; PositionerConstraints constraintAdjustments; const QPoint parentClientPos = transientFor()->pos() + transientFor()->clientPos(); QRect popupPosition; // returns if a target is within the supplied bounds, optional edges argument states which side to check auto inBounds = [bounds](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool { if (edges & Qt::LeftEdge && target.left() < bounds.left()) { return false; } if (edges & Qt::TopEdge && target.top() < bounds.top()) { return false; } if (edges & Qt::RightEdge && target.right() > bounds.right()) { //normal QRect::right issue cancels out return false; } if (edges & Qt::BottomEdge && target.bottom() > bounds.bottom()) { return false; } return true; }; if (m_shellSurface) { anchorRect = QRect(m_shellSurface->transientOffset(), QSize(1,1)); anchorEdge = Qt::TopEdge | Qt::LeftEdge; gravity = Qt::BottomEdge | Qt::RightEdge; //our single point represents the top left of the popup constraintAdjustments = (PositionerConstraint::SlideX | PositionerConstraint::SlideY); } else if (m_xdgShellPopup) { anchorRect = m_xdgShellPopup->anchorRect(); anchorEdge = m_xdgShellPopup->anchorEdge(); gravity = m_xdgShellPopup->gravity(); offset = m_xdgShellPopup->anchorOffset(); constraintAdjustments = m_xdgShellPopup->constraintAdjustments(); } else { Q_UNREACHABLE(); } //initial position popupPosition = QRect(popupOffset(anchorRect, anchorEdge, gravity) + offset + parentClientPos, geometry().size()); //if that fits, we don't need to do anything if (inBounds(popupPosition)) { return popupPosition; } //otherwise apply constraint adjustment per axis in order XDG Shell Popup states if (constraintAdjustments & PositionerConstraint::FlipX) { if (!inBounds(popupPosition, Qt::LeftEdge | Qt::RightEdge)) { //flip both edges (if either bit is set, XOR both) auto flippedAnchorEdge = anchorEdge; if (flippedAnchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { flippedAnchorEdge ^= (Qt::LeftEdge | Qt::RightEdge); } auto flippedGravity = gravity; if (flippedGravity & (Qt::LeftEdge | Qt::RightEdge)) { flippedGravity ^= (Qt::LeftEdge | Qt::RightEdge); } auto flippedPopupPosition = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity) + offset + parentClientPos, geometry().size()); //if it still doesn't fit we should continue with the unflipped version if (inBounds(flippedPopupPosition, Qt::LeftEdge | Qt::RightEdge)) { popupPosition.setX(flippedPopupPosition.x()); } } } if (constraintAdjustments & PositionerConstraint::SlideX) { if (!inBounds(popupPosition, Qt::LeftEdge)) { popupPosition.setX(bounds.x()); } if (!inBounds(popupPosition, Qt::RightEdge)) { popupPosition.setX(bounds.x() + bounds.width() - geometry().width()); } } if (constraintAdjustments & PositionerConstraint::ResizeX) { //TODO //but we need to sort out when this is run as resize should only happen before first configure } if (constraintAdjustments & PositionerConstraint::FlipY) { if (!inBounds(popupPosition, Qt::TopEdge | Qt::BottomEdge)) { //flip both edges (if either bit is set, XOR both) auto flippedAnchorEdge = anchorEdge; if (flippedAnchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { flippedAnchorEdge ^= (Qt::TopEdge | Qt::BottomEdge); } auto flippedGravity = gravity; if (flippedGravity & (Qt::TopEdge | Qt::BottomEdge)) { flippedGravity ^= (Qt::TopEdge | Qt::BottomEdge); } auto flippedPopupPosition = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity) + offset + parentClientPos, geometry().size()); //if it still doesn't fit we should continue with the unflipped version if (inBounds(flippedPopupPosition, Qt::TopEdge | Qt::BottomEdge)) { popupPosition.setY(flippedPopupPosition.y()); } } } if (constraintAdjustments & PositionerConstraint::SlideY) { if (!inBounds(popupPosition, Qt::TopEdge)) { popupPosition.setY(bounds.y()); } if (!inBounds(popupPosition, Qt::BottomEdge)) { popupPosition.setY(bounds.y() + bounds.height() - geometry().height()); } } if (constraintAdjustments & PositionerConstraint::ResizeY) { //TODO } return popupPosition; } QPoint ShellClient::popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity) const { const QSize popupSize = geometry().size(); QPoint anchorPoint; switch (anchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { case Qt::LeftEdge: anchorPoint.setX(anchorRect.x()); break; case Qt::RightEdge: anchorPoint.setX(anchorRect.x() + anchorRect.width()); break; default: anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0)); } switch (anchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { case Qt::TopEdge: anchorPoint.setY(anchorRect.y()); break; case Qt::BottomEdge: anchorPoint.setY(anchorRect.y() + anchorRect.height()); break; default: anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0)); } // calculate where the top left point of the popup will end up with the applied gravity // gravity indicates direction. i.e if gravitating towards the top the popup's bottom edge // will next to the anchor point QPoint popupPosAdjust; switch (gravity & (Qt::LeftEdge | Qt::RightEdge)) { case Qt::LeftEdge: popupPosAdjust.setX(-popupSize.width()); break; case Qt::RightEdge: popupPosAdjust.setX(0); break; default: popupPosAdjust.setX(qRound(-popupSize.width() / 2.0)); } switch (gravity & (Qt::TopEdge | Qt::BottomEdge)) { case Qt::TopEdge: popupPosAdjust.setY(-popupSize.height()); break; case Qt::BottomEdge: popupPosAdjust.setY(0); break; default: popupPosAdjust.setY(qRound(-popupSize.height() / 2.0)); } return anchorPoint + popupPosAdjust; } bool ShellClient::isWaitingForMoveResizeSync() const { return !m_pendingConfigureRequests.isEmpty(); } 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(); if (changed && !m_unmapped) { updateDecoration(false); } } ); } bool ShellClient::shouldExposeToWindowManagement() { if (isInternal()) { return false; } if (isLockScreen()) { return false; } if (m_xdgShellPopup) { 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 (m_requestedMaximizeMode == 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->processId() == 0) { 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); } bool ShellClient::hasPopupGrab() const { return m_hasPopupGrab; } void ShellClient::popupDone() { if (m_shellSurface) { m_shellSurface->popupDone(); } if (m_xdgShellPopup) { m_xdgShellPopup->popupDone(); } } void ShellClient::updateClientOutputs() { QVector clientOutputs; const auto outputs = waylandServer()->display()->outputs(); for (OutputInterface* output: qAsConst(outputs)) { const QRect outputGeom(output->globalPosition(), output->pixelSize() / output->scale()); if (geometry().intersects(outputGeom)) { clientOutputs << output; } } surface()->setOutputs(clientOutputs); } bool ShellClient::isPopupWindow() const { + if (Toplevel::isPopupWindow()) { + return true; + } if (m_shellSurface != nullptr) { return m_shellSurface->isPopup(); } if (m_xdgShellPopup != nullptr) { return true; } return false; } }