source: trunk/src/pmkbdhk/pmkbdhk.cpp@ 9969

Last change on this file since 9969 was 9813, checked in by sandervl, 23 years ago

Special handling of AltGr key. Must send Ctrl key messages like Windows

File size: 18.6 KB
Line 
1/* $Id: pmkbdhk.cpp,v 1.4 2003-02-16 18:29:05 sandervl Exp $ */
2/*
3 * OS/2 native Presentation Manager hooks
4 *
5 *
6 * Large Portions (C) Ulrich M”ller, XWorkplace
7 * Copyright 2001 Patrick Haller (patrick.haller@innotek.de)
8 *
9 *
10 * Project Odin Software License can be found in LICENSE.TXT
11 *
12 */
13#define INCL_WIN
14#define INCL_WININPUT
15#define INCL_WINMESSAGEMGR
16#define INCL_WINHOOKS
17#define INCL_PM
18#define INCL_DOSERRORS
19#define INCL_DOSSEMAPHORES
20#include <os2.h>
21#include <stdlib.h>
22#include <string.h>
23
24#include <pmscan.h>
25
26#include <pmkbdhk.h>
27#include "pmkbdhkp.h"
28
29/*
30
31 To improve the ODIN keyboard management, it is required to intercept
32 any possible key from the Presentation Manager input queue.
33
34 Sketch:
35
36 - intercept keyboard messages before accelerators are translated
37 (HK_PREACCEL)
38 - determine if the message is targetted for an ODIN window
39 - if no ODIN window, pass thru
40 - if ODIN window
41 - check content of message.
42 - if sensible key event, rewrite message so PM won't handle it
43 itself (F1, F10, PrtScr, Ctrl, Alt, AltGr)
44 Ensure, GetKeyState() / GetAsyncKeyState() will still work properly!
45 The so rewritten message is handled specially inside the ODIN
46 message procedure. There, additionally required messages such as
47 WM_MENU if ALT is pressed need to be generated.
48 - if no sensible key, pass thru
49
50 Installation:
51 - automatically install hook on loading of the DLL
52 - automatically uninstall the hook on DLL termination
53 - DLL is GLOBAL INITTERM, used shared mem only!
54
55*/
56
57
58/**********
59 * Prototypes
60 **********/
61
62BOOL EXPENTRY hookPreAccelHook(HAB hab, PQMSG pqmsg, ULONG option);
63
64// _CRT_init is the C run-time environment initialization function.
65// It will return 0 to indicate success and -1 to indicate failure.
66int _Optlink _CRT_init(void);
67
68// _CRT_term is the C run-time environment termination function.
69// It only needs to be called when the C run-time functions are statically
70// linked, as is the case with XWorkplace.
71void _Optlink _CRT_term(void);
72
73
74
75/******************************************************************
76 *
77 * Module Global Variables
78 *
79 ******************************************************************/
80
81HOOKDATA G_HookData;
82
83
84/******************************************************************
85 *
86 * Helper functions
87 *
88 ******************************************************************/
89
90/*
91 *@@ InitializeGlobalsForHooks:
92 * this gets called from hookInit to initialize
93 * the global variables. We query the PM desktop,
94 * the window list, and other stuff we need all
95 * the time.
96 */
97
98VOID InitializeGlobalsForHooks(VOID)
99{
100 HENUM henum;
101 HWND hwndThis;
102 BOOL fFound;
103
104 // PM desktop (the WPS desktop is handled by the daemon)
105 G_HookData.hwndPMDesktop = WinQueryDesktopWindow(G_HookData.habDaemonObject,
106 NULLHANDLE);
107
108 WinQueryWindowProcess(HWND_DESKTOP, &G_HookData.pidPM, NULL);
109 // V0.9.7 (2001-01-21) [umoeller]
110
111 // enumerate desktop window to find the window list:
112 // according to PMTREE, the window list has the following
113 // window hierarchy:
114 // WC_FRAME
115 // +--- WC_TITLEBAR
116 // +--- Menu
117 // +--- WindowList
118 // +---
119 fFound = FALSE;
120 henum = WinBeginEnumWindows(HWND_DESKTOP);
121 while ( (!fFound)
122 && (hwndThis = WinGetNextWindow(henum))
123 )
124 {
125 CHAR szClass[200];
126 if (WinQueryClassName(hwndThis, sizeof(szClass), szClass))
127 {
128 if (!strcmp(szClass, "#1"))
129 {
130 // frame window: check the children
131 HENUM henumFrame;
132 HWND hwndChild;
133 henumFrame = WinBeginEnumWindows(hwndThis);
134 while ( (!fFound)
135 && (hwndChild = WinGetNextWindow(henumFrame))
136 )
137 {
138 CHAR szChildClass[200];
139 if (WinQueryClassName(hwndChild, sizeof(szChildClass), szChildClass))
140 {
141 if (!strcmp(szChildClass, "WindowList"))
142 {
143 // yup, found:
144 G_HookData.hwndWindowList = hwndThis;
145 fFound = TRUE;
146 }
147 }
148 }
149 WinEndEnumWindows(henumFrame);
150 }
151 }
152 }
153 WinEndEnumWindows(henum);
154
155}
156
157
158/******************************************************************
159 *
160 * Hook interface
161 *
162 ******************************************************************/
163
164/*
165 *@@ _DLL_InitTerm:
166 * this function gets called automatically by the OS/2 DLL
167 * during DosLoadModule processing, on the thread which
168 * invoked DosLoadModule.
169 *
170 * We override this function (which is normally provided by
171 * the runtime library) to intercept this DLL's module handle.
172 *
173 * Since OS/2 calls this function directly, it must have
174 * _System linkage.
175 *
176 * Note: You must then link using the /NOE option, because
177 * the VAC++ runtimes also contain a _DLL_Initterm, and the
178 * linker gets in trouble otherwise.
179 * The XWorkplace makefile takes care of this.
180 *
181 * This function must return 0 upon errors or 1 otherwise.
182 *
183 *@@changed V0.9.0 [umoeller]: reworked locale initialization
184 */
185
186unsigned long _System _DLL_InitTerm(unsigned long hModule,
187 unsigned long ulFlag)
188{
189 switch (ulFlag)
190 {
191 case 0:
192 {
193 // store the DLL handle in the global variable
194 G_HookData.hmodDLL = hModule;
195
196 // now initialize the C run-time environment before we
197 // call any runtime functions
198 if (_CRT_init() == -1)
199 return (0); // error
200
201 break; }
202
203 case 1:
204 // DLL being freed: cleanup runtime
205 _CRT_term();
206 break;
207
208 default:
209 // other code: beep for error
210 DosBeep(100, 100);
211 return (0); // error
212 }
213
214 // a non-zero value must be returned to indicate success
215 return (1);
216}
217
218/*
219 *@@ hookInit:
220 * registers (sets) all the hooks and initializes data.
221 *
222 * In any case, a pointer to the DLL's static HOOKDATA
223 * structure is returned. In this struct, the caller
224 * can examine the two flags for whether the hooks
225 * were successfully installed.
226 *
227 * Note: All the exported hook* interface functions must
228 * only be called by the same process, which is the
229 * XWorkplace daemon (XWPDAEMN.EXE).
230 *
231 * This gets called by XWPDAEMN.EXE when
232 *
233 *@@changed V0.9.1 (2000-02-01) [umoeller]: fixed missing global updates
234 *@@changed V0.9.2 (2000-02-21) [umoeller]: added new system hooks
235 */
236
237PHOOKDATA EXPENTRY hookInit(HAB hab)
238{
239 APIRET arc = NO_ERROR;
240
241 if (arc == NO_ERROR)
242 {
243 G_HookData.habDaemonObject = hab;
244
245 // G_HookData.hmtxPageMage = hmtxPageMage;
246
247 if (G_HookData.hmodDLL) // initialized by _DLL_InitTerm
248 {
249 // BOOL fSuccess = FALSE;
250
251 // initialize globals needed by the hook
252 InitializeGlobalsForHooks();
253
254 // install hooks, but only once...
255#if 0
256 if (!G_HookData.fLockupHooked)
257 G_HookData.fLockupHooked = WinSetHook(G_HookData.habDaemonObject,
258 NULLHANDLE, // system hook
259 HK_LOCKUP, // lockup hook
260 (PFN)hookLockupHook,
261 G_HookData.hmodDLL);
262#endif
263 if (!G_HookData.fPreAccelHooked)
264 G_HookData.fPreAccelHooked = WinSetHook(G_HookData.habDaemonObject,
265 NULLHANDLE, // system hook
266 HK_PREACCEL, // pre-accelerator table hook (undocumented)
267 (PFN)hookPreAccelHook,
268 G_HookData.hmodDLL);
269 }
270
271 }
272
273 return (&G_HookData);
274}
275
276/*
277 *@@ hookKill:
278 * deregisters the hook function and frees allocated
279 * resources.
280 *
281 * Note: This function must only be called by the same
282 * process which called hookInit (that is, the daemon),
283 * or resources cannot be properly freed.
284 *
285 *@@changed V0.9.1 (2000-02-01) [umoeller]: fixed missing global updates
286 *@@changed V0.9.2 (2000-02-21) [umoeller]: added new system hooks
287 *@@changed V0.9.3 (2000-04-20) [umoeller]: added function keys support
288 */
289
290BOOL EXPENTRY hookKill(void)
291{
292 BOOL brc = FALSE;
293
294 if (G_HookData.fPreAccelHooked)
295 {
296 WinReleaseHook(G_HookData.habDaemonObject,
297 NULLHANDLE,
298 HK_PREACCEL, // pre-accelerator table hook (undocumented)
299 (PFN)hookPreAccelHook,
300 G_HookData.hmodDLL);
301 brc = TRUE;
302 G_HookData.fPreAccelHooked = FALSE;
303 }
304
305#if 0
306 if (G_HookData.fLockupHooked)
307 {
308 WinReleaseHook(G_HookData.habDaemonObject,
309 NULLHANDLE,
310 HK_LOCKUP, // lockup hook
311 (PFN)hookLockupHook,
312 G_HookData.hmodDLL);
313 brc = TRUE;
314 G_HookData.fLockupHooked = FALSE;
315 }
316#endif
317
318 return (brc);
319}
320
321
322//
323// This function detects if the current window is
324// a win32 window
325//
326
327static HWND hwndLastWin32Window = 0;
328
329BOOL i_isWin32Window(HWND hwnd)
330{
331 // Note: this hook is called on the stack of the current process
332 // so we rather keep the buffer small ...
333 CHAR szClass[80];
334
335 if (hwndLastWin32Window == hwnd)
336 return TRUE;
337
338 for(;;)
339 {
340 if (WinQueryClassName(hwnd, sizeof(szClass), szClass))
341 {
342 if (strcmp(szClass, WIN32_STDFRAMECLASS) == 0)
343 {
344 hwndLastWin32Window = hwnd;
345 return TRUE;
346 }
347 else
348 {
349 // walk up the tree
350 hwnd = WinQueryWindow(hwnd,
351 QW_PARENT);
352
353 // no parent window found?
354 if (NULLHANDLE == hwnd)
355 {
356 hwndLastWin32Window = 0;
357 return FALSE;
358 }
359 }
360 }
361 else
362 {
363 hwndLastWin32Window = 0;
364 return FALSE;
365 }
366 }
367}
368
369
370/******************************************************************
371 *
372 * Pre-accelerator hook
373 *
374 ******************************************************************/
375
376/*
377 *@@ hookPreAccelHook:
378 * this is the pre-accelerator-table hook. Like
379 * hookInputHook, this gets called when messages
380 * are coming in from the system input queue, but
381 * as opposed to a "regular" input hook, this hook
382 * gets called even before translation from accelerator
383 * tables occurs.
384 *
385 * Pre-accel hooks are not documented in the PM Reference.
386 * I have found this idea (and part of the implementation)
387 * in the source code of ProgramCommander/2, so thanks
388 * go out (once again) to Roman Stangl.
389 *
390 * In this hook, we check for the global object hotkeys
391 * so that we can use certain key combinations regardless
392 * of whether they might be currently used in application
393 * accelerator tables. This is especially useful for
394 * "Alt" key combinations, which are usually intercepted
395 * when menus have corresponding shortcuts.
396 *
397 * As a result, as opposed to other hotkey software you
398 * might know, XWorkplace does properly react to "Ctrl+Alt"
399 * keyboard combinations, even if a menu would get called
400 * with the "Alt" key. ;-)
401 *
402 * As with hookInputHook, we return TRUE if the message is
403 * to be swallowed, or FALSE if the current application (or
404 * the next hook in the hook chain) should still receive the
405 * message.
406 *
407 *@@changed V0.9.3 (2000-04-09) [umoeller]: added check for system lockup
408 *@@changed V0.9.3 (2000-04-09) [umoeller]: added PageMage hotkeys
409 *@@changed V0.9.3 (2000-04-09) [umoeller]: added KC_SCANCODE check
410 *@@changed V0.9.3 (2000-04-10) [umoeller]: moved debug code to hook
411 */
412
413BOOL EXPENTRY hookPreAccelHook(HAB hab, PQMSG pqmsg, ULONG option)
414{
415 // set return value:
416 // per default, pass message on to next hook or application
417 BOOL brc = FALSE;
418
419 if (pqmsg == NULL)
420 return (FALSE);
421
422 switch(pqmsg->msg)
423 {
424 /*
425 * WM_CHAR:
426 * keyboard activity.
427 */
428
429 case WM_CHAR:
430 {
431 // Note:
432 // what happens it the system is locked up?
433 // do we have to detect the screensaver to kick in?
434 // if ( (!G_HookData.hwndLockupFrame) // system not locked up
435 // ...
436
437
438 // is this an WIN32 window?
439 if (!i_isWin32Window(pqmsg->hwnd))
440 {
441 // no, so pass thru
442 return FALSE;
443 }
444
445 // check if we've encountered a so called critical key
446 // (this is the scan code which is supposed to be valid
447 // always for the hooks! */
448 switch ( CHAR4FROMMP(pqmsg->mp1) )
449 {
450 default:
451 return FALSE;
452
453 // Intercept PM Window Hotkeys such as
454 // Alt-F7 do enable window moving by keyboard.
455 case PMSCAN_F1:
456 case PMSCAN_F2:
457 case PMSCAN_F3:
458 case PMSCAN_F4:
459 case PMSCAN_F5:
460 case PMSCAN_F6:
461 case PMSCAN_F7:
462 case PMSCAN_F8:
463 case PMSCAN_F9:
464 case PMSCAN_F10:
465 case PMSCAN_F11:
466 case PMSCAN_F12:
467
468 // Try to prevent Ctrl-Esc, etc. from being intercepted by PM
469 case PMSCAN_ESC:
470 case PMSCAN_CTRLLEFT:
471 case PMSCAN_CTRLRIGHT:
472
473 case PMSCAN_PRINT:
474 case PMSCAN_ALTLEFT:
475 case PMSCAN_SCROLLLOCK:
476 case PMSCAN_ENTER:
477 case PMSCAN_PADENTER:
478 case PMSCAN_CAPSLOCK:
479 case PMSCAN_SHIFTLEFT:
480 case PMSCAN_SHIFTRIGHT:
481 // OK, as we've got a special key here, we've got
482 // to rewrite the message so PM will ignore the key
483 // and won't translate the message to anything else.
484
485 pqmsg->msg = WM_CHAR_SPECIAL;
486
487 break;
488
489 //
490 // AltGr needs special handling
491 //
492 // AltGr -> WM_KEYDOWN (VK_CONTROL), WM_KEYDOWN (VK_MENU)
493 // WM_SYSKEYUP (VK_CONTROL)
494 // WM_KEYUP (VK_MENU)
495 //
496 // Ctrl+AltGr -> WM_KEYDOWN (VK_CONTROL), WM_KEYUP (VK_CONTROL)
497 // WM_KEYDOWN (VK_CONTROL)
498 // WM_KEYDOWN (VK_MENU)
499 // WM_KEYUP (VK_MENU)
500 // WM_KEYUP (VK_CONTROL)
501 //
502 // AltGr+Ctrl -> WM_KEYDOWN (VK_CONTROL), WM_KEYDOWN (VK_MENU)
503 // WM_KEYDOWN (VK_CONTROL)
504 // WM_SYSKEYUP (VK_CONTROL)
505 // WM_SYSKEYUP (VK_CONTROL)
506 // WM_KEYUP (VK_MENU)
507 //
508 // AltGr down -> if Ctrl down, send WM_KEYUP (VK_CONTROL)
509 // endif
510 // Send WM_KEYDOWN (VK_CONTROL)
511 // Send WM_KEYDOWN (VK_MENU)
512 // AltGr up -> if !(Ctrl down before AltGr was pressed || Ctrl up)
513 // Send WM_SYSKEYUP (VK_CONTROL)
514 // endif
515 // Send WM_KEYDOWN (VK_MENU)
516 //
517 // NOTE: Ctrl = Ctrl-Left; AltGr doesn't care about the right Ctrl key
518 //
519 case PMSCAN_ALTRIGHT:
520 {
521 QMSG msg = *pqmsg;
522 ULONG ctrlstate;
523 ULONG flags;
524 ULONG mp1, mp2;
525
526 flags = SHORT1FROMMP(pqmsg->mp1);
527
528 pqmsg->msg = WM_CHAR_SPECIAL;
529
530 if(flags & KC_KEYUP)
531 {//AltGr up
532 ctrlstate = WinGetPhysKeyState(HWND_DESKTOP, PMSCAN_CTRLLEFT);
533 if(!(ctrlstate & 0x8000))
534 {//ctrl is up, translate this message to Ctrl key up
535 mp1 = (PMSCAN_CTRLLEFT << 24); //scancode
536 mp1 |= (1 << 16); //repeat count
537 mp1 |= (KC_ALT | KC_KEYUP | KC_VIRTUALKEY | KC_SCANCODE);
538 mp2 = (VK_CTRL << 16); //virtual keycode
539 pqmsg->msg = WM_CHAR_SPECIAL_ALTGRCONTROL;
540 pqmsg->mp1 = (MPARAM)mp1;
541 pqmsg->mp2 = (MPARAM)mp2;
542
543 //and finally, post the AltGr WM_CHAR message
544 WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL, msg.mp1, msg.mp2);
545 }
546 //else do nothing
547 }
548 else
549 {//AltGr down
550 ctrlstate = WinGetPhysKeyState(HWND_DESKTOP, PMSCAN_CTRLLEFT);
551 if(ctrlstate & 0x8000)
552 {//ctrl is down, translate this message to Ctrl key up
553 mp1 = (PMSCAN_CTRLLEFT << 24); //scancode
554 mp1 |= (1 << 16); //repeat count
555 mp1 |= (KC_KEYUP | KC_VIRTUALKEY | KC_SCANCODE);
556 mp2 = (VK_CTRL << 16); //virtual keycode
557 pqmsg->msg = WM_CHAR_SPECIAL_ALTGRCONTROL;
558 pqmsg->mp1 = (MPARAM)mp1;
559 pqmsg->mp2 = (MPARAM)mp2;
560 }
561 //send left control key down message
562 mp1 = (PMSCAN_CTRLLEFT << 24); //scancode
563 mp1 |= (1 << 16); //repeat count
564 mp1 |= (KC_CTRL | KC_VIRTUALKEY | KC_SCANCODE);
565 mp2 = (VK_CTRL << 16); //virtual keycode
566
567 if(ctrlstate & 0x8000)
568 {//ctrl is down, must post this message
569 WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL_ALTGRCONTROL, (MPARAM)mp1, (MPARAM)mp2);
570 }
571 else
572 {//translate this message into control key down
573 pqmsg->msg = WM_CHAR_SPECIAL_ALTGRCONTROL;
574 pqmsg->mp1 = (MPARAM)mp1;
575 pqmsg->mp2 = (MPARAM)mp2;
576 }
577 //and finally, post the AltGr WM_CHAR message
578 WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL, msg.mp1, msg.mp2);
579 }
580 break;
581 }
582
583 }
584 }
585
586 break; // WM_CHAR
587 } // end switch(msg)
588
589 return (brc);
590}
591
Note: See TracBrowser for help on using the repository browser.