Updated 2012-01-17 10:01:11 by dkf

TkTray is an extension for managing system tray icons with Tk on X Window System.

There are some other extensions for this purpose, like freedock mentioned at system tray. I decided to write my own implementation for a number of reasons:

  • freedock is good enough, but GPL license restricts its uses.
  • tksystray introduces dependency on Imlib. Also, it manages icon windows in the way that prevents wm geometry and winfo from returning something useful. Copying conditions of tksystray are unclear, as no license file is provided.

Tktray is released under BSDL. It uses Tk image manipulation routines to handle images, thus no external dependencies are introduced.

Tktray will handle the case when the system tray manager is not running yet when an icon is created. When the tray manager starts later, Tktray will notice it and request docking for all existing icons.

Version history:

  • tktray 1.3.8 [1] - support for alpha composition, non-photo image types, some bug fixes...
  • tktray 1.3.3 [2] - TEA update, C89 -ansi -pedantic, support for complex alpha images, a lot of bug fixes...
  • tktray 1.2 [3] - prevent crash in some cases where Tk image goes away (thanks to Sergey Golovan)
  • tktray 1.1 [4] - introduced bbox subcommand for getting on-screen icon coordinates
  • tktray 1.0 [5] - initial release

Googie - 3 Nov 2009 - Just tried it (v1.2) under Slackware64 13.0 with Xorg 1.6.3 and KDE 4.3.1 with composition effects enabled. It crashes while trying to:
 tktray::icon .tray -image $img

The crash message is:
 X Error of failed request:  BadValue (integer parameter out of range for operation)
   Major opcode of failed request:  53 (X_CreatePixmap)
   Value in failed request:  0x0
   Serial number of failed request:  415
   Current serial number in output stream:  416

