[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-devel
Subject: Re: Kicker/Taskbar: Mouseover effect w/ thumbnails
From: Stefan Nikolaus <stefan.nikolaus () kdemail ! net>
Date: 2005-04-26 15:09:57
Message-ID: 200504261710.00848.stefan.nikolaus () kdemail ! net
[Download RAW message or body]
[Attachment #2 (multipart/signed)]
[Attachment #4 (multipart/mixed)]
Okay, here's what I got so far... [1]
The attached patch uses the XComposite, XRender and XFixes extensions to
generate thumbnails. The latter is needed for shaped windows, e.g. xeyes.
Windows, that are minimized or reside on a different virtual desktop on
startup, will have no thumnails until they are restored or the desktop is
switched, respectively.
I've put two options in taskbar.kcfg. One to enable the thumbnails, as
proposed by Aaron, and one to set the thumnails' maximum dimension in pixels.
I prefer this to a percentage size.
As is, the scaling of the thumbnails is done by QImage::smoothScale(). You can
switch to XRender scaling by #if 0-ing the QImage::smoothScale() code part.
The pros for the QImage::smoothScale() method are the better look and the
proper handling of shaped windows. Using XRender, shaped windows are drawn on
an opaque (black) background. If you don't scale it, it's shown with
transparent bg, but who wants "100% thumbnails". The advantage of XRender is
its speed. At least on my machine the QImage::smoothScale() method sucks in
this way. I think, the two QPixmap/QImage conversions slow things down.
Opinions and ideas how to improve one of the methods are welcome.
Not having looked much into it, I see following approaches:
QImage::smoothScale():
- do the smooth scaling on a QPixmap basis (saves the two QPixmap/QImage
conversions)
- write the window pixmap directly into a Qimage (saves one conversion)
XRender:
- create a mask for the shape windows, scale and apply it on the thumbnail
- find another filter for the XRender scaling
Bye,
Stefan
[1] due to the excellent Composite HOWTO of Fredrik Höglund and some insights
taken from Hans Oischinger's Kompose
["kdebase-check-xextensions.patch" (text/x-diff)]
Index: configure.in.in
===================================================================
RCS file: /home/kde/kdebase/configure.in.in,v
retrieving revision 1.66
diff -u -p -r1.66 configure.in.in
--- configure.in.in 7 Feb 2005 12:27:53 -0000 1.66
+++ configure.in.in 26 Apr 2005 13:17:30 -0000
@@ -209,6 +209,30 @@ else
fi
AC_SUBST(LIB_XRENDER)
+dnl XComposite check
+AC_CHECK_HEADER(X11/extensions/Xcomposite.h, [xcomposite_h=yes], [xcomposite_h=no], \
[#include <X11/Xlib.h>]) +if test "$xcomposite_h" = yes; then
+ KDE_CHECK_LIB(Xcomposite, XCompositeQueryExtension, [
+ LIB_XCOMPOSITE=-lXcomposite
+ AC_DEFINE_UNQUOTED(HAVE_XCOMPOSITE, 1, [Define if you have the XComposite \
extension]) + ], [], -lXext -X11 $X_EXTRA_LIBS)
+else
+ LIB_XCOMPOSITE=
+fi
+AC_SUBST(LIB_XCOMPOSITE)
+
+dnl XFixes check
+AC_CHECK_HEADER(X11/extensions/Xfixes.h, [xfixes_h=yes], [xfixes_h=no], [#include \
<X11/Xlib.h>]) +if test "$xfixes_h" = yes; then
+ KDE_CHECK_LIB(Xfixes, XFixesQueryExtension, [
+ LIB_XFIXES=-lXfixes
+ AC_DEFINE_UNQUOTED(HAVE_XFIXES, 1, [Define if you have the XFixes extension])
+ ], [], -lXext -X11 $X_EXTRA_LIBS)
+else
+ LIB_XFIXES=
+fi
+AC_SUBST(LIB_XFIXES)
+
LDFLAGS="$ac_save_ldflags"
dnl ----- end of X11 extension checks -----
["taskbar-windowgrab-2.patch" (text/x-diff)]
? Doxyfile
? api
? taskmanager/taskmanagerIface.h
? taskmanager/taskmanagerIface_skel.cpp
Index: share/kickertip.cpp
===================================================================
RCS file: /home/kde/kdebase/kicker/share/kickertip.cpp,v
retrieving revision 1.17
diff -u -p -r1.17 kickertip.cpp
--- share/kickertip.cpp 24 Apr 2005 19:14:56 -0000 1.17
+++ share/kickertip.cpp 26 Apr 2005 15:00:06 -0000
@@ -247,7 +247,7 @@ void KickerTip::displayInternal()
textRect.moveBy(-textRect.left(), -textRect.top());
textRect.addCoords(0, 0, 2, 2);
- int margin = QMAX(m_icon.height() / 4, marginHint);
+ int margin = QMAX(QMAX(m_icon.width(), m_icon.height()) / 4, marginHint);
int height = QMAX(m_icon.height(), textRect.height()) + 2 * margin;
int textX = m_icon.isNull() ? margin : 2 + m_icon.width() + 2 * margin;
int width = textX + textRect.width() + margin;
Index: taskbar/taskbar.kcfg
===================================================================
RCS file: /home/kde/kdebase/kicker/taskbar/taskbar.kcfg,v
retrieving revision 1.8
diff -u -p -r1.8 taskbar.kcfg
--- taskbar/taskbar.kcfg 20 Apr 2005 21:14:26 -0000 1.8
+++ taskbar/taskbar.kcfg 26 Apr 2005 15:00:07 -0000
@@ -126,5 +126,13 @@
<default>true</default>
<label>Show a visible button frame on the task the cursor is positioned \
over</label> </entry>
+ <entry key="ShowThumbnails" type="Bool" >
+ <default>true</default>
+ <label></label>
+ </entry>
+ <entry key="ThumbnailMaxDimension" type="UInt" >
+ <default>100</default>
+ <label></label>
+ </entry>
</group>
</kcfg>
Index: taskbar/taskcontainer.cpp
===================================================================
RCS file: /home/kde/kdebase/kicker/taskbar/taskcontainer.cpp,v
retrieving revision 1.136
diff -u -p -r1.136 taskcontainer.cpp
--- taskbar/taskcontainer.cpp 25 Apr 2005 00:41:45 -0000 1.136
+++ taskbar/taskcontainer.cpp 26 Apr 2005 15:00:07 -0000
@@ -1469,21 +1469,15 @@ void TaskContainer::updateKickerTip(Kick
}
QPixmap pixmap;
-#if 0
- if (TaskbarSettings::showThumbnails() &&
+ if (TaskBarSettings::showThumbnails() &&
m_filteredTasks.count() == 1)
{
Task::Ptr t = m_filteredTasks.first();
- t->updateThumbnail();
- if (t->hasThumbnail())
- {
- pixmap.convertFromImage(t->thumbnail().convertToImage().smoothScale(64, \
64));
- }
+ pixmap = t->thumbnail(TaskBarSettings::thumbnailMaxDimension());
}
if (pixmap.isNull())
-#endif
{
// try to load icon via net_wm
pixmap = KWin::icon(tasks.first()->window(),
Index: taskmanager/Makefile.am
===================================================================
RCS file: /home/kde/kdebase/kicker/taskmanager/Makefile.am,v
retrieving revision 1.7
diff -u -p -r1.7 Makefile.am
--- taskmanager/Makefile.am 19 Apr 2005 20:06:42 -0000 1.7
+++ taskmanager/Makefile.am 26 Apr 2005 15:00:07 -0000
@@ -5,7 +5,7 @@ libtaskmanager_la_SOURCES = tasklmbmenu.
libtaskmanager_la_METASOURCES = AUTO
libtaskmanager_la_LDFLAGS = $(all_libraries) -version-info 1:0:0 -no-undefined
-libtaskmanager_la_LIBADD = $(LIB_KDECORE) ../share/libkickermain.la
+libtaskmanager_la_LIBADD = $(LIB_KDECORE) $(LIB_XFIXES) $(LIB_XRENDER) \
$(LIB_XCOMPOSITE) ../share/libkickermain.la
messages:
$(XGETTEXT) *.cpp *.h -o $(podir)/libtaskmanager.pot
Index: taskmanager/taskmanager.cpp
===================================================================
RCS file: /home/kde/kdebase/kicker/taskmanager/taskmanager.cpp,v
retrieving revision 1.104
diff -u -p -r1.104 taskmanager.cpp
--- taskmanager/taskmanager.cpp 25 Apr 2005 09:10:55 -0000 1.104
+++ taskmanager/taskmanager.cpp 26 Apr 2005 15:00:07 -0000
@@ -40,6 +40,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE
TaskManager* TaskManager::m_self = 0;
static KStaticDeleter<TaskManager> staticTaskManagerDeleter;
+bool TaskManager::m_usableXComposite = false;
TaskManager* TaskManager::the()
{
@@ -64,6 +65,10 @@ TaskManager::TaskManager()
connect(m_winModule, SIGNAL(currentDesktopChanged(int)), \
SLOT(currentDesktopChanged(int)));
connect(m_winModule, SIGNAL(windowChanged(WId,unsigned int)), \
SLOT(windowChanged(WId,unsigned int)));
+#ifdef THUMBNAILING_POSSIBLE
+ initComposite();
+#endif // THUMBNAILING_POSSIBLE
+
// register existing windows
const QValueList<WId> windows = m_winModule->windows();
QValueList<WId>::ConstIterator end(windows.end());
@@ -103,9 +108,87 @@ void TaskManager::configure_startup()
_startup_info->setTimeout( c.readUnsignedNumEntry( "Timeout", 30 ));
}
+void TaskManager::initComposite()
+{
+#ifdef THUMBNAILING_POSSIBLE
+ Display *dpy = QPaintDevice::x11AppDisplay();
+
+ // XComposite extension check
+ int event_base, error_base;
+ if (XCompositeQueryExtension(dpy, &event_base, &error_base))
+ {
+ int major = 0, minor = 99; // The highest version we support
+
+ // Version check is needed. Otherwise, the server will return
+ // BadRequest for any operations other than QueryVersion.
+ XCompositeQueryVersion(dpy, &major, &minor);
+ kdDebug() << QString("XComposite %1.%2").arg(major).arg(minor) << endl;
+
+ // We use XCompositeNameWindowPixmap(), i.e. we need at least
+ // version 0.2.
+ if (major == 0 && minor < 2)
+ {
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ // XRender extension check
+ if (XRenderQueryExtension(dpy, &event_base, &error_base))
+ {
+ int major = 0, minor = 99; // The highest version we support
+
+ XRenderQueryVersion(dpy, &major, &minor);
+ kdDebug() << QString("XRender %1.%2").arg(major).arg(minor) << endl;
+
+ // We use SetPictureTransform() and SetPictureFilter(), i.e. we
+ // need at least version 0.6.
+ if (major == 0 && minor < 6)
+ {
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ // XFixes extension check
+ if (XFixesQueryExtension(dpy, &event_base, &error_base))
+ {
+ int major = 3, minor = 99; // The highest version we support
+
+ XFixesQueryVersion(dpy, &major, &minor);
+ kdDebug() << QString("XFixes %1.%2").arg(major).arg(minor) << endl;
+
+ // We use Region objects, i.e. we need at least version 2.0.
+ if (major < 2)
+ {
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ // if we get here, we've got usable extensions
+ m_usableXComposite = true;
+
+ // redirecting windows to backing pixmaps
+ for (int i = 0; i < ScreenCount(dpy); i++)
+ {
+ XCompositeRedirectSubwindows(dpy, RootWindow(dpy, i), \
CompositeRedirectAutomatic); + }
+#endif // THUMBNAILING_POSSIBLE
+}
+
Task::Ptr TaskManager::findTask(WId w)
{
- QMap<WId, Task::Ptr>::iterator it = m_tasksByWId.find(w);
+ Task::Dict::iterator it = m_tasksByWId.find(w);
if (it == m_tasksByWId.end())
{
@@ -302,10 +385,31 @@ void TaskManager::windowChanged(WId w, u
{
// moved to different desktop or is on all or change in \
iconification/withdrawnnes emit windowChanged(t);
+
+ if (dirty & NET::WMState && m_usableXComposite && !t->isMinimized())
+ {
+ // update on restoring a minimized window
+ updateWindowPixmap(w);
+ }
}
else if (dirty & NET::WMGeometry)
{
emit windowChangedGeometry(t);
+
+ if (m_usableXComposite)
+ {
+ // update on size changes
+ updateWindowPixmap(w);
+ }
+ }
+}
+
+void TaskManager::updateWindowPixmap(WId w)
+{
+ Task::Ptr task = findTask(w);
+ if (task)
+ {
+ task->updateWindowPixmap();
}
}
@@ -456,6 +560,7 @@ Task::Task(WId win, QObject *parent, con
: QObject(parent, name),
_active(false),
_win(win),
+ m_frameId(win),
_info(KWin::windowInfo(_win, 0, NET::WM2AllowedActions)),
_lastWidth(0),
_lastHeight(0),
@@ -463,7 +568,8 @@ Task::Task(WId win, QObject *parent, con
_lastIcon(),
_thumbSize(0.2),
_thumb(),
- _grab()
+ _grab(),
+ m_windowPixmap(0)
{
// try to load icon via net_wm
_pixmap = KWin::icon(_win, 16, 16, true);
@@ -480,11 +586,68 @@ Task::Task(WId win, QObject *parent, con
// load xapp icon
if (_pixmap.isNull())
- _pixmap = SmallIcon("kcmx");
+ {
+ _pixmap = SmallIcon("kcmx");
+ }
+
+#ifdef THUMBNAILING_POSSIBLE
+ if (TaskManager::useXComposite())
+ {
+ findWindowFrameId();
+ if (!isMinimized() && isOnCurrentDesktop())
+ {
+ updateWindowPixmap();
+ }
+ }
+#endif // THUMBNAILING_POSSIBLE
}
Task::~Task()
{
+#ifdef THUMBNAILING_POSSIBLE
+ if (m_windowPixmap)
+ {
+ XFreePixmap(QPaintDevice::x11AppDisplay(), m_windowPixmap);
+ }
+#endif // THUMBNAILING_POSSIBLE
+}
+
+// Task::findWindowFrameId()
+// Code was copied from Kompose.
+// Copyright (C) 2004 Hans Oischinger
+void Task::findWindowFrameId()
+{
+#ifdef THUMBNAILING_POSSIBLE
+ Window target_win, parent, root;
+ Window *children;
+ uint nchildren;
+
+ target_win = _win;
+ for (;;)
+ {
+ if (!XQueryTree(QPaintDevice::x11AppDisplay(), target_win, &root,
+ &parent, &children, &nchildren))
+ {
+ break;
+ }
+
+ if (children)
+ {
+ XFree(children); // it's a list, that's deallocated!
+ }
+
+ if (!parent || parent == root)
+ {
+ break;
+ }
+ else
+ {
+ target_win = parent;
+ }
+ }
+
+ m_frameId = target_win;
+#endif // THUMBNAILING_POSSIBLE
}
void Task::refreshIcon()
@@ -1104,6 +1267,117 @@ void Task::generateThumbnail()
emit thumbnailChanged();
}
+QPixmap Task::thumbnail(int maxDimension)
+{
+ if (!TaskManager::useXComposite() || !m_windowPixmap)
+ {
+ return QPixmap();
+ }
+
+#ifdef THUMBNAILING_POSSIBLE
+ Display *dpy = QPaintDevice::x11AppDisplay();
+
+ // We need to find out some things about the window, such as it's size,
+ // it's position on the screen, and the format of the pixel data
+ XWindowAttributes attr;
+ XGetWindowAttributes(dpy, m_frameId, &attr);
+
+ XRenderPictFormat *format = XRenderFindVisualFormat(dpy, attr.visual);
+ bool hasAlpha = (format->type == PictTypeDirect &&
+ format->direct.alphaMask);
+ int x = attr.x;
+ int y = attr.y;
+ int width = attr.width;
+ int height = attr.height;
+
+ // Create a Render picture so we can reference the window contents.
+ // We need to set the subwindow mode to IncludeInferiors, otherwise child
+ // widgets in the window won't be included when we draw it, which is not
+ // what we want.
+ XRenderPictureAttributes pa;
+ pa.subwindow_mode = IncludeInferiors; // Don't clip child widgets
+
+ Picture picture = XRenderCreatePicture(dpy, m_windowPixmap, format, \
CPSubwindowMode, &pa); +
+ // Create a copy of the bounding region for the window
+ XserverRegion region = XFixesCreateRegionFromWindow(dpy, m_frameId, \
WindowRegionBounding); +
+ XFixesSetPictureClipRegion(dpy, picture, 0, 0, region);
+ XFixesDestroyRegion(dpy, region);
+
+ double factor;
+ if (width > height)
+ {
+ factor = (double)maxDimension / (double)width;
+ }
+ else
+ {
+ factor = (double)maxDimension / (double)height;
+ }
+ int thumbnailWidth = width * factor;
+ int thumbnailHeight = height * factor;
+
+ QPixmap thumbnail(thumbnailWidth, thumbnailHeight);
+ thumbnail.fill(QApplication::palette().active().background());
+#if 1
+ QPixmap full(width, height);
+ full.fill(QApplication::palette().active().background());
+
+ XRenderComposite(dpy,
+ hasAlpha ? PictOpOver : PictOpSrc,
+ picture, // src
+ None, // mask
+ full.x11RenderHandle(), // dst
+ 0, 0, // src offset
+ 0, 0, // mask offset
+ 0, 0, // dst offset
+ width, height);
+
+ thumbnail.convertFromImage(full.convertToImage().smoothScale(thumbnailWidth,
+ thumbnailHeight));
+#else
+ // XRENDER scaling
+ XRenderSetPictureFilter( dpy, picture, FilterBilinear, 0, 0 );
+ // Scaling matrix
+ XTransform xform = {{
+ { XDoubleToFixed( 1 ), XDoubleToFixed( 0 ), XDoubleToFixed( 0 ) },
+ { XDoubleToFixed( 0 ), XDoubleToFixed( 1 ), XDoubleToFixed( 0 ) },
+ { XDoubleToFixed( 0 ), XDoubleToFixed( 0 ), XDoubleToFixed( factor ) }
+ }};
+
+ XRenderSetPictureTransform(dpy, picture, &xform);
+
+ XRenderComposite(QPaintDevice::x11AppDisplay(),
+ hasAlpha ? PictOpOver : PictOpSrc,
+ picture, // src
+ None, // mask
+ thumbnail.x11RenderHandle(), // dst
+ 0, 0, // src offset
+ 0, 0, // mask offset
+ 0, 0, // dst offset
+ thumbnailWidth, thumbnailHeight);
+#endif
+ XRenderFreePicture(dpy, picture);
+
+ return thumbnail;
+
+#endif // THUMBNAILING_POSSIBLE
+}
+
+void Task::updateWindowPixmap()
+{
+#ifdef THUMBNAILING_POSSIBLE
+ Display *dpy = QPaintDevice::x11AppDisplay();
+
+ if (m_windowPixmap)
+ {
+ XFreePixmap(dpy, m_windowPixmap);
+ }
+
+ m_windowPixmap = XCompositeNameWindowPixmap(dpy, m_frameId);
+#endif // THUMBNAILING_POSSIBLE
+}
+
Startup::Startup(const KStartupInfoId& id, const KStartupInfoData& data,
QObject * parent, const char *name)
: QObject(parent, name), _id(id), _data(data)
Index: taskmanager/taskmanager.h
===================================================================
RCS file: /home/kde/kdebase/kicker/taskmanager/taskmanager.h,v
retrieving revision 1.69
diff -u -p -r1.69 taskmanager.h
--- taskmanager/taskmanager.h 24 Apr 2005 19:10:50 -0000 1.69
+++ taskmanager/taskmanager.h 26 Apr 2005 15:00:07 -0000
@@ -36,11 +36,24 @@ CONNECTION WITH THE SOFTWARE OR THE USE
#include <qrect.h>
#include <qvaluelist.h>
-#include <dcopobject.h>
#include <ksharedptr.h>
#include <kstartupinfo.h>
#include <kwin.h>
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if defined(HAVE_XCOMPOSITE) && defined(HAVE_XRENDER) && defined(HAVE_XFIXES)
+#include <X11/Xlib.h>
+#include <X11/extensions/Xcomposite.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/Xrender.h>
+#if XCOMPOSITE_VERSION >= 00200 && (RENDER_MAJOR > 0 || RENDER_MINOR >= 6) && \
XFIXES_VERSION >= 20000 +#define THUMBNAILING_POSSIBLE
+#endif
+#endif
+
class KWinModule;
class TaskManager;
@@ -291,6 +304,10 @@ public:
*/
const QPixmap &thumbnail() const { return _thumb; }
+ QPixmap thumbnail(int maxDimension);
+
+ void updateWindowPixmap();
+
public slots:
// actions
@@ -426,9 +443,13 @@ protected slots:
//* @internal
void generateThumbnail();
+protected:
+ void findWindowFrameId();
+
private:
bool _active;
WId _win;
+ WId m_frameId;
QPixmap _pixmap;
KWin::WindowInfo _info;
QValueList<WId> _transients;
@@ -442,8 +463,7 @@ private:
double _thumbSize;
QPixmap _thumb;
QPixmap _grab;
-
- class TaskPrivate *d;
+ Pixmap m_windowPixmap;
};
@@ -596,6 +616,8 @@ public:
KWinModule* winModule() const { return m_winModule; }
+ static bool useXComposite() { return m_usableXComposite; }
+
signals:
/**
* Emitted when a new task has started.
@@ -653,8 +675,9 @@ protected slots:
void gotStartupChange( const KStartupInfoId&, const KStartupInfoData& );
protected:
-
void configure_startup();
+ void initComposite();
+ void updateWindowPixmap(WId);
private:
TaskManager();
@@ -668,6 +691,7 @@ private:
bool m_trackGeometry;
static TaskManager* m_self;
+ static bool m_usableXComposite;
class TaskManagerPrivate *d;
};
Index: taskmanager/taskrmbmenu.cpp
===================================================================
RCS file: /home/kde/kdebase/kicker/taskmanager/taskrmbmenu.cpp,v
retrieving revision 1.20
diff -u -p -r1.20 taskrmbmenu.cpp
--- taskmanager/taskrmbmenu.cpp 24 Apr 2005 19:10:50 -0000 1.20
+++ taskmanager/taskrmbmenu.cpp 26 Apr 2005 15:00:07 -0000
@@ -29,6 +29,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE
#include "taskmanager.h"
+#ifdef THUMBNAILING_POSSIBLE
+#include <fixx11h.h>
+#endif
+
#include "taskrmbmenu.h"
#include "taskrmbmenu.moc"
[Attachment #9 (application/pgp-signature)]
>> Visit http://mail.kde.org/mailman/listinfo/kde-devel#unsub to unsubscribe <<
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic