source: trunk/src/3rdparty/os2/xsystray/plugin/w_xsystray.c@ 951

Last change on this file since 951 was 951, checked in by Dmitry A. Kuminov, 14 years ago

OS/2: xsystray: Paint empty box for NULL icon.

This is what tray widgets on Windows and Linux do. Previously,
xsystray would leave an old icon if NULL icon was sent to it,
which was totally confusing.

File size: 61.8 KB
Line 
1
2/*
3 *@@sourcefile w_xsystray.c:
4 * Extended system tray widget for XCenter/eCenter.
5 *
6 * Implementation.
7 *
8 * Copyright (C) 2009-2011 Dmitriy Kuminov
9 *
10 * This file is part of the Extended system tray widget source package.
11 * Extended system tray widget is free software; you can redistribute it
12 * and/or modify it under the terms of the GNU General Public License as
13 * published by the Free Software Foundation, in version 2 as it comes in
14 * the "COPYING" file of the Extended system tray widget distribution. This
15 * program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
18 * more details.
19 */
20
21#if defined(__IBMC__) || defined(__IBMCPP__)
22#pragma strings(readonly)
23#endif
24
25/*
26 * Suggested #include order:
27 * 1) os2.h
28 * 2) C library headers
29 * 3) setup.h (code generation and debugging options)
30 * 4) headers in helpers\
31 * 5) at least one SOM implementation header (*.ih)
32 * 6) dlgids.h, headers in shared\ (as needed)
33 * 7) headers in implementation dirs (e.g. filesys\, as needed)
34 * 8) #pragma hdrstop and then more SOM headers which crash with precompiled headers
35 */
36
37#if defined(__GNUC__) && defined(__EMX__)
38#define OS2EMX_PLAIN_CHAR
39#endif
40
41#define INCL_BASE
42#define INCL_PM
43#include <os2.h>
44
45// C library headers
46#include <stdio.h>
47#include <string.h>
48#include <stdlib.h>
49
50// generic headers
51// If this file were part of the XWorkplace sources, we'd now include
52// the generic "setup.h" file, which has common set up code for every
53// single XWorkplace code file. But it's not, so we won't include that.
54// #include "setup.h" // code generation and debugging options
55
56// headers in /helpers
57// This would be the place to include headers from the "XWorkplace helpers".
58// But since we do a minimal sample here, we can't include those helpful
59// routines... again, see the src\widgets in the XWorkplace source code
60// for how these functions can be imported from XFLDR.DLL to avoid duplicate
61// code.
62// #include "helpers\dosh.h" // Control Program helper routines
63// #include "helpers\gpih.h" // GPI helper routines
64// #include "helpers\prfh.h" // INI file helper routines;
65 // this include is required for some
66 // of the structures in shared\center.h
67// #include "helpers\winh.h" // PM helper routines
68// #include "helpers\xstring.h" // extended string helpers
69
70// XWorkplace implementation headers
71// If this file were part of the XCenter sources, we'd now include
72// "center.h" from the "include\shared" directory. Since we're not
73// part of the XCenter sources here, we include that file from the
74// "toolkit" directory in the binary release. That file is identical
75// to "include\shared\center.h" in the XWorkplace sources.
76#include "shared\center.h" // public XCenter interfaces
77
78//#define ENABLE_LOG_TO "c:\\xsystray_plugin.dbg"
79
80#include "w_xsystray.h"
81
82#if defined(__IBMC__) || defined(__IBMCPP__)
83#pragma hdrstop // VAC++ keeps crashing otherwise
84#endif
85
86// copy paste from helpers\comctl.h
87// @todo not necessary when we become part of XWorkplace
88
89#define TTN_NEEDTEXT 1000
90#define TTN_SHOW 1001
91#define TTN_POP 1002
92
93#define TTFMT_PSZ 0x01
94#define TTFMT_STRINGRES 0x02
95
96typedef struct _TOOLTIPTEXT
97{
98 HWND hwndTooltip;
99 HWND hwndTool;
100 ULONG ulFormat;
101 PSZ pszText;
102 HMODULE hmod;
103 ULONG idResource;
104} TOOLTIPTEXT, *PTOOLTIPTEXT;
105
106#define TTM_FIRST (WM_USER + 1000)
107#define TTM_UPDATETIPTEXT (TTM_FIRST + 9)
108#define TTM_SHOWTOOLTIPNOW (TTM_FIRST + 17)
109
110/* ******************************************************************
111 *
112 * Private definitions
113 *
114 ********************************************************************/
115
116/*
117 *@@ ICONDATA:
118 * Per-icon data.
119 */
120
121typedef struct
122{
123 HWND hwnd;
124 // associated window
125 USHORT usId;
126 // icon ID
127 HPOINTER hIcon;
128 // icon handle
129 ULONG ulMsgId;
130 // message ID for notifications
131 PSZ pszToolTip;
132 // icon tooltip (NULL if none)
133 BOOL bIsToolTipShowing;
134 // whether the tooltip is currently shown
135 BOOL bMemoryPoolGiven;
136 // TRUE if SYSTRAYDATA::pvMemoryPool is already given to
137 // the process hwnd belongs to
138
139} ICONDATA, *PICONDATA;
140
141/*
142 *@@ SYSTRAYDATA:
143 * Global system tray data.
144 */
145
146typedef struct
147{
148 HWND hwndServer;
149 // systtem tray server window handle
150 LONG lIconWidth;
151 // system icon width in px
152 LONG lIconHeight;
153 // system icon height in px
154 LONG lIconPad;
155 // padding around each icon in px
156 PICONDATA pIcons;
157 // array of icons currently shown in the system tray
158 size_t cIcons;
159 // number of icons in the pIcons array
160 size_t cIconsMax;
161 // maximum number of icons pIcons can fit
162 CHAR szToolTip[sizeof(((PSYSTRAYCTLDATA)0)->u.icon.szToolTip)];
163 // "static" buffer for the tooltip (XCenter requirement)
164 PVOID pvMemoryPool;
165 // memory pool for NOTIFYDATA structures
166
167} SYSTRAYDATA, *PSYSTRAYDATA;
168
169#define ICONARRAY_GROW 4
170 // number of element the icon array is increased by when there is no
171 // space for the newly added icon
172
173#define SERVER_MEMORYPOOL_SIZE 65536
174 // taking NOTIFYDATA size into account (<=32 B), this is enough for at
175 // least 2048 simultaneous notification messages, which (even taking
176 // slowly responsing clients into account) sounds sane since in most
177 // cases the structure is freed once it reaches the target event queue
178 // and before a message created as a copy of it is sent to the target
179 // window procedure
180
181#define TID_CHECKALIVE 1
182 // timer that checks if windows associated with icons are still alive
183#define TID_CHECKALIVE_TIMEOUT 2000 // ms
184 // how often to perform alive checks
185
186static ULONG WM_XST_CREATED = 0;
187 // identity of the WM_XST_CREATED message taken from the atom table
188static ULONG WM_XST_NOTIFY = 0;
189 // identity of the WM_XST_NOTIFY message taken from the atom table
190
191static ULONG QWL_USER_SERVER_DATA = 0;
192 // offset to the PXCENTERWIDGET pointer in the widget data array
193
194static
195VOID WgtXSysTrayUpdateAfterIconAddRemove(PXCENTERWIDGET pWidget);
196
197/* ******************************************************************
198 *
199 * XCenter widget class definition
200 *
201 ********************************************************************/
202
203/*
204 * This contains the name of the PM window class and
205 * the XCENTERWIDGETCLASS definition(s) for the widget
206 * class(es) in this DLL.
207 *
208 * The address of this structure (or an array of these
209 * structures, if there were several widget classes in
210 * this plugin) is returned by the "init" export
211 * (WgtInitModule).
212 */
213
214static const XCENTERWIDGETCLASS G_WidgetClasses[] =
215{
216 {
217 WNDCLASS_WIDGET_XSYSTRAY, // PM window class name
218 0, // additional flag, not used here
219 INTCLASS_WIDGET_XSYSTRAY, // internal widget class name
220 HUMANSTR_WIDGET_XSYSTRAY, // widget class name displayed to user
221 WGTF_UNIQUEGLOBAL | // widget class flags
222 WGTF_TOOLTIP,
223 NULL // no settings dialog
224 }
225};
226
227/* ******************************************************************
228 *
229 * Function imports from XFLDR.DLL
230 *
231 ********************************************************************/
232
233/*
234 * To reduce the size of the widget DLL, it can
235 * be compiled with the VAC subsystem libraries.
236 * In addition, instead of linking frequently
237 * used helpers against the DLL again, you can
238 * import them from XFLDR.DLL, whose module handle
239 * is given to you in the INITMODULE export.
240 *
241 * Note that importing functions from XFLDR.DLL
242 * is _not_ a requirement. We can't do this in
243 * this minimal sample anyway without having access
244 * to the full XWorkplace source code.
245 *
246 * If you want to know how you can import the useful
247 * functions from XFLDR.DLL to use them in your widget
248 * plugin, again, see src\widgets in the XWorkplace sources.
249 * The actual imports would then be made by WgtInitModule.
250 */
251
252// @todo the function declarations should become not necessary when we move into
253// XWorkplace. Let's hope the prototypes will not change until then. Let's also
254// pray that XFLDRxxx.DLL has the _Optlink calling convention for exports and
255// not the default __cdecl (which happens if it builds with GCC according to the
256// XWPENTRY definition in the current xwphelpers sources)
257
258#define XWPENTRY _Optlink
259
260typedef VOID XWPENTRY GPIHDRAW3DFRAME(HPS hps,
261 PRECTL prcl,
262 USHORT usWidth,
263 LONG lColorLeft,
264 LONG lColorRight);
265typedef GPIHDRAW3DFRAME *PGPIHDRAW3DFRAME;
266PGPIHDRAW3DFRAME pgpihDraw3DFrame = NULL;
267
268typedef struct _RESOLVEFUNCTION
269{
270 const char *pcszFunctionName;
271 PFN *ppFuncAddress;
272} RESOLVEFUNCTION, *PRESOLVEFUNCTION;
273RESOLVEFUNCTION G_aImports[] =
274{
275 { "gpihDraw3DFrame", (PFN*)&pgpihDraw3DFrame },
276};
277
278/* ******************************************************************
279 *
280 * Private widget instance data
281 *
282 ********************************************************************/
283
284// None presently. The samples in src\widgets in the XWorkplace
285// sources cleanly separate setup string data from other widget
286// instance data to allow for easier manipulation with settings
287// dialogs. We have skipped this for the minimal sample.
288
289/* ******************************************************************
290 *
291 * Widget setup management
292 *
293 ********************************************************************/
294
295// None presently. See above.
296
297/* ******************************************************************
298 *
299 * Widget settings dialog
300 *
301 ********************************************************************/
302
303// None currently. To see how a setup dialog can be done,
304// see the "window list" widget in the XWorkplace sources
305// (src\widgets\w_winlist.c).
306
307/* ******************************************************************
308 *
309 * Callbacks stored in XCENTERWIDGETCLASS
310 *
311 ********************************************************************/
312
313// If you implement a settings dialog, you must write a
314// "show settings dlg" function and store its function pointer
315// in XCENTERWIDGETCLASS.
316
317/* ******************************************************************
318 *
319 * Helper methods
320 *
321 ********************************************************************/
322
323/*
324 *@@ FreeIconData:
325 * Frees all members of the ICONDATA structure and resets them to 0.
326 */
327
328static
329VOID FreeIconData(PICONDATA pData)
330{
331 pData->hwnd = NULLHANDLE;
332 pData->usId = 0;
333 if (pData->hIcon != NULLHANDLE)
334 {
335 WinDestroyPointer(pData->hIcon);
336 pData->hIcon = NULLHANDLE;
337
338 }
339 pData->ulMsgId = 0;
340 if (pData->pszToolTip)
341 {
342 free(pData->pszToolTip);
343 pData->pszToolTip = NULL;
344 }
345}
346
347/*
348 *@@ FindIconData:
349 * Searches for the icon with the given identity. Returns NULL if not
350 * found.
351 */
352
353static
354PICONDATA FindIconData(PSYSTRAYDATA pSysTrayData,
355 HWND hwnd, // in: associated window handle
356 USHORT usId, // in: icon ID
357 size_t *pIdx) // out: index of the icon in the icon array
358 // (optional, may be NULL)
359{
360 size_t i;
361 for (i = 0; i < pSysTrayData->cIcons; ++i)
362 {
363 if (pSysTrayData->pIcons[i].hwnd == hwnd &&
364 pSysTrayData->pIcons[i].usId == usId)
365 {
366 if (pIdx)
367 *pIdx = i;
368 return &pSysTrayData->pIcons[i];
369 }
370 }
371
372 if (pIdx)
373 *pIdx = i;
374 return NULL;
375}
376
377/*
378 *@@ FindIconDataAtPt:
379 * Searches for the icon under the given point.
380 * Returns NULL if no icon found (e.g. the pad space).
381 *
382 * Refer to WgtPaint() for system tray geometry description.
383 */
384
385static
386PICONDATA FindIconDataAtPt(PXCENTERWIDGET pWidget,
387 PPOINTL pptl, // point coordinates (relative to systray)
388 size_t *pIdx) // out: index of the icon in the icon array
389 // (optional, may be NULL)
390{
391 PSYSTRAYDATA pSysTrayData = (PSYSTRAYDATA)pWidget->pUser;
392
393 SWP swp;
394 RECTL rcl;
395 BOOL bLeftToRight;
396 LONG y, lIconStep;
397 size_t i;
398
399 // start with invalid index index
400 if (pIdx)
401 *pIdx = pSysTrayData->cIcons;
402
403 WinQueryWindowPos(pWidget->hwndWidget, &swp);
404 WinQueryWindowRect(pWidget->pGlobals->hwndClient, &rcl);
405
406 y = (swp.cy - pSysTrayData->lIconHeight) / 2;
407 if (pptl->y < y || pptl->y >= y + pSysTrayData->lIconHeight)
408 return NULL; // hit pad space
409
410 // detect the direction
411 bLeftToRight = swp.x + swp.cx / 2 < (rcl.xRight / 2);
412
413 lIconStep = pSysTrayData->lIconWidth + pSysTrayData->lIconPad;
414
415 // which icon is that?
416 if (bLeftToRight)
417 {
418 i = pptl->x / lIconStep;
419 if (pptl->x % lIconStep < pSysTrayData->lIconPad)
420 return NULL; // hit pad space
421 }
422 else
423 {
424 i = (swp.cx - pptl->x - 1) / lIconStep;
425 if ((swp.cx - pptl->x - 1) % lIconStep < pSysTrayData->lIconPad)
426 return NULL; // hit pad space
427 }
428 if (i >= pSysTrayData->cIcons)
429 return NULL; // hit pad space
430
431 if (pIdx)
432 *pIdx = i;
433 return &pSysTrayData->pIcons[i];
434}
435
436static
437VOID FreeSysTrayData(PSYSTRAYDATA pSysTrayData)
438{
439 // destroy the server
440 if (pSysTrayData->hwndServer != NULLHANDLE)
441 {
442 WinDestroyWindow(pSysTrayData->hwndServer);
443 pSysTrayData->hwndServer = NULLHANDLE;
444 }
445
446 // free all system tray data
447 if (pSysTrayData->pvMemoryPool)
448 {
449 DosFreeMem(pSysTrayData->pvMemoryPool);
450 }
451 if (pSysTrayData->pIcons)
452 {
453 size_t i;
454 for (i = 0; i < pSysTrayData->cIcons; ++i)
455 FreeIconData(&pSysTrayData->pIcons[i]);
456 pSysTrayData->cIcons = 0;
457 free(pSysTrayData->pIcons);
458 pSysTrayData->pIcons = NULL;
459 }
460
461 free(pSysTrayData);
462}
463
464/*
465 *@@ AllocNotifyDataPtr:
466 * Allocates a SYSTRAYCTLDATA struct in the pool of shared memory on belalf
467 * of the client window identified by pIconData->hwnd.
468 *
469 * If there is no free space in the pool, it returns NULL. On success,
470 * fills in msg and mp1 fields of the returned structure using the
471 * information provided in pIconData.
472 *
473 * Note that the allocated structure is supposed to be freed using
474 * FreeNotifyDataPtr by the client-side API implementation in another
475 * process.
476 */
477static
478PNOTIFYDATA AllocNotifyDataPtr(PVOID pvMemoryPool, // in: memory pool base address
479 PICONDATA pIconData) // in: icon data
480{
481 // NOTE: we cannot use DosSubAllocMem() and friends since we want to be able
482 // to free blocks allocated to clients which death we detect but we cannot
483 // be sure about the layout of memory DosSub API uses. Therefore, we provide
484 // our own sub-allocation scheme which is rather simple because we need to
485 // allocate equally sized blocks (each is NOTIFYDATA struct). The memory
486 // pool is laid out as follows: MEMPOOLHDR followed by a number of
487 // MEMPOOLBLK.
488
489 APIRET arc;
490 PID pid;
491 TID tid;
492 PMEMPOOLHDR pHdr;
493 ULONG ulMax, ulNeedsCommit, ulNext, ulCurr;
494 PNOTIFYDATA pData;
495
496 LOGF(("pvMemoryPool %p\n", pvMemoryPool));
497 LOGF(("hwnd %lx\n", pIconData->hwnd));
498
499 if (!pIconData->bMemoryPoolGiven)
500 {
501 LOGF(("Giving memory pool to %lx\n", pIconData->hwnd));
502
503 arc = ERROR_INVALID_HANDLE;
504 if (WinQueryWindowProcess(pIconData->hwnd, &pid, &tid))
505 arc = DosGiveSharedMem(pvMemoryPool, pid, PAG_READ | PAG_WRITE);
506 if (arc != NO_ERROR)
507 return NULL;
508
509 pIconData->bMemoryPoolGiven = TRUE;
510 }
511
512 pHdr = (PMEMPOOLHDR)pvMemoryPool;
513
514 // maximum address that is still enough for a block
515 ulMax = pHdr->ulBeyond - sizeof(MEMPOOLBLK);
516
517 ulNeedsCommit = pHdr->ulNeedsCommit;
518 ulNext = pHdr->ulNext;
519 ulCurr = ulNext;
520
521 LOGF(("ulNeedsCommit %p\n", ulNeedsCommit));
522 LOGF(("ulNext %p\n", ulNext));
523
524 do
525 {
526 if (ulCurr >= ulNeedsCommit)
527 {
528 // commit more memory; it's OK two or more threads will do the same
529 DosSetMem((PVOID)ulNeedsCommit, 4096,
530 PAG_COMMIT | PAG_READ | PAG_WRITE);
531 // advance the address (only if nobody has already done so -- they
532 // could already commit more than we did)
533 __atomic_cmpxchg32((volatile uint32_t *)&pHdr->ulNeedsCommit,
534 ulNeedsCommit + 4096, ulNeedsCommit);
535 }
536
537 if (__atomic_cmpxchg32((volatile uint32_t *)ulCurr,
538 pIconData->hwnd, NULLHANDLE))
539 break;
540
541 ulCurr += sizeof(MEMPOOLBLK);
542 if (ulCurr > ulMax)
543 // start over
544 ulCurr = ((ULONG)pvMemoryPool) + sizeof(MEMPOOLHDR);
545
546 if (ulCurr == ulNext)
547 return NULL; // no free blocks!
548 }
549 while (1);
550
551 LOGF(("ulCurr %p\n", ulCurr));
552
553 // memorize a pointer to the new struct
554 pData = &((PMEMPOOLBLK)ulCurr)->NotifyData;
555
556 // advance to the next possibly free block
557 ulCurr += sizeof(MEMPOOLBLK);
558 if (ulCurr > ulMax)
559 // start over
560 ulCurr = ((ULONG)pvMemoryPool) + sizeof(MEMPOOLHDR);
561
562 // store the new next address until someone else has already done that
563 __atomic_cmpxchg32((volatile uint32_t *)&pHdr->ulNext, ulCurr, ulNext);
564
565 // fill in parts of the allocated NOTIFYDATA
566 memset(pData, 0, sizeof(*pData));
567
568 pData->msg = pIconData->ulMsgId;
569 pData->mp1 = MPFROMSHORT(pIconData->usId);
570
571 return pData;
572}
573
574/*
575 *@@ PostNotifyMsg:
576 * Posts WM_XST_NOTIFY to the given client window. Frees pNotifyData if
577 * posting fails.
578 */
579
580VOID PostNotifyMsg(PSYSTRAYDATA pSysTrayData, HWND hwnd,
581 PNOTIFYDATA pNotifyData)
582{
583 LOGF(("hwnd %lx\n", hwnd));
584 LOGF(("pNotifyData %p\n", pNotifyData));
585
586 if (!WinPostMsg(hwnd, WM_XST_NOTIFY, pNotifyData, pSysTrayData->pvMemoryPool))
587 {
588 LOGF(("WinPostMsg() failed, last error %lx\n", WinGetLastError(0)));
589 FreeNotifyDataPtr(pSysTrayData->pvMemoryPool, hwnd, pNotifyData);
590 }
591}
592
593/*
594 *@@ DrawPointer:
595 * Draws a pointer in a presentation space.
596 */
597
598static
599BOOL DrawPointer(HPS hps, LONG lx, LONG ly, HPOINTER hptrPointer, BOOL bMini)
600{
601 return WinDrawPointer(hps, lx, ly, hptrPointer, bMini ? DP_MINI : DP_NORMAL);
602 // @todo: for icons with real alpha, do manual alpha blending
603}
604
605/* ******************************************************************
606 *
607 * PM window class implementation
608 *
609 ********************************************************************/
610
611/*
612 * This code has the actual PM window class.
613 *
614 */
615
616/*
617 *@@ MwgtControl:
618 * implementation for WM_CONTROL in fnwpXSysTray.
619 *
620 * The XCenter communicates with widgets thru
621 * WM_CONTROL messages. At the very least, the
622 * widget should respond to XN_QUERYSIZE because
623 * otherwise it will be given some dumb default
624 * size.
625 */
626
627static
628BOOL WgtControl(PXCENTERWIDGET pWidget,
629 MPARAM mp1,
630 MPARAM mp2)
631{
632 PSYSTRAYDATA pSysTrayData = (PSYSTRAYDATA)pWidget->pUser;
633 BOOL brc = FALSE;
634
635 USHORT usID = SHORT1FROMMP(mp1),
636 usNotifyCode = SHORT2FROMMP(mp1);
637
638 // is this from the XCenter client?
639 if (usID == ID_XCENTER_CLIENT)
640 {
641 // yes:
642
643 switch (usNotifyCode)
644 {
645 /*
646 * XN_QUERYSIZE:
647 * XCenter wants to know our size.
648 */
649
650 case XN_QUERYSIZE:
651 {
652 PSIZEL pszl = (PSIZEL)mp2;
653 LONG pad = pSysTrayData->lIconPad;
654 size_t cnt = pSysTrayData->cIcons;
655 // desired width
656 if (cnt)
657 pszl->cx = pad + (pSysTrayData->lIconWidth + pad) * cnt;
658 else
659 pszl->cx = pad;
660 // desired minimum height
661 pszl->cy = pSysTrayData->lIconHeight + pad * 2;
662 brc = TRUE;
663 }
664 break;
665
666 }
667 }
668 else if (usID == ID_XCENTER_TOOLTIP)
669 {
670 PICONDATA pIconData;
671 POINTL ptl;
672
673 WinQueryMsgPos(pWidget->habWidget, &ptl);
674 // make the coordinates systray-relative
675 WinMapWindowPoints(HWND_DESKTOP, pWidget->hwndWidget, &ptl, 1);
676
677 pIconData = FindIconDataAtPt(pWidget, &ptl, NULL);
678
679 switch (usNotifyCode)
680 {
681 case TTN_NEEDTEXT:
682 {
683 LOGF(("TTN_NEEDTEXT\n"));
684
685 PTOOLTIPTEXT pttt = (PTOOLTIPTEXT)mp2;
686 pttt->ulFormat = TTFMT_PSZ;
687
688 if (!pIconData || !pIconData->pszToolTip)
689 {
690 pttt->pszText = pSysTrayData->cIcons ?
691 "Indicator area" : "Indicator area (empty)";
692 }
693 else
694 {
695 strncpy(pSysTrayData->szToolTip, pIconData->pszToolTip,
696 sizeof(pSysTrayData->szToolTip) - 1);
697 // be on the safe side
698 pSysTrayData->szToolTip[sizeof(pSysTrayData->szToolTip) - 1] = '\0';
699
700 pttt->pszText = pSysTrayData->szToolTip;
701 }
702
703 LOGF((" pszText '%s'\n", pttt->pszText));
704 }
705 break;
706
707 case TTN_SHOW:
708 if (pIconData)
709 pIconData->bIsToolTipShowing = TRUE;
710 break;
711
712 case TTN_POP:
713 if (pIconData)
714 pIconData->bIsToolTipShowing = FALSE;
715 break;
716 }
717 }
718
719 return brc;
720}
721
722/*
723 *@@ WgtPaint:
724 * implementation for WM_PAINT in fnwpXSysTray.
725 *
726 * Draws all the icons. If the widget's center is located to the left from
727 * the XCenter's center, icons go left to right. Otherwise, they go right
728 * to left.
729 *
730 * NOTE: This function must be keept in sync with FindIconDataAtPt() and
731 * SYSTRAYCMD_QUERYRECT in terms of system tray geometry.
732 */
733/*
734 +---------------------------+ p = lIconPad
735 | p | w = lIconWidth
736 | +-------+ +-------+ | h = lIconHeight
737 | p | w | p | w | p |
738 | | h| | h| |
739 | | | | | | If "Frame around statics" is on in XCenter
740 | +-------+ +-------+ | properties, then a 1 px 3D frame is drawn
741 | p | within the pad area. So, lIconPad must
742 +---------------------------+ be at least 2 px.
743 */
744
745static
746VOID WgtPaint(HWND hwnd,
747 PXCENTERWIDGET pWidget)
748{
749 PSYSTRAYDATA pSysTrayData = (PSYSTRAYDATA)pWidget->pUser;
750 HPS hps;
751 RECTL rclPaint;
752
753 if ((hps = WinBeginPaint(hwnd, NULLHANDLE, &rclPaint)))
754 {
755 SWP swp;
756 RECTL rcl;
757 BOOL bLeftToRight;
758 LONG x, y, lIconStep;
759 size_t i;
760
761 WinQueryWindowPos(hwnd, &swp);
762 WinQueryWindowRect(pWidget->pGlobals->hwndClient, &rcl);
763
764 // correct the paint area
765 // @todo find out why it exceeds the window bounds
766 if (rclPaint.xLeft < 0)
767 rclPaint.xLeft = 0;
768 if (rclPaint.xRight > swp.cx)
769 rclPaint.xRight = swp.cx;
770 if (rclPaint.yBottom < 0)
771 rclPaint.yBottom = 0;
772 if (rclPaint.yTop > swp.cy)
773 rclPaint.yTop = swp.cy;
774
775 LOGF(("rclPaint %d,%d-%d,%d\n",
776 rclPaint.xLeft, rclPaint.yBottom, rclPaint.xRight, rclPaint.yTop));
777
778 // switch HPS to RGB mode
779 GpiCreateLogColorTable(hps, 0, LCOLF_RGB, 0, 0, NULL);
780
781 // draw icons left to right if our center is closer to the left edge
782 // of XCenter and right to left otherwise
783 bLeftToRight = swp.x + swp.cx / 2 < (rcl.xRight / 2);
784
785 WinFillRect(hps, &rclPaint,
786 WinQuerySysColor(HWND_DESKTOP, SYSCLR_DIALOGBACKGROUND, 0));
787
788 if ((pWidget->pGlobals->flDisplayStyle & XCS_SUNKBORDERS))
789 {
790 rcl.xLeft = 0;
791 rcl.yBottom = 0;
792 rcl.xRight = swp.cx - 1;
793 rcl.yTop = swp.cy - 1;
794 pgpihDraw3DFrame(hps, &rcl, 1,
795 pWidget->pGlobals->lcol3DDark,
796 pWidget->pGlobals->lcol3DLight);
797 }
798
799 // always center the icon vertically (we may be given more height than
800 // we requested)
801 y = (swp.cy - pSysTrayData->lIconHeight) / 2;
802
803 if (bLeftToRight)
804 x = pSysTrayData->lIconPad;
805 else
806 x = swp.cx - pSysTrayData->lIconPad - pSysTrayData->lIconWidth;
807
808 lIconStep = pSysTrayData->lIconWidth + pSysTrayData->lIconPad;
809
810 // where to start from?
811 if (bLeftToRight)
812 {
813 i = rclPaint.xLeft / lIconStep;
814 x = pSysTrayData->lIconPad + i * lIconStep;
815 }
816 else
817 {
818 i = (swp.cx - rclPaint.xRight) / lIconStep;
819 x = swp.cx - (i + 1) * lIconStep;
820 // negate the step, for convenience
821 lIconStep = -lIconStep;
822 }
823
824 // draw as many icons as we can / need
825 for (; i < pSysTrayData->cIcons; ++i)
826 {
827 if (x >= rclPaint.xRight)
828 break;
829
830 // just leave an empty box if the icon is NULL, this is what
831 // Windows and Linux tray widgets do
832 if (pSysTrayData->pIcons[i].hIcon != NULLHANDLE)
833 DrawPointer(hps, x, y, pSysTrayData->pIcons[i].hIcon, DP_MINI);
834 x += lIconStep;
835 }
836
837 WinEndPaint(hps);
838 }
839}
840
841/*
842 *@@ WgtMouse:
843 * implementation for WM_BUTTONxyyy in fnwpXSysTray.
844 *
845 * Posts a notification to the window associated with the icon and returns
846 * TRUE if this mouse message is within the icon bounds. Otherwise returns
847 * FALSE.
848 *
849 * Refer to WgtPaint for more details about the widget geometry.
850 */
851
852static
853BOOL WgtMouse(HWND hwnd, ULONG msg, MRESULT mp1, MRESULT mp2,
854 PXCENTERWIDGET pWidget)
855{
856 PSYSTRAYDATA pSysTrayData = (PSYSTRAYDATA)pWidget->pUser;
857
858 POINTL ptl;
859
860 PICONDATA pIconData;
861 PNOTIFYDATA pNotifyData;
862
863 ptl.x = ((PPOINTS)&mp1)->x;
864 ptl.y = ((PPOINTS)&mp1)->y;
865
866 LOGF(("msg %x ptl %ld,%ld\n", msg, ptl.x, ptl.y));
867
868 pIconData = FindIconDataAtPt(pWidget, &ptl, NULL);
869 if (!pIconData)
870 return FALSE; // hit pad space
871
872 LOGF(("hwnd %x\n", pIconData->hwnd));
873 LOGF(("usId %d\n", pIconData->usId));
874 LOGF(("hIcon %x\n", pIconData->hIcon));
875
876 // make the coordinates global
877 WinMapWindowPoints(hwnd, HWND_DESKTOP, &ptl, 1);
878
879 // allocate a NOTIFYDATA struct
880 pNotifyData = AllocNotifyDataPtr(pSysTrayData->pvMemoryPool, pIconData);
881 if (!pNotifyData)
882 return FALSE;
883
884 switch (msg)
885 {
886 case WM_HSCROLL:
887 case WM_VSCROLL:
888 pNotifyData->mp1 += XST_IN_WHEEL << 16;
889 pNotifyData->u.WheelMsg.ulWheelMsg = msg;
890 pNotifyData->u.WheelMsg.ptsPointerPos.x = ptl.x;
891 pNotifyData->u.WheelMsg.ptsPointerPos.y = ptl.y;
892 pNotifyData->u.WheelMsg.usCmd = SHORT2FROMMP(mp2);
893 pNotifyData->mp2 = &pNotifyData->u.WheelMsg;
894 break;
895
896 case WM_CONTEXTMENU:
897 pNotifyData->mp1 += XST_IN_CONTEXT << 16;
898 pNotifyData->u.ContextMsg.ptsPointerPos.x = ptl.x;
899 pNotifyData->u.ContextMsg.ptsPointerPos.y = ptl.y;
900 pNotifyData->u.ContextMsg.fPointer = TRUE;
901 pNotifyData->mp2 = &pNotifyData->u.ContextMsg;
902 break;
903
904 default:
905 pNotifyData->mp1 += XST_IN_MOUSE << 16;
906 pNotifyData->u.MouseMsg.ulMouseMsg = msg;
907 pNotifyData->u.MouseMsg.ptsPointerPos.x = ptl.x;
908 pNotifyData->u.MouseMsg.ptsPointerPos.y = ptl.y;
909 pNotifyData->u.MouseMsg.fsHitTestRes = SHORT1FROMMP(mp2);
910 pNotifyData->u.MouseMsg.fsFlags = SHORT2FROMMP(mp2);
911 pNotifyData->mp2 = &pNotifyData->u.MouseMsg;
912 break;
913 }
914
915 PostNotifyMsg(pSysTrayData, pIconData->hwnd, pNotifyData);
916
917 return TRUE;
918}
919
920/*
921 *@@ fnwpXSysTray:
922 * window procedure for the Extended system tray widget class.
923 *
924 * There are a few rules which widget window procs
925 * must follow. See XCENTERWIDGETCLASS in center.h
926 * for details.
927 *
928 * Other than that, this is a regular window procedure
929 * which follows the basic rules for a PM window class.
930 */
931
932static
933MRESULT EXPENTRY fnwpXSysTray(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
934{
935 // get widget data from QWL_USER (stored there by WM_CREATE)
936 PXCENTERWIDGET pWidget = (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER);
937 // this ptr is valid after WM_CREATE
938
939 switch (msg)
940 {
941 /*
942 * WM_CREATE:
943 * as with all widgets, we receive a pointer to the
944 * XCENTERWIDGET in mp1, which was created for us.
945 *
946 * The first thing the widget MUST do on WM_CREATE
947 * is to store the XCENTERWIDGET pointer (from mp1)
948 * in the QWL_USER window word by calling:
949 *
950 * WinSetWindowPtr(hwnd, QWL_USER, mp1);
951 *
952 * We could use XCENTERWIDGET.pUser for allocating
953 * another private memory block for our own stuff,
954 * for example to be able to store fonts and colors.
955 * We ain't doing this in the minimal sample.
956 */
957
958 case WM_CREATE:
959 {
960 LOGF(("WM_CREATE\n"));
961
962 PSYSTRAYDATA pSysTrayData = NULL;
963 APIRET arc;
964
965 WinSetWindowPtr(hwnd, QWL_USER, mp1);
966 if ( (!(pWidget = (PXCENTERWIDGET)mp1))
967 || (!pWidget->pfnwpDefWidgetProc)
968 )
969 // shouldn't happen... stop window creation!!
970 return (MRESULT)TRUE;
971
972 pSysTrayData = malloc(sizeof(*pSysTrayData));
973 if (pSysTrayData == NULL)
974 return (MRESULT)TRUE;
975
976 // initialize the SYSTRAYDATA structure
977 memset(pSysTrayData, 0, sizeof(*pSysTrayData));
978 pSysTrayData->lIconWidth = WinQuerySysValue(HWND_DESKTOP, SV_CXICON) / 2;
979 pSysTrayData->lIconHeight = WinQuerySysValue(HWND_DESKTOP, SV_CYICON) / 2;
980 pSysTrayData->lIconPad = pSysTrayData->lIconHeight / 8;
981 pSysTrayData->cIconsMax = ICONARRAY_GROW;
982 pSysTrayData->pIcons = malloc(sizeof(*pSysTrayData->pIcons) *
983 pSysTrayData->cIconsMax);
984 if (pSysTrayData->pIcons == NULL)
985 {
986 FreeSysTrayData(pSysTrayData);
987 return (MRESULT)TRUE;
988 }
989 pSysTrayData->cIcons = 0;
990
991 // Allocate the memory pool for NOTIFYDATA structs (we don't
992 // PAG_COMMIT all memory, AllocNotifyDataPtr() will do so as needed)
993 arc = DosAllocSharedMem((PVOID)&pSysTrayData->pvMemoryPool, NULL,
994 SERVER_MEMORYPOOL_SIZE,
995 PAG_READ | PAG_WRITE | OBJ_GIVEABLE);
996 if (arc == NO_ERROR)
997 {
998 PMEMPOOLHDR pHdr = (PMEMPOOLHDR)pSysTrayData->pvMemoryPool;
999 arc = DosSetMem(pSysTrayData->pvMemoryPool, 4096,
1000 PAG_COMMIT | PAG_READ | PAG_WRITE);
1001 if (arc == NO_ERROR)
1002 {
1003 pHdr->ulBeyond = (ULONG)pSysTrayData->pvMemoryPool +
1004 SERVER_MEMORYPOOL_SIZE;
1005 pHdr->ulNeedsCommit = (ULONG)pSysTrayData->pvMemoryPool +
1006 4096;
1007 pHdr->ulNext = (ULONG)pSysTrayData->pvMemoryPool +
1008 sizeof(MEMPOOLHDR);
1009 }
1010 }
1011 if (arc != NO_ERROR)
1012 {
1013 FreeSysTrayData(pSysTrayData);
1014 return (MRESULT)TRUE;
1015 }
1016
1017 // create the "server" window (note that we pass the XCENTERWIDGET
1018 // pointer on to it)
1019 pSysTrayData->hwndServer =
1020 WinCreateWindow(HWND_DESKTOP, WNDCLASS_WIDGET_XSYSTRAY_SERVER,
1021 NULL, WS_MINIMIZED,
1022 0, 0, 0, 0,
1023 HWND_DESKTOP, HWND_BOTTOM,
1024 0, mp1, NULL);
1025 if (pSysTrayData->hwndServer == NULLHANDLE)
1026 {
1027 FreeSysTrayData(pSysTrayData);
1028 return (MRESULT)TRUE;
1029 }
1030
1031 pWidget->pUser = pSysTrayData;
1032
1033 // inform all interested parties that we are fired up
1034 WinBroadcastMsg(HWND_DESKTOP, WM_XST_CREATED,
1035 NULL, NULL, BMSG_POST);
1036
1037 return FALSE; // confirm success
1038 }
1039 break;
1040
1041 /*
1042 * WM_DESTROY:
1043 * clean up. This _must_ be passed on to
1044 * ctrDefWidgetProc.
1045 */
1046
1047 case WM_DESTROY:
1048 {
1049 LOGF(("WM_DESTROY\n"));
1050
1051 PSYSTRAYDATA pSysTrayData = (PSYSTRAYDATA)pWidget->pUser;
1052
1053 // stop the check alive timer
1054 WinStopTimer(pWidget->habWidget, pSysTrayData->hwndServer,
1055 TID_CHECKALIVE);
1056
1057 FreeSysTrayData(pSysTrayData);
1058 pWidget->pUser = NULL;
1059
1060 // We _MUST_ pass this on, or the default widget proc
1061 // cannot clean up, so break
1062 }
1063 break;
1064
1065 /*
1066 * WM_CONTROL:
1067 * process notifications/queries from the XCenter.
1068 */
1069
1070 case WM_CONTROL:
1071 return (MPARAM)WgtControl(pWidget, mp1, mp2);
1072 break;
1073
1074 /*
1075 * WM_PAINT:
1076 * well, paint the widget.
1077 */
1078
1079 case WM_PAINT:
1080 WgtPaint(hwnd, pWidget);
1081 return (MRESULT)TRUE;
1082 break;
1083
1084 /*
1085 * WM_PRESPARAMCHANGED:
1086 * A well-behaved widget would intercept
1087 * this and store fonts and colors.
1088 */
1089
1090 /* case WM_PRESPARAMCHANGED:
1091 break; */
1092
1093 /*
1094 * All mouse click and wheel events:
1095 * Note that we hide WM_CONTEXTMENU from XCenter when it is within
1096 * the icon bounds as it's a responsibility of the application
1097 * owning the icon to show it.
1098 */
1099
1100 case WM_BUTTON1UP:
1101 case WM_BUTTON1DOWN:
1102 case WM_BUTTON1CLICK:
1103 case WM_BUTTON1DBLCLK:
1104 case WM_BUTTON2UP:
1105 case WM_BUTTON2DOWN:
1106 case WM_BUTTON2CLICK:
1107 case WM_BUTTON2DBLCLK:
1108 case WM_BUTTON3UP:
1109 case WM_BUTTON3DOWN:
1110 case WM_BUTTON3CLICK:
1111 case WM_BUTTON3DBLCLK:
1112 case WM_CONTEXTMENU:
1113 case WM_VSCROLL:
1114 case WM_HSCROLL:
1115 {
1116 if (WgtMouse(hwnd, msg, mp1, mp2, pWidget))
1117 return (MRESULT)TRUE;
1118 // we didn't hit the icon, pass it on to XCenter
1119 }
1120 break;
1121
1122 default:
1123 break;
1124
1125 } // end switch(msg)
1126
1127 return pWidget->pfnwpDefWidgetProc(hwnd, msg, mp1, mp2);
1128}
1129
1130/*
1131 *@@ WgtXSysTrayUpdateAfterIconAddRemove:
1132 * Name says it all.
1133 */
1134
1135static
1136VOID WgtXSysTrayUpdateAfterIconAddRemove(PXCENTERWIDGET pWidget)
1137{
1138 PSYSTRAYDATA pSysTrayData = (PSYSTRAYDATA)pWidget->pUser;
1139
1140 if (pSysTrayData->cIcons == 1)
1141 {
1142 // start a timer to perform "is window alive" checks
1143 WinStartTimer(pWidget->habWidget, pSysTrayData->hwndServer,
1144 TID_CHECKALIVE,
1145 TID_CHECKALIVE_TIMEOUT);
1146 }
1147 else
1148 if (pSysTrayData->cIcons == 0)
1149 {
1150 // stop the check alive timer
1151 WinStopTimer(pWidget->habWidget, pSysTrayData->hwndServer,
1152 TID_CHECKALIVE);
1153 }
1154
1155 // ask XCenter to take our new size into account (this will also
1156 // invalidate us)
1157 WinPostMsg(pWidget->pGlobals->hwndClient,
1158 XCM_REFORMAT,
1159 (MPARAM)XFMF_GETWIDGETSIZES,
1160 0);
1161}
1162
1163/*
1164 *@@ WgtXSysTrayControl:
1165 * implementation for WM_XST_CONTROL in fnwpXSysTrayServer.
1166 *
1167 * Serves as an entry point for all client-side API requests to the
1168 * Extended system tray.
1169 *
1170 * Note that this message is being sent from another process which is
1171 * awaiting for an answer, so it must return as far as possible (by
1172 * rescheduling any potentially long term operation for another cycle).
1173 */
1174
1175static
1176ULONG WgtXSysTrayControl(HWND hwnd, PXCENTERWIDGET pWidget,
1177 PSYSTRAYCTLDATA pCtlData)
1178{
1179 BOOL brc = FALSE;
1180 ULONG xrc = XST_FAIL;
1181
1182 PSYSTRAYDATA pSysTrayData = (PSYSTRAYDATA)pWidget->pUser;
1183
1184 switch (pCtlData->ulCommand)
1185 {
1186 case SYSTRAYCMD_GETVERSION:
1187 {
1188 LOGF(("SYSTRAYCMD_GETVERSION\n"));
1189
1190 pCtlData->bAcknowledged = TRUE;
1191 pCtlData->u.version.ulMajor = XSYSTRAY_VERSION_MAJOR;
1192 pCtlData->u.version.ulMinor = XSYSTRAY_VERSION_MINOR;
1193 pCtlData->u.version.ulRevision = XSYSTRAY_VERSION_REVISION;
1194 xrc = XST_OK;
1195 }
1196 break;
1197
1198 case SYSTRAYCMD_ADDICON:
1199 {
1200 POINTERINFO Info;
1201 HPOINTER hIcon = NULLHANDLE;
1202 size_t i;
1203 PICONDATA pData;
1204
1205 LOGF(("SYSTRAYCMD_ADDICON\n"));
1206 LOGF((" hwnd %x\n", pCtlData->hwndSender));
1207 LOGF((" usId %d\n", pCtlData->u.icon.usId));
1208 LOGF((" hIcon %x\n", pCtlData->u.icon.hIcon));
1209 LOGF((" szToolTip '%s'\n", pCtlData->u.icon.szToolTip));
1210
1211 pCtlData->bAcknowledged = TRUE;
1212
1213 // make a private copy of the provided icon (it will get lost after
1214 // we return from this message)
1215 if (pCtlData->u.icon.hIcon != NULLHANDLE)
1216 {
1217 brc = WinQueryPointerInfo(pCtlData->u.icon.hIcon, &Info);
1218 if (!brc)
1219 break;
1220 hIcon = WinCreatePointerIndirect(HWND_DESKTOP, &Info);
1221 if (hIcon == NULLHANDLE)
1222 break;
1223 }
1224
1225 pData = FindIconData(pSysTrayData, pCtlData->hwndSender,
1226 pCtlData->u.icon.usId, &i);
1227 if (pData)
1228 {
1229 LOGF((" Replacing with hIcon %x\n", hIcon));
1230
1231 // try update the tooltip first
1232 free(pData->pszToolTip);
1233 pData->pszToolTip = NULL;
1234 if (pCtlData->u.icon.szToolTip[0] != '\0')
1235 {
1236 pData->pszToolTip = strdup(pCtlData->u.icon.szToolTip);
1237 if (!pData->pszToolTip)
1238 {
1239 if (hIcon != NULLHANDLE)
1240 WinDestroyPointer(hIcon);
1241 break;
1242 }
1243 }
1244
1245 if (pData->bIsToolTipShowing)
1246 {
1247 if (pData->pszToolTip)
1248 // update the tooltip on screen
1249 WinSendMsg(pWidget->pGlobals->hwndTooltip,
1250 TTM_UPDATETIPTEXT,
1251 (MPARAM)pData->pszToolTip, 0);
1252 else
1253 // hide the tooltip
1254 WinSendMsg(pWidget->pGlobals->hwndTooltip,
1255 TTM_SHOWTOOLTIPNOW,
1256 (MPARAM)FALSE, 0);
1257 }
1258
1259 // now update the icon
1260 if (pData->hIcon != NULLHANDLE)
1261 WinDestroyPointer(pData->hIcon);
1262 pData->hIcon = hIcon;
1263 pData->ulMsgId = pCtlData->u.icon.ulMsgId;
1264
1265 // we didn't change the number of icons so simply invalidate
1266 WinInvalidateRect(pWidget->hwndWidget, NULL, FALSE);
1267
1268 xrc = XST_REPLACED;
1269 }
1270 else
1271 {
1272 LOGF((" Adding new hIcon %x\n", hIcon));
1273
1274 if (pSysTrayData->cIcons == pSysTrayData->cIconsMax)
1275 {
1276 PICONDATA pNewIcons;
1277
1278 LOGF((" Allocating more memory (new icon count is %u)!\n",
1279 pSysTrayData->cIcons + 1));
1280
1281 pNewIcons = realloc(pSysTrayData->pIcons,
1282 sizeof(*pSysTrayData->pIcons) *
1283 pSysTrayData->cIconsMax + ICONARRAY_GROW);
1284 if (pNewIcons == NULL)
1285 {
1286 if (hIcon != NULLHANDLE)
1287 WinDestroyPointer(hIcon);
1288 break;
1289 }
1290
1291 pSysTrayData->pIcons = pNewIcons;
1292 pSysTrayData->cIconsMax += ICONARRAY_GROW;
1293 }
1294
1295 i = pSysTrayData->cIcons;
1296
1297 pData = &pSysTrayData->pIcons[i];
1298 memset(pData, 0, sizeof(*pData));
1299
1300 pData->hwnd = pCtlData->hwndSender;
1301 pData->usId = pCtlData->u.icon.usId;
1302 pData->hIcon = hIcon;
1303 pData->ulMsgId = pCtlData->u.icon.ulMsgId;
1304
1305 if (pCtlData->u.icon.szToolTip[0] != '\0')
1306 {
1307 pData->pszToolTip = strdup(pCtlData->u.icon.szToolTip);
1308 if (!pData->pszToolTip)
1309 {
1310 if (hIcon != NULLHANDLE)
1311 WinDestroyPointer(hIcon);
1312 break;
1313 }
1314 }
1315
1316 ++pSysTrayData->cIcons;
1317
1318 WgtXSysTrayUpdateAfterIconAddRemove(pWidget);
1319
1320 xrc = XST_OK;
1321 }
1322 }
1323 break;
1324
1325 case SYSTRAYCMD_REPLACEICON:
1326 {
1327 POINTERINFO Info;
1328 HPOINTER hIcon = NULLHANDLE;
1329 size_t i;
1330 PICONDATA pData;
1331
1332 LOGF(("SYSTRAYCMD_REPLACEICON\n"));
1333 LOGF((" hwnd %x\n", pCtlData->hwndSender));
1334 LOGF((" usId %d\n", pCtlData->u.icon.usId));
1335
1336 pCtlData->bAcknowledged = TRUE;
1337
1338 // make a private copy of the provided icon (it will get lost after
1339 // we return from this message)
1340 if (pCtlData->u.icon.hIcon != NULLHANDLE)
1341 {
1342 brc = WinQueryPointerInfo(pCtlData->u.icon.hIcon, &Info);
1343 if (!brc)
1344 break;
1345 hIcon = WinCreatePointerIndirect(HWND_DESKTOP, &Info);
1346 if (hIcon == NULLHANDLE)
1347 break;
1348 }
1349
1350 pData = FindIconData(pSysTrayData, pCtlData->hwndSender,
1351 pCtlData->u.icon.usId, &i);
1352 if (pData)
1353 {
1354 LOGF((" Replacing with hIcon %x\n", hIcon));
1355
1356 if (pData->hIcon != NULLHANDLE)
1357 WinDestroyPointer(pData->hIcon);
1358 pData->hIcon = hIcon;
1359 pData->ulMsgId = pCtlData->u.icon.ulMsgId;
1360
1361 // we didn't change the number of icons so simply invalidate
1362 WinInvalidateRect(pWidget->hwndWidget, NULL, FALSE);
1363
1364 xrc = XST_OK;
1365 }
1366 else
1367 LOGF((" Icon not found!\n"));
1368 }
1369 break;
1370
1371 case SYSTRAYCMD_REMOVEICON:
1372 {
1373 size_t i;
1374 PICONDATA pData;
1375
1376 LOGF(("SYSTRAYCMD_REMOVEICON\n"));
1377 LOGF((" hwnd %x\n", pCtlData->hwndSender));
1378 LOGF((" usId %d\n", pCtlData->u.icon.usId));
1379
1380 pCtlData->bAcknowledged = TRUE;
1381
1382 pData = FindIconData(pSysTrayData, pCtlData->hwndSender,
1383 pCtlData->u.icon.usId, &i);
1384 if (pData)
1385 {
1386 LOGF((" Removing hIcon %x\n", pData->hIcon));
1387
1388 FreeIconData(pData);
1389
1390 --pSysTrayData->cIcons;
1391 if (pSysTrayData->cIcons > 0)
1392 {
1393 memcpy(&pSysTrayData->pIcons[i],
1394 &pSysTrayData->pIcons[i + 1],
1395 sizeof(*pSysTrayData->pIcons) * (pSysTrayData->cIcons - i));
1396 }
1397
1398 WgtXSysTrayUpdateAfterIconAddRemove(pWidget);
1399
1400 xrc = XST_OK;
1401 }
1402 else
1403 LOGF((" Icon not found!\n"));
1404 }
1405 break;
1406
1407 case SYSTRAYCMD_SETTOOLTIP:
1408 {
1409 size_t i;
1410 PICONDATA pData;
1411
1412 LOGF(("SYSTRAYCMD_SETTOOLTIP\n"));
1413 LOGF((" hwnd %x\n", pCtlData->hwndSender));
1414 LOGF((" usId %d\n", pCtlData->u.icon.usId));
1415
1416 pCtlData->bAcknowledged = TRUE;
1417
1418 pData = FindIconData(pSysTrayData, pCtlData->hwndSender,
1419 pCtlData->u.icon.usId, &i);
1420 if (pData)
1421 {
1422 LOGF((" Replacing with szToolTip '%s'\n",
1423 pCtlData->u.icon.szToolTip));
1424
1425 free(pData->pszToolTip);
1426 pData->pszToolTip = NULL;
1427 if (pCtlData->u.icon.szToolTip[0] != '\0')
1428 {
1429 pData->pszToolTip = strdup(pCtlData->u.icon.szToolTip);
1430 if (!pData->pszToolTip)
1431 break;
1432 }
1433
1434 if (pData->bIsToolTipShowing)
1435 {
1436 if (pData->pszToolTip)
1437 // update the tooltip on screen
1438 WinSendMsg(pWidget->pGlobals->hwndTooltip,
1439 TTM_UPDATETIPTEXT,
1440 (MPARAM)pData->pszToolTip, 0);
1441 else
1442 // hide the tooltip
1443 WinSendMsg(pWidget->pGlobals->hwndTooltip,
1444 TTM_SHOWTOOLTIPNOW,
1445 (MPARAM)FALSE, 0);
1446 }
1447
1448 xrc = XST_OK;
1449 }
1450 else
1451 LOGF((" Icon not found!\n"));
1452 }
1453 break;
1454
1455 case SYSTRAYCMD_QUERYRECT:
1456 {
1457 size_t i;
1458 PICONDATA pData;
1459
1460 LOGF(("SYSTRAYCMD_QUERYRECT\n"));
1461 LOGF((" hwnd %x\n", pCtlData->hwndSender));
1462 LOGF((" usId %d\n", pCtlData->u.icon.usId));
1463
1464 pCtlData->bAcknowledged = TRUE;
1465
1466 pData = FindIconData(pSysTrayData, pCtlData->hwndSender,
1467 pCtlData->u.icon.usId, &i);
1468 if (pData)
1469 {
1470 // Refer to FindIconDataAtPt() for details
1471
1472 SWP swp;
1473 RECTL rcl;
1474 BOOL bLeftToRight;
1475 LONG y, lIconStep;
1476
1477 WinQueryWindowPos(pWidget->hwndWidget, &swp);
1478 WinQueryWindowRect(pWidget->pGlobals->hwndClient, &rcl);
1479
1480 y = (swp.cy - pSysTrayData->lIconHeight) / 2;
1481
1482 // detect the direction
1483 bLeftToRight = swp.x + swp.cx / 2 < (rcl.xRight / 2);
1484
1485 lIconStep = pSysTrayData->lIconWidth + pSysTrayData->lIconPad;
1486
1487 pCtlData->u.rect.rclIcon.yBottom = y;
1488 pCtlData->u.rect.rclIcon.yTop = y + pSysTrayData->lIconHeight;
1489
1490 if (bLeftToRight)
1491 {
1492 pCtlData->u.rect.rclIcon.xLeft =
1493 pSysTrayData->lIconPad + (lIconStep) * i;
1494 pCtlData->u.rect.rclIcon.xRight =
1495 pCtlData->u.rect.rclIcon.xLeft +
1496 pSysTrayData->lIconWidth;
1497 }
1498 else
1499 {
1500 pCtlData->u.rect.rclIcon.xLeft =
1501 swp.cx - (lIconStep) * (i + 1);
1502 pCtlData->u.rect.rclIcon.xRight =
1503 pCtlData->u.rect.rclIcon.xLeft +
1504 pSysTrayData->lIconWidth;
1505 }
1506
1507 // convert to screen coordinates
1508 WinMapWindowPoints(pWidget->hwndWidget, HWND_DESKTOP,
1509 (PPOINTL)&pCtlData->u.rect.rclIcon, 2);
1510 xrc = XST_OK;
1511 }
1512 else
1513 LOGF((" Icon not found!\n"));
1514 }
1515 break;
1516
1517 default:
1518 break;
1519 }
1520
1521 LOGF(("return %d (WinGetLastError is %x)\n",
1522 xrc, WinGetLastError(pWidget->habWidget)));
1523
1524 return xrc;
1525}
1526
1527/*
1528 *@@ WgtXSysTrayTimer:
1529 * implementation for WM_TIMER in fnwpXSysTrayServer.
1530 */
1531
1532static
1533VOID WgtXSysTrayTimer(HWND hwnd, PXCENTERWIDGET pWidget,
1534 USHORT usTimerId)
1535{
1536 PSYSTRAYDATA pSysTrayData = (PSYSTRAYDATA)pWidget->pUser;
1537 PMEMPOOLHDR pMemPoolHdr = (PMEMPOOLHDR)pSysTrayData->pvMemoryPool;
1538 PMEMPOOLBLK pMemPoolBlk;
1539 ULONG ulMemPoolMax;
1540
1541 if (usTimerId == TID_CHECKALIVE)
1542 {
1543 // check if windows associated with the icons are still alive
1544 // and remove those icons whose windows are invalid
1545 BOOL bAnyDead = FALSE;
1546 size_t i;
1547 for (i = 0; i < pSysTrayData->cIcons; ++i)
1548 {
1549 if (!WinIsWindow(pWidget->habWidget, pSysTrayData->pIcons[i].hwnd))
1550 {
1551 PICONDATA pData = &pSysTrayData->pIcons[i];
1552
1553 LOGF(("Removing icon of dead window!\n"));
1554 LOGF((" hwnd %x\n", pData->hwnd));
1555 LOGF((" usId %ld\n", pData->usId));
1556 LOGF((" hIcon %x\n", pData->hIcon));
1557
1558 // free memory blocks from the pool allocated for this client
1559 ulMemPoolMax = pMemPoolHdr->ulBeyond;
1560 if (ulMemPoolMax > pMemPoolHdr->ulNeedsCommit)
1561 ulMemPoolMax = pMemPoolHdr->ulNeedsCommit;
1562 ulMemPoolMax -= sizeof(MEMPOOLBLK);
1563
1564 pMemPoolBlk = pMemPoolHdr->aBlocks;
1565 while ((ULONG)pMemPoolBlk <= ulMemPoolMax)
1566 {
1567 if (pMemPoolBlk->hwnd == pData->hwnd)
1568 {
1569 LOGF((" freeing memory block %p\n", pMemPoolBlk));
1570 FreeNotifyDataPtr(pSysTrayData->pvMemoryPool,
1571 pData->hwnd,
1572 &pMemPoolBlk->NotifyData);
1573 }
1574 ++pMemPoolBlk;
1575 }
1576
1577 bAnyDead = TRUE;
1578 FreeIconData(pData);
1579 // pData->hwnd is NULLHANDLE here
1580 }
1581 }
1582
1583 if (bAnyDead)
1584 {
1585 // compact the icon array
1586 i = 0;
1587 while (i < pSysTrayData->cIcons)
1588 {
1589 if (pSysTrayData->pIcons[i].hwnd == NULLHANDLE)
1590 {
1591 --pSysTrayData->cIcons;
1592 if (pSysTrayData->cIcons > 0)
1593 {
1594 memcpy(&pSysTrayData->pIcons[i],
1595 &pSysTrayData->pIcons[i + 1],
1596 sizeof(*pSysTrayData->pIcons) * (pSysTrayData->cIcons - i));
1597 }
1598 }
1599 else
1600 ++i;
1601 }
1602
1603 WgtXSysTrayUpdateAfterIconAddRemove(pWidget);
1604 }
1605 }
1606}
1607
1608/*
1609 *@@ fnwpXSysTrayServer:
1610 * window procedure for the Extended system tray server window class.
1611 *
1612 * A separate "server" class is necessary because we need a CS_FRAME
1613 * top-level window for DDE (which we need to support to be backward
1614 * compatible with the System tray wdget from the SysTray/WPS package) and
1615 * also to make ourselves discoverable for the client-side implementation
1616 * of our new extended API (which queries the class of each top-level
1617 * window to find the system tray server).
1618 */
1619
1620static
1621MRESULT EXPENTRY fnwpXSysTrayServer(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
1622{
1623 // get widget data from QWL_USER_SERVER_DATA (stored there by WM_CREATE)
1624 PXCENTERWIDGET pWidget =
1625 (PXCENTERWIDGET)WinQueryWindowPtr(hwnd, QWL_USER_SERVER_DATA);
1626 // this ptr is valid after WM_CREATE
1627
1628 switch (msg)
1629 {
1630 case WM_CREATE:
1631 LOGF(("WM_CREATE\n"));
1632 WinSetWindowPtr(hwnd, QWL_USER_SERVER_DATA, mp1);
1633 return FALSE; // confirm success
1634 break;
1635
1636 case WM_DESTROY:
1637 LOGF(("WM_DESTROY\n"));
1638 break;
1639
1640 /*
1641 * WM_XST_CONTROL:
1642 * This is the message sent to us by the clinet-side implementation
1643 * of the API to request some function. mp1 points to a
1644 * SYSTRAYCTLDATA structure.
1645 */
1646
1647 case WM_XST_CONTROL:
1648 return (MRESULT)WgtXSysTrayControl(hwnd, pWidget,
1649 (PSYSTRAYCTLDATA)mp1);
1650 break;
1651
1652 /*
1653 * WM_TIMER:
1654 * timer event.
1655 */
1656
1657 case WM_TIMER:
1658 WgtXSysTrayTimer(hwnd, pWidget, SHORT1FROMMP(mp1));
1659 return (MRESULT)TRUE;
1660 break;
1661
1662 default:
1663 break;
1664 } // end switch(msg)
1665
1666 return WinDefWindowProc(hwnd, msg, mp1, mp2);
1667}
1668
1669/* ******************************************************************
1670 *
1671 * Exported procedures
1672 *
1673 ********************************************************************/
1674
1675/*
1676 *@@ WgtInitModule:
1677 * required export with ordinal 1, which must tell
1678 * the XCenter how many widgets this DLL provides,
1679 * and give the XCenter an array of XCENTERWIDGETCLASS
1680 * structures describing the widgets.
1681 *
1682 * With this call, you are given the module handle of
1683 * XFLDR.DLL. For convenience, and if you have the full
1684 * XWorkplace source code, you could resolve imports
1685 * for some useful functions which are exported thru
1686 * src\shared\xwp.def. We don't do this here.
1687 *
1688 * This function must also register the PM window classes
1689 * which are specified in the XCENTERWIDGETCLASS array
1690 * entries. For this, you are given a HAB which you
1691 * should pass to WinRegisterClass. For the window
1692 * class style (4th param to WinRegisterClass),
1693 * you should specify
1694 *
1695 + CS_PARENTCLIP | CS_SIZEREDRAW | CS_SYNCPAINT
1696 *
1697 * Your widget window _will_ be resized by the XCenter,
1698 * even if you're not planning it to be.
1699 *
1700 * This function only gets called _once_ when the widget
1701 * DLL has been successfully loaded by the XCenter. If
1702 * there are several instances of a widget running (in
1703 * the same or in several XCenters), this function does
1704 * not get called again. However, since the XCenter unloads
1705 * the widget DLLs again if they are no longer referenced
1706 * by any XCenter, this might get called again when the
1707 * DLL is re-loaded.
1708 *
1709 * There will ever be only one load occurence of the DLL.
1710 * The XCenter manages sharing the DLL between several
1711 * XCenters. As a result, it doesn't matter if the DLL
1712 * has INITINSTANCE etc. set or not.
1713 *
1714 * If this returns 0, this is considered an error, and the
1715 * DLL will be unloaded again immediately.
1716 *
1717 * If this returns any value > 0, *ppaClasses must be
1718 * set to a static array (best placed in the DLL's
1719 * global data) of XCENTERWIDGETCLASS structures,
1720 * which must have as many entries as the return value.
1721 */
1722
1723ULONG EXPENTRY WgtInitModule(HAB hab, // XCenter's anchor block
1724 HMODULE hmodPlugin, // module handle of the widget DLL
1725 HMODULE hmodXFLDR, // XFLDR.DLL module handle
1726 PCXCENTERWIDGETCLASS *ppaClasses,
1727 PSZ pszErrorMsg) // if 0 is returned, 500 bytes of error msg
1728{
1729 ULONG ulrc = 0, ul = 0;
1730 CLASSINFO ClassInfo;
1731
1732 LOGF(("hmodPlugin %x\n", hmodPlugin));
1733
1734 do
1735 {
1736 // resolve imports from XFLDR.DLL (this is basically
1737 // a copy of the doshResolveImports code, but we can't
1738 // use that before resolving...)
1739 for (ul = 0;
1740 ul < sizeof(G_aImports) / sizeof(G_aImports[0]);
1741 ul++)
1742 {
1743 APIRET arc;
1744 if ((arc = DosQueryProcAddr(hmodXFLDR,
1745 0, // ordinal, ignored
1746 (PSZ)G_aImports[ul].pcszFunctionName,
1747 G_aImports[ul].ppFuncAddress))
1748 != NO_ERROR)
1749 {
1750 snprintf(pszErrorMsg, 500,
1751 "Import %s failed with %ld.",
1752 G_aImports[ul].pcszFunctionName, arc);
1753 break;
1754 }
1755 }
1756 if (ul < sizeof(G_aImports) / sizeof(G_aImports[0]))
1757 break;
1758
1759 // register our PM window class
1760 if (!WinRegisterClass(hab,
1761 WNDCLASS_WIDGET_XSYSTRAY,
1762 fnwpXSysTray,
1763 CS_PARENTCLIP | CS_SIZEREDRAW | CS_SYNCPAINT,
1764 sizeof(PVOID))
1765 // extra memory to reserve for QWL_USER
1766 )
1767 {
1768 snprintf(pszErrorMsg, 500,
1769 "WinRegisterClass(%s) failed with %lX.",
1770 WNDCLASS_WIDGET_XSYSTRAY, WinGetLastError(hab));
1771 break;
1772 }
1773
1774 // get the window data size for the WC_FRAME class (any window class
1775 // that specifies CS_FRAME must have at least this number, otherise
1776 // WinRegisterClass returns 0x1003
1777 if (!WinQueryClassInfo(hab, (PSZ)WC_FRAME, &ClassInfo))
1778 break;
1779 QWL_USER_SERVER_DATA = ClassInfo.cbWindowData;
1780
1781 if (!WinRegisterClass(hab,
1782 WNDCLASS_WIDGET_XSYSTRAY_SERVER,
1783 fnwpXSysTrayServer,
1784 CS_FRAME,
1785 QWL_USER_SERVER_DATA + sizeof(PVOID))
1786 // extra memory to reserve for QWL_USER
1787 )
1788 {
1789 // error registering class: report error then
1790 snprintf(pszErrorMsg, 500,
1791 "WinRegisterClass(%s) failed with %lX",
1792 WNDCLASS_WIDGET_XSYSTRAY_SERVER, WinGetLastError(hab));
1793 break;
1794 }
1795
1796 if (WM_XST_CREATED == 0)
1797 WM_XST_CREATED = WinAddAtom(WinQuerySystemAtomTable(),
1798 WM_XST_CREATED_ATOM);
1799 if (WM_XST_NOTIFY == 0)
1800 WM_XST_NOTIFY = WinAddAtom(WinQuerySystemAtomTable(),
1801 WM_XST_NOTIFY_ATOM);
1802
1803 // no error:
1804 // return widget classes array
1805 *ppaClasses = G_WidgetClasses;
1806
1807 // return no. of classes in this DLL (one here):
1808 ulrc = sizeof(G_WidgetClasses) / sizeof(G_WidgetClasses[0]);
1809 }
1810 while (0);
1811
1812 LOGF(("pszErrorMsg '%s'\n", pszErrorMsg));
1813 LOGF(("ulrc %d\n", ulrc));
1814
1815 return ulrc;
1816}
1817
1818/*
1819 *@@ WgtUnInitModule:
1820 * optional export with ordinal 2, which can clean
1821 * up global widget class data.
1822 *
1823 * This gets called by the XCenter right before
1824 * a widget DLL gets unloaded. Note that this
1825 * gets called even if the "init module" export
1826 * returned 0 (meaning an error) and the DLL
1827 * gets unloaded right away.
1828 */
1829
1830VOID EXPENTRY WgtUnInitModule(VOID)
1831{
1832 LOGF(("\n"));
1833}
1834
1835/*
1836 *@@ WgtQueryVersion:
1837 * this new export with ordinal 3 can return the
1838 * XWorkplace version number which is required
1839 * for this widget to run. For example, if this
1840 * returns 0.9.10, this widget will not run on
1841 * earlier XWorkplace versions.
1842 *
1843 * NOTE: This export was mainly added because the
1844 * prototype for the "Init" export was changed
1845 * with V0.9.9. If this returns 0.9.9, it is
1846 * assumed that the INIT export understands
1847 * the new FNWGTINITMODULE_099 format (see center.h).
1848 */
1849
1850VOID EXPENTRY WgtQueryVersion(PULONG pulMajor,
1851 PULONG pulMinor,
1852 PULONG pulRevision)
1853{
1854 *pulMajor = 0;
1855 *pulMinor = 9;
1856 *pulRevision = 9;
1857}
1858
Note: See TracBrowser for help on using the repository browser.