LinuxComponentPeer::setIcon implementation

EDIT -> updated code for final version
here it is:

[code] void setIcon (const Image& newIcon)
{
int index = 0;
int dataSize = newIcon.getWidth() * newIcon.getHeight() + 2;
uint32* data = (uint32*) juce_malloc (dataSize * sizeof (uint32));

    data[index++] = newIcon.getWidth();
    data[index++] = newIcon.getHeight();

    for (int y = 0; y < newIcon.getHeight(); y++) {
        for (int x = 0; x < newIcon.getWidth(); x++) {
            data[index++] = (uint32) newIcon.getPixelAt (x, y).getARGB ();
        }
    }

    XChangeProperty (display, windowH,
                     XInternAtom (display, "_NET_WM_ICON", False), XA_CARDINAL, 32,
                     PropModeReplace, (unsigned char*) data, dataSize);

    XSync (display, False);

    juce_free (data);
}

[/code]

can surely be optimized, but since most icons are 16x16 or 32/32 this doesn’t impact too much on performance.

would be cool if we could call this from any Component that is actually on Desktop, rather than getting the specific peer and set it manually after a dynamic_cast…

Cool. Thanks kraken, I’ll merge that in. Yes, a method to set the icon for any comp would be a neater way of doing it - I’ll add that.

Oh, you don’t seem to free the data you allocate - I assume that’s a bug, and not done deliberately for some reason?

well… ehrm yeah it’s a feature not implemented rather than a bug…

i’ll try adding a memory free when the window is destroyed and i tell you after…

So X windows doesn’t take a copy of the data?

yes sure. just add a delete[] before returning :slight_smile:

Is this code does the same thing as your function setIcon? The point is that I’d already posted the code at Sat May 05, 2007 at the forum and Julian has promissed me to include that into his TODO list… Weird…

[code]void Component::setWindowIcon(const Image* imageToUse)
{
LinuxComponentPeer* const peer
= dynamic_cast <LinuxComponentPeer*> (getTopLevelComponent()->getPeer());

if (peer == 0) 
    return; 

Window windowH = (Window) peer->getNativeHandle(); 

if (windowH) 
{ 
      XWMHints *wmHints = XAllocWMHints(); 
    wmHints->icon_pixmap = /*juce_createPixmapFromImage(*imageToUse);*/ //this function is not suitable for the purpose for now. It generates pixmaps with strange colours. 
      wmHints->flags = IconPixmapHint; 

    XSetWMHints(display, windowH, wmHints); 
  XFree(wmHints); 
} 

}[/code]

yes, basically it tells the WM to use that specific ARGB icon for the use in a taskbar.

Your way was not understood by all window managers, and still it lacked the real implementation.

Oh, sorry, I must have forgotten about that. It’ll be there on my list somewhere!

that function should be done like this !

[code]void Component::setWindowIcon(const Image* imageToUse)
{
LinuxComponentPeer* const peer
= dynamic_cast <LinuxComponentPeer*> (getTopLevelComponent()->getPeer());

if (peer == 0)
    return;

peer->setIcon (*imageToUse);

}[/code]

First, there is an buffer overflow bug in LinuxComponentPeer::setIcon(). This line

(in SVN) should be

(like it is in krakens initial post) otherwise you overflow the buffer.

Second…

[quote=“kraken”]yes, basically it tells the WM to use that specific ARGB icon for the use in a taskbar.

Your way was not understood by all window managers, and still it lacked the real implementation.[/quote]

Well, your way isn’t understood by all window managers either, at least my GNOME (Ubuntu Hardy) doesn’t display an window icon if it’s set as _NET_WM_ICON.

I implemented the other way to set an windows icon (the way Ptomaine suggested twice) and made some tests on my Ubuntu Hardy:

GNOME: _NET_WM_ICON fails, IconPixmapHint|IconMaskHint works
Xfce: _NET_WM_ICON works, IconPixmapHint|IconMaskHint works
KDE: _NET_WM_ICON works, IconPixmapHint|IconMaskHint works

So, IconPixmapHint|IconMaskHint is at least needed for GNOME.

I finally looked with xprop at the properties of some standard programs of GNOME, Xfce and KDE and all use both ways to set the icon.

Here is the code for them IconPixmapHint|IconMaskHint way to set the window icon (all in juce_linux_Windowing.cpp). First two functions to create pixmaps from images:

