diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69270754e5..dd54a7ae4a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -105,6 +105,7 @@ set(kwin_SRCS scripting/scripting_logging.cpp scripting/scripting_model.cpp scripting/scriptingutils.cpp + scripting/thumbnailitem.cpp scripting/workspace_wrapper.cpp session.cpp session_consolekit.cpp @@ -120,7 +121,6 @@ set(kwin_SRCS surfaceitem_x11.cpp syncalarmx11filter.cpp tablet_input.cpp - thumbnailitem.cpp toplevel.cpp touch_hide_cursor_spy.cpp touch_input.cpp diff --git a/src/effects.cpp b/src/effects.cpp index b341d04d38..b26dad7ab5 100644 --- a/src/effects.cpp +++ b/src/effects.cpp @@ -31,7 +31,6 @@ #include "scripting/scriptedeffect.h" #include "screens.h" #include "screenlockerwatcher.h" -#include "thumbnailitem.h" #include "virtualdesktops.h" #include "window_property_notify_x11_filter.h" #include "workspace.h" @@ -39,6 +38,8 @@ #include "kwineffectquickview.h" #include +#include +#include #include @@ -2098,47 +2099,6 @@ void EffectWindowImpl::elevate(bool elevate) effects->setElevatedWindow(this, elevate); } -void EffectWindowImpl::registerThumbnail(AbstractThumbnailItem *item) -{ - if (WindowThumbnailItem *thumb = qobject_cast(item)) { - insertThumbnail(thumb); - connect(thumb, &QObject::destroyed, this, &EffectWindowImpl::thumbnailDestroyed); - connect(thumb, &WindowThumbnailItem::wIdChanged, this, &EffectWindowImpl::thumbnailTargetChanged); - } else if (DesktopThumbnailItem *desktopThumb = qobject_cast(item)) { - m_desktopThumbnails.append(desktopThumb); - connect(desktopThumb, &QObject::destroyed, this, &EffectWindowImpl::desktopThumbnailDestroyed); - } -} - -void EffectWindowImpl::thumbnailDestroyed(QObject *object) -{ - // we know it is a ThumbnailItem - m_thumbnails.remove(static_cast(object)); -} - -void EffectWindowImpl::thumbnailTargetChanged() -{ - if (WindowThumbnailItem *item = qobject_cast(sender())) { - insertThumbnail(item); - } -} - -void EffectWindowImpl::insertThumbnail(WindowThumbnailItem *item) -{ - EffectWindow *w = effects->findWindow(item->wId()); - if (w) { - m_thumbnails.insert(item, QPointer(static_cast(w))); - } else { - m_thumbnails.insert(item, QPointer()); - } -} - -void EffectWindowImpl::desktopThumbnailDestroyed(QObject *object) -{ - // we know it is a DesktopThumbnailItem - m_desktopThumbnails.removeAll(static_cast(object)); -} - void EffectWindowImpl::minimize() { if (auto client = qobject_cast(toplevel)) { diff --git a/src/effects.h b/src/effects.h index ee9ed48f8b..f7427bd2ab 100644 --- a/src/effects.h +++ b/src/effects.h @@ -35,11 +35,6 @@ class QDBusServiceWatcher; namespace KWin { - -class AbstractThumbnailItem; -class DesktopThumbnailItem; -class WindowThumbnailItem; - class AbstractClient; class Compositor; class Deleted; @@ -508,25 +503,10 @@ public: void setData(int role, const QVariant &data) override; QVariant data(int role) const override; - void registerThumbnail(AbstractThumbnailItem *item); - QHash > const &thumbnails() const { - return m_thumbnails; - } - QList const &desktopThumbnails() const { - return m_desktopThumbnails; - } - -private Q_SLOTS: - void thumbnailDestroyed(QObject *object); - void thumbnailTargetChanged(); - void desktopThumbnailDestroyed(QObject *object); private: - void insertThumbnail(WindowThumbnailItem *item); Toplevel* toplevel; Scene::Window* sw; // This one is used only during paint pass. QHash dataMap; - QHash > m_thumbnails; - QList m_desktopThumbnails; bool managed = false; bool waylandClient; bool x11Client; diff --git a/src/scene.cpp b/src/scene.cpp index 9b4ad518a2..97a62dee91 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -74,7 +74,6 @@ #include "screens.h" #include "shadow.h" #include "wayland_server.h" -#include "thumbnailitem.h" #include "composite.h" namespace KWin @@ -484,8 +483,6 @@ void Scene::clearStackingOrder() stacking_order.clear(); } -static Scene::Window *s_recursionCheck = nullptr; - void Scene::paintWindow(Window* w, int mask, const QRegion &_region) { // no painting outside visible screen (and no transformations) @@ -497,129 +494,8 @@ void Scene::paintWindow(Window* w, int mask, const QRegion &_region) return; } - if (s_recursionCheck == w) { - return; - } - WindowPaintData data(w->window()->effectWindow(), screenProjectionMatrix()); effects->paintWindow(effectWindow(w), mask, region, data); - // paint thumbnails on top of window - paintWindowThumbnails(w, region, data.opacity(), data.brightness(), data.saturation()); - // and desktop thumbnails - paintDesktopThumbnails(w); -} - -static void adjustClipRegion(AbstractThumbnailItem *item, QRegion &clippingRegion) -{ - if (item->clip() && item->clipTo()) { - // the x/y positions of the parent item are not correct. The margins are added, though the size seems fine - // that's why we have to get the offset by inspecting the anchors properties - QQuickItem *parentItem = item->clipTo(); - QPointF offset; - QVariant anchors = parentItem->property("anchors"); - if (anchors.isValid()) { - if (QObject *anchorsObject = anchors.value()) { - offset.setX(anchorsObject->property("leftMargin").toReal()); - offset.setY(anchorsObject->property("topMargin").toReal()); - } - } - QRectF rect = QRectF(parentItem->position() - offset, QSizeF(parentItem->width(), parentItem->height())); - if (QQuickItem *p = parentItem->parentItem()) { - rect = p->mapRectToScene(rect); - } - clippingRegion &= rect.adjusted(0,0,-1,-1).translated(item->window()->position()).toRect(); - } -} - -void Scene::paintWindowThumbnails(Scene::Window *w, const QRegion ®ion, qreal opacity, qreal brightness, qreal saturation) -{ - EffectWindowImpl *wImpl = static_cast(effectWindow(w)); - for (QHash >::const_iterator it = wImpl->thumbnails().constBegin(); - it != wImpl->thumbnails().constEnd(); - ++it) { - if (it.value().isNull()) { - continue; - } - WindowThumbnailItem *item = it.key(); - if (!item->isVisible()) { - continue; - } - EffectWindowImpl *thumb = it.value().data(); - WindowPaintData thumbData(thumb, screenProjectionMatrix()); - thumbData.setOpacity(opacity); - thumbData.setBrightness(brightness * item->brightness()); - thumbData.setSaturation(saturation * item->saturation()); - - const QRect visualThumbRect(thumb->expandedGeometry()); - - QSizeF size = QSizeF(visualThumbRect.size()); - size.scale(QSizeF(item->width(), item->height()), Qt::KeepAspectRatio); - if (size.width() > visualThumbRect.width() || size.height() > visualThumbRect.height()) { - size = QSizeF(visualThumbRect.size()); - } - thumbData.setXScale(size.width() / static_cast(visualThumbRect.width())); - thumbData.setYScale(size.height() / static_cast(visualThumbRect.height())); - - if (!item->window()) { - continue; - } - const QPointF point = item->mapToScene(QPointF(0,0)); - qreal x = point.x() + w->x() + (item->width() - size.width())/2; - qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; - x -= thumb->x(); - y -= thumb->y(); - // compensate shadow topleft padding - x += (thumb->x()-visualThumbRect.x())*thumbData.xScale(); - y += (thumb->y()-visualThumbRect.y())*thumbData.yScale(); - thumbData.setXTranslation(x); - thumbData.setYTranslation(y); - int thumbMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS; - if (thumbData.opacity() == 1.0) { - thumbMask |= PAINT_WINDOW_OPAQUE; - } else { - thumbMask |= PAINT_WINDOW_TRANSLUCENT; - } - QRegion clippingRegion = region; - clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); - adjustClipRegion(item, clippingRegion); - effects->drawWindow(thumb, thumbMask, clippingRegion, thumbData); - } -} - -void Scene::paintDesktopThumbnails(Scene::Window *w) -{ - EffectWindowImpl *wImpl = static_cast(effectWindow(w)); - for (QList::const_iterator it = wImpl->desktopThumbnails().constBegin(); - it != wImpl->desktopThumbnails().constEnd(); - ++it) { - DesktopThumbnailItem *item = *it; - if (!item->isVisible()) { - continue; - } - if (!item->window()) { - continue; - } - s_recursionCheck = w; - - ScreenPaintData data; - const QSize &screenSize = screens()->size(); - QSize size = screenSize; - - size.scale(item->width(), item->height(), Qt::KeepAspectRatio); - data *= QVector2D(size.width() / double(screenSize.width()), - size.height() / double(screenSize.height())); - const QPointF point = item->mapToScene(item->position()); - const qreal x = point.x() + w->x() + (item->width() - size.width())/2; - const qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; - const QRect region = QRect(x, y, item->width(), item->height()); - QRegion clippingRegion = region; - clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); - adjustClipRegion(item, clippingRegion); - data += QPointF(x, y); - const int desktopMask = PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; - paintDesktop(item->desktop(), desktopMask, clippingRegion, data); - s_recursionCheck = nullptr; - } } void Scene::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) diff --git a/src/scene.h b/src/scene.h index 7b8cd1782c..d411b3b88b 100644 --- a/src/scene.h +++ b/src/scene.h @@ -26,7 +26,6 @@ class DecoratedClientImpl; } class AbstractOutput; -class AbstractThumbnailItem; class DecorationRenderer; class Deleted; class EffectFrameImpl; @@ -193,6 +192,8 @@ public: virtual PlatformSurfaceTexture *createPlatformSurfaceTextureX11(SurfacePixmapX11 *pixmap); virtual PlatformSurfaceTexture *createPlatformSurfaceTextureWayland(SurfacePixmapWayland *pixmap); + virtual void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data); + Q_SIGNALS: void frameRendered(); void resetCompositing(); @@ -236,7 +237,6 @@ protected: // let the scene decide whether it's better to paint more of the screen, eg. in order to allow a buffer swap // the default is NOOP virtual void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen); - virtual void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data); virtual void paintEffectQuickView(EffectQuickView *w) = 0; @@ -263,8 +263,6 @@ protected: // windows in their stacking order QVector< Window* > stacking_order; private: - void paintWindowThumbnails(Scene::Window *w, const QRegion ®ion, qreal opacity, qreal brightness, qreal saturation); - void paintDesktopThumbnails(Scene::Window *w); std::chrono::milliseconds m_expectedPresentTimestamp = std::chrono::milliseconds::zero(); void reallocRepaints(); QHash< Toplevel*, Window* > m_windows; diff --git a/src/scripting/scripting.cpp b/src/scripting/scripting.cpp index 27352af485..27e842bdc0 100644 --- a/src/scripting/scripting.cpp +++ b/src/scripting/scripting.cpp @@ -17,10 +17,10 @@ #include "screenedgeitem.h" #include "scripting_model.h" #include "scripting_logging.h" +#include "thumbnailitem.h" #include "options.h" #include "screenedge.h" -#include "thumbnailitem.h" #include "workspace.h" #include "x11client.h" // KDE diff --git a/src/scripting/thumbnailitem.cpp b/src/scripting/thumbnailitem.cpp new file mode 100644 index 0000000000..58879a9d57 --- /dev/null +++ b/src/scripting/thumbnailitem.cpp @@ -0,0 +1,446 @@ +/* + SPDX-FileCopyrightText: 2011 Martin Gräßlin + SPDX-FileCopyrightText: 2020 David Edmundson + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "thumbnailitem.h" +#include "abstract_client.h" +#include "composite.h" +#include "effects.h" +#include "scene.h" +#include "screens.h" +#include "scripting_logging.h" +#include "workspace.h" + +#include +#include + +#include +#include +#include +#include + +namespace KWin +{ +class ThumbnailTextureProvider : public QSGTextureProvider +{ +public: + explicit ThumbnailTextureProvider(QQuickWindow *window); + + QSGTexture *texture() const override; + void setTexture(const QSharedPointer &nativeTexture); + void setTexture(QSGTexture *texture); + +private: + QQuickWindow *m_window; + QSharedPointer m_nativeTexture; + QScopedPointer m_texture; +}; + +ThumbnailTextureProvider::ThumbnailTextureProvider(QQuickWindow *window) + : m_window(window) +{ +} + +QSGTexture *ThumbnailTextureProvider::texture() const +{ + return m_texture.data(); +} + +void ThumbnailTextureProvider::setTexture(const QSharedPointer &nativeTexture) +{ + if (m_nativeTexture != nativeTexture) { + const GLuint textureId = nativeTexture->texture(); + m_nativeTexture = nativeTexture; + m_texture.reset(m_window->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, + &textureId, 0, + nativeTexture->size(), + QQuickWindow::TextureHasAlphaChannel)); + m_texture->setFiltering(QSGTexture::Linear); + m_texture->setHorizontalWrapMode(QSGTexture::ClampToEdge); + m_texture->setVerticalWrapMode(QSGTexture::ClampToEdge); + } + + // The textureChanged signal must be emitted also if only texture data changes. + Q_EMIT textureChanged(); +} + +void ThumbnailTextureProvider::setTexture(QSGTexture *texture) +{ + m_nativeTexture = nullptr; + m_texture.reset(texture); + Q_EMIT textureChanged(); +} + +class ThumbnailTextureProviderCleanupJob : public QRunnable +{ +public: + explicit ThumbnailTextureProviderCleanupJob(ThumbnailTextureProvider *provider) + : m_provider(provider) + { + } + + void run() override + { + m_provider.reset(); + } + +private: + QScopedPointer m_provider; +}; + +ThumbnailItemBase::ThumbnailItemBase(QQuickItem *parent) + : QQuickItem(parent) +{ + setFlag(ItemHasContents); + handleCompositingToggled(); + + connect(Compositor::self(), &Compositor::aboutToToggleCompositing, + this, &ThumbnailItemBase::destroyOffscreenTexture); + connect(Compositor::self(), &Compositor::compositingToggled, + this, &ThumbnailItemBase::handleCompositingToggled); +} + +ThumbnailItemBase::~ThumbnailItemBase() +{ + destroyOffscreenTexture(); + + if (m_provider) { + if (window()) { + window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider), + QQuickWindow::AfterSynchronizingStage); + } else { + qCCritical(KWIN_SCRIPTING) << "Can't destroy thumbnail texture provider because window is null"; + } + } +} + +void ThumbnailItemBase::releaseResources() +{ + if (m_provider) { + window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider), + QQuickWindow::AfterSynchronizingStage); + m_provider = nullptr; + } +} + +bool ThumbnailItemBase::isTextureProvider() const +{ + return true; +} + +QSGTextureProvider *ThumbnailItemBase::textureProvider() const +{ + if (QQuickItem::isTextureProvider()) { + return QQuickItem::textureProvider(); + } + if (!m_provider) { + m_provider = new ThumbnailTextureProvider(window()); + } + return m_provider; +} + +void ThumbnailItemBase::handleCompositingToggled() +{ + Scene *scene = Compositor::self()->scene(); + if (scene && scene->compositingType() == OpenGLCompositing) { + connect(scene, &Scene::frameRendered, this, &ThumbnailItemBase::updateOffscreenTexture); + } +} + +QSize ThumbnailItemBase::sourceSize() const +{ + return m_sourceSize; +} + +void ThumbnailItemBase::setSourceSize(const QSize &sourceSize) +{ + if (m_sourceSize != sourceSize) { + m_sourceSize = sourceSize; + invalidateOffscreenTexture(); + Q_EMIT sourceSizeChanged(); + } +} + +void ThumbnailItemBase::destroyOffscreenTexture() +{ + Scene *scene = Compositor::self()->scene(); + if (!scene || scene->compositingType() != OpenGLCompositing) { + return; + } + + if (m_offscreenTexture) { + scene->makeOpenGLContextCurrent(); + m_offscreenTarget.reset(); + m_offscreenTexture.reset(); + + if (m_acquireFence) { + glDeleteSync(m_acquireFence); + m_acquireFence = 0; + } + scene->doneOpenGLContextCurrent(); + } +} + +QSGNode *ThumbnailItemBase::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) +{ + if (Compositor::compositing() && !m_offscreenTexture) { + return oldNode; + } + + // Wait for rendering commands to the offscreen texture complete if there are any. + if (m_acquireFence) { + glClientWaitSync(m_acquireFence, GL_SYNC_FLUSH_COMMANDS_BIT, 5000); + glDeleteSync(m_acquireFence); + m_acquireFence = 0; + } + + if (!m_provider) { + m_provider = new ThumbnailTextureProvider(window()); + } + + if (m_offscreenTexture) { + m_provider->setTexture(m_offscreenTexture); + } else { + m_provider->setTexture(window()->createTextureFromImage(fallbackImage())); + } + + QSGImageNode *node = static_cast(oldNode); + if (!node) { + node = window()->createImageNode(); + } + node->setTexture(m_provider->texture()); + + if (m_offscreenTexture && m_offscreenTexture->isYInverted()) { + node->setTextureCoordinatesTransform(QSGImageNode::MirrorVertically); + } else { + node->setTextureCoordinatesTransform(QSGImageNode::NoTransform); + } + + const QSizeF size = QSizeF(node->texture()->textureSize()) + .scaled(boundingRect().size(), Qt::KeepAspectRatio); + const qreal x = boundingRect().x() + (boundingRect().width() - size.width()) / 2; + const qreal y = boundingRect().y() + (boundingRect().height() - size.height()) / 2; + node->setRect(QRectF(QPointF(x, y), size)); + + return node; +} + +void ThumbnailItemBase::setSaturation(qreal saturation) +{ + Q_UNUSED(saturation) + qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.saturation is removed. Use a shader effect to change saturation"; +} + +void ThumbnailItemBase::setBrightness(qreal brightness) +{ + Q_UNUSED(brightness) + qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.brightness is removed. Use a shader effect to change brightness"; +} + +void ThumbnailItemBase::setClipTo(QQuickItem *clip) +{ + Q_UNUSED(clip) + qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.clipTo is removed and it has no replacements"; +} + +WindowThumbnailItem::WindowThumbnailItem(QQuickItem *parent) + : ThumbnailItemBase(parent) +{ +} + +QUuid WindowThumbnailItem::wId() const +{ + return m_wId; +} + +void WindowThumbnailItem::setWId(const QUuid &wId) +{ + if (m_wId == wId) { + return; + } + m_wId = wId; + if (!m_wId.isNull()) { + setClient(workspace()->findAbstractClient(wId)); + } else if (m_client) { + m_client = nullptr; + Q_EMIT clientChanged(); + } + Q_EMIT wIdChanged(); +} + +AbstractClient *WindowThumbnailItem::client() const +{ + return m_client; +} + +void WindowThumbnailItem::setClient(AbstractClient *client) +{ + if (m_client == client) { + return; + } + if (m_client) { + disconnect(m_client, &AbstractClient::frameGeometryChanged, + this, &WindowThumbnailItem::invalidateOffscreenTexture); + disconnect(m_client, &AbstractClient::damaged, + this, &WindowThumbnailItem::invalidateOffscreenTexture); + } + m_client = client; + if (m_client) { + connect(m_client, &AbstractClient::frameGeometryChanged, + this, &WindowThumbnailItem::invalidateOffscreenTexture); + connect(m_client, &AbstractClient::damaged, + this, &WindowThumbnailItem::invalidateOffscreenTexture); + setWId(m_client->internalId()); + } else { + setWId(QUuid()); + } + invalidateOffscreenTexture(); + Q_EMIT clientChanged(); +} + +QImage WindowThumbnailItem::fallbackImage() const +{ + if (m_client) { + return m_client->icon().pixmap(boundingRect().size().toSize()).toImage(); + } + return QImage(); +} + +void WindowThumbnailItem::invalidateOffscreenTexture() +{ + m_dirty = true; + update(); +} + +void WindowThumbnailItem::updateOffscreenTexture() +{ + if (m_acquireFence || !m_dirty || !m_client) { + return; + } + + const QRect geometry = m_client->frameGeometry(); + QSize textureSize = geometry.size(); + if (sourceSize().width() > 0) { + textureSize.setWidth(sourceSize().width()); + } + if (sourceSize().height() > 0) { + textureSize.setHeight(sourceSize().height()); + } + + if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) { + m_offscreenTexture.reset(new GLTexture(GL_RGBA8, textureSize)); + m_offscreenTexture->setFilter(GL_LINEAR); + m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); + m_offscreenTarget.reset(new GLRenderTarget(*m_offscreenTexture)); + } + + GLRenderTarget::pushRenderTarget(m_offscreenTarget.data()); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + + QMatrix4x4 projectionMatrix; + projectionMatrix.ortho(geometry.x(), geometry.x() + geometry.width(), + geometry.y(), geometry.y() + geometry.height(), -1, 1); + + EffectWindowImpl *effectWindow = m_client->effectWindow(); + WindowPaintData data(effectWindow); + data.setProjectionMatrix(projectionMatrix); + + // The thumbnail must be rendered using kwin's opengl context as VAOs are not + // shared across contexts. Unfortunately, this also introduces a latency of 1 + // frame, which is not ideal, but it is acceptable for things such as thumbnails. + const int mask = Scene::PAINT_WINDOW_TRANSFORMED; + effectWindow->sceneWindow()->performPaint(mask, infiniteRegion(), data); + GLRenderTarget::popRenderTarget(); + + // The fence is needed to avoid the case where qtquick renderer starts using + // the texture while all rendering commands to it haven't completed yet. + m_dirty = false; + m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + + // We know that the texture has changed, so schedule an item update. + update(); +} + +DesktopThumbnailItem::DesktopThumbnailItem(QQuickItem *parent) + : ThumbnailItemBase(parent) +{ +} + +int DesktopThumbnailItem::desktop() const +{ + return m_desktop; +} + +void DesktopThumbnailItem::setDesktop(int desktop) +{ + desktop = qBound(1, desktop, VirtualDesktopManager::self()->count()); + if (m_desktop != desktop) { + m_desktop = desktop; + invalidateOffscreenTexture(); + Q_EMIT desktopChanged(); + } +} + +QImage DesktopThumbnailItem::fallbackImage() const +{ + return QImage(); +} + +void DesktopThumbnailItem::invalidateOffscreenTexture() +{ + update(); +} + +void DesktopThumbnailItem::updateOffscreenTexture() +{ + if (m_acquireFence) { + return; + } + + const QRect geometry = screens()->geometry(); + QSize textureSize = geometry.size(); + if (sourceSize().width() > 0) { + textureSize.setWidth(sourceSize().width()); + } + if (sourceSize().height() > 0) { + textureSize.setHeight(sourceSize().height()); + } + + if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) { + m_offscreenTexture.reset(new GLTexture(GL_RGBA8, textureSize)); + m_offscreenTexture->setFilter(GL_LINEAR); + m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); + m_offscreenTexture->setYInverted(true); + m_offscreenTarget.reset(new GLRenderTarget(*m_offscreenTexture)); + } + + GLRenderTarget::pushRenderTarget(m_offscreenTarget.data()); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + + QMatrix4x4 projectionMatrix; + projectionMatrix.ortho(geometry); + ScreenPaintData data(projectionMatrix); + + // The thumbnail must be rendered using kwin's opengl context as VAOs are not + // shared across contexts. Unfortunately, this also introduces a latency of 1 + // frame, which is not ideal, but it is acceptable for things such as thumbnails. + const int mask = Scene::PAINT_WINDOW_TRANSFORMED | Scene::PAINT_SCREEN_TRANSFORMED; + Scene *scene = Compositor::self()->scene(); + scene->paintDesktop(m_desktop, mask, infiniteRegion(), data); + GLRenderTarget::popRenderTarget(); + + // The fence is needed to avoid the case where qtquick renderer starts using + // the texture while all rendering commands to it haven't completed yet. + m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + + // We know that the texture has changed, so schedule an item update. + update(); +} + +} // namespace KWin diff --git a/src/scripting/thumbnailitem.h b/src/scripting/thumbnailitem.h new file mode 100644 index 0000000000..b53b16e325 --- /dev/null +++ b/src/scripting/thumbnailitem.h @@ -0,0 +1,140 @@ +/* + SPDX-FileCopyrightText: 2011 Martin Gräßlin + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include + +#include + +namespace KWin +{ +class AbstractClient; +class GLRenderTarget; +class GLTexture; +class ThumbnailTextureProvider; + +class ThumbnailItemBase : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QSize sourceSize READ sourceSize WRITE setSourceSize NOTIFY sourceSizeChanged) + /** + * TODO Plasma 6: Remove. + * @deprecated use a shader effect to change the brightness + */ + Q_PROPERTY(qreal brightness READ brightness WRITE setBrightness NOTIFY brightnessChanged) + /** + * TODO Plasma 6: Remove. + * @deprecated use a shader effect to change color saturation + */ + Q_PROPERTY(qreal saturation READ saturation WRITE setSaturation NOTIFY saturationChanged) + /** + * TODO Plasma 6: Remove. + * @deprecated clipTo has no replacement + */ + Q_PROPERTY(QQuickItem *clipTo READ clipTo WRITE setClipTo NOTIFY clipToChanged) + +public: + explicit ThumbnailItemBase(QQuickItem *parent = nullptr); + ~ThumbnailItemBase() override; + + qreal brightness() const { return 1; } + void setBrightness(qreal brightness); + + qreal saturation() const { return 1; } + void setSaturation(qreal saturation); + + QQuickItem *clipTo() const { return nullptr; } + void setClipTo(QQuickItem *clip); + + QSize sourceSize() const; + void setSourceSize(const QSize &sourceSize); + + QSGTextureProvider *textureProvider() const override; + bool isTextureProvider() const override; + QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) override; + +Q_SIGNALS: + void brightnessChanged(); + void saturationChanged(); + void clipToChanged(); + void sourceSizeChanged(); + +protected: + void releaseResources() override; + + virtual QImage fallbackImage() const = 0; + virtual void invalidateOffscreenTexture() = 0; + virtual void updateOffscreenTexture() = 0; + void destroyOffscreenTexture(); + + mutable ThumbnailTextureProvider *m_provider = nullptr; + QSharedPointer m_offscreenTexture; + QScopedPointer m_offscreenTarget; + GLsync m_acquireFence = 0; + +private: + void handleCompositingToggled(); + + QSize m_sourceSize; +}; + +class WindowThumbnailItem : public ThumbnailItemBase +{ + Q_OBJECT + Q_PROPERTY(QUuid wId READ wId WRITE setWId NOTIFY wIdChanged) + Q_PROPERTY(KWin::AbstractClient *client READ client WRITE setClient NOTIFY clientChanged) + +public: + explicit WindowThumbnailItem(QQuickItem *parent = nullptr); + + QUuid wId() const; + void setWId(const QUuid &wId); + + AbstractClient *client() const; + void setClient(AbstractClient *client); + +Q_SIGNALS: + void wIdChanged(); + void clientChanged(); + +protected: + QImage fallbackImage() const override; + void invalidateOffscreenTexture() override; + void updateOffscreenTexture() override; + +private: + QUuid m_wId; + QPointer m_client; + bool m_dirty = false; +}; + +class DesktopThumbnailItem : public ThumbnailItemBase +{ + Q_OBJECT + Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) + +public: + explicit DesktopThumbnailItem(QQuickItem *parent = nullptr); + + int desktop() const; + void setDesktop(int desktop); + +Q_SIGNALS: + void desktopChanged(); + +protected: + QImage fallbackImage() const override; + void invalidateOffscreenTexture() override; + void updateOffscreenTexture() override; + +private: + int m_desktop = 1; +}; + +} // namespace KWin diff --git a/src/tabbox/tabboxhandler.cpp b/src/tabbox/tabboxhandler.cpp index 9a8500c3eb..6082446887 100644 --- a/src/tabbox/tabboxhandler.cpp +++ b/src/tabbox/tabboxhandler.cpp @@ -15,7 +15,6 @@ // tabbox #include "clientmodel.h" #include "desktopmodel.h" -#include "thumbnailitem.h" #include "scripting/scripting.h" #include "switcheritem.h" #include "tabbox_logging.h" diff --git a/src/thumbnailitem.cpp b/src/thumbnailitem.cpp deleted file mode 100644 index e1b3284d7d..0000000000 --- a/src/thumbnailitem.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* - KWin - the KDE window manager - This file is part of the KDE project. - - SPDX-FileCopyrightText: 2011 Martin Gräßlin - - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -#include "thumbnailitem.h" -// KWin -#include "x11client.h" -#include "composite.h" -#include "effects.h" -#include "workspace.h" -#include "wayland_server.h" -// Qt -#include -#include -#include - -namespace KWin -{ - -AbstractThumbnailItem::AbstractThumbnailItem(QQuickItem *parent) - : QQuickPaintedItem(parent) - , m_brightness(1.0) - , m_saturation(1.0) - , m_clipToItem() -{ - connect(Compositor::self(), &Compositor::compositingToggled, this, &AbstractThumbnailItem::compositingToggled); - compositingToggled(); - QTimer::singleShot(0, this, &AbstractThumbnailItem::init); -} - -AbstractThumbnailItem::~AbstractThumbnailItem() -{ -} - -void AbstractThumbnailItem::compositingToggled() -{ - m_parent.clear(); - if (effects) { - connect(effects, &EffectsHandler::windowAdded, this, &AbstractThumbnailItem::effectWindowAdded); - connect(effects, &EffectsHandler::windowDamaged, this, &AbstractThumbnailItem::repaint); - effectWindowAdded(); - } -} - -void AbstractThumbnailItem::init() -{ - findParentEffectWindow(); - if (m_parent) { - m_parent->registerThumbnail(this); - } -} - -void AbstractThumbnailItem::findParentEffectWindow() -{ - if (effects) { - QQuickWindow *qw = window(); - if (!qw) { - qCDebug(KWIN_CORE) << "No QQuickWindow assigned yet"; - return; - } - if (auto *w = static_cast(effects->findWindow(qw))) { - m_parent = QPointer(w); - } - } -} - -void AbstractThumbnailItem::effectWindowAdded() -{ - // the window might be added before the EffectWindow is created - // by using this slot we can register the thumbnail when it is finally created - if (m_parent.isNull()) { - findParentEffectWindow(); - if (m_parent) { - m_parent->registerThumbnail(this); - } - } -} - -void AbstractThumbnailItem::setBrightness(qreal brightness) -{ - if (qFuzzyCompare(brightness, m_brightness)) { - return; - } - m_brightness = brightness; - update(); - Q_EMIT brightnessChanged(); -} - -void AbstractThumbnailItem::setSaturation(qreal saturation) -{ - if (qFuzzyCompare(saturation, m_saturation)) { - return; - } - m_saturation = saturation; - update(); - Q_EMIT saturationChanged(); -} - -void AbstractThumbnailItem::setClipTo(QQuickItem *clip) -{ - m_clipToItem = QPointer(clip); - Q_EMIT clipToChanged(); -} - -WindowThumbnailItem::WindowThumbnailItem(QQuickItem* parent) - : AbstractThumbnailItem(parent) - , m_wId(nullptr) - , m_client(nullptr) -{ -} - -WindowThumbnailItem::~WindowThumbnailItem() -{ -} - -void WindowThumbnailItem::setWId(const QUuid &wId) -{ - if (m_wId == wId) { - return; - } - m_wId = wId; - if (m_wId != nullptr) { - setClient(workspace()->findAbstractClient([this] (const AbstractClient *c) { return c->internalId() == m_wId; })); - } else if (m_client) { - m_client = nullptr; - Q_EMIT clientChanged(); - } - Q_EMIT wIdChanged(wId); -} - -void WindowThumbnailItem::setClient(AbstractClient *client) -{ - if (m_client == client) { - return; - } - m_client = client; - if (m_client) { - setWId(m_client->internalId()); - } else { - setWId({}); - } - Q_EMIT clientChanged(); -} - -void WindowThumbnailItem::paint(QPainter *painter) -{ - if (effects) { - return; - } - auto client = workspace()->findAbstractClient([this] (const AbstractClient *c) { return c->internalId() == m_wId; }); - if (!client) { - return; - } - QPixmap pixmap = client->icon().pixmap(boundingRect().size().toSize()); - const QSize size(boundingRect().size().toSize() - pixmap.size()); - painter->drawPixmap(boundingRect().adjusted(size.width()/2.0, size.height()/2.0, -size.width()/2.0, -size.height()/2.0).toRect(), - pixmap); -} - -void WindowThumbnailItem::repaint(KWin::EffectWindow *w) -{ - if (static_cast(w)->window()->internalId() == m_wId) { - update(); - } -} - -DesktopThumbnailItem::DesktopThumbnailItem(QQuickItem *parent) - : AbstractThumbnailItem(parent) - , m_desktop(0) -{ -} - -DesktopThumbnailItem::~DesktopThumbnailItem() -{ -} - -void DesktopThumbnailItem::setDesktop(int desktop) -{ - desktop = qBound(1, desktop, VirtualDesktopManager::self()->count()); - if (desktop == m_desktop) { - return; - } - m_desktop = desktop; - update(); - Q_EMIT desktopChanged(m_desktop); -} - -void DesktopThumbnailItem::paint(QPainter *painter) -{ - Q_UNUSED(painter) - if (effects) { - return; - } - // TODO: render icon -} - -void DesktopThumbnailItem::repaint(EffectWindow *w) -{ - if (w->isOnDesktop(m_desktop)) { - update(); - } -} - -} // namespace KWin diff --git a/src/thumbnailitem.h b/src/thumbnailitem.h deleted file mode 100644 index e9395afe29..0000000000 --- a/src/thumbnailitem.h +++ /dev/null @@ -1,139 +0,0 @@ -/* - KWin - the KDE window manager - This file is part of the KDE project. - - SPDX-FileCopyrightText: 2011 Martin Gräßlin - - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -#ifndef KWIN_THUMBNAILITEM_H -#define KWIN_THUMBNAILITEM_H - -#include -#include -#include -#include - -namespace KWin -{ - -class AbstractClient; -class EffectWindow; -class EffectWindowImpl; - -class AbstractThumbnailItem : public QQuickPaintedItem -{ - Q_OBJECT - Q_PROPERTY(qreal brightness READ brightness WRITE setBrightness NOTIFY brightnessChanged) - Q_PROPERTY(qreal saturation READ saturation WRITE setSaturation NOTIFY saturationChanged) - Q_PROPERTY(QQuickItem *clipTo READ clipTo WRITE setClipTo NOTIFY clipToChanged) -public: - ~AbstractThumbnailItem() override; - qreal brightness() const; - qreal saturation() const; - QQuickItem *clipTo() const; - -public Q_SLOTS: - void setBrightness(qreal brightness); - void setSaturation(qreal saturation); - void setClipTo(QQuickItem *clip); - -Q_SIGNALS: - void brightnessChanged(); - void saturationChanged(); - void clipToChanged(); - -protected: - explicit AbstractThumbnailItem(QQuickItem *parent = nullptr); - -protected Q_SLOTS: - virtual void repaint(KWin::EffectWindow* w) = 0; - -private Q_SLOTS: - void init(); - void effectWindowAdded(); - void compositingToggled(); - -private: - void findParentEffectWindow(); - QPointer m_parent; - qreal m_brightness; - qreal m_saturation; - QPointer m_clipToItem; -}; - -class WindowThumbnailItem : public AbstractThumbnailItem -{ - Q_OBJECT - Q_PROPERTY(QUuid wId READ wId WRITE setWId NOTIFY wIdChanged SCRIPTABLE true) - Q_PROPERTY(KWin::AbstractClient *client READ client WRITE setClient NOTIFY clientChanged) -public: - explicit WindowThumbnailItem(QQuickItem *parent = nullptr); - ~WindowThumbnailItem() override; - - QUuid wId() const { - return m_wId; - } - void setWId(const QUuid &wId); - AbstractClient *client() const; - void setClient(AbstractClient *client); - void paint(QPainter *painter) override; -Q_SIGNALS: - void wIdChanged(const QUuid &wid); - void clientChanged(); -protected Q_SLOTS: - void repaint(KWin::EffectWindow* w) override; -private: - QUuid m_wId; - AbstractClient *m_client; -}; - -class DesktopThumbnailItem : public AbstractThumbnailItem -{ - Q_OBJECT - Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) -public: - DesktopThumbnailItem(QQuickItem *parent = nullptr); - ~DesktopThumbnailItem() override; - - int desktop() const { - return m_desktop; - } - void setDesktop(int desktop); - void paint(QPainter *painter) override; -Q_SIGNALS: - void desktopChanged(int desktop); -protected Q_SLOTS: - void repaint(KWin::EffectWindow* w) override; -private: - int m_desktop; -}; - -inline -qreal AbstractThumbnailItem::brightness() const -{ - return m_brightness; -} - -inline -qreal AbstractThumbnailItem::saturation() const -{ - return m_saturation; -} - -inline -QQuickItem* AbstractThumbnailItem::clipTo() const -{ - return m_clipToItem.data(); -} - -inline -AbstractClient *WindowThumbnailItem::client() const -{ - return m_client; -} - -} // KWin - -#endif // KWIN_THUMBNAILITEM_H