1 /********************************************************************************
2 * *
3 * K e y b o a r d H a n d l i n g *
4 * *
5 *********************************************************************************
6 * Copyright (C) 1997,2021 by Jeroen van der Zijp. All Rights Reserved. *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU Lesser General Public License as published by *
10 * the Free Software Foundation; either version 3 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This library is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU Lesser General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU Lesser General Public License *
19 * along with this program. If not, see <http://www.gnu.org/licenses/> *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "fxkeys.h"
25 #include "FXArray.h"
26 #include "FXHash.h"
27 #include "FXMutex.h"
28 #include "FXStream.h"
29 #include "FXObject.h"
30 #include "FXString.h"
31 #include "FXStringDictionary.h"
32 #include "FXSettings.h"
33 #include "FXRegistry.h"
34 #include "FXSize.h"
35 #include "FXPoint.h"
36 #include "FXRectangle.h"
37 #include "FXEvent.h"
38 #include "FXWindow.h"
39 #include "FXApp.h"
40 #include "fxpriv.h"
41
42 /*
43 Notes:
44
45 - Translate key cap codes to FOX key symbols on MS-Windows.
46 - Windows code first written by Daniel Gehriger <gehriger@linkcad.com.
47 */
48
49 using namespace FX;
50
51 /*******************************************************************************/
52
53 namespace FX {
54
55
56 #ifdef WIN32
57
58 //-------------------------------------------------------------------------
59 //
60 // Notes (Daniel Gehriger <gehriger@linkcad.com>:
61 //
62 //
63 // [In the comments below, WM_KEYx refers to WM_KEYDOWN|WM_SYSKEYDOWN|
64 // WM_KEYUP|WM_SYSKEYUP; WM_CHAR refers to WM_CHAR and WM_IME_CHAR]
65 //
66 // Notes
67 // =====
68 //
69 // 1. MapVirtualKeyEx and ToAsciiEx don't work in Korean Win98 (although
70 // they're fine in other Korean Windows products); MapVirtualKey and
71 // ToAscii work fine.
72 //
73 //
74 // 2. Some keyboard layouts use the Right-ALT key as a 2nd level
75 // shift key, and label it as AltGr. With these layouts, it is IMPOSSIBLE
76 // to distinguish between "Ctrl + AltGr + Key" and "AltGr + Key" during the
77 // WM_KEYDOWN messages. Neither the keyboard state nor the messages differ
78 // between the two key sequences.
79 //
80 // Implications: suppose some menu command has been bound to "Ctrl + }". On my
81 // Swiss keyboard, I have to type "AltGr + $" to get the right bracket. This means
82 // that FOX receives the sames messages if I type "}" or "Left-Ctrl + }" !
83 //
84 // Solution: I wrote a function winkeyCheckLayout() that checks if a keyboard
85 // layout uses the Right-Alt key as AltGr. If it does, I interpret any combination
86 // of "Left-Ctrl + AltGr" as "AltGr", i.e. I drop the Left-Ctrl key. Keyboard
87 // accelerators can still be reached with the "Right-Ctrl".
88 //
89 //
90 // 3. Related to the previous comment: how can we distinguish between left
91 // and right Alt / Ctrl / Shift ? Windows 95/98/Me do not set the
92 // corresponding VK_L*/VK_R* keyboard states, as NT does. We must look
93 // at bit 24, the "extended bit" of lParam in the window messages. The code
94 // below goes to great lenghts to correctly determine the state of the
95 // left / right keys on these systems.
96 //
97 //
98 // 4. The Win32 function ToAscii() has internal state. This means that calling this
99 // function twice with the same arguments does NOT generally yield the same result.
100 // For instance, if the circumflex (^) is used to compose characters in the
101 // selected keyboard layout, and one types '^' followed by 'e', calling
102 // ToAscii() to translate the 'e' yields '�' (e-grave). Calling ToAscii() again,
103 // however, yields a raw 'e'. Also, I found that the Win32 function
104 // TranslateMessage(), which is used to have Win32 generate WM_CHAR messages,
105 // calls ToAscii() internally.
106 //
107 // The implications of this are :
108 //
109 // 1. Either use TranslateMessage() OR use ToAscii() when processing the above
110 // messages. Calling both breaks keyboard processing !!!!
111 //
112 // 2. Only call ToAscii() when a WM_KEYUP/WM_KEYDOWN/WM_SYSKEYUP/WM_SYSKEYDOWN
113 // message has been received.
114 //
115 //
116 // 5. Keyboard accelerators: they are used to trigger some command by typing
117 // a character sequence. Some accelerator sequences include the Shift key:
118 //
119 // Example: "Ctrl + {" versus "Ctrl + Shift + {".
120 //
121 // However, on some keyboards, some of the characters in the accelerator
122 // sequences themselves require the Shift key as a character modifier.
123 //
124 // Example: the US keyboard layout requires "Shift + [" to type "{".
125 //
126 // There is NO workaround, except not using Shift in accelerator sequences
127 // that include non-letters.
128 //
129 //-------------------------------------------------------------------------
130
131 // Keyboard map for control keys
132 static const FXuint keymapCtl[] = {
133 VK_PRIOR, KEY_Prior,
134 VK_NEXT, KEY_Next,
135 VK_END, KEY_End,
136 VK_HOME, KEY_Home,
137 VK_LEFT, KEY_Left,
138 VK_UP, KEY_Up,
139 VK_RIGHT, KEY_Right,
140 VK_DOWN, KEY_Down,
141 VK_INSERT, KEY_Insert,
142 VK_DELETE, KEY_Delete,
143 VK_HELP, KEY_Help,
144 VK_F1, KEY_F1,
145 VK_F2, KEY_F2,
146 VK_F3, KEY_F3,
147 VK_F4, KEY_F4,
148 VK_F5, KEY_F5,
149 VK_F6, KEY_F6,
150 VK_F7, KEY_F7,
151 VK_F8, KEY_F8,
152 VK_F9, KEY_F9,
153 VK_F10, KEY_F10,
154 VK_F11, KEY_F11,
155 VK_F12, KEY_F12,
156 VK_F13, KEY_F13,
157 VK_F14, KEY_F14,
158 VK_F15, KEY_F15,
159 VK_F16, KEY_F16,
160 VK_F17, KEY_F17,
161 VK_F18, KEY_F18,
162 VK_F19, KEY_F19,
163 VK_F20, KEY_F20,
164 VK_F21, KEY_F21,
165 VK_F22, KEY_F22,
166 VK_F23, KEY_F23,
167 VK_F24, KEY_F24,
168 VK_SCROLL, KEY_Scroll_Lock,
169 VK_CLEAR, KEY_Begin,
170 VK_CAPITAL, KEY_Caps_Lock,
171 VK_NUMLOCK, KEY_Num_Lock,
172 VK_SNAPSHOT, KEY_Print,
173 VK_CANCEL, KEY_Break,
174 VK_PAUSE, KEY_Pause,
175 VK_BACK, KEY_BackSpace,
176 VK_TAB, KEY_Tab,
177 VK_ESCAPE, KEY_Escape,
178 VK_SPACE, KEY_space,
179 VK_MULTIPLY, KEY_KP_Multiply,
180 VK_ADD, KEY_KP_Add,
181 VK_SEPARATOR, KEY_KP_Separator,
182 VK_SUBTRACT, KEY_KP_Subtract,
183 VK_DECIMAL, KEY_KP_Decimal,
184 VK_DIVIDE, KEY_KP_Divide,
185 VK_NUMPAD0, KEY_KP_0,
186 VK_NUMPAD1, KEY_KP_1,
187 VK_NUMPAD2, KEY_KP_2,
188 VK_NUMPAD3, KEY_KP_3,
189 VK_NUMPAD4, KEY_KP_4,
190 VK_NUMPAD5, KEY_KP_5,
191 VK_NUMPAD6, KEY_KP_6,
192 VK_NUMPAD7, KEY_KP_7,
193 VK_NUMPAD8, KEY_KP_8,
194 VK_NUMPAD9, KEY_KP_9,
195 VK_LWIN, KEY_Super_L,
196 VK_RWIN, KEY_Super_R
197 };
198
199
200 #define KEYDOWN(ks,vk) (((ks)[vk]&0x80)!=0)
201 #define KEYUP(ks,vk) (((ks)[vk]&0x80)==0)
202 #define KEYTOGGLED(ks,vk) (((ks)[vk]&0x01)!=0)
203
204
205 // True if OS does not distinguish between left & right Alt/Ctrl/Shift keys
206 static FXbool bNoLR=false;
207 static BYTE ksRShft=0;
208 static BYTE ksRCtrl=0;
209 static BYTE ksLMenu=0;
210 static BYTE ksRMenu=0;
211
212
213 // Retrieves the current input code page
wkbGetCodePage()214 UINT wkbGetCodePage(){
215 static HKL hklOld=NULL;
216 static UINT uCPID=0;
217 HKL hkl=GetKeyboardLayout(0);
218 if(hklOld!=hkl || uCPID==0){
219 hklOld=hkl;
220 char lpLCData[256];
221 if(GetLocaleInfoA(LANGIDFROMLCID(LOWORD(hkl)),LOCALE_IDEFAULTANSICODEPAGE,lpLCData,sizeof(lpLCData))==0) return CP_ACP;
222 uCPID=atoi(lpLCData);
223 }
224 return uCPID;
225 }
226
227
228 // Checks if the right-hand ALT key is used as a 2nd shift key
wkbAltGrDown(PBYTE ks)229 static FXbool wkbAltGrDown(PBYTE ks){
230 static FXbool bHasAltGr=false;
231 static HKL hklOld = NULL;
232 HKL hkl=GetKeyboardLayout(0);
233 if(hklOld!=hkl){
234 hklOld=hkl;
235 bHasAltGr=false;
236 for(FXuint ch=0x20; ch<=0xff ; ++ch){
237 // <MSDN>
238 // For keyboard layouts that use the right-hand ALT key as a shift key
239 // (for example, the French keyboard layout), the shift state is
240 // represented by the value 6, because the right-hand ALT key is
241 // converted internally into CTRL+ALT.
242 // </MSDN>
243 if(HIBYTE(VkKeyScanEx(ch,hkl))==6){
244 bHasAltGr=true;
245 break;
246 }
247 }
248 }
249 if(bNoLR)
250 return bHasAltGr && KEYDOWN(ks,VK_MENU) && ksRMenu;
251 else
252 return bHasAltGr /* && KEYDOWN(ks, VK_LCONTROL) */ && KEYDOWN(ks,VK_RMENU);
253 }
254
255
256 // Return the current state of the modifier keys and mouse buttons
fxmodifierkeys()257 unsigned int fxmodifierkeys(){
258 FXuint state=0;
259 BYTE ks[256];
260 GetKeyboardState(ks);
261 if(KEYDOWN(ks,VK_SHIFT)) state|=SHIFTMASK;
262 if(KEYTOGGLED(ks,VK_CAPITAL)) state|=CAPSLOCKMASK;
263 if(KEYTOGGLED(ks,VK_NUMLOCK)) state|=NUMLOCKMASK;
264 if(KEYTOGGLED(ks,VK_SCROLL)) state|=SCROLLLOCKMASK;
265 if(KEYDOWN(ks,VK_LBUTTON)) state|=LEFTBUTTONMASK;
266 if(KEYDOWN(ks,VK_MBUTTON)) state|=MIDDLEBUTTONMASK;
267 if(KEYDOWN(ks,VK_RBUTTON)) state|=RIGHTBUTTONMASK;
268 if(KEYDOWN(ks,VK_LWIN)) state|=METAMASK; // Added JVZ
269 if(KEYDOWN(ks,VK_RWIN)) state|=METAMASK;
270 if(wkbAltGrDown(ks)){
271 // Left-Ctrl + Right-Alt = AltGr is used to compose characters;
272 // If AltGr is pressed, only allow Right-Control & Left-Alt
273 if(ksRCtrl) state|=CONTROLMASK;
274 if(ksLMenu) state|=ALTMASK;
275 }
276 else{
277 if(KEYDOWN(ks,VK_CONTROL)) state|=CONTROLMASK;
278 if(KEYDOWN(ks,VK_MENU)) state|=ALTMASK;
279 }
280 return state;
281 }
282
283 //VK_LWIN (5B)
284 //Left Windows key (Microsoft Natural keyboard)
285
286 //VK_RWIN (5C)
287 //Right Windows key (Natural keyboard)
288
289 // Map Win32 virtual key codes to FOX key codes
wkbMapKeyCode(UINT iMsg,WPARAM uVirtKey,LPARAM lParam)290 FXuint wkbMapKeyCode(UINT iMsg,WPARAM uVirtKey,LPARAM lParam){
291 BYTE ks[256];
292 char c;
293
294 // Get keyboard state
295 if(GetKeyboardState(ks)!=0){
296
297 // Determine left/right key states
298 BYTE ksOldRShft=ksRShft;
299 BYTE ksOldRCtrl=ksRCtrl;
300 BYTE ksOldRMenu=ksRMenu;
301
302 FXuint xt=HIWORD(lParam)&KF_EXTENDED;
303
304 if(!bNoLR && iMsg==WM_KEYDOWN){
305 if(uVirtKey==VK_CONTROL) bNoLR|=(KEYDOWN(ks,xt ? VK_RCONTROL : VK_LCONTROL) ^ KEYDOWN(ks,VK_CONTROL));
306 if(uVirtKey==VK_MENU) bNoLR|=(KEYDOWN(ks,xt ? VK_RMENU : VK_LMENU) ^ KEYDOWN(ks,VK_MENU));
307 }
308
309 // OS does not save correct left/right key states (most Win95/98/Me)
310 if(bNoLR){
311 ksRShft = ks[VK_RSHIFT];
312 ksRCtrl = (KEYDOWN(ks,VK_CONTROL) ? (uVirtKey==VK_CONTROL&&xt) ? 0x80 : ksRCtrl : 0x00);
313 ksRMenu = (KEYDOWN(ks,VK_MENU) ? (uVirtKey==VK_MENU && xt) ? 0x80 : ksRMenu : 0x00);
314 ksLMenu = (KEYDOWN(ks,VK_MENU) ? (uVirtKey==VK_MENU && !xt) ? 0x80 : ksLMenu : 0x00);
315 }
316
317 // OS saves correct left/right key states
318 else{
319 ksRShft = KEYDOWN(ks,VK_RSHIFT);
320 ksRCtrl = KEYDOWN(ks,VK_RCONTROL);
321 ksRMenu = KEYDOWN(ks,VK_RMENU);
322 ksLMenu = KEYDOWN(ks,VK_LMENU);
323 }
324
325 // Map virtual key code
326 switch(uVirtKey){
327 case VK_SHIFT:
328 if(iMsg==WM_KEYDOWN && HIWORD(lParam)&KF_REPEAT)
329 return KEY_VoidSymbol;
330 else
331 return (ksRShft^ksOldRShft) ? KEY_Shift_R : KEY_Shift_L;
332
333 case VK_CONTROL:
334 if(iMsg==WM_KEYDOWN && HIWORD(lParam)&KF_REPEAT)
335 return KEY_VoidSymbol;
336 else
337 return (ksRCtrl^ksOldRCtrl) ? KEY_Control_R : KEY_Control_L;
338
339 case VK_MENU:
340 if((iMsg==WM_KEYDOWN || iMsg==WM_SYSKEYDOWN) && (HIWORD(lParam)&KF_REPEAT)) // Patch Jon Sargeant <delta17@cox.net>
341 return KEY_VoidSymbol;
342 else
343 return (ksRMenu^ksOldRMenu) ? KEY_Alt_R : KEY_Alt_L;
344
345 case VK_RETURN:
346 if(uVirtKey==VK_RETURN && (HIWORD(lParam)&KF_EXTENDED))
347 return KEY_KP_Enter;
348 else
349 return KEY_Return;
350
351 default:
352
353 // Map remaining special keys
354 for(FXuint i=0; i<ARRAYNUMBER(keymapCtl)/2; ++i){
355 if(keymapCtl[2*i]==uVirtKey){
356 return keymapCtl[2*i+1];
357 }
358 }
359
360 // Map characters (they come in as uppercase,
361 // but FOX wants them converted according the current shift state...
362 if('A'<=uVirtKey && uVirtKey<='Z'){
363 return (FXuint)(KEYDOWN(ks,VK_SHIFT) ? uVirtKey : uVirtKey-'A'+KEY_a);
364 }
365
366 // Ask Windows to map remaining characters
367 c=(char)LOWORD(MapVirtualKeyEx(uVirtKey,2,GetKeyboardLayout(0))); // FIXME ";" and ":" map to same keysym; this is wrong!
368 if(c) return c;
369 }
370 }
371 return KEY_VoidSymbol;
372 }
373
374
375 // Windows 9x don't support ToUnicodeEx (but Windows 98/Me export the function
376 // as a no-op from User32.DLL. The following code determines upon the first call
377 // to fxToUnicodeEx if the OS supports ToUnicodeEx and fixes the function pointer
378 // to send subsequent calls directly to the OS. Otherwise, a stub is used.
379 int WINAPI wkbToUnicodeExStub(UINT,UINT,const BYTE*,LPWSTR,int,UINT,HKL);
380 int WINAPI wkbToUnicodeExWin9x(UINT,UINT,const BYTE*,LPWSTR,int,UINT,HKL);
381
382 typedef int (WINAPI *PFN_TOUNICODEEX)(UINT,UINT,const BYTE*,LPWSTR,int,UINT,HKL);
383
384 PFN_TOUNICODEEX ToUnicodeEx=wkbToUnicodeExStub;
385
386 // Stub function for first call to fxToUnicodeEx()
wkbToUnicodeExStub(UINT uVirtKey,UINT uScanCode,const BYTE * lpKeyState,LPWSTR pwszBuff,int cchBuff,UINT wFlags,HKL dwhkl)387 int WINAPI wkbToUnicodeExStub(UINT uVirtKey,UINT uScanCode,const BYTE* lpKeyState,LPWSTR pwszBuff,int cchBuff,UINT wFlags,HKL dwhkl){
388 OSVERSIONINFOA osinfo={sizeof(OSVERSIONINFOA)};
389 GetVersionExA(&osinfo);
390 if(osinfo.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS){
391 // Windows 9x/Me => use stub
392 ToUnicodeEx = wkbToUnicodeExWin9x;
393 }
394 else{
395 // Windows NT => forward to OS
396 HMODULE user32Dll=LoadLibraryA("user32");
397 ToUnicodeEx=(PFN_TOUNICODEEX)GetProcAddress(user32Dll,"ToUnicodeEx");
398 if(!ToUnicodeEx){ToUnicodeEx=wkbToUnicodeExWin9x;}
399 }
400 return ToUnicodeEx(uVirtKey, uScanCode, lpKeyState, pwszBuff, cchBuff, wFlags, dwhkl);
401 }
402
403
404 // Adapter function for Windows 9x/Me
wkbToUnicodeExWin9x(UINT uVirtKey,UINT uScanCode,const BYTE * lpKeyState,LPWSTR pwszBuff,int cchBuff,UINT wFlags,HKL dwhkl)405 int WINAPI wkbToUnicodeExWin9x(UINT uVirtKey,UINT uScanCode,const BYTE* lpKeyState,LPWSTR pwszBuff,int cchBuff,UINT wFlags,HKL dwhkl){
406 WORD c;
407 int cnt=ToAsciiEx(uVirtKey,uScanCode,(BYTE*)lpKeyState,&c,wFlags,dwhkl);
408 if(cnt<=0) return cnt;
409 return MultiByteToWideChar(CP_ACP,0,(LPCSTR)&c,cnt,pwszBuff,cchBuff);
410 }
411
412 #endif
413
414 }
415