[10443] | 1 | /* $Id: kbdhook.cpp,v 1.2 2004-02-10 15:36:04 sandervl Exp $ */
|
---|
[10382] | 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 |
|
---|
[10443] | 213 | WinPostMsg(pqmsg->hwnd, WM_CHAR_SPECIAL, pqmsg->mp1, pqmsg->mp2);
|
---|
| 214 | return TRUE;
|
---|
[10382] | 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
|
---|
[10443] | 266 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL_ALTGRCONTROL, (MPARAM)mp1, (MPARAM)mp2);
|
---|
[10382] | 267 |
|
---|
| 268 | //and finally, post the AltGr WM_CHAR message
|
---|
| 269 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL, msg.mp1, msg.mp2);
|
---|
| 270 | }
|
---|
[10443] | 271 | else {
|
---|
| 272 | WinPostMsg(pqmsg->hwnd, WM_CHAR_SPECIAL, pqmsg->mp1, pqmsg->mp2);
|
---|
| 273 | }
|
---|
[10382] | 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
|
---|
[10443] | 286 |
|
---|
| 287 | WinPostMsg(pqmsg->hwnd, WM_CHAR_SPECIAL_ALTGRCONTROL, (MPARAM)mp1, (MPARAM)mp2);
|
---|
[10382] | 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
|
---|
[10443] | 303 | WinPostMsg(pqmsg->hwnd, WM_CHAR_SPECIAL_ALTGRCONTROL, (MPARAM)mp1, (MPARAM)mp2);
|
---|
[10382] | 304 | }
|
---|
| 305 | //and finally, post the AltGr WM_CHAR message
|
---|
| 306 | WinPostMsg(msg.hwnd, WM_CHAR_SPECIAL, msg.mp1, msg.mp2);
|
---|
| 307 | }
|
---|
[10443] | 308 | return TRUE;
|
---|
[10382] | 309 | }
|
---|
| 310 | }
|
---|
| 311 | break; // WM_CHAR
|
---|
| 312 | }
|
---|
| 313 | } // end switch(msg)
|
---|
| 314 | return FALSE;
|
---|
| 315 | }
|
---|