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