source: trunk/src/3rdparty/os2/xsystray/apilib/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.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 15.3 KB
Line 
1
2/*
3 *@@sourcefile xsystray.c:
4 * Extended system tray widget for XCenter/eCenter.
5 *
6 * Implementation of the public API.
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#define OS2EMX_PLAIN_CHAR
22#define INCL_DOSERRORS
23#define INCL_DOSPROCESS
24#define INCL_WINWINDOWMGR
25#define INCL_WINERRORS
26#define INCL_WINATOM
27#define INCL_WINPOINTERS
28#define INCL_WINHOOKS
29#define INCL_WINWINDOWMGR
30#include <os2.h>
31
32#define XSTAPI_IMPL
33#include "xsystray.h"
34
35//#define ENABLE_LOG_TO "c:\\xsystray_api.dbg"
36
37#include "w_xsystray.h"
38
39#include <string.h>
40#include <sys/builtin.h> // atomics
41#include <sys/fmutex.h> // fast mutex
42#include <sys/smutex.h> // simple mutex
43
44static ULONG WM_XST_CREATED = 0;
45 // identity of the WM_XST_CREATED message taken from the atom table
46static ULONG WM_XST_NOTIFY = 0;
47 // identity of the WM_XST_NOTIFY message taken from the atom table
48
49static
50volatile HWND G_hwndSysTray = NULLHANDLE;
51 // window handle of the system tray server
52
53static
54volatile PVOID G_pvMemoryPool = NULL;
55 // shared memory pool for SYSTRAYCTLDATA structs used by
56 // WM_XST_CONTROL messages. Note that once allocated, this memory
57 // is never freed: it is intentional since the memory is assumed
58 // to be always in need and that the system will free it when the
59 // application terminates
60
61#define CLIENT_MEMORYPOOL_SIZE 65536
62 // taking SYSTRAYCTLDATA size into account (<=1024 B), this is enough
63 // for at least 64 threads sending WM_XST_CONTROL simultaneously, which
64 // sounds sane
65
66typedef struct
67{
68 HMQ hmq;
69 unsigned cRefs;
70} HMQREFS, *PHMQREFS;
71
72static PHMQREFS G_pHmqRefs = NULL;
73 // array of references to each HMQ that we make
74static size_t G_cHmqRefs = 0;
75 // number of elements in G_pHmqRefs
76static size_t G_cHmqRefsMax = 0;
77 // maximum number of elements in G_pHmqRefs
78static _fmutex G_fmtx;
79 // fast mutex to protect
80static _smutex G_smtx = 0;
81 // simple mutex for xstAddSysTrayIcon()
82#define HMQREFS_GROW 4
83 // how fast G_pHmqRefs grows when more space is necessary
84
85// @todo to be on the safe side with casting in __atomic_cmpxchg32() we need
86// compile-time assertions like this:
87// AssertCompile(sizeof(uint32_t) == sizeof(HWND));
88// AssertCompile(sizeof(uint32_t) == sizeof(PVOID));
89
90static HWND FindSysTrayServerWindow()
91{
92 char buf[sizeof(WNDCLASS_WIDGET_XSYSTRAY_SERVER) + 1];
93 HWND hwnd;
94 HENUM henum = WinBeginEnumWindows(HWND_DESKTOP);
95 while ((hwnd = WinGetNextWindow(henum)) != NULLHANDLE)
96 {
97 LONG len = WinQueryClassName(hwnd, sizeof(buf), buf);
98 buf[len] = '\0';
99 if (strcmp(WNDCLASS_WIDGET_XSYSTRAY_SERVER, buf) == 0)
100 break;
101 }
102 WinEndEnumWindows(henum);
103
104 return hwnd;
105}
106
107static ULONG SendSysTrayCtlMsg(PSYSTRAYCTLDATA pData)
108{
109 APIRET arc;
110 PID pid;
111 TID tid;
112 MRESULT mrc;
113
114 BOOL bTriedFind = FALSE;
115
116 do
117 {
118 if (G_hwndSysTray == NULLHANDLE)
119 {
120 bTriedFind = TRUE;
121 HWND hwnd = FindSysTrayServerWindow();
122 __atomic_cmpxchg32((volatile uint32_t *)&G_hwndSysTray,
123 hwnd, NULLHANDLE);
124 if (G_hwndSysTray == NULLHANDLE)
125 break;
126 }
127
128 if (bTriedFind)
129 {
130 arc = ERROR_INVALID_HANDLE;
131 if (WinQueryWindowProcess(G_hwndSysTray, &pid, &tid))
132 arc = DosGiveSharedMem(G_pvMemoryPool,
133 pid, PAG_READ | PAG_WRITE);
134 if (arc != NO_ERROR)
135 break;
136 }
137
138 pData->bAcknowledged = FALSE;
139
140 mrc = WinSendMsg(G_hwndSysTray, WM_XST_CONTROL, pData, NULL);
141 if (pData->bAcknowledged)
142 return (ULONG)mrc;
143
144 // if we failed to send the message, it may mean that XCenter was restarted
145 // or the system tray was re-enabled. Try to get a new handle (only if we
146 // didn't already do it in this call)
147 if (!bTriedFind)
148 {
149 G_hwndSysTray = NULLHANDLE;
150 continue;
151 }
152
153 break;
154 }
155 while (1);
156
157 return XST_FAIL;
158}
159
160/*
161 *@@ AllocSysTrayCtlDataPtr:
162 * Allocates a SYSTRAYCTLDATA struct in the pool of shared memory.
163 *
164 * If there is no free space in the pool, it returns NULL. The allocated
165 * memory must be freed by FreeSysTrayCtlDataPtr() when not needed.
166 */
167
168static PSYSTRAYCTLDATA AllocSysTrayCtlDataPtr()
169{
170 APIRET arc;
171 PVOID pvPool;
172 PSYSTRAYCTLDATA pData;
173
174 if (!G_pvMemoryPool)
175 {
176 // Note: we don't PAG_COMMIT, DosSubAllocMem will do so when needed
177 arc = DosAllocSharedMem((PVOID)&pvPool, NULL, CLIENT_MEMORYPOOL_SIZE,
178 PAG_READ | PAG_WRITE | OBJ_GIVEABLE);
179 if (arc == NO_ERROR)
180 arc = DosSubSetMem(pvPool,
181 DOSSUB_INIT | DOSSUB_SPARSE_OBJ,
182 CLIENT_MEMORYPOOL_SIZE);
183 if (!__atomic_cmpxchg32((volatile uint32_t *)&G_pvMemoryPool,
184 (uint32_t)pvPool, (uint32_t)NULL))
185 {
186 // another thread has already got an entry, discard our try
187 if (pvPool)
188 DosFreeMem(pvPool);
189 }
190 else
191 {
192 // we could fail to allocate while being the first... give up
193 if (arc != NO_ERROR)
194 return NULL;
195 }
196 }
197
198 arc = DosSubAllocMem(G_pvMemoryPool, (PVOID)&pData, sizeof(*pData));
199 if (arc != NO_ERROR)
200 return NULL;
201
202 return pData;
203}
204
205static VOID FreeSysTrayCtlDataPtr(PSYSTRAYCTLDATA pData)
206{
207 DosSubFreeMem(G_pvMemoryPool, pData, sizeof(*pData));
208}
209
210/*
211 *@@ InputHook:
212 * This is used to intercept posted WM_XST_NOTIFY messages and apply
213 * special processing to them (compose a client window-specific
214 * notification message, free the NOTIFYDATA structure and post the
215 * composed message to the target window).
216 */
217
218static BOOL EXPENTRY InputHook(HAB hab, PQMSG pQmsg, ULONG fs)
219{
220 if (pQmsg->msg == WM_XST_NOTIFY)
221 {
222 PNOTIFYDATA pNotifyData = (PNOTIFYDATA)pQmsg->mp1;
223 PVOID pvMemoryPool = (PVOID)pQmsg->mp2;
224
225 // copy NOTIFYDATA and free it
226 NOTIFYDATA NotifyData = *pNotifyData;
227 FreeNotifyDataPtr(pvMemoryPool, pQmsg->hwnd, pNotifyData);
228
229 // start with a copy of the message and change the fields we need
230 QMSG newMsg = *pQmsg;
231 newMsg.msg = NotifyData.msg;
232 newMsg.mp1 = NotifyData.mp1;
233 newMsg.mp2 = NotifyData.mp2;
234
235 LOGF(("Dispatching msg %08lx to hwnd %lx\n", newMsg.msg, newMsg.hwnd));
236
237 // deliver the message
238 WinDispatchMsg(hab, &newMsg);
239
240 return TRUE;
241 }
242
243 return FALSE;
244}
245
246BOOL xstQuerySysTrayVersion(PULONG pulMajor,
247 PULONG pulMinor,
248 PULONG pulRevision)
249{
250 BOOL brc;
251 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
252 if (!pData)
253 return FALSE;
254
255 pData->ulCommand = SYSTRAYCMD_GETVERSION;
256 pData->hwndSender = NULLHANDLE;
257
258 brc = SendSysTrayCtlMsg(pData) == XST_OK;
259 if (brc)
260 {
261 if (pulMajor)
262 *pulMajor = pData->u.version.ulMajor;
263 if (pulMinor)
264 *pulMinor = pData->u.version.ulMinor;
265 if (pulRevision)
266 *pulRevision = pData->u.version.ulRevision;
267 }
268
269 FreeSysTrayCtlDataPtr(pData);
270
271 return brc;
272}
273
274BOOL xstAddSysTrayIcon(HWND hwnd,
275 USHORT usId,
276 HPOINTER hIcon,
277 PCSZ pcszToolTip,
278 ULONG ulMsgId,
279 ULONG ulFlags)
280{
281 BOOL brc;
282 ULONG xrc = XST_FAIL;
283 PPIB ppib;
284 HAB hab;
285 HMQ hmq;
286 size_t i;
287
288 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
289 if (!pData)
290 return FALSE;
291
292 if (WM_XST_NOTIFY == 0)
293 WM_XST_NOTIFY = WinAddAtom(WinQuerySystemAtomTable(),
294 WM_XST_NOTIFY_ATOM);
295
296 hab = WinQueryAnchorBlock(hwnd);
297 hmq = WinQueryWindowULong(hwnd, QWL_HMQ);
298 if (hmq == NULLHANDLE)
299 return FALSE;
300
301 // initialize the HMQ refs array
302 // @todo remove _smutex usage when we get into the DLL and initialize
303 // _fmutex + array in the DLL init routine
304 _smutex_request(&G_smtx);
305 if (!G_pHmqRefs)
306 {
307 if (_fmutex_create(&G_fmtx, 0))
308 return FALSE;
309 G_pHmqRefs = malloc(sizeof(*G_pHmqRefs) * HMQREFS_GROW);
310 if (!G_pHmqRefs)
311 return FALSE;
312 G_cHmqRefs = 0;
313 G_cHmqRefsMax = HMQREFS_GROW;
314 }
315 _smutex_release(&G_smtx);
316
317 // give all processes temporary access to hIcon
318 brc = hIcon != NULLHANDLE ? WinSetPointerOwner(hIcon, 0, FALSE) : TRUE;
319 if (brc)
320 {
321 pData->ulCommand = SYSTRAYCMD_ADDICON;
322 pData->hwndSender = hwnd;
323
324 pData->u.icon.usId = usId;
325 pData->u.icon.hIcon = hIcon;
326 pData->u.icon.ulMsgId = ulMsgId;
327
328 if (!pcszToolTip)
329 pData->u.icon.szToolTip[0] = '\0';
330 else
331 {
332 strncpy(pData->u.icon.szToolTip, pcszToolTip,
333 sizeof(pData->u.icon.szToolTip) - 1);
334 // be on the safe side
335 pData->u.icon.szToolTip[sizeof(pData->u.icon.szToolTip) - 1] = '\0';
336 }
337
338 xrc = SendSysTrayCtlMsg(pData);
339 brc = xrc == XST_OK || xrc == XST_REPLACED;
340
341 // revoke temporary access to hIcon
342 if (hIcon != NULLHANDLE)
343 {
344 DosGetInfoBlocks(NULL, &ppib);
345 WinSetPointerOwner(hIcon, ppib->pib_ulpid, TRUE);
346 }
347 }
348
349 FreeSysTrayCtlDataPtr(pData);
350
351 if (xrc == XST_OK)
352 {
353 // install the message hook for the new icon to intercept WM_XST_NOTIFY
354 // messages or increase the reference count if already done so
355 brc = FALSE;
356 _fmutex_request(&G_fmtx, _FMR_IGNINT);
357 do
358 {
359 for (i = 0; i < G_cHmqRefs; ++i)
360 if (G_pHmqRefs[i].hmq == hmq)
361 break;
362 if (i < G_cHmqRefs)
363 ++G_pHmqRefs[i].cRefs;
364 else
365 {
366 if (i == G_cHmqRefsMax)
367 {
368 PHMQREFS pNewRefs = realloc(G_pHmqRefs,
369 sizeof(*G_pHmqRefs) *
370 (G_cHmqRefsMax + HMQREFS_GROW));
371 if (!pNewRefs)
372 break;
373 G_pHmqRefs = pNewRefs;
374 G_cHmqRefsMax += HMQREFS_GROW;
375 }
376 brc = WinSetHook(hab, hmq, HK_INPUT, (PFN)InputHook, NULLHANDLE);
377 if (!brc)
378 break;
379 ++G_cHmqRefs;
380 G_pHmqRefs[i].hmq = hmq;
381 G_pHmqRefs[i].cRefs = 1;
382 }
383 brc = TRUE;
384 }
385 while (0);
386 _fmutex_release(&G_fmtx);
387
388 if (!brc)
389 xstRemoveSysTrayIcon(hwnd, usId);
390 }
391
392 return brc;
393}
394
395BOOL xstReplaceSysTrayIcon(HWND hwnd,
396 USHORT usId,
397 HPOINTER hIcon)
398{
399 BOOL brc;
400 PPIB ppib;
401
402 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
403 if (!pData)
404 return FALSE;
405
406 // give all processes temporary access to hIcon
407 brc = hIcon != NULLHANDLE ? WinSetPointerOwner(hIcon, 0, FALSE) : TRUE;
408 if (brc)
409 {
410 pData->ulCommand = SYSTRAYCMD_REPLACEICON;
411 pData->hwndSender = hwnd;
412
413 pData->u.icon.usId = usId;
414 pData->u.icon.hIcon = hIcon;
415
416 brc = SendSysTrayCtlMsg(pData) == XST_OK;
417
418 // revoke temporary access to hIcon
419 if (hIcon != NULLHANDLE)
420 {
421 DosGetInfoBlocks(NULL, &ppib);
422 WinSetPointerOwner(hIcon, ppib->pib_ulpid, TRUE);
423 }
424 }
425
426 FreeSysTrayCtlDataPtr(pData);
427
428 return brc;
429}
430
431BOOL xstRemoveSysTrayIcon(HWND hwnd,
432 USHORT usId)
433{
434 BOOL brc;
435 HAB hab;
436 HMQ hmq;
437 size_t i;
438
439 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
440 if (!pData)
441 return FALSE;
442
443 hab = WinQueryAnchorBlock(hwnd);
444 hmq = WinQueryWindowULong(hwnd, QWL_HMQ);
445 if (hmq == NULLHANDLE)
446 return FALSE;
447
448 pData->ulCommand = SYSTRAYCMD_REMOVEICON;
449 pData->hwndSender = hwnd;
450 pData->u.icon.usId = usId;
451
452 brc = SendSysTrayCtlMsg(pData) == XST_OK;
453
454 FreeSysTrayCtlDataPtr(pData);
455
456 if (brc)
457 {
458 // remove the message hook if it's the last reference to the HMQ
459 _fmutex_request(&G_fmtx, _FMR_IGNINT);
460 do
461 {
462 for (i = 0; i < G_cHmqRefs; ++i)
463 if (G_pHmqRefs[i].hmq == hmq)
464 break;
465 if (i == G_cHmqRefs)
466 // unknown HMQ??
467 break;
468
469 if (--G_pHmqRefs[i].cRefs == 0)
470 WinReleaseHook(hab, hmq, HK_INPUT, (PFN)InputHook, NULLHANDLE);
471 }
472 while (0);
473 _fmutex_release(&G_fmtx);
474 }
475
476 return brc;
477}
478
479BOOL xstSetSysTrayIconToolTip(HWND hwnd,
480 USHORT usId,
481 PCSZ pcszToolTip)
482{
483 BOOL brc;
484 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
485 if (!pData)
486 return FALSE;
487
488 pData->ulCommand = SYSTRAYCMD_SETTOOLTIP;
489 pData->hwndSender = hwnd;
490 pData->u.icon.usId = usId;
491
492 if (!pcszToolTip)
493 pData->u.icon.szToolTip[0] = '\0';
494 else
495 {
496 strncpy(pData->u.icon.szToolTip, pcszToolTip,
497 sizeof(pData->u.icon.szToolTip) - 1);
498 // be on the safe side
499 pData->u.icon.szToolTip[sizeof(pData->u.icon.szToolTip) - 1] = '\0';
500 }
501
502 brc = SendSysTrayCtlMsg(pData) == XST_OK;
503
504 FreeSysTrayCtlDataPtr(pData);
505
506 return brc;
507}
508
509BOOL xstShowSysTrayIconBalloon(HWND hwnd, USHORT usId, PCSZ pcszTitle,
510 PCSZ pcszText, ULONG ulFlags, ULONG ulTimeout)
511{
512 // @todo implement
513 return FALSE;
514}
515
516BOOL xstHideSysTrayIconBalloon(HWND hwnd, USHORT usId)
517{
518 // @todo implement
519 return FALSE;
520}
521
522BOOL xstQuerySysTrayIconRect(HWND hwnd, USHORT usId, PRECTL prclRect)
523{
524 BOOL brc;
525 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
526 if (!pData)
527 return FALSE;
528
529 pData->ulCommand = SYSTRAYCMD_QUERYRECT;
530 pData->hwndSender = hwnd;
531 pData->u.icon.usId = usId;
532
533 brc = SendSysTrayCtlMsg(pData) == XST_OK;
534 if (brc)
535 {
536 *prclRect = pData->u.rect.rclIcon;
537 }
538
539 FreeSysTrayCtlDataPtr(pData);
540
541 return brc;
542}
543
544ULONG xstGetSysTrayCreatedMsgId()
545{
546 if (WM_XST_CREATED == 0)
547 WM_XST_CREATED = WinAddAtom(WinQuerySystemAtomTable(),
548 WM_XST_CREATED_ATOM);
549 return WM_XST_CREATED;
550}
551
552ULONG xstGetSysTrayMaxTextLen()
553{
554 return sizeof(((PSYSTRAYCTLDATA)0)->u.icon.szToolTip);
555}
556
Note: See TracBrowser for help on using the repository browser.