[code]Pixmap juce_createColourPixmapFromImage (Display* display, const Image& image)
{
const int width = image.getWidth();
const int height = image.getHeight();
const int size = width * height;
uint32* const colour = (uint32*) juce_malloc (size * sizeof (uint32));
int index = 0;

for (int y = 0; y < height; ++y)
    for (int x = 0; x < width; ++x)
        colour[index++] = image.getPixelAt (x, y).getARGB();

XImage *ximage = XCreateImage (display, CopyFromParent, 24, ZPixmap,
                               0, (char *) colour, width, height, 32, 0);
Pixmap pixmap = XCreatePixmap (display, DefaultRootWindow (display),
                               width, height, 24);
GC gc = XCreateGC (display, pixmap, 0, 0);

XPutImage (display, pixmap, gc, ximage, 0, 0, 0, 0, width, height);
XFreeGC (display, gc);
juce_free (colour);

return pixmap;

}

Pixmap juce_createMaskPixmapFromImage (Display* display, const Image& image)
{
const int width = image.getWidth();
const int height = image.getHeight();
const int stride = (width + 7) >> 3;
uint8* const mask = (uint8*) juce_calloc (stride * height);
bool msbfirst = (BitmapBitOrder (display) == MSBFirst);

for (int y = 0; y < height; ++y)
{
    for (int x = 0; x < width; ++x)
    {
        const uint8 bit = (uint8) (1 << (msbfirst ? (7 - (x & 7)) : (x & 7)));
        const int offset = y * stride + (x >> 3);

        if (image.getPixelAt (x, y).getAlpha() >= 128)
            mask[offset] |= bit;
    }
}

Pixmap pixmap = XCreatePixmapFromBitmapData (display, DefaultRootWindow (display),
                                             (char*) mask, width, height, 1, 0, 1);

juce_free (mask);

return pixmap;

}[/code]

Next the extended setIcon method in LinuxComponentPeer and the new deleteIconPixmaps method to free the pixmaps:

[code] void setIcon (const Image& newIcon)
{
const int width = newIcon.getWidth();
const int height = newIcon.getHeight();
const int dataSize = width * height + 2;

  •   uint32* const data = (uint32*) juce_malloc (dataSize);
    
  •   uint32* const data = (uint32*) juce_malloc (dataSize * sizeof (uint32));
      int index = 0;
    
      data[index++] = width;
      data[index++] = height;
    
      for (int y = 0; y < height; ++y)
          for (int x = 0; x < width; ++x)
              data[index++] = newIcon.getPixelAt (x, y).getARGB();
    
      XChangeProperty (display, windowH,
                       XInternAtom (display, "_NET_WM_ICON", False),
                       XA_CARDINAL, 32, PropModeReplace,
                       (unsigned char*) data, dataSize);
    
      juce_free (data);
    
  •   // update hints with the pixmaps
    
  •   deleteIconPixmaps();
    
  •   XWMHints *wmHints = XGetWMHints (display, windowH);
    
  •   if (wmHints == 0)
    
  •       wmHints = XAllocWMHints();
    
  •   wmHints->flags |= IconPixmapHint | IconMaskHint;
    
  •   wmHints->icon_pixmap = juce_createColourPixmapFromImage (display, newIcon);
    
  •   wmHints->icon_mask = juce_createMaskPixmapFromImage (display, newIcon);
    
  •   XSetWMHints (display, windowH, wmHints);
    
  •   XFree (wmHints);
    
      XSync (display, False);
    

    }

  • void deleteIconPixmaps()

  • {

  •   XWMHints *wmHints = XGetWMHints (display, windowH);
    
  •   if (wmHints != 0)
    
  •   {
    
  •       if ((wmHints->flags & IconPixmapHint) != 0)
    
  •       {
    
  •           wmHints->flags &= ~IconPixmapHint;
    
  •           XFreePixmap (display, wmHints->icon_pixmap);
    
  •       }
    
  •       if ((wmHints->flags & IconMaskHint) != 0)
    
  •       {
    
  •           wmHints->flags &= ~IconMaskHint;
    
  •           XFreePixmap (display, wmHints->icon_mask);
    
  •       }
    
  •       XSetWMHints (display, windowH, wmHints);
    
  •       XFree (wmHints);
    
  •   }
    
  • }[/code]

Finally the modified destructor to call the new deleteIconPixmaps method:

[code] ~LinuxComponentPeer()
{
// it’s dangerous to delete a window on a thread other than the message thread…
checkMessageManagerIsLocked

    deleteTaskBarIcon();
  •   deleteIconPixmaps();
    
      destroyWindow();
    
      windowH = 0;
      delete repainter;
    
    }[/code]

Excellent stuff! I guess the juce_createMouseCursorFromImage() function could also be tidied up to use these two new image functions too.

Thanks!

Well, maybe you should at least test if your changes compile before you commit them to SVN, because you put the two pixmap functions after their usage in LinuxComponentPeer::setIcon.

sure ! that was old and buggy code (but it worked for long time in my gnome system < 2.12).

Here are other things (while you are at it). it basically adds setTooltip implementation for SystemTrayIconComponent on linux:

/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-7 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   JUCE is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330,
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#ifndef __JUCE_SYSTEMTRAYICONCOMPONENT_JUCEHEADER__
#define __JUCE_SYSTEMTRAYICONCOMPONENT_JUCEHEADER__

#if JUCE_WIN32 || JUCE_LINUX

#include "../juce_Component.h"
#include "../windows/juce_TooltipWindow.h"


//==============================================================================
/**
    On Windows only, this component sits in the taskbar tray as a small icon.

    To use it, just create one of these components, but don't attempt to make it
    visible, add it to a parent, or put it on the desktop.

    You can then call setIconImage() to create an icon for it in the taskbar.

    To change the icon's tooltip, you can use setIconTooltip().

    To respond to mouse-events, you can override the normal mouseDown(),
    mouseUp(), mouseDoubleClick() and mouseMove() methods, and although the x, y
    position will not be valid, you can use this to respond to clicks. Traditionally
    you'd use a left-click to show your application's window, and a right-click
    to show a pop-up menu.
*/
class JUCE_API  SystemTrayIconComponent  : public Component
#if JUCE_LINUX
                                         , public SettableTooltipClient
#endif
{
public:
    //==============================================================================
    SystemTrayIconComponent();

    /** Destructor. */
    ~SystemTrayIconComponent();

    //==============================================================================
    /** Changes the image shown in the taskbar.
    */
    void setIconImage (const Image& newImage);

    /** Changes the tooltip that Windows shows above the icon. */
    void setIconTooltip (const String& tooltip);

#if JUCE_LINUX
    /** @internal */
    void paint (Graphics& g);
#endif

    //==============================================================================
    juce_UseDebuggingNewOperator

private:

#if JUCE_LINUX
    TooltipWindow tooltipWindow;
#endif

    //==============================================================================
    SystemTrayIconComponent (const SystemTrayIconComponent&);
    const SystemTrayIconComponent& operator= (const SystemTrayIconComponent&);
};


#endif
#endif   // __JUCE_SYSTEMTRAYICONCOMPONENT_JUCEHEADER__

and juce_linux_Windowing.cpp:

[code]//==============================================================================
void SystemTrayIconComponent::setIconImage (const Image& newImage)
{
if (! isOnDesktop ())
addToDesktop (0);

LinuxComponentPeer* const wp = dynamic_cast <LinuxComponentPeer*> (getPeer());

if (wp != 0)
{
    wp->setTaskBarIcon (newImage);

    setSize (newImage.getWidth(), newImage.getHeight ());
    setVisible (true);
    toFront (false);
    repaint();
}

}

void SystemTrayIconComponent::paint (Graphics& g)
{
LinuxComponentPeer* const wp = dynamic_cast <LinuxComponentPeer*> (getPeer());

if (wp != 0)
{
    const Image* const image = wp->getTaskbarIcon();

    if (image != 0)
        g.drawImageAt (image, 0, 0, false);
}

}

void SystemTrayIconComponent::setIconTooltip (const String& tooltip)
{
setTooltip (tooltip);
}
[/code]

Hope this time is working globally in whole window managers (kde4 included !).

[quote=“kraken”]sure ! that was old and buggy code (but it worked for long time in my gnome system < 2.12).
[/quote]

I tested on two Ubuntu Hardy machines with GNOME 2.22.3 and on both the _NET_WM_ICON doesn’t work. I wonder why it doesn’t work with this GNOME, because KDE and Xfce work with _NET_WM_ICON.

About the SystemTrayIconComponent: The class documentation should be updated too, because it says

and that’s no longer true.

doh! sorry, slightly haphazard at the moment because I’ve got about 3 different source trees going in parallel while I do this mac cocoa stuff!