source: trunk/src/3rdparty/os2/xsystray/apilib/xsystray.c@ 835

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

gui: Extracted xsystray API from Qt and put it to a separate DLL dynamically loaded at runtime (see #99 for details).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 20.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 Dmitry A. 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#include "w_xsystray.h"
36
37#include <string.h>
38#include <sys/builtin.h> // atomics
39#include <sys/fmutex.h> // fast mutex
40#include <sys/smutex.h> // simple mutex
41
42static ULONG WM_XST_CREATED = 0;
43 // identity of the WM_XST_CREATED message taken from the atom table
44static ULONG WM_XST_NOTIFY = 0;
45 // identity of the WM_XST_NOTIFY message taken from the atom table
46
47static
48volatile HWND G_hwndSysTray = NULLHANDLE;
49 // window handle of the system tray server
50
51static
52volatile PVOID G_pvMemoryPool = NULL;
53 // shared memory pool for SYSTRAYCTLDATA structs used by
54 // WM_XST_CONTROL messages. Note that once allocated, this memory
55 // is never freed: it is intentional since the memory is assumed
56 // to be always in need and that the system will free it when the
57 // application terminates
58
59#define CLIENT_MEMORYPOOL_SIZE 65536
60 // taking SYSTRAYCTLDATA size into account (<=1024 B), this is enough
61 // for at least 64 threads sending WM_XST_CONTROL simultaneously, which
62 // sounds sane
63
64typedef struct
65{
66 HMQ hmq;
67 unsigned cRefs;
68} HMQREFS, *PHMQREFS;
69
70static PHMQREFS G_pHmqRefs = NULL;
71 // array of references to each HMQ that we make
72static size_t G_cHmqRefs = 0;
73 // number of elements in G_pHmqRefs
74static size_t G_cHmqRefsMax = 0;
75 // maximum number of elements in G_pHmqRefs
76static _fmutex G_fmtx;
77 // fast mutex to protect
78static _smutex G_smtx = 0;
79 // simple mutex for xstAddSysTrayIcon()
80#define HMQREFS_GROW 4
81 // how fast G_pHmqRefs grows when more space is necessary
82
83// @todo to be on the safe side with casting in __atomic_cmpxchg32() we need
84// compile-time assertions like this:
85// AssertCompile(sizeof(uint32_t) == sizeof(HWND));
86// AssertCompile(sizeof(uint32_t) == sizeof(PVOID));
87
88static HWND FindSysTrayServerWindow()
89{
90 char buf[sizeof(WNDCLASS_WIDGET_XSYSTRAY_SERVER) + 1];
91 HWND hwnd;
92 HENUM henum = WinBeginEnumWindows(HWND_DESKTOP);
93 while ((hwnd = WinGetNextWindow(henum)) != NULLHANDLE)
94 {
95 LONG len = WinQueryClassName(hwnd, sizeof(buf), buf);
96 buf[len] = '\0';
97 if (strcmp(WNDCLASS_WIDGET_XSYSTRAY_SERVER, buf) == 0)
98 break;
99 }
100 WinEndEnumWindows(henum);
101
102 return hwnd;
103}
104
105static ULONG SendSysTrayCtlMsg(PSYSTRAYCTLDATA pData)
106{
107 APIRET arc;
108 PID pid;
109 TID tid;
110 MRESULT mrc;
111
112 BOOL bTriedFind = FALSE;
113
114 do
115 {
116 if (G_hwndSysTray == NULLHANDLE)
117 {
118 bTriedFind = TRUE;
119 HWND hwnd = FindSysTrayServerWindow();
120 __atomic_cmpxchg32((volatile uint32_t *)&G_hwndSysTray,
121 hwnd, NULLHANDLE);
122 if (G_hwndSysTray == NULLHANDLE)
123 break;
124 }
125
126 if (bTriedFind)
127 {
128 arc = ERROR_INVALID_HANDLE;
129 if (WinQueryWindowProcess(G_hwndSysTray, &pid, &tid))
130 arc = DosGiveSharedMem(G_pvMemoryPool,
131 pid, PAG_READ | PAG_WRITE);
132 if (arc != NO_ERROR)
133 break;
134 }
135
136 pData->bAcknowledged = FALSE;
137
138 mrc = WinSendMsg(G_hwndSysTray, WM_XST_CONTROL, pData, NULL);
139 if (pData->bAcknowledged)
140 return (ULONG)mrc;
141
142 // if we failed to send the message, it may mean that XCenter was restarted
143 // or the system tray was re-enabled. Try to get a new handle (only if we
144 // didn't already do it in this call)
145 if (!bTriedFind)
146 {
147 G_hwndSysTray = NULLHANDLE;
148 continue;
149 }
150
151 break;
152 }
153 while (1);
154
155 return XST_FAIL;
156}
157
158/*
159 *@@ AllocSysTrayCtlDataPtr:
160 * Allocates a SYSTRAYCTLDATA struct in the pool of shared memory.
161 *
162 * If there is no free space in the pool, it returns NULL. The allocated
163 * memory must be freed by FreeSysTrayCtlDataPtr() when not needed.
164 */
165
166static PSYSTRAYCTLDATA AllocSysTrayCtlDataPtr()
167{
168 APIRET arc;
169 PVOID pvPool;
170 PSYSTRAYCTLDATA pData;
171
172 if (!G_pvMemoryPool)
173 {
174 // Note: we don't PAG_COMMIT, DosSubAllocMem will do so when needed
175 arc = DosAllocSharedMem((PVOID)&pvPool, NULL, CLIENT_MEMORYPOOL_SIZE,
176 PAG_READ | PAG_WRITE | OBJ_GIVEABLE);
177 if (arc == NO_ERROR)
178 arc = DosSubSetMem(pvPool,
179 DOSSUB_INIT | DOSSUB_SPARSE_OBJ,
180 CLIENT_MEMORYPOOL_SIZE);
181 if (!__atomic_cmpxchg32((volatile uint32_t *)&G_pvMemoryPool,
182 (uint32_t)pvPool, (uint32_t)NULL))
183 {
184 // another thread has already got an entry, discard our try
185 if (pvPool)
186 DosFreeMem(pvPool);
187 }
188 else
189 {
190 // we could fail to allocate while being the first... give up
191 if (arc != NO_ERROR)
192 return NULL;
193 }
194 }
195
196 arc = DosSubAllocMem(G_pvMemoryPool, (PVOID)&pData, sizeof(*pData));
197 if (arc != NO_ERROR)
198 return NULL;
199
200 return pData;
201}
202
203static VOID FreeSysTrayCtlDataPtr(PSYSTRAYCTLDATA pData)
204{
205 DosSubFreeMem(G_pvMemoryPool, pData, sizeof(*pData));
206}
207
208/*
209 *@@ InputHook:
210 * This is used to intercept posted WM_XST_NOTIFY messages and apply
211 * special processing to them (compose a client window-specific
212 * notification message, free the NOTIFYDATA structure and post the
213 * composed message to the target window).
214 */
215
216static BOOL EXPENTRY InputHook(HAB hab, PQMSG pQmsg, ULONG fs)
217{
218 if (pQmsg->msg == WM_XST_NOTIFY)
219 {
220 PNOTIFYDATA pNotifyData = (PNOTIFYDATA)pQmsg->mp1;
221 PVOID pvMemoryPool = (PVOID)pQmsg->mp2;
222
223 // copy NOTIFYDATA and free it
224 NOTIFYDATA NotifyData = *pNotifyData;
225 FreeNotifyDataPtr(pvMemoryPool, pQmsg->hwnd, pNotifyData);
226
227 // start with a copy of the message and change the fields we need
228 QMSG newMsg = *pQmsg;
229 newMsg.msg = NotifyData.msg;
230 newMsg.mp1 = NotifyData.mp1;
231 newMsg.mp2 = NotifyData.mp2;
232
233 // deliver the message
234 WinDispatchMsg(hab, &newMsg);
235
236 return TRUE;
237 }
238
239 return FALSE;
240}
241
242/*
243 *@@ xstQuerySysTrayVersion:
244 *
245 * Returns the version of the Extended system tray in the variables pointed
246 * to by arguments. Any argument may be NULL in which case the
247 * corresponding component of the version is not returned.
248 *
249 * Returns TRUE on success and FALSE if the Extended system tray is not
250 * installed or not operational.
251 *
252 * NOTE: When the Extended system tray is started up or gets enabled after
253 * being temporarily disabled, it sends a message with the ID returned by
254 * xstGetSysTrayCreatedMsgId() to all top-level WC_FRAME windows on the
255 * desktop to let them add tray icons if they need.
256 */
257
258BOOL xstQuerySysTrayVersion(PULONG pulMajor, // out: major version number
259 PULONG pulMinor, // out: minor version number
260 PULONG pulRevision) // out: revision number
261{
262 BOOL brc;
263 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
264 if (!pData)
265 return FALSE;
266
267 pData->ulCommand = SYSTRAYCMD_GETVERSION;
268 pData->hwndSender = NULLHANDLE;
269
270 brc = SendSysTrayCtlMsg(pData) == XST_OK;
271 if (brc)
272 {
273 if (pulMajor)
274 *pulMajor = pData->u.version.ulMajor;
275 if (pulMinor)
276 *pulMinor = pData->u.version.ulMinor;
277 if (pulRevision)
278 *pulRevision = pData->u.version.ulRevision;
279 }
280
281 FreeSysTrayCtlDataPtr(pData);
282
283 return brc;
284}
285
286/*
287 *@@ xstAddSysTrayIcon:
288 *
289 * Adds an icon for the given window handle to the system tray. The icon ID
290 * is used to distinguish between several icons for the same window handle.
291 * If the icon with the specified ID already exists in the system tray, it
292 * will be replaced.
293 *
294 * Returns TRUE on success and FALSE otherwise.
295 *
296 * The specified window handle receives notification messages about icon
297 * events using the message ID specified by the ulMsgId parameter. The
298 * layout of the message parameters is as follows:
299 *
300 * param1
301 * USHORT usIconID icon ID
302 * USHORT usNotifyCode notify code, one of XST_IN_ constants
303 *
304 * param2
305 * PVOID pData notify code specific data (see below)
306 *
307 * The following notify codes are currently recognized:
308 *
309 * XST_IN_MOUSE:
310 * Mouse event in the icon area. Currently, only mouse click
311 * messages are recognized. param2 is a pointer to the XSTMOUSEMSG
312 * structure containing full mouse message details.
313 *
314 * XST_IN_CONTEXT:
315 * Context menu event in the icon area. param2 is a pointer to the
316 * XSTCONTEXTMSG structure containing full message details.
317 *
318 * XST_IN_WHEEL:
319 * Mouse wheel event in the icon area. param2 is a pointer to the
320 * XSTWHEELTMSG structure containing full message details.
321 *
322 * NOTE: The maximum tooltip text length (including terminating null) is
323 * limited to a value returned by xstGetSysTrayMaxTextLen(). If the
324 * supplied string is longer, it will be truncated.
325 */
326
327BOOL xstAddSysTrayIcon(HWND hwnd, // in: window handle associated with the icon
328 USHORT usId, // in: icon ID to add
329 HPOINTER hIcon, // in: icon handle
330 PCSZ pcszToolTip,// in: tooltip text
331 ULONG ulMsgId, // in: message ID for notifications
332 ULONG ulFlags) // in: flags (not currently used, must be 0)
333{
334 BOOL brc;
335 ULONG xrc = XST_FAIL;
336 PPIB ppib;
337 HAB hab;
338 HMQ hmq;
339 size_t i;
340
341 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
342 if (!pData)
343 return FALSE;
344
345 if (WM_XST_NOTIFY == 0)
346 WM_XST_NOTIFY = WinAddAtom(WinQuerySystemAtomTable(),
347 WM_XST_NOTIFY_ATOM);
348
349 hab = WinQueryAnchorBlock(hwnd);
350 hmq = WinQueryWindowULong(hwnd, QWL_HMQ);
351 if (hmq == NULLHANDLE)
352 return FALSE;
353
354 // initialize the HMQ refs array
355 // @todo remove _smutex usage when we get into the DLL and initialize
356 // _fmutex + array in the DLL init routine
357 _smutex_request(&G_smtx);
358 if (!G_pHmqRefs)
359 {
360 if (_fmutex_create(&G_fmtx, 0))
361 return FALSE;
362 G_pHmqRefs = malloc(sizeof(*G_pHmqRefs) * HMQREFS_GROW);
363 if (!G_pHmqRefs)
364 return FALSE;
365 G_cHmqRefs = 0;
366 G_cHmqRefsMax = HMQREFS_GROW;
367 }
368 _smutex_release(&G_smtx);
369
370 // give all processes temporary access to hIcon
371 brc = WinSetPointerOwner(hIcon, 0, FALSE);
372 if (brc)
373 {
374 pData->ulCommand = SYSTRAYCMD_ADDICON;
375 pData->hwndSender = hwnd;
376
377 pData->u.icon.usId = usId;
378 pData->u.icon.hIcon = hIcon;
379 pData->u.icon.ulMsgId = ulMsgId;
380
381 if (!pcszToolTip)
382 pData->u.icon.szToolTip[0] = '\0';
383 else
384 {
385 strncpy(pData->u.icon.szToolTip, pcszToolTip,
386 sizeof(pData->u.icon.szToolTip) - 1);
387 // be on the safe side
388 pData->u.icon.szToolTip[sizeof(pData->u.icon.szToolTip) - 1] = '\0';
389 }
390
391 xrc = SendSysTrayCtlMsg(pData);
392 brc = xrc == XST_OK || xrc == XST_REPLACED;
393
394 // revoke temporary access to hIcon
395 DosGetInfoBlocks(NULL, &ppib);
396 WinSetPointerOwner(hIcon, ppib->pib_ulpid, TRUE);
397 }
398
399 FreeSysTrayCtlDataPtr(pData);
400
401 if (xrc == XST_OK)
402 {
403 // install the message hook for the new icon to intercept WM_XST_NOTIFY
404 // messages or increase the reference count if already done so
405 brc = FALSE;
406 _fmutex_request(&G_fmtx, _FMR_IGNINT);
407 do
408 {
409 for (i = 0; i < G_cHmqRefs; ++i)
410 if (G_pHmqRefs[i].hmq == hmq)
411 break;
412 if (i < G_cHmqRefs)
413 ++G_pHmqRefs[i].cRefs;
414 else
415 {
416 if (i == G_cHmqRefsMax)
417 {
418 PHMQREFS pNewRefs = realloc(G_pHmqRefs,
419 sizeof(*G_pHmqRefs) *
420 (G_cHmqRefsMax + HMQREFS_GROW));
421 if (!pNewRefs)
422 break;
423 G_pHmqRefs = pNewRefs;
424 G_cHmqRefsMax += HMQREFS_GROW;
425 }
426 brc = WinSetHook(hab, hmq, HK_INPUT, (PFN)InputHook, NULLHANDLE);
427 if (!brc)
428 break;
429 ++G_cHmqRefs;
430 G_pHmqRefs[i].hmq = hmq;
431 G_pHmqRefs[i].cRefs = 1;
432 }
433 brc = TRUE;
434 }
435 while (0);
436 _fmutex_release(&G_fmtx);
437
438 if (!brc)
439 xstRemoveSysTrayIcon(hwnd, usId);
440 }
441
442 return brc;
443}
444
445/*
446 *@@ xstReplaceSysTrayIcon:
447 *
448 * Replaces the existing icon previously added by xstAddSysTrayIcon() with
449 * a new icon.
450 *
451 * Returns TRUE on success and FALSE otherwise.
452 */
453
454BOOL xstReplaceSysTrayIcon(HWND hwnd, // in: window handle associated with the icon
455 USHORT usId, // in: icon ID to change
456 HPOINTER hIcon) // in: new icon handle
457{
458 BOOL brc;
459 PPIB ppib;
460
461 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
462 if (!pData)
463 return FALSE;
464
465 // give all processes temporary access to hIcon
466 brc = WinSetPointerOwner(hIcon, 0, FALSE);
467 if (brc)
468 {
469 pData->ulCommand = SYSTRAYCMD_REPLACEICON;
470 pData->hwndSender = hwnd;
471
472 pData->u.icon.usId = usId;
473 pData->u.icon.hIcon = hIcon;
474
475 brc = SendSysTrayCtlMsg(pData) == XST_OK;
476
477 // revoke temporary access to hIcon
478 DosGetInfoBlocks(NULL, &ppib);
479 WinSetPointerOwner(hIcon, ppib->pib_ulpid, TRUE);
480 }
481
482 FreeSysTrayCtlDataPtr(pData);
483
484 return brc;
485}
486
487/*
488 *@@ xstRemoveSysTrayIcon:
489 *
490 * Removes the icon previously added by xstAddSysTrayIcon() from the system
491 * tray.
492 *
493 * Returns TRUE on success and FALSE otherwise.
494 */
495
496BOOL xstRemoveSysTrayIcon(HWND hwnd, // in: window handle associated with the icon
497 USHORT usId) // in: icon ID to remove
498{
499 BOOL brc;
500 HAB hab;
501 HMQ hmq;
502 size_t i;
503
504 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
505 if (!pData)
506 return FALSE;
507
508 hab = WinQueryAnchorBlock(hwnd);
509 hmq = WinQueryWindowULong(hwnd, QWL_HMQ);
510 if (hmq == NULLHANDLE)
511 return FALSE;
512
513 pData->ulCommand = SYSTRAYCMD_REMOVEICON;
514 pData->hwndSender = hwnd;
515 pData->u.icon.usId = usId;
516
517 brc = SendSysTrayCtlMsg(pData) == XST_OK;
518
519 FreeSysTrayCtlDataPtr(pData);
520
521 if (brc)
522 {
523 // remove the message hook if it's the last reference to the HMQ
524 _fmutex_request(&G_fmtx, _FMR_IGNINT);
525 do
526 {
527 for (i = 0; i < G_cHmqRefs; ++i)
528 if (G_pHmqRefs[i].hmq == hmq)
529 break;
530 if (i == G_cHmqRefs)
531 // unknown HMQ??
532 break;
533
534 if (--G_pHmqRefs[i].cRefs == 0)
535 WinReleaseHook(hab, hmq, HK_INPUT, (PFN)InputHook, NULLHANDLE);
536 }
537 while (0);
538 _fmutex_release(&G_fmtx);
539 }
540
541 return brc;
542}
543
544/*
545 *@@ xstSetSysTrayIconToolTip:
546 *
547 * Sets the tooltip text for the given icon in the system tray. This text
548 * is shown when the mouse pointer is held still over the icon area for
549 * some time.
550 *
551 * If pszText is NULL, the tooltip text is reset and will not be shown next
552 * time. The old tooltip is hidden if it is being currently shown.
553 *
554 * Returns TRUE on success and FALSE otherwise.
555 *
556 * NOTE: The maximum tooltip text length (including terminating null) is
557 * limited to a value returned by xstGetSysTrayMaxTextLen(). If the
558 * supplied string is longer, it will be truncated.
559 */
560
561BOOL xstSetSysTrayIconToolTip(HWND hwnd, // in: window handle associated with the icon
562 USHORT usId, // in: icon ID to set the tooltip for
563 PCSZ pcszToolTip) // in: tooltip text
564{
565 BOOL brc;
566 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
567 if (!pData)
568 return FALSE;
569
570 pData->ulCommand = SYSTRAYCMD_SETTOOLTIP;
571 pData->hwndSender = hwnd;
572 pData->u.icon.usId = usId;
573
574 if (!pcszToolTip)
575 pData->u.icon.szToolTip[0] = '\0';
576 else
577 {
578 strncpy(pData->u.icon.szToolTip, pcszToolTip,
579 sizeof(pData->u.icon.szToolTip) - 1);
580 // be on the safe side
581 pData->u.icon.szToolTip[sizeof(pData->u.icon.szToolTip) - 1] = '\0';
582 }
583
584 brc = SendSysTrayCtlMsg(pData) == XST_OK;
585
586 FreeSysTrayCtlDataPtr(pData);
587
588 return brc;
589}
590
591BOOL xstShowSysTrayIconBalloon(HWND hwnd, USHORT usId, PCSZ pcszTitle,
592 PCSZ pcszText, ULONG ulFlags, ULONG ulTimeout)
593{
594 // @todo implement
595 return FALSE;
596}
597
598BOOL xstHideSysTrayIconBalloon(HWND hwnd, USHORT usId)
599{
600 // @todo implement
601 return FALSE;
602}
603
604/*
605 *@@ xstQuerySysTrayIconRect:
606 *
607 * Obtains a rectangle occupied by the given icon (in screen coordinates,
608 * top right corner exclusive).
609 *
610 * Returns TRUE on success and FALSE otherwise.
611 */
612BOOL xstQuerySysTrayIconRect(HWND hwnd, USHORT usId, PRECTL prclRect)
613{
614 BOOL brc;
615 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
616 if (!pData)
617 return FALSE;
618
619 pData->ulCommand = SYSTRAYCMD_QUERYRECT;
620 pData->hwndSender = hwnd;
621 pData->u.icon.usId = usId;
622
623 brc = SendSysTrayCtlMsg(pData) == XST_OK;
624 if (brc)
625 {
626 *prclRect = pData->u.rect.rclIcon;
627 }
628
629 FreeSysTrayCtlDataPtr(pData);
630
631 return brc;
632}
633
634/*
635 *@@ xstGetSysTrayCreatedMsgId:
636 *
637 * Returns the ID of the message that is sent by the Extended system tray
638 * to all top-level WC_FRAME windows on the desktop to let them add tray
639 * icons if they need.
640 *
641 * NOTE: The returned value never changes until reboot so it is a good
642 * practice to cache it instead of calling this function each time from the
643 * window procedure of every involved window.
644 */
645
646ULONG xstGetSysTrayCreatedMsgId()
647{
648 if (WM_XST_CREATED == 0)
649 WM_XST_CREATED = WinAddAtom(WinQuerySystemAtomTable(),
650 WM_XST_CREATED_ATOM);
651 return WM_XST_CREATED;
652}
653
654/*
655 *@@ xstGetSysTrayMaxTextLen:
656 *
657 * Returns the maximum length of the text (in bytes, including the
658 * terminating null) that can be shown in the tooltop of the icon in the
659 * system tray. You can use the returned value to determine the maximum
660 * length of the string passed as pszText to xstSetSysTrayIconToolTip().
661 *
662 * The returned value also defines the maximum length of both the title and
663 * the text (including terminating nulls) of the icon's balloon for the
664 * xstShowSysTrayIconBalloon() call.
665 *
666 * Returns TRUE on success and FALSE otherwise.
667 *
668 * NOTE: The returned value never changes until reboot so it is a good
669 * practice to cache it instead of calling this function each time.
670 */
671
672ULONG xstGetSysTrayMaxTextLen()
673{
674 return sizeof(((PSYSTRAYCTLDATA)0)->u.icon.szToolTip);
675}
676
Note: See TracBrowser for help on using the repository browser.