Changed the GDI driver interface to pass an opaque PHYSDEV pointer
[wine] / windows / message.c
1 /*
2  * Message queues related functions
3  *
4  * Copyright 1993, 1994 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <sys/time.h>
25 #include <sys/types.h>
26
27 #include "winbase.h"
28 #include "wingdi.h"
29 #include "winuser.h"
30 #include "message.h"
31 #include "winerror.h"
32 #include "wine/server.h"
33 #include "win.h"
34 #include "heap.h"
35 #include "hook.h"
36 #include "input.h"
37 #include "spy.h"
38 #include "winpos.h"
39 #include "dde.h"
40 #include "queue.h"
41 #include "winproc.h"
42 #include "user.h"
43 #include "thread.h"
44 #include "task.h"
45 #include "controls.h"
46 #include "wine/debug.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(msg);
49 WINE_DECLARE_DEBUG_CHANNEL(key);
50
51 #define WM_NCMOUSEFIRST         WM_NCMOUSEMOVE
52 #define WM_NCMOUSELAST          WM_NCMBUTTONDBLCLK
53
54 static BYTE QueueKeyStateTable[256];
55
56
57 /***********************************************************************
58  *           is_keyboard_message
59  */
60 inline static BOOL is_keyboard_message( UINT message )
61 {
62     return (message >= WM_KEYFIRST && message <= WM_KEYLAST);
63 }
64
65
66 /***********************************************************************
67  *           is_mouse_message
68  */
69 inline static BOOL is_mouse_message( UINT message )
70 {
71     return ((message >= WM_NCMOUSEFIRST && message <= WM_NCMOUSELAST) ||
72             (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST));
73 }
74
75
76 /***********************************************************************
77  *           check_message_filter
78  */
79 inline static BOOL check_message_filter( const MSG *msg, HWND hwnd, UINT first, UINT last )
80 {
81     if (hwnd)
82     {
83         if (msg->hwnd != hwnd && !IsChild( hwnd, msg->hwnd )) return FALSE;
84     }
85     if (first || last)
86     {
87        return (msg->message >= first && msg->message <= last);
88     }
89     return TRUE;
90 }
91
92
93 /***********************************************************************
94  *           process_sent_messages
95  *
96  * Process all pending sent messages.
97  */
98 inline static void process_sent_messages(void)
99 {
100     MSG msg;
101     MSG_peek_message( &msg, 0, 0, 0, GET_MSG_REMOVE | GET_MSG_SENT_ONLY );
102 }
103
104
105 /***********************************************************************
106  *           queue_hardware_message
107  *
108  * store a hardware message in the thread queue
109  */
110 static void queue_hardware_message( MSG *msg, ULONG_PTR extra_info, enum message_type type )
111 {
112     SERVER_START_REQ( send_message )
113     {
114         req->type   = type;
115         req->id     = (void *)GetWindowThreadProcessId( msg->hwnd, NULL );
116         req->win    = msg->hwnd;
117         req->msg    = msg->message;
118         req->wparam = msg->wParam;
119         req->lparam = msg->lParam;
120         req->x      = msg->pt.x;
121         req->y      = msg->pt.y;
122         req->time   = msg->time;
123         req->info   = extra_info;
124         req->timeout = 0;
125         wine_server_call( req );
126     }
127     SERVER_END_REQ;
128 }
129
130
131 /***********************************************************************
132  *           update_queue_key_state
133  */
134 static void update_queue_key_state( UINT msg, WPARAM wp )
135 {
136     BOOL down = FALSE;
137
138     switch (msg)
139     {
140     case WM_LBUTTONDOWN:
141         down = TRUE;
142         /* fall through */
143     case WM_LBUTTONUP:
144         wp = VK_LBUTTON;
145         break;
146     case WM_MBUTTONDOWN:
147         down = TRUE;
148         /* fall through */
149     case WM_MBUTTONUP:
150         wp = VK_MBUTTON;
151         break;
152     case WM_RBUTTONDOWN:
153         down = TRUE;
154         /* fall through */
155     case WM_RBUTTONUP:
156         wp = VK_RBUTTON;
157         break;
158     case WM_KEYDOWN:
159     case WM_SYSKEYDOWN:
160         down = TRUE;
161         /* fall through */
162     case WM_KEYUP:
163     case WM_SYSKEYUP:
164         wp = wp & 0xff;
165         break;
166     }
167     if (down)
168     {
169         BYTE *p = &QueueKeyStateTable[wp];
170         if (!(*p & 0x80)) *p ^= 0x01;
171         *p |= 0x80;
172     }
173     else QueueKeyStateTable[wp] &= ~0x80;
174 }
175
176
177 /***********************************************************************
178  *           MSG_SendParentNotify
179  *
180  * Send a WM_PARENTNOTIFY to all ancestors of the given window, unless
181  * the window has the WS_EX_NOPARENTNOTIFY style.
182  */
183 static void MSG_SendParentNotify( HWND hwnd, WORD event, WORD idChild, POINT pt )
184 {
185     /* pt has to be in the client coordinates of the parent window */
186     MapWindowPoints( 0, hwnd, &pt, 1 );
187     for (;;)
188     {
189         HWND parent;
190
191         if (!(GetWindowLongA( hwnd, GWL_STYLE ) & WS_CHILD)) break;
192         if (GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_NOPARENTNOTIFY) break;
193         if (!(parent = GetParent(hwnd))) break;
194         MapWindowPoints( hwnd, parent, &pt, 1 );
195         hwnd = parent;
196         SendMessageA( hwnd, WM_PARENTNOTIFY,
197                       MAKEWPARAM( event, idChild ), MAKELPARAM( pt.x, pt.y ) );
198     }
199 }
200
201
202 /***********************************************************************
203  *          MSG_JournalPlayBackMsg
204  *
205  * Get an EVENTMSG struct via call JOURNALPLAYBACK hook function 
206  */
207 void MSG_JournalPlayBackMsg(void)
208 {
209     EVENTMSG tmpMsg;
210     MSG msg;
211     LRESULT wtime;
212     int keyDown,i;
213
214     if (!HOOK_IsHooked( WH_JOURNALPLAYBACK )) return;
215
216     wtime=HOOK_CallHooksA( WH_JOURNALPLAYBACK, HC_GETNEXT, 0, (LPARAM)&tmpMsg );
217     /*  TRACE(msg,"Playback wait time =%ld\n",wtime); */
218     if (wtime<=0)
219     {
220         wtime=0;
221         msg.message = tmpMsg.message;
222         msg.hwnd    = tmpMsg.hwnd;
223         msg.time    = tmpMsg.time;
224         if ((tmpMsg.message >= WM_KEYFIRST) && (tmpMsg.message <= WM_KEYLAST))
225         {
226             msg.wParam  = tmpMsg.paramL & 0xFF;
227             msg.lParam  = MAKELONG(tmpMsg.paramH&0x7ffff,tmpMsg.paramL>>8);
228             if (tmpMsg.message == WM_KEYDOWN || tmpMsg.message == WM_SYSKEYDOWN)
229             {
230                 for (keyDown=i=0; i<256 && !keyDown; i++)
231                     if (InputKeyStateTable[i] & 0x80)
232                         keyDown++;
233                 if (!keyDown)
234                     msg.lParam |= 0x40000000;
235                 InputKeyStateTable[msg.wParam] |= 0x80;
236                 AsyncKeyStateTable[msg.wParam] |= 0x80;
237             }
238             else                                       /* WM_KEYUP, WM_SYSKEYUP */
239             {
240                 msg.lParam |= 0xC0000000;
241                 InputKeyStateTable[msg.wParam] &= ~0x80;
242             }
243             if (InputKeyStateTable[VK_MENU] & 0x80)
244                 msg.lParam |= 0x20000000;
245             if (tmpMsg.paramH & 0x8000)              /*special_key bit*/
246                 msg.lParam |= 0x01000000;
247
248             msg.pt.x = msg.pt.y = 0;
249             queue_hardware_message( &msg, 0, MSG_HARDWARE_RAW );
250         }
251         else if ((tmpMsg.message>= WM_MOUSEFIRST) && (tmpMsg.message <= WM_MOUSELAST))
252         {
253             switch (tmpMsg.message)
254             {
255             case WM_LBUTTONDOWN:
256                 InputKeyStateTable[VK_LBUTTON] |= 0x80;
257                 AsyncKeyStateTable[VK_LBUTTON] |= 0x80;
258                 break;
259             case WM_LBUTTONUP:
260                 InputKeyStateTable[VK_LBUTTON] &= ~0x80;
261                 break;
262             case WM_MBUTTONDOWN:
263                 InputKeyStateTable[VK_MBUTTON] |= 0x80;
264                 AsyncKeyStateTable[VK_MBUTTON] |= 0x80;
265                 break;
266             case WM_MBUTTONUP:
267                 InputKeyStateTable[VK_MBUTTON] &= ~0x80;
268                 break;
269             case WM_RBUTTONDOWN:
270                 InputKeyStateTable[VK_RBUTTON] |= 0x80;
271                 AsyncKeyStateTable[VK_RBUTTON] |= 0x80;
272                 break;
273             case WM_RBUTTONUP:
274                 InputKeyStateTable[VK_RBUTTON] &= ~0x80;
275                 break;
276             }
277             SetCursorPos(tmpMsg.paramL,tmpMsg.paramH);
278             msg.lParam=MAKELONG(tmpMsg.paramL,tmpMsg.paramH);
279             msg.wParam=0;
280             if (InputKeyStateTable[VK_LBUTTON] & 0x80) msg.wParam |= MK_LBUTTON;
281             if (InputKeyStateTable[VK_MBUTTON] & 0x80) msg.wParam |= MK_MBUTTON;
282             if (InputKeyStateTable[VK_RBUTTON] & 0x80) msg.wParam |= MK_RBUTTON;
283
284             msg.pt.x = tmpMsg.paramL;
285             msg.pt.y = tmpMsg.paramH;
286             queue_hardware_message( &msg, 0, MSG_HARDWARE_RAW );
287         }
288         HOOK_CallHooksA( WH_JOURNALPLAYBACK, HC_SKIP, 0, (LPARAM)&tmpMsg);
289     }
290     else
291     {
292         if( tmpMsg.message == WM_QUEUESYNC )
293             if (HOOK_IsHooked( WH_CBT ))
294                 HOOK_CallHooksA( WH_CBT, HCBT_QS, 0, 0L);
295     }
296 }
297
298
299 /***********************************************************************
300  *          process_raw_keyboard_message
301  *
302  * returns TRUE if the contents of 'msg' should be passed to the application
303  */
304 static BOOL process_raw_keyboard_message( MSG *msg, ULONG_PTR extra_info )
305 {
306     if (!(msg->hwnd = GetFocus()))
307     {
308         /* Send the message to the active window instead,  */
309         /* translating messages to their WM_SYS equivalent */
310         msg->hwnd = GetActiveWindow();
311         if (msg->message < WM_SYSKEYDOWN) msg->message += WM_SYSKEYDOWN - WM_KEYDOWN;
312     }
313
314     if (HOOK_IsHooked( WH_JOURNALRECORD ))
315     {
316         EVENTMSG event;
317
318         event.message = msg->message;
319         event.hwnd    = msg->hwnd;
320         event.time    = msg->time;
321         event.paramL  = (msg->wParam & 0xFF) | (HIWORD(msg->lParam) << 8);
322         event.paramH  = msg->lParam & 0x7FFF;
323         if (HIWORD(msg->lParam) & 0x0100) event.paramH |= 0x8000; /* special_key - bit */
324         HOOK_CallHooksA( WH_JOURNALRECORD, HC_ACTION, 0, (LPARAM)&event );
325     }
326
327     return (msg->hwnd != 0);
328 }
329
330
331 /***********************************************************************
332  *          process_cooked_keyboard_message
333  *
334  * returns TRUE if the contents of 'msg' should be passed to the application
335  */
336 static BOOL process_cooked_keyboard_message( MSG *msg, BOOL remove )
337 {
338     if (remove)
339     {
340         update_queue_key_state( msg->message, msg->wParam );
341
342         /* Handle F1 key by sending out WM_HELP message */
343         if ((msg->message == WM_KEYUP) &&
344             (msg->wParam == VK_F1) &&
345             (msg->hwnd != GetDesktopWindow()) &&
346             !MENU_IsMenuActive())
347         {
348             HELPINFO hi;
349             hi.cbSize = sizeof(HELPINFO);
350             hi.iContextType = HELPINFO_WINDOW;
351             hi.iCtrlId = GetWindowLongA( msg->hwnd, GWL_ID );
352             hi.hItemHandle = msg->hwnd;
353             hi.dwContextId = GetWindowContextHelpId( msg->hwnd );
354             hi.MousePos = msg->pt;
355             SendMessageA(msg->hwnd, WM_HELP, 0, (LPARAM)&hi);
356         }
357     }
358
359     if (HOOK_CallHooksA( WH_KEYBOARD, remove ? HC_ACTION : HC_NOREMOVE,
360                          LOWORD(msg->wParam), msg->lParam ))
361     {
362         /* skip this message */
363         HOOK_CallHooksA( WH_CBT, HCBT_KEYSKIPPED, LOWORD(msg->wParam), msg->lParam );
364         return FALSE;
365     }
366     return TRUE;
367 }
368
369
370 /***********************************************************************
371  *          process_raw_mouse_message
372  *
373  * returns TRUE if the contents of 'msg' should be passed to the application
374  */
375 static BOOL process_raw_mouse_message( MSG *msg, ULONG_PTR extra_info )
376 {
377     static MSG clk_msg;
378
379     POINT pt;
380     INT ht, hittest;
381
382     /* find the window to dispatch this mouse message to */
383
384     hittest = HTCLIENT;
385     if (!(msg->hwnd = PERQDATA_GetCaptureWnd( &ht )))
386     {
387         /* If no capture HWND, find window which contains the mouse position.
388          * Also find the position of the cursor hot spot (hittest) */
389         HWND hWndScope = (HWND)extra_info;
390
391         if (!IsWindow(hWndScope)) hWndScope = 0;
392         if (!(msg->hwnd = WINPOS_WindowFromPoint( hWndScope, msg->pt, &hittest )))
393             msg->hwnd = GetDesktopWindow();
394         ht = hittest;
395     }
396
397     if (HOOK_IsHooked( WH_JOURNALRECORD ))
398     {
399         EVENTMSG event;
400         event.message = msg->message;
401         event.time    = msg->time;
402         event.hwnd    = msg->hwnd;
403         event.paramL  = msg->pt.x;
404         event.paramH  = msg->pt.y;
405         HOOK_CallHooksA( WH_JOURNALRECORD, HC_ACTION, 0, (LPARAM)&event );
406     }
407
408     /* translate double clicks */
409
410     if ((msg->message == WM_LBUTTONDOWN) ||
411         (msg->message == WM_RBUTTONDOWN) ||
412         (msg->message == WM_MBUTTONDOWN))
413     {
414         BOOL update = TRUE;
415         /* translate double clicks -
416          * note that ...MOUSEMOVEs can slip in between
417          * ...BUTTONDOWN and ...BUTTONDBLCLK messages */
418
419         if (GetClassLongA( msg->hwnd, GCL_STYLE ) & CS_DBLCLKS || ht != HTCLIENT )
420         {
421            if ((msg->message == clk_msg.message) &&
422                (msg->hwnd == clk_msg.hwnd) &&
423                (msg->time - clk_msg.time < GetDoubleClickTime()) &&
424                (abs(msg->pt.x - clk_msg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
425                (abs(msg->pt.y - clk_msg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
426            {
427                msg->message += (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
428                clk_msg.message = 0;
429                update = FALSE;
430            }
431         }
432         /* update static double click conditions */
433         if (update) clk_msg = *msg;
434     }
435
436     pt = msg->pt;
437     /* Note: windows has no concept of a non-client wheel message */
438     if (hittest != HTCLIENT && msg->message != WM_MOUSEWHEEL)
439     {
440         msg->message += WM_NCMOUSEMOVE - WM_MOUSEMOVE;
441         msg->wParam = hittest;
442     }
443     else ScreenToClient( msg->hwnd, &pt );
444     msg->lParam = MAKELONG( pt.x, pt.y );
445     return TRUE;
446 }
447
448
449 /***********************************************************************
450  *          process_cooked_mouse_message
451  *
452  * returns TRUE if the contents of 'msg' should be passed to the application
453  */
454 static BOOL process_cooked_mouse_message( MSG *msg, ULONG_PTR extra_info, BOOL remove )
455 {
456     INT hittest = HTCLIENT;
457     UINT raw_message = msg->message;
458     BOOL eatMsg;
459
460     if (msg->message >= WM_NCMOUSEFIRST && msg->message <= WM_NCMOUSELAST)
461     {
462         raw_message += WM_MOUSEFIRST - WM_NCMOUSEFIRST;
463         hittest = msg->wParam;
464     }
465     if (raw_message == WM_LBUTTONDBLCLK ||
466         raw_message == WM_RBUTTONDBLCLK ||
467         raw_message == WM_MBUTTONDBLCLK)
468     {
469         raw_message += WM_LBUTTONDOWN - WM_LBUTTONDBLCLK;
470     }
471
472     if (remove) update_queue_key_state( raw_message, 0 );
473
474     if (HOOK_IsHooked( WH_MOUSE ))
475     {
476         MOUSEHOOKSTRUCT hook;
477         hook.pt           = msg->pt;
478         hook.hwnd         = msg->hwnd;
479         hook.wHitTestCode = hittest;
480         hook.dwExtraInfo  = extra_info;
481         if (HOOK_CallHooksA( WH_MOUSE, remove ? HC_ACTION : HC_NOREMOVE,
482                              msg->message, (LPARAM)&hook ))
483         {
484             hook.pt           = msg->pt;
485             hook.hwnd         = msg->hwnd;
486             hook.wHitTestCode = hittest;
487             hook.dwExtraInfo  = extra_info;
488             HOOK_CallHooksA( WH_CBT, HCBT_CLICKSKIPPED, msg->message, (LPARAM)&hook );
489             return FALSE;
490         }
491     }
492
493     if ((hittest == HTERROR) || (hittest == HTNOWHERE))
494     {
495         SendMessageA( msg->hwnd, WM_SETCURSOR, msg->hwnd, MAKELONG( hittest, raw_message ));
496         return FALSE;
497     }
498
499     if (!remove || GetCapture()) return TRUE;
500
501     eatMsg = FALSE;
502
503     if ((raw_message == WM_LBUTTONDOWN) ||
504         (raw_message == WM_RBUTTONDOWN) ||
505         (raw_message == WM_MBUTTONDOWN))
506     {
507         HWND hwndTop = GetAncestor( msg->hwnd, GA_ROOT );
508
509         /* Send the WM_PARENTNOTIFY,
510          * note that even for double/nonclient clicks
511          * notification message is still WM_L/M/RBUTTONDOWN.
512          */
513         MSG_SendParentNotify( msg->hwnd, raw_message, 0, msg->pt );
514
515         /* Activate the window if needed */
516
517         if (msg->hwnd != GetActiveWindow() && hwndTop != GetDesktopWindow())
518         {
519             LONG ret = SendMessageA( msg->hwnd, WM_MOUSEACTIVATE, hwndTop,
520                                      MAKELONG( hittest, raw_message ) );
521
522             switch(ret)
523             {
524             case MA_NOACTIVATEANDEAT:
525                 eatMsg = TRUE;
526                 /* fall through */
527             case MA_NOACTIVATE:
528                 break;
529             case MA_ACTIVATEANDEAT:
530                 eatMsg = TRUE;
531                 /* fall through */
532             case MA_ACTIVATE:
533             case 0:
534                 if (hwndTop != GetForegroundWindow() )
535                 {
536                     if (!WINPOS_SetActiveWindow( hwndTop, TRUE , TRUE ))
537                         eatMsg = TRUE;
538                 }
539                 break;
540             default:
541                 WARN( "unknown WM_MOUSEACTIVATE code %ld\n", ret );
542                 break;
543             }
544         }
545     }
546
547     /* send the WM_SETCURSOR message */
548
549     /* Windows sends the normal mouse message as the message parameter
550        in the WM_SETCURSOR message even if it's non-client mouse message */
551     SendMessageA( msg->hwnd, WM_SETCURSOR, msg->hwnd, MAKELONG( hittest, raw_message ));
552
553     return !eatMsg;
554 }
555
556
557 /***********************************************************************
558  *          process_hardware_message
559  *
560  * returns TRUE if the contents of 'msg' should be passed to the application
561  */
562 BOOL MSG_process_raw_hardware_message( MSG *msg, ULONG_PTR extra_info, HWND hwnd_filter,
563                                        UINT first, UINT last, BOOL remove )
564 {
565     if (is_keyboard_message( msg->message ))
566     {
567         if (!process_raw_keyboard_message( msg, extra_info )) return FALSE;
568     }
569     else if (is_mouse_message( msg->message ))
570     {
571         if (!process_raw_mouse_message( msg, extra_info )) return FALSE;
572     }
573     else
574     {
575         ERR( "unknown message type %x\n", msg->message );
576         return FALSE;
577     }
578
579     /* check destination thread and filters */
580     if (!check_message_filter( msg, hwnd_filter, first, last ) ||
581         !WIN_IsCurrentThread( msg->hwnd ))
582     {
583         /* queue it for later, or for another thread */
584         queue_hardware_message( msg, extra_info, MSG_HARDWARE_COOKED );
585         return FALSE;
586     }
587
588     /* save the message in the cooked queue if we didn't want to remove it */
589     if (!remove) queue_hardware_message( msg, extra_info, MSG_HARDWARE_COOKED );
590     return TRUE;
591 }
592
593
594 /***********************************************************************
595  *          MSG_process_cooked_hardware_message
596  *
597  * returns TRUE if the contents of 'msg' should be passed to the application
598  */
599 BOOL MSG_process_cooked_hardware_message( MSG *msg, ULONG_PTR extra_info, BOOL remove )
600 {
601     if (is_keyboard_message( msg->message ))
602         return process_cooked_keyboard_message( msg, remove );
603
604     if (is_mouse_message( msg->message ))
605         return process_cooked_mouse_message( msg, extra_info, remove );
606
607     ERR( "unknown message type %x\n", msg->message );
608     return FALSE;
609 }
610
611
612 /**********************************************************************
613  *              GetKeyState (USER.106)
614  */
615 INT16 WINAPI GetKeyState16(INT16 vkey)
616 {
617     return GetKeyState(vkey);
618 }
619
620
621 /**********************************************************************
622  *              GetKeyState (USER32.@)
623  *
624  * An application calls the GetKeyState function in response to a
625  * keyboard-input message.  This function retrieves the state of the key
626  * at the time the input message was generated.  (SDK 3.1 Vol 2. p 390)
627  */
628 SHORT WINAPI GetKeyState(INT vkey)
629 {
630     INT retval;
631
632     if (vkey >= 'a' && vkey <= 'z') vkey += 'A' - 'a';
633     retval = ((WORD)(QueueKeyStateTable[vkey] & 0x80) << 8 ) | (QueueKeyStateTable[vkey] & 0x01);
634     /* TRACE(key, "(0x%x) -> %x\n", vkey, retval); */
635     return retval;
636 }
637
638
639 /**********************************************************************
640  *              GetKeyboardState (USER.222)
641  *              GetKeyboardState (USER32.@)
642  *
643  * An application calls the GetKeyboardState function in response to a
644  * keyboard-input message.  This function retrieves the state of the keyboard
645  * at the time the input message was generated.  (SDK 3.1 Vol 2. p 387)
646  */
647 BOOL WINAPI GetKeyboardState(LPBYTE lpKeyState)
648 {
649     TRACE_(key)("(%p)\n", lpKeyState);
650     if (lpKeyState) memcpy(lpKeyState, QueueKeyStateTable, 256);
651     return TRUE;
652 }
653
654
655 /**********************************************************************
656  *              SetKeyboardState (USER.223)
657  *              SetKeyboardState (USER32.@)
658  */
659 BOOL WINAPI SetKeyboardState(LPBYTE lpKeyState)
660 {
661     TRACE_(key)("(%p)\n", lpKeyState);
662     if (lpKeyState) memcpy(QueueKeyStateTable, lpKeyState, 256);
663     return TRUE;
664 }
665
666
667 /***********************************************************************
668  *              WaitMessage (USER.112) Suspend thread pending messages
669  *              WaitMessage (USER32.@) Suspend thread pending messages
670  *
671  * WaitMessage() suspends a thread until events appear in the thread's
672  * queue.
673  */
674 BOOL WINAPI WaitMessage(void)
675 {
676     return (MsgWaitForMultipleObjectsEx( 0, NULL, INFINITE, QS_ALLINPUT, 0 ) != WAIT_FAILED);
677 }
678
679
680 /***********************************************************************
681  *              MsgWaitForMultipleObjectsEx   (USER32.@)
682  */
683 DWORD WINAPI MsgWaitForMultipleObjectsEx( DWORD count, CONST HANDLE *pHandles,
684                                           DWORD timeout, DWORD mask, DWORD flags )
685 {
686     HANDLE handles[MAXIMUM_WAIT_OBJECTS];
687     DWORD i, ret;
688     MESSAGEQUEUE *msgQueue;
689
690     if (count > MAXIMUM_WAIT_OBJECTS-1)
691     {
692         SetLastError( ERROR_INVALID_PARAMETER );
693         return WAIT_FAILED;
694     }
695
696     if (!(msgQueue = QUEUE_Current())) return WAIT_FAILED;
697
698     /* set the queue mask */
699     SERVER_START_REQ( set_queue_mask )
700     {
701         req->wake_mask    = (flags & MWMO_INPUTAVAILABLE) ? mask : 0;
702         req->changed_mask = mask;
703         req->skip_wait    = 0;
704         wine_server_call( req );
705     }
706     SERVER_END_REQ;
707
708     /* Add the thread event to the handle list */
709     for (i = 0; i < count; i++) handles[i] = pHandles[i];
710     handles[count] = msgQueue->server_queue;
711
712
713     if (USER_Driver.pMsgWaitForMultipleObjectsEx)
714     {
715         ret = USER_Driver.pMsgWaitForMultipleObjectsEx( count+1, handles, timeout, mask, flags );
716         if (ret == count+1) ret = count; /* pretend the msg queue is ready */
717     }
718     else
719         ret = WaitForMultipleObjectsEx( count+1, handles, flags & MWMO_WAITALL,
720                                         timeout, flags & MWMO_ALERTABLE );
721     return ret;
722 }
723
724
725 /***********************************************************************
726  *              MsgWaitForMultipleObjects (USER32.@)
727  */
728 DWORD WINAPI MsgWaitForMultipleObjects( DWORD count, CONST HANDLE *handles,
729                                         BOOL wait_all, DWORD timeout, DWORD mask )
730 {
731     return MsgWaitForMultipleObjectsEx( count, handles, timeout, mask,
732                                         wait_all ? MWMO_WAITALL : 0 );
733 }
734
735
736 /***********************************************************************
737  *              WaitForInputIdle (USER32.@)
738  */
739 DWORD WINAPI WaitForInputIdle( HANDLE hProcess, DWORD dwTimeOut )
740 {
741     DWORD start_time, elapsed, ret;
742     HANDLE idle_event = -1;
743
744     SERVER_START_REQ( wait_input_idle )
745     {
746         req->handle = hProcess;
747         req->timeout = dwTimeOut;
748         if (!(ret = wine_server_call_err( req ))) idle_event = reply->event;
749     }
750     SERVER_END_REQ;
751     if (ret) return WAIT_FAILED;  /* error */
752     if (!idle_event) return 0;  /* no event to wait on */
753
754     start_time = GetTickCount();
755     elapsed = 0;
756
757     TRACE("waiting for %x\n", idle_event );
758     do
759     {
760         ret = MsgWaitForMultipleObjects ( 1, &idle_event, FALSE, dwTimeOut - elapsed, QS_SENDMESSAGE );
761         switch (ret)
762         {
763         case WAIT_OBJECT_0+1:
764             process_sent_messages();
765             break;
766         case WAIT_TIMEOUT:
767         case WAIT_FAILED:
768             TRACE("timeout or error\n");
769             return ret;
770         default:
771             TRACE("finished\n");
772             return 0;
773         }
774         if (dwTimeOut != INFINITE)
775         {
776             elapsed = GetTickCount() - start_time;
777             if (elapsed > dwTimeOut)
778                 break;
779         }
780     }
781     while (1);
782
783     return WAIT_TIMEOUT;
784 }
785
786
787 /***********************************************************************
788  *              UserYield (USER.332)
789  *              UserYield16 (USER32.@)
790  */
791 void WINAPI UserYield16(void)
792 {
793    DWORD count;
794
795     /* Handle sent messages */
796     process_sent_messages();
797
798     /* Yield */
799     ReleaseThunkLock(&count);
800     if (count)
801     {
802         RestoreThunkLock(count);
803         /* Handle sent messages again */
804         process_sent_messages();
805     }
806 }
807
808
809 struct accent_char
810 {
811     BYTE ac_accent;
812     BYTE ac_char;
813     BYTE ac_result;
814 };
815
816 static const struct accent_char accent_chars[] =
817 {
818 /* A good idea should be to read /usr/X11/lib/X11/locale/iso8859-x/Compose */
819     {'`', 'A', '\300'},  {'`', 'a', '\340'},
820     {'\'', 'A', '\301'}, {'\'', 'a', '\341'},
821     {'^', 'A', '\302'},  {'^', 'a', '\342'},
822     {'~', 'A', '\303'},  {'~', 'a', '\343'},
823     {'"', 'A', '\304'},  {'"', 'a', '\344'},
824     {'O', 'A', '\305'},  {'o', 'a', '\345'},
825     {'0', 'A', '\305'},  {'0', 'a', '\345'},
826     {'A', 'A', '\305'},  {'a', 'a', '\345'},
827     {'A', 'E', '\306'},  {'a', 'e', '\346'},
828     {',', 'C', '\307'},  {',', 'c', '\347'},
829     {'`', 'E', '\310'},  {'`', 'e', '\350'},
830     {'\'', 'E', '\311'}, {'\'', 'e', '\351'},
831     {'^', 'E', '\312'},  {'^', 'e', '\352'},
832     {'"', 'E', '\313'},  {'"', 'e', '\353'},
833     {'`', 'I', '\314'},  {'`', 'i', '\354'},
834     {'\'', 'I', '\315'}, {'\'', 'i', '\355'},
835     {'^', 'I', '\316'},  {'^', 'i', '\356'},
836     {'"', 'I', '\317'},  {'"', 'i', '\357'},
837     {'-', 'D', '\320'},  {'-', 'd', '\360'},
838     {'~', 'N', '\321'},  {'~', 'n', '\361'},
839     {'`', 'O', '\322'},  {'`', 'o', '\362'},
840     {'\'', 'O', '\323'}, {'\'', 'o', '\363'},
841     {'^', 'O', '\324'},  {'^', 'o', '\364'},
842     {'~', 'O', '\325'},  {'~', 'o', '\365'},
843     {'"', 'O', '\326'},  {'"', 'o', '\366'},
844     {'/', 'O', '\330'},  {'/', 'o', '\370'},
845     {'`', 'U', '\331'},  {'`', 'u', '\371'},
846     {'\'', 'U', '\332'}, {'\'', 'u', '\372'},
847     {'^', 'U', '\333'},  {'^', 'u', '\373'},
848     {'"', 'U', '\334'},  {'"', 'u', '\374'},
849     {'\'', 'Y', '\335'}, {'\'', 'y', '\375'},
850     {'T', 'H', '\336'},  {'t', 'h', '\376'},
851     {'s', 's', '\337'},  {'"', 'y', '\377'},
852     {'s', 'z', '\337'},  {'i', 'j', '\377'},
853         /* iso-8859-2 uses this */
854     {'<', 'L', '\245'},  {'<', 'l', '\265'},    /* caron */
855     {'<', 'S', '\251'},  {'<', 's', '\271'},
856     {'<', 'T', '\253'},  {'<', 't', '\273'},
857     {'<', 'Z', '\256'},  {'<', 'z', '\276'},
858     {'<', 'C', '\310'},  {'<', 'c', '\350'},
859     {'<', 'E', '\314'},  {'<', 'e', '\354'},
860     {'<', 'D', '\317'},  {'<', 'd', '\357'},
861     {'<', 'N', '\322'},  {'<', 'n', '\362'},
862     {'<', 'R', '\330'},  {'<', 'r', '\370'},
863     {';', 'A', '\241'},  {';', 'a', '\261'},    /* ogonek */
864     {';', 'E', '\312'},  {';', 'e', '\332'},
865     {'\'', 'Z', '\254'}, {'\'', 'z', '\274'},   /* acute */
866     {'\'', 'R', '\300'}, {'\'', 'r', '\340'},
867     {'\'', 'L', '\305'}, {'\'', 'l', '\345'},
868     {'\'', 'C', '\306'}, {'\'', 'c', '\346'},
869     {'\'', 'N', '\321'}, {'\'', 'n', '\361'},
870 /*  collision whith S, from iso-8859-9 !!! */
871     {',', 'S', '\252'},  {',', 's', '\272'},    /* cedilla */
872     {',', 'T', '\336'},  {',', 't', '\376'},
873     {'.', 'Z', '\257'},  {'.', 'z', '\277'},    /* dot above */
874     {'/', 'L', '\243'},  {'/', 'l', '\263'},    /* slash */
875     {'/', 'D', '\320'},  {'/', 'd', '\360'},
876     {'(', 'A', '\303'},  {'(', 'a', '\343'},    /* breve */
877     {'\275', 'O', '\325'}, {'\275', 'o', '\365'},       /* double acute */
878     {'\275', 'U', '\334'}, {'\275', 'u', '\374'},
879     {'0', 'U', '\332'},  {'0', 'u', '\372'},    /* ring above */
880         /* iso-8859-3 uses this */
881     {'/', 'H', '\241'},  {'/', 'h', '\261'},    /* slash */
882     {'>', 'H', '\246'},  {'>', 'h', '\266'},    /* circumflex */
883     {'>', 'J', '\254'},  {'>', 'j', '\274'},
884     {'>', 'C', '\306'},  {'>', 'c', '\346'},
885     {'>', 'G', '\330'},  {'>', 'g', '\370'},
886     {'>', 'S', '\336'},  {'>', 's', '\376'},
887 /*  collision whith G( from iso-8859-9 !!!   */
888     {'(', 'G', '\253'},  {'(', 'g', '\273'},    /* breve */
889     {'(', 'U', '\335'},  {'(', 'u', '\375'},
890 /*  collision whith I. from iso-8859-3 !!!   */
891     {'.', 'I', '\251'},  {'.', 'i', '\271'},    /* dot above */
892     {'.', 'C', '\305'},  {'.', 'c', '\345'},
893     {'.', 'G', '\325'},  {'.', 'g', '\365'},
894         /* iso-8859-4 uses this */
895     {',', 'R', '\243'},  {',', 'r', '\263'},    /* cedilla */
896     {',', 'L', '\246'},  {',', 'l', '\266'},
897     {',', 'G', '\253'},  {',', 'g', '\273'},
898     {',', 'N', '\321'},  {',', 'n', '\361'},
899     {',', 'K', '\323'},  {',', 'k', '\363'},
900     {'~', 'I', '\245'},  {'~', 'i', '\265'},    /* tilde */
901     {'-', 'E', '\252'},  {'-', 'e', '\272'},    /* macron */
902     {'-', 'A', '\300'},  {'-', 'a', '\340'},
903     {'-', 'I', '\317'},  {'-', 'i', '\357'},
904     {'-', 'O', '\322'},  {'-', 'o', '\362'},
905     {'-', 'U', '\336'},  {'-', 'u', '\376'},
906     {'/', 'T', '\254'},  {'/', 't', '\274'},    /* slash */
907     {'.', 'E', '\314'},  {'.', 'e', '\344'},    /* dot above */
908     {';', 'I', '\307'},  {';', 'i', '\347'},    /* ogonek */
909     {';', 'U', '\331'},  {';', 'u', '\371'},
910         /* iso-8859-9 uses this */
911         /* iso-8859-9 has really bad choosen G( S, and I. as they collide
912          * whith the same letters on other iso-8859-x (that is they are on
913          * different places :-( ), if you use turkish uncomment these and
914          * comment out the lines in iso-8859-2 and iso-8859-3 sections
915          * FIXME: should be dynamic according to chosen language
916          *        if/when Wine has turkish support.  
917          */ 
918 /*  collision whith G( from iso-8859-3 !!!   */
919 /*  {'(', 'G', '\320'},  {'(', 'g', '\360'}, */ /* breve */
920 /*  collision whith S, from iso-8859-2 !!! */
921 /*  {',', 'S', '\336'},  {',', 's', '\376'}, */ /* cedilla */
922 /*  collision whith I. from iso-8859-3 !!!   */
923 /*  {'.', 'I', '\335'},  {'.', 'i', '\375'}, */ /* dot above */
924 };
925
926
927 /***********************************************************************
928  *              TranslateMessage (USER32.@)
929  *
930  * Implementation of TranslateMessage.
931  *
932  * TranslateMessage translates virtual-key messages into character-messages,
933  * as follows :
934  * WM_KEYDOWN/WM_KEYUP combinations produce a WM_CHAR or WM_DEADCHAR message.
935  * ditto replacing WM_* with WM_SYS*
936  * This produces WM_CHAR messages only for keys mapped to ASCII characters
937  * by the keyboard driver.
938  */
939 BOOL WINAPI TranslateMessage( const MSG *msg )
940 {
941     static int dead_char;
942     UINT message;
943     WCHAR wp[2];
944
945     if (msg->message >= WM_KEYFIRST && msg->message <= WM_KEYLAST)
946         TRACE_(key)("(%s, %04X, %08lX)\n",
947                     SPY_GetMsgName(msg->message, msg->hwnd), msg->wParam, msg->lParam );
948
949     if ((msg->message != WM_KEYDOWN) && (msg->message != WM_SYSKEYDOWN)) return FALSE;
950
951     TRACE_(key)("Translating key %s (%04x), scancode %02x\n",
952                  SPY_GetVKeyName(msg->wParam), msg->wParam, LOBYTE(HIWORD(msg->lParam)));
953
954     /* FIXME : should handle ToUnicode yielding 2 */
955     switch (ToUnicode(msg->wParam, HIWORD(msg->lParam), QueueKeyStateTable, wp, 2, 0))
956     {
957     case 1:
958         message = (msg->message == WM_KEYDOWN) ? WM_CHAR : WM_SYSCHAR;
959         /* Should dead chars handling go in ToAscii ? */
960         if (dead_char)
961         {
962             int i;
963
964             if (wp[0] == ' ') wp[0] =  dead_char;
965             if (dead_char == 0xa2) dead_char = '(';
966             else if (dead_char == 0xa8) dead_char = '"';
967             else if (dead_char == 0xb2) dead_char = ';';
968             else if (dead_char == 0xb4) dead_char = '\'';
969             else if (dead_char == 0xb7) dead_char = '<';
970             else if (dead_char == 0xb8) dead_char = ',';
971             else if (dead_char == 0xff) dead_char = '.';
972             for (i = 0; i < sizeof(accent_chars)/sizeof(accent_chars[0]); i++)
973                 if ((accent_chars[i].ac_accent == dead_char) &&
974                     (accent_chars[i].ac_char == wp[0]))
975                 {
976                     wp[0] = accent_chars[i].ac_result;
977                     break;
978                 }
979             dead_char = 0;
980         }
981         TRACE_(key)("1 -> PostMessage(%s)\n", SPY_GetMsgName(message, msg->hwnd));
982         PostMessageW( msg->hwnd, message, wp[0], msg->lParam );
983         return TRUE;
984
985     case -1:
986         message = (msg->message == WM_KEYDOWN) ? WM_DEADCHAR : WM_SYSDEADCHAR;
987         dead_char = wp[0];
988         TRACE_(key)("-1 -> PostMessage(%s)\n", SPY_GetMsgName(message, msg->hwnd));
989         PostMessageW( msg->hwnd, message, wp[0], msg->lParam );
990         return TRUE;
991     }
992     return FALSE;
993 }
994
995
996 /***********************************************************************
997  *              DispatchMessageA (USER32.@)
998  */
999 LONG WINAPI DispatchMessageA( const MSG* msg )
1000 {
1001     WND * wndPtr;
1002     LONG retval;
1003     int painting;
1004     WNDPROC winproc;
1005
1006       /* Process timer messages */
1007     if ((msg->message == WM_TIMER) || (msg->message == WM_SYSTIMER))
1008     {
1009         if (msg->lParam)
1010         {
1011 /*            HOOK_CallHooks32A( WH_CALLWNDPROC, HC_ACTION, 0, FIXME ); */
1012
1013             /* before calling window proc, verify whether timer is still valid;
1014                there's a slim chance that the application kills the timer
1015                between GetMessage and DispatchMessage API calls */
1016             if (!TIMER_IsTimerValid(msg->hwnd, (UINT) msg->wParam, (HWINDOWPROC) msg->lParam))
1017                 return 0; /* invalid winproc */
1018
1019             return CallWindowProcA( (WNDPROC)msg->lParam, msg->hwnd,
1020                                    msg->message, msg->wParam, GetTickCount() );
1021         }
1022     }
1023
1024     if (!(wndPtr = WIN_GetPtr( msg->hwnd )))
1025     {
1026         if (msg->hwnd) SetLastError( ERROR_INVALID_WINDOW_HANDLE );
1027         return 0;
1028     }
1029     if (wndPtr == WND_OTHER_PROCESS)
1030     {
1031         if (IsWindow( msg->hwnd ))
1032             ERR( "cannot dispatch msg to other process window %x\n", msg->hwnd );
1033         SetLastError( ERROR_INVALID_WINDOW_HANDLE );
1034         return 0;
1035     }
1036     if (!(winproc = wndPtr->winproc))
1037     {
1038         WIN_ReleasePtr( wndPtr );
1039         return 0;
1040     }
1041     painting = (msg->message == WM_PAINT);
1042     if (painting) wndPtr->flags |= WIN_NEEDS_BEGINPAINT;
1043     WIN_ReleasePtr( wndPtr );
1044 /*    hook_CallHooks32A( WH_CALLWNDPROC, HC_ACTION, 0, FIXME ); */
1045
1046     SPY_EnterMessage( SPY_DISPATCHMESSAGE, msg->hwnd, msg->message,
1047                       msg->wParam, msg->lParam );
1048     retval = CallWindowProcA( winproc, msg->hwnd, msg->message,
1049                               msg->wParam, msg->lParam );
1050     SPY_ExitMessage( SPY_RESULT_OK, msg->hwnd, msg->message, retval,
1051                      msg->wParam, msg->lParam );
1052
1053     if (painting && (wndPtr = WIN_GetPtr( msg->hwnd )) && (wndPtr != WND_OTHER_PROCESS))
1054     {
1055         BOOL validate = ((wndPtr->flags & WIN_NEEDS_BEGINPAINT) && wndPtr->hrgnUpdate);
1056         wndPtr->flags &= ~WIN_NEEDS_BEGINPAINT;
1057         WIN_ReleasePtr( wndPtr );
1058         if (validate)
1059         {
1060             ERR( "BeginPaint not called on WM_PAINT for hwnd %04x!\n", msg->hwnd );
1061             /* Validate the update region to avoid infinite WM_PAINT loop */
1062             RedrawWindow( msg->hwnd, NULL, 0,
1063                           RDW_NOFRAME | RDW_VALIDATE | RDW_NOCHILDREN | RDW_NOINTERNALPAINT );
1064         }
1065     }
1066     return retval;
1067 }
1068
1069
1070 /***********************************************************************
1071  *              DispatchMessageW (USER32.@) Process Message
1072  *
1073  * Process the message specified in the structure *_msg_.
1074  *
1075  * If the lpMsg parameter points to a WM_TIMER message and the
1076  * parameter of the WM_TIMER message is not NULL, the lParam parameter
1077  * points to the function that is called instead of the window
1078  * procedure.
1079  *  
1080  * The message must be valid.
1081  *
1082  * RETURNS
1083  *
1084  *   DispatchMessage() returns the result of the window procedure invoked.
1085  *
1086  * CONFORMANCE
1087  *
1088  *   ECMA-234, Win32 
1089  *
1090  */
1091 LONG WINAPI DispatchMessageW( const MSG* msg )
1092 {
1093     WND * wndPtr;
1094     LONG retval;
1095     int painting;
1096     WNDPROC winproc;
1097
1098       /* Process timer messages */
1099     if ((msg->message == WM_TIMER) || (msg->message == WM_SYSTIMER))
1100     {
1101         if (msg->lParam)
1102         {
1103 /*            HOOK_CallHooks32W( WH_CALLWNDPROC, HC_ACTION, 0, FIXME ); */
1104
1105             /* before calling window proc, verify whether timer is still valid;
1106                there's a slim chance that the application kills the timer
1107                between GetMessage and DispatchMessage API calls */
1108             if (!TIMER_IsTimerValid(msg->hwnd, (UINT) msg->wParam, (HWINDOWPROC) msg->lParam))
1109                 return 0; /* invalid winproc */
1110
1111             return CallWindowProcW( (WNDPROC)msg->lParam, msg->hwnd,
1112                                    msg->message, msg->wParam, GetTickCount() );
1113         }
1114     }
1115
1116     if (!(wndPtr = WIN_GetPtr( msg->hwnd )))
1117     {
1118         if (msg->hwnd) SetLastError( ERROR_INVALID_WINDOW_HANDLE );
1119         return 0;
1120     }
1121     if (wndPtr == WND_OTHER_PROCESS)
1122     {
1123         if (IsWindow( msg->hwnd ))
1124             ERR( "cannot dispatch msg to other process window %x\n", msg->hwnd );
1125         SetLastError( ERROR_INVALID_WINDOW_HANDLE );
1126         return 0;
1127     }
1128     if (!(winproc = wndPtr->winproc))
1129     {
1130         WIN_ReleasePtr( wndPtr );
1131         return 0;
1132     }
1133     painting = (msg->message == WM_PAINT);
1134     if (painting) wndPtr->flags |= WIN_NEEDS_BEGINPAINT;
1135     WIN_ReleasePtr( wndPtr );
1136 /*    HOOK_CallHooks32W( WH_CALLWNDPROC, HC_ACTION, 0, FIXME ); */
1137
1138     SPY_EnterMessage( SPY_DISPATCHMESSAGE, msg->hwnd, msg->message,
1139                       msg->wParam, msg->lParam );
1140     retval = CallWindowProcW( winproc, msg->hwnd, msg->message,
1141                               msg->wParam, msg->lParam );
1142     SPY_ExitMessage( SPY_RESULT_OK, msg->hwnd, msg->message, retval,
1143                      msg->wParam, msg->lParam );
1144
1145     if (painting && (wndPtr = WIN_GetPtr( msg->hwnd )) && (wndPtr != WND_OTHER_PROCESS))
1146     {
1147         BOOL validate = ((wndPtr->flags & WIN_NEEDS_BEGINPAINT) && wndPtr->hrgnUpdate);
1148         wndPtr->flags &= ~WIN_NEEDS_BEGINPAINT;
1149         WIN_ReleasePtr( wndPtr );
1150         if (validate)
1151         {
1152             ERR( "BeginPaint not called on WM_PAINT for hwnd %04x!\n", msg->hwnd );
1153             /* Validate the update region to avoid infinite WM_PAINT loop */
1154             RedrawWindow( msg->hwnd, NULL, 0,
1155                           RDW_NOFRAME | RDW_VALIDATE | RDW_NOCHILDREN | RDW_NOINTERNALPAINT );
1156         }
1157     }
1158     return retval;
1159 }
1160
1161
1162 /***********************************************************************
1163  *              RegisterWindowMessage (USER.118)
1164  *              RegisterWindowMessageA (USER32.@)
1165  */
1166 WORD WINAPI RegisterWindowMessageA( LPCSTR str )
1167 {
1168     TRACE("%s\n", str );
1169     return GlobalAddAtomA( str );
1170 }
1171
1172
1173 /***********************************************************************
1174  *              RegisterWindowMessageW (USER32.@)
1175  */
1176 WORD WINAPI RegisterWindowMessageW( LPCWSTR str )
1177 {
1178     TRACE("%p\n", str );
1179     return GlobalAddAtomW( str );
1180 }
1181
1182
1183 /***********************************************************************
1184  *              BroadcastSystemMessage (USER32.@)
1185  */
1186 LONG WINAPI BroadcastSystemMessage(
1187         DWORD dwFlags,LPDWORD recipients,UINT uMessage,WPARAM wParam,
1188         LPARAM lParam
1189 ) {
1190         FIXME("(%08lx,%08lx,%08x,%08x,%08lx): stub!\n",
1191               dwFlags,*recipients,uMessage,wParam,lParam
1192         );
1193         return 0;
1194 }