1 | /* $Id: kbdhook.cpp,v 1.2 2004-02-10 15:36:04 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 <os2wrap.h>
|
---|
22 | #include <stdlib.h>
|
---|
23 | #include <stdio.h>
|
---|
24 | #include <string.h>
|
---|
25 | #include <signal.h>
|
---|
26 | #include <pmscan.h>
|
---|
27 |
|
---|
28 | #include <odin.h>
|
---|
29 | #include <pmkbdhk.h>
|
---|
30 | #include <kbdhook.h>
|
---|
31 | #include <custombuild.h>
|
---|
32 |
|
---|
33 | /*
|
---|
34 |
|
---|
35 | To improve the ODIN keyboard management, it is required to intercept
|
---|
36 | any possible key from the Presentation Manager input queue.
|
---|
37 |
|
---|
38 | Sketch:
|
---|
39 |
|
---|
40 | - intercept keyboard messages before accelerators are translated
|
---|
41 | (HK_PREACCEL)
|
---|
42 | - determine if the message is targetted for an ODIN window
|
---|
43 | - if no ODIN window, pass thru
|
---|
44 | - if ODIN window
|
---|
45 | - check content of message.
|
---|
46 | - if sensible key event, rewrite message so PM won't handle it
|
---|
47 | itself (F1, F10, PrtScr, Ctrl, Alt, AltGr)
|
---|
48 | Ensure, GetKeyState() / GetAsyncKeyState() will still work properly!
|
---|
49 | The so rewritten message is handled specially inside the ODIN
|
---|
50 | message procedure. There, additionally required messages such as
|
---|
51 | WM_MENU if ALT is pressed need to be generated.
|
---|
52 | - if no sensible key, pass thru
|
---|
53 |
|
---|
54 | Installation:
|
---|
55 | - automatically install hook on loading of the DLL
|
---|
56 | - automatically uninstall the hook on DLL termination
|
---|
57 | - DLL is GLOBAL INITTERM, used shared mem only!
|
---|
58 | - Whenever process terminates OS/2 frees all hooks process initiated,
|
---|
59 | no matter whether DLL is still in memory or not. So in sequence:
|
---|
60 | Load 1 VIO app, load 2 VIO app, close 1st VIO app - KBD hook stops working because
|
---|
61 | it was released by OS/2 on 1 process. Creating hooks for each VIO app solves
|
---|
62 | this problem. Hook processes needed messages and removes them from other
|
---|
63 | hooks if needed. On termination of process 1 we still have hook of process 2.
|
---|
64 | */
|
---|
65 |
|
---|
66 |
|
---|
67 | static char szWindowClass[256] = "";
|
---|
68 |
|
---|
69 | BOOL EXPENTRY hookPreAccelHook(HAB hab, PQMSG pqmsg, ULONG option);
|
---|
70 |
|
---|
71 | /*
|
---|
72 | *@@ hookInit:
|
---|
73 | * registers (sets) all the hooks and initializes data.
|
---|
74 | * we do call this for every process.
|
---|
75 | */
|
---|
76 |
|
---|
77 | BOOL WIN32API hookInit(ULONG hab)
|
---|
78 | {
|
---|
79 | if(szWindowClass[0] == 0)
|
---|
80 | {//query odin client window class name
|
---|
81 | strcpy(szWindowClass, QueryCustomStdClassName());
|
---|
82 | }
|
---|
83 | return WinSetHook(hab, HMQ_CURRENT, // queue hook
|
---|
84 | HK_PREACCEL, // pre-accelerator table hook (undocumented)
|
---|
85 | (PFN)hookPreAccelHook,
|
---|
86 | NULL);
|
---|
87 | }
|
---|
88 |
|
---|
89 | /*
|
---|
90 | *@@ hookKill:
|
---|
91 | * deregisters the hook function and frees allocated
|
---|
92 | * resources.
|
---|
93 | */
|
---|
94 |
|
---|
95 | BOOL WIN32API hookKill(ULONG hab)
|
---|
96 | {
|
---|
97 | WinReleaseHook(hab, HMQ_CURRENT,
|
---|
98 | HK_PREACCEL, // pre-accelerator table hook (undocumented)
|
---|
99 | (PFN)hookPreAccelHook,
|
---|
100 | NULL);
|
---|
101 | return TRUE;
|
---|
102 | }
|
---|
103 |
|
---|
104 | //
|
---|
105 | // This function detects if the current window is
|
---|
106 | // a win32 window
|
---|
107 | //
|
---|
108 |
|
---|
109 | static HWND hwndLastWin32Window = 0;
|
---|
110 |
|
---|
111 | BOOL i_isWin32Window(HWND hwnd)
|
---|
112 | {
|
---|
113 | // Note: this hook is called on the stack of the current process
|
---|
114 | // so we rather keep the buffer small ...
|
---|
115 | CHAR szClass[80];
|
---|
116 |
|
---|
117 | if (hwndLastWin32Window == hwnd)
|
---|
118 | return TRUE;
|
---|
119 |
|
---|
120 | if (WinQueryClassName(hwnd, sizeof(szClass), szClass))
|
---|
121 | {
|
---|
122 | if (strcmp(szClass, szWindowClass) == 0)
|
---|
123 | {
|
---|
124 | hwndLastWin32Window = hwnd;
|
---|
125 | return TRUE;
|
---|
126 | }
|
---|
127 | }
|
---|
128 | return FALSE;
|
---|
129 | }
|
---|
130 |
|
---|
131 | /*
|
---|
132 | *@@ hookPreAccelHook:
|
---|
133 | * this is the pre-accelerator-table hook. Like
|
---|
134 | * hookInputHook, this gets called when messages
|
---|
135 | * are coming in from the system input queue, but
|
---|
136 | * as opposed to a "regular" input hook, this hook
|
---|
137 | * gets called even before translation from accelerator
|
---|
138 | * tables occurs.
|
---|
139 | *
|
---|
140 | * Pre-accel hooks are not documented in the PM Reference.
|
---|
141 | * I have found this idea (and part of the implementation)
|
---|
142 | * in the source code of ProgramCommander/2, so thanks
|
---|
143 | * go out (once again) to Roman Stangl.
|
---|
144 | *
|
---|
145 | * In this hook, we check for the global object hotkeys
|
---|
146 | * so that we can use certain key combinations regardless
|
---|
147 | * of whether they might be currently used in application
|
---|
148 | * accelerator tables. This is especially useful for
|
---|
149 | * "Alt" key combinations, which are usually intercepted
|
---|
150 | * when menus have corresponding shortcuts.
|
---|
151 | *
|
---|
152 | * ODIN: Keep in mind that effort must be taken to make processing as
|
---|
153 | * optimal as possible as this is install by each and every Odin
|
---|
154 | * process and they will process and reject the exact same messages.
|
---|
155 | * Only the first called hook will actually do something useful.
|
---|
156 | */
|
---|
157 |
|
---|
158 | BOOL EXPENTRY hookPreAccelHook(HAB hab, PQMSG pqmsg, ULONG option)
|
---|
159 | {
|
---|
160 | if (pqmsg == NULL)
|
---|
161 | return (FALSE);
|
---|
162 |
|
---|
163 | switch(pqmsg->msg)
|
---|
164 | {
|
---|
165 |
|
---|
166 | case WM_CHAR:
|
---|
167 | {
|
---|
168 | // is this an WIN32 window?
|
---|
169 | if (!i_isWin32Window(pqmsg->hwnd))
|
---|
170 | {
|
---|
171 | // no, so pass thru
|
---|
172 | return FALSE;
|
---|
173 | }
|
---|
174 |
|
---|
175 | // check if we've encountered a so called critical key
|
---|
176 | // (this is the scan code which is supposed to be valid
|
---|
177 | // always for the hooks!)
|
---|
178 | switch ( CHAR4FROMMP(pqmsg->mp1) )
|
---|
179 | {
|
---|
180 | default:
|
---|
181 | return FALSE;
|
---|
182 |
|
---|
183 | // Intercept PM Window Hotkeys such as
|
---|
184 | // Alt-F7 do enable window moving by keyboard.
|
---|
185 | case PMSCAN_F1:
|
---|
186 | case PMSCAN_F2:
|
---|
187 | case PMSCAN_F3:
|
---|
188 | case PMSCAN_F4:
|
---|
189 | case PMSCAN_F5:
|
---|
190 | case PMSCAN_F6:
|
---|
191 | case PMSCAN_F7:
|
---|
192 | case PMSCAN_F8:
|
---|
193 | case PMSCAN_F9:
|
---|
194 | case PMSCAN_F10:
|
---|
195 | case PMSCAN_F11:
|
---|
196 | case PMSCAN_F12:
|
---|
197 |
|
---|
198 | // Try to prevent Ctrl-Esc, etc. from being intercepted by PM
|
---|
199 | case PMSCAN_ESC:
|
---|
200 | case PMSCAN_CTRLLEFT:
|
---|
201 | case PMSCAN_CTRLRIGHT:
|
---|
202 |
|
---|
203 | case PMSCAN_PRINT:
|
---|
204 | case PMSCAN_ALTLEFT:
|
---|
205 | case PMSCAN_SCROLLLOCK:
|
---|
206 | case PMSCAN_ENTER:
|
---|
207 | case PMSCAN_PADENTER:
|
---|
208 | case PMSCAN_CAPSLOCK:
|
---|
209 | // OK, as we've got a special key here, we've got
|
---|
210 | // to rewrite the message so PM will ignore the key
|
---|
211 | // and won't translate the message to anything else.
|
---|
212 |
|
---|
213 | WinPostMsg(pqmsg->hwnd, WM_CHAR_SPECIAL, pqmsg->mp1, pqmsg->mp2);
|
---|
214 | return TRUE;
|
---|
215 |
|
---|
216 | //
|
---|
217 | // AltGr needs special handling
|
---|
218 | //
|
---|
219 | // AltGr -> WM_KEYDOWN (VK_CONTROL), WM_KEYDOWN (VK_MENU)
|
---|
220 | // WM_SYSKEYUP (VK_CONTROL)
|
---|
221 | // WM_KEYUP (VK_MENU)
|
---|
222 | //
|
---|
223 | // Ctrl+AltGr -> WM_KEYDOWN (VK_CONTROL), WM_KEYUP (VK_CONTROL)
|
---|
224 | // WM_KEYDOWN (VK_CONTROL)
|
---|
225 | // WM_KEYDOWN (VK_MENU)
|
---|
226 | // WM_KEYUP (VK_MENU)
|
---|
227 | // WM_KEYUP (VK_CONTROL)
|
---|
228 | //
|
---|
229 | // AltGr+Ctrl -> WM_KEYDOWN (VK_CONTROL), WM_KEYDOWN (VK_MENU)
|
---|
230 | // WM_KEYDOWN (VK_CONTROL)
|
---|
231 | // WM_SYSKEYUP (VK_CONTROL)
|
---|
232 | // WM_SYSKEYUP (VK_CONTROL)
|
---|
233 | // WM_KEYUP (VK_MENU)
|
---|
234 | //
|
---|
235 | // AltGr down -> if Ctrl down, send WM_KEYUP (VK_CONTROL)
|
---|
236 | // endif
|
---|
237 | // Send WM_KEYDOWN (VK_CONTROL)
|
---|
238 | // Send WM_KEYDOWN (VK_MENU)
|
---|
239 | // AltGr up -> if !(Ctrl down before AltGr was pressed || Ctrl up)
|
---|
240 | // Send WM_SYSKEYUP (VK_CONTROL)
|
---|
241 | // endif
|
---|
242 | // Send WM_KEYDOWN (VK_MENU)
|
---|
243 | //
|
---|
244 | // NOTE: Ctrl = Ctrl-Left; AltGr doesn't care about the right Ctrl key
|
---|
245 | //
|
---|
246 | case PMSCAN_ALTRIGHT:
|
---|
247 | {
|
---|
248 | QMSG msg = *pqmsg;
|
---|
249 | ULONG ctrlstate;
|
---|
250 | ULONG flags;
|
---|
251 | ULONG mp1, mp2;
|
---|
252 |
|
---|
253 | flags = SHORT1FROMMP(pqmsg->mp1);
|
---|
254 |
|
---|
255 | if (flags & KC_KEYUP)
|
---|
256 | {
|
---|
257 | //AltGr up
|
---|
258 | ctrlstate = WinGetPhysKeyState(HWND_DESKTOP, PMSCAN_CTRLLEFT);
|
---|
259 | if (!(ctrlstate & 0x8000))
|
---|
260 | {
|
---|
261 | //ctrl is up, translate this message to Ctrl key up
|
---|
262 | mp1 = (PMSCAN_CTRLLEFT << 24); //scancode
|
---|
263 | mp1 |= (1 << 16); //repeat count
|
---|
264 | mp1 |= (KC_ALT | KC_KEYUP | KC_VIRTUALKEY | KC_SCANCODE);
|
---|
265 | mp2 = (VK_CTRL << 16); //virtual keycode
|
---|
266 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL_ALTGRCONTROL, (MPARAM)mp1, (MPARAM)mp2);
|
---|
267 |
|
---|
268 | //and finally, post the AltGr WM_CHAR message
|
---|
269 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL, msg.mp1, msg.mp2);
|
---|
270 | }
|
---|
271 | else {
|
---|
272 | WinPostMsg(pqmsg->hwnd, WM_CHAR_SPECIAL, pqmsg->mp1, pqmsg->mp2);
|
---|
273 | }
|
---|
274 | }
|
---|
275 | else
|
---|
276 | {
|
---|
277 | //AltGr down
|
---|
278 | ctrlstate = WinGetPhysKeyState(HWND_DESKTOP, PMSCAN_CTRLLEFT);
|
---|
279 | if (ctrlstate & 0x8000)
|
---|
280 | {
|
---|
281 | //ctrl is down, translate this message to Ctrl key up
|
---|
282 | mp1 = (PMSCAN_CTRLLEFT << 24); //scancode
|
---|
283 | mp1 |= (1 << 16); //repeat count
|
---|
284 | mp1 |= (KC_KEYUP | KC_VIRTUALKEY | KC_SCANCODE);
|
---|
285 | mp2 = (VK_CTRL << 16); //virtual keycode
|
---|
286 |
|
---|
287 | WinPostMsg(pqmsg->hwnd, WM_CHAR_SPECIAL_ALTGRCONTROL, (MPARAM)mp1, (MPARAM)mp2);
|
---|
288 | }
|
---|
289 | //send left control key down message
|
---|
290 | mp1 = (PMSCAN_CTRLLEFT << 24); //scancode
|
---|
291 | mp1 |= (1 << 16); //repeat count
|
---|
292 | mp1 |= (KC_CTRL | KC_VIRTUALKEY | KC_SCANCODE);
|
---|
293 | mp2 = (VK_CTRL << 16); //virtual keycode
|
---|
294 |
|
---|
295 | if (ctrlstate & 0x8000)
|
---|
296 | {
|
---|
297 | //ctrl is down, must post this message
|
---|
298 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL_ALTGRCONTROL, (MPARAM)mp1, (MPARAM)mp2);
|
---|
299 | }
|
---|
300 | else
|
---|
301 | {
|
---|
302 | //translate this message into control key down
|
---|
303 | WinPostMsg(pqmsg->hwnd, WM_CHAR_SPECIAL_ALTGRCONTROL, (MPARAM)mp1, (MPARAM)mp2);
|
---|
304 | }
|
---|
305 | //and finally, post the AltGr WM_CHAR message
|
---|
306 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL, msg.mp1, msg.mp2);
|
---|
307 | }
|
---|
308 | return TRUE;
|
---|
309 | }
|
---|
310 | }
|
---|
311 | break; // WM_CHAR
|
---|
312 | }
|
---|
313 | } // end switch(msg)
|
---|
314 | return FALSE;
|
---|
315 | }
|
---|