1 | /* $Id: pmkbdhk.cpp,v 1.10 2003-10-22 16:10:59 sandervl Exp $ */
|
---|
2 | /*
|
---|
3 | * OS/2 native Presentation Manager hooks
|
---|
4 | *
|
---|
5 | *
|
---|
6 | * Large Portions (C) Ulrich Mller, XWorkplace
|
---|
7 | * Copyright 2001 Patrick Haller (patrick.haller@innotek.de)
|
---|
8 | * Copyright 2002-2003 Innotek Systemberatung GmbH
|
---|
9 | *
|
---|
10 | * Project Odin Software License can be found in LICENSE.TXT
|
---|
11 | *
|
---|
12 | */
|
---|
13 |
|
---|
14 | #define INCL_WIN
|
---|
15 | #define INCL_WININPUT
|
---|
16 | #define INCL_WINMESSAGEMGR
|
---|
17 | #define INCL_WINHOOKS
|
---|
18 | #define INCL_PM
|
---|
19 | #define INCL_BASE
|
---|
20 | #define INCL_ERRORS
|
---|
21 | #include <os2.h>
|
---|
22 | #include <stdlib.h>
|
---|
23 | #include <stdio.h>
|
---|
24 | #include <string.h>
|
---|
25 | #include <signal.h>
|
---|
26 | #include <pmscan.h>
|
---|
27 |
|
---|
28 | #include <pmkbdhk.h>
|
---|
29 | #include "pmkbdhkp.h"
|
---|
30 |
|
---|
31 |
|
---|
32 | /*
|
---|
33 |
|
---|
34 | To improve the ODIN keyboard management, it is required to intercept
|
---|
35 | any possible key from the Presentation Manager input queue.
|
---|
36 |
|
---|
37 | Sketch:
|
---|
38 |
|
---|
39 | - intercept keyboard messages before accelerators are translated
|
---|
40 | (HK_PREACCEL)
|
---|
41 | - determine if the message is targetted for an ODIN window
|
---|
42 | - if no ODIN window, pass thru
|
---|
43 | - if ODIN window
|
---|
44 | - check content of message.
|
---|
45 | - if sensible key event, rewrite message so PM won't handle it
|
---|
46 | itself (F1, F10, PrtScr, Ctrl, Alt, AltGr)
|
---|
47 | Ensure, GetKeyState() / GetAsyncKeyState() will still work properly!
|
---|
48 | The so rewritten message is handled specially inside the ODIN
|
---|
49 | message procedure. There, additionally required messages such as
|
---|
50 | WM_MENU if ALT is pressed need to be generated.
|
---|
51 | - if no sensible key, pass thru
|
---|
52 |
|
---|
53 | Installation:
|
---|
54 | - automatically install hook on loading of the DLL
|
---|
55 | - automatically uninstall the hook on DLL termination
|
---|
56 | - DLL is GLOBAL INITTERM, used shared mem only!
|
---|
57 | - Whenever process terminates OS/2 frees all hooks process initiated,
|
---|
58 | no matter whether DLL is still in memory or not. So in sequence:
|
---|
59 | Load 1 VIO app, load 2 VIO app, close 1st VIO app - KBD hook stops working because
|
---|
60 | it was released by OS/2 on 1 process. Creating hooks for each VIO app solves
|
---|
61 | this problem. Hook processes needed messages and removes them from other
|
---|
62 | hooks if needed. On termination of process 1 we still have hook of process 2.
|
---|
63 | */
|
---|
64 |
|
---|
65 |
|
---|
66 | BOOL EXPENTRY hookPreAccelHook(HAB hab, PQMSG pqmsg, ULONG option);
|
---|
67 |
|
---|
68 | HOOKDATA G_HookData = {0};
|
---|
69 |
|
---|
70 | unsigned long _System _DLL_InitTerm(unsigned long hModule,
|
---|
71 | unsigned long ulFlag)
|
---|
72 | {
|
---|
73 | switch (ulFlag)
|
---|
74 | {
|
---|
75 | case 0:
|
---|
76 | {
|
---|
77 | // store the DLL handle in the global variable
|
---|
78 | G_HookData.hmodDLL = hModule;
|
---|
79 | break;
|
---|
80 | }
|
---|
81 |
|
---|
82 | case 1:
|
---|
83 | break;
|
---|
84 |
|
---|
85 | default:
|
---|
86 | return (0); // error
|
---|
87 | }
|
---|
88 | // a non-zero value must be returned to indicate success
|
---|
89 | return (1);
|
---|
90 | }
|
---|
91 |
|
---|
92 | /*
|
---|
93 | *@@ hookInit:
|
---|
94 | * registers (sets) all the hooks and initializes data.
|
---|
95 | * we do call this for every process.
|
---|
96 | */
|
---|
97 |
|
---|
98 | BOOL WIN32API hookInit(HAB hab, PSZ pszWindowClassName)
|
---|
99 | {
|
---|
100 | if (G_HookData.hmodDLL) // initialized by _DLL_InitTerm
|
---|
101 | {
|
---|
102 | BOOL fSuccess;
|
---|
103 |
|
---|
104 | // install hooks, for each app use its own hook
|
---|
105 | // hooks automatically releases when process finishes
|
---|
106 | // so we need to set hook for each vio app. The reason why
|
---|
107 | // djmutex used one global hook is because it was loaded
|
---|
108 | // by daemon.
|
---|
109 |
|
---|
110 | G_HookData.habDaemonObject = hab;
|
---|
111 | G_HookData.fPreAccelHooked = WinSetHook(hab, NULLHANDLE, // system hook
|
---|
112 | HK_PREACCEL, // pre-accelerator table hook (undocumented)
|
---|
113 | (PFN)hookPreAccelHook,
|
---|
114 | G_HookData.hmodDLL);
|
---|
115 |
|
---|
116 | strncpy(G_HookData.szWindowClass, pszWindowClassName, sizeof(G_HookData.szWindowClass));
|
---|
117 | return TRUE;
|
---|
118 | }
|
---|
119 | return FALSE;
|
---|
120 | }
|
---|
121 |
|
---|
122 | /*
|
---|
123 | *@@ hookKill:
|
---|
124 | * deregisters the hook function and frees allocated
|
---|
125 | * resources.
|
---|
126 | */
|
---|
127 |
|
---|
128 | BOOL WIN32API hookKill()
|
---|
129 | {
|
---|
130 | // PM will cleanup the hook when the the creators message queue is destroyed
|
---|
131 | // or process terminates.
|
---|
132 |
|
---|
133 | if (G_HookData.fPreAccelHooked)
|
---|
134 | {
|
---|
135 | WinReleaseHook(G_HookData.habDaemonObject,
|
---|
136 | NULLHANDLE,
|
---|
137 | HK_PREACCEL, // pre-accelerator table hook (undocumented)
|
---|
138 | (PFN)hookPreAccelHook,
|
---|
139 | G_HookData.hmodDLL);
|
---|
140 |
|
---|
141 | G_HookData.fPreAccelHooked = FALSE;
|
---|
142 | }
|
---|
143 |
|
---|
144 | return TRUE;
|
---|
145 | }
|
---|
146 |
|
---|
147 |
|
---|
148 | //
|
---|
149 | // This function detects if the current window is
|
---|
150 | // a win32 window
|
---|
151 | //
|
---|
152 |
|
---|
153 | static HWND hwndLastWin32Window = 0;
|
---|
154 |
|
---|
155 | BOOL i_isWin32Window(HWND hwnd)
|
---|
156 | {
|
---|
157 | // Note: this hook is called on the stack of the current process
|
---|
158 | // so we rather keep the buffer small ...
|
---|
159 | CHAR szClass[80];
|
---|
160 |
|
---|
161 | if (hwndLastWin32Window == hwnd)
|
---|
162 | return TRUE;
|
---|
163 |
|
---|
164 | if (WinQueryClassName(hwnd, sizeof(szClass), szClass))
|
---|
165 | {
|
---|
166 | if (strcmp(szClass, G_HookData.szWindowClass) == 0)
|
---|
167 | {
|
---|
168 | hwndLastWin32Window = hwnd;
|
---|
169 | return TRUE;
|
---|
170 | }
|
---|
171 | }
|
---|
172 | return FALSE;
|
---|
173 | }
|
---|
174 |
|
---|
175 |
|
---|
176 | /*
|
---|
177 | *@@ hookPreAccelHook:
|
---|
178 | * this is the pre-accelerator-table hook. Like
|
---|
179 | * hookInputHook, this gets called when messages
|
---|
180 | * are coming in from the system input queue, but
|
---|
181 | * as opposed to a "regular" input hook, this hook
|
---|
182 | * gets called even before translation from accelerator
|
---|
183 | * tables occurs.
|
---|
184 | *
|
---|
185 | * Pre-accel hooks are not documented in the PM Reference.
|
---|
186 | * I have found this idea (and part of the implementation)
|
---|
187 | * in the source code of ProgramCommander/2, so thanks
|
---|
188 | * go out (once again) to Roman Stangl.
|
---|
189 | *
|
---|
190 | * In this hook, we check for the global object hotkeys
|
---|
191 | * so that we can use certain key combinations regardless
|
---|
192 | * of whether they might be currently used in application
|
---|
193 | * accelerator tables. This is especially useful for
|
---|
194 | * "Alt" key combinations, which are usually intercepted
|
---|
195 | * when menus have corresponding shortcuts.
|
---|
196 | *
|
---|
197 | * ODIN: Keep in mind that effort must be taken to make processing as
|
---|
198 | * optimal as possible as this is install by each and every Odin
|
---|
199 | * process and they will process and reject the exact same messages.
|
---|
200 | * Only the first called hook will actually do something useful.
|
---|
201 | */
|
---|
202 |
|
---|
203 | BOOL EXPENTRY hookPreAccelHook(HAB hab, PQMSG pqmsg, ULONG option)
|
---|
204 | {
|
---|
205 | if (pqmsg == NULL)
|
---|
206 | return (FALSE);
|
---|
207 |
|
---|
208 | switch(pqmsg->msg)
|
---|
209 | {
|
---|
210 |
|
---|
211 | case WM_CHAR:
|
---|
212 | {
|
---|
213 | // Note:
|
---|
214 | // what happens it the system is locked up?
|
---|
215 | // do we have to detect the screensaver to kick in?
|
---|
216 | // if ( (!G_HookData.hwndLockupFrame) // system not locked up
|
---|
217 | // ...
|
---|
218 |
|
---|
219 | // is this an WIN32 window?
|
---|
220 | if (!i_isWin32Window(pqmsg->hwnd))
|
---|
221 | {
|
---|
222 | // no, so pass thru
|
---|
223 | return FALSE;
|
---|
224 | }
|
---|
225 |
|
---|
226 | // check if we've encountered a so called critical key
|
---|
227 | // (this is the scan code which is supposed to be valid
|
---|
228 | // always for the hooks! */
|
---|
229 | switch ( CHAR4FROMMP(pqmsg->mp1) )
|
---|
230 | {
|
---|
231 | default:
|
---|
232 | return FALSE;
|
---|
233 |
|
---|
234 | // Intercept PM Window Hotkeys such as
|
---|
235 | // Alt-F7 do enable window moving by keyboard.
|
---|
236 | case PMSCAN_F1:
|
---|
237 | case PMSCAN_F2:
|
---|
238 | case PMSCAN_F3:
|
---|
239 | case PMSCAN_F4:
|
---|
240 | case PMSCAN_F5:
|
---|
241 | case PMSCAN_F6:
|
---|
242 | case PMSCAN_F7:
|
---|
243 | case PMSCAN_F8:
|
---|
244 | case PMSCAN_F9:
|
---|
245 | case PMSCAN_F10:
|
---|
246 | case PMSCAN_F11:
|
---|
247 | case PMSCAN_F12:
|
---|
248 |
|
---|
249 | // Try to prevent Ctrl-Esc, etc. from being intercepted by PM
|
---|
250 | case PMSCAN_ESC:
|
---|
251 | case PMSCAN_CTRLLEFT:
|
---|
252 | case PMSCAN_CTRLRIGHT:
|
---|
253 |
|
---|
254 | case PMSCAN_PRINT:
|
---|
255 | case PMSCAN_ALTLEFT:
|
---|
256 | case PMSCAN_SCROLLLOCK:
|
---|
257 | case PMSCAN_ENTER:
|
---|
258 | case PMSCAN_PADENTER:
|
---|
259 | case PMSCAN_CAPSLOCK:
|
---|
260 | // OK, as we've got a special key here, we've got
|
---|
261 | // to rewrite the message so PM will ignore the key
|
---|
262 | // and won't translate the message to anything else.
|
---|
263 |
|
---|
264 | pqmsg->msg = WM_CHAR_SPECIAL;
|
---|
265 |
|
---|
266 | break;
|
---|
267 |
|
---|
268 | //
|
---|
269 | // AltGr needs special handling
|
---|
270 | //
|
---|
271 | // AltGr -> WM_KEYDOWN (VK_CONTROL), WM_KEYDOWN (VK_MENU)
|
---|
272 | // WM_SYSKEYUP (VK_CONTROL)
|
---|
273 | // WM_KEYUP (VK_MENU)
|
---|
274 | //
|
---|
275 | // Ctrl+AltGr -> WM_KEYDOWN (VK_CONTROL), WM_KEYUP (VK_CONTROL)
|
---|
276 | // WM_KEYDOWN (VK_CONTROL)
|
---|
277 | // WM_KEYDOWN (VK_MENU)
|
---|
278 | // WM_KEYUP (VK_MENU)
|
---|
279 | // WM_KEYUP (VK_CONTROL)
|
---|
280 | //
|
---|
281 | // AltGr+Ctrl -> WM_KEYDOWN (VK_CONTROL), WM_KEYDOWN (VK_MENU)
|
---|
282 | // WM_KEYDOWN (VK_CONTROL)
|
---|
283 | // WM_SYSKEYUP (VK_CONTROL)
|
---|
284 | // WM_SYSKEYUP (VK_CONTROL)
|
---|
285 | // WM_KEYUP (VK_MENU)
|
---|
286 | //
|
---|
287 | // AltGr down -> if Ctrl down, send WM_KEYUP (VK_CONTROL)
|
---|
288 | // endif
|
---|
289 | // Send WM_KEYDOWN (VK_CONTROL)
|
---|
290 | // Send WM_KEYDOWN (VK_MENU)
|
---|
291 | // AltGr up -> if !(Ctrl down before AltGr was pressed || Ctrl up)
|
---|
292 | // Send WM_SYSKEYUP (VK_CONTROL)
|
---|
293 | // endif
|
---|
294 | // Send WM_KEYDOWN (VK_MENU)
|
---|
295 | //
|
---|
296 | // NOTE: Ctrl = Ctrl-Left; AltGr doesn't care about the right Ctrl key
|
---|
297 | //
|
---|
298 | case PMSCAN_ALTRIGHT:
|
---|
299 | {
|
---|
300 | QMSG msg = *pqmsg;
|
---|
301 | ULONG ctrlstate;
|
---|
302 | ULONG flags;
|
---|
303 | ULONG mp1, mp2;
|
---|
304 |
|
---|
305 | flags = SHORT1FROMMP(pqmsg->mp1);
|
---|
306 |
|
---|
307 | pqmsg->msg = WM_CHAR_SPECIAL;
|
---|
308 |
|
---|
309 | if (flags & KC_KEYUP)
|
---|
310 | {
|
---|
311 | //AltGr up
|
---|
312 | ctrlstate = WinGetPhysKeyState(HWND_DESKTOP, PMSCAN_CTRLLEFT);
|
---|
313 | if (!(ctrlstate & 0x8000))
|
---|
314 | {
|
---|
315 | //ctrl is up, translate this message to Ctrl key up
|
---|
316 | mp1 = (PMSCAN_CTRLLEFT << 24); //scancode
|
---|
317 | mp1 |= (1 << 16); //repeat count
|
---|
318 | mp1 |= (KC_ALT | KC_KEYUP | KC_VIRTUALKEY | KC_SCANCODE);
|
---|
319 | mp2 = (VK_CTRL << 16); //virtual keycode
|
---|
320 | pqmsg->msg = WM_CHAR_SPECIAL_ALTGRCONTROL;
|
---|
321 | pqmsg->mp1 = (MPARAM)mp1;
|
---|
322 | pqmsg->mp2 = (MPARAM)mp2;
|
---|
323 |
|
---|
324 | //and finally, post the AltGr WM_CHAR message
|
---|
325 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL, msg.mp1, msg.mp2);
|
---|
326 | }
|
---|
327 | //else do nothing
|
---|
328 | }
|
---|
329 | else
|
---|
330 | {
|
---|
331 | //AltGr down
|
---|
332 | ctrlstate = WinGetPhysKeyState(HWND_DESKTOP, PMSCAN_CTRLLEFT);
|
---|
333 | if (ctrlstate & 0x8000)
|
---|
334 | {
|
---|
335 | //ctrl is down, translate this message to Ctrl key up
|
---|
336 | mp1 = (PMSCAN_CTRLLEFT << 24); //scancode
|
---|
337 | mp1 |= (1 << 16); //repeat count
|
---|
338 | mp1 |= (KC_KEYUP | KC_VIRTUALKEY | KC_SCANCODE);
|
---|
339 | mp2 = (VK_CTRL << 16); //virtual keycode
|
---|
340 | pqmsg->msg = WM_CHAR_SPECIAL_ALTGRCONTROL;
|
---|
341 | pqmsg->mp1 = (MPARAM)mp1;
|
---|
342 | pqmsg->mp2 = (MPARAM)mp2;
|
---|
343 | }
|
---|
344 | //send left control key down message
|
---|
345 | mp1 = (PMSCAN_CTRLLEFT << 24); //scancode
|
---|
346 | mp1 |= (1 << 16); //repeat count
|
---|
347 | mp1 |= (KC_CTRL | KC_VIRTUALKEY | KC_SCANCODE);
|
---|
348 | mp2 = (VK_CTRL << 16); //virtual keycode
|
---|
349 |
|
---|
350 | if (ctrlstate & 0x8000)
|
---|
351 | {
|
---|
352 | //ctrl is down, must post this message
|
---|
353 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL_ALTGRCONTROL, (MPARAM)mp1, (MPARAM)mp2);
|
---|
354 | }
|
---|
355 | else
|
---|
356 | {
|
---|
357 | //translate this message into control key down
|
---|
358 | pqmsg->msg = WM_CHAR_SPECIAL_ALTGRCONTROL;
|
---|
359 | pqmsg->mp1 = (MPARAM)mp1;
|
---|
360 | pqmsg->mp2 = (MPARAM)mp2;
|
---|
361 | }
|
---|
362 | //and finally, post the AltGr WM_CHAR message
|
---|
363 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL, msg.mp1, msg.mp2);
|
---|
364 | }
|
---|
365 | break;
|
---|
366 | }
|
---|
367 | }
|
---|
368 | break; // WM_CHAR
|
---|
369 | }
|
---|
370 | } // end switch(msg)
|
---|
371 | return FALSE;
|
---|
372 | }
|
---|