summaryrefslogtreecommitdiffstats
path: root/unix/tkUnixSysTray.c
diff options
context:
space:
mode:
authorKevin Walzer <kw@codebykevin.com>2020-09-22 02:14:12 (GMT)
committerKevin Walzer <kw@codebykevin.com>2020-09-22 02:14:12 (GMT)
commit02ea7c43872165be2d68131e4cbaabe87268842d (patch)
treef021472d0ade12afab77a171cd8f719f975cabf7 /unix/tkUnixSysTray.c
parent2dd96a74a429ac0d9dbf568028736f7e49d1b2b6 (diff)
downloadtk-02ea7c43872165be2d68131e4cbaabe87268842d.zip
tk-02ea7c43872165be2d68131e4cbaabe87268842d.tar.gz
tk-02ea7c43872165be2d68131e4cbaabe87268842d.tar.bz2
Eliminate most warnings
Diffstat (limited to 'unix/tkUnixSysTray.c')
-rw-r--r--unix/tkUnixSysTray.c3361
1 files changed, 1695 insertions, 1666 deletions
diff --git a/unix/tkUnixSysTray.c b/unix/tkUnixSysTray.c
index 192ab65..67b6d83 100644
--- a/unix/tkUnixSysTray.c
+++ b/unix/tkUnixSysTray.c
@@ -1,1666 +1,1695 @@
-/*
- * tkUnixSysTray.c implements a "systray" Tcl command which permits to
- * change the system tray/taskbar icon of a Tk toplevel window and
- * to post system notifications.
- *
- * Copyright (c) 2005 Anton Kovalenko.
- * Copyright (c) 2020 Kevin Walzer/WordTech Communications LLC.
- *
- * See the file "license.terms" for information on usage and redistribution of
- * this file, and for a DISCLAIMER OF ALL WARRANTIES.
-
-*/
-
-#include <tcl.h>
-#include <tk.h>
-#inlude "tkUnixInt.h"
-
-/*
- * Based extensively on the tktray extension package. Here we are removing
- * non-essential parts of tktray.
- */
-
-#include <time.h>
-#include <string.h>
-#include <stdio.h>
-
-#include <X11/X.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-
-
-/* XEmbed definitions
- * See http://www.freedesktop.org/wiki/Standards_2fxembed_2dspec
- * */
-#define XEMBED_MAPPED (1 << 0)
-/* System tray opcodes
- * See http://www.freedesktop.org/wiki/Standards_2fsystemtray_2dspec
- * */
-#define SYSTEM_TRAY_REQUEST_DOCK 0
-#define SYSTEM_TRAY_BEGIN_MESSAGE 1
-#define SYSTEM_TRAY_CANCEL_MESSAGE 2
-
-/* Flags of widget configuration options */
-#define ICON_CONF_IMAGE (1<<0) /* Image changed */
-#define ICON_CONF_REDISPLAY (1<<1) /* Redisplay required */
-#define ICON_CONF_XEMBED (1<<2) /* Remapping or unmapping required */
-#define ICON_CONF_CLASS (1<<3) /* TODO WM_CLASS update required */
-#define ICON_CONF_FIRST_TIME (1<<4) /* For IconConfigureMethod invoked by the constructor */
-
-/* Widget states */
-#define ICON_FLAG_REDRAW_PENDING (1<<0)
-#define ICON_FLAG_ARGB32 (1<<1)
-#define ICON_FLAG_DIRTY_EDGES (1<<2)
-
-
-#define TKU_NO_BAD_WINDOW_BEGIN(display) \
- { Tk_ErrorHandler error__handler = \
- Tk_CreateErrorHandler(display,BadWindow,-1,-1,(int(*)())NULL, NULL);
-#define TKU_NO_BAD_WINDOW_END Tk_DeleteErrorHandler(error__handler); }
-
-
-
-/* Subscribe for extra X11 events (needed for MANAGER selection) */
-void TKU_AddInput( Display* dpy, Window win, long add_to_mask)
-{
- XWindowAttributes xswa;
- TKU_NO_BAD_WINDOW_BEGIN(dpy)
- XGetWindowAttributes(dpy,win,&xswa);
- XSelectInput(dpy,win,xswa.your_event_mask|add_to_mask);
- TKU_NO_BAD_WINDOW_END
-}
-
-/* Get Tk Window wrapper (make it exist if ny) */
-static Tk_Window TKU_Wrapper(Tk_Window w, Tcl_Interp* interp)
-{
- Tk_Window wrapper = TKU_GetWrapper(w);
- if (!wrapper) {
- Tk_MakeWindowExist(w);
- TKU_WmWithdraw(w, interp);
- Tk_MapWindow(w);
- wrapper = TKU_GetWrapper(w);
- }
- return wrapper;
-}
-
-/* Return X window id for Tk window (make it exist if ny) */
-static Window TKU_XID(Tk_Window w)
-{
- Window xid = Tk_WindowId(w);
- if (xid == None) {
- Tk_MakeWindowExist(w);
- xid = Tk_WindowId(w);
- }
- return xid;
-}
-
-static void TKU_VirtualEvent(Tk_Window tkwin, Tk_Uid eventid)
-{
- union {XEvent general; XVirtualEvent virtual;} event;
-
- memset(&event, 0, sizeof(event));
- event.general.xany.type = VirtualEvent;
- event.general.xany.serial = NextRequest(Tk_Display(tkwin));
- event.general.xany.send_event = False;
- event.general.xany.window = Tk_WindowId(tkwin);
- event.general.xany.display = Tk_Display(tkwin);
- event.virtual.name = eventid;
-
- Tk_QueueWindowEvent(&event.general, TCL_QUEUE_TAIL);
-}
-
-
-/* Data structure representing dock widget */
-typedef struct {
- /* standard for widget */
- Tk_Window tkwin, drawingWin;
- Window wrapper;
- Window myManager;
- Window trayManager;
-
- Tk_OptionTable options;
- Tcl_Interp *interp;
- Tcl_Command widgetCmd;
-
- Tk_Image image; /* image to be drawn */
-
- /* Only one of imageVisualInstance and photo is needed for argb32
- * operations. Unless imageString changes, imageVisualInstance is
- * always valid for the same drawingWin instance, but photo is
- * invalidated by any "whole image" type change. */
-
- Tk_Image imageVisualInstance; /* image instance for use with argb32 */
- Tk_PhotoHandle photo; /* !null if it's really a photo */
-
- /* Offscreen pixmap is created for a given imageWidth,
- * imageHeight, drawingWin, and invalidated (and freed) on image
- * resize or drawingWin destruction.
-
- * Contents of this pixmap is synced on demand; when image changes
- * but is not resized, pixmap is marked as out-of-sync. Next time
- * when redisplay is needed, pixmap is updated before drawing
- * operation.
- */
-
- Pixmap offscreenPixmap;
- /* There is no need to recreate GC ever; it remains valid once
- * created */
-
- GC offscreenGC;
-
- /* XImage for drawing ARGB32 photo on offscreenPixmap. Should be
- freed and nullified each time when a pixmap is freed. Needed
- (and created) when redrawing an image being a photo on ARGB32
- offscreen pixmap. */
- XImage *offscreenImage; /* for photo (argb32) drawing code */
-
- Visual *bestVisual; /* Visual, when it's specified by tray
- * manager AND is guessed to be
- * ARGB32 */
- Colormap bestColormap; /* Colormap for bestVisual */
-
- Atom aMANAGER;
- Atom a_NET_SYSTEM_TRAY_Sn;
- Atom a_XEMBED_INFO;
- Atom a_NET_SYSTEM_TRAY_MESSAGE_DATA;
- Atom a_NET_SYSTEM_TRAY_OPCODE;
- Atom a_NET_SYSTEM_TRAY_ORIENTATION;
- Atom a_NET_SYSTEM_TRAY_VISUAL;
-
- int flags; /* ICON_FLAG_ - see defines above */
- int msgid; /* Last balloon message ID */
- int useShapeExt;
-
- int x,y,width,height;
- int imageWidth, imageHeight;
- int requestedWidth, requestedHeight;
- int visible; /* whether XEMBED_MAPPED should be set */
- int docked; /* whether an icon should be docked */
- char *imageString, /* option: -image as string */
- *classString; /* option: -class as string */
-} DockIcon;
-
-
-/*
- * Forward declarations for procedures defined in this file.
- */
-
-static int TrayIconCreateCmd(ClientData cd, Tcl_Interp *interp,
- int objc, Tcl_Obj * CONST objv[]);
-static int TrayIconObjectCmd(ClientData cd, Tcl_Interp *interp,
- int objc, Tcl_Obj * CONST objv[]);
-static int TrayIconConfigureMethod(DockIcon *icon, Tcl_Interp* interp,
- int objc, Tcl_Obj* CONST objv[],
- int addflags);
-static int PostBalloon(DockIcon* icon, const char * utf8msg,
- long timeout);
-static void CancelBalloon(DockIcon* icon, int msgid);
-static int QueryTrayOrientation(DockIcon* icon);
-
-
-static void TrayIconDeleteProc( ClientData cd );
-static Atom DockSelectionAtomFor(Tk_Window tkwin);
-static void DockToManager(DockIcon *icon);
-static void CreateTrayIconWindow(DockIcon *icon);
-
-static void TrayIconRequestSize(DockIcon* icon, int w, int h);
-static void TrayIconForceImageChange(DockIcon* icon);
-static void TrayIconUpdate(DockIcon* icon, int mask);
-
-static void EventuallyRedrawIcon(DockIcon* icon);
-static void DisplayIcon(ClientData cd);
-
-static void RetargetEvent(DockIcon *icon, XEvent *ev);
-
-static void TrayIconEvent(ClientData cd, XEvent* ev);
-static void UserIconEvent(ClientData cd, XEvent* ev);
-static void TrayIconWrapperEvent(ClientData cd, XEvent* ev);
-static int IconGenericHandler(ClientData cd, XEvent *ev);
-
-int Tktray_Init ( Tcl_Interp* interp );
-
-
-/*
- *----------------------------------------------------------------------
- *
- * TrayIconObjectCmd --
- *
- * Manage attributes of tray icon.
- *
- * Results:
- * Various values of the tray icon are set and retrieved.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static int TrayIconObjectCmd(ClientData cd, Tcl_Interp *interp,
- int objc, Tcl_Obj * CONST objv[])
-{
- DockIcon *icon = (DockIcon*)cd;
- int bbox[4] = {0,0,0,0};
- Tcl_Obj * bboxObj;
- int wcmd;
- int i;
- XWindowAttributes xwa;
- Window bogus;
- int msgid;
-
- enum {XWC_CONFIGURE=0, XWC_CGET, XWC_BALLOON, XWC_CANCEL, XWC_BBOX, XWC_DOCKED, XWC_ORIENTATION};
- const char *st_wcmd[]={"configure","cget","balloon","cancel","bbox","docked","orientation",NULL};
-
- long timeout = 0;
- Tcl_Obj* optionValue;
-
- if (objc<2) {
- Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
- return TCL_ERROR;
- }
- if (Tcl_GetIndexFromObj(interp, objv[1], st_wcmd,
- "subcommand",TCL_EXACT,&wcmd)!=TCL_OK) {
- return TCL_ERROR;
- }
-
- switch (wcmd) {
- case XWC_CONFIGURE:
- return
- TrayIconConfigureMethod(icon,interp,objc-2,objv+2,0);
- case XWC_CGET:
- if (objc!=3) {
- Tcl_WrongNumArgs(interp,2,objv,"option");
- return TCL_ERROR;
- }
- optionValue = Tk_GetOptionValue(interp,(char*)icon,icon->options,objv[2],icon->tkwin);
- if (optionValue) {
- Tcl_SetObjResult(interp,optionValue);
- return TCL_OK;
- } else {
- return TCL_ERROR;
- }
-
- case XWC_BALLOON:
- if ((objc!=3) && (objc!=4)) {
- Tcl_WrongNumArgs(interp, 2, objv, "message ?timeout?");
- return TCL_ERROR;
- }
- if (objc==4) {
- if (Tcl_GetLongFromObj(interp,objv[3],&timeout)!=TCL_OK)
- return TCL_ERROR;
- }
- msgid = PostBalloon(icon,Tcl_GetString(objv[2]), timeout);
- Tcl_SetObjResult(interp,Tcl_NewIntObj(msgid));
- return TCL_OK;
-
- case XWC_CANCEL:
- if (objc!=3) {
- Tcl_WrongNumArgs(interp, 2, objv, "messageId");
- return TCL_ERROR;
- }
- if (Tcl_GetIntFromObj(interp,objv[2],&msgid)!=TCL_OK) {
- return TCL_ERROR;
- }
- if (msgid)
- CancelBalloon(icon,msgid);
- return TCL_OK;
-
- case XWC_BBOX:
- if (icon->drawingWin) {
- XGetWindowAttributes(Tk_Display(icon->drawingWin),
- TKU_XID(icon->drawingWin),
- &xwa);
-
- XTranslateCoordinates(Tk_Display(icon->drawingWin),
- TKU_XID(icon->drawingWin),
- xwa.root, 0,0, &icon->x, &icon->y, &bogus);
- bbox[0] = icon->x;
- bbox[1] = icon->y;
- bbox[2] = bbox[0] + icon->width - 1;
- bbox[3] = bbox[1] + icon->height - 1;
- }
- bboxObj = Tcl_NewObj();
- for (i=0; i<4; ++i) {
- Tcl_ListObjAppendElement(interp, bboxObj,
- Tcl_NewIntObj(bbox[i]));
- }
- Tcl_SetObjResult(interp, bboxObj);
- return TCL_OK;
-
- case XWC_DOCKED:
- Tcl_SetObjResult(interp, Tcl_NewBooleanObj(icon->myManager!=None));
- return TCL_OK;
- case XWC_ORIENTATION:
- if (icon->myManager == None || icon->wrapper == None) {
- Tcl_SetResult(interp, "none", TCL_STATIC);
- } else {
- switch(QueryTrayOrientation(icon)) {
- case 0:
- Tcl_SetResult(interp, "horizontal", TCL_STATIC);
- break;
- case 1:
- Tcl_SetResult(interp, "vertical", TCL_STATIC);
- break;
- default:
- Tcl_SetResult(interp, "unknown", TCL_STATIC);
- break;
- }
- }
- return TCL_OK;
- }
- return TCL_OK;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * QueryTrayOrientation --
- *
- * Obtain the orientation of the tray icon.
- *
- * Results:
- * Orientation is returned.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static int QueryTrayOrientation(DockIcon* icon)
-{
- Atom retType = None;
- int retFormat = 32;
- unsigned long retNitems, retBytesAfter;
- unsigned char *retProp = NULL;
- int result=-1;
-
- if (icon->wrapper != None &&
- icon->myManager != None) {
- XGetWindowProperty(Tk_Display(icon->tkwin),
- icon->myManager,
- icon->a_NET_SYSTEM_TRAY_ORIENTATION,
- /* offset */ 0,
- /* length */ 1,
- /* delete */ False,
- /* type */ XA_CARDINAL,
- &retType, &retFormat, &retNitems,
- &retBytesAfter, &retProp);
- if (retType == XA_CARDINAL && retFormat == 32 && retNitems == 1) {
- result = (int) *(long*)retProp;
- }
- if (retProp) {
- XFree(retProp);
- }
- }
- return result;
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * DockSelectionAtomFor --
- *
- * Obtain the dock selection atom.
- *
- * Results:
- * Selection returned.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-
-static Atom DockSelectionAtomFor(Tk_Window tkwin)
-{
- char buf[256];
- /* no snprintf in C89 */
- sprintf(buf,"_NET_SYSTEM_TRAY_S%d",Tk_ScreenNumber(tkwin));
- return Tk_InternAtom(tkwin,buf);
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * XembedSetState --
- *
- * Set the xembed state.
- *
- * Results:
- * Updates the xembed state.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void XembedSetState(DockIcon *icon, long xembedState)
-{
- long info[] = { 0, 0 };
- info[1] = xembedState;
- if (icon->drawingWin) {
- XChangeProperty(Tk_Display(icon->drawingWin),
- icon->wrapper,
- icon->a_XEMBED_INFO,
- icon->a_XEMBED_INFO, 32,
- PropModeReplace, (unsigned char*)info, 2);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * XembedRequestDock --
- *
- * Obtain the docking window.
- *
- * Results:
- * The dock window is requested.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void XembedRequestDock(DockIcon *icon)
-{
- Tk_Window tkwin = icon->drawingWin;
- XEvent ev;
- Display *dpy = Tk_Display(tkwin);
-
- memset(&ev, 0, sizeof(ev));
- ev.xclient.type = ClientMessage;
- ev.xclient.window = icon->myManager;
- ev.xclient.message_type = icon->a_NET_SYSTEM_TRAY_OPCODE;
- ev.xclient.format = 32;
- ev.xclient.data.l[0]=0;
- ev.xclient.data.l[1]=SYSTEM_TRAY_REQUEST_DOCK;
- ev.xclient.data.l[2]=icon->wrapper;
- ev.xclient.data.l[3]=0;
- ev.xclient.data.l[4]=0;
- XSendEvent(dpy, icon->myManager, True, StructureNotifyMask|SubstructureNotifyMask, &ev);
- }
-
-/*
- *----------------------------------------------------------------------
- *
- * CheckArgbVisual --
- *
- * Find out if a visual is recommended and if it looks like argb32.
- *
- * Results:
- * Render the visual as needed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void CheckArgbVisual(DockIcon *icon)
-{
- /* Find out if a visual is recommended and if it looks like argb32.
- For such visuals we should:
- Recreate a window if it's created but the depth is wrong;
- Don't use ParentRelative but blank background.
- For photo images, draw into a window by XPutImage.
- */
- Atom retType = None;
- int retFormat = 32;
- unsigned long retNitems, retBytesAfter;
- unsigned char *retProp = NULL;
- Visual *match = NULL;
- int depth = 0;
- Colormap cmap = None;
-
- TKU_NO_BAD_WINDOW_BEGIN(Tk_Display(icon->tkwin))
- XGetWindowProperty(Tk_Display(icon->tkwin),
- icon->trayManager,
- icon->a_NET_SYSTEM_TRAY_VISUAL,
- /* offset */ 0,
- /* length */ 1,
- /* delete */ False,
- /* type */ XA_VISUALID,
- &retType, &retFormat, &retNitems,
- &retBytesAfter, &retProp);
- TKU_NO_BAD_WINDOW_END
- if (retType == XA_VISUALID &&
- retNitems == 1 &&
- retFormat == 32) {
- char numeric[256];
- sprintf(numeric,"%ld",*(long*)retProp);
- XFree(retProp);
- match = Tk_GetVisual(icon->interp, icon->tkwin,
- numeric, &depth, &cmap);
- }
- if (match&& depth==32 &&
- match->red_mask == 0xFF0000UL &&
- match->green_mask == 0x00FF00UL &&
- match->blue_mask == 0x0000FFUL) {
- icon->bestVisual = match;
- icon->bestColormap = cmap;
- } else {
- icon->bestVisual = NULL;
- icon->bestColormap = None;
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * CreateTrayIconWindow --
- *
- * Create and configure the window for the icon tray.
- *
- * Results:
- * The window is created and displayed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-static void CreateTrayIconWindow(DockIcon *icon)
-{
- Tcl_SavedResult oldResult;
- Tk_Window tkwin;
- Tk_Window wrapper;
- XSetWindowAttributes attr;
-
- Tcl_SaveResult(icon->interp, &oldResult);
- /* Use the same name (tail) as the widget name, to enable
- name-based icon management for supporting trays, as promised by
- the docs */
- tkwin = icon->drawingWin = Tk_CreateWindow(icon->interp, icon->tkwin,
- Tk_Name(icon->tkwin), "");
- if (tkwin) {
- Tk_SetClass(icon->drawingWin,icon->classString);
- Tk_CreateEventHandler(icon->drawingWin,ExposureMask|StructureNotifyMask|ButtonPressMask|ButtonReleaseMask|
- EnterWindowMask|LeaveWindowMask|PointerMotionMask,
- TrayIconEvent,(ClientData)icon);
- if(icon->bestVisual) {
- Tk_SetWindowVisual(icon->drawingWin,icon->bestVisual,
- 32,icon->bestColormap);
- icon->flags |= ICON_FLAG_ARGB32;
- Tk_SetWindowBackground(tkwin, 0);
- } else {
- Tk_SetWindowBackgroundPixmap(tkwin, ParentRelative);
- icon->flags &= ~ICON_FLAG_ARGB32;
- }
- Tk_MakeWindowExist(tkwin);
- TKU_WmWithdraw(tkwin,icon->interp);
- wrapper = TKU_Wrapper(tkwin,icon->interp);
-
- attr.override_redirect = True;
- Tk_ChangeWindowAttributes(wrapper,CWOverrideRedirect,&attr);
- Tk_CreateEventHandler(wrapper,StructureNotifyMask,TrayIconWrapperEvent,(ClientData)icon);
- if (!icon->bestVisual) {
- Tk_SetWindowBackgroundPixmap(wrapper, ParentRelative);
- } else {
- Tk_SetWindowBackground(tkwin, 0);
- }
- icon->wrapper = TKU_XID(wrapper);
- TrayIconForceImageChange(icon);
- } else {
- Tcl_BackgroundError(icon->interp);
- }
- Tcl_RestoreResult(icon->interp, &oldResult);
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * DockToManager --
- *
- * Helper function to manage icon in display.
- *
- * Results:
- * Icon is created and displayed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-static void DockToManager(DockIcon *icon)
-{
- icon->myManager = icon->trayManager;
- TKU_VirtualEvent(icon->tkwin,Tk_GetUid("IconCreate"));
- XembedSetState(icon, icon->visible ? XEMBED_MAPPED : 0);
- XembedRequestDock(icon);
-}
-
-static
-Tk_OptionSpec IconOptionSpec[]={
- {TK_OPTION_STRING,"-image","image","Image",
- (char *) NULL, -1, Tk_Offset(DockIcon, imageString),
- TK_OPTION_NULL_OK, (ClientData) NULL,
- ICON_CONF_IMAGE | ICON_CONF_REDISPLAY},
- {TK_OPTION_STRING,"-class","class","Class",
- "TrayIcon", -1, Tk_Offset(DockIcon, classString),
- 0, (ClientData) NULL,
- ICON_CONF_CLASS},
- {TK_OPTION_BOOLEAN,"-docked","docked","Docked",
- "1", -1, Tk_Offset(DockIcon, docked),
- 0, (ClientData) NULL,
- ICON_CONF_XEMBED | ICON_CONF_REDISPLAY},
- {TK_OPTION_BOOLEAN,"-shape","shape","Shape",
- "0", -1, Tk_Offset(DockIcon, useShapeExt),
- 0, (ClientData) NULL,
- ICON_CONF_IMAGE | ICON_CONF_REDISPLAY},
- {TK_OPTION_BOOLEAN,"-visible","visible","Visible",
- "1", -1, Tk_Offset(DockIcon, visible),
- 0, (ClientData) NULL,
- ICON_CONF_XEMBED | ICON_CONF_REDISPLAY},
- {TK_OPTION_END}
-};
-
-/*
- *----------------------------------------------------------------------
- *
- * TrayIconRequestSize --
- *
- * Set icon size.
- *
- * Results:
- * Icon size is obtained/set.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void TrayIconRequestSize(DockIcon* icon, int w, int h)
-{
- if (icon->drawingWin) {
- if (icon->requestedWidth != w ||
- icon->requestedHeight != h) {
- Tk_SetMinimumRequestSize(icon->drawingWin,w,h);
- Tk_GeometryRequest(icon->drawingWin,w,h);
- Tk_SetGrid(icon->drawingWin,1,1,w,h);
- icon->requestedWidth = w;
- icon->requestedHeight = h;
- }
- } else {
- /* Sign that no size is requested yet */
- icon->requestedWidth = 0;
- icon->requestedHeight = 0;
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TrayIconImageChanged --
- *
- * Fires when icon state changes.
- *
- * Results:
- * Icon changes are rendered.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void TrayIconImageChanged(ClientData cd,
- int x, int y, int w, int h,
- int imgw, int imgh)
-{
- DockIcon *icon = (DockIcon*) cd;
- if (imgw != icon->imageWidth ||
- imgh != icon->imageHeight) {
- if (icon->offscreenImage) {
- XDestroyImage(icon->offscreenImage);
- icon->offscreenImage = NULL;
- }
- if (icon->offscreenPixmap) {
- /* its size is bad */
- Tk_FreePixmap(Tk_Display(icon->tkwin),
- icon->offscreenPixmap);
- icon->offscreenPixmap = None;
- }
- /* if some image dimension decreases,
- empty areas around the image should be cleared */
- if (imgw < icon->imageWidth ||
- imgh < icon->imageHeight) {
- icon->flags |= ICON_FLAG_DIRTY_EDGES;
- }
- }
- icon->imageWidth = imgw;
- icon->imageHeight = imgh;
- if (imgw == w && imgh == h && x == 0 && y == 0) {
- icon->photo = NULL; /* invalidate */
-
- }
- TrayIconRequestSize(icon,imgw,imgh);
- EventuallyRedrawIcon(icon);
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * IgnoreImageChange --
- *
- * Currently no-op.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-
-static void IgnoreImageChange(ClientData cd,
- int x, int y, int w, int h,
- int imgw, int imgh)
-{
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * ForceImageChange --
- *
- * Push icon changes through.
- *
- * Results:
- * Icon image is updated.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void TrayIconForceImageChange(DockIcon* icon)
-{
- if (icon->image) {
- int w,h;
- Tk_SizeOfImage(icon->image,&w,&h);
- TrayIconImageChanged((ClientData)icon,0,0,w,h,w,h);
- }
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * EventuallyRedrawIcon --
- *
- * Update image icon.
- *
- * Results:
- * Icon image is updated.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void EventuallyRedrawIcon(DockIcon* icon) {
- if (icon->drawingWin && icon->myManager) { /* don't redraw invisible icon */
- if (!(icon->flags&ICON_FLAG_REDRAW_PENDING)) { /* don't schedule multiple redraw ops */
- icon->flags|=ICON_FLAG_REDRAW_PENDING;
- Tcl_DoWhenIdle(DisplayIcon,(ClientData)icon);
- }
- }
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * DisplayIcon --
- *
- * Main function for displaying icon.
- *
- * Results:
- * Icon image is displayed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void DisplayIcon(ClientData cd)
-{
- DockIcon *icon = (DockIcon*)cd;
- int w = icon->imageWidth, h = icon->imageHeight;
- int imgx, imgy, outx, outy, outw, outh;
- imgx = (icon->width >= w) ? 0 : -(icon->width - w)/2;
- imgy = (icon->height >= h) ? 0 : -(icon->height - h)/2;
- outx = (icon->width >= w) ? (icon->width - w)/2 : 0;
- outy = (icon->height >= h) ? (icon->height - h)/2 : 0;
- outw = (icon->width >= w) ? w : icon->width;
- outh = (icon->height >= h) ? h : icon->height;
-
- icon->flags&=(~ICON_FLAG_REDRAW_PENDING);
-
- if (icon->drawingWin && icon->docked) {
- if (icon->flags & ICON_FLAG_ARGB32) {
- /* ARGB32 redraw: never use a ParentRelative method, and
- no need to clear window except FIXME when its size changed.
- Draw on the offscreen pixmap instead, then copy to the window.
- */
- if (icon->offscreenPixmap == None) {
- icon->offscreenPixmap = Tk_GetPixmap(Tk_Display(icon->drawingWin),
- Tk_WindowId(icon->drawingWin),
- w, h, 32);
- }
- if (!icon->photo) {
- icon->photo = Tk_FindPhoto(icon->interp,
- icon->imageString);
- }
- if (!icon->photo && !icon->imageVisualInstance) {
- Tcl_SavedResult saved;
- Tcl_SaveResult(icon->interp,&saved);
- icon->imageVisualInstance = Tk_GetImage(icon->interp,icon->drawingWin,icon->imageString,
- IgnoreImageChange,(ClientData)NULL);
- Tcl_RestoreResult(icon->interp,&saved);
- }
- if (icon->photo && !icon->offscreenImage) {
- icon->offscreenImage = XGetImage(Tk_Display(icon->drawingWin),
- icon->offscreenPixmap,
- 0, 0, w, h, AllPlanes, ZPixmap);
- }
- if (icon->offscreenGC == None) {
- XGCValues gcv;
- gcv.function = GXcopy;
- gcv.plane_mask = AllPlanes;
- gcv.foreground = 0;
- gcv.background = 0;
- icon->offscreenGC = Tk_GetGC(icon->drawingWin,
- GCFunction|GCPlaneMask|
- GCForeground|GCBackground,
- &gcv);
- }
- if (icon->flags & ICON_FLAG_DIRTY_EDGES) {
- XClearWindow(Tk_Display(icon->drawingWin),
- TKU_XID(icon->drawingWin));
- icon->flags &= ~ICON_FLAG_DIRTY_EDGES;
- }
- if (icon->photo) {
- Tk_PhotoImageBlock pib;
- int cx,cy;
- XImage *xim = icon->offscreenImage;
- /* redraw photo using raw data */
- Tk_PhotoGetImage(icon->photo,&pib);
- for (cy=0;cy<h;++cy) {
- for (cx=0;cx<w;++cx) {
- XPutPixel(xim,cx,cy,
- (*(pib.pixelPtr +
- pib.pixelSize*cx +
- pib.pitch*cy +
- pib.offset[0])<<16) |
- (*(pib.pixelPtr +
- pib.pixelSize*cx +
- pib.pitch*cy +
- pib.offset[1])<<8) |
- (*(pib.pixelPtr +
- pib.pixelSize*cx +
- pib.pitch*cy +
- pib.offset[2])) |
- (pib.offset[3] ?
- (*(pib.pixelPtr +
- pib.pixelSize*cx +
- pib.pitch*cy +
- pib.offset[3])<<24) : 0));
- }
- }
- XPutImage(Tk_Display(icon->drawingWin),
- icon->offscreenPixmap,
- icon->offscreenGC,
- icon->offscreenImage,
- 0,0,0,0,w,h);
- } else {
- XFillRectangle(Tk_Display(icon->drawingWin),
- icon->offscreenPixmap,
- icon->offscreenGC,
- 0,0,w,h);
- if (icon->imageVisualInstance) {
- Tk_RedrawImage(icon->imageVisualInstance,
- 0,0,w,h,
- icon->offscreenPixmap,
- 0,0);
- }
- }
- XCopyArea(Tk_Display(icon->drawingWin),
- icon->offscreenPixmap,
- TKU_XID(icon->drawingWin),
- icon->offscreenGC,
- imgx,imgy,outw,outh,outx,outy);
- } else {
- /* Non-argb redraw: clear window and draw an image over it.
- For photos it gives a correct alpha blending with a parent
- window background, even if it's a fancy pixmap (proved to
- work with lxpanel fancy backgrounds).
- */
- XClearWindow(Tk_Display(icon->drawingWin),
- TKU_XID(icon->drawingWin));
- if (icon->image && icon->visible) {
- Tk_RedrawImage(icon->image,imgx,imgy,outw,outh,
- TKU_XID(icon->drawingWin),
- outx, outy);
- }
- }
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * RetargetEvent --
- *
- * Redirect X events to widgets.
- *
- * Results:
- * Icon image is displayed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void RetargetEvent(DockIcon *icon, XEvent *ev)
-{
- int send = 0;
- Window* saveWin1 = NULL, *saveWin2 = NULL;
- if (!icon->visible)
- return;
- switch (ev->type) {
- case MotionNotify:
- send = 1;
- saveWin1 = &ev->xmotion.subwindow;
- saveWin2 = &ev->xmotion.window;
- break;
- case LeaveNotify:
- case EnterNotify:
- send = 1;
- saveWin1 = &ev->xcrossing.subwindow;
- saveWin2 = &ev->xcrossing.window;
- break;
- case ButtonPress:
- case ButtonRelease:
- send = 1;
- saveWin1 = &ev->xbutton.subwindow;
- saveWin2 = &ev->xbutton.window;
- break;
- case MappingNotify:
- send = 1;
- saveWin1 = &ev->xmapping.window;
- }
- if (saveWin1) {
- Tk_MakeWindowExist(icon->tkwin);
- *saveWin1 = Tk_WindowId(icon->tkwin);
- if (saveWin2) *saveWin2 = *saveWin1;
- }
- if (send) {
- ev->xany.send_event = 0x147321ac;
- Tk_HandleEvent(ev);
- }
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * TrayIconWrapperEvent --
- *
- * Ensure automapping in root window is done in withdrawn state.
- *
- * Results:
- * Icon image is displayed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-
-static void TrayIconWrapperEvent(ClientData cd, XEvent* ev)
-{
-
- /* Some embedders, like Docker, add icon windows to save set
- (XAddToSaveSet), so when they crash the icon is reparented to root.
- We have to make sure that automatic mapping in root is done in
- withdrawn state (no way to prevent it entirely)
- */
- DockIcon *icon = (DockIcon*)cd;
- XWindowAttributes attr;
- if (icon->drawingWin) {
- switch(ev->type) {
- case ReparentNotify:
- /* With virtual roots and screen roots etc, the only way
- to check for reparent-to-root is to ask for this root
- first */
- XGetWindowAttributes(ev->xreparent.display,
- ev->xreparent.window,
- &attr);
- if (attr.root == ev->xreparent.parent) {
- /* upon reparent to root, */
- if (icon->drawingWin) {
- /* we were sent away to root */
- TKU_WmWithdraw(icon->drawingWin,icon->interp);
- if (icon->myManager)
- TKU_VirtualEvent(icon->tkwin,Tk_GetUid("IconDestroy"));
- icon->myManager = None;
-
- }
- } /* Reparenting into some other embedder is theoretically possible,
- and everything would just work in this case */
- break;
- }
- }
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * TrayIconEvent --
- *
- * Handle X events.
- *
- * Results:
- * Events are handled and processed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void TrayIconEvent(ClientData cd, XEvent* ev)
-{
- DockIcon *icon = (DockIcon*)cd;
-
- switch (ev->type) {
- case Expose:
- if (!ev->xexpose.count)
- EventuallyRedrawIcon(icon);
- break;
-
- case DestroyNotify:
- /* If anonymous window is destroyed first, then either
- something went wrong with a tray (if -visible) or we just
- reconfigured to invisibility: nothing to be done in both
- cases.
- If unreal window is destroyed first, freeing the data structures
- is the only thing to do.
- */
- if (icon->myManager) {
- TKU_VirtualEvent(icon->tkwin,Tk_GetUid("IconDestroy"));
- }
- Tcl_CancelIdleCall(DisplayIcon,(ClientData)icon);
- icon->flags &= ~ICON_FLAG_REDRAW_PENDING;
- icon->drawingWin = NULL;
- icon->requestedWidth = 0; /* trigger re-request on recreation */
- icon->requestedHeight = 0;
- icon->wrapper = None;
- icon->myManager = None;
-
- break;
-
- case ConfigureNotify:
- TKU_VirtualEvent(icon->tkwin,Tk_GetUid("IconConfigure"));
- if (icon->width != ev->xconfigure.width ||
- icon->height != ev->xconfigure.height) {
- icon->width = ev->xconfigure.width;
- icon->height = ev->xconfigure.height;
- icon->flags |= ICON_FLAG_DIRTY_EDGES;
- EventuallyRedrawIcon(icon);
- }
- RetargetEvent(icon,ev);
- break;
- case MotionNotify:
- case ButtonPress: /* fall through */
- case ButtonRelease:
- case EnterNotify:
- case LeaveNotify:
- RetargetEvent(icon,ev);
- break;
-
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * UserIconEvent --
- *
- * Handle user events.
- *
- * Results:
- * Events are handled and processed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void UserIconEvent(ClientData cd, XEvent* ev)
-{
- DockIcon *icon = (DockIcon*)cd;
-
- switch (ev->type) {
-
- case DestroyNotify:
- Tk_DeleteGenericHandler(IconGenericHandler, (ClientData)icon);
- if(icon->drawingWin) {
- icon->visible = 0;
- Tcl_CancelIdleCall(DisplayIcon,(ClientData)icon);
- icon->flags &= ~ICON_FLAG_REDRAW_PENDING;
- Tk_DestroyWindow(icon->drawingWin);
- }
- if(icon->imageVisualInstance) {
- Tk_FreeImage(icon->imageVisualInstance);
- icon->image = NULL;
- }
- if(icon->offscreenImage) {
- XDestroyImage(icon->offscreenImage);
- icon->offscreenImage = NULL;
- }
- if(icon->offscreenGC) {
- Tk_FreeGC(Tk_Display(icon->tkwin),icon->offscreenGC);
- icon->offscreenGC = NULL;
- }
- if(icon->offscreenPixmap) {
- Tk_FreePixmap(Tk_Display(icon->tkwin),icon->offscreenPixmap);
- }
- if(icon->image) {
- Tk_FreeImage(icon->image);
- icon->image = NULL;
- }
- if(icon->widgetCmd)
- Tcl_DeleteCommandFromToken(icon->interp,icon->widgetCmd);
- Tk_FreeConfigOptions((char*)icon, icon->options, icon->tkwin);
- break;
- }
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * PostBalloon --
- *
- * Display tooltip/balloon window over tray icon.
- *
- * Results:
- * Window is displayed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static int PostBalloon(DockIcon* icon, const char * utf8msg,
- long timeout)
-{
- Tk_Window tkwin = icon -> tkwin;
- Display* dpy = Tk_Display(tkwin);
- int length = strlen(utf8msg);
- XEvent ev;
-
- if (!(icon->drawingWin) || (icon->myManager == None))
- return 0;
- /* overflow protection */
- if (icon->msgid < 0)
- icon->msgid = 0;
-
- memset(&ev, 0, sizeof(ev));
- ev.xclient.type = ClientMessage;
- ev.xclient.window = icon->wrapper;
-
- ev.xclient.message_type =
- icon->a_NET_SYSTEM_TRAY_OPCODE;
- ev.xclient.format = 32;
- ev.xclient.data.l[0]=CurrentTime;
- ev.xclient.data.l[1]=SYSTEM_TRAY_BEGIN_MESSAGE;
- ev.xclient.data.l[2]=timeout;
- ev.xclient.data.l[3]=length;
- ev.xclient.data.l[4]=++icon->msgid;
- TKU_NO_BAD_WINDOW_BEGIN(Tk_Display(icon->tkwin))
- XSendEvent(dpy, icon->myManager , True, StructureNotifyMask|SubstructureNotifyMask, &ev);
- XSync(dpy, False);
-
- /* Sending message elements */
- while (length>0) {
- ev.type = ClientMessage;
- ev.xclient.window = icon->wrapper;
- ev.xclient.message_type = icon->a_NET_SYSTEM_TRAY_MESSAGE_DATA;
- ev.xclient.format = 8;
-
- memset(ev.xclient.data.b,0,20);
- strncpy(ev.xclient.data.b,utf8msg,length<20?length:20);
- XSendEvent(dpy, icon->myManager, True, StructureNotifyMask|SubstructureNotifyMask, &ev);
- XSync(dpy,False);
- utf8msg+=20;
- length-=20;
- }
- TKU_NO_BAD_WINDOW_END;
- return icon->msgid;
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * CancelBalloon --
- *
- * Remove balloon from display over tray icon.
- *
- * Results:
- * Window is destroyed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void CancelBalloon(DockIcon* icon, int msgid)
-{
- Tk_Window tkwin = icon -> tkwin;
- Display* dpy = Tk_Display(tkwin);
- XEvent ev;
-
- if (!(icon->drawingWin) || (icon->myManager == None))
- return;
- /* overflow protection */
- if (icon->msgid < 0)
- icon->msgid = 0;
-
- memset(&ev, 0, sizeof(ev));
- ev.type = ClientMessage;
-
- ev.xclient.window = icon->wrapper;
-
- ev.xclient.message_type =
- icon->a_NET_SYSTEM_TRAY_OPCODE;
- ev.xclient.format = 32;
- ev.xclient.data.l[0]=CurrentTime;
- ev.xclient.data.l[1]=SYSTEM_TRAY_CANCEL_MESSAGE;
- ev.xclient.data.l[2]=msgid;
- TKU_NO_BAD_WINDOW_BEGIN(Tk_Display(icon->tkwin))
- XSendEvent(dpy, icon->myManager , True,
- StructureNotifyMask|SubstructureNotifyMask, &ev);
- TKU_NO_BAD_WINDOW_END
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * IconGenericHandler --
- *
- * Process non-tk events.
- *
- * Results:
- * Events are processed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static int IconGenericHandler(ClientData cd, XEvent *ev)
-{
- DockIcon *icon = (DockIcon*)cd;
-
- if ((ev->type == ClientMessage) &&
- (ev->xclient.message_type == icon->aMANAGER) &&
- ((Atom)ev->xclient.data.l[1] == icon->a_NET_SYSTEM_TRAY_Sn)) {
- icon->trayManager = (Window)ev->xclient.data.l[2];
- XSelectInput(ev->xclient.display,icon->trayManager,StructureNotifyMask);
- if (icon->myManager == None)
- TrayIconUpdate(icon, ICON_CONF_XEMBED);
- return 1;
- }
- if (ev->type == DestroyNotify) {
- if (ev->xdestroywindow.window == icon->trayManager) {
- icon->trayManager = None;
- }
- if (ev->xdestroywindow.window == icon->myManager) {
- icon->myManager = None;
- icon->wrapper = None;
- if (icon->drawingWin) {
- Tk_DestroyWindow(icon->drawingWin);
- icon->drawingWin = NULL;
- }
- }
- }
- return 0;
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * TrayIconUpdate --
- *
- * Get in touch with new options that are certainly valid.
- *
- * Results:
- * Options updated.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-static void TrayIconUpdate(DockIcon* icon, int mask)
-{
- /* why should someone need this option?
- anyway, let's handle it if we provide it */
- if (mask & ICON_CONF_CLASS) {
- if (icon->drawingWin)
- Tk_SetClass(icon->drawingWin,Tk_GetUid(icon->classString));
- }
- /*
- First, ensure right icon visibility.
- If should be visible and not yet managed,
- we have to get the tray or wait for it.
- If should be invisible and managed,
- real-window is simply destroyed.
- If should be invisible and not managed,
- generic handler should be abandoned.
- */
- if (mask & ICON_CONF_XEMBED) {
- if (icon->myManager == None &&
- icon->trayManager != None &&
- icon->docked) {
- CheckArgbVisual(icon);
- if (icon->drawingWin &&
- ((icon->bestVisual && !(icon->flags & ICON_FLAG_ARGB32)) ||
- (!icon->bestVisual && (icon->flags & ICON_FLAG_ARGB32)))) {
- icon->myManager = None;
- icon->wrapper = None;
- icon->requestedWidth = icon->requestedHeight = 0;
- Tk_DestroyWindow(icon->drawingWin);
- icon->drawingWin = NULL;
- }
- if (!icon->drawingWin) {
- CreateTrayIconWindow(icon);
- }
- if (icon->drawingWin) {
- DockToManager(icon);
- }
- }
- if (icon->myManager != None &&
- icon->drawingWin != NULL &&
- !icon->docked) {
- Tk_DestroyWindow(icon->drawingWin);
- icon->drawingWin = NULL;
- icon->myManager = None;
- icon->wrapper = None;
- }
- if (icon->drawingWin) {
- XembedSetState(icon, icon->visible ? XEMBED_MAPPED : 0);
- }
- }
- if (mask & ICON_CONF_IMAGE) {
- TrayIconForceImageChange(icon);
- }
- if (mask & ICON_CONF_REDISPLAY) {
- EventuallyRedrawIcon(icon);
- }
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * TrayIconConfigureMethod --
- *
- * Returns TCL_ERROR if some option is invalid,
- * or else retrieve resource references and free old resources.
- *
- * Results:
- * Widget configured.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-
-static int TrayIconConfigureMethod(DockIcon *icon, Tcl_Interp* interp,
- int objc, Tcl_Obj* CONST objv[],
- int addflags)
-{
- Tk_SavedOptions saved;
- Tk_Image newImage = NULL;
- int mask = 0;
-
- if (objc<=1 && !(addflags & ICON_CONF_FIRST_TIME)) {
- Tcl_Obj* info = Tk_GetOptionInfo(interp,(char*)icon,icon->options,
- objc? objv[0]: NULL, icon->tkwin);
- if (info) {
- Tcl_SetObjResult(interp,info);
- return TCL_OK;
- } else {
- return TCL_ERROR; /* msg by Tk_GetOptionInfo */
- }
- }
-
- if (Tk_SetOptions(interp,(char*)icon,icon->options,objc,objv,
- icon->tkwin,&saved,&mask)!=TCL_OK) {
- return TCL_ERROR; /* msg by Tk_SetOptions */
- }
- mask |= addflags;
- /* now check option validity */
- if (mask & ICON_CONF_IMAGE) {
- if (icon->imageString) {
- newImage = Tk_GetImage(interp, icon->tkwin, icon->imageString,
- TrayIconImageChanged, (ClientData)icon);
- if (!newImage) {
- Tk_RestoreSavedOptions(&saved);
- return TCL_ERROR; /* msg by Tk_GetImage */
- }
- }
- if (icon->image) {
- Tk_FreeImage(icon->image);
- icon->image = NULL;
- }
- if (icon->imageVisualInstance) {
- Tk_FreeImage(icon->imageVisualInstance);
- icon->imageVisualInstance = NULL;
- }
- icon->image = newImage; /* may be null, as intended */
- icon->photo = NULL; /* invalidate photo reference */
- }
- Tk_FreeSavedOptions(&saved);
- /* Now as we are reconfigured... */
- TrayIconUpdate(icon,mask);
- return TCL_OK;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TrayIconDeleteProc --
- *
- * Delete tray window and clean up.
- *
- * Results:
- * Window destroyed.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-
-static void TrayIconDeleteProc( ClientData cd )
-{
- DockIcon *icon = (DockIcon*) cd;
- Tk_DestroyWindow(icon->tkwin);
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * TrayIconCreateCmd --
- *
- * Create tray command and (unreal) window.
- *
- * Results:
- * Icon tray and hidden window created.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-
-static int TrayIconCreateCmd(ClientData cd, Tcl_Interp *interp,
- int objc, Tcl_Obj * CONST objv[])
-{
- Tk_Window mainWindow = cd;
- DockIcon *icon;
-
- icon = (DockIcon*)attemptckalloc(sizeof(DockIcon));
- if (!icon) {
- Tcl_SetResult(interp, "running out of memory", TCL_STATIC);
- goto handleErrors;
- }
- memset(icon,0,sizeof(*icon));
-
- if (objc < 2||(objc%2)) {
- Tcl_WrongNumArgs(interp, 1, objv, "pathName ?option value ...?");
- goto handleErrors;
- }
-
- /* It's not a toplevel window by now. It really doesn't matter,
- because it's not really shown */
- icon->tkwin =
- Tk_CreateWindowFromPath(interp,
- mainWindow,
- Tcl_GetString(objv[1]),"");
- if (icon->tkwin == NULL) {
- goto handleErrors;
- }
-
- /* Subscribe to StructureNotify */
- TKU_AddInput(Tk_Display(icon->tkwin),
- RootWindowOfScreen(Tk_Screen(icon->tkwin)),StructureNotifyMask);
- TKU_AddInput(Tk_Display(icon->tkwin),
- RootWindow(Tk_Display(icon->tkwin),0),StructureNotifyMask);
- /* Spec says "screen 0" not "default", but... */
- TKU_AddInput(Tk_Display(icon->tkwin),
- DefaultRootWindow(Tk_Display(icon->tkwin)),StructureNotifyMask);
-
- /* Early tracking of DestroyNotify is essential */
- Tk_CreateEventHandler(icon->tkwin,StructureNotifyMask,
- UserIconEvent,(ClientData)icon);
-
- /* Now try setting options */
- icon->options = Tk_CreateOptionTable(interp,IconOptionSpec);
- /* Class name is used for retrieving defaults, so... */
- Tk_SetClass(icon->tkwin, Tk_GetUid("TrayIcon"));
- if (Tk_InitOptions(interp,(char*)icon,icon->options,icon->tkwin)!=TCL_OK) {
- goto handleErrors;
- }
-
- icon->a_NET_SYSTEM_TRAY_Sn = DockSelectionAtomFor(icon->tkwin);
- icon->a_NET_SYSTEM_TRAY_OPCODE = Tk_InternAtom(icon->tkwin,"_NET_SYSTEM_TRAY_OPCODE");
- icon->a_NET_SYSTEM_TRAY_MESSAGE_DATA = Tk_InternAtom(icon->tkwin,"_NET_SYSTEM_TRAY_MESSAGE_DATA");
- icon->a_NET_SYSTEM_TRAY_ORIENTATION = Tk_InternAtom(icon->tkwin,"_NET_SYSTEM_TRAY_ORIENTATION");
- icon->a_NET_SYSTEM_TRAY_VISUAL = Tk_InternAtom(icon->tkwin,"_NET_SYSTEM_TRAY_VISUAL");
- icon->a_XEMBED_INFO = Tk_InternAtom(icon->tkwin,"_XEMBED_INFO");
- icon->aMANAGER = Tk_InternAtom(icon->tkwin,"MANAGER");
-
- icon->interp = interp;
-
- icon->trayManager = XGetSelectionOwner(Tk_Display(icon->tkwin),icon->a_NET_SYSTEM_TRAY_Sn);
- if (icon->trayManager) {
- XSelectInput(Tk_Display(icon->tkwin),icon->trayManager, StructureNotifyMask);
- }
-
- Tk_CreateGenericHandler(IconGenericHandler, (ClientData)icon);
-
- if (objc>3) {
- if (TrayIconConfigureMethod(icon, interp, objc-2, objv+2,
- ICON_CONF_XEMBED|ICON_CONF_IMAGE|ICON_CONF_FIRST_TIME)!=TCL_OK) {
- goto handleErrors;
- }
- }
-
- icon->widgetCmd =
- Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]),
- TrayIconObjectCmd, (ClientData)icon, TrayIconDeleteProc);
-
-
- /* Sometimes a command just can't be created... */
- if (!icon->widgetCmd) {
- goto handleErrors;
- }
-
- Tcl_SetObjResult(interp,objv[1]);
- return TCL_OK;
-
-handleErrors:
- /* Rolling back */
- if (icon) {
- if (icon->options) {
- Tk_DeleteOptionTable(icon->options);
- icon->options = NULL;
- }
- if (icon->tkwin) {
- /* Resources will be freed by DestroyNotify handler */
- Tk_DestroyWindow(icon->tkwin);
- }
- ckfree((char*)icon);
- }
- return TCL_ERROR;
-}
-
-
-/*
- *----------------------------------------------------------------------
- *
- * Tktray_Init --
- *
- * Initialize the command.
- *
- * Results:
- * Command initialized.
- *
- * Side effects:
- * None.
- *
- *-------------------------------z---------------------------------------
- */
-
-int Tktray_Init ( Tcl_Interp* interp )
-{
-
- Tcl_CreateObjCommand(interp, "_systray",
- TrayIconCreateCmd, Tk_MainWindow(interp), NULL );
-
- return TCL_OK;
-}
+/*
+ * tkUnixSysTray.c implements a "systray" Tcl command which permits to
+ * change the system tray/taskbar icon of a Tk toplevel window and
+ * to post system notifications.
+ *
+ * Copyright (c) 2005 Anton Kovalenko.
+ * Copyright (c) 2020 Kevin Walzer/WordTech Communications LLC.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+
+*/
+
+#include <tcl.h>
+#include <tk.h>
+#include "tkUnixInt.h"
+
+/*
+ * Based extensively on the tktray extension package. Here we are removing
+ * non-essential parts of tktray.
+ */
+
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <X11/X.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+
+/* XEmbed definitions
+ * See http://www.freedesktop.org/wiki/Standards_2fxembed_2dspec
+ * */
+#define XEMBED_MAPPED (1 << 0)
+/* System tray opcodes
+ * See http://www.freedesktop.org/wiki/Standards_2fsystemtray_2dspec
+ * */
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+/* Flags of widget configuration options */
+#define ICON_CONF_IMAGE (1<<0) /* Image changed */
+#define ICON_CONF_REDISPLAY (1<<1) /* Redisplay required */
+#define ICON_CONF_XEMBED (1<<2) /* Remapping or unmapping required */
+#define ICON_CONF_CLASS (1<<3) /* TODO WM_CLASS update required */
+#define ICON_CONF_FIRST_TIME (1<<4) /* For IconConfigureMethod invoked by the constructor */
+
+/* Widget states */
+#define ICON_FLAG_REDRAW_PENDING (1<<0)
+#define ICON_FLAG_ARGB32 (1<<1)
+#define ICON_FLAG_DIRTY_EDGES (1<<2)
+
+
+#define TKU_NO_BAD_WINDOW_BEGIN(display) \
+ { Tk_ErrorHandler error__handler = \
+ Tk_CreateErrorHandler(display,BadWindow,-1,-1,(int(*)())NULL, NULL);
+#define TKU_NO_BAD_WINDOW_END Tk_DeleteErrorHandler(error__handler); }
+
+/*Declaration for utility functions.*/
+static void TKU_WmWithdraw(Tk_Window winPtr, Tcl_Interp* interp);
+static Tk_Window TKU_GetWrapper(Tk_Window winPtr);
+void TKU_AddInput( Display* dpy, Window win, long add_to_mask);
+static Tk_Window TKU_Wrapper(Tk_Window w, Tcl_Interp* interp);
+static Window TKU_XID(Tk_Window w);
+static void TKU_VirtualEvent(Tk_Window tkwin, Tk_Uid eventid);
+
+/* Customized window withdraw */
+static void TKU_WmWithdraw(Tk_Window winPtr, Tcl_Interp* interp)
+{
+ /*Silence compiler warning.*/
+ (void) interp;
+ TkpWmSetState((TkWindow*)winPtr, WithdrawnState);
+}
+
+/* The wrapper should exist */
+static Tk_Window TKU_GetWrapper(Tk_Window winPtr)
+{
+ return (Tk_Window)
+ TkpGetWrapperWindow((TkWindow*)winPtr);
+}
+
+/* Subscribe for extra X11 events (needed for MANAGER selection) */
+void TKU_AddInput( Display* dpy, Window win, long add_to_mask)
+{
+ XWindowAttributes xswa;
+ TKU_NO_BAD_WINDOW_BEGIN(dpy)
+ XGetWindowAttributes(dpy,win,&xswa);
+ XSelectInput(dpy,win,xswa.your_event_mask|add_to_mask);
+ TKU_NO_BAD_WINDOW_END
+}
+
+/* Get Tk Window wrapper (make it exist if ny) */
+static Tk_Window TKU_Wrapper(Tk_Window w, Tcl_Interp* interp)
+{
+ Tk_Window wrapper = TKU_GetWrapper(w);
+ if (!wrapper) {
+ Tk_MakeWindowExist(w);
+ TKU_WmWithdraw(w, interp);
+ Tk_MapWindow(w);
+ wrapper = (Tk_Window) TKU_GetWrapper(w);
+ }
+ return wrapper;
+}
+
+/* Return X window id for Tk window (make it exist if ny) */
+static Window TKU_XID(Tk_Window w)
+{
+ Window xid = Tk_WindowId(w);
+ if (xid == None) {
+ Tk_MakeWindowExist(w);
+ xid = Tk_WindowId(w);
+ }
+ return xid;
+}
+
+static void TKU_VirtualEvent(Tk_Window tkwin, Tk_Uid eventid)
+{
+ union {XEvent general; XVirtualEvent virtual;} event;
+
+ memset(&event, 0, sizeof(event));
+ event.general.xany.type = VirtualEvent;
+ event.general.xany.serial = NextRequest(Tk_Display(tkwin));
+ event.general.xany.send_event = False;
+ event.general.xany.window = Tk_WindowId(tkwin);
+ event.general.xany.display = Tk_Display(tkwin);
+ event.virtual.name = eventid;
+
+ Tk_QueueWindowEvent(&event.general, TCL_QUEUE_TAIL);
+}
+
+
+/* Data structure representing dock widget */
+typedef struct {
+ /* standard for widget */
+ Tk_Window tkwin, drawingWin;
+ Window wrapper;
+ Window myManager;
+ Window trayManager;
+
+ Tk_OptionTable options;
+ Tcl_Interp *interp;
+ Tcl_Command widgetCmd;
+
+ Tk_Image image; /* image to be drawn */
+
+ /* Only one of imageVisualInstance and photo is needed for argb32
+ * operations. Unless imageString changes, imageVisualInstance is
+ * always valid for the same drawingWin instance, but photo is
+ * invalidated by any "whole image" type change. */
+
+ Tk_Image imageVisualInstance; /* image instance for use with argb32 */
+ Tk_PhotoHandle photo; /* !null if it's really a photo */
+
+ /* Offscreen pixmap is created for a given imageWidth,
+ * imageHeight, drawingWin, and invalidated (and freed) on image
+ * resize or drawingWin destruction.
+
+ * Contents of this pixmap is synced on demand; when image changes
+ * but is not resized, pixmap is marked as out-of-sync. Next time
+ * when redisplay is needed, pixmap is updated before drawing
+ * operation.
+ */
+
+ Pixmap offscreenPixmap;
+ /* There is no need to recreate GC ever; it remains valid once
+ * created */
+
+ GC offscreenGC;
+
+ /* XImage for drawing ARGB32 photo on offscreenPixmap. Should be
+ freed and nullified each time when a pixmap is freed. Needed
+ (and created) when redrawing an image being a photo on ARGB32
+ offscreen pixmap. */
+ XImage *offscreenImage; /* for photo (argb32) drawing code */
+
+ Visual *bestVisual; /* Visual, when it's specified by tray
+ * manager AND is guessed to be
+ * ARGB32 */
+ Colormap bestColormap; /* Colormap for bestVisual */
+
+ Atom aMANAGER;
+ Atom a_NET_SYSTEM_TRAY_Sn;
+ Atom a_XEMBED_INFO;
+ Atom a_NET_SYSTEM_TRAY_MESSAGE_DATA;
+ Atom a_NET_SYSTEM_TRAY_OPCODE;
+ Atom a_NET_SYSTEM_TRAY_ORIENTATION;
+ Atom a_NET_SYSTEM_TRAY_VISUAL;
+
+ int flags; /* ICON_FLAG_ - see defines above */
+ int msgid; /* Last balloon message ID */
+ int useShapeExt;
+
+ int x,y,width,height;
+ int imageWidth, imageHeight;
+ int requestedWidth, requestedHeight;
+ int visible; /* whether XEMBED_MAPPED should be set */
+ int docked; /* whether an icon should be docked */
+ char *imageString, /* option: -image as string */
+ *classString; /* option: -class as string */
+} DockIcon;
+
+
+/*
+ * Forward declarations for procedures defined in this file.
+ */
+
+static int TrayIconCreateCmd(ClientData cd, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int TrayIconObjectCmd(ClientData cd, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int TrayIconConfigureMethod(DockIcon *icon, Tcl_Interp* interp,
+ int objc, Tcl_Obj *const objv[],
+ int addflags);
+static int PostBalloon(DockIcon* icon, const char * utf8msg,
+ long timeout);
+static void CancelBalloon(DockIcon* icon, int msgid);
+static int QueryTrayOrientation(DockIcon* icon);
+
+
+static void TrayIconDeleteProc( ClientData cd );
+static Atom DockSelectionAtomFor(Tk_Window tkwin);
+static void DockToManager(DockIcon *icon);
+static void CreateTrayIconWindow(DockIcon *icon);
+
+static void TrayIconRequestSize(DockIcon* icon, int w, int h);
+static void TrayIconForceImageChange(DockIcon* icon);
+static void TrayIconUpdate(DockIcon* icon, int mask);
+
+static void EventuallyRedrawIcon(DockIcon* icon);
+static void DisplayIcon(ClientData cd);
+
+static void RetargetEvent(DockIcon *icon, XEvent *ev);
+
+static void TrayIconEvent(ClientData cd, XEvent* ev);
+static void UserIconEvent(ClientData cd, XEvent* ev);
+static void TrayIconWrapperEvent(ClientData cd, XEvent* ev);
+static int IconGenericHandler(ClientData cd, XEvent *ev);
+
+int Tktray_Init ( Tcl_Interp* interp );
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TrayIconObjectCmd --
+ *
+ * Manage attributes of tray icon.
+ *
+ * Results:
+ * Various values of the tray icon are set and retrieved.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static int TrayIconObjectCmd(ClientData cd, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ DockIcon *icon = (DockIcon*)cd;
+ int bbox[4] = {0,0,0,0};
+ Tcl_Obj * bboxObj;
+ int wcmd;
+ int i;
+ XWindowAttributes xwa;
+ Window bogus;
+ int msgid;
+
+ enum {XWC_CONFIGURE=0, XWC_CGET, XWC_BALLOON, XWC_CANCEL, XWC_BBOX, XWC_DOCKED, XWC_ORIENTATION};
+ const char *st_wcmd[]={"configure","cget","balloon","cancel","bbox","docked","orientation",NULL};
+
+ long timeout = 0;
+ Tcl_Obj* optionValue;
+
+ if (objc<2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObj(interp, objv[1], st_wcmd,
+ "subcommand",TCL_EXACT,&wcmd)!=TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ switch (wcmd) {
+ case XWC_CONFIGURE:
+ return
+ TrayIconConfigureMethod(icon,interp,objc-2,objv+2,0);
+ case XWC_CGET:
+ if (objc!=3) {
+ Tcl_WrongNumArgs(interp,2,objv,"option");
+ return TCL_ERROR;
+ }
+ optionValue = Tk_GetOptionValue(interp,(char*)icon,icon->options,objv[2],icon->tkwin);
+ if (optionValue) {
+ Tcl_SetObjResult(interp,optionValue);
+ return TCL_OK;
+ } else {
+ return TCL_ERROR;
+ }
+
+ case XWC_BALLOON:
+ if ((objc!=3) && (objc!=4)) {
+ Tcl_WrongNumArgs(interp, 2, objv, "message ?timeout?");
+ return TCL_ERROR;
+ }
+ if (objc==4) {
+ if (Tcl_GetLongFromObj(interp,objv[3],&timeout)!=TCL_OK)
+ return TCL_ERROR;
+ }
+ msgid = PostBalloon(icon,Tcl_GetString(objv[2]), timeout);
+ Tcl_SetObjResult(interp,Tcl_NewIntObj(msgid));
+ return TCL_OK;
+
+ case XWC_CANCEL:
+ if (objc!=3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "messageId");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIntFromObj(interp,objv[2],&msgid)!=TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (msgid)
+ CancelBalloon(icon,msgid);
+ return TCL_OK;
+
+ case XWC_BBOX:
+ if (icon->drawingWin) {
+ XGetWindowAttributes(Tk_Display(icon->drawingWin),
+ TKU_XID(icon->drawingWin),
+ &xwa);
+
+ XTranslateCoordinates(Tk_Display(icon->drawingWin),
+ TKU_XID(icon->drawingWin),
+ xwa.root, 0,0, &icon->x, &icon->y, &bogus);
+ bbox[0] = icon->x;
+ bbox[1] = icon->y;
+ bbox[2] = bbox[0] + icon->width - 1;
+ bbox[3] = bbox[1] + icon->height - 1;
+ }
+ bboxObj = Tcl_NewObj();
+ for (i=0; i<4; ++i) {
+ Tcl_ListObjAppendElement(interp, bboxObj,
+ Tcl_NewIntObj(bbox[i]));
+ }
+ Tcl_SetObjResult(interp, bboxObj);
+ return TCL_OK;
+
+ case XWC_DOCKED:
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(icon->myManager!=None));
+ return TCL_OK;
+ case XWC_ORIENTATION:
+ if (icon->myManager == None || icon->wrapper == None) {
+ Tcl_SetResult(interp, "none", TCL_STATIC);
+ } else {
+ switch(QueryTrayOrientation(icon)) {
+ case 0:
+ Tcl_SetResult(interp, "horizontal", TCL_STATIC);
+ break;
+ case 1:
+ Tcl_SetResult(interp, "vertical", TCL_STATIC);
+ break;
+ default:
+ Tcl_SetResult(interp, "unknown", TCL_STATIC);
+ break;
+ }
+ }
+ return TCL_OK;
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * QueryTrayOrientation --
+ *
+ * Obtain the orientation of the tray icon.
+ *
+ * Results:
+ * Orientation is returned.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static int QueryTrayOrientation(DockIcon* icon)
+{
+ Atom retType = None;
+ int retFormat = 32;
+ unsigned long retNitems, retBytesAfter;
+ unsigned char *retProp = NULL;
+ int result=-1;
+
+ if (icon->wrapper != None &&
+ icon->myManager != None) {
+ XGetWindowProperty(Tk_Display(icon->tkwin),
+ icon->myManager,
+ icon->a_NET_SYSTEM_TRAY_ORIENTATION,
+ /* offset */ 0,
+ /* length */ 1,
+ /* delete */ False,
+ /* type */ XA_CARDINAL,
+ &retType, &retFormat, &retNitems,
+ &retBytesAfter, &retProp);
+ if (retType == XA_CARDINAL && retFormat == 32 && retNitems == 1) {
+ result = (int) *(long*)retProp;
+ }
+ if (retProp) {
+ XFree(retProp);
+ }
+ }
+ return result;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DockSelectionAtomFor --
+ *
+ * Obtain the dock selection atom.
+ *
+ * Results:
+ * Selection returned.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+
+static Atom DockSelectionAtomFor(Tk_Window tkwin)
+{
+ char buf[256];
+ /* no snprintf in C89 */
+ sprintf(buf,"_NET_SYSTEM_TRAY_S%d",Tk_ScreenNumber(tkwin));
+ return Tk_InternAtom(tkwin,buf);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * XembedSetState --
+ *
+ * Set the xembed state.
+ *
+ * Results:
+ * Updates the xembed state.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void XembedSetState(DockIcon *icon, long xembedState)
+{
+ long info[] = { 0, 0 };
+ info[1] = xembedState;
+ if (icon->drawingWin) {
+ XChangeProperty(Tk_Display(icon->drawingWin),
+ icon->wrapper,
+ icon->a_XEMBED_INFO,
+ icon->a_XEMBED_INFO, 32,
+ PropModeReplace, (unsigned char*)info, 2);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * XembedRequestDock --
+ *
+ * Obtain the docking window.
+ *
+ * Results:
+ * The dock window is requested.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void XembedRequestDock(DockIcon *icon)
+{
+ Tk_Window tkwin = icon->drawingWin;
+ XEvent ev;
+ Display *dpy = Tk_Display(tkwin);
+
+ memset(&ev, 0, sizeof(ev));
+ ev.xclient.type = ClientMessage;
+ ev.xclient.window = icon->myManager;
+ ev.xclient.message_type = icon->a_NET_SYSTEM_TRAY_OPCODE;
+ ev.xclient.format = 32;
+ ev.xclient.data.l[0]=0;
+ ev.xclient.data.l[1]=SYSTEM_TRAY_REQUEST_DOCK;
+ ev.xclient.data.l[2]=icon->wrapper;
+ ev.xclient.data.l[3]=0;
+ ev.xclient.data.l[4]=0;
+ XSendEvent(dpy, icon->myManager, True, StructureNotifyMask|SubstructureNotifyMask, &ev);
+ }
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CheckArgbVisual --
+ *
+ * Find out if a visual is recommended and if it looks like argb32.
+ *
+ * Results:
+ * Render the visual as needed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void CheckArgbVisual(DockIcon *icon)
+{
+ /* Find out if a visual is recommended and if it looks like argb32.
+ For such visuals we should:
+ Recreate a window if it's created but the depth is wrong;
+ Don't use ParentRelative but blank background.
+ For photo images, draw into a window by XPutImage.
+ */
+ Atom retType = None;
+ int retFormat = 32;
+ unsigned long retNitems, retBytesAfter;
+ unsigned char *retProp = NULL;
+ Visual *match = NULL;
+ int depth = 0;
+ Colormap cmap = None;
+
+ TKU_NO_BAD_WINDOW_BEGIN(Tk_Display(icon->tkwin))
+ XGetWindowProperty(Tk_Display(icon->tkwin),
+ icon->trayManager,
+ icon->a_NET_SYSTEM_TRAY_VISUAL,
+ /* offset */ 0,
+ /* length */ 1,
+ /* delete */ False,
+ /* type */ XA_VISUALID,
+ &retType, &retFormat, &retNitems,
+ &retBytesAfter, &retProp);
+ TKU_NO_BAD_WINDOW_END
+ if (retType == XA_VISUALID &&
+ retNitems == 1 &&
+ retFormat == 32) {
+ char numeric[256];
+ sprintf(numeric,"%ld",*(long*)retProp);
+ XFree(retProp);
+ match = Tk_GetVisual(icon->interp, icon->tkwin,
+ numeric, &depth, &cmap);
+ }
+ if (match&& depth==32 &&
+ match->red_mask == 0xFF0000UL &&
+ match->green_mask == 0x00FF00UL &&
+ match->blue_mask == 0x0000FFUL) {
+ icon->bestVisual = match;
+ icon->bestColormap = cmap;
+ } else {
+ icon->bestVisual = NULL;
+ icon->bestColormap = None;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CreateTrayIconWindow --
+ *
+ * Create and configure the window for the icon tray.
+ *
+ * Results:
+ * The window is created and displayed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+static void CreateTrayIconWindow(DockIcon *icon)
+{
+ Tcl_SavedResult oldResult;
+ Tk_Window tkwin;
+ Tk_Window wrapper;
+ XSetWindowAttributes attr;
+
+ Tcl_SaveResult(icon->interp, &oldResult);
+ /* Use the same name (tail) as the widget name, to enable
+ name-based icon management for supporting trays, as promised by
+ the docs */
+ tkwin = icon->drawingWin = Tk_CreateWindow(icon->interp, icon->tkwin,
+ Tk_Name(icon->tkwin), "");
+ if (tkwin) {
+ Tk_SetClass(icon->drawingWin,icon->classString);
+ Tk_CreateEventHandler(icon->drawingWin,ExposureMask|StructureNotifyMask|ButtonPressMask|ButtonReleaseMask|
+ EnterWindowMask|LeaveWindowMask|PointerMotionMask,
+ TrayIconEvent,(ClientData)icon);
+ if(icon->bestVisual) {
+ Tk_SetWindowVisual(icon->drawingWin,icon->bestVisual,
+ 32,icon->bestColormap);
+ icon->flags |= ICON_FLAG_ARGB32;
+ Tk_SetWindowBackground(tkwin, 0);
+ } else {
+ Tk_SetWindowBackgroundPixmap(tkwin, ParentRelative);
+ icon->flags &= ~ICON_FLAG_ARGB32;
+ }
+ Tk_MakeWindowExist(tkwin);
+ TKU_WmWithdraw(tkwin,icon->interp);
+ wrapper = TKU_Wrapper(tkwin,icon->interp);
+
+ attr.override_redirect = True;
+ Tk_ChangeWindowAttributes(wrapper,CWOverrideRedirect,&attr);
+ Tk_CreateEventHandler(wrapper,StructureNotifyMask,TrayIconWrapperEvent,(ClientData)icon);
+ if (!icon->bestVisual) {
+ Tk_SetWindowBackgroundPixmap(wrapper, ParentRelative);
+ } else {
+ Tk_SetWindowBackground(tkwin, 0);
+ }
+ icon->wrapper = TKU_XID(wrapper);
+ TrayIconForceImageChange(icon);
+ } else {
+ Tcl_BackgroundError(icon->interp);
+ }
+ Tcl_RestoreResult(icon->interp, &oldResult);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DockToManager --
+ *
+ * Helper function to manage icon in display.
+ *
+ * Results:
+ * Icon is created and displayed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+static void DockToManager(DockIcon *icon)
+{
+ icon->myManager = icon->trayManager;
+ TKU_VirtualEvent(icon->tkwin,Tk_GetUid("IconCreate"));
+ XembedSetState(icon, icon->visible ? XEMBED_MAPPED : 0);
+ XembedRequestDock(icon);
+}
+
+static
+Tk_OptionSpec IconOptionSpec[]={
+ {TK_OPTION_STRING,"-image","image","Image",
+ (char *) NULL, -1, Tk_Offset(DockIcon, imageString),
+ TK_OPTION_NULL_OK, (ClientData) NULL,
+ ICON_CONF_IMAGE | ICON_CONF_REDISPLAY},
+ {TK_OPTION_STRING,"-class","class","Class",
+ "TrayIcon", -1, Tk_Offset(DockIcon, classString),
+ 0, (ClientData) NULL,
+ ICON_CONF_CLASS},
+ {TK_OPTION_BOOLEAN,"-docked","docked","Docked",
+ "1", -1, Tk_Offset(DockIcon, docked),
+ 0, (ClientData) NULL,
+ ICON_CONF_XEMBED | ICON_CONF_REDISPLAY},
+ {TK_OPTION_BOOLEAN,"-shape","shape","Shape",
+ "0", -1, Tk_Offset(DockIcon, useShapeExt),
+ 0, (ClientData) NULL,
+ ICON_CONF_IMAGE | ICON_CONF_REDISPLAY},
+ {TK_OPTION_BOOLEAN,"-visible","visible","Visible",
+ "1", -1, Tk_Offset(DockIcon, visible),
+ 0, (ClientData) NULL,
+ ICON_CONF_XEMBED | ICON_CONF_REDISPLAY},
+ {TK_OPTION_END}
+};
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TrayIconRequestSize --
+ *
+ * Set icon size.
+ *
+ * Results:
+ * Icon size is obtained/set.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void TrayIconRequestSize(DockIcon* icon, int w, int h)
+{
+ if (icon->drawingWin) {
+ if (icon->requestedWidth != w ||
+ icon->requestedHeight != h) {
+ Tk_SetMinimumRequestSize(icon->drawingWin,w,h);
+ Tk_GeometryRequest(icon->drawingWin,w,h);
+ Tk_SetGrid(icon->drawingWin,1,1,w,h);
+ icon->requestedWidth = w;
+ icon->requestedHeight = h;
+ }
+ } else {
+ /* Sign that no size is requested yet */
+ icon->requestedWidth = 0;
+ icon->requestedHeight = 0;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TrayIconImageChanged --
+ *
+ * Fires when icon state changes.
+ *
+ * Results:
+ * Icon changes are rendered.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void TrayIconImageChanged(ClientData cd,
+ int x, int y, int w, int h,
+ int imgw, int imgh)
+{
+ DockIcon *icon = (DockIcon*) cd;
+ if (imgw != icon->imageWidth ||
+ imgh != icon->imageHeight) {
+ if (icon->offscreenImage) {
+ XDestroyImage(icon->offscreenImage);
+ icon->offscreenImage = NULL;
+ }
+ if (icon->offscreenPixmap) {
+ /* its size is bad */
+ Tk_FreePixmap(Tk_Display(icon->tkwin),
+ icon->offscreenPixmap);
+ icon->offscreenPixmap = None;
+ }
+ /* if some image dimension decreases,
+ empty areas around the image should be cleared */
+ if (imgw < icon->imageWidth ||
+ imgh < icon->imageHeight) {
+ icon->flags |= ICON_FLAG_DIRTY_EDGES;
+ }
+ }
+ icon->imageWidth = imgw;
+ icon->imageHeight = imgh;
+ if (imgw == w && imgh == h && x == 0 && y == 0) {
+ icon->photo = NULL; /* invalidate */
+
+ }
+ TrayIconRequestSize(icon,imgw,imgh);
+ EventuallyRedrawIcon(icon);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * IgnoreImageChange --
+ *
+ * Currently no-op.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+
+static void IgnoreImageChange(ClientData cd,
+ int x, int y, int w, int h,
+ int imgw, int imgh)
+{
+ /*Silence compiler warnings.*/
+ (void)cd;
+ (void)x;
+ (void)y;
+ (void)w;
+ (void)h;
+ (void)imgw;
+ (void)imgh;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ForceImageChange --
+ *
+ * Push icon changes through.
+ *
+ * Results:
+ * Icon image is updated.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void TrayIconForceImageChange(DockIcon* icon)
+{
+ if (icon->image) {
+ int w,h;
+ Tk_SizeOfImage(icon->image,&w,&h);
+ TrayIconImageChanged((ClientData)icon,0,0,w,h,w,h);
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * EventuallyRedrawIcon --
+ *
+ * Update image icon.
+ *
+ * Results:
+ * Icon image is updated.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void EventuallyRedrawIcon(DockIcon* icon) {
+ if (icon->drawingWin && icon->myManager) { /* don't redraw invisible icon */
+ if (!(icon->flags&ICON_FLAG_REDRAW_PENDING)) { /* don't schedule multiple redraw ops */
+ icon->flags|=ICON_FLAG_REDRAW_PENDING;
+ Tcl_DoWhenIdle(DisplayIcon,(ClientData)icon);
+ }
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DisplayIcon --
+ *
+ * Main function for displaying icon.
+ *
+ * Results:
+ * Icon image is displayed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void DisplayIcon(ClientData cd)
+{
+ DockIcon *icon = (DockIcon*)cd;
+ int w = icon->imageWidth, h = icon->imageHeight;
+ int imgx, imgy, outx, outy, outw, outh;
+ imgx = (icon->width >= w) ? 0 : -(icon->width - w)/2;
+ imgy = (icon->height >= h) ? 0 : -(icon->height - h)/2;
+ outx = (icon->width >= w) ? (icon->width - w)/2 : 0;
+ outy = (icon->height >= h) ? (icon->height - h)/2 : 0;
+ outw = (icon->width >= w) ? w : icon->width;
+ outh = (icon->height >= h) ? h : icon->height;
+
+ icon->flags&=(~ICON_FLAG_REDRAW_PENDING);
+
+ if (icon->drawingWin && icon->docked) {
+ if (icon->flags & ICON_FLAG_ARGB32) {
+ /* ARGB32 redraw: never use a ParentRelative method, and
+ no need to clear window except FIXME when its size changed.
+ Draw on the offscreen pixmap instead, then copy to the window.
+ */
+ if (icon->offscreenPixmap == None) {
+ icon->offscreenPixmap = Tk_GetPixmap(Tk_Display(icon->drawingWin),
+ Tk_WindowId(icon->drawingWin),
+ w, h, 32);
+ }
+ if (!icon->photo) {
+ icon->photo = Tk_FindPhoto(icon->interp,
+ icon->imageString);
+ }
+ if (!icon->photo && !icon->imageVisualInstance) {
+ Tcl_SavedResult saved;
+ Tcl_SaveResult(icon->interp,&saved);
+ icon->imageVisualInstance = Tk_GetImage(icon->interp,icon->drawingWin,icon->imageString,
+ IgnoreImageChange,(ClientData)NULL);
+ Tcl_RestoreResult(icon->interp,&saved);
+ }
+ if (icon->photo && !icon->offscreenImage) {
+ icon->offscreenImage = XGetImage(Tk_Display(icon->drawingWin),
+ icon->offscreenPixmap,
+ 0, 0, w, h, AllPlanes, ZPixmap);
+ }
+ if (icon->offscreenGC == None) {
+ XGCValues gcv;
+ gcv.function = GXcopy;
+ gcv.plane_mask = AllPlanes;
+ gcv.foreground = 0;
+ gcv.background = 0;
+ icon->offscreenGC = Tk_GetGC(icon->drawingWin,
+ GCFunction|GCPlaneMask|
+ GCForeground|GCBackground,
+ &gcv);
+ }
+ if (icon->flags & ICON_FLAG_DIRTY_EDGES) {
+ XClearWindow(Tk_Display(icon->drawingWin),
+ TKU_XID(icon->drawingWin));
+ icon->flags &= ~ICON_FLAG_DIRTY_EDGES;
+ }
+ if (icon->photo) {
+ Tk_PhotoImageBlock pib;
+ int cx,cy;
+ XImage *xim = icon->offscreenImage;
+ /* redraw photo using raw data */
+ Tk_PhotoGetImage(icon->photo,&pib);
+ for (cy=0;cy<h;++cy) {
+ for (cx=0;cx<w;++cx) {
+ XPutPixel(xim,cx,cy,
+ (*(pib.pixelPtr +
+ pib.pixelSize*cx +
+ pib.pitch*cy +
+ pib.offset[0])<<16) |
+ (*(pib.pixelPtr +
+ pib.pixelSize*cx +
+ pib.pitch*cy +
+ pib.offset[1])<<8) |
+ (*(pib.pixelPtr +
+ pib.pixelSize*cx +
+ pib.pitch*cy +
+ pib.offset[2])) |
+ (pib.offset[3] ?
+ (*(pib.pixelPtr +
+ pib.pixelSize*cx +
+ pib.pitch*cy +
+ pib.offset[3])<<24) : 0));
+ }
+ }
+ XPutImage(Tk_Display(icon->drawingWin),
+ icon->offscreenPixmap,
+ icon->offscreenGC,
+ icon->offscreenImage,
+ 0,0,0,0,w,h);
+ } else {
+ XFillRectangle(Tk_Display(icon->drawingWin),
+ icon->offscreenPixmap,
+ icon->offscreenGC,
+ 0,0,w,h);
+ if (icon->imageVisualInstance) {
+ Tk_RedrawImage(icon->imageVisualInstance,
+ 0,0,w,h,
+ icon->offscreenPixmap,
+ 0,0);
+ }
+ }
+ XCopyArea(Tk_Display(icon->drawingWin),
+ icon->offscreenPixmap,
+ TKU_XID(icon->drawingWin),
+ icon->offscreenGC,
+ imgx,imgy,outw,outh,outx,outy);
+ } else {
+ /* Non-argb redraw: clear window and draw an image over it.
+ For photos it gives a correct alpha blending with a parent
+ window background, even if it's a fancy pixmap (proved to
+ work with lxpanel fancy backgrounds).
+ */
+ XClearWindow(Tk_Display(icon->drawingWin),
+ TKU_XID(icon->drawingWin));
+ if (icon->image && icon->visible) {
+ Tk_RedrawImage(icon->image,imgx,imgy,outw,outh,
+ TKU_XID(icon->drawingWin),
+ outx, outy);
+ }
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RetargetEvent --
+ *
+ * Redirect X events to widgets.
+ *
+ * Results:
+ * Icon image is displayed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void RetargetEvent(DockIcon *icon, XEvent *ev)
+{
+ int send = 0;
+ Window* saveWin1 = NULL, *saveWin2 = NULL;
+ if (!icon->visible)
+ return;
+ switch (ev->type) {
+ case MotionNotify:
+ send = 1;
+ saveWin1 = &ev->xmotion.subwindow;
+ saveWin2 = &ev->xmotion.window;
+ break;
+ case LeaveNotify:
+ case EnterNotify:
+ send = 1;
+ saveWin1 = &ev->xcrossing.subwindow;
+ saveWin2 = &ev->xcrossing.window;
+ break;
+ case ButtonPress:
+ case ButtonRelease:
+ send = 1;
+ saveWin1 = &ev->xbutton.subwindow;
+ saveWin2 = &ev->xbutton.window;
+ break;
+ case MappingNotify:
+ send = 1;
+ saveWin1 = &ev->xmapping.window;
+ }
+ if (saveWin1) {
+ Tk_MakeWindowExist(icon->tkwin);
+ *saveWin1 = Tk_WindowId(icon->tkwin);
+ if (saveWin2) *saveWin2 = *saveWin1;
+ }
+ if (send) {
+ ev->xany.send_event = 0x147321ac;
+ Tk_HandleEvent(ev);
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TrayIconWrapperEvent --
+ *
+ * Ensure automapping in root window is done in withdrawn state.
+ *
+ * Results:
+ * Icon image is displayed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+
+static void TrayIconWrapperEvent(ClientData cd, XEvent* ev)
+{
+
+ /* Some embedders, like Docker, add icon windows to save set
+ (XAddToSaveSet), so when they crash the icon is reparented to root.
+ We have to make sure that automatic mapping in root is done in
+ withdrawn state (no way to prevent it entirely)
+ */
+ DockIcon *icon = (DockIcon*)cd;
+ XWindowAttributes attr;
+ if (icon->drawingWin) {
+ switch(ev->type) {
+ case ReparentNotify:
+ /* With virtual roots and screen roots etc, the only way
+ to check for reparent-to-root is to ask for this root
+ first */
+ XGetWindowAttributes(ev->xreparent.display,
+ ev->xreparent.window,
+ &attr);
+ if (attr.root == ev->xreparent.parent) {
+ /* upon reparent to root, */
+ if (icon->drawingWin) {
+ /* we were sent away to root */
+ TKU_WmWithdraw(icon->drawingWin,icon->interp);
+ if (icon->myManager)
+ TKU_VirtualEvent(icon->tkwin,Tk_GetUid("IconDestroy"));
+ icon->myManager = None;
+
+ }
+ } /* Reparenting into some other embedder is theoretically possible,
+ and everything would just work in this case */
+ break;
+ }
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TrayIconEvent --
+ *
+ * Handle X events.
+ *
+ * Results:
+ * Events are handled and processed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void TrayIconEvent(ClientData cd, XEvent* ev)
+{
+ DockIcon *icon = (DockIcon*)cd;
+
+ switch (ev->type) {
+ case Expose:
+ if (!ev->xexpose.count)
+ EventuallyRedrawIcon(icon);
+ break;
+
+ case DestroyNotify:
+ /* If anonymous window is destroyed first, then either
+ something went wrong with a tray (if -visible) or we just
+ reconfigured to invisibility: nothing to be done in both
+ cases.
+ If unreal window is destroyed first, freeing the data structures
+ is the only thing to do.
+ */
+ if (icon->myManager) {
+ TKU_VirtualEvent(icon->tkwin,Tk_GetUid("IconDestroy"));
+ }
+ Tcl_CancelIdleCall(DisplayIcon,(ClientData)icon);
+ icon->flags &= ~ICON_FLAG_REDRAW_PENDING;
+ icon->drawingWin = NULL;
+ icon->requestedWidth = 0; /* trigger re-request on recreation */
+ icon->requestedHeight = 0;
+ icon->wrapper = None;
+ icon->myManager = None;
+
+ break;
+
+ case ConfigureNotify:
+ TKU_VirtualEvent(icon->tkwin,Tk_GetUid("IconConfigure"));
+ if (icon->width != ev->xconfigure.width ||
+ icon->height != ev->xconfigure.height) {
+ icon->width = ev->xconfigure.width;
+ icon->height = ev->xconfigure.height;
+ icon->flags |= ICON_FLAG_DIRTY_EDGES;
+ EventuallyRedrawIcon(icon);
+ }
+ RetargetEvent(icon,ev);
+ break;
+ case MotionNotify:
+ case ButtonPress: /* fall through */
+ case ButtonRelease:
+ case EnterNotify:
+ case LeaveNotify:
+ RetargetEvent(icon,ev);
+ break;
+
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UserIconEvent --
+ *
+ * Handle user events.
+ *
+ * Results:
+ * Events are handled and processed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void UserIconEvent(ClientData cd, XEvent* ev)
+{
+ DockIcon *icon = (DockIcon*)cd;
+
+ switch (ev->type) {
+
+ case DestroyNotify:
+ Tk_DeleteGenericHandler(IconGenericHandler, (ClientData)icon);
+ if(icon->drawingWin) {
+ icon->visible = 0;
+ Tcl_CancelIdleCall(DisplayIcon,(ClientData)icon);
+ icon->flags &= ~ICON_FLAG_REDRAW_PENDING;
+ Tk_DestroyWindow(icon->drawingWin);
+ }
+ if(icon->imageVisualInstance) {
+ Tk_FreeImage(icon->imageVisualInstance);
+ icon->image = NULL;
+ }
+ if(icon->offscreenImage) {
+ XDestroyImage(icon->offscreenImage);
+ icon->offscreenImage = NULL;
+ }
+ if(icon->offscreenGC) {
+ Tk_FreeGC(Tk_Display(icon->tkwin),icon->offscreenGC);
+ icon->offscreenGC = NULL;
+ }
+ if(icon->offscreenPixmap) {
+ Tk_FreePixmap(Tk_Display(icon->tkwin),icon->offscreenPixmap);
+ }
+ if(icon->image) {
+ Tk_FreeImage(icon->image);
+ icon->image = NULL;
+ }
+ if(icon->widgetCmd)
+ Tcl_DeleteCommandFromToken(icon->interp,icon->widgetCmd);
+ Tk_FreeConfigOptions((char*)icon, icon->options, icon->tkwin);
+ break;
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * PostBalloon --
+ *
+ * Display tooltip/balloon window over tray icon.
+ *
+ * Results:
+ * Window is displayed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static int PostBalloon(DockIcon* icon, const char * utf8msg,
+ long timeout)
+{
+ Tk_Window tkwin = icon -> tkwin;
+ Display* dpy = Tk_Display(tkwin);
+ int length = strlen(utf8msg);
+ XEvent ev;
+
+ if (!(icon->drawingWin) || (icon->myManager == None))
+ return 0;
+ /* overflow protection */
+ if (icon->msgid < 0)
+ icon->msgid = 0;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.xclient.type = ClientMessage;
+ ev.xclient.window = icon->wrapper;
+
+ ev.xclient.message_type =
+ icon->a_NET_SYSTEM_TRAY_OPCODE;
+ ev.xclient.format = 32;
+ ev.xclient.data.l[0]=CurrentTime;
+ ev.xclient.data.l[1]=SYSTEM_TRAY_BEGIN_MESSAGE;
+ ev.xclient.data.l[2]=timeout;
+ ev.xclient.data.l[3]=length;
+ ev.xclient.data.l[4]=++icon->msgid;
+ TKU_NO_BAD_WINDOW_BEGIN(Tk_Display(icon->tkwin))
+ XSendEvent(dpy, icon->myManager , True, StructureNotifyMask|SubstructureNotifyMask, &ev);
+ XSync(dpy, False);
+
+ /* Sending message elements */
+ while (length>0) {
+ ev.type = ClientMessage;
+ ev.xclient.window = icon->wrapper;
+ ev.xclient.message_type = icon->a_NET_SYSTEM_TRAY_MESSAGE_DATA;
+ ev.xclient.format = 8;
+
+ memset(ev.xclient.data.b,0,20);
+ strncpy(ev.xclient.data.b,utf8msg,length<20?length:20);
+ XSendEvent(dpy, icon->myManager, True, StructureNotifyMask|SubstructureNotifyMask, &ev);
+ XSync(dpy,False);
+ utf8msg+=20;
+ length-=20;
+ }
+ TKU_NO_BAD_WINDOW_END;
+ return icon->msgid;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CancelBalloon --
+ *
+ * Remove balloon from display over tray icon.
+ *
+ * Results:
+ * Window is destroyed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void CancelBalloon(DockIcon* icon, int msgid)
+{
+ Tk_Window tkwin = icon -> tkwin;
+ Display* dpy = Tk_Display(tkwin);
+ XEvent ev;
+
+ if (!(icon->drawingWin) || (icon->myManager == None))
+ return;
+ /* overflow protection */
+ if (icon->msgid < 0)
+ icon->msgid = 0;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = ClientMessage;
+
+ ev.xclient.window = icon->wrapper;
+
+ ev.xclient.message_type =
+ icon->a_NET_SYSTEM_TRAY_OPCODE;
+ ev.xclient.format = 32;
+ ev.xclient.data.l[0]=CurrentTime;
+ ev.xclient.data.l[1]=SYSTEM_TRAY_CANCEL_MESSAGE;
+ ev.xclient.data.l[2]=msgid;
+ TKU_NO_BAD_WINDOW_BEGIN(Tk_Display(icon->tkwin))
+ XSendEvent(dpy, icon->myManager , True,
+ StructureNotifyMask|SubstructureNotifyMask, &ev);
+ TKU_NO_BAD_WINDOW_END
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * IconGenericHandler --
+ *
+ * Process non-tk events.
+ *
+ * Results:
+ * Events are processed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static int IconGenericHandler(ClientData cd, XEvent *ev)
+{
+ DockIcon *icon = (DockIcon*)cd;
+
+ if ((ev->type == ClientMessage) &&
+ (ev->xclient.message_type == icon->aMANAGER) &&
+ ((Atom)ev->xclient.data.l[1] == icon->a_NET_SYSTEM_TRAY_Sn)) {
+ icon->trayManager = (Window)ev->xclient.data.l[2];
+ XSelectInput(ev->xclient.display,icon->trayManager,StructureNotifyMask);
+ if (icon->myManager == None)
+ TrayIconUpdate(icon, ICON_CONF_XEMBED);
+ return 1;
+ }
+ if (ev->type == DestroyNotify) {
+ if (ev->xdestroywindow.window == icon->trayManager) {
+ icon->trayManager = None;
+ }
+ if (ev->xdestroywindow.window == icon->myManager) {
+ icon->myManager = None;
+ icon->wrapper = None;
+ if (icon->drawingWin) {
+ Tk_DestroyWindow(icon->drawingWin);
+ icon->drawingWin = NULL;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TrayIconUpdate --
+ *
+ * Get in touch with new options that are certainly valid.
+ *
+ * Results:
+ * Options updated.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static void TrayIconUpdate(DockIcon* icon, int mask)
+{
+ /* why should someone need this option?
+ anyway, let's handle it if we provide it */
+ if (mask & ICON_CONF_CLASS) {
+ if (icon->drawingWin)
+ Tk_SetClass(icon->drawingWin,Tk_GetUid(icon->classString));
+ }
+ /*
+ First, ensure right icon visibility.
+ If should be visible and not yet managed,
+ we have to get the tray or wait for it.
+ If should be invisible and managed,
+ real-window is simply destroyed.
+ If should be invisible and not managed,
+ generic handler should be abandoned.
+ */
+ if (mask & ICON_CONF_XEMBED) {
+ if (icon->myManager == None &&
+ icon->trayManager != None &&
+ icon->docked) {
+ CheckArgbVisual(icon);
+ if (icon->drawingWin &&
+ ((icon->bestVisual && !(icon->flags & ICON_FLAG_ARGB32)) ||
+ (!icon->bestVisual && (icon->flags & ICON_FLAG_ARGB32)))) {
+ icon->myManager = None;
+ icon->wrapper = None;
+ icon->requestedWidth = icon->requestedHeight = 0;
+ Tk_DestroyWindow(icon->drawingWin);
+ icon->drawingWin = NULL;
+ }
+ if (!icon->drawingWin) {
+ CreateTrayIconWindow(icon);
+ }
+ if (icon->drawingWin) {
+ DockToManager(icon);
+ }
+ }
+ if (icon->myManager != None &&
+ icon->drawingWin != NULL &&
+ !icon->docked) {
+ Tk_DestroyWindow(icon->drawingWin);
+ icon->drawingWin = NULL;
+ icon->myManager = None;
+ icon->wrapper = None;
+ }
+ if (icon->drawingWin) {
+ XembedSetState(icon, icon->visible ? XEMBED_MAPPED : 0);
+ }
+ }
+ if (mask & ICON_CONF_IMAGE) {
+ TrayIconForceImageChange(icon);
+ }
+ if (mask & ICON_CONF_REDISPLAY) {
+ EventuallyRedrawIcon(icon);
+ }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TrayIconConfigureMethod --
+ *
+ * Returns TCL_ERROR if some option is invalid,
+ * or else retrieve resource references and free old resources.
+ *
+ * Results:
+ * Widget configured.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+
+static int TrayIconConfigureMethod(DockIcon *icon, Tcl_Interp* interp,
+ int objc, Tcl_Obj *const objv[],
+ int addflags)
+{
+ Tk_SavedOptions saved;
+ Tk_Image newImage = NULL;
+ int mask = 0;
+
+ if (objc<=1 && !(addflags & ICON_CONF_FIRST_TIME)) {
+ Tcl_Obj* info = Tk_GetOptionInfo(interp,(char*)icon,icon->options,
+ objc? objv[0]: NULL, icon->tkwin);
+ if (info) {
+ Tcl_SetObjResult(interp,info);
+ return TCL_OK;
+ } else {
+ return TCL_ERROR; /* msg by Tk_GetOptionInfo */
+ }
+ }
+
+ if (Tk_SetOptions(interp,(char*)icon,icon->options,objc,objv,
+ icon->tkwin,&saved,&mask)!=TCL_OK) {
+ return TCL_ERROR; /* msg by Tk_SetOptions */
+ }
+ mask |= addflags;
+ /* now check option validity */
+ if (mask & ICON_CONF_IMAGE) {
+ if (icon->imageString) {
+ newImage = Tk_GetImage(interp, icon->tkwin, icon->imageString,
+ TrayIconImageChanged, (ClientData)icon);
+ if (!newImage) {
+ Tk_RestoreSavedOptions(&saved);
+ return TCL_ERROR; /* msg by Tk_GetImage */
+ }
+ }
+ if (icon->image) {
+ Tk_FreeImage(icon->image);
+ icon->image = NULL;
+ }
+ if (icon->imageVisualInstance) {
+ Tk_FreeImage(icon->imageVisualInstance);
+ icon->imageVisualInstance = NULL;
+ }
+ icon->image = newImage; /* may be null, as intended */
+ icon->photo = NULL; /* invalidate photo reference */
+ }
+ Tk_FreeSavedOptions(&saved);
+ /* Now as we are reconfigured... */
+ TrayIconUpdate(icon,mask);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TrayIconDeleteProc --
+ *
+ * Delete tray window and clean up.
+ *
+ * Results:
+ * Window destroyed.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+
+static void TrayIconDeleteProc( ClientData cd )
+{
+ DockIcon *icon = (DockIcon*) cd;
+ Tk_DestroyWindow(icon->tkwin);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TrayIconCreateCmd --
+ *
+ * Create tray command and (unreal) window.
+ *
+ * Results:
+ * Icon tray and hidden window created.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+
+static int TrayIconCreateCmd(ClientData cd, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ Tk_Window mainWindow = cd;
+ DockIcon *icon;
+
+ icon = (DockIcon*)attemptckalloc(sizeof(DockIcon));
+ if (!icon) {
+ Tcl_SetResult(interp, "running out of memory", TCL_STATIC);
+ goto handleErrors;
+ }
+ memset(icon,0,sizeof(*icon));
+
+ if (objc < 2||(objc%2)) {
+ Tcl_WrongNumArgs(interp, 1, objv, "pathName ?option value ...?");
+ goto handleErrors;
+ }
+
+ /* It's not a toplevel window by now. It really doesn't matter,
+ because it's not really shown */
+ icon->tkwin =
+ Tk_CreateWindowFromPath(interp,
+ mainWindow,
+ Tcl_GetString(objv[1]),"");
+ if (icon->tkwin == NULL) {
+ goto handleErrors;
+ }
+
+ /* Subscribe to StructureNotify */
+ TKU_AddInput(Tk_Display(icon->tkwin),
+ RootWindowOfScreen(Tk_Screen(icon->tkwin)),StructureNotifyMask);
+ TKU_AddInput(Tk_Display(icon->tkwin),
+ RootWindow(Tk_Display(icon->tkwin),0),StructureNotifyMask);
+ /* Spec says "screen 0" not "default", but... */
+ TKU_AddInput(Tk_Display(icon->tkwin),
+ DefaultRootWindow(Tk_Display(icon->tkwin)),StructureNotifyMask);
+
+ /* Early tracking of DestroyNotify is essential */
+ Tk_CreateEventHandler(icon->tkwin,StructureNotifyMask,
+ UserIconEvent,(ClientData)icon);
+
+ /* Now try setting options */
+ icon->options = Tk_CreateOptionTable(interp,IconOptionSpec);
+ /* Class name is used for retrieving defaults, so... */
+ Tk_SetClass(icon->tkwin, Tk_GetUid("TrayIcon"));
+ if (Tk_InitOptions(interp,(char*)icon,icon->options,icon->tkwin)!=TCL_OK) {
+ goto handleErrors;
+ }
+
+ icon->a_NET_SYSTEM_TRAY_Sn = DockSelectionAtomFor(icon->tkwin);
+ icon->a_NET_SYSTEM_TRAY_OPCODE = Tk_InternAtom(icon->tkwin,"_NET_SYSTEM_TRAY_OPCODE");
+ icon->a_NET_SYSTEM_TRAY_MESSAGE_DATA = Tk_InternAtom(icon->tkwin,"_NET_SYSTEM_TRAY_MESSAGE_DATA");
+ icon->a_NET_SYSTEM_TRAY_ORIENTATION = Tk_InternAtom(icon->tkwin,"_NET_SYSTEM_TRAY_ORIENTATION");
+ icon->a_NET_SYSTEM_TRAY_VISUAL = Tk_InternAtom(icon->tkwin,"_NET_SYSTEM_TRAY_VISUAL");
+ icon->a_XEMBED_INFO = Tk_InternAtom(icon->tkwin,"_XEMBED_INFO");
+ icon->aMANAGER = Tk_InternAtom(icon->tkwin,"MANAGER");
+
+ icon->interp = interp;
+
+ icon->trayManager = XGetSelectionOwner(Tk_Display(icon->tkwin),icon->a_NET_SYSTEM_TRAY_Sn);
+ if (icon->trayManager) {
+ XSelectInput(Tk_Display(icon->tkwin),icon->trayManager, StructureNotifyMask);
+ }
+
+ Tk_CreateGenericHandler(IconGenericHandler, (ClientData)icon);
+
+ if (objc>3) {
+ if (TrayIconConfigureMethod(icon, interp, objc-2, objv+2,
+ ICON_CONF_XEMBED|ICON_CONF_IMAGE|ICON_CONF_FIRST_TIME)!=TCL_OK) {
+ goto handleErrors;
+ }
+ }
+
+ icon->widgetCmd =
+ Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]),
+ TrayIconObjectCmd, (ClientData)icon, TrayIconDeleteProc);
+
+
+ /* Sometimes a command just can't be created... */
+ if (!icon->widgetCmd) {
+ goto handleErrors;
+ }
+
+ Tcl_SetObjResult(interp,objv[1]);
+ return TCL_OK;
+
+handleErrors:
+ /* Rolling back */
+ if (icon) {
+ if (icon->options) {
+ Tk_DeleteOptionTable(icon->options);
+ icon->options = NULL;
+ }
+ if (icon->tkwin) {
+ /* Resources will be freed by DestroyNotify handler */
+ Tk_DestroyWindow(icon->tkwin);
+ }
+ ckfree((char*)icon);
+ }
+ return TCL_ERROR;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tktray_Init --
+ *
+ * Initialize the command.
+ *
+ * Results:
+ * Command initialized.
+ *
+ * Side effects:
+ * None.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+int Tktray_Init ( Tcl_Interp* interp )
+{
+
+ Tcl_CreateObjCommand(interp, "_systray",
+ TrayIconCreateCmd, Tk_MainWindow(interp), NULL );
+
+ return TCL_OK;
+}