xref: /reactos/win32ss/user/ntuser/accelerator.c (revision 8a978a17)
1 /*
2  * COPYRIGHT:        See COPYING in the top level directory
3  * PROJECT:          ReactOS kernel
4  * PURPOSE:          Window accelerator
5  * FILE:             win32ss/user/ntuser/accelerator.c
6  * PROGRAMER:        Casper S. Hornstrup (chorns@users.sourceforge.net)
7  *                   Copyright 1993 Martin Ayotte
8  *                   Copyright 1994 Alexandre Julliard
9  *                   Copyright 1997 Morten Welinder
10  *                   Copyright 2011 Rafal Harabien
11  */
12 
13 #include <win32k.h>
14 DBG_DEFAULT_CHANNEL(UserAccel);
15 
16 #define FVIRT_TBL_END 0x80
17 #define FVIRT_MASK 0x7F
18 
19 /* FUNCTIONS *****************************************************************/
20 
21 PACCELERATOR_TABLE FASTCALL UserGetAccelObject(HACCEL hAccel)
22 {
23     PACCELERATOR_TABLE Accel;
24 
25     if (!hAccel)
26     {
27         EngSetLastError(ERROR_INVALID_ACCEL_HANDLE);
28         return NULL;
29     }
30 
31     Accel = UserGetObject(gHandleTable, hAccel, TYPE_ACCELTABLE);
32     if (!Accel)
33     {
34         EngSetLastError(ERROR_INVALID_ACCEL_HANDLE);
35         return NULL;
36     }
37 
38     return Accel;
39 }
40 
41 
42 static
43 BOOLEAN FASTCALL
44 co_IntTranslateAccelerator(
45     PWND Window,
46     CONST MSG *pMsg,
47     CONST ACCEL *pAccel)
48 {
49     BOOL bFound = FALSE;
50     UINT Mask = 0, nPos;
51     HWND hWnd;
52     HMENU hMenu, hSubMenu;
53     PMENU MenuObject;
54 
55     ASSERT_REFS_CO(Window);
56 
57     hWnd = Window->head.h;
58 
59     TRACE("IntTranslateAccelerator(hwnd %p, message %x, wParam %x, lParam %x, fVirt 0x%x, key %x, cmd %x)\n",
60           hWnd, pMsg->message, pMsg->wParam, pMsg->lParam, pAccel->fVirt, pAccel->key, pAccel->cmd);
61 
62     if (UserGetKeyState(VK_CONTROL) & 0x8000) Mask |= FCONTROL;
63     if (UserGetKeyState(VK_MENU) & 0x8000) Mask |= FALT; // FIXME: VK_LMENU (msg winetest!)
64     if (UserGetKeyState(VK_SHIFT) & 0x8000) Mask |= FSHIFT;
65     TRACE("Mask 0x%x\n", Mask);
66 
67     if (pAccel->fVirt & FVIRTKEY)
68     {
69         /* This is a virtual key. Process WM_(SYS)KEYDOWN messages. */
70         if (pMsg->message == WM_KEYDOWN || pMsg->message == WM_SYSKEYDOWN)
71         {
72             /* Check virtual key and SHIFT, CTRL, LALT state */
73             if (pMsg->wParam == pAccel->key && Mask == (pAccel->fVirt & (FSHIFT | FCONTROL | FALT)))
74             {
75                 bFound = TRUE;
76             }
77         }
78     }
79     else
80     {
81         /* This is a char code. Process WM_(SYS)CHAR messages. */
82         if (pMsg->message == WM_CHAR || pMsg->message == WM_SYSCHAR)
83         {
84             /* Check char code and LALT state only */
85             if (pMsg->wParam == pAccel->key && (Mask & FALT) == (pAccel->fVirt & FALT))
86             {
87                 bFound = TRUE;
88             }
89         }
90     }
91 
92     if (!bFound)
93     {
94         /* Don't translate this msg */
95         TRACE("IntTranslateAccelerator returns FALSE\n");
96         return FALSE;
97     }
98 
99     /* Check if accelerator is associated with menu command */
100     hMenu = (Window->style & WS_CHILD) ? 0 : (HMENU)Window->IDMenu;
101     hSubMenu = NULL;
102     MenuObject = UserGetMenuObject(hMenu);
103     nPos = pAccel->cmd;
104     if (MenuObject)
105     {
106         if ((MENU_FindItem (&MenuObject, &nPos, MF_BYPOSITION)))
107             hSubMenu = MenuObject->head.h;
108         else
109             hMenu = NULL;
110     }
111     if (!hMenu)
112     {
113         /* Check system menu now */
114         hMenu = Window->SystemMenu;
115         hSubMenu = hMenu; /* system menu is a popup menu */
116         MenuObject = UserGetMenuObject(hMenu);
117         nPos = pAccel->cmd;
118         if (MenuObject)
119         {
120             if ((MENU_FindItem (&MenuObject, &nPos, MF_BYPOSITION)))
121                 hSubMenu = MenuObject->head.h;
122             else
123                 hMenu = NULL;
124         }
125     }
126 
127     /* If this is a menu item, there is no capturing enabled and
128        window is not disabled, send WM_INITMENU */
129     if (hMenu && !IntGetCaptureWindow())
130     {
131         co_IntSendMessage(hWnd, WM_INITMENU, (WPARAM)hMenu, 0L);
132         if (hSubMenu)
133         {
134             nPos = IntFindSubMenu(&hMenu, hSubMenu);
135             TRACE("hSysMenu = %p, hSubMenu = %p, nPos = %u\n", hMenu, hSubMenu, nPos);
136             co_IntSendMessage(hWnd, WM_INITMENUPOPUP, (WPARAM)hSubMenu, MAKELPARAM(nPos, TRUE));
137         }
138     }
139 
140     /* Don't send any message if:
141        - window is disabled
142        - menu item is disabled
143        - this is window menu and window is minimized */
144     if (!(Window->style & WS_DISABLED) &&
145             !(hMenu && IntGetMenuState(hMenu, pAccel->cmd, MF_BYCOMMAND) & (MF_DISABLED | MF_GRAYED)) &&
146             !(hMenu && hMenu == (HMENU)Window->IDMenu && (Window->style & WS_MINIMIZED)))
147     {
148         /* If this is system menu item, send WM_SYSCOMMAND, otherwise send WM_COMMAND */
149         if (hMenu && hMenu == Window->SystemMenu)
150         {
151             TRACE("Sending WM_SYSCOMMAND, wParam=%0x\n", pAccel->cmd);
152             co_IntSendMessage(hWnd, WM_SYSCOMMAND, pAccel->cmd, 0x00010000L);
153         }
154         else
155         {
156             TRACE("Sending WM_COMMAND, wParam=%0x\n", 0x10000 | pAccel->cmd);
157             co_IntSendMessage(hWnd, WM_COMMAND, 0x10000 | pAccel->cmd, 0L);
158         }
159     }
160 
161     TRACE("IntTranslateAccelerator returns TRUE\n");
162     return TRUE;
163 }
164 
165 
166 /* SYSCALLS *****************************************************************/
167 
168 
169 ULONG
170 APIENTRY
171 NtUserCopyAcceleratorTable(
172     HACCEL hAccel,
173     LPACCEL Entries,
174     ULONG EntriesCount)
175 {
176     PACCELERATOR_TABLE Accel;
177     ULONG Ret;
178     DECLARE_RETURN(int);
179 
180     TRACE("Enter NtUserCopyAcceleratorTable\n");
181     UserEnterShared();
182 
183     Accel = UserGetAccelObject(hAccel);
184     if (!Accel)
185     {
186         RETURN(0);
187     }
188 
189     /* If Entries is NULL return table size */
190     if (!Entries)
191     {
192         RETURN(Accel->Count);
193     }
194 
195     /* Don't overrun */
196     if (Accel->Count < EntriesCount)
197         EntriesCount = Accel->Count;
198 
199     Ret = 0;
200 
201     _SEH2_TRY
202     {
203         ProbeForWrite(Entries, EntriesCount*sizeof(Entries[0]), 4);
204 
205         for (Ret = 0; Ret < EntriesCount; Ret++)
206         {
207             Entries[Ret].fVirt = Accel->Table[Ret].fVirt;
208             Entries[Ret].key = Accel->Table[Ret].key;
209             Entries[Ret].cmd = Accel->Table[Ret].cmd;
210         }
211     }
212     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
213     {
214         SetLastNtError(_SEH2_GetExceptionCode());
215         Ret = 0;
216     }
217     _SEH2_END;
218 
219     RETURN(Ret);
220 
221 CLEANUP:
222     TRACE("Leave NtUserCopyAcceleratorTable, ret=%i\n", _ret_);
223     UserLeave();
224     END_CLEANUP;
225 }
226 
227 HACCEL
228 APIENTRY
229 NtUserCreateAcceleratorTable(
230     LPACCEL Entries,
231     ULONG EntriesCount)
232 {
233     PACCELERATOR_TABLE Accel;
234     HACCEL hAccel;
235     ULONG Index;
236     NTSTATUS Status = STATUS_SUCCESS;
237     DECLARE_RETURN(HACCEL);
238     PTHREADINFO pti;
239 
240     TRACE("Enter NtUserCreateAcceleratorTable(Entries %p, EntriesCount %u)\n",
241           Entries, EntriesCount);
242     UserEnterExclusive();
243 
244     if (!Entries || EntriesCount <= 0)
245     {
246         SetLastNtError(STATUS_INVALID_PARAMETER);
247         RETURN( (HACCEL) NULL );
248     }
249 
250     pti = PsGetCurrentThreadWin32Thread();
251 
252     Accel = UserCreateObject(gHandleTable,
253         pti->rpdesk,
254         pti,
255         (PHANDLE)&hAccel,
256         TYPE_ACCELTABLE,
257         sizeof(ACCELERATOR_TABLE));
258 
259     if (Accel == NULL)
260     {
261         SetLastNtError(STATUS_NO_MEMORY);
262         RETURN( (HACCEL) NULL );
263     }
264 
265     Accel->Count = EntriesCount;
266     Accel->Table = ExAllocatePoolWithTag(PagedPool, EntriesCount * sizeof(ACCEL), USERTAG_ACCEL);
267     if (Accel->Table == NULL)
268     {
269         UserDereferenceObject(Accel);
270         UserDeleteObject(hAccel, TYPE_ACCELTABLE);
271         SetLastNtError(STATUS_NO_MEMORY);
272         RETURN( (HACCEL) NULL);
273     }
274 
275     _SEH2_TRY
276     {
277         ProbeForRead(Entries, EntriesCount * sizeof(ACCEL), 4);
278 
279         for (Index = 0; Index < EntriesCount; Index++)
280         {
281             Accel->Table[Index].fVirt = Entries[Index].fVirt & FVIRT_MASK;
282             if(Accel->Table[Index].fVirt & FVIRTKEY)
283             {
284                 Accel->Table[Index].key = Entries[Index].key;
285             }
286             else
287             {
288                 RtlMultiByteToUnicodeN(&Accel->Table[Index].key,
289                 sizeof(WCHAR),
290                 NULL,
291                 (PCSTR)&Entries[Index].key,
292                 sizeof(CHAR));
293             }
294 
295             Accel->Table[Index].cmd = Entries[Index].cmd;
296         }
297     }
298     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
299     {
300         Status = _SEH2_GetExceptionCode();
301     }
302     _SEH2_END;
303 
304     if (!NT_SUCCESS(Status))
305     {
306         ExFreePoolWithTag(Accel->Table, USERTAG_ACCEL);
307         UserDereferenceObject(Accel);
308         UserDeleteObject(hAccel, TYPE_ACCELTABLE);
309         SetLastNtError(Status);
310         RETURN( (HACCEL) NULL);
311     }
312 
313     /* FIXME: Save HandleTable in a list somewhere so we can clean it up again */
314 
315     /* Release the extra reference (UserCreateObject added 2 references) */
316     UserDereferenceObject(Accel);
317 
318     RETURN(hAccel);
319 
320 CLEANUP:
321     TRACE("Leave NtUserCreateAcceleratorTable(Entries %p, EntriesCount %u) = %p\n",
322           Entries, EntriesCount, _ret_);
323     UserLeave();
324     END_CLEANUP;
325 }
326 
327 BOOLEAN
328 UserDestroyAccelTable(PVOID Object)
329 {
330     PACCELERATOR_TABLE Accel = Object;
331 
332     if (Accel->Table != NULL)
333     {
334         ExFreePoolWithTag(Accel->Table, USERTAG_ACCEL);
335         Accel->Table = NULL;
336     }
337 
338     UserDeleteObject(Accel->head.h, TYPE_ACCELTABLE);
339     return TRUE;
340 }
341 
342 BOOLEAN
343 APIENTRY
344 NtUserDestroyAcceleratorTable(
345     HACCEL hAccel)
346 {
347     PACCELERATOR_TABLE Accel;
348     DECLARE_RETURN(BOOLEAN);
349 
350     /* FIXME: If the handle table is from a call to LoadAcceleratorTable, decrement it's
351        usage count (and return TRUE).
352     FIXME: Destroy only tables created using CreateAcceleratorTable.
353      */
354 
355     TRACE("NtUserDestroyAcceleratorTable(Table %p)\n", hAccel);
356     UserEnterExclusive();
357 
358     if (!(Accel = UserGetAccelObject(hAccel)))
359     {
360         RETURN( FALSE);
361     }
362 
363     UserDestroyAccelTable(Accel);
364 
365     RETURN( TRUE);
366 
367 CLEANUP:
368     TRACE("Leave NtUserDestroyAcceleratorTable(Table %p) = %u\n", hAccel, _ret_);
369     UserLeave();
370     END_CLEANUP;
371 }
372 
373 int
374 APIENTRY
375 NtUserTranslateAccelerator(
376     HWND hWnd,
377     HACCEL hAccel,
378     LPMSG pUnsafeMessage)
379 {
380     PWND Window = NULL;
381     PACCELERATOR_TABLE Accel = NULL;
382     ULONG i;
383     MSG Message;
384     USER_REFERENCE_ENTRY AccelRef, WindowRef;
385     DECLARE_RETURN(int);
386 
387     TRACE("NtUserTranslateAccelerator(hWnd %p, hAccel %p, Message %p)\n",
388           hWnd, hAccel, pUnsafeMessage);
389     UserEnterShared();
390 
391     if (hWnd == NULL)
392     {
393         RETURN( 0);
394     }
395 
396     _SEH2_TRY
397     {
398         ProbeForRead(pUnsafeMessage, sizeof(MSG), 4);
399         RtlCopyMemory(&Message, pUnsafeMessage, sizeof(MSG));
400     }
401     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
402     {
403         SetLastNtError(_SEH2_GetExceptionCode());
404         _SEH2_YIELD(RETURN( 0));
405     }
406     _SEH2_END;
407 
408     if ((Message.message != WM_KEYDOWN) &&
409         (Message.message != WM_SYSKEYDOWN) &&
410         (Message.message != WM_SYSCHAR) &&
411         (Message.message != WM_CHAR))
412     {
413         RETURN( 0);
414     }
415 
416     Accel = UserGetAccelObject(hAccel);
417     if (!Accel)
418     {
419         RETURN( 0);
420     }
421 
422     UserRefObjectCo(Accel, &AccelRef);
423 
424     Window = UserGetWindowObject(hWnd);
425     if (!Window)
426     {
427         RETURN( 0);
428     }
429 
430     UserRefObjectCo(Window, &WindowRef);
431 
432     /* FIXME: Associate AcceleratorTable with the current thread */
433 
434     for (i = 0; i < Accel->Count; i++)
435     {
436         if (co_IntTranslateAccelerator(Window, &Message, &Accel->Table[i]))
437         {
438             RETURN( 1);
439         }
440 
441         /* Undocumented feature... */
442         if (Accel->Table[i].fVirt & FVIRT_TBL_END)
443             break;
444     }
445 
446     RETURN( 0);
447 
448 CLEANUP:
449     if (Window) UserDerefObjectCo(Window);
450     if (Accel) UserDerefObjectCo(Accel);
451 
452     TRACE("NtUserTranslateAccelerator returns %d\n", _ret_);
453     UserLeave();
454     END_CLEANUP;
455 }
456