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

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

xsystray: Put primitive logging macros to the header to make them accessible in any source.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 15.7 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 // deliver the message
236 WinDispatchMsg(hab, &newMsg);
237
238 return TRUE;
239 }
240
241 return FALSE;
242}
243
244BOOL xstQuerySysTrayVersion(PULONG pulMajor, // out: major version number
245 PULONG pulMinor, // out: minor version number
246 PULONG pulRevision) // out: revision number
247{
248 BOOL brc;
249 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
250 if (!pData)
251 return FALSE;
252
253 pData->ulCommand = SYSTRAYCMD_GETVERSION;
254 pData->hwndSender = NULLHANDLE;
255
256 brc = SendSysTrayCtlMsg(pData) == XST_OK;
257 if (brc)
258 {
259 if (pulMajor)
260 *pulMajor = pData->u.version.ulMajor;
261 if (pulMinor)
262 *pulMinor = pData->u.version.ulMinor;
263 if (pulRevision)
264 *pulRevision = pData->u.version.ulRevision;
265 }
266
267 FreeSysTrayCtlDataPtr(pData);
268
269 return brc;
270}
271
272BOOL xstAddSysTrayIcon(HWND hwnd, // in: window handle associated with the icon
273 USHORT usId, // in: icon ID to add
274 HPOINTER hIcon, // in: icon handle
275 PCSZ pcszToolTip,// in: tooltip text
276 ULONG ulMsgId, // in: message ID for notifications
277 ULONG ulFlags) // in: flags (not currently used, must be 0)
278{
279 BOOL brc;
280 ULONG xrc = XST_FAIL;
281 PPIB ppib;
282 HAB hab;
283 HMQ hmq;
284 size_t i;
285
286 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
287 if (!pData)
288 return FALSE;
289
290 if (WM_XST_NOTIFY == 0)
291 WM_XST_NOTIFY = WinAddAtom(WinQuerySystemAtomTable(),
292 WM_XST_NOTIFY_ATOM);
293
294 hab = WinQueryAnchorBlock(hwnd);
295 hmq = WinQueryWindowULong(hwnd, QWL_HMQ);
296 if (hmq == NULLHANDLE)
297 return FALSE;
298
299 // initialize the HMQ refs array
300 // @todo remove _smutex usage when we get into the DLL and initialize
301 // _fmutex + array in the DLL init routine
302 _smutex_request(&G_smtx);
303 if (!G_pHmqRefs)
304 {
305 if (_fmutex_create(&G_fmtx, 0))
306 return FALSE;
307 G_pHmqRefs = malloc(sizeof(*G_pHmqRefs) * HMQREFS_GROW);
308 if (!G_pHmqRefs)
309 return FALSE;
310 G_cHmqRefs = 0;
311 G_cHmqRefsMax = HMQREFS_GROW;
312 }
313 _smutex_release(&G_smtx);
314
315 // give all processes temporary access to hIcon
316 brc = WinSetPointerOwner(hIcon, 0, FALSE);
317 if (brc)
318 {
319 pData->ulCommand = SYSTRAYCMD_ADDICON;
320 pData->hwndSender = hwnd;
321
322 pData->u.icon.usId = usId;
323 pData->u.icon.hIcon = hIcon;
324 pData->u.icon.ulMsgId = ulMsgId;
325
326 if (!pcszToolTip)
327 pData->u.icon.szToolTip[0] = '\0';
328 else
329 {
330 strncpy(pData->u.icon.szToolTip, pcszToolTip,
331 sizeof(pData->u.icon.szToolTip) - 1);
332 // be on the safe side
333 pData->u.icon.szToolTip[sizeof(pData->u.icon.szToolTip) - 1] = '\0';
334 }
335
336 xrc = SendSysTrayCtlMsg(pData);
337 brc = xrc == XST_OK || xrc == XST_REPLACED;
338
339 // revoke temporary access to hIcon
340 DosGetInfoBlocks(NULL, &ppib);
341 WinSetPointerOwner(hIcon, ppib->pib_ulpid, TRUE);
342 }
343
344 FreeSysTrayCtlDataPtr(pData);
345
346 if (xrc == XST_OK)
347 {
348 // install the message hook for the new icon to intercept WM_XST_NOTIFY
349 // messages or increase the reference count if already done so
350 brc = FALSE;
351 _fmutex_request(&G_fmtx, _FMR_IGNINT);
352 do
353 {
354 for (i = 0; i < G_cHmqRefs; ++i)
355 if (G_pHmqRefs[i].hmq == hmq)
356 break;
357 if (i < G_cHmqRefs)
358 ++G_pHmqRefs[i].cRefs;
359 else
360 {
361 if (i == G_cHmqRefsMax)
362 {
363 PHMQREFS pNewRefs = realloc(G_pHmqRefs,
364 sizeof(*G_pHmqRefs) *
365 (G_cHmqRefsMax + HMQREFS_GROW));
366 if (!pNewRefs)
367 break;
368 G_pHmqRefs = pNewRefs;
369 G_cHmqRefsMax += HMQREFS_GROW;
370 }
371 brc = WinSetHook(hab, hmq, HK_INPUT, (PFN)InputHook, NULLHANDLE);
372 if (!brc)
373 break;
374 ++G_cHmqRefs;
375 G_pHmqRefs[i].hmq = hmq;
376 G_pHmqRefs[i].cRefs = 1;
377 }
378 brc = TRUE;
379 }
380 while (0);
381 _fmutex_release(&G_fmtx);
382
383 if (!brc)
384 xstRemoveSysTrayIcon(hwnd, usId);
385 }
386
387 return brc;
388}
389
390BOOL xstReplaceSysTrayIcon(HWND hwnd, // in: window handle associated with the icon
391 USHORT usId, // in: icon ID to change
392 HPOINTER hIcon) // in: new icon handle
393{
394 BOOL brc;
395 PPIB ppib;
396
397 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
398 if (!pData)
399 return FALSE;
400
401 // give all processes temporary access to hIcon
402 brc = WinSetPointerOwner(hIcon, 0, FALSE);
403 if (brc)
404 {
405 pData->ulCommand = SYSTRAYCMD_REPLACEICON;
406 pData->hwndSender = hwnd;
407
408 pData->u.icon.usId = usId;
409 pData->u.icon.hIcon = hIcon;
410
411 brc = SendSysTrayCtlMsg(pData) == XST_OK;
412
413 // revoke temporary access to hIcon
414 DosGetInfoBlocks(NULL, &ppib);
415 WinSetPointerOwner(hIcon, ppib->pib_ulpid, TRUE);
416 }
417
418 FreeSysTrayCtlDataPtr(pData);
419
420 return brc;
421}
422
423BOOL xstRemoveSysTrayIcon(HWND hwnd, // in: window handle associated with the icon
424 USHORT usId) // in: icon ID to remove
425{
426 BOOL brc;
427 HAB hab;
428 HMQ hmq;
429 size_t i;
430
431 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
432 if (!pData)
433 return FALSE;
434
435 hab = WinQueryAnchorBlock(hwnd);
436 hmq = WinQueryWindowULong(hwnd, QWL_HMQ);
437 if (hmq == NULLHANDLE)
438 return FALSE;
439
440 pData->ulCommand = SYSTRAYCMD_REMOVEICON;
441 pData->hwndSender = hwnd;
442 pData->u.icon.usId = usId;
443
444 brc = SendSysTrayCtlMsg(pData) == XST_OK;
445
446 FreeSysTrayCtlDataPtr(pData);
447
448 if (brc)
449 {
450 // remove the message hook if it's the last reference to the HMQ
451 _fmutex_request(&G_fmtx, _FMR_IGNINT);
452 do
453 {
454 for (i = 0; i < G_cHmqRefs; ++i)
455 if (G_pHmqRefs[i].hmq == hmq)
456 break;
457 if (i == G_cHmqRefs)
458 // unknown HMQ??
459 break;
460
461 if (--G_pHmqRefs[i].cRefs == 0)
462 WinReleaseHook(hab, hmq, HK_INPUT, (PFN)InputHook, NULLHANDLE);
463 }
464 while (0);
465 _fmutex_release(&G_fmtx);
466 }
467
468 return brc;
469}
470
471BOOL xstSetSysTrayIconToolTip(HWND hwnd, // in: window handle associated with the icon
472 USHORT usId, // in: icon ID to set the tooltip for
473 PCSZ pcszToolTip) // in: tooltip text
474{
475 BOOL brc;
476 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
477 if (!pData)
478 return FALSE;
479
480 pData->ulCommand = SYSTRAYCMD_SETTOOLTIP;
481 pData->hwndSender = hwnd;
482 pData->u.icon.usId = usId;
483
484 if (!pcszToolTip)
485 pData->u.icon.szToolTip[0] = '\0';
486 else
487 {
488 strncpy(pData->u.icon.szToolTip, pcszToolTip,
489 sizeof(pData->u.icon.szToolTip) - 1);
490 // be on the safe side
491 pData->u.icon.szToolTip[sizeof(pData->u.icon.szToolTip) - 1] = '\0';
492 }
493
494 brc = SendSysTrayCtlMsg(pData) == XST_OK;
495
496 FreeSysTrayCtlDataPtr(pData);
497
498 return brc;
499}
500
501BOOL xstShowSysTrayIconBalloon(HWND hwnd, USHORT usId, PCSZ pcszTitle,
502 PCSZ pcszText, ULONG ulFlags, ULONG ulTimeout)
503{
504 // @todo implement
505 return FALSE;
506}
507
508BOOL xstHideSysTrayIconBalloon(HWND hwnd, USHORT usId)
509{
510 // @todo implement
511 return FALSE;
512}
513
514BOOL xstQuerySysTrayIconRect(HWND hwnd, USHORT usId, PRECTL prclRect)
515{
516 BOOL brc;
517 PSYSTRAYCTLDATA pData = AllocSysTrayCtlDataPtr();
518 if (!pData)
519 return FALSE;
520
521 pData->ulCommand = SYSTRAYCMD_QUERYRECT;
522 pData->hwndSender = hwnd;
523 pData->u.icon.usId = usId;
524
525 brc = SendSysTrayCtlMsg(pData) == XST_OK;
526 if (brc)
527 {
528 *prclRect = pData->u.rect.rclIcon;
529 }
530
531 FreeSysTrayCtlDataPtr(pData);
532
533 return brc;
534}
535
536ULONG xstGetSysTrayCreatedMsgId()
537{
538 if (WM_XST_CREATED == 0)
539 WM_XST_CREATED = WinAddAtom(WinQuerySystemAtomTable(),
540 WM_XST_CREATED_ATOM);
541 return WM_XST_CREATED;
542}
543
544ULONG xstGetSysTrayMaxTextLen()
545{
546 return sizeof(((PSYSTRAYCTLDATA)0)->u.icon.szToolTip);
547}
548
Note: See TracBrowser for help on using the repository browser.