summaryrefslogtreecommitdiffstats
path: root/win/tkWinSysTray.c
diff options
context:
space:
mode:
Diffstat (limited to 'win/tkWinSysTray.c')
-rw-r--r--win/tkWinSysTray.c1177
1 files changed, 1177 insertions, 0 deletions
diff --git a/win/tkWinSysTray.c b/win/tkWinSysTray.c
new file mode 100644
index 0000000..a710ee6
--- /dev/null
+++ b/win/tkWinSysTray.c
@@ -0,0 +1,1177 @@
+/*
+ * tkWinSysTray.c --
+ *
+ * tkWinSysTray.c implements a "systray" Tcl command which permits to
+ * change the system tray/taskbar icon of a Tk toplevel window and
+ * a "sysnotify" command to post system notifications.
+ *
+ * Copyright © 1995-1996 Microsoft Corp.
+ * Copyright © 1998 Brueckner & Jarosch Ing.GmbH, Erfurt, Germany
+ * Copyright © 2020 Kevin Walzer/WordTech Communications LLC.
+ * Copyright © 2020 Eric Boudaillier.
+ * Copyright © 2020 Francois Vogel.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tkInt.h"
+#include <windows.h>
+#include <shellapi.h>
+#include "tkWin.h"
+#include "tkWinInt.h"
+#include "tkWinIco.h"
+
+/*
+ * Based extensively on the winico extension and sample code from Microsoft.
+ * Some of the code was adapted into tkWinWM.c to implement the "wm iconphoto"
+ * command (TIP 159), and here we are borrowing that code to use Tk images
+ * to create system tray icons instead of ico files. Additionally, we are
+ * removing obsolete parts of the winico extension, and implementing
+ * more of the Shell_Notification API to add balloon/system notifications.
+ */
+
+#define GETHINSTANCE Tk_GetHINSTANCE()
+
+typedef struct IcoInfo {
+ HICON hIcon; /* icon handle returned by LoadIcon. */
+ unsigned id; /* Identifier for command; used to
+ * cancel it. */
+ Tcl_Obj *taskbar_txt; /* text to display in the taskbar */
+ Tcl_Interp *interp; /* interp which created the icon */
+ Tcl_Obj *taskbar_command; /* command to eval if events in the taskbar
+ * arrive */
+ int taskbar_flags; /* taskbar related flags*/
+ HWND hwndFocus;
+ struct IcoInfo *nextPtr;
+} IcoInfo;
+
+/* Per-interp struture */
+typedef struct IcoInterpInfo {
+ HWND hwnd; /* Handler window */
+ int counter; /* Counter for IcoInfo id generation */
+ IcoInfo *firstIcoPtr; /* List of created IcoInfo */
+ struct IcoInterpInfo *nextPtr;
+} IcoInterpInfo;
+
+#define TASKBAR_ICON 1
+#define ICON_MESSAGE WM_USER + 1234
+
+#define HANDLER_CLASS "Wtk_TaskbarHandler"
+static HWND CreateTaskbarHandlerWindow(void);
+
+static IcoInterpInfo *firstIcoInterpPtr = NULL;
+static Tk_EventProc WinIcoDestroy;
+
+/*
+ * If someone wants to see the several masks somewhere on the screen...
+ * set the ICO_DRAW define and feel free to make some Tcl commands
+ * for accessing it. The normal drawing of an Icon to a DC is really easy:
+ * DrawIcon(hdc,x,y,hIcon) or , more complicated
+ * DrawIconEx32PlusMoreParameters ...
+ */
+
+/* #define ICO_DRAW */
+#ifdef ICO_DRAW
+#define RectWidth(r)((r).right - (r).left + 1)
+#define RectHeight(r)((r).bottom - (r).top + 1)
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DrawXORMask --
+ *
+ * Using DIB functions, draw XOR mask on hDC in Rect.
+ *
+ * Results:
+ * Icon is rendered.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static BOOL
+DrawXORMask(
+ HDC hDC,
+ RECT Rect,
+ LPLPICONIMAGE lpIcon)
+{
+ int x, y;
+
+ /* Sanity checks */
+ if (lpIcon == NULL)
+ return FALSE;
+ if (lpIcon->lpBits == NULL)
+ return FALSE;
+
+ /* Account for height*2 thing */
+ lpIcon->lpbi->bmiHeader.biHeight /= 2;
+
+ /* Locate it */
+ x = Rect.left + ((RectWidth(Rect) - lpIcon->lpbi->bmiHeader.biWidth) / 2);
+ y = Rect.top + ((RectHeight(Rect) - lpIcon->lpbi->bmiHeader.biHeight) / 2);
+
+ /* Blast it to the screen */
+ SetDIBitsToDevice(hDC, x, y,
+ lpIcon->lpbi->bmiHeader.biWidth,
+ lpIcon->lpbi->bmiHeader.biHeight,
+ 0, 0, 0, lpIcon->lpbi->bmiHeader.biHeight,
+ lpIcon->lpXOR, lpIcon->lpbi, DIB_RGB_COLORS);
+
+ /* UnAccount for height*2 thing */
+ lpIcon->lpbi->bmiHeader.biHeight *= 2;
+
+ return TRUE;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DrawANDMask --
+ *
+ * Using DIB functions, draw AND mask on hDC in Rect.
+ *
+ * Results:
+ * Icon is rendered.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+BOOL
+DrawANDMask(
+ HDC hDC,
+ RECT Rect,
+ LPLPICONIMAGE lpIcon)
+{
+ LPBITMAPINFO lpbi;
+ int x, y;
+
+ /* Sanity checks */
+ if (lpIcon == NULL)
+ return FALSE;
+ if (lpIcon->lpBits == NULL)
+ return FALSE;
+
+ /* Need a bitmap header for the mono mask */
+ lpbi = ckalloc(sizeof(BITMAPINFO) + (2 * sizeof(RGBQUAD)));
+ lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ lpbi->bmiHeader.biWidth = lpIcon->lpbi->bmiHeader.biWidth;
+ lpbi->bmiHeader.biHeight = lpIcon->lpbi->bmiHeader.biHeight / 2;
+ lpbi->bmiHeader.biPlanes = 1;
+ lpbi->bmiHeader.biBitCount = 1;
+ lpbi->bmiHeader.biCompression = BI_RGB;
+ lpbi->miHeader.biSizeImage = 0;
+ lpbi->bmiHeader.biXPelsPerMeter = 0;
+ lpbi->bmiHeader.biYPelsPerMeter = 0;
+ lpbi->bmiHeader.biClrUsed = 0;
+ lpbi->bmiHeader.biClrImportant = 0;
+ lpbi->bmiColors[0].rgbRed = 0;
+ lpbi->bmiColors[0].rgbGreen = 0;
+ lpbi->bmiColors[0].rgbBlue = 0;
+ lpbi->bmiColors[0].rgbReserved = 0;
+ lpbi->bmiColors[1].rgbRed = 255;
+ lpbi->bmiColors[1].rgbGreen = 255;
+ lpbi->bmiColors[1].rgbBlue = 255;
+ lpbi->bmiColors[1].rgbReserved = 0;
+
+ /* Locate it */
+ x = Rect.left + ((RectWidth(Rect) - lpbi->bmiHeader.biWidth) / 2);
+ y = Rect.top + ((RectHeight(Rect) - lpbi->bmiHeader.biHeight) / 2);
+
+ /* Blast it to the screen */
+ SetDIBitsToDevice(hDC, x, y,
+ lpbi->bmiHeader.biWidth,
+ lpbi->bmiHeader.biHeight,
+ 0, 0, 0, lpbi->bmiHeader.biHeight,
+ lpIcon->lpAND, lpbi, DIB_RGB_COLORS);
+
+ /* clean up */
+ ckfree(lpbi);
+
+ return TRUE;
+}
+#endif /* ICO_DRAW */
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TaskbarOperation --
+ *
+ * Management of icon display.
+ *
+ * Results:
+ * Icon is displayed or deleted.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TaskbarOperation(
+ IcoInterpInfo *icoInterpPtr,
+ IcoInfo *icoPtr,
+ int oper)
+{
+ NOTIFYICONDATAW ni;
+ WCHAR *str;
+
+ ni.cbSize = sizeof(NOTIFYICONDATAW);
+ ni.hWnd = icoInterpPtr->hwnd;
+ ni.uID = icoPtr->id;
+ ni.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
+ ni.uCallbackMessage = ICON_MESSAGE;
+ ni.hIcon = icoPtr->hIcon;
+
+ if (icoPtr->taskbar_txt != NULL) {
+ Tcl_DString dst;
+ Tcl_DStringInit(&dst);
+ str = (WCHAR *)Tcl_UtfToWCharDString(Tcl_GetString(icoPtr->taskbar_txt), TCL_INDEX_NONE, &dst);
+ wcsncpy(ni.szTip, str, (Tcl_DStringLength(&dst) + 2) / 2);
+ Tcl_DStringFree(&dst);
+ } else {
+ ni.szTip[0] = 0;
+ }
+
+ if (Shell_NotifyIconW(oper, &ni) == 1) {
+ if (oper == NIM_ADD || oper == NIM_MODIFY) {
+ icoPtr->taskbar_flags |= TASKBAR_ICON;
+ }
+ if (oper == NIM_DELETE) {
+ icoPtr->taskbar_flags &= ~TASKBAR_ICON;
+ }
+ }
+ /* Silently ignore error? */
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * NewIcon --
+ *
+ * Create icon for display in system tray.
+ *
+ * Results:
+ * Icon is created for display.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static IcoInfo *
+NewIcon(
+ Tcl_Interp *interp,
+ IcoInterpInfo *icoInterpPtr,
+ HICON hIcon)
+{
+ IcoInfo *icoPtr;
+
+ icoPtr = (IcoInfo *)ckalloc(sizeof(IcoInfo));
+ memset(icoPtr, 0, sizeof(IcoInfo));
+ icoPtr->id = ++icoInterpPtr->counter;
+ icoPtr->hIcon = hIcon;
+ icoPtr->taskbar_txt = NULL;
+ icoPtr->interp = interp;
+ icoPtr->taskbar_command = NULL;
+ icoPtr->taskbar_flags = 0;
+ icoPtr->hwndFocus = NULL;
+ icoPtr->nextPtr = icoInterpPtr->firstIcoPtr;
+ icoInterpPtr->firstIcoPtr = icoPtr;
+ return icoPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeIcoPtr --
+ *
+ * Delete icon and free memory.
+ *
+ * Results:
+ * Icon is removed from display.
+ *
+ * Side effects:
+ * Memory/resources freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeIcoPtr(
+ IcoInterpInfo *icoInterpPtr,
+ IcoInfo *icoPtr)
+{
+ IcoInfo *prevPtr;
+ if (icoInterpPtr->firstIcoPtr == icoPtr) {
+ icoInterpPtr->firstIcoPtr = icoPtr->nextPtr;
+ } else {
+ for (prevPtr = icoInterpPtr->firstIcoPtr; prevPtr->nextPtr != icoPtr;
+ prevPtr = prevPtr->nextPtr) {
+ /* Empty loop body. */
+ }
+ prevPtr->nextPtr = icoPtr->nextPtr;
+ }
+ if (icoPtr->taskbar_flags & TASKBAR_ICON) {
+ TaskbarOperation(icoInterpPtr, icoPtr, NIM_DELETE);
+ }
+ if (icoPtr->taskbar_txt != NULL) {
+ Tcl_DecrRefCount(icoPtr->taskbar_txt);
+ }
+ if (icoPtr->taskbar_command != NULL) {
+ Tcl_DecrRefCount(icoPtr->taskbar_command);
+ }
+ ckfree(icoPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GetIcoPtr --
+ *
+ * Get pointer to icon for display.
+ *
+ * Results:
+ * Icon is obtained for display.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static IcoInfo *
+GetIcoPtr(
+ Tcl_Interp *interp,
+ IcoInterpInfo *icoInterpPtr,
+ const char *string)
+{
+ IcoInfo *icoPtr;
+ unsigned id;
+ const char *start;
+ char *end;
+
+ if (strncmp(string, "ico#", 4) != 0) {
+ goto notfound;
+ }
+ start = string + 4;
+ id = strtoul(start, &end, 10);
+ if ((end == start) || (*end != 0)) {
+ goto notfound;
+ }
+ for (icoPtr = icoInterpPtr->firstIcoPtr; icoPtr != NULL; icoPtr = icoPtr->nextPtr) {
+ if (icoPtr->id == id) {
+ return icoPtr;
+ }
+ }
+
+notfound:
+ Tcl_AppendResult(interp, "icon \"", string,
+ "\" doesn't exist", NULL);
+ return NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GetInt --
+ *
+ * Utility function for calculating buffer length.
+ *
+ * Results:
+ * Length.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+GetInt(
+ long theint,
+ char *buffer,
+ size_t len)
+{
+ snprintf(buffer, len, "0x%lx", theint);
+ buffer[len - 1] = 0;
+ return (int) strlen(buffer);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GetIntDec --
+ *
+ * Utility function for calculating buffer length.
+ *
+ * Results:
+ * Length.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+GetIntDec(
+ long theint,
+ char *buffer,
+ size_t len)
+{
+ snprintf(buffer, len - 1, "%ld", theint);
+ buffer[len - 1] = 0;
+ return (int) strlen(buffer);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TaskbarExpandPercents --
+ *
+ * Parse strings in taskbar display.
+ *
+ * Results:
+ * Strings.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static char*
+TaskbarExpandPercents(
+ IcoInfo *icoPtr,
+ const char *msgstring,
+ WPARAM wParam,
+ LPARAM lParam,
+ char *before,
+ char *after,
+ int *aftersize)
+{
+#define SPACELEFT (*aftersize-(dst-after)-1)
+#define AFTERLEN ((*aftersize>0)?(*aftersize*2):1024)
+#define ALLOCLEN ((len>AFTERLEN)?(len*2):AFTERLEN)
+ char buffer[TCL_INTEGER_SPACE + 5];
+ char* dst;
+ dst = after;
+ while (*before) {
+ const char *ptr = before;
+ int len = 1;
+ if(*before == '%') {
+ switch(before[1]){
+ case 'M':
+ case 'm': {
+ before++;
+ len = strlen(msgstring);
+ ptr = msgstring;
+ break;
+ }
+ /* case 'W': {
+ before++;
+ len = (int)strlen(winstring);
+ ptr = winstring;
+ break;
+ }
+ */
+ case 'i': {
+ before++;
+ snprintf(buffer, sizeof(buffer) - 1, "ico#%d", icoPtr->id);
+ len = strlen(buffer);
+ ptr = buffer;
+ break;
+ }
+ case 'w': {
+ before++;
+ len = GetInt((long)wParam,buffer, sizeof(buffer));
+ ptr = buffer;
+ break;
+ }
+ case 'l': {
+ before++;
+ len = GetInt((long)lParam,buffer, sizeof(buffer));
+ ptr = buffer;
+ break;
+ }
+ case 't': {
+ before++;
+ len = GetInt((long)GetTickCount(), buffer, sizeof(buffer));
+ ptr = buffer;
+ break;
+ }
+ case 'x': {
+ POINT pt;
+ GetCursorPos(&pt);
+ before++;
+ len = GetIntDec((long)pt.x, buffer, sizeof(buffer));
+ ptr = buffer;
+ break;
+ }
+ case 'y': {
+ POINT pt;
+ GetCursorPos(&pt);
+ before++;
+ len = GetIntDec((long)pt.y,buffer, sizeof(buffer));
+ ptr = buffer;
+ break;
+ }
+ case 'X': {
+ DWORD dw;
+ dw = GetMessagePos();
+ before++;
+ len = GetIntDec((long)LOWORD(dw),buffer, sizeof(buffer));
+ ptr = buffer;
+ break;
+ }
+ case 'Y': {
+ DWORD dw;
+ dw = GetMessagePos();
+ before++;
+ len = GetIntDec((long)HIWORD(dw),buffer, sizeof(buffer));
+ ptr = buffer;
+ break;
+ }
+ case 'H': {
+ before++;
+ len = GetInt(PTR2INT(icoPtr->hwndFocus), buffer, sizeof(buffer));
+ ptr = buffer;
+ break;
+ }
+ case '%': {
+ before++;
+ len = 1;
+ ptr = "%";
+ break;
+ }
+ }
+ }
+ if (SPACELEFT < len) {
+ char *newspace;
+ ptrdiff_t dist = dst - after;
+ int alloclen = ALLOCLEN;
+ newspace = (char *)ckalloc(alloclen);
+ if (dist>0)
+ memcpy(newspace, after, dist);
+ if (after && *aftersize) {
+ ckfree(after);
+ }
+ *aftersize =alloclen;
+ after = newspace;
+ dst = after + dist;
+ }
+ if (len > 0) {
+ memcpy(dst, ptr, len);
+ }
+ dst += len;
+ if ((dst-after)>(*aftersize-1)) {
+ printf("oops\n");
+ }
+ before++;
+ }
+ *dst = 0;
+ return after;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TaskbarEval --
+ *
+ * Parse mouse and keyboard events over taskbar.
+ *
+ * Results:
+ * Event processing.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TaskbarEval(
+ IcoInfo *icoPtr,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ const char *msgstring = "none";
+ char evalspace[200];
+ int evalsize = 200;
+ char *expanded;
+ int fixup = 0;
+
+ switch (lParam) {
+ case WM_MOUSEMOVE:
+ msgstring = "WM_MOUSEMOVE";
+ icoPtr->hwndFocus = GetFocus();
+ break;
+ case WM_LBUTTONDOWN:
+ msgstring = "WM_LBUTTONDOWN";
+ fixup = 1;
+ break;
+ case WM_LBUTTONUP:
+ msgstring = "WM_LBUTTONUP";
+ fixup = 1;
+ break;
+ case WM_LBUTTONDBLCLK:
+ msgstring = "WM_LBUTTONDBLCLK";
+ fixup = 1;
+ break;
+ case WM_RBUTTONDOWN:
+ msgstring = "WM_RBUTTONDOWN";
+ fixup = 1;
+ break;
+ case WM_RBUTTONUP:
+ msgstring = "WM_RBUTTONUP";
+ fixup = 1;
+ break;
+ case WM_RBUTTONDBLCLK:
+ msgstring = "WM_RBUTTONDBLCLK";
+ fixup = 1;
+ break;
+ case WM_MBUTTONDOWN:
+ msgstring = "WM_MBUTTONDOWN";
+ fixup = 1;
+ break;
+ case WM_MBUTTONUP:
+ msgstring = "WM_MBUTTONUP";
+ fixup = 1;
+ break;
+ case WM_MBUTTONDBLCLK:
+ msgstring = "WM_MBUTTONDBLCLK";
+ fixup = 1;
+ break;
+ default:
+ msgstring = "WM_NULL";
+ fixup = 0;
+ }
+ expanded = TaskbarExpandPercents(icoPtr, msgstring, wParam, lParam,
+ Tcl_GetString(icoPtr->taskbar_command), evalspace, &evalsize);
+ if (icoPtr->interp != NULL) {
+ int result;
+ HWND hwnd = NULL;
+
+ /* See http://support.microsoft.com/kb/q135788/
+ * Seems to have moved to https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/135788 */
+ if (fixup) {
+ if (icoPtr->hwndFocus != NULL && IsWindow(icoPtr->hwndFocus)) {
+ hwnd = icoPtr->hwndFocus;
+ } else {
+ Tk_Window tkwin = Tk_MainWindow(icoPtr->interp);
+ if (tkwin != NULL) {
+ hwnd = Tk_GetHWND(Tk_WindowId(tkwin));
+ }
+ }
+ if (hwnd != NULL) {
+ SetForegroundWindow(hwnd);
+ }
+ }
+
+ result = Tcl_GlobalEval(icoPtr->interp, expanded);
+
+ if (hwnd != NULL) {
+ /* See http://support.microsoft.com/kb/q135788/
+ * Seems to have moved to https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/135788 */
+ PostMessageW(hwnd, WM_NULL, 0, 0);
+ }
+ if (result != TCL_OK) {
+ char buffer[100];
+ snprintf(buffer, 100, "\n (command bound to taskbar-icon ico#%d)", icoPtr->id);
+ Tcl_AddErrorInfo(icoPtr->interp, buffer);
+ Tcl_BackgroundError(icoPtr->interp);
+ }
+ }
+ if (expanded != evalspace) {
+ ckfree(expanded);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TaskbarHandlerProc --
+ *
+ * Windows callback procedure, if ICON_MESSAGE arrives, try to execute
+ * the taskbar_command.
+ *
+ * Results:
+ * Command execution.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static LRESULT CALLBACK
+TaskbarHandlerProc(
+ HWND hwnd,
+ UINT message,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ static UINT msgTaskbarCreated = 0;
+ IcoInterpInfo *icoInterpPtr;
+ IcoInfo *icoPtr;
+
+ switch (message) {
+ case WM_CREATE:
+ msgTaskbarCreated = RegisterWindowMessage(TEXT("TaskbarCreated"));
+ break;
+
+ case ICON_MESSAGE:
+ for (icoInterpPtr = firstIcoInterpPtr; icoInterpPtr != NULL; icoInterpPtr = icoInterpPtr->nextPtr) {
+ if (icoInterpPtr->hwnd == hwnd) {
+ for (icoPtr = icoInterpPtr->firstIcoPtr; icoPtr != NULL; icoPtr = icoPtr->nextPtr) {
+ if (icoPtr->id == wParam) {
+ if (icoPtr->taskbar_command != NULL) {
+ TaskbarEval(icoPtr, wParam, lParam);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ break;
+
+ default:
+ /*
+ * Check to see if explorer has been restarted and we need to
+ * re-add our icons.
+ */
+ if (message == msgTaskbarCreated) {
+ for (icoInterpPtr = firstIcoInterpPtr; icoInterpPtr != NULL; icoInterpPtr = icoInterpPtr->nextPtr) {
+ if (icoInterpPtr->hwnd == hwnd) {
+ for (icoPtr = icoInterpPtr->firstIcoPtr; icoPtr != NULL; icoPtr = icoPtr->nextPtr) {
+ if (icoPtr->taskbar_flags & TASKBAR_ICON) {
+ TaskbarOperation(icoInterpPtr, icoPtr, NIM_ADD);
+ }
+ }
+ break;
+ }
+ }
+ }
+ return DefWindowProc(hwnd, message, wParam, lParam);
+ }
+ return 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RegisterHandlerClass --
+ *
+ * Registers the handler window class.
+ *
+ * Results:
+ * Handler class registered.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RegisterHandlerClass(
+ HINSTANCE hInstance)
+{
+ WNDCLASS wndclass;
+ memset(&wndclass, 0, sizeof(WNDCLASS));
+ wndclass.style = 0;
+ wndclass.lpfnWndProc = TaskbarHandlerProc;
+ wndclass.cbClsExtra = 0;
+ wndclass.cbWndExtra = 0;
+ wndclass.hInstance = hInstance;
+ wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
+ wndclass.lpszMenuName = NULL;
+ wndclass.lpszClassName = HANDLER_CLASS;
+ return RegisterClass(&wndclass);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CreateTaskbarHandlerWindow --
+ *
+ * Creates a hidden window to handle taskbar messages.
+ *
+ * Results:
+ * Hidden window created.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static HWND
+CreateTaskbarHandlerWindow(void) {
+ static int registered = 0;
+ HINSTANCE hInstance = GETHINSTANCE;
+ if (!registered) {
+ if (!RegisterHandlerClass(hInstance))
+ return 0;
+ registered = 1;
+ }
+ return CreateWindow(HANDLER_CLASS, "", WS_OVERLAPPED, 0, 0,
+ CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * WinIcoDestroy --
+ *
+ * Event handler to delete systray icons when interp main window
+ * is deleted, either by destroy, interp deletion or application
+ * exit.
+ *
+ * Results:
+ * Icon/window removed and memory freed.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+WinIcoDestroy(
+ void *clientData,
+ XEvent *eventPtr)
+{
+ IcoInterpInfo *icoInterpPtr = (IcoInterpInfo*) clientData;
+ IcoInterpInfo *prevIcoInterpPtr;
+ IcoInfo *icoPtr;
+ IcoInfo *nextPtr;
+
+ if (eventPtr->type != DestroyNotify) {
+ return;
+ }
+
+ if (firstIcoInterpPtr == icoInterpPtr) {
+ firstIcoInterpPtr = icoInterpPtr->nextPtr;
+ } else {
+ for (prevIcoInterpPtr = firstIcoInterpPtr; prevIcoInterpPtr->nextPtr != icoInterpPtr;
+ prevIcoInterpPtr = prevIcoInterpPtr->nextPtr) {
+ /* Empty loop body. */
+ }
+ prevIcoInterpPtr->nextPtr = icoInterpPtr->nextPtr;
+ }
+
+ DestroyWindow(icoInterpPtr->hwnd);
+ for (icoPtr = icoInterpPtr->firstIcoPtr; icoPtr != NULL; icoPtr = nextPtr) {
+ nextPtr = icoPtr->nextPtr;
+ FreeIcoPtr(icoInterpPtr, icoPtr);
+ }
+ ckfree(icoInterpPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * WinSystrayCmd --
+ *
+ * Main command for creating, displaying, and removing icons from taskbar.
+ *
+ * Results:
+ * Management of icon display in taskbar/system tray.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+WinSystrayCmd(
+ void *clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ static const char *const cmdStrings[] = {
+ "add", "delete", "modify", NULL
+ };
+ enum { CMD_ADD, CMD_DELETE, CMD_MODIFY };
+ static const char *const optStrings[] = {
+ "-callback", "-image", "-text", NULL
+ };
+ enum { OPT_CALLBACK, OPT_IMAGE, OPT_TEXT };
+ int cmd, opt;
+
+ HICON hIcon;
+ int i;
+ IcoInterpInfo *icoInterpPtr = (IcoInterpInfo*) clientData;
+ IcoInfo *icoPtr = NULL;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "command ...");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObj(interp, objv[1], cmdStrings, "command",
+ 0, &cmd) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ switch (cmd) {
+ case CMD_ADD:
+ case CMD_MODIFY: {
+ Tcl_Obj *imageObj = NULL, *textObj = NULL, *callbackObj = NULL;
+ int optStart;
+ int oper;
+ if (cmd == CMD_ADD) {
+ optStart = 2;
+ oper = NIM_ADD;
+ } else {
+ optStart = 3;
+ oper = NIM_MODIFY;
+ if (objc != 5) {
+ Tcl_WrongNumArgs(interp, 2, objv, "id option value");
+ return TCL_ERROR;
+ }
+ icoPtr = GetIcoPtr(interp, icoInterpPtr, Tcl_GetString(objv[2]));
+ if (icoPtr == NULL) {
+ return TCL_ERROR;
+ }
+ }
+ for (i = optStart; i < objc; i += 2) {
+ if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option",
+ 0, &opt) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ if (i+1 >= objc) {
+ Tcl_AppendResult(interp,
+ "missing value for option \"", Tcl_GetString(objv[i]),
+ "\"", NULL);
+ return TCL_ERROR;
+ }
+ switch (opt) {
+ case OPT_IMAGE:
+ imageObj = objv[i+1];
+ break;
+ case OPT_TEXT:
+ textObj = objv[i+1];
+ break;
+ case OPT_CALLBACK:
+ callbackObj = objv[i+1];
+ break;
+ }
+ }
+ if (cmd == CMD_ADD && imageObj == NULL) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("missing required option \"-image\"", TCL_INDEX_NONE));
+ return TCL_ERROR;
+ }
+ if (imageObj != NULL) {
+ Tk_PhotoHandle photo;
+ int width, height;
+ Tk_PhotoImageBlock block;
+
+ photo = Tk_FindPhoto(interp, Tcl_GetString(imageObj));
+ if (photo == NULL) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "image \"%s\" doesn't exist", Tcl_GetString(imageObj)));
+ return TCL_ERROR;
+ }
+ Tk_PhotoGetSize(photo, &width, &height);
+ Tk_PhotoGetImage(photo, &block);
+ hIcon = CreateIcoFromPhoto(width, height, block);
+ if (hIcon == NULL) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "failed to create an iconphoto with image \"%s\"", Tcl_GetString(imageObj)));
+ return TCL_ERROR;
+ }
+ }
+ if (cmd == CMD_ADD) {
+ icoPtr = NewIcon(interp, icoInterpPtr, hIcon);
+ } else {
+ if (imageObj != NULL) {
+ DestroyIcon(icoPtr->hIcon);
+ icoPtr->hIcon = hIcon;
+ }
+ }
+ if (callbackObj != NULL) {
+ if (icoPtr->taskbar_command != NULL) {
+ Tcl_DecrRefCount(icoPtr->taskbar_command);
+ }
+ icoPtr->taskbar_command = callbackObj;
+ Tcl_IncrRefCount(icoPtr->taskbar_command);
+ }
+ if (textObj != NULL) {
+ if (icoPtr->taskbar_txt != NULL) {
+ Tcl_DecrRefCount(icoPtr->taskbar_txt);
+ }
+ icoPtr->taskbar_txt = textObj;
+ Tcl_IncrRefCount(icoPtr->taskbar_txt);
+ }
+ TaskbarOperation(icoInterpPtr, icoPtr, oper);
+ if (cmd == CMD_ADD) {
+ char buffer[5 + TCL_INTEGER_SPACE];
+ int n;
+ n = snprintf(buffer, sizeof(buffer) - 1, "ico#%d", icoPtr->id);
+ buffer[n] = 0;
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(buffer, n));
+ }
+ return TCL_OK;
+ }
+ case CMD_DELETE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "id");
+ return TCL_ERROR;
+ }
+ icoPtr = GetIcoPtr(interp, icoInterpPtr, Tcl_GetString(objv[2]));
+ if (icoPtr == NULL) {
+ return TCL_ERROR;
+ }
+ FreeIcoPtr(icoInterpPtr, icoPtr);
+ return TCL_OK;
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * WinSysNotifyCmd --
+ *
+ * Main command for creating and displaying notifications/balloons from system tray.
+ *
+ * Results:
+ * Display of notifications.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+WinSysNotifyCmd(
+ void *clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ IcoInterpInfo *icoInterpPtr = (IcoInterpInfo*) clientData;
+ IcoInfo *icoPtr;
+ Tcl_DString infodst;
+ Tcl_DString titledst;
+ NOTIFYICONDATAW ni;
+ char *msgtitle;
+ char *msginfo;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "command ...");
+ return TCL_ERROR;
+ }
+ if (strcmp(Tcl_GetString(objv[1]), "notify") != 0) {
+ Tcl_AppendResult(interp, "unknown subcommand \"", Tcl_GetString(objv[1]),
+ "\": must be notify", NULL);
+ return TCL_ERROR;
+ }
+ if (objc != 5) {
+ Tcl_WrongNumArgs(interp, 2, objv, "id title detail");
+ return TCL_ERROR;
+ }
+
+ icoPtr = GetIcoPtr(interp, icoInterpPtr, Tcl_GetString(objv[2]));
+ if (icoPtr == NULL) {
+ return TCL_ERROR;
+ }
+
+ ni.cbSize = sizeof(NOTIFYICONDATAW);
+ ni.hWnd = icoInterpPtr->hwnd;
+ ni.uID = icoPtr->id;
+ ni.uFlags = NIF_INFO;
+ ni.uCallbackMessage = ICON_MESSAGE;
+ ni.hIcon = icoPtr->hIcon;
+ ni.dwInfoFlags = NIIF_INFO; /* Use a sane platform-specific icon here.*/
+
+ msgtitle = Tcl_GetString(objv[3]);
+ msginfo = Tcl_GetString(objv[4]);
+
+ /* Balloon notification for system tray icon. */
+ if (msgtitle != NULL) {
+ WCHAR *title;
+ Tcl_DStringInit(&titledst);
+ title = Tcl_UtfToWCharDString(msgtitle, TCL_INDEX_NONE, &titledst);
+ wcsncpy(ni.szInfoTitle, title, (Tcl_DStringLength(&titledst) + 2) / 2);
+ Tcl_DStringFree(&titledst);
+ }
+ if (msginfo != NULL) {
+ WCHAR *info;
+ Tcl_DStringInit(&infodst);
+ info = Tcl_UtfToWCharDString(msginfo, TCL_INDEX_NONE, &infodst);
+ wcsncpy(ni.szInfo, info, (Tcl_DStringLength(&infodst) + 2) / 2);
+ Tcl_DStringFree(&infodst);
+ }
+
+ Shell_NotifyIconW(NIM_MODIFY, &ni);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * WinIcoInit --
+ *
+ * Initialize this package and create script-level commands.
+ *
+ * Results:
+ * Initialization of code.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+WinIcoInit(
+ Tcl_Interp *interp)
+{
+ IcoInterpInfo *icoInterpPtr;
+ Tk_Window mainWindow;
+
+ mainWindow = Tk_MainWindow(interp);
+ if (mainWindow == NULL) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("main window has been destroyed", TCL_INDEX_NONE));
+ return TCL_ERROR;
+ }
+
+ icoInterpPtr = (IcoInterpInfo*) ckalloc(sizeof(IcoInterpInfo));
+ icoInterpPtr->counter = 0;
+ icoInterpPtr->firstIcoPtr = NULL;
+ icoInterpPtr->hwnd = CreateTaskbarHandlerWindow();
+ icoInterpPtr->nextPtr = firstIcoInterpPtr;
+ firstIcoInterpPtr = icoInterpPtr;
+ Tcl_CreateObjCommand(interp, "::tk::systray::_systray", WinSystrayCmd,
+ icoInterpPtr, NULL);
+ Tcl_CreateObjCommand(interp, "::tk::sysnotify::_sysnotify", WinSysNotifyCmd,
+ icoInterpPtr, NULL);
+
+ Tk_CreateEventHandler(mainWindow, StructureNotifyMask,
+ WinIcoDestroy, icoInterpPtr);
+
+ return TCL_OK;
+}
+
+/*
+ * Local variables:
+ * mode: c
+ * indent-tabs-mode: nil
+ * End:
+ */