1 /*
2  * tkWinSysTray.c --
3  *
4  * 	tkWinSysTray.c implements a "systray" Tcl command which permits to
5  * 	change the system tray/taskbar icon of a Tk toplevel window and
6  * 	a "sysnotify" command to post system notifications.
7  *
8  * Copyright © 1995-1996 Microsoft Corp.
9  * Copyright © 1998 Brueckner & Jarosch Ing.GmbH, Erfurt, Germany
10  * Copyright © 2020 Kevin Walzer/WordTech Communications LLC.
11  * Copyright © 2020 Eric Boudaillier.
12  * Copyright © 2020 Francois Vogel.
13  *
14  * See the file "license.terms" for information on usage and redistribution of
15  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
16  */
17 
18 #include "tkInt.h"
19 #include <windows.h>
20 #include <shellapi.h>
21 #include "tkWin.h"
22 #include "tkWinInt.h"
23 #include "tkWinIco.h"
24 
25 /*
26  * Based extensively on the winico extension and sample code from Microsoft.
27  * Some of the code was adapted into tkWinWM.c to implement the "wm iconphoto"
28  * command (TIP 159), and here we are borrowing that code to use Tk images
29  * to create system tray icons instead of ico files. Additionally, we are
30  * removing obsolete parts of the winico extension, and implementing
31  * more of the Shell_Notification API to add balloon/system notifications.
32  */
33 
34 #define GETHINSTANCE Tk_GetHINSTANCE()
35 
36 #ifdef HAVE_STDLIB_H
37 #include <stdlib.h>
38 #endif
39 
40 #ifdef HAVE_STDINT_H
41 #include <stdint.h>
42 #endif
43 
44 #ifdef _MSC_VER
45 /*
46  * Earlier versions of MSVC don't know snprintf, but _snprintf is compatible.
47  * Note that sprintf is deprecated.
48  */
49 # define snprintf _snprintf
50 #endif
51 
52 typedef struct IcoInfo {
53     HICON hIcon;                /* icon handle returned by LoadIcon. */
54     unsigned id;                /* Identifier for command;  used to
55                                  * cancel it. */
56     Tcl_Obj *taskbar_txt;       /* text to display in the taskbar */
57     Tcl_Interp *interp;         /* interp which created the icon */
58     Tcl_Obj *taskbar_command;   /* command to eval if events in the taskbar
59                                  * arrive */
60     int taskbar_flags;          /* taskbar related flags*/
61     HWND hwndFocus;
62     struct IcoInfo *nextPtr;
63 } IcoInfo;
64 
65 /* Per-interp struture */
66 typedef struct IcoInterpInfo {
67     HWND hwnd;                  /* Handler window */
68     int counter;                /* Counter for IcoInfo id generation */
69     IcoInfo *firstIcoPtr;       /* List of created IcoInfo */
70     struct IcoInterpInfo *nextPtr;
71 } IcoInterpInfo;
72 
73 #define TASKBAR_ICON 1
74 #define ICON_MESSAGE WM_USER + 1234
75 
76 #define HANDLER_CLASS "Wtk_TaskbarHandler"
77 static HWND CreateTaskbarHandlerWindow(void);
78 
79 static IcoInterpInfo *firstIcoInterpPtr = NULL;
80 static Tk_EventProc WinIcoDestroy;
81 
82 /*
83  * If someone wants to see the several masks somewhere on the screen...
84  * set the ICO_DRAW define and feel free to make some Tcl commands
85  * for accessing it.  The normal drawing of an Icon to a DC is really easy:
86  * DrawIcon(hdc,x,y,hIcon) or , more complicated
87  * DrawIconEx32PlusMoreParameters ...
88  */
89 
90 /* #define ICO_DRAW */
91 #ifdef ICO_DRAW
92 #define RectWidth(r)((r).right - (r).left + 1)
93 #define RectHeight(r)((r).bottom - (r).top + 1)
94 
95 /*
96  *----------------------------------------------------------------------
97  *
98  * DrawXORMask --
99  *
100  * 	Using DIB functions, draw XOR mask on hDC in Rect.
101  *
102  * Results:
103  *	Icon is rendered.
104  *
105  * Side effects:
106  *	None.
107  *
108  *----------------------------------------------------------------------
109  */
110 
111 static BOOL
DrawXORMask(HDC hDC,RECT Rect,LPLPICONIMAGE lpIcon)112 DrawXORMask(
113     HDC hDC,
114     RECT Rect,
115     LPLPICONIMAGE lpIcon)
116 {
117     int x, y;
118 
119     /* Sanity checks */
120     if (lpIcon == NULL)
121         return FALSE;
122     if (lpIcon->lpBits == NULL)
123         return FALSE;
124 
125     /* Account for height*2 thing */
126     lpIcon->lpbi->bmiHeader.biHeight /= 2;
127 
128     /* Locate it */
129     x = Rect.left + ((RectWidth(Rect) - lpIcon->lpbi->bmiHeader.biWidth) / 2);
130     y = Rect.top + ((RectHeight(Rect) - lpIcon->lpbi->bmiHeader.biHeight) / 2);
131 
132     /* Blast it to the screen */
133     SetDIBitsToDevice(hDC, x, y,
134             lpIcon->lpbi->bmiHeader.biWidth,
135             lpIcon->lpbi->bmiHeader.biHeight,
136             0, 0, 0, lpIcon->lpbi->bmiHeader.biHeight,
137             lpIcon->lpXOR, lpIcon->lpbi, DIB_RGB_COLORS);
138 
139     /* UnAccount for height*2 thing */
140     lpIcon->lpbi->bmiHeader.biHeight *= 2;
141 
142     return TRUE;
143 }
144 
145 /*
146  *----------------------------------------------------------------------
147  *
148  * DrawANDMask --
149  *
150  * 	Using DIB functions, draw AND mask on hDC in Rect.
151  *
152  * Results:
153  *	Icon is rendered.
154  *
155  * Side effects:
156  *	None.
157  *
158  *----------------------------------------------------------------------
159  */
160 
161 BOOL
DrawANDMask(HDC hDC,RECT Rect,LPLPICONIMAGE lpIcon)162 DrawANDMask(
163     HDC hDC,
164     RECT Rect,
165     LPLPICONIMAGE lpIcon)
166 {
167     LPBITMAPINFO lpbi;
168     int x, y;
169 
170     /* Sanity checks */
171     if (lpIcon == NULL)
172         return FALSE;
173     if (lpIcon->lpBits == NULL)
174         return FALSE;
175 
176     /* Need a bitmap header for the mono mask */
177     lpbi = ckalloc(sizeof(BITMAPINFO) + (2 * sizeof(RGBQUAD)));
178     lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
179     lpbi->bmiHeader.biWidth = lpIcon->lpbi->bmiHeader.biWidth;
180     lpbi->bmiHeader.biHeight = lpIcon->lpbi->bmiHeader.biHeight / 2;
181     lpbi->bmiHeader.biPlanes = 1;
182     lpbi->bmiHeader.biBitCount = 1;
183     lpbi->bmiHeader.biCompression = BI_RGB;
184     lpbi->miHeader.biSizeImage = 0;
185     lpbi->bmiHeader.biXPelsPerMeter = 0;
186     lpbi->bmiHeader.biYPelsPerMeter = 0;
187     lpbi->bmiHeader.biClrUsed = 0;
188     lpbi->bmiHeader.biClrImportant = 0;
189     lpbi->bmiColors[0].rgbRed = 0;
190     lpbi->bmiColors[0].rgbGreen = 0;
191     lpbi->bmiColors[0].rgbBlue = 0;
192     lpbi->bmiColors[0].rgbReserved = 0;
193     lpbi->bmiColors[1].rgbRed = 255;
194     lpbi->bmiColors[1].rgbGreen = 255;
195     lpbi->bmiColors[1].rgbBlue = 255;
196     lpbi->bmiColors[1].rgbReserved = 0;
197 
198     /* Locate it */
199     x = Rect.left + ((RectWidth(Rect) - lpbi->bmiHeader.biWidth) / 2);
200     y = Rect.top + ((RectHeight(Rect) - lpbi->bmiHeader.biHeight) / 2);
201 
202     /* Blast it to the screen */
203     SetDIBitsToDevice(hDC, x, y,
204             lpbi->bmiHeader.biWidth,
205             lpbi->bmiHeader.biHeight,
206             0, 0, 0, lpbi->bmiHeader.biHeight,
207             lpIcon->lpAND, lpbi, DIB_RGB_COLORS);
208 
209     /* clean up */
210     ckfree((char *) lpbi);
211 
212     return TRUE;
213 }
214 #endif /* ICO_DRAW */
215 
216 /*
217  *----------------------------------------------------------------------
218  *
219  * TaskbarOperation --
220  *
221  * 	Management of icon display.
222  *
223  * Results:
224  *	Icon is displayed or deleted.
225  *
226  * Side effects:
227  *	None.
228  *
229  *----------------------------------------------------------------------
230  */
231 
232 static int
TaskbarOperation(IcoInterpInfo * icoInterpPtr,IcoInfo * icoPtr,int oper)233 TaskbarOperation(
234     IcoInterpInfo *icoInterpPtr,
235     IcoInfo *icoPtr,
236     int oper)
237 {
238     NOTIFYICONDATAW ni;
239     WCHAR *str;
240 
241     ni.cbSize = sizeof(NOTIFYICONDATAW);
242     ni.hWnd = icoInterpPtr->hwnd;
243     ni.uID = icoPtr->id;
244     ni.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
245     ni.uCallbackMessage = ICON_MESSAGE;
246     ni.hIcon = icoPtr->hIcon;
247 
248     if (icoPtr->taskbar_txt != NULL) {
249         Tcl_DString dst;
250         Tcl_DStringInit(&dst);
251         str = (WCHAR *)Tcl_UtfToWCharDString(Tcl_GetString(icoPtr->taskbar_txt), -1, &dst);
252         wcsncpy(ni.szTip, str, (Tcl_DStringLength(&dst) + 2) / 2);
253         Tcl_DStringFree(&dst);
254     } else {
255         ni.szTip[0] = 0;
256     }
257 
258     if (Shell_NotifyIconW(oper, &ni) == 1) {
259         if (oper == NIM_ADD || oper == NIM_MODIFY) {
260             icoPtr->taskbar_flags |= TASKBAR_ICON;
261         }
262         if (oper == NIM_DELETE) {
263             icoPtr->taskbar_flags &= ~TASKBAR_ICON;
264         }
265     }
266     /* Silently ignore error? */
267     return TCL_OK;
268 }
269 
270 /*
271  *----------------------------------------------------------------------
272  *
273  * NewIcon --
274  *
275  * 	Create icon for display in system tray.
276  *
277  * Results:
278  *	Icon is created for display.
279  *
280  * Side effects:
281  *	None.
282  *
283  *----------------------------------------------------------------------
284  */
285 
286 static IcoInfo *
NewIcon(Tcl_Interp * interp,IcoInterpInfo * icoInterpPtr,HICON hIcon)287 NewIcon(
288     Tcl_Interp *interp,
289     IcoInterpInfo *icoInterpPtr,
290     HICON hIcon)
291 {
292     IcoInfo *icoPtr;
293 
294     icoPtr = (IcoInfo *)ckalloc(sizeof(IcoInfo));
295     memset(icoPtr, 0, sizeof(IcoInfo));
296     icoPtr->id = ++icoInterpPtr->counter;
297     icoPtr->hIcon = hIcon;
298     icoPtr->taskbar_txt = NULL;
299     icoPtr->interp = interp;
300     icoPtr->taskbar_command = NULL;
301     icoPtr->taskbar_flags = 0;
302     icoPtr->hwndFocus = NULL;
303     icoPtr->nextPtr = icoInterpPtr->firstIcoPtr;
304     icoInterpPtr->firstIcoPtr = icoPtr;
305     return icoPtr;
306 }
307 
308 /*
309  *----------------------------------------------------------------------
310  *
311  * FreeIcoPtr --
312  *
313  * 	Delete icon and free memory.
314  *
315  * Results:
316  *	Icon is removed from display.
317  *
318  * Side effects:
319  *	Memory/resources freed.
320  *
321  *----------------------------------------------------------------------
322  */
323 
324 static void
FreeIcoPtr(IcoInterpInfo * icoInterpPtr,IcoInfo * icoPtr)325 FreeIcoPtr(
326     IcoInterpInfo *icoInterpPtr,
327     IcoInfo *icoPtr)
328 {
329     IcoInfo *prevPtr;
330     if (icoInterpPtr->firstIcoPtr == icoPtr) {
331         icoInterpPtr->firstIcoPtr = icoPtr->nextPtr;
332     } else {
333         for (prevPtr = icoInterpPtr->firstIcoPtr; prevPtr->nextPtr != icoPtr;
334                 prevPtr = prevPtr->nextPtr) {
335             /* Empty loop body. */
336         }
337         prevPtr->nextPtr = icoPtr->nextPtr;
338     }
339     if (icoPtr->taskbar_flags & TASKBAR_ICON) {
340         TaskbarOperation(icoInterpPtr, icoPtr, NIM_DELETE);
341     }
342     if (icoPtr->taskbar_txt != NULL) {
343         Tcl_DecrRefCount(icoPtr->taskbar_txt);
344     }
345     if (icoPtr->taskbar_command != NULL) {
346         Tcl_DecrRefCount(icoPtr->taskbar_command);
347     }
348     ckfree(icoPtr);
349 }
350 
351 /*
352  *----------------------------------------------------------------------
353  *
354  * GetIcoPtr --
355  *
356  * 	Get pointer to icon for display.
357  *
358  * Results:
359  *	Icon is obtained for display.
360  *
361  * Side effects:
362  *	None.
363  *
364  *----------------------------------------------------------------------
365  */
366 
367 static IcoInfo *
GetIcoPtr(Tcl_Interp * interp,IcoInterpInfo * icoInterpPtr,const char * string)368 GetIcoPtr(
369     Tcl_Interp *interp,
370     IcoInterpInfo *icoInterpPtr,
371     const char *string)
372 {
373     IcoInfo *icoPtr;
374     unsigned id;
375     const char *start;
376     char *end;
377 
378     if (strncmp(string, "ico#", 4) != 0) {
379         goto notfound;
380     }
381     start = string + 4;
382     id = strtoul(start, &end, 10);
383     if ((end == start) || (*end != 0)) {
384         goto notfound;
385     }
386     for (icoPtr = icoInterpPtr->firstIcoPtr; icoPtr != NULL; icoPtr = icoPtr->nextPtr) {
387         if (icoPtr->id == id) {
388             return icoPtr;
389         }
390     }
391 
392 notfound:
393     Tcl_AppendResult(interp, "icon \"", string,
394         "\" doesn't exist", (char *) NULL);
395     return NULL;
396 }
397 
398 /*
399  *----------------------------------------------------------------------
400  *
401  * GetInt --
402  *
403  * Utility function for calculating buffer length.
404  *
405  * Results:
406  *	Length.
407  *
408  * Side effects:
409  *	None.
410  *
411  *----------------------------------------------------------------------
412  */
413 
414 static int
GetInt(long theint,char * buffer,size_t len)415 GetInt(
416     long theint,
417     char *buffer,
418     size_t len)
419 {
420     snprintf(buffer, len, "0x%lx", theint);
421     buffer[len - 1] = 0;
422     return (int) strlen(buffer);
423 }
424 
425 /*
426  *----------------------------------------------------------------------
427  *
428  * GetIntDec --
429  *
430  * Utility function for calculating buffer length.
431  *
432  * Results:
433  *	Length.
434  *
435  * Side effects:
436  *	None.
437  *
438  *----------------------------------------------------------------------
439  */
440 
441 static int
GetIntDec(long theint,char * buffer,size_t len)442 GetIntDec(
443     long theint,
444     char *buffer,
445     size_t len)
446 {
447     snprintf(buffer, len - 1, "%ld", theint);
448     buffer[len - 1] = 0;
449     return (int) strlen(buffer);
450 }
451 
452 /*
453  *----------------------------------------------------------------------
454  *
455  * TaskbarExpandPercents --
456  *
457  * Parse strings in taskbar display.
458  *
459  * Results:
460  *	Strings.
461  *
462  * Side effects:
463  *	None.
464  *
465  *----------------------------------------------------------------------
466  */
467 
468 static char*
TaskbarExpandPercents(IcoInfo * icoPtr,const char * msgstring,WPARAM wParam,LPARAM lParam,char * before,char * after,int * aftersize)469 TaskbarExpandPercents(
470     IcoInfo *icoPtr,
471     const char *msgstring,
472     WPARAM wParam,
473     LPARAM lParam,
474     char *before,
475     char *after,
476     int *aftersize)
477 {
478 #define SPACELEFT (*aftersize-(dst-after)-1)
479 #define AFTERLEN ((*aftersize>0)?(*aftersize*2):1024)
480 #define ALLOCLEN ((len>AFTERLEN)?(len*2):AFTERLEN)
481     char buffer[TCL_INTEGER_SPACE + 5];
482     char* dst;
483     dst = after;
484     while (*before) {
485         const char *ptr = before;
486         int len = 1;
487         if(*before == '%') {
488             switch(before[1]){
489                 case 'M':
490                 case 'm': {
491                     before++;
492                     len = strlen(msgstring);
493                     ptr = msgstring;
494                     break;
495                 }
496                 /* case 'W': {
497                    before++;
498                    len = (int)strlen(winstring);
499                    ptr = winstring;
500                    break;
501                    }
502                 */
503                 case 'i': {
504                     before++;
505                     snprintf(buffer, sizeof(buffer) - 1, "ico#%d", icoPtr->id);
506                     len = strlen(buffer);
507                     ptr = buffer;
508                     break;
509                 }
510                 case 'w': {
511                     before++;
512                     len = GetInt((long)wParam,buffer, sizeof(buffer));
513                     ptr = buffer;
514                     break;
515                 }
516                 case 'l': {
517                     before++;
518                     len = GetInt((long)lParam,buffer, sizeof(buffer));
519                     ptr = buffer;
520                     break;
521                 }
522                 case 't': {
523                     before++;
524                     len = GetInt((long)GetTickCount(), buffer, sizeof(buffer));
525                     ptr = buffer;
526                     break;
527                 }
528                 case 'x': {
529                     POINT pt;
530                     GetCursorPos(&pt);
531                     before++;
532                     len = GetIntDec((long)pt.x, buffer, sizeof(buffer));
533                     ptr = buffer;
534                     break;
535                 }
536                 case 'y': {
537                     POINT pt;
538                     GetCursorPos(&pt);
539                     before++;
540                     len = GetIntDec((long)pt.y,buffer, sizeof(buffer));
541                     ptr = buffer;
542                     break;
543                 }
544                 case 'X': {
545                     DWORD dw;
546                     dw = GetMessagePos();
547                     before++;
548                     len = GetIntDec((long)LOWORD(dw),buffer, sizeof(buffer));
549                     ptr = buffer;
550                     break;
551                 }
552                 case 'Y': {
553                     DWORD dw;
554                     dw = GetMessagePos();
555                     before++;
556                     len = GetIntDec((long)HIWORD(dw),buffer, sizeof(buffer));
557                     ptr = buffer;
558                     break;
559                 }
560                 case 'H': {
561                     before++;
562                     len = GetInt(PTR2INT(icoPtr->hwndFocus), buffer, sizeof(buffer));
563                     ptr = buffer;
564                     break;
565                 }
566                 case '%': {
567                     before++;
568                     len = 1;
569                     ptr = "%";
570                     break;
571                 }
572             }
573         }
574         if (SPACELEFT < len) {
575             char *newspace;
576             ptrdiff_t dist = dst - after;
577             int alloclen = ALLOCLEN;
578             newspace = (char *)ckalloc(alloclen);
579             if (dist>0)
580                 memcpy(newspace, after, dist);
581             if (after && *aftersize) {
582                 ckfree(after);
583             }
584             *aftersize =alloclen;
585             after = newspace;
586             dst = after + dist;
587         }
588         if (len > 0) {
589             memcpy(dst, ptr, len);
590         }
591         dst += len;
592         if ((dst-after)>(*aftersize-1)) {
593             printf("oops\n");
594         }
595         before++;
596     }
597     *dst = 0;
598     return after;
599 }
600 
601 /*
602  *----------------------------------------------------------------------
603  *
604  * TaskbarEval --
605  *
606  * Parse mouse and keyboard events over taskbar.
607  *
608  * Results:
609  *	Event processing.
610  *
611  * Side effects:
612  *	None.
613  *
614  *----------------------------------------------------------------------
615  */
616 
617 static void
TaskbarEval(IcoInfo * icoPtr,WPARAM wParam,LPARAM lParam)618 TaskbarEval(
619     IcoInfo *icoPtr,
620     WPARAM wParam,
621     LPARAM lParam)
622 {
623     const char *msgstring = "none";
624     char evalspace[200];
625     int evalsize = 200;
626     char *expanded;
627     int fixup = 0;
628 
629     switch (lParam) {
630     case WM_MOUSEMOVE:
631         msgstring = "WM_MOUSEMOVE";
632         icoPtr->hwndFocus = GetFocus();
633         break;
634     case WM_LBUTTONDOWN:
635         msgstring = "WM_LBUTTONDOWN";
636         fixup = 1;
637         break;
638     case WM_LBUTTONUP:
639         msgstring = "WM_LBUTTONUP";
640         fixup = 1;
641         break;
642     case WM_LBUTTONDBLCLK:
643         msgstring = "WM_LBUTTONDBLCLK";
644         fixup = 1;
645         break;
646     case WM_RBUTTONDOWN:
647         msgstring = "WM_RBUTTONDOWN";
648         fixup = 1;
649         break;
650     case WM_RBUTTONUP:
651         msgstring = "WM_RBUTTONUP";
652         fixup = 1;
653         break;
654     case WM_RBUTTONDBLCLK:
655         msgstring = "WM_RBUTTONDBLCLK";
656         fixup = 1;
657         break;
658     case WM_MBUTTONDOWN:
659         msgstring = "WM_MBUTTONDOWN";
660         fixup = 1;
661         break;
662     case WM_MBUTTONUP:
663         msgstring = "WM_MBUTTONUP";
664         fixup = 1;
665         break;
666     case WM_MBUTTONDBLCLK:
667         msgstring = "WM_MBUTTONDBLCLK";
668         fixup = 1;
669         break;
670     default:
671         msgstring = "WM_NULL";
672         fixup = 0;
673     }
674     expanded = TaskbarExpandPercents(icoPtr, msgstring, wParam, lParam,
675             Tcl_GetString(icoPtr->taskbar_command), evalspace, &evalsize);
676     if (icoPtr->interp != NULL) {
677         int result;
678         HWND hwnd = NULL;
679 
680         /* See http://support.microsoft.com/kb/q135788/
681          * Seems to have moved to https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/135788 */
682         if (fixup) {
683             if (icoPtr->hwndFocus != NULL && IsWindow(icoPtr->hwndFocus)) {
684                 hwnd = icoPtr->hwndFocus;
685             } else {
686                 Tk_Window tkwin = Tk_MainWindow(icoPtr->interp);
687                 if (tkwin != NULL) {
688                     hwnd = Tk_GetHWND(Tk_WindowId(tkwin));
689                 }
690             }
691             if (hwnd != NULL) {
692                 SetForegroundWindow(hwnd);
693             }
694         }
695 
696         result = Tcl_GlobalEval(icoPtr->interp, expanded);
697 
698         if (hwnd != NULL) {
699             /* See http://support.microsoft.com/kb/q135788/
700              * Seems to have moved to https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/135788 */
701             PostMessageW(hwnd, WM_NULL, 0, 0);
702         }
703         if (result != TCL_OK) {
704             char buffer[100];
705             sprintf(buffer, "\n  (command bound to taskbar-icon ico#%d)", icoPtr->id);
706             Tcl_AddErrorInfo(icoPtr->interp, buffer);
707             Tcl_BackgroundError(icoPtr->interp);
708         }
709     }
710     if (expanded != evalspace) {
711         ckfree(expanded);
712     }
713 }
714 
715 /*
716  *----------------------------------------------------------------------
717  *
718  * TaskbarHandlerProc --
719  *
720  * 	Windows callback procedure, if ICON_MESSAGE arrives, try to execute
721  * 	the taskbar_command.
722  *
723  * Results:
724  *	Command execution.
725  *
726  * Side effects:
727  *	None.
728  *
729  *----------------------------------------------------------------------
730  */
731 
732 static LRESULT CALLBACK
TaskbarHandlerProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)733 TaskbarHandlerProc(
734     HWND hwnd,
735     UINT message,
736     WPARAM wParam,
737     LPARAM lParam)
738 {
739     static UINT msgTaskbarCreated = 0;
740     IcoInterpInfo *icoInterpPtr;
741     IcoInfo *icoPtr;
742 
743     switch (message) {
744     case WM_CREATE:
745         msgTaskbarCreated = RegisterWindowMessage(TEXT("TaskbarCreated"));
746         break;
747 
748     case ICON_MESSAGE:
749         for (icoInterpPtr = firstIcoInterpPtr; icoInterpPtr != NULL; icoInterpPtr = icoInterpPtr->nextPtr) {
750             if (icoInterpPtr->hwnd == hwnd) {
751                 for (icoPtr = icoInterpPtr->firstIcoPtr; icoPtr != NULL; icoPtr = icoPtr->nextPtr) {
752                     if (icoPtr->id == wParam) {
753                         if (icoPtr->taskbar_command != NULL) {
754                             TaskbarEval(icoPtr, wParam, lParam);
755                         }
756                         break;
757                     }
758                 }
759                 break;
760             }
761         }
762         break;
763 
764     default:
765         /*
766          * Check to see if explorer has been restarted and we need to
767          * re-add our icons.
768          */
769         if (message == msgTaskbarCreated) {
770             for (icoInterpPtr = firstIcoInterpPtr; icoInterpPtr != NULL; icoInterpPtr = icoInterpPtr->nextPtr) {
771                 if (icoInterpPtr->hwnd == hwnd) {
772                     for (icoPtr = icoInterpPtr->firstIcoPtr; icoPtr != NULL; icoPtr = icoPtr->nextPtr) {
773                         if (icoPtr->taskbar_flags & TASKBAR_ICON) {
774                             TaskbarOperation(icoInterpPtr, icoPtr, NIM_ADD);
775                         }
776                     }
777                     break;
778                 }
779             }
780         }
781         return DefWindowProc(hwnd, message, wParam, lParam);
782     }
783     return 0;
784 }
785 
786 /*
787  *----------------------------------------------------------------------
788  *
789  * RegisterHandlerClass --
790  *
791  * 	Registers the handler window class.
792  *
793  * Results:
794  *	Handler class registered.
795  *
796  * Side effects:
797  *	None.
798  *
799  *----------------------------------------------------------------------
800  */
801 
802 static int
RegisterHandlerClass(HINSTANCE hInstance)803 RegisterHandlerClass(
804     HINSTANCE hInstance)
805 {
806     WNDCLASS wndclass;
807     memset(&wndclass, 0, sizeof(WNDCLASS));
808     wndclass.style = 0;
809     wndclass.lpfnWndProc = TaskbarHandlerProc;
810     wndclass.cbClsExtra = 0;
811     wndclass.cbWndExtra = 0;
812     wndclass.hInstance = hInstance;
813     wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
814     wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
815     wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
816     wndclass.lpszMenuName = NULL;
817     wndclass.lpszClassName = HANDLER_CLASS;
818     return RegisterClass(&wndclass);
819 }
820 
821 /*
822  *----------------------------------------------------------------------
823  *
824  * CreateTaskbarHandlerWindow --
825  *
826  * 	Creates a hidden window to handle taskbar messages.
827  *
828  * Results:
829  *	Hidden window created.
830  *
831  * Side effects:
832  *	None.
833  *
834  *----------------------------------------------------------------------
835  */
836 
837 static HWND
CreateTaskbarHandlerWindow(void)838 CreateTaskbarHandlerWindow(void) {
839     static int registered = 0;
840     HINSTANCE hInstance = GETHINSTANCE;
841     if (!registered) {
842         if (!RegisterHandlerClass(hInstance))
843             return 0;
844         registered = 1;
845     }
846     return CreateWindow(HANDLER_CLASS, "", WS_OVERLAPPED, 0, 0,
847             CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
848 }
849 
850 /*
851  *----------------------------------------------------------------------
852  *
853  * WinIcoDestroy --
854  *
855  *	Event handler to delete systray icons when interp main window
856  *	is deleted, either by destroy, interp deletion or application
857  *	exit.
858  *
859  * Results:
860  *	Icon/window removed and memory freed.
861  *
862  * Side effects:
863  *	None.
864  *
865  *----------------------------------------------------------------------
866  */
867 
868 static void
WinIcoDestroy(ClientData clientData,XEvent * eventPtr)869 WinIcoDestroy(
870     ClientData clientData,
871     XEvent *eventPtr)
872 {
873     IcoInterpInfo *icoInterpPtr = (IcoInterpInfo*) clientData;
874     IcoInterpInfo *prevIcoInterpPtr;
875     IcoInfo *icoPtr;
876     IcoInfo *nextPtr;
877 
878     if (eventPtr->type != DestroyNotify) {
879         return;
880     }
881 
882     if (firstIcoInterpPtr == icoInterpPtr) {
883         firstIcoInterpPtr = icoInterpPtr->nextPtr;
884     } else {
885         for (prevIcoInterpPtr = firstIcoInterpPtr; prevIcoInterpPtr->nextPtr != icoInterpPtr;
886                 prevIcoInterpPtr = prevIcoInterpPtr->nextPtr) {
887             /* Empty loop body. */
888         }
889         prevIcoInterpPtr->nextPtr = icoInterpPtr->nextPtr;
890     }
891 
892     DestroyWindow(icoInterpPtr->hwnd);
893     for (icoPtr = icoInterpPtr->firstIcoPtr; icoPtr != NULL; icoPtr = nextPtr) {
894             nextPtr = icoPtr->nextPtr;
895         FreeIcoPtr(icoInterpPtr, icoPtr);
896     }
897     ckfree((char *) icoInterpPtr);
898 }
899 
900 /*
901  *----------------------------------------------------------------------
902  *
903  * WinSystrayCmd --
904  *
905  * 	Main command for creating, displaying, and removing icons from taskbar.
906  *
907  * Results:
908  *	Management of icon display in taskbar/system tray.
909  *
910  * Side effects:
911  *	None.
912  *
913  *----------------------------------------------------------------------
914  */
915 
916 static int
WinSystrayCmd(ClientData clientData,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])917 WinSystrayCmd(
918     ClientData clientData,
919     Tcl_Interp *interp,
920     int objc,
921     Tcl_Obj *const objv[])
922 {
923     static const char *const cmdStrings[] = {
924         "add", "delete", "modify", NULL
925     };
926     enum { CMD_ADD, CMD_DELETE, CMD_MODIFY };
927     static const char *const optStrings[] = {
928         "-image", "-text", "-callback", NULL
929     };
930     enum { OPT_IMAGE, OPT_TEXT, OPT_CALLBACK };
931     int cmd, opt;
932 
933     HICON hIcon;
934     int i;
935     IcoInterpInfo *icoInterpPtr = (IcoInterpInfo*) clientData;
936     IcoInfo *icoPtr = NULL;
937 
938     if (objc < 2) {
939         Tcl_WrongNumArgs(interp, 1, objv, "command ...");
940         return TCL_ERROR;
941     }
942     if (Tcl_GetIndexFromObj(interp, objv[1], cmdStrings, "command",
943             0, &cmd) == TCL_ERROR) {
944         return TCL_ERROR;
945     }
946     switch (cmd) {
947         case CMD_ADD:
948         case CMD_MODIFY: {
949             Tcl_Obj *imageObj = NULL, *textObj = NULL, *callbackObj = NULL;
950             int optStart;
951             int oper;
952             if (cmd == CMD_ADD) {
953                 optStart = 2;
954                 oper = NIM_ADD;
955             } else {
956                 optStart = 3;
957                 oper = NIM_MODIFY;
958                 if (objc != 5) {
959                     Tcl_WrongNumArgs(interp, 2, objv, "id option value");
960                     return TCL_ERROR;
961                 }
962                 icoPtr = GetIcoPtr(interp, icoInterpPtr, Tcl_GetString(objv[2]));
963                 if (icoPtr == NULL) {
964                     return TCL_ERROR;
965                 }
966             }
967             for (i = optStart; i < objc; i += 2) {
968                 if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option",
969                         0, &opt) == TCL_ERROR) {
970                     return TCL_ERROR;
971                 }
972                 if (i+1 >= objc) {
973                     Tcl_AppendResult(interp,
974                             "missing value for option \"", Tcl_GetString(objv[i]),
975                             "\"", NULL);
976                     return TCL_ERROR;
977                 }
978                 switch (opt) {
979                     case OPT_IMAGE:
980                         imageObj = objv[i+1];
981                         break;
982                     case OPT_TEXT:
983                         textObj = objv[i+1];
984                         break;
985                     case OPT_CALLBACK:
986                         callbackObj = objv[i+1];
987                         break;
988                 }
989             }
990             if (cmd == CMD_ADD && imageObj == NULL) {
991                 Tcl_SetObjResult(interp, Tcl_NewStringObj("missing required option \"-image\"", -1));
992                 return TCL_ERROR;
993             }
994             if (imageObj != NULL) {
995                 Tk_PhotoHandle photo;
996                 int width, height;
997                 Tk_PhotoImageBlock block;
998 
999                 photo = Tk_FindPhoto(interp, Tcl_GetString(imageObj));
1000                 if (photo == NULL) {
1001                     Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1002                             "image \"%s\" doesn't exist", Tcl_GetString(imageObj)));
1003                     return TCL_ERROR;
1004                 }
1005                 Tk_PhotoGetSize(photo, &width, &height);
1006                 Tk_PhotoGetImage(photo, &block);
1007                 hIcon = CreateIcoFromPhoto(width, height, block);
1008                 if (hIcon == NULL) {
1009                     Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1010                             "failed to create an iconphoto with image \"%s\"", Tcl_GetString(imageObj)));
1011                     return TCL_ERROR;
1012                 }
1013             }
1014             if (cmd == CMD_ADD) {
1015                 icoPtr = NewIcon(interp, icoInterpPtr, hIcon);
1016             } else {
1017                 if (imageObj != NULL) {
1018                     DestroyIcon(icoPtr->hIcon);
1019                     icoPtr->hIcon = hIcon;
1020                 }
1021             }
1022             if (callbackObj != NULL) {
1023                 if (icoPtr->taskbar_command != NULL) {
1024                     Tcl_DecrRefCount(icoPtr->taskbar_command);
1025                 }
1026                 icoPtr->taskbar_command = callbackObj;
1027                 Tcl_IncrRefCount(icoPtr->taskbar_command);
1028             }
1029             if (textObj != NULL) {
1030                 if (icoPtr->taskbar_txt != NULL) {
1031                     Tcl_DecrRefCount(icoPtr->taskbar_txt);
1032                 }
1033                 icoPtr->taskbar_txt = textObj;
1034                 Tcl_IncrRefCount(icoPtr->taskbar_txt);
1035             }
1036             TaskbarOperation(icoInterpPtr, icoPtr, oper);
1037             if (cmd == CMD_ADD) {
1038                 char buffer[5 + TCL_INTEGER_SPACE];
1039                 int n;
1040                 n = _snprintf(buffer, sizeof(buffer) - 1, "ico#%d", icoPtr->id);
1041                 buffer[n] = 0;
1042                 Tcl_SetObjResult(interp, Tcl_NewStringObj(buffer, n));
1043             }
1044             return TCL_OK;
1045         }
1046         case CMD_DELETE:
1047             if (objc != 3) {
1048                 Tcl_WrongNumArgs(interp, 2, objv, "id");
1049                 return TCL_ERROR;
1050             }
1051             icoPtr = GetIcoPtr(interp, icoInterpPtr, Tcl_GetString(objv[2]));
1052             if (icoPtr == NULL) {
1053                 return TCL_ERROR;
1054             }
1055             FreeIcoPtr(icoInterpPtr, icoPtr);
1056             return TCL_OK;
1057     }
1058     return TCL_OK;
1059 }
1060 
1061 /*
1062  *----------------------------------------------------------------------
1063  *
1064  * WinSysNotifyCmd --
1065  *
1066  * 	Main command for creating and displaying notifications/balloons from system tray.
1067  *
1068  * Results:
1069  *	Display of notifications.
1070  *
1071  * Side effects:
1072  *	None.
1073  *
1074  *----------------------------------------------------------------------
1075  */
1076 
1077 static int
WinSysNotifyCmd(ClientData clientData,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])1078 WinSysNotifyCmd(
1079     ClientData clientData,
1080     Tcl_Interp *interp,
1081     int objc,
1082     Tcl_Obj *const objv[])
1083 {
1084     IcoInterpInfo *icoInterpPtr = (IcoInterpInfo*) clientData;
1085     IcoInfo *icoPtr;
1086     Tcl_DString infodst;
1087     Tcl_DString titledst;
1088     NOTIFYICONDATAW ni;
1089     char *msgtitle;
1090     char *msginfo;
1091 
1092     if (objc < 2) {
1093         Tcl_WrongNumArgs(interp, 1, objv, "command ...");
1094         return TCL_ERROR;
1095     }
1096     if (strcmp(Tcl_GetString(objv[1]), "notify") != 0) {
1097         Tcl_AppendResult(interp, "unknown subcommand \"", Tcl_GetString(objv[1]),
1098                 "\": must be notify", NULL);
1099         return TCL_ERROR;
1100     }
1101     if (objc != 5) {
1102         Tcl_WrongNumArgs(interp, 2, objv, "id title detail");
1103         return TCL_ERROR;
1104     }
1105 
1106     icoPtr = GetIcoPtr(interp, icoInterpPtr, Tcl_GetString(objv[2]));
1107     if (icoPtr == NULL) {
1108         return TCL_ERROR;
1109     }
1110 
1111     ni.cbSize = sizeof(NOTIFYICONDATAW);
1112     ni.hWnd = icoInterpPtr->hwnd;
1113     ni.uID = icoPtr->id;
1114     ni.uFlags = NIF_INFO;
1115     ni.uCallbackMessage = ICON_MESSAGE;
1116     ni.hIcon = icoPtr->hIcon;
1117     ni.dwInfoFlags = NIIF_INFO; /* Use a sane platform-specific icon here.*/
1118 
1119     msgtitle = Tcl_GetString(objv[3]);
1120     msginfo = Tcl_GetString(objv[4]);
1121 
1122     /* Balloon notification for system tray icon. */
1123     if (msgtitle != NULL) {
1124         WCHAR *title;
1125         Tcl_DStringInit(&titledst);
1126         title = Tcl_UtfToWCharDString(msgtitle, -1, &titledst);
1127         wcsncpy(ni.szInfoTitle, title, (Tcl_DStringLength(&titledst) + 2) / 2);
1128         Tcl_DStringFree(&titledst);
1129     }
1130     if (msginfo != NULL) {
1131         WCHAR *info;
1132         Tcl_DStringInit(&infodst);
1133         info = Tcl_UtfToWCharDString(msginfo, -1, &infodst);
1134         wcsncpy(ni.szInfo, info, (Tcl_DStringLength(&infodst) + 2) / 2);
1135         Tcl_DStringFree(&infodst);
1136     }
1137 
1138     Shell_NotifyIconW(NIM_MODIFY, &ni);
1139     return TCL_OK;
1140 }
1141 
1142 /*
1143  *----------------------------------------------------------------------
1144  *
1145  * WinIcoInit --
1146  *
1147  * 	Initialize this package and create script-level commands.
1148  *
1149  * Results:
1150  *	Initialization of code.
1151  *
1152  * Side effects:
1153  *	None.
1154  *
1155  *----------------------------------------------------------------------
1156  */
1157 
1158 int
WinIcoInit(Tcl_Interp * interp)1159 WinIcoInit(
1160     Tcl_Interp *interp)
1161 {
1162     IcoInterpInfo *icoInterpPtr;
1163     Tk_Window mainWindow;
1164 
1165     mainWindow = Tk_MainWindow(interp);
1166     if (mainWindow == NULL) {
1167         Tcl_SetObjResult(interp, Tcl_NewStringObj("main window has been destroyed", -1));
1168         return TCL_ERROR;
1169     }
1170 
1171     icoInterpPtr = (IcoInterpInfo*) ckalloc(sizeof(IcoInterpInfo));
1172     icoInterpPtr->counter = 0;
1173     icoInterpPtr->firstIcoPtr = NULL;
1174     icoInterpPtr->hwnd = CreateTaskbarHandlerWindow();
1175     icoInterpPtr->nextPtr = firstIcoInterpPtr;
1176     firstIcoInterpPtr = icoInterpPtr;
1177     Tcl_CreateObjCommand(interp, "::tk::systray::_systray", WinSystrayCmd,
1178             (ClientData) icoInterpPtr, NULL);
1179     Tcl_CreateObjCommand(interp, "::tk::sysnotify::_sysnotify", WinSysNotifyCmd,
1180             (ClientData) icoInterpPtr, NULL);
1181 
1182     Tk_CreateEventHandler(mainWindow, StructureNotifyMask,
1183             WinIcoDestroy, (ClientData) icoInterpPtr);
1184 
1185     return TCL_OK;
1186 }
1187 
1188 /*
1189  * Local variables:
1190  * mode: c
1191  * indent-tabs-mode: nil
1192  * End:
1193  */
1194