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

Last change on this file since 838 was 837, checked in by Dmitry A. Kuminov, 15 years ago

xsystray: Moved instant API documentation from .c to xsystray.h and bumped the version number to 0.1.1 due to r836.

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