WK I couldn't contact the author directly, and it seems that older GCCs throw a bunch of errors, here's a patch for 1.1 version:
diff -r -u tktray-1.1-orig/tktray.c tktray-1.1/tktray.c
--- tktray-1.1-orig/tktray.c        2006-01-06 18:43:47.000000000 +0100
+++ tktray-1.1/tktray.c        2007-02-03 14:51:56.000000000 +0100
@@ -72,7 +72,14 @@
 static void
 TKU_ImageChanged(ClientData cd,int x, int y, int w, int h, int imgw, int imgh)
 {
+    register int cx,cy;
+    XImage *xim;
+    char *ida;
+    XGCValues gcv;
     TKU_ImageRep *ir = (TKU_ImageRep*) cd;
+    GC gc;
+    Tk_PhotoImageBlock pib;
+
     ir->resized = ( (ir->w != imgw) || (ir->h != imgh) 
             ||(ir->mask == None) );
     if (ir->resized) {
@@ -91,11 +98,9 @@
         x = 0; y = 0; w = imgw; h = imgh;
     }
     /* Allright: create XImage, put it onto mask */
-    char *ida = Tcl_Alloc(w*h);
-    XImage *xim = XCreateImage(Tk_Display(ir->tkwin),
+    ida = Tcl_Alloc(w*h);
+    xim = XCreateImage(Tk_Display(ir->tkwin),
             Tk_Visual(ir->tkwin),1,XYBitmap,0,ida,w,h,8,0); 
-    register int cx,cy;
-    Tk_PhotoImageBlock pib;
     Tk_PhotoGetImage(ir->photo,&pib);
     for(cy=0;cy<h;++cy)
         for(cx=0;cx<w;++cx) {
@@ -105,10 +110,9 @@
             XPutPixel(xim, cx, cy, alpha<128? 0: 1);
         }
 
-    XGCValues gcv;
     gcv.foreground = 1;
     gcv.background = 0;
-    GC gc = XCreateGC(Tk_Display(ir->tkwin),
+    gc = XCreateGC(Tk_Display(ir->tkwin),
             ir->mask,GCForeground|GCBackground,&gcv);
     XSync(Tk_Display(ir->tkwin),False); //FIXME: DEBUG:
     XPutImage(Tk_Display(ir->tkwin),ir->mask,gc,xim,0,0,x,y,w,h);
@@ -125,6 +129,8 @@
 TKU_BindImageRep( Tcl_Interp * interp, Tk_Window tkwin,
         CONST char *image_name, TKU_ImageRep *ir) {
     Tk_PhotoHandle photo;
+    int w,h;
+
     if (image_name == NULL)
         return TCL_ERROR;
     photo = Tk_FindPhoto(interp, image_name);
@@ -136,7 +142,6 @@
     ir->photo = photo;
     ir->tkimg = Tk_GetImage(interp, tkwin, image_name, TKU_ImageChanged,
             (ClientData)ir);
-    int w,h;
     Tk_PhotoGetSize(photo,&w,&h);
     TKU_ImageChanged((ClientData)ir,0,0,w,h,w,h);
     return TCL_OK;
@@ -229,6 +234,7 @@
     Window child_return;
     int wcmd;
     int i;
+    unsigned long timeout = 0;
     enum {XWC_CONFIGURE=0, XWC_BALLOON, XWC_BBOX};
     const char *st_wcmd[]={"configure","balloon","bbox",NULL};
 
@@ -250,7 +256,6 @@
                 Tcl_WrongNumArgs(interp, 2, objv, "message ?timeout?");
                 return TCL_ERROR;
             }
-            unsigned long timeout = 0;
             if (objc==4) 
                 if (Tcl_GetLongFromObj(interp,objv[3],&timeout)!=TCL_OK)
                     return TCL_ERROR;
@@ -304,8 +309,8 @@
     Window tray =
         XGetSelectionOwner(dpy,icon->docksel);
     if (tray != None) {
-        Tk_MakeWindowExist(tkwin);
         XEvent ev;
+        Tk_MakeWindowExist(tkwin);
             memset(&ev, 0, sizeof(ev));
         ev.xclient.type = ClientMessage;
         ev.xclient.window = tray;
@@ -370,8 +375,9 @@
     Tk_Window tkwin = icon -> tkwin;
     Window w = icon -> wrapper;
     Display *dpy = Tk_Display(tkwin);
+    Atom XEMBED_INFO;
     Tk_MakeWindowExist(tkwin);
-    Atom XEMBED_INFO = Tk_InternAtom(tkwin,"_XEMBED_INFO");
+    XEMBED_INFO = Tk_InternAtom(tkwin,"_XEMBED_INFO");
     if (mask & ICON_CONF_IMAGE) {
         TKU_BindImageRep(icon->interp,tkwin,
                     icon->imageString,&icon->img);
@@ -398,8 +404,9 @@
     DockIcon *icon = (DockIcon*) cd;
     icon->flags |=ICON_FLAG_RESHAPE_ON_REDRAW;
     if (icon->img.resized) {
+        XSizeHints *xszh;
         Tk_MakeWindowExist(icon->tkwin);
-        XSizeHints *xszh = XAllocSizeHints();
+        xszh = XAllocSizeHints();
         xszh->flags = PMinSize;
         xszh->min_width = imgw+2;
         xszh->min_height = imgh+2;
@@ -453,10 +460,12 @@
 static
 void DisplayIcon(ClientData cd)
 {
+    int winw;
+    int winh;
     DockIcon *icon = (DockIcon*)cd;
     if (icon->flags & ICON_FLAG_DELETED) return ;
-    int winw = Tk_Width(icon->tkwin),
-        winh = Tk_Height(icon->tkwin);
+    winw = Tk_Width(icon->tkwin),
+    winh = Tk_Height(icon->tkwin);
     icon->flags&=(~ICON_FLAG_REDRAW_PENDING);
     if (winw==0||winh==0||(!icon->visible)||
             icon->img.tkimg==NULL)
@@ -512,15 +521,18 @@
 {
     Tk_Window tkwin = icon -> tkwin;
     Display* dpy = Tk_Display(tkwin);
+    int length;
+    XEvent ev;
+
     if (icon->tray == None) 
         return 0;
-    int length = strlen(utf8msg);
-    XEvent ev;
+    length = strlen(utf8msg);
+
     memset(&ev, 0, sizeof(ev));
     ev.type = ClientMessage;
     ev.xclient.window = icon->wrapper;
     ev.xclient.message_type = 
-        Tk_InternAtom(tkwin,"_NET_SYSTEM_TRAY_OPCODE");
+    Tk_InternAtom(tkwin,"_NET_SYSTEM_TRAY_OPCODE");
     ev.xclient.format = 32;
     ev.xclient.data.l[0]=time(NULL);
     ev.xclient.data.l[1]=SYSTEM_TRAY_BEGIN_MESSAGE;
@@ -530,6 +542,7 @@
     XSendEvent(dpy, icon->tray, False, NoEventMask, &ev);
     XSync(dpy, False);
     /* Sending message elements */
+
     ev.xclient.message_type =
         Tk_InternAtom(tkwin,"_NET_SYSTEM_TRAY_MESSAGE_DATA");
     ev.xclient.format = 8;
@@ -553,14 +566,16 @@
         int objc, Tcl_Obj * CONST objv[]) 
 {
     DockIcon *icon;
+    Tk_Window tkwin;
     if (objc < 2||(objc%2)) {
         Tcl_WrongNumArgs(interp, 1, objv, "pathName ?option value ...?");
         return TCL_ERROR;
     }
-    Tk_Window tkwin = 
-        Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), 
+    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), 
                 Tcl_GetString(objv[1]),"");
+
     TkpWmSetState((TkWindow*)tkwin, WithdrawnState);
+
     if (tkwin == NULL) 
         return TCL_ERROR;
     TKU_CompleteToplevel(tkwin);

See also edit