Improve thumbnail item integration

Currently, thumbnail items are rendered by kwin. This means that qtquick
code cannot do things such as applying shader effects to window thumbnails
or simply draw custom controls on top of thumbnails.

With this change, task switchers and qml extensions will be able to
place their own contents on top of thumbnails and apply custom effects
to them.

In order to integrate window thumbnails, a window is rendered on kwin
side using its own opengl context. A fence is inserted in the command
stream to ensure that the qtquick machinery doesn't start using the
offscreen texture while there are still rendering commands being executed.

Thumbnails are rendered into offscreen textures as we don't have full
control over when qtquick windows render their contents and to work around
the fact that things such as VAOs can't be shared across OpenGL contexts.

WindowThumbnailItem and DesktopThumbnailItem act as texture providers.
This commit is contained in:
Vlad Zahorodnii 2021-06-02 11:45:50 +03:00
parent 172c541cad
commit 3427143017
11 changed files with 592 additions and 541 deletions

View file

@ -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

View file

@ -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 <QDebug>
#include <QMouseEvent>
#include <QWheelEvent>
#include <Plasma/Theme>
@ -2098,47 +2099,6 @@ void EffectWindowImpl::elevate(bool elevate)
effects->setElevatedWindow(this, elevate);
}
void EffectWindowImpl::registerThumbnail(AbstractThumbnailItem *item)
{
if (WindowThumbnailItem *thumb = qobject_cast<WindowThumbnailItem*>(item)) {
insertThumbnail(thumb);
connect(thumb, &QObject::destroyed, this, &EffectWindowImpl::thumbnailDestroyed);
connect(thumb, &WindowThumbnailItem::wIdChanged, this, &EffectWindowImpl::thumbnailTargetChanged);
} else if (DesktopThumbnailItem *desktopThumb = qobject_cast<DesktopThumbnailItem*>(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<WindowThumbnailItem*>(object));
}
void EffectWindowImpl::thumbnailTargetChanged()
{
if (WindowThumbnailItem *item = qobject_cast<WindowThumbnailItem*>(sender())) {
insertThumbnail(item);
}
}
void EffectWindowImpl::insertThumbnail(WindowThumbnailItem *item)
{
EffectWindow *w = effects->findWindow(item->wId());
if (w) {
m_thumbnails.insert(item, QPointer<EffectWindowImpl>(static_cast<EffectWindowImpl*>(w)));
} else {
m_thumbnails.insert(item, QPointer<EffectWindowImpl>());
}
}
void EffectWindowImpl::desktopThumbnailDestroyed(QObject *object)
{
// we know it is a DesktopThumbnailItem
m_desktopThumbnails.removeAll(static_cast<DesktopThumbnailItem*>(object));
}
void EffectWindowImpl::minimize()
{
if (auto client = qobject_cast<AbstractClient *>(toplevel)) {

View file

@ -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<WindowThumbnailItem*, QPointer<EffectWindowImpl> > const &thumbnails() const {
return m_thumbnails;
}
QList<DesktopThumbnailItem*> 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<int, QVariant> dataMap;
QHash<WindowThumbnailItem*, QPointer<EffectWindowImpl> > m_thumbnails;
QList<DesktopThumbnailItem*> m_desktopThumbnails;
bool managed = false;
bool waylandClient;
bool x11Client;

View file

@ -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<QObject*>()) {
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 &region, qreal opacity, qreal brightness, qreal saturation)
{
EffectWindowImpl *wImpl = static_cast<EffectWindowImpl*>(effectWindow(w));
for (QHash<WindowThumbnailItem*, QPointer<EffectWindowImpl> >::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<qreal>(visualThumbRect.width()));
thumbData.setYScale(size.height() / static_cast<qreal>(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<EffectWindowImpl*>(effectWindow(w));
for (QList<DesktopThumbnailItem*>::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 &region, ScreenPaintData &data)

View file

@ -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 &region, 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 &region, bool opaqueFullscreen);
virtual void paintDesktop(int desktop, int mask, const QRegion &region, 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 &region, 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;

View file

@ -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

View file

@ -0,0 +1,446 @@
/*
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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 <kwingltexture.h>
#include <kwinglutils.h>
#include <QSGImageNode>
#include <QRunnable>
#include <QQuickWindow>
#include <QSGTextureProvider>
namespace KWin
{
class ThumbnailTextureProvider : public QSGTextureProvider
{
public:
explicit ThumbnailTextureProvider(QQuickWindow *window);
QSGTexture *texture() const override;
void setTexture(const QSharedPointer<GLTexture> &nativeTexture);
void setTexture(QSGTexture *texture);
private:
QQuickWindow *m_window;
QSharedPointer<GLTexture> m_nativeTexture;
QScopedPointer<QSGTexture> m_texture;
};
ThumbnailTextureProvider::ThumbnailTextureProvider(QQuickWindow *window)
: m_window(window)
{
}
QSGTexture *ThumbnailTextureProvider::texture() const
{
return m_texture.data();
}
void ThumbnailTextureProvider::setTexture(const QSharedPointer<GLTexture> &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<ThumbnailTextureProvider> 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<QSGImageNode *>(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<int>(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

View file

@ -0,0 +1,140 @@
/*
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QQuickItem>
#include <QUuid>
#include <epoxy/gl.h>
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<GLTexture> m_offscreenTexture;
QScopedPointer<GLRenderTarget> 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<AbstractClient> 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

View file

@ -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"

View file

@ -1,209 +0,0 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
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 <QDebug>
#include <QPainter>
#include <QQuickWindow>
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<EffectWindowImpl*>(effects->findWindow(qw))) {
m_parent = QPointer<EffectWindowImpl>(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<QQuickItem>(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<KWin::EffectWindowImpl*>(w)->window()->internalId() == m_wId) {
update();
}
}
DesktopThumbnailItem::DesktopThumbnailItem(QQuickItem *parent)
: AbstractThumbnailItem(parent)
, m_desktop(0)
{
}
DesktopThumbnailItem::~DesktopThumbnailItem()
{
}
void DesktopThumbnailItem::setDesktop(int desktop)
{
desktop = qBound<int>(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

View file

@ -1,139 +0,0 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KWIN_THUMBNAILITEM_H
#define KWIN_THUMBNAILITEM_H
#include <QPointer>
#include <QUuid>
#include <QWeakPointer>
#include <QQuickPaintedItem>
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<EffectWindowImpl> m_parent;
qreal m_brightness;
qreal m_saturation;
QPointer<QQuickItem> 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