[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