diff options
author | Kevin Walzer <kw@codebykevin.com> | 2020-09-22 02:14:12 (GMT) |
---|---|---|
committer | Kevin Walzer <kw@codebykevin.com> | 2020-09-22 02:14:12 (GMT) |
commit | 02ea7c43872165be2d68131e4cbaabe87268842d (patch) | |
tree | f021472d0ade12afab77a171cd8f719f975cabf7 /unix | |
parent | 2dd96a74a429ac0d9dbf568028736f7e49d1b2b6 (diff) | |
download | tk-02ea7c43872165be2d68131e4cbaabe87268842d.zip tk-02ea7c43872165be2d68131e4cbaabe87268842d.tar.gz tk-02ea7c43872165be2d68131e4cbaabe87268842d.tar.bz2 |
Eliminate most warnings
Diffstat (limited to 'unix')
-rw-r--r-- | unix/Makefile.in | 9 | ||||
-rw-r--r-- | unix/tkUnixInt.h | 1 | ||||
-rw-r--r-- | unix/tkUnixSysTray.c | 3361 |
3 files changed, 1701 insertions, 1670 deletions
diff --git a/unix/Makefile.in b/unix/Makefile.in index edf0587..c4d0241 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -392,7 +392,7 @@ X11_OBJS = tkUnix.o tkUnix3d.o tkUnixButton.o tkUnixColor.o tkUnixConfig.o \ tkUnixCursor.o tkUnixDraw.o tkUnixEmbed.o tkUnixEvent.o \ tkUnixFocus.o $(FONT_OBJS) tkUnixInit.o tkUnixKey.o tkUnixMenu.o \ tkUnixMenubu.o tkUnixScale.o tkUnixScrlbr.o tkUnixSelect.o \ - tkUnixSend.o tkUnixWm.o tkUnixXId.o + tkUnixSend.o tkUnixSysTray.o tkUnixWm.o tkUnixXId.o AQUA_OBJS = tkMacOSXBitmap.o tkMacOSXButton.o tkMacOSXClipboard.o \ tkMacOSXColor.o tkMacOSXConfig.o tkMacOSXCursor.o tkMacOSXDebug.o \ @@ -514,8 +514,8 @@ X11_SRCS = \ $(UNIX_DIR)/tkUnixMenu.c $(UNIX_DIR)/tkUnixMenubu.c \ $(UNIX_DIR)/tkUnixScale.c $(UNIX_DIR)/tkUnixScrlbr.c \ $(UNIX_DIR)/tkUnixSelect.c \ - $(UNIX_DIR)/tkUnixSend.c $(UNIX_DIR)/tkUnixWm.c \ - $(UNIX_DIR)/tkUnixXId.c + $(UNIX_DIR)/tkUnixSend.c $(UNIX_DIR)/tkUnixSysTray.c \ + $(UNIX_DIR)/tkUnixWm.c $(UNIX_DIR)/tkUnixXId.c AQUA_SRCS = \ $(MAC_OSX_DIR)/tkMacOSXBitmap.c $(MAC_OSX_DIR)/tkMacOSXButton.c \ @@ -1260,6 +1260,9 @@ tkUnixSelect.o: $(UNIX_DIR)/tkUnixSelect.c tkUnixSend.o: $(UNIX_DIR)/tkUnixSend.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tkUnixSend.c +tkUnixSysTray.o: $(UNIX_DIR)/tkUnixSysTray.c + $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tkUnixSysTray.c + tkUnixWm.o: $(UNIX_DIR)/tkUnixWm.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tkUnixWm.c diff --git a/unix/tkUnixInt.h b/unix/tkUnixInt.h index 6916f27..1064d45 100644 --- a/unix/tkUnixInt.h +++ b/unix/tkUnixInt.h @@ -29,7 +29,6 @@ MODULE_SCOPE int Tktray_Init (Tcl_Interp* interp); #endif /* _TKUNIXINT */ -MODULE_SCOPE int Tktray_Init (Tcl_Interp* interp); /* * Local Variables: 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; +} |