-
Notifications
You must be signed in to change notification settings - Fork 1
/
hook.cpp
4580 lines (4278 loc) · 250 KB
/
hook.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
AutoHotkey
Copyright 2003-2008 Chris Mallett (support@autohotkey.com)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "stdafx.h" // pre-compiled headers
#include "hook.h"
#include "globaldata.h" // for access to several global vars
#include "util.h" // for snprintfcat()
#include "window.h" // for MsgBox()
#include "application.h" // For MsgSleep().
// Declare static variables (global to only this file/module, i.e. no external linkage):
static HANDLE sKeybdMutex = NULL;
static HANDLE sMouseMutex = NULL;
#define KEYBD_MUTEX_NAME "AHK Keybd"
#define MOUSE_MUTEX_NAME "AHK Mouse"
// Whether to disguise the next up-event for lwin/rwin to suppress Start Menu.
// These are made global, rather than static inside the hook function, so that
// we can ensure they are initialized by the keyboard init function every
// time it's called (currently it can be only called once):
static bool sDisguiseNextLWinUp; // Initialized by ResetHook().
static bool sDisguiseNextRWinUp; //
static bool sDisguiseNextLAltUp; //
static bool sDisguiseNextRAltUp; //
static bool sAltTabMenuIsVisible; //
static vk_type sVKtoIgnoreNextTimeDown; //
// The prefix key that's currently down (i.e. in effect).
// It's tracked this way, rather than as a count of the number of prefixes currently down, out of
// concern that such a count might accidentally wind up above zero (due to a key-up being missed somehow)
// and never come back down, thus penalizing performance until the program is restarted:
static key_type *pPrefixKey; // Initialized by ResetHook().
// Less memory overhead (space and performance) to allocate a solid block for multidimensional arrays:
// These store all the valid modifier+suffix combinations (those that result in hotkey actions) except
// those with a ModifierVK/SC. Doing it this way should cut the CPU overhead caused by having many
// hotkeys handled by the hook, down to a fraction of what it would be otherwise. Indeed, doing it
// this way makes the performance impact of adding many additional hotkeys of this type exactly zero
// once the program has started up and initialized. The main alternative is a binary search on an
// array of keyboard-hook hotkeys (similar to how the mouse is done):
static HotkeyIDType *kvkm = NULL;
static HotkeyIDType *kscm = NULL;
static HotkeyIDType *hotkey_up = NULL;
static key_type *kvk = NULL;
static key_type *ksc = NULL;
// Macros for convenience in accessing the above arrays as multidimensional objects.
// When using them, be sure to consistently access the first index as ModLR (i.e. the rows)
// and the second as VK or SC (i.e. the columns):
#define Kvkm(i,j) kvkm[(i)*(MODLR_MAX + 1) + (j)]
#define Kscm(i,j) kscm[(i)*(MODLR_MAX + 1) + (j)]
#define KVKM_SIZE ((MODLR_MAX + 1)*(VK_ARRAY_COUNT))
#define KSCM_SIZE ((MODLR_MAX + 1)*(SC_ARRAY_COUNT))
// Notes about the following variables:
// Used to help make a workaround for the way the keyboard driver generates physical
// shift-key events to release the shift key whenever it is physically down during
// the press or release of a dual-state Numpad key. These keyboard driver generated
// shift-key events only seem to happen when Numlock is ON, the shift key is logically
// or physically down, and a dual-state numpad key is pressed or released (i.e. the shift
// key might not have been down for the press, but if it's down for the release, the driver
// will suddenly start generating shift events). I think the purpose of these events is to
// allow the shift keyto temporarily alter the state of the Numlock key for the purpose of
// sending that one key, without the shift key actually being "seen" as down while the key
// itself is sent (since some apps may have special behavior when they detect the shift key
// is down).
// Note: numlock, numpaddiv/mult/sub/add/enter are not affected by this because they have only
// a single state (i.e. they are unaffected by the state of the Numlock key). Also, these
// driver-generated events occur at a level lower than the hook, so it doesn't matter whether
// the hook suppresses the keys involved (i.e. the shift events still happen anyway).
// So which keys are not physical even though they're non-injected?:
// 1) The shift-up that precedes a down of a dual-state numpad key (only happens when shift key is logically down).
// 2) The shift-down that precedes a pressing down (or releasing in certain very rare cases caused by the
// exact sequence of keys received) of a key WHILE the numpad key in question is still down.
// Although this case may seem rare, it's happened to both Robert Yaklin and myself when doing various
// sorts of hotkeys.
// 3) The shift-up that precedes an up of a dual-state numpad key. This only happens if the shift key is
// logically down for any reason at this exact moment, which can be achieved via the send command.
// 4) The shift-down that follows the up of a dual-state numpad key (i.e. the driver is restoring the shift state
// to what it was before). This can be either immediate or "lazy". It's lazy whenever the user had pressed
// another key while a numpad key was being held down (i.e. case #2 above), in which case the driver waits
// indefinitely for the user to press any other key and then immediately sneaks in the shift key-down event
// right before it in the input stream (insertion).
// 5) Similar to #4, but if the driver needs to generate a shift-up for an unexpected Numpad-up event,
// the restoration of the shift key will be "lazy". This case was added in response to the below
// example, wherein the shift key got stuck physically down (incorrectly) by the hook:
// 68 048 d 0.00 Num 8
// 6B 04E d 0.09 Num +
// 68 048 i d 0.00 Num 8
// 68 048 i u 0.00 Num 8
// A0 02A i d 0.02 Shift part of the macro
// 01 000 i d 0.03 LButton
// A0 02A u 0.00 Shift driver, for the next key
// 26 048 u 0.00 Num 8
// A0 02A d 0.49 Shift driver lazy down (but not detected as non-physical)
// 6B 04E d 0.00 Num +
// The below timeout is for the subset of driver-generated shift-events that occur immediately
// before or after some other keyboard event. The elapsed time is usually zero, but using 22ms
// here just in case slower systems or systems under load have longer delays between keystrokes:
#define SHIFT_KEY_WORKAROUND_TIMEOUT 22
static bool sNextPhysShiftDownIsNotPhys; // All of these are initialized by ResetHook().
static vk_type sPriorVK;
static sc_type sPriorSC;
static bool sPriorEventWasKeyUp;
static bool sPriorEventWasPhysical;
static DWORD sPriorEventTickCount;
static modLR_type sPriorModifiersLR_physical;
static BYTE sPriorShiftState;
static BYTE sPriorLShiftState;
enum DualNumpadKeys {PAD_DECIMAL, PAD_NUMPAD0, PAD_NUMPAD1, PAD_NUMPAD2, PAD_NUMPAD3
, PAD_NUMPAD4, PAD_NUMPAD5, PAD_NUMPAD6, PAD_NUMPAD7, PAD_NUMPAD8, PAD_NUMPAD9
, PAD_DELETE, PAD_INSERT, PAD_END, PAD_DOWN, PAD_NEXT, PAD_LEFT, PAD_CLEAR
, PAD_RIGHT, PAD_HOME, PAD_UP, PAD_PRIOR, PAD_TOTAL_COUNT};
static bool sPadState[PAD_TOTAL_COUNT]; // Initialized by ChangeHookState()
/////////////////////////////////////////////////////////////////////////////////////////////
/*
One of the main objectives of a the hooks is to minimize the amount of CPU overhead caused by every
input event being handled by the procedure. One way this is done is to return immediately on
simple conditions that are relatively frequent (such as receiving a key that's not involved in any
hotkey combination).
Another way is to avoid API or system calls that might have a high overhead. That's why the state of
every prefix key is tracked independently, rather than calling the WinAPI to determine if the
key is actually down at the moment of consideration.
*/
inline bool IsIgnored(ULONG_PTR aExtraInfo)
// KEY_PHYS_IGNORE events must be mostly ignored because currently there is no way for a given
// hook instance to detect if it sent the event or some other instance. Therefore, to treat
// such events as true physical events might cause infinite loops or other side-effects in
// the instance that generated the event. More review of this is needed if KEY_PHYS_IGNORE
// events ever need to be treated as true physical events by the instances of the hook that
// didn't originate them:
{
return aExtraInfo == KEY_IGNORE || aExtraInfo == KEY_PHYS_IGNORE || aExtraInfo == KEY_IGNORE_ALL_EXCEPT_MODIFIER;
}
LRESULT CALLBACK LowLevelKeybdProc(int aCode, WPARAM wParam, LPARAM lParam)
{
/*
if (aCode != HC_ACTION) // MSDN docs specify that both LL keybd & mouse hook should return in this case.
return CallNextHookEx(g_KeybdHook, aCode, wParam, lParam);
KBDLLHOOKSTRUCT &event = *(PKBDLLHOOKSTRUCT)lParam; // For convenience, maintainability, and possibly performance.
// Change the event to be physical if that is indicated in its dwExtraInfo attribute.
// This is done for cases when the hook is installed multiple times and one instance of
// it wants to inform the others that this event should be considered physical for the
// purpose of updating modifier and key states:
if (event.dwExtraInfo == KEY_PHYS_IGNORE)
event.flags &= ~LLKHF_INJECTED;
// Make all keybd events physical to try to fool the system into accepting CTRL-ALT-DELETE.
// This didn't work, which implies that Ctrl-Alt-Delete is trapped at a lower level than
// this hook (folks have said that it's trapped in the keyboard driver itself):
//event.flags &= ~LLKHF_INJECTED;
// Note: Some scan codes are shared by more than one key (e.g. Numpad7 and NumpadHome). This is why
// the keyboard hook must be able to handle hotkeys by either their virtual key or their scan code.
// i.e. if sc were always used in preference to vk, we wouldn't be able to distinguish between such keys.
bool key_up = (wParam == WM_KEYUP || wParam == WM_SYSKEYUP);
vk_type vk = (vk_type)event.vkCode;
sc_type sc = (sc_type)event.scanCode;
if (vk && !sc) // Might happen if another app calls keybd_event with a zero scan code.
sc = vk_to_sc(vk);
// MapVirtualKey() does *not* include 0xE0 in HIBYTE if key is extended. In case it ever
// does in the future (or if event.scanCode ever does), force sc to be an 8-bit value
// so that it's guaranteed consistent and to ensure it won't exceed SC_MAX (which might cause
// array indexes to be out-of-bounds). The 9th bit is later set to 1 if the key is extended:
sc &= 0xFF;
// Change sc to be extended if indicated. But avoid doing so for VK_RSHIFT, which is
// apparently considered extended by the API when it shouldn't be. Update: Well, it looks like
// VK_RSHIFT really is an extended key, at least on WinXP (and probably be extension on the other
// NT based OSes as well). What little info I could find on the 'net about this is contradictory,
// but it's clear that some things just don't work right if the non-extended scan code is sent. For
// example, the shift key will appear to get stuck down in the foreground app if the non-extended
// scan code is sent with VK_RSHIFT key-up event:
if ((event.flags & LLKHF_EXTENDED)) // && vk != VK_RSHIFT)
sc |= 0x100;
// The below must be done prior to any returns that indirectly call UpdateKeybdState() to update
// modifier state.
// Update: It seems best to do the below unconditionally, even if the OS is Win2k or WinXP,
// since it seems like this translation will add value even in those cases:
// To help ensure consistency with Windows XP and 2k, for which this hook has been primarily
// designed and tested, translate neutral modifier keys into their left/right specific VKs,
// since beardboy's testing shows that NT4 receives the neutral keys like Win9x does:
switch (vk)
{
case VK_SHIFT: vk = (sc == SC_RSHIFT) ? VK_RSHIFT : VK_LSHIFT; break;
case VK_CONTROL: vk = (sc == SC_RCONTROL) ? VK_RCONTROL : VK_LCONTROL; break;
case VK_MENU: vk = (sc == SC_RALT) ? VK_RMENU : VK_LMENU; break;
}
// Now that the above has translated VK_CONTROL to VK_LCONTROL (if necessary):
if (vk == VK_LCONTROL)
{
// The following helps hasten AltGr detection after script startup. It's kept to supplement
// LayoutHasAltGr() because that function isn't 100% reliable for the reasons described there.
// It shouldn't be necessary to check what type of LControl event (up or down) is received, since
// it should be impossible for any unrelated keystrokes to be received while g_HookReceiptOfLControlMeansAltGr
// is true. This is because all unrelated keystrokes stay buffered until the main thread calls GetMessage().
// UPDATE for v1.0.39: Now that the hook has a dedicated thread, the above is no longer 100% certain.
// However, I think confidence is very high that this AltGr detection method is okay as it is because:
// 1) Hook thread has high priority, which means it generally shouldn't get backlogged with buffered keystrokes.
// 2) When the main thread calls keybd_event(), there is strong evidence that the OS immediately preempts
// the main thread (or executes a SendMessage(), which is the same as preemption) and gives the next
// timeslice to the hook thread, which then immediately processes the incoming keystroke as long
// as there are no keystrokes in the queue ahead of it (see #1).
// 3) Even if #1 and #2 fail to hold, the probability of misclassifying an LControl event seems very low.
// If there is ever concern, adding a call to IsIgnored() below would help weed out physical keystrokes
// (or those of other programs) that arrive during a vulnerable time.
if (g_HookReceiptOfLControlMeansAltGr)
{
// But don't reset g_HookReceiptOfLControlMeansAltGr here to avoid timing problems where the hook
// is installed at a time when g_HookReceiptOfLControlMeansAltGr is wrongly true because the
// inactive hook never made it false. Let KeyEvent() do that.
Get_active_window_keybd_layout // Defines the variable active_window_keybd_layout for use below.
LayoutHasAltGr(active_window_keybd_layout, CONDITION_TRUE);
// The following must be done; otherwise, if LayoutHasAltGr hasn't yet been autodetected by the
// time the first AltGr keystroke comes through, that keystroke would cause LControl to get stuck down
// as seen in g_modifiersLR_physical.
event.flags |= LLKHF_INJECTED; // Flag it as artificial for any other instances of the hook that may be running.
event.dwExtraInfo = g_HookReceiptOfLControlMeansAltGr; // The one who set this variable put the desired ExtraInfo in it.
}
else // Since AltGr wasn't detected above, see if any other means is ready to detect it.
{
// v1.0.42.04: This section was moved out of IsIgnored() to here because:
// 1) Immediately correcting the incoming event simplifies other parts of the hook.
// 2) It allows this instance of the hook to communicate with other instances of the hook by
// correcting the bogus values directly inside the event structure. This is something those
// other hooks can't do easily if the keystrokes were generated/simulated inside our instance
// (due to our instance's KeyEvent() function communicating corrections via
// g_HookReceiptOfLControlMeansAltGr and g_IgnoreNextLControlDown/Up).
//
// This new approach solves an AltGr keystroke's disruption of A_TimeIdlePhysical and related
// problems that were caused by AltGr's incoming keystroke being marked by the driver or OS as a
// physical event (even when the AltGr keystroke that caused it was artificial). It might not
// be a perfect solution, but it's pretty complete. For example, with the exception of artificial
// AltGr keystrokes from non-AHK sources, it completely solves the A_TimeIdlePhysical issue because
// by definition, any script that *uses* A_TimeIdlePhysical in a way that the fix applies to also
// has the keyboard hook installed (if it only has the mouse hook installed, the fix isn't in effect,
// but it also doesn't matter because that script detects only mouse events as true physical events,
// as described in the documentation for A_TimeIdlePhysical).
if (key_up)
{
if (g_IgnoreNextLControlUp)
{
event.flags |= LLKHF_INJECTED; // Flag it as artificial for any other instances of the hook that may be running.
event.dwExtraInfo = g_IgnoreNextLControlUp; // The one who set this variable put the desired ExtraInfo in here.
}
}
else // key-down event
{
if (g_IgnoreNextLControlDown)
{
event.flags |= LLKHF_INJECTED; // Flag it as artificial for any other instances of the hook that may be running.
event.dwExtraInfo = g_IgnoreNextLControlDown; // The one who set this variable put the desired ExtraInfo in here.
}
}
}
} // if (vk == VK_LCONTROL)
return LowLevelCommon(g_KeybdHook, aCode, wParam, lParam, vk, sc, key_up, event.dwExtraInfo, event.flags);
*/
}
LRESULT CALLBACK LowLevelMouseProc(int aCode, WPARAM wParam, LPARAM lParam)
{
/*
// code != HC_ACTION should be evaluated PRIOR to considering the values
// of wParam and lParam, because those values may be invalid or untrustworthy
// whenever code < 0.
if (aCode != HC_ACTION)
return CallNextHookEx(g_MouseHook, aCode, wParam, lParam);
MSLLHOOKSTRUCT &event = *(PMSLLHOOKSTRUCT)lParam; // For convenience, maintainability, and possibly performance.
// Make all mouse events physical to try to simulate mouse clicks in games that normally ignore
// artificial input.
//event.flags &= ~LLMHF_INJECTED;
if (!(event.flags & LLMHF_INJECTED)) // Physical mouse movement or button action (uses LLMHF vs. LLKHF).
g_TimeLastInputPhysical = GetTickCount();
// Above: Don't use event.time, mostly because SendInput can produce invalid timestamps on such events
// (though in truth, that concern isn't valid because SendInput's input isn't marked as physical).
// Another concern is the comments at the other update of "g_TimeLastInputPhysical" elsewhere in this file.
// A final concern is that some drivers might be faulty and might not generate an accurate timestamp.
if (wParam == WM_MOUSEMOVE) // Only after updating for physical input, above, is this checked.
return (g_BlockMouseMove && !(event.flags & LLMHF_INJECTED)) ? 1 : CallNextHookEx(g_MouseHook, aCode, wParam, lParam);
// Above: In v1.0.43.11, a new mode was added to block mouse movement only since it's more flexible than
// BlockInput (which keybd too, and blocks all mouse buttons too). However, this mode blocks only
// physical mouse movement because it seems most flexible (and simplest) to allow all artificial
// movement, even if that movement came from a source other than an AHK script (such as some other
// macro program).
// MSDN: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN, or WM_RBUTTONUP.
// But what about the middle button? It's undocumented, but it is received.
// What about doubleclicks (e.g. WM_LBUTTONDBLCLK): I checked: They are NOT received.
// This is expected because each click in a doubleclick could be separately suppressed by
// the hook, which would make it become a non-doubleclick.
vk_type vk = 0;
sc_type sc = 0; // To be overriden if this even is a wheel turn.
short wheel_delta;
bool key_up = true; // Set default to safest value.
switch (wParam)
{
case WM_MOUSEWHEEL:
// MSDN: "A positive value indicates that the wheel was rotated forward, away from the user;
// a negative value indicates that the wheel was rotated backward, toward the user. One wheel
// click is defined as WHEEL_DELTA, which is 120." Testing shows that on XP at least, the
// abs(delta) is greater than 120 when the user turns the wheel quickly (also depends on
// granularity of wheel hardware); i.e. the system combines multiple turns into a single event.
wheel_delta = GET_WHEEL_DELTA_WPARAM(event.mouseData); // Must typecast to short (not int) via macro, otherwise the conversion to negative/positive number won't be correct.
vk = wheel_delta < 0 ? VK_WHEEL_DOWN : VK_WHEEL_UP;
sc = (wheel_delta > 0 ? wheel_delta : -wheel_delta) / WHEEL_DELTA; // Friendless of conversion seems to outweigh lack of flexibility if future OSes change the 120 default.
key_up = false; // Always consider wheel movements to be "key down" events.
break;
case WM_LBUTTONUP: vk = VK_LBUTTON; break;
case WM_RBUTTONUP: vk = VK_RBUTTON; break;
case WM_MBUTTONUP: vk = VK_MBUTTON; break;
case WM_NCXBUTTONUP: // NC means non-client.
case WM_XBUTTONUP: vk = (HIWORD(event.mouseData) == XBUTTON1) ? VK_XBUTTON1 : VK_XBUTTON2; break;
case WM_LBUTTONDOWN: vk = VK_LBUTTON; key_up = false; break;
case WM_RBUTTONDOWN: vk = VK_RBUTTON; key_up = false; break;
case WM_MBUTTONDOWN: vk = VK_MBUTTON; key_up = false; break;
case WM_NCXBUTTONDOWN:
case WM_XBUTTONDOWN: vk = (HIWORD(event.mouseData) == XBUTTON1) ? VK_XBUTTON1 : VK_XBUTTON2; key_up = false; break;
}
return LowLevelCommon(g_MouseHook, aCode, wParam, lParam, vk, sc, key_up, event.dwExtraInfo, event.flags);
*/
}
LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lParam, const vk_type aVK
, sc_type aSC, bool aKeyUp, ULONG_PTR aExtraInfo, DWORD aEventFlags)
// v1.0.38.06: The keyboard and mouse hooks now call this common function to reduce code size and improve
// maintainability. The code size savings as of v1.0.38.06 is 3.5 KB of uncompressed code, but that
// savings will grow larger if more complexity is ever added to the hooks.
{
/*
HotkeyIDType hotkey_id_to_post = HOTKEY_ID_INVALID; // Set default.
bool is_ignored = IsIgnored(aExtraInfo);
// The following is done for more than just convenience. It solves problems that would otherwise arise
// due to the value of a global var such as KeyHistoryNext changing due to the reentrancy of
// this procedure. For example, a call to KeyEvent() in here would alter the value of
// KeyHistoryNext, in most cases before we had a chance to finish using the old value. In other
// words, we use an automatic variable so that every instance of this function will get its
// own copy of the variable whose value will stays constant until that instance returns:
KeyHistoryItem *pKeyHistoryCurr, khi_temp; // Must not be static (see above). Serves as a storage spot for a single keystroke in case key history is disabled.
if (!g_KeyHistory)
pKeyHistoryCurr = &khi_temp; // Having a non-NULL pKeyHistoryCurr simplifies the code in other places.
else
{
pKeyHistoryCurr = g_KeyHistory + g_KeyHistoryNext;
if (++g_KeyHistoryNext >= g_MaxHistoryKeys)
g_KeyHistoryNext = 0;
pKeyHistoryCurr->vk = aVK; // aSC is done later below.
pKeyHistoryCurr->key_up = aKeyUp;
g_HistoryTickNow = GetTickCount();
pKeyHistoryCurr->elapsed_time = (g_HistoryTickNow - g_HistoryTickPrev) / (float)1000;
g_HistoryTickPrev = g_HistoryTickNow;
HWND fore_win = GetForegroundWindow();
if (fore_win)
{
if (fore_win != g_HistoryHwndPrev)
{
// The following line is commented out in favor of the one beneath it (seem below comment):
//GetWindowText(fore_win, pKeyHistoryCurr->target_window, sizeof(pKeyHistoryCurr->target_window));
PostMessage(g_hWnd, AHK_GETWINDOWTEXT, (WPARAM)pKeyHistoryCurr->target_window, (LPARAM)fore_win);
// v1.0.44.12: The reason for the above is that clicking a window's close or minimize button
// (and possibly other types of title bar clicks) causes a delay for the following window, at least
// when XP Theme (but not classic theme) is in effect:
//#InstallMouseHook
//Gui, +AlwaysOnTop
//Gui, Show, w200 h100
//return
// The problem came about from the following sequence of events:
// 1) User clicks the one of the script's window's title bar's close, minimize, or maximize button.
// 2) WM_NCLBUTTONDOWN is sent to the window's window proc, which then passes it on to
// DefWindowProc or DefDlgProc, which then apparently enters a loop in which no messages
// (or a very limited subset) are pumped.
// 3) If anyone sends a message to that window (such as GetWindowText(), which sends a message
// in cases where it doesn't have the title pre-cached), the message will not receive a reply
// until after the mouse button is released.
// 4) But the hook is the very thing that's supposed to release the mouse button, and it can't
// until a reply is received.
// 5) Thus, a deadlock occurs. So after a short but noticeable delay, the OS sees the hook as
// unresponsive and bypasses it, sending the click through normally, which breaks the deadlock.
// 6) A similar situation might arise when a right-click-down is sent to the title bar or
// sys-menu-icon.
//
// SOLUTION:
// Post the message to our main thread to have it do the GetWindowText call. That way, if
// the target window is one of the main thread's own window's, there's no chance it can be
// in an unreponsive state like the deadlock described above. In addition, do this for ALL
// windows because its simpler, more maintainable, and especially might sovle other hook
// performance problems if GetWindowText() has other situations where it is slow to return
// (which seems likely).
// Although the above solution could create rare situations where there's a lag before window text
// is updated, that seems unlikely to be common or have signficant consequences. Furthermore,
// it has the advantage of improving hook performance by avoiding the call to GetWindowText (which
// incidentally might solve hotkey lag problems that have been observed while the active window
// is momentarily busy/unresponsive -- but maybe not because the main thread would then be lagged
// instead of the hook thread, which is effectively the same result from user's POV).
// Note: It seems best not to post the message to the hook thread because if LButton is down,
// the hook's main event loop would be sending a message to an unresponsive thread (our main thread),
// which would create the same deadlock.
// ALTERNATE SOLUTIONS:
// - #1: Avoid calling GetWindowText at all when LButton or RButton is in a logically-down state.
// - Same as #1, but do so only if one of the main thread's target windows is known to be in a tight loop (might be too unreliable to detect all such cases).
// - Same as #1 but less rigorous and more catch-all, such as by checking if the active window belongs to our thread.
// - Avoid calling GetWindowText at all upon release of LButton.
// - Same, but only if the window to have text retrieved belongs to our process.
// - Same, but only if the mouse is inside the close/minimize/etc. buttons of the active window.
}
else // i.e. where possible, avoid the overhead of the call to GetWindowText().
*pKeyHistoryCurr->target_window = '\0';
}
else
strcpy(pKeyHistoryCurr->target_window, "N/A"); // Due to AHK_GETWINDOWTEXT, this could collide with main thread's writing to same string; but in addition to being extremely rare, it would likely be inconsequential.
g_HistoryHwndPrev = fore_win; // Updated unconditionally in case fore_win is NULL.
}
// Keep the following flush with the above to indicate that they're related.
// The following is done even if key history is disabled because firing a wheel hotkey via PostMessage gets
// the notch count from pKeyHistoryCurr->sc.
pKeyHistoryCurr->sc = aSC; // Will be zero if our caller is the mouse hook (except for wheel notch count).
// After logging the wheel notch count (above), purify aSC for readability and maintainability.
if (aVK == VK_WHEEL_DOWN || aVK == VK_WHEEL_UP)
aSC = 0; // Also relied upon by by sc_takes_precedence below.
bool is_artificial;
if (aHook == g_MouseHook)
{
if ( !(is_artificial = (aEventFlags & LLMHF_INJECTED)) ) // It's a physical mouse event.
g_PhysicalKeyState[aVK] = aKeyUp ? 0 : STATE_DOWN;
}
else // Keybd hook.
{
// Even if the below is set to false, the event might be reclassified as artificial later (though it
// won't be logged as such). See comments in KeybdEventIsPhysical() for details.
is_artificial = aEventFlags & LLKHF_INJECTED; // LLKHF vs. LLMHF
// If the scan code is extended, the key that was pressed is not a dual-state numpad key,
// i.e. it could be the counterpart key, such as End vs. NumpadEnd, located elsewhere on
// the keyboard, but we're not interested in those. Also, Numlock must be ON because
// otherwise the driver will not generate those false-physical shift key events:
if (!(aSC & 0x100) && (IsKeyToggledOn(VK_NUMLOCK)))
{
switch (aVK)
{
case VK_DELETE: case VK_DECIMAL: sPadState[PAD_DECIMAL] = !aKeyUp; break;
case VK_INSERT: case VK_NUMPAD0: sPadState[PAD_NUMPAD0] = !aKeyUp; break;
case VK_END: case VK_NUMPAD1: sPadState[PAD_NUMPAD1] = !aKeyUp; break;
case VK_DOWN: case VK_NUMPAD2: sPadState[PAD_NUMPAD2] = !aKeyUp; break;
case VK_NEXT: case VK_NUMPAD3: sPadState[PAD_NUMPAD3] = !aKeyUp; break;
case VK_LEFT: case VK_NUMPAD4: sPadState[PAD_NUMPAD4] = !aKeyUp; break;
case VK_CLEAR: case VK_NUMPAD5: sPadState[PAD_NUMPAD5] = !aKeyUp; break;
case VK_RIGHT: case VK_NUMPAD6: sPadState[PAD_NUMPAD6] = !aKeyUp; break;
case VK_HOME: case VK_NUMPAD7: sPadState[PAD_NUMPAD7] = !aKeyUp; break;
case VK_UP: case VK_NUMPAD8: sPadState[PAD_NUMPAD8] = !aKeyUp; break;
case VK_PRIOR: case VK_NUMPAD9: sPadState[PAD_NUMPAD9] = !aKeyUp; break;
}
}
// Track physical state of keyboard & mouse buttons. Also, if it's a modifier, let another section
// handle it because it's not as simple as just setting the value to true or false (e.g. if LShift
// goes up, the state of VK_SHIFT should stay down if VK_RSHIFT is down, or up otherwise).
// Also, even if this input event will wind up being suppressed (usually because of being
// a hotkey), still update the physical state anyway, because we want the physical state to
// be entirely independent of the logical state (i.e. we want the key to be reported as
// physically down even if it isn't logically down):
if (!kvk[aVK].as_modifiersLR && KeybdEventIsPhysical(aEventFlags, aVK, aKeyUp))
g_PhysicalKeyState[aVK] = aKeyUp ? 0 : STATE_DOWN;
// Pointer to the key record for the current key event. Establishes this_key as an alias
// for the array element in kvk or ksc that corresponds to the vk or sc, respectively.
// I think the compiler can optimize the performance of reference variables better than
// pointers because the pointer indirection step is avoided. In any case, this must be
// a true alias to the object, not a copy of it, because it's address (&this_key) is compared
// to other addresses for equality further below.
}
// The following is done even if key history is disabled because sAltTabMenuIsVisible relies on it:
pKeyHistoryCurr->event_type = is_ignored ? 'i' : (is_artificial ? 'a' : ' '); // v1.0.42.04: 'a' was added, but 'i' takes precedence over 'a'.
// v1.0.43: Block the Win keys during journal playback to prevent keystrokes hitting the Start Menu
// if the user accidentally presses one of those keys during playback. Note: Keys other than Win
// don't need to be blocked because the playback hook defers them until after playback.
// Only block the down-events in case the user is physically holding down the key at the start
// of playback but releases it during the Send (avoids Win key becoming logically stuck down).
// This also serves to block Win shortcuts such as Win+R and Win+E during playback.
// Also, it seems best to block artificial LWIN keystrokes too, in case some other script or
// program tries to display the Start Menu during playback.
if (g_BlockWinKeys && (aVK == VK_LWIN || aVK == VK_RWIN) && !aKeyUp)
return SuppressThisKey;
// v1.0.37.07: Cancel the alt-tab menu upon receipt of Escape so that it behaves like the OS's native Alt-Tab.
// Even if is_ignored==true, it seems more flexible/useful to cancel the Alt-Tab menu upon receiving
// an Escape keystroke of any kind.
// Update: Must not handle Alt-Up here in a way similar to Esc-down in case the hook sent Alt-up to
// dismiss its own menu. Otherwise, the shift key might get stuck down if Shift-Alt-Tab was in effect.
// Instead, the release-of-prefix-key section should handle it via its checks of this_key.it_put_shift_down, etc.
if (sAltTabMenuIsVisible && aVK == VK_ESCAPE && !aKeyUp)
{
// When the alt-tab window is owned by the script (it is owned by csrss.exe unless the script
// is the process that invoked the alt-tab window), testing shows that the script must be the
// originator of the Escape keystroke. Therefore, substitute a simulated keystroke for the
// user's physical keystroke. It might be necessary to do this even if is_ignored==true because
// a keystroke from some other script/process might not qualify as a valid means to cancel it.
// UPDATE for v1.0.39: The escape handler below works only if the hook's thread invoked the
// alt-tab window, not if the script's thread did via something like "Send {Alt down}{tab down}".
// This is true even if the process ID is checked instead of the thread ID below. I think this
// behavior is due to the window obeying escape only when its own thread sends it. This
// is probably done to allow a program to automate the alt-tab menu without interference
// from Escape keystrokes typed by the user. Although this could probably be fixed by
// sending a message to the main thread and having it send the Escape keystroke, it seems
// best not to do this because:
// 1) The ability to dismiss a script-invoked alt-tab menu with escape would vary depending on
// whether the keyboard hook is installed (i.e. it's inconsistent).
// 2) It's more flexible to preserve the ability to protect the alt-tab menu from physical
// escape keystrokes typed by the user. The script can simulate an escape key to explicitly
// close an alt-tab window it invoked (a simulated escape keystroke can apparently also close
// any alt-tab menu, even one invoked by physical keystrokes; but the converse isn't true).
// 3) Lesser reason: Reduces code size and complexity.
HWND alt_tab_window;
if ((alt_tab_window = FindWindow("#32771", NULL)) // There is an alt-tab window...
&& GetWindowThreadProcessId(alt_tab_window, NULL) == GetCurrentThreadId()) // ...and it's owned by the hook thread (not the main thread).
{
KeyEvent(KEYDOWN, VK_ESCAPE);
// By definition, an Alt key should be logically down if the alt-tab menu is visible (even if it
// isn't, sending an extra up-event seems harmless). Releasing that Alt key seems best because:
// 1) If the prefix key that pushed down the alt key is still physically held down and the user
// presses a new (non-alt-tab) suffix key to form a hotkey, it avoids any alt-key disruption
// of things such as MouseClick that that subroutine might due.
// 2) If the user holds down the prefix, presses Escape to dismiss the menu, then presses an
// alt-tab suffix, testing shows that the existing alt-tab logic here in the hook will put
// alt or shift-alt back down if it needs to.
KeyEvent(KEYUP, (g_modifiersLR_logical & MOD_RALT) ? VK_RMENU : VK_LMENU);
return SuppressThisKey; // Testing shows that by contrast, the upcoming key-up on Escape doesn't require this logic.
}
// Otherwise, the alt-tab window doesn't exist or (more likely) it's owned by some other process
// such as crss.exe. Do nothing extra to avoid inteferring with the native function of Escape or
// any remappings or hotkeys assigned to Escape. Also, do not set sAltTabMenuIsVisible to false
// in any of the cases here because there is logic elsewhere in the hook that does that more
// reliably; it takes into account things such as whether the Escape keystroke will be suppressed
// due to being a hotkey).
}
bool sc_takes_precedence = ksc[aSC].sc_takes_precedence;
// Check hook type too in case a script every explicitly specifies scan code zero as a hotkey:
key_type &this_key = *((aHook == g_KeybdHook && sc_takes_precedence) ? (ksc + aSC) : (kvk + aVK));
// Do this after above since AllowKeyToGoToSystem requires that sc be properly determined.
// Another reason to do it after the above is due to the fact that KEY_PHYS_IGNORE permits
// an ignored key to be considered physical input, which is handled above:
if (is_ignored)
{
// This is a key sent by our own app that we want to ignore.
// It's important never to change this to call the SuppressKey function because
// that function would cause an infinite loop when the Numlock key is pressed,
// which would likely hang the entire system:
// UPDATE: This next part is for cases where more than one script is using the hook
// simultaneously. In such cases, it desirable for the KEYEVENT_PHYS() of one
// instance to affect the down-state of the current prefix-key in the other
// instances. This check is done here -- even though there may be a better way to
// implement it -- to minimize the chance of side-effects that a more fundamental
// change might cause (i.e. a more fundamental change would require a lot more
// testing, though it might also fix more things):
if (aExtraInfo == KEY_PHYS_IGNORE && aKeyUp && pPrefixKey == &this_key)
{
this_key.is_down = false;
this_key.down_performed_action = false; // Seems best, but only for PHYS_IGNORE.
pPrefixKey = NULL;
}
return AllowKeyToGoToSystem;
}
if (!aKeyUp) // Set defaults for this down event.
{
this_key.hotkey_down_was_suppressed = false;
this_key.hotkey_to_fire_upon_release = HOTKEY_ID_INVALID;
// Don't do the following because of the keyboard key-repeat feature. In other words,
// the NO_SUPPRESS_NEXT_UP_EVENT should stay pending even in the face of consecutive
// down-events. Even if it's possible for the flag to never be cleared due to never
// reaching any of the parts that clear it (which currently seems impossible), it seems
// inconsequential since by its very nature, this_key never consults the flag.
// this_key.no_suppress &= ~NO_SUPPRESS_NEXT_UP_EVENT;
}
if (aHook == g_KeybdHook)
{
// The below DISGUISE events are done only after ignored events are returned from, above.
// In other words, only non-ignored events (usually physical) are disguised. The Send {Blind}
// method is designed with this in mind, since it's more concerned that simulated keystrokes
// don't get disguised (i.e. it seems best to disguise physical keystrokes even during {Blind} mode).
// Do this only after the above because the SuppressThisKey macro relies
// on the vk variable being available. It also relies upon the fact that sc has
// already been properly determined. Also, in rare cases it may be necessary to disguise
// both left and right, which is why it's not done as a generic windows key:
if ( aKeyUp && ((sDisguiseNextLWinUp && aVK == VK_LWIN) || (sDisguiseNextRWinUp && aVK == VK_RWIN)
|| (sDisguiseNextLAltUp && aVK == VK_LMENU) || (sDisguiseNextRAltUp && aVK == VK_RMENU)) )
{
// Do this first to avoid problems with reentrancy triggered by the KeyEvent() calls further below.
switch (aVK)
{
case VK_LWIN: sDisguiseNextLWinUp = false; break;
case VK_RWIN: sDisguiseNextRWinUp = false; break;
// UPDATE: The comment below is no longer a concern since neutral keys are translated higher above
// into their left/right-specific counterpart:
// For now, assume VK_MENU the left alt key. This neutral key is probably never received anyway
// due to the nature of this type of hook on NT/2k/XP and beyond. Later, this can be further
// optimized to check the scan code and such (what's being done here isn't that essential to
// start with, so it's not a high priority -- but when it is done, be sure to review the
// above IF statement also).
case VK_LMENU: sDisguiseNextLAltUp = false; break;
case VK_RMENU: sDisguiseNextRAltUp = false; break;
}
// Send our own up-event to replace this one. But since ours has the Shift key
// held down for it, the Start Menu or foreground window's menu bar won't be invoked.
// It's necessary to send an up-event so that it's state, as seen by the system,
// is put back into the up position, which would be needed if its previous
// down-event wasn't suppressed (probably due to the fact that this win or alt
// key is a prefix but not a suffix).
// Fix for v1.0.25: Use CTRL vs. Shift to avoid triggering the LAlt+Shift language-change hotkey.
// This is definitely needed for ALT, but is done here for WIN also in case ALT is down,
// which might cause the use of SHIFT as the disguise key to trigger the language switch.
if (!(g_modifiersLR_logical & (MOD_LCONTROL | MOD_RCONTROL))) // Neither CTRL key is down.
KeyEvent(KEYDOWNANDUP, VK_CONTROL);
// Since the above call to KeyEvent() calls the keybd hook recursively, a quick down-and-up
// on Control is all that is necessary to disguise the key. This is because the OS will see
// that the Control keystroke occurred while ALT or WIN is still down because we haven't
// done CallNextHookEx() yet.
// Fix for v1.0.36.07: Don't return here because this release might also be a hotkey such as
// "~LWin Up::".
}
}
else // Mouse hook
{
// If no vk, there's no mapping for this key, so currently there's no way to process it.
if (!aVK)
return AllowKeyToGoToSystem;
// Also, if the script is displaying a menu (tray, main, or custom popup menu), always
// pass left-button events through -- even if LButton is defined as a hotkey -- so
// that menu items can be properly selected. This is necessary because if LButton is
// a hotkey, it can't launch now anyway due to the script being uninterruptible while
// a menu is visible. And since it can't launch, it can't do its typical "MouseClick
// left" to send a true mouse-click through as a replacement for the suppressed
// button-down and button-up events caused by the hotkey. Also, for simplicity this
// is done regardless of which modifier keys the user is holding down since the desire
// to fire mouse hotkeys while a context or popup menu is displayed seems too rare.
//
// Update for 1.0.37.05: The below has been extended to look for menus beyond those
// supported by g_MenuIsVisible, namely the context menus of a MonthCal or Edit control
// (even the script's main window's edit control's context menu). It has also been
// extended to include RButton because:
// 1) Right and left buttons may have been swapped via control panel to take on each others' functions.
// 2) Right-click is a valid way to select a context menu items (but apparently not popup or menu bar items).
// 3) Right-click should invoke another instance of the context menu (or dismiss existing menu, depending
// on where the click occurs) if user clicks outside of our thread's existing context menu.
HWND menu_hwnd;
if ( (aVK == VK_LBUTTON || aVK == VK_RBUTTON) && (g_MenuIsVisible // Ordered for short-circuit performance.
|| ((menu_hwnd = FindWindow("#32768", NULL))
&& GetWindowThreadProcessId(menu_hwnd, NULL) == g_MainThreadID)) ) // Don't call GetCurrentThreadId() because our thread is different than main's.
{
// Bug-fix for v1.0.22: If "LControl & LButton::" (and perhaps similar combinations)
// is a hotkey, the foreground window would think that the mouse is stuck down, at least
// if the user clicked outside the menu to dismiss it. Specifically, this comes about
// as follows:
// The wrong up-event is suppressed:
// ...because down_performed_action was true when it should have been false
// ...because the while-menu-was-displayed up-event never set it to false
// ...because it returned too early here before it could get to that part further below.
this_key.down_performed_action = false; // Seems ok in this case to do this for both aKeyUp and !aKeyUp.
this_key.is_down = !aKeyUp;
return AllowKeyToGoToSystem;
}
} // Mouse hook.
// Any key-down event (other than those already ignored and returned from,
// above) should probably be considered an attempt by the user to use the
// prefix key that's currently being held down as a "modifier". That way,
// if pPrefixKey happens to also be a suffix, its suffix action won't fire
// when the key is released, which is probably the correct thing to do 90%
// or more of the time. But don't consider the modifiers themselves to have
// been modified by a prefix key, since that is almost never desirable:
if (pPrefixKey && pPrefixKey != &this_key && !aKeyUp) // There is a prefix key being held down and the user has now pressed some other key.
if ( (aHook == g_KeybdHook) ? !this_key.as_modifiersLR : pPrefixKey->as_modifiersLR )
pPrefixKey->was_just_used = AS_PREFIX; // Indicate that currently-down prefix key has been "used".
// Formerly, the above was done only for keyboard hook, not the mouse. This was because
// most people probably would not want a prefix key's suffix-action to be stopped
// from firing just because a non-hotkey mouse button was pressed while the key
// was held down (i.e. for games). But now a small exception to this has been made:
// Prefix keys that are also modifiers (ALT/SHIFT/CTRL/WIN) will now not fire their
// suffix action on key-up if they modified a mouse button event (since Ctrl-LeftClick,
// for example, is a valid native action and we don't want to give up that flexibility).
// WinAPI docs state that for both virtual keys and scan codes:
// "If there is no translation, the return value is zero."
// Therefore, zero is never a key that can be validly configured (and likely it's never received here anyway).
// UPDATE: For performance reasons, this check isn't even done. Even if sc and vk are both zero, both kvk[0]
// and ksc[0] should have all their attributes initialized to FALSE so nothing should happen for that key
// anyway.
//if (!vk && !sc)
// return AllowKeyToGoToSystem;
if (!this_key.used_as_prefix && !this_key.used_as_suffix)
return AllowKeyToGoToSystem;
bool is_explicit_key_up_hotkey = false; // Set default.
HotkeyIDType hotkey_id_with_flags = HOTKEY_ID_INVALID; //
bool firing_is_certain = false; //
HotkeyIDType hotkey_id_temp; // For informal/temp storage of the ID-without-flags.
bool down_performed_action, was_down_before_up;
if (aKeyUp)
{
// Save prior to reset. These var's should only be used further below in conjunction with aKeyUp
// being TRUE. Otherwise, their values will be unreliable (refer to some other key, probably).
was_down_before_up = this_key.is_down;
down_performed_action = this_key.down_performed_action; // Save prior to reset below.
// Reset these values in preparation for the next call to this procedure that involves this key:
this_key.down_performed_action = false;
if (this_key.hotkey_to_fire_upon_release != HOTKEY_ID_INVALID)
{
hotkey_id_with_flags = this_key.hotkey_to_fire_upon_release;
is_explicit_key_up_hotkey = true; // Can't rely on (hotkey_id_with_flags & HOTKEY_KEY_UP) because some key-up hotkeys (such as the hotkey_up array) might not be flagged that way.
// The line below is done even though the down-event also resets it in case it is ever
// possible for keys to generate mulitple consecutive key-up events (faulty or unusual keyboards?)
this_key.hotkey_to_fire_upon_release = HOTKEY_ID_INVALID;
}
}
this_key.is_down = !aKeyUp;
bool modifiers_were_corrected = false;
if (aHook == g_KeybdHook)
{
// The below was added to fix hotkeys that have a neutral suffix such as "Control & LShift".
// It may also fix other things and help future enhancements:
if (this_key.as_modifiersLR)
{
// The neutral modifier "Win" is not currently supported.
kvk[VK_CONTROL].is_down = kvk[VK_LCONTROL].is_down || kvk[VK_RCONTROL].is_down;
kvk[VK_MENU].is_down = kvk[VK_LMENU].is_down || kvk[VK_RMENU].is_down;
kvk[VK_SHIFT].is_down = kvk[VK_LSHIFT].is_down || kvk[VK_RSHIFT].is_down;
// No longer possible because vk is translated early on from neutral to left-right specific:
// I don't think these ever happen with physical keyboard input, but it might with artificial input:
//case VK_CONTROL: kvk[sc == SC_RCONTROL ? VK_RCONTROL : VK_LCONTROL].is_down = !aKeyUp; break;
//case VK_MENU: kvk[sc == SC_RALT ? VK_RMENU : VK_LMENU].is_down = !aKeyUp; break;
//case VK_SHIFT: kvk[sc == SC_RSHIFT ? VK_RSHIFT : VK_LSHIFT].is_down = !aKeyUp; break;
}
}
else // Mouse hook
{
// If the mouse hook is installed without the keyboard hook, update g_modifiersLR_logical
// manually so that it can be referred to by the mouse hook after this point:
if (!g_KeybdHook)
{
g_modifiersLR_logical = g_modifiersLR_logical_non_ignored = GetModifierLRState(true);
modifiers_were_corrected = true;
}
}
modLR_type modifiersLRnew;
bool this_toggle_key_can_be_toggled = this_key.pForceToggle && *this_key.pForceToggle == NEUTRAL; // Relies on short-circuit boolean order.
///////////////////////////////////////////////////////////////////////////////////////
// CASE #1 of 4: PREFIX key has been pressed down. But use it in this capacity only if
// no other prefix is already in effect or if this key isn't a suffix. Update: Or if
// this key-down is the same as the prefix already down, since we want to be able to
// a prefix when it's being used in its role as a modified suffix (see below comments).
///////////////////////////////////////////////////////////////////////////////////////
if (this_key.used_as_prefix && !aKeyUp && (!pPrefixKey || !this_key.used_as_suffix || &this_key == pPrefixKey))
{
// v1.0.41: Even if this prefix key is non-suppressed (passes through to active window),
// still call PrefixHasNoEnabledSuffixes() because don't want to overwrite the old value of
// pPrefixKey (see comments in "else" later below).
// v1.0.44: Added check for PREFIX_ACTUAL so that a PREFIX_FORCED prefix will be considered
// a prefix even if it has no suffixes. This fixes an unintentional change in v1.0.41 where
// naked, neutral modifier hotkeys Control::, Alt::, and Shift:: started firing on press-down
// rather than release as intended. The PREFIX_FORCED facility may also provide the means to
// introduce a new hotkey modifier such as an "up2" keyword that makes any key into a prefix
// key even if it never acts as a prefix for other keys, which in turn has the benefit of firing
// on key-up, but only if the no other key was pressed while the user was holding it down.
bool has_no_enabled_suffixes;
if ( !(has_no_enabled_suffixes = (this_key.used_as_prefix == PREFIX_ACTUAL)
&& Hotkey::PrefixHasNoEnabledSuffixes(sc_takes_precedence ? aSC : aVK, sc_takes_precedence)) )
{
// This check is necessary in cases such as the following, in which the "A" key continues
// to repeat becauses pressing a mouse button (unlike pressing a keyboard key) does not
// stop the prefix key from repeating:
// $a::send, a
// a & lbutton::
if (&this_key != pPrefixKey)
{
// Override any other prefix key that might be in effect with this one, in case the
// prior one, due to be old for example, was invalid somehow. UPDATE: It seems better
// to leave the old one in effect to support the case where one prefix key is modifying
// a second one in its role as a suffix. In other words, if key1 is a prefix and
// key2 is both a prefix and a suffix, we want to leave key1 in effect as a prefix,
// rather than key2. Hence, a null-check was added in the above if-stmt:
pPrefixKey = &this_key;
// It should be safe to init this because even if the current key is repeating,
// it should be impossible to receive here the key-downs that occurred after
// the first, because there's a return-on-repeat check farther above (update: that check
// is gone now). Even if that check weren't done, it's safe to reinitialize this to zero
// because on most (all?) keyboards & OSs, the moment the user presses another key while
// this one is held down, the key-repeating ceases and does not resume for
// this key (though the second key will begin to repeat if it too is held down).
// In other words, the fear that this would be wrongly initialized and thus cause
// this prefix's suffix-action to fire upon key-release seems unfounded.
// It seems easier (and may perform better than alternative ways) to init this
// here rather than say, upon the release of the prefix key:
this_key.was_just_used = 0; // Init to indicate it hasn't yet been used in its role as a prefix.
}
}
//else this prefix has no enabled suffixes, so its role as prefix is also disabled.
// Therefore, don't set pPrefixKey to this_key because don't want the following line
// (in another section) to execute when a suffix comes in (there may be other reasons too,
// such as not wanting to lose track of the previous prefix key in cases where the user is
// holding down more than one prefix):
// pPrefixKey->was_just_used = AS_PREFIX
if (this_key.used_as_suffix) // v1.0.41: Added this check to avoid doing all of the below when unnecessary.
{
// This new section was added May 30, 2004, to fix scenarios such as the following example:
// a & b::Msgbox a & b
// $^a::MsgBox a
// Previously, the ^a hotkey would only fire on key-up (unless it was registered, in which
// case it worked as intended on the down-event). When the user presses A, it's okay (and
// probably desirable) to have recorded that event as a prefix-key-down event (above).
// But in addition to that, we now check if this is a normal, modified hotkey that should
// fire now rather than waiting for the key-up event. This is done because it makes sense,
// it's more correct, and also it makes the behavior of a hooked ^a hotkey consistent with
// that of a registered ^a.
// Prior to considering whether to fire a hotkey, correct the hook's modifier state.
// Although this is rarely needed, there are times when the OS disables the hook, thus
// it is possible for it to miss keystrokes. See comments in GetModifierLRState()
// for more info:
if (!modifiers_were_corrected)
{
modifiers_were_corrected = true;
GetModifierLRState(true);
}
// non_ignored is always used when considering whether a key combination is in place to
// trigger a hotkey:
modifiersLRnew = g_modifiersLR_logical_non_ignored;
if (this_key.as_modifiersLR) // This will always be false if our caller is the mouse hook.
// Hotkeys are not defined to modify themselves, so look for a match accordingly.
modifiersLRnew &= ~this_key.as_modifiersLR;
// For this case to be checked, there must be at least one modifier key currently down (other
// than this key itself if it's a modifier), because if there isn't and this prefix is also
// a suffix, its suffix action should only fire on key-up (i.e. not here, but later on).
// UPDATE: In v1.0.41, an exception to the above is when a prefix is disabled via
// has_no_enabled_suffixes, in which case it seems desirable for most uses to have its
// suffix action fire on key-down rather than key-up.
if (modifiersLRnew || has_no_enabled_suffixes)
{
// Check hook type too in case a script every explicitly specifies scan code zero as a hotkey:
hotkey_id_with_flags = (aHook == g_KeybdHook && sc_takes_precedence)
? Kscm(modifiersLRnew, aSC) : Kvkm(modifiersLRnew, aVK);
if (hotkey_id_with_flags & HOTKEY_KEY_UP) // And it's okay even if it's is HOTKEY_ID_INVALID.
{
// Queue it for later, which is done here rather than upon release of the key so that
// the user can release the key's modifiers before releasing the key itself, which
// is likely to happen pretty often. v1.0.41: This is done even if the hotkey is subject
// to #IfWin because it seems more correct to check those criteria at the actual time
// the key is released rather than now:
this_key.hotkey_to_fire_upon_release = hotkey_id_with_flags;
hotkey_id_with_flags = HOTKEY_ID_INVALID;
}
else // hotkey_id_with_flags is either HOTKEY_ID_INVALID or a valid key-down hotkey.
{
hotkey_id_temp = hotkey_id_with_flags & HOTKEY_ID_MASK;
if (hotkey_id_temp < Hotkey::sHotkeyCount)
this_key.hotkey_to_fire_upon_release = hotkey_up[hotkey_id_temp]; // Might assign HOTKEY_ID_INVALID.
// Since this prefix key is being used in its capacity as a suffix instead,
// hotkey_id_with_flags now contains a hotkey ready for firing later below.
// v1.0.41: Above is done even if the hotkey is subject to #IfWin because:
// 1) The down-hotkey's #IfWin criteria might be different from that of the up's.
// 2) It seems more correct to check those criteria at the actual time the key is
// released rather than now (and also probably reduces code size).
}
}
// Alt-tab need not be checked here (like it is in the similar section below) because all
// such hotkeys use (or were converted at load-time to use) a modifier_vk, not a set of
// modifiers or modifierlr's.
} // if (this_key.used_as_suffix)
if (hotkey_id_with_flags == HOTKEY_ID_INVALID)
{
if (has_no_enabled_suffixes)
{
this_key.no_suppress |= NO_SUPPRESS_NEXT_UP_EVENT; // Since the "down" is non-suppressed, so should the "up".
pKeyHistoryCurr->event_type = '#'; // '#' to indicate this prefix key is disabled due to #IfWin criterion.
}
// In this case, a key-down event can't trigger a suffix, so return immediately.
// If our caller is the mouse hook, both of the following will always be false:
// this_key.as_modifiersLR
// this_toggle_key_can_be_toggled
return (this_key.as_modifiersLR || (this_key.no_suppress & NO_SUPPRESS_PREFIX)
|| this_toggle_key_can_be_toggled || has_no_enabled_suffixes)
? AllowKeyToGoToSystem : SuppressThisKey;
}
//else valid suffix hotkey has been found; this will now fall through to Case #4 by virtue of aKeyUp==false.
}
//////////////////////////////////////////////////////////////////////////////////
// CASE #2 of 4: SUFFIX key (that's not a prefix, or is one but has just been used
// in its capacity as a suffix instead) has been released.
// This is done before Case #3 for performance reasons.
//////////////////////////////////////////////////////////////////////////////////
// v1.0.37.05: Added "|| down_performed_action" to the final check below because otherwise a
// script such as the following would send two M's for +b, one upon down and one upon up:
// +b::Send, M
// b & z::return
// I don't remember exactly what the "pPrefixKey != &this_key" check is for below, but it is kept
// to minimize the chance of breaking other things:
bool fell_through_from_case2 = false; // Set default.
if (this_key.used_as_suffix && aKeyUp && (pPrefixKey != &this_key || down_performed_action)) // Note: hotkey_id_with_flags might be already valid due to this_key.hotkey_to_fire_upon_release.
{
if (pPrefixKey == &this_key) // v1.0.37.05: Added so that scripts such as the example above don't leave pPrefixKey wrongly non-NULL.
pPrefixKey = NULL; // Also, it seems unnecessary to check this_key.it_put_alt_down and such like is done in Case #3.
// If it did perform an action, suppress this key-up event. Do this even
// if this key is a modifier because it's previous key-down would have
// already been suppressed (since this case is for suffixes that aren't
// also prefixes), thus the key-up can be safely suppressed as well.
// It's especially important to do this for keys whose up-events are
// special actions within the OS, such as AppsKey, Lwin, and Rwin.
// Toggleable keys are also suppressed here on key-up because their
// previous key-down event would have been suppressed in order for
// down_performed_action to be true. UPDATE: Added handling for
// NO_SUPPRESS_NEXT_UP_EVENT and also applied this next part to both
// mouse and keyboard.
// v1.0.40.01: It was observed that a hotkey that consists of a mouse button as a prefix and
// a keyboard key as a suffix can cause sticking keys in rare cases. For example, when
// "MButton & LShift" is a hotkey, if you hold down LShift long enough for it to begin
// auto-repeating then press MButton, the hotkey fires the next time LShift auto-repeats (since
// pressing a mouse button doesn't stop a keyboard key from auto-repeating). Fixing that type
// of firing seems likely to break more things than it fixes. But since it isn't fixed, when
// the user releases LShift, the up-event is suppressed here, which causes the key to get
// stuck down. That could be fixed in the following ways, but all of them seem likely to break
// more things than they fix, especially given the rarity that both a hotkey of this type would
// exist and its mirror image does something useful that isn't a hotkey (for example, Shift+MButton
// is a meaningful action in few if any applications):
// 1) Don't suppress the physical release of a suffix key if that key is logically down (as reported
// by GetKeyState/GetAsyncKeyState): Seems too broad in scope because there might be cases where
// the script or user wants the key to stay logically down (e.g. Send {Shift down}{a down}).
// 2) Same as #1 but limit the non-suppression to only times when the suffix key was logically down
// when its first qualified physical down-event came in. This is definitely better but like
// #1, the uncertainty of breaking existing scripts and/or causing more harm than good seems too
// high.
// 3) Same as #2 but limit it only to cases where the suffix key is a keyboard key and its prefix
// is a mouse key. Although very selective, it doesn't mitigate the fact it might still do more
// harm than good and/or break existing scripts.
// In light of the above, it seems best to keep this documented here as a known limitation for now.
//
// v1.0.28: The following check is done to support certain keyboards whose keys or scroll wheels
// generate up events without first having generated any down-event for the key. UPDATE: I think
// this check is now also needed to allow fall-through in cases like "b" and "b up" both existing.
if (!this_key.used_as_key_up)
{
bool suppress_up_event;
if (this_key.no_suppress & NO_SUPPRESS_NEXT_UP_EVENT)
{
suppress_up_event = false;
this_key.no_suppress &= ~NO_SUPPRESS_NEXT_UP_EVENT; // This ticket has been used up.
}
else // the default is to suppress the up-event.
suppress_up_event = true;