| 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 | } | 
|---|