forked from HotKeyIt/ahkdll
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkeyboard_mouse.cpp
4779 lines (4379 loc) · 258 KB
/
keyboard_mouse.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-2009 Chris Mallett ([email protected])
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 "keyboard_mouse.h"
#include "globaldata.h" // for g->KeyDelay
#include "application.h" // for MsgSleep()
#include "util.h" // for strlicmp()
#include "window.h" // for IsWindowHung()
// Added for v1.0.25. Search on sPrevEventType for more comments:
static KeyEventTypes sPrevEventType;
static vk_type sPrevVK = 0;
// For v1.0.25, the below is static to track it in between sends, so that the below will continue
// to work:
// Send {LWinDown}
// Send {LWinUp} ; Should still open the Start Menu even though it's a separate Send.
static vk_type sPrevEventModifierDown = 0;
static modLR_type sModifiersLR_persistent = 0; // Tracks this script's own lifetime/persistent modifiers (the ones it caused to be persistent and thus is responsible for tracking).
static modLR_type sModifiersLR_remapped = 0;
// v1.0.44.03: Below supports multiple keyboard layouts better by having script adapt to active window's layout.
#define MAX_CACHED_LAYOUTS 10 // Hard to imagine anyone using more languages/layouts than this, but even if they do it will still work; performance would just be a little worse due to being uncached.
static CachedLayoutType sCachedLayout[MAX_CACHED_LAYOUTS] = {{0}};
static HKL sTargetKeybdLayout; // Set by SendKeys() for use by the functions it calls directly and indirectly.
static ResultType sTargetLayoutHasAltGr; //
// v1.0.43: Support for SendInput() and journal-playback hook:
#define MAX_INITIAL_EVENTS_SI 500UL // sizeof(INPUT) == 28 as of 2006. Since Send is called so often, and since most Sends are short, reducing the load on the stack is also a deciding factor for these.
#define MAX_INITIAL_EVENTS_PB 1500UL // sizeof(PlaybackEvent) == 8, so more events are justified before resorting to malloc().
static LPINPUT sEventSI; // No init necessary. An array that's allocated/deallocated by SendKeys().
static PlaybackEvent *&sEventPB = (PlaybackEvent *&)sEventSI;
static UINT sEventCount, sMaxEvents; // Number of items in the above arrays and the current array capacity.
static UINT sCurrentEvent;
static modLR_type sEventModifiersLR; // Tracks the modifier state to following the progress/building of the SendInput array.
static POINT sSendInputCursorPos; // Tracks/predicts cursor position as SendInput array is built.
#ifndef MINIDLL
static HookType sHooksToRemoveDuringSendInput;
#endif
static SendModes sSendMode = SM_EVENT; // Whether a SendInput or Hook array is currently being constructed.
static bool sAbortArraySend; // No init needed.
static bool sFirstCallForThisEvent; //
static bool sInBlindMode; //
static DWORD sThisEventTime; //
void DisguiseWinAltIfNeeded(vk_type aVK)
// For v1.0.25, the following situation is fixed by the code below: If LWin or LAlt
// becomes a persistent modifier (e.g. via Send {LWin down}) and the user physically
// releases LWin immediately before: 1) the {LWin up} is scheduled; and 2) SendKey()
// returns. Then SendKey() will push the modifier back down so that it is in effect
// for other things done by its caller (SendKeys) and also so that if the Send
// operation ends, the key will still be down as the user intended (to modify future
// keystrokes, physical or simulated). However, since that down-event is followed
// immediately by an up-event, the Start Menu appears for WIN-key or the active
// window's menu bar is activated for ALT-key. SOLUTION: Disguise Win-up and Alt-up
// events in these cases. This workaround has been successfully tested. It's also
// limited is scope so that a script can still explicitly invoke the Start Menu with
// "Send {LWin}", or activate the menu bar with "Send {Alt}".
// The check of sPrevEventModifierDown allows "Send {LWinDown}{LWinUp}" etc., to
// continue to work.
// v1.0.40: For maximum flexibility and minimum interference while in blind mode,
// don't disguise Win and Alt keystrokes then.
{
// Caller has ensured that aVK is about to have a key-up event, so if the event immediately
// prior to this one is a key-down of the same type of modifier key, it's our job here
// to send the disguising keystrokes now (if appropriate).
if (sPrevEventType == KEYDOWN && sPrevEventModifierDown != aVK && !sInBlindMode
// SendPlay mode can't display Start Menu, so no need for disguise keystrokes (such keystrokes might cause
// unwanted effects in certain games):
&& ((aVK == VK_LWIN || aVK == VK_RWIN) && (sPrevVK == VK_LWIN || sPrevVK == VK_RWIN) && sSendMode != SM_PLAY
|| (aVK == VK_LMENU || (aVK == VK_RMENU && sTargetLayoutHasAltGr != CONDITION_TRUE)) && (sPrevVK == VK_LMENU || sPrevVK == VK_RMENU)))
KeyEventMenuMask(KEYDOWNANDUP); // Disguise it to suppress Start Menu or prevent activation of active window's menu bar.
}
// moved from SendKeys
void SendUnicodeChar(wchar_t aChar, modLR_type aModifiers)
{
// Set modifier keystate as specified by caller. Generally this will be 0, since
// key combinations with Unicode packets either do nothing at all or do the same as
// without the modifiers. All modifiers are known to interfere in some applications.
SetModifierLRState(aModifiers, sSendMode ? sEventModifiersLR : GetModifierLRState(), NULL, false, true, KEY_IGNORE);
if (sSendMode == SM_INPUT)
{
// Calling SendInput() now would cause characters to appear out of sequence.
// Instead, put them into the array and allow them to be sent in sequence.
PutKeybdEventIntoArray(0, 0, aChar, KEYEVENTF_UNICODE, KEY_IGNORE_LEVEL(g->SendLevel));
PutKeybdEventIntoArray(0, 0, aChar, KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, KEY_IGNORE_LEVEL(g->SendLevel));
return;
}
//else caller has ensured sSendMode is SM_EVENT. In that mode, events are sent one at a time,
// so it is safe to immediately call SendInput(). SM_PLAY is not supported; for simplicity,
// SendASC() is called instead of this function. Although this means Unicode chars probably
// won't work, it seems better than sending chars out of order. One possible alternative could
// be to "flush" the event array, but since SendInput and SendEvent are probably much more common,
// this is left for a future version.
INPUT u_input[2];
u_input[0].type = INPUT_KEYBOARD;
u_input[0].ki.wVk = 0;
u_input[0].ki.wScan = aChar;
u_input[0].ki.dwFlags = KEYEVENTF_UNICODE;
u_input[0].ki.time = 0;
// L25: Set dwExtraInfo to ensure AutoHotkey ignores the event; otherwise it may trigger a SCxxx hotkey (where xxx is u_code).
u_input[0].ki.dwExtraInfo = KEY_IGNORE_LEVEL(g->SendLevel);
u_input[1].type = INPUT_KEYBOARD;
u_input[1].ki.wVk = 0;
u_input[1].ki.wScan = aChar;
u_input[1].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
u_input[1].ki.time = 0;
u_input[1].ki.dwExtraInfo = KEY_IGNORE_LEVEL(g->SendLevel);
SendInput(2, u_input, sizeof(INPUT));
}
void SendKeys(LPTSTR aKeys, SendRawModes aSendRaw, SendModes aSendModeOrig, HWND aTargetWindow)
// The aKeys string must be modifiable (not constant), since for performance reasons,
// it's allowed to be temporarily altered by this function. mThisHotkeyModifiersLR, if non-zero,
// should be the set of modifiers used to trigger the hotkey that called the subroutine
// containing the Send that got us here. If any of those modifiers are still down,
// they will be released prior to sending the batch of keys specified in <aKeys>.
// v1.0.43: aSendModeOrig was added.
{
if (!*aKeys)
return;
global_struct &g = *::g; // Reduces code size and may improve performance.
DWORD orig_last_script_rest = g_script.mLastScriptRest;
#ifdef _WIN64
DWORD aThreadID = __readgsdword(0x48); // Used to identify if code is called from different thread (AutoHotkey.dll)
#else
DWORD aThreadID = __readfsdword(0x24);
#endif
// For performance and also to reserve future flexibility, recognize {Blind} only when it's the first item
// in the string.
if (sInBlindMode = !aSendRaw && !_tcsnicmp(aKeys, _T("{Blind}"), 7)) // Don't allow {Blind} while in raw mode due to slight chance {Blind} is intended to be sent as a literal string.
// Blind Mode (since this seems too obscure to document, it's mentioned here): Blind Mode relies
// on modifiers already down for something like ^c because ^c is saying "manifest a ^c", which will
// happen if ctrl is already down. By contrast, Blind does not release shift to produce lowercase
// letters because avoiding that adds flexibility that couldn't be achieved otherwise.
// Thus, ^c::Send {Blind}c produces the same result when ^c is substituted for the final c.
// But Send {Blind}{LControl down} will generate the extra events even if ctrl already down.
aKeys += 7; // Remove "{Blind}" from further consideration.
if (!aSendRaw && !_tcsnicmp(aKeys, _T("{Text}"), 6))
{
// Setting this early allows CapsLock and the Win+L workaround to be skipped:
aSendRaw = SCM_RAW_TEXT;
aKeys += 6;
}
int orig_key_delay = g.KeyDelay;
int orig_press_duration = g.PressDuration;
if (aSendModeOrig == SM_INPUT || aSendModeOrig == SM_INPUT_FALLBACK_TO_PLAY) // Caller has ensured aTargetWindow==NULL for SendInput and SendPlay modes.
{
// Both of these modes fall back to a different mode depending on whether some other script
// is running with a keyboard/mouse hook active. Of course, the detection of this isn't foolproof
// because older versions of AHK may be running and/or other apps with LL keyboard hooks. It's
// just designed to add a lot of value for typical usage because SendInput is preferred due to it
// being considerably faster than SendPlay, especially for long replacements when the CPU is under
// heavy load.
if ( SystemHasAnotherKeybdHook() // This function has been benchmarked to ensure it doesn't yield our timeslice, etc. 200 calls take 0ms according to tick-count, even when CPU is maxed.
|| !aSendRaw && SystemHasAnotherMouseHook() && tcscasestr(aKeys, _T("{Click")) ) // Ordered for short-circuit boolean performance. v1.0.43.09: Fixed to be strcasestr vs. !strcasestr
{
// Need to detect in advance what type of array to build (for performance and code size). That's why
// it done this way, and here are the comments about it:
// strcasestr() above has an unwanted amount of overhead if aKeys is huge, but it seems acceptable
// because it's called only when system has another mouse hook but *not* another keybd hook (very rare).
// Also, for performance reasons, {LButton and such are not checked for, which is documented and seems
// justified because the new {Click} method is expected to become prevalent, especially since this
// whole section only applies when the new SendInput mode is in effect.
// Finally, checking aSendRaw isn't foolproof because the string might contain {Raw} prior to {Click,
// but the complexity and performance of checking for that seems unjustified given the rarity,
// especially since there are almost never any consequences to reverting to hook mode vs. SendInput.
if (aSendModeOrig == SM_INPUT_FALLBACK_TO_PLAY)
aSendModeOrig = SM_PLAY;
else // aSendModeOrig == SM_INPUT, so fall back to EVENT.
{
aSendModeOrig = SM_EVENT;
// v1.0.43.08: When SendInput reverts to SendEvent mode, the majority of users would want
// a fast sending rate that is more comparable to SendInput's speed that the default KeyDelay
// of 10ms. PressDuration may be generally superior to KeyDelay because it does a delay after
// each changing of modifier state (which tends to improve reliability for certain apps).
// The following rules seem likely to be the best benefit in terms of speed and reliability:
// KeyDelay 0+,-1+ --> -1, 0
// KeyDelay -1, 0+ --> -1, 0
// KeyDelay -1,-1 --> -1, -1
g.PressDuration = (g.KeyDelay < 0 && g.PressDuration < 0) ? -1 : 0;
g.KeyDelay = -1; // Above line must be done before this one.
}
}
else // SendInput is available and no other impacting hooks are obviously present on the system, so use SendInput unconditionally.
aSendModeOrig = SM_INPUT; // Resolve early so that other sections don't have to consider SM_INPUT_FALLBACK_TO_PLAY a valid value.
}
// Might be better to do this prior to changing capslock state. UPDATE: In v1.0.44.03, the following section
// has been moved to the top of the function because:
// 1) For ControlSend, GetModifierLRState() might be more accurate if the threads are attached beforehand.
// 2) Determines sTargetKeybdLayout and sTargetLayoutHasAltGr early (for maintainability).
bool threads_are_attached = false; // Set default.
DWORD keybd_layout_thread = 0; //
DWORD target_thread; // Doesn't need init.
if (aTargetWindow) // Caller has ensured this is NULL for SendInput and SendPlay modes.
{
if ((target_thread = GetWindowThreadProcessId(aTargetWindow, NULL)) // Assign.
&& target_thread != g_MainThreadID && !IsWindowHung(aTargetWindow))
{
threads_are_attached = AttachThreadInput(g_MainThreadID, target_thread, TRUE) != 0;
keybd_layout_thread = target_thread; // Testing shows that ControlSend benefits from the adapt-to-layout technique too.
}
//else no target thread, or it's our thread, or it's hung; so keep keybd_layout_thread at its default.
}
else
{
// v1.0.48.01: On Vista or later, work around the fact that an "L" keystroke (physical or artificial) will
// lock the computer whenever either Windows key is physically pressed down (artificially releasing the
// Windows key isn't enough to solve it because Win+L is apparently detected aggressively like
// Ctrl-Alt-Delete. Unlike the handling of SM_INPUT in another section, this one here goes into
// effect for all Sends because waiting for an "L" keystroke to be sent would be too late since the
// Windows would have already been artificially released by then, so IsKeyDownAsync() wouldn't be
// able to detect when the user physically releases the key.
if ( (g_script.mThisHotkeyModifiersLR & (MOD_LWIN|MOD_RWIN)) // Limit the scope to only those hotkeys that have a Win modifier, since anything outside that scope hasn't been fully analyzed.
#ifndef MINIDLL
&& (GetTickCount() - g_script.mThisHotkeyStartTime) < (DWORD)50 // Ensure g_script.mThisHotkeyModifiersLR is up-to-date enough to be reliable.
#endif
&& aSendModeOrig != SM_PLAY // SM_PLAY is reported to be incapable of locking the computer.
&& !sInBlindMode // The philosophy of blind-mode is that the script should have full control, so don't do any waiting during blind mode.
&& aSendRaw != SCM_RAW_TEXT // {Text} mode does not trigger Win+L.
&& g_os.IsWinVistaOrLater() // Only Vista (and presumably later OSes) check the physical state of the Windows key for Win+L.
&& aThreadID == g_MainThreadID // Exclude the hook thread because it isn't allowed to call anything like MsgSleep, nor are any calls from the hook thread within the understood/analyzed scope of this workaround.
)
{
bool wait_for_win_key_release;
if (aSendRaw)
wait_for_win_key_release = StrChrAny(aKeys, _T("Ll")) != NULL;
else
{
// It seems worthwhile to scan for any "L" characters to avoid waiting for the release
// of the Windows key when there are no L's. For performance and code size, the check
// below isn't comprehensive (e.g. it fails to consider things like {L} and #L).
// Although RegExMatch() could be used instead of the below, that would use up one of
// the RegEx cache entries, plus it would probably perform worse. So scan manually.
LPTSTR L_pos, brace_pos;
for (wait_for_win_key_release = false, brace_pos = aKeys; L_pos = StrChrAny(brace_pos, _T("Ll"));)
{
// Encountering a #L seems too rare, and the consequences too mild (or nonexistent), to
// justify the following commented-out section:
//if (L_pos > aKeys && L_pos[-1] == '#') // A simple check; it won't detect things like #+L.
// brace_pos = L_pos + 1;
//else
if (!(brace_pos = StrChrAny(L_pos + 1, _T("{}"))) || *brace_pos == '{') // See comment below.
{
wait_for_win_key_release = true;
break;
}
//else it found a '}' without a preceding '{', which means this "L" is inside braces.
// For simplicity, ignore such L's (probably not a perfect check, but seems worthwhile anyway).
}
}
if (wait_for_win_key_release)
while (IsKeyDownAsync(VK_LWIN) || IsKeyDownAsync(VK_RWIN)) // Even if the keyboard hook is installed, it seems best to use IsKeyDownAsync() vs. g_PhysicalKeyState[] because it's more likely to produce consistent behavior.
SLEEP_WITHOUT_INTERRUPTION(INTERVAL_UNSPECIFIED); // Seems best not to allow other threads to launch, for maintainability and because SendKeys() isn't designed to be interruptible.
}
// v1.0.44.03: The following change is meaningful only to people who use more than one keyboard layout.
// It seems that the vast majority of them would want the Send command (as well as other features like
// Hotstrings and the Input command) to adapt to the keyboard layout of the active window (or target window
// in the case of ControlSend) rather than sticking with the script's own keyboard layout. In addition,
// testing shows that this adapt-to-layout method costs almost nothing in performance, especially since
// the active window, its thread, and its layout are retrieved only once for each Send rather than once
// for each keystroke.
// v1.1.27.01: Use the thread of the focused control, which may differ from the active window.
keybd_layout_thread = GetFocusedCtrlThread();
}
#ifdef AHKX
sTargetKeybdLayout = g_HKL; // If keybd_layout_thread==0, this will get our thread's own layout, which seems like the best/safest default.
sTargetLayoutHasAltGr = LayoutHasAltGr(sTargetKeybdLayout); // Note that WM_INPUTLANGCHANGEREQUEST is not monitored by MsgSleep for the purpose of caching our thread's keyboard layout. This is because it would be unreliable if another msg pump such as MsgBox is running. Plus it hardly helps perf. at all, and hurts maintainability.
#else
sTargetKeybdLayout = GetKeyboardLayout(keybd_layout_thread); // If keybd_layout_thread==0, this will get our thread's own layout, which seems like the best/safest default.
sTargetLayoutHasAltGr = LayoutHasAltGr(sTargetKeybdLayout); // Note that WM_INPUTLANGCHANGEREQUEST is not monitored by MsgSleep for the purpose of caching our thread's keyboard layout. This is because it would be unreliable if another msg pump such as MsgBox is running. Plus it hardly helps perf. at all, and hurts maintainability.
#endif
// Below is now called with "true" so that the hook's modifier state will be corrected (if necessary)
// prior to every send.
modLR_type mods_current = GetModifierLRState(true); // Current "logical" modifier state.
// For any modifiers put in the "down" state by {xxx DownR}, keep only those which
// are still logically down before each Send starts. Otherwise each Send would reset
// the modifier to "down" even after the user "releases" it by some other means.
sModifiersLR_remapped &= mods_current;
// Make a best guess of what the physical state of the keys is prior to starting (there's no way
// to be certain without the keyboard hook). Note: We only want those physical
// keys that are also logically down (it's possible for a key to be down physically
// but not logically such as when R-control, for example, is a suffix hotkey and the
// user is physically holding it down):
modLR_type mods_down_physically_orig, mods_down_physically_and_logically
, mods_down_physically_but_not_logically_orig;
if (g_KeybdHook)
{
// Since hook is installed, use its more reliable tracking to determine which
// modifiers are down.
mods_down_physically_orig = g_modifiersLR_physical;
mods_down_physically_and_logically = g_modifiersLR_physical & g_modifiersLR_logical; // intersect
mods_down_physically_but_not_logically_orig = g_modifiersLR_physical & ~g_modifiersLR_logical;
}
else // Use best-guess instead.
{
// Even if TickCount has wrapped due to system being up more than about 49 days,
// DWORD subtraction still gives the right answer as long as g_script.mThisHotkeyStartTime
// itself isn't more than about 49 days ago:
if ((GetTickCount() - g_script.mThisHotkeyStartTime) < (DWORD)g_HotkeyModifierTimeout) // Elapsed time < timeout-value
mods_down_physically_orig = mods_current & g_script.mThisHotkeyModifiersLR; // Bitwise AND is set intersection.
else
// Since too much time as passed since the user pressed the hotkey, it seems best,
// based on the action that will occur below, to assume that no hotkey modifiers
// are physically down:
mods_down_physically_orig = 0;
mods_down_physically_and_logically = mods_down_physically_orig;
mods_down_physically_but_not_logically_orig = 0; // There's no way of knowing, so assume none.
}
// Any of the external modifiers that are down but NOT due to the hotkey are probably
// logically down rather than physically (perhaps from a prior command such as
// "Send, {CtrlDown}". Since there's no way to be sure without the keyboard hook or some
// driver-level monitoring, it seems best to assume that
// they are logically vs. physically down. This value contains the modifiers that
// we will not attempt to change (e.g. "Send, A" will not release the LWin
// before sending "A" if this value indicates that LWin is down). The below sets
// the value to be all the down-keys in mods_current except any that are physically
// down due to the hotkey itself. UPDATE: To improve the above, we now exclude from
// the set of persistent modifiers any that weren't made persistent by this script.
// Such a policy seems likely to do more good than harm as there have been cases where
// a modifier was detected as persistent just because #HotkeyModifier had timed out
// while the user was still holding down the key, but then when the user released it,
// this logic here would think it's still persistent and push it back down again
// to enforce it as "always-down" during the send operation. Thus, the key would
// basically get stuck down even after the send was over:
sModifiersLR_persistent &= mods_current & ~mods_down_physically_and_logically;
modLR_type persistent_modifiers_for_this_SendKeys, extra_persistent_modifiers_for_blind_mode;
if (sInBlindMode)
{
// The following value is usually zero unless the user is currently holding down
// some modifiers as part of a hotkey. These extra modifiers are the ones that
// this send operation (along with all its calls to SendKey and similar) should
// consider to be down for the duration of the Send (unless they go up via an
// explicit {LWin up}, etc.)
extra_persistent_modifiers_for_blind_mode = mods_current & ~sModifiersLR_persistent;
persistent_modifiers_for_this_SendKeys = mods_current;
}
else
{
extra_persistent_modifiers_for_blind_mode = 0;
persistent_modifiers_for_this_SendKeys = sModifiersLR_persistent;
}
// Above:
// Keep sModifiersLR_persistent and persistent_modifiers_for_this_SendKeys in sync with each other from now on.
// By contrast to persistent_modifiers_for_this_SendKeys, sModifiersLR_persistent is the lifetime modifiers for
// this script that stay in effect between sends. For example, "Send {LAlt down}" leaves the alt key down
// even after the Send ends, by design.
//
// It seems best not to change persistent_modifiers_for_this_SendKeys in response to the user making physical
// modifier changes during the course of the Send. This is because it seems more often desirable that a constant
// state of modifiers be kept in effect for the entire Send rather than having the user's release of a hotkey
// modifier key, which typically occurs at some unpredictable time during the Send, to suddenly alter the nature
// of the Send in mid-stride. Another reason is to make the behavior of Send consistent with that of SendInput.
// The default behavior is to turn the capslock key off prior to sending any keys
// because otherwise lowercase letters would come through as uppercase and vice versa.
ToggleValueType prior_capslock_state;
// Remember that apps like MS Word have an auto-correct feature that might make it
// wrongly seem that the turning off of Capslock below needs a Sleep(0) to take effect.
prior_capslock_state = g.StoreCapslockMode && !sInBlindMode && aSendRaw != SCM_RAW_TEXT
? ToggleKeyState(VK_CAPITAL, TOGGLED_OFF)
: TOGGLE_INVALID; // In blind mode, don't do store capslock (helps remapping and also adds flexibility).
// sSendMode must be set only after setting Capslock state above, because the hook method
// is incapable of changing the on/off state of toggleable keys like Capslock.
// However, it can change Capslock state as seen in the window to which playback events are being
// sent; but the behavior seems inconsistent and might vary depending on OS type, so it seems best
// not to rely on it.
sSendMode = aSendModeOrig;
if (sSendMode) // Build an array. We're also responsible for setting sSendMode to SM_EVENT prior to returning.
{
size_t mem_size;
if (sSendMode == SM_INPUT)
{
mem_size = MAX_INITIAL_EVENTS_SI * sizeof(INPUT);
sMaxEvents = MAX_INITIAL_EVENTS_SI;
}
else // Playback type.
{
mem_size = MAX_INITIAL_EVENTS_PB * sizeof(PlaybackEvent);
sMaxEvents = MAX_INITIAL_EVENTS_PB;
}
// _alloca() is used to avoid the overhead of malloc/free (99% of Sends will thus fit in stack memory).
// _alloca() never returns a failure code, it just raises an exception (e.g. stack overflow).
InitEventArray(_alloca(mem_size), sMaxEvents, mods_current);
}
bool blockinput_prev = g_BlockInput;
bool do_selective_blockinput = (g_BlockInputMode == TOGGLE_SEND || g_BlockInputMode == TOGGLE_SENDANDMOUSE)
&& !sSendMode && !aTargetWindow;
if (do_selective_blockinput)
Line::ScriptBlockInput(true); // Turn it on unconditionally even if it was on, since Ctrl-Alt-Del might have disabled it.
vk_type vk;
sc_type sc;
modLR_type key_as_modifiersLR = 0;
modLR_type mods_for_next_key = 0;
// Above: For v1.0.35, it was changed to modLR vs. mod so that AltGr keys such as backslash and '{'
// are supported on layouts such as German when sending to apps such as Putty that are fussy about
// which ALT key is held down to produce the character.
vk_type this_event_modifier_down;
size_t key_text_length, key_name_length;
TCHAR *end_pos, *space_pos, *next_word, old_char;
KeyEventTypes event_type;
int repeat_count, click_x, click_y;
bool move_offset;
enum { KEYDOWN_TEMP = 0, KEYDOWN_PERSISTENT, KEYDOWN_REMAP } key_down_type;
DWORD placeholder;
LONG_OPERATION_INIT // Needed even for SendInput/Play.
for (; *aKeys; ++aKeys, sPrevEventModifierDown = this_event_modifier_down)
{
this_event_modifier_down = 0; // Set default for this iteration, overridden selectively below.
if (!sSendMode)
LONG_OPERATION_UPDATE_FOR_SENDKEYS // This does not measurably affect the performance of SendPlay/Event.
if (!aSendRaw && _tcschr(_T("^+!#{}"), *aKeys))
{
switch (*aKeys)
{
case '^':
if (!(persistent_modifiers_for_this_SendKeys & (MOD_LCONTROL|MOD_RCONTROL)))
mods_for_next_key |= MOD_LCONTROL;
// else don't add it, because the value of mods_for_next_key may also used to determine
// which keys to release after the key to which this modifier applies is sent.
// We don't want persistent modifiers to ever be released because that's how
// AutoIt2 behaves and it seems like a reasonable standard.
continue;
case '+':
if (!(persistent_modifiers_for_this_SendKeys & (MOD_LSHIFT|MOD_RSHIFT)))
mods_for_next_key |= MOD_LSHIFT;
continue;
case '!':
if (!(persistent_modifiers_for_this_SendKeys & (MOD_LALT|MOD_RALT)))
mods_for_next_key |= MOD_LALT;
continue;
case '#':
if (!(persistent_modifiers_for_this_SendKeys & (MOD_LWIN|MOD_RWIN)))
mods_for_next_key |= MOD_LWIN;
continue;
case '}': continue; // Important that these be ignored. Be very careful about changing this, see below.
case '{':
{
if ( !(end_pos = _tcschr(aKeys + 1, '}')) ) // Ignore it and due to rarity, don't reset mods_for_next_key.
continue; // This check is relied upon by some things below that assume a '}' is present prior to the terminator.
aKeys = omit_leading_whitespace(aKeys + 1); // v1.0.43: Skip leading whitespace inside the braces to be more flexible.
if ( !(key_text_length = end_pos - aKeys) )
{
if (end_pos[1] == '}')
{
// The literal string "{}}" has been encountered, which is interpreted as a single "}".
++end_pos;
key_text_length = 1;
}
else if (IS_SPACE_OR_TAB(end_pos[1])) // v1.0.48: Support "{} down}", "{} downtemp}" and "{} up}".
{
next_word = omit_leading_whitespace(end_pos + 1);
if ( !_tcsnicmp(next_word, _T("Down"), 4) // "Down" or "DownTemp" (or likely enough).
|| !_tcsnicmp(next_word, _T("Up"), 2) )
{
if ( !(end_pos = _tcschr(next_word, '}')) ) // See comments at similar section above.
continue;
key_text_length = end_pos - aKeys; // This result must be non-zero due to the checks above.
}
else
goto brace_case_end; // The loop's ++aKeys will now skip over the '}', ignoring it.
}
else // Empty braces {} were encountered (or all whitespace, but literal whitespace isn't sent).
goto brace_case_end; // The loop's ++aKeys will now skip over the '}', ignoring it.
}
if (!_tcsnicmp(aKeys, _T("Click"), 5))
{
*end_pos = '\0'; // Temporarily terminate the string here to omit the closing brace from consideration below.
ParseClickOptions(omit_leading_whitespace(aKeys + 5), click_x, click_y, vk
, event_type, repeat_count, move_offset);
*end_pos = '}'; // Undo temp termination.
if (repeat_count < 1) // Allow {Click 100, 100, 0} to do a mouse-move vs. click (but modifiers like ^{Click..} aren't supported in this case.
MouseMove(click_x, click_y, placeholder, g.DefaultMouseSpeed, move_offset);
else // Use SendKey because it supports modifiers (e.g. ^{Click}) SendKey requires repeat_count>=1.
SendKey(vk, 0, mods_for_next_key, persistent_modifiers_for_this_SendKeys
, repeat_count, event_type, 0, aTargetWindow, click_x, click_y, move_offset);
goto brace_case_end; // This {} item completely handled, so move on to next.
}
else if (!_tcsnicmp(aKeys, _T("Raw"), 3)) // This is used by auto-replace hotstrings too.
{
// As documented, there's no way to switch back to non-raw mode afterward since there's no
// correct way to support special (non-literal) strings such as {Raw Off} while in raw mode.
aSendRaw = SCM_RAW;
goto brace_case_end; // This {} item completely handled, so move on to next.
}
else if (!_tcsnicmp(aKeys, _T("Text"), 4)) // Added in v1.1.27
{
if (omit_leading_whitespace(aKeys + 4) == end_pos)
aSendRaw = SCM_RAW_TEXT;
//else: ignore this {Text something} to reserve for future use.
goto brace_case_end; // This {} item completely handled, so move on to next.
}
// Since above didn't "goto", this item isn't {Click}.
event_type = KEYDOWNANDUP; // Set defaults.
repeat_count = 1; //
key_name_length = key_text_length; //
*end_pos = '\0'; // Temporarily terminate the string here to omit the closing brace from consideration below.
if (space_pos = StrChrAny(aKeys, _T(" \t"))) // Assign. Also, it relies on the fact that {} key names contain no spaces.
{
old_char = *space_pos;
*space_pos = '\0'; // Temporarily terminate here so that TextToVK() can properly resolve a single char.
key_name_length = space_pos - aKeys; // Override the default value set above.
next_word = omit_leading_whitespace(space_pos + 1);
UINT next_word_length = (UINT)(end_pos - next_word);
if (next_word_length > 0)
{
if (!_tcsnicmp(next_word, _T("Down"), 4))
{
event_type = KEYDOWN;
// v1.0.44.05: Added key_down_is_persistent (which is not initialized except here because
// it's only applicable when event_type==KEYDOWN). It avoids the following problem:
// When a key is remapped to become a modifier (such as F1::Control), launching one of
// the script's own hotkeys via F1 would lead to bad side-effects if that hotkey uses
// the Send command. This is because the Send command assumes that any modifiers pressed
// down by the script itself (such as Control) are intended to stay down during all
// keystrokes generated by that script. To work around this, something like KeyWait F1
// would otherwise be needed. within any hotkey triggered by the F1 key.
if (!_tcsnicmp(next_word + 4, _T("Temp"), 4)) // "DownTemp" means non-persistent.
key_down_type = KEYDOWN_TEMP;
else if (toupper(next_word[4] == 'R')) // "DownR" means treated as a physical modifier (R = remap); i.e. not kept down during Send, but restored after Send (unlike Temp).
key_down_type = KEYDOWN_REMAP;
else
key_down_type = KEYDOWN_PERSISTENT;
}
else if (!_tcsicmp(next_word, _T("Up")))
event_type = KEYUP;
else
repeat_count = ATOI(next_word);
// Above: If negative or zero, that is handled further below.
// There is no complaint for values <1 to support scripts that want to conditionally send
// zero keystrokes, e.g. Send {a %Count%}
}
}
TextToVKandSC(aKeys, vk, sc, &mods_for_next_key, sTargetKeybdLayout);
if (space_pos) // undo the temporary termination
*space_pos = old_char;
*end_pos = '}'; // undo the temporary termination
if (repeat_count < 1)
goto brace_case_end; // Gets rid of one level of indentation. Well worth it.
if (vk || sc)
{
if (key_as_modifiersLR = KeyToModifiersLR(vk, sc)) // Assign
{
if (!aTargetWindow)
{
if (event_type == KEYDOWN) // i.e. make {Shift down} have the same effect {ShiftDown}
{
this_event_modifier_down = vk;
if (key_down_type == KEYDOWN_PERSISTENT) // v1.0.44.05.
sModifiersLR_persistent |= key_as_modifiersLR;
else if (key_down_type == KEYDOWN_REMAP) // v1.1.27.00
sModifiersLR_remapped |= key_as_modifiersLR;
persistent_modifiers_for_this_SendKeys |= key_as_modifiersLR; // v1.0.44.06: Added this line to fix the fact that "DownTemp" should keep the key pressed down after the send.
}
else if (event_type == KEYUP) // *not* KEYDOWNANDUP, since that would be an intentional activation of the Start Menu or menu bar.
{
DisguiseWinAltIfNeeded(vk);
sModifiersLR_persistent &= ~key_as_modifiersLR;
sModifiersLR_remapped &= ~key_as_modifiersLR;
// By contrast with KEYDOWN, KEYUP should also remove this modifier
// from extra_persistent_modifiers_for_blind_mode if it happens to be
// in there. For example, if "#i::Send {LWin Up}" is a hotkey,
// LWin should become persistently up in every respect.
extra_persistent_modifiers_for_blind_mode &= ~key_as_modifiersLR;
// Fix for v1.0.43: Also remove LControl if this key happens to be AltGr.
if (vk == VK_RMENU && sTargetLayoutHasAltGr == CONDITION_TRUE) // It is AltGr.
extra_persistent_modifiers_for_blind_mode &= ~MOD_LCONTROL;
// Since key_as_modifiersLR isn't 0, update to reflect any changes made above:
persistent_modifiers_for_this_SendKeys = sModifiersLR_persistent | extra_persistent_modifiers_for_blind_mode;
}
// else must never change sModifiersLR_persistent in response to KEYDOWNANDUP
// because that would break existing scripts. This is because that same
// modifier key may have been pushed down via {ShiftDown} rather than "{Shift Down}".
// In other words, {Shift} should never undo the effects of a prior {ShiftDown}
// or {Shift down}.
}
//else don't add this event to sModifiersLR_persistent because it will not be
// manifest via keybd_event. Instead, it will done via less intrusively
// (less interference with foreground window) via SetKeyboardState() and
// PostMessage(). This change is for ControlSend in v1.0.21 and has been
// documented.
}
// Below: sModifiersLR_persistent stays in effect (pressed down) even if the key
// being sent includes that same modifier. Surprisingly, this is how AutoIt2
// behaves also, which is good. Example: Send, {AltDown}!f ; this will cause
// Alt to still be down after the command is over, even though F is modified
// by Alt.
SendKey(vk, sc, mods_for_next_key, persistent_modifiers_for_this_SendKeys
, repeat_count, event_type, key_as_modifiersLR, aTargetWindow);
}
else if (isdigit((UCHAR)*aKeys) && key_name_length > 1)
{
if (sSendMode != SM_EVENT) // || (sSendMode == SM_INPUT && !SystemHasAnotherKeybdHook()))
PutKeybdEventIntoArray(0, 0, 0, 0,ATOI(aKeys));
else
SLEEP_WITHOUT_INTERRUPTION(ATOI(aKeys));
}
else if (key_name_length == 1) // No vk/sc means a char of length one is sent via special method.
{
// v1.0.40: SendKeySpecial sends only keybd_event keystrokes, not ControlSend style
// keystrokes.
// v1.0.43.07: Added check of event_type!=KEYUP, which causes something like Send {ð up} to
// do nothing if the curr. keyboard layout lacks such a key. This is relied upon by remappings
// such as F1::ð (i.e. a destination key that doesn't have a VK, at least in English).
if (event_type != KEYUP) // In this mode, mods_for_next_key and event_type are ignored due to being unsupported.
{
if (aTargetWindow)
{
// Although MSDN says WM_CHAR uses UTF-16, it seems to really do automatic
// translation between ANSI and UTF-16; we rely on this for correct results:
for (int i = 0; i < repeat_count; ++i)
PostMessage(aTargetWindow, WM_CHAR, aKeys[0], 0);
}
else
SendKeySpecial(aKeys[0], repeat_count, mods_for_next_key | persistent_modifiers_for_this_SendKeys);
}
}
// See comment "else must never change sModifiersLR_persistent" above about why
// !aTargetWindow is used below:
else if (vk = TextToSpecial(aKeys, key_text_length, event_type
, persistent_modifiers_for_this_SendKeys, !aTargetWindow)) // Assign.
{
if (!aTargetWindow)
{
if (event_type == KEYDOWN)
this_event_modifier_down = vk;
else // It must be KEYUP because TextToSpecial() never returns KEYDOWNANDUP.
DisguiseWinAltIfNeeded(vk);
}
// Since we're here, repeat_count > 0.
// v1.0.42.04: A previous call to SendKey() or SendKeySpecial() might have left modifiers
// in the wrong state (e.g. Send +{F1}{ControlDown}). Since modifiers can sometimes affect
// each other, make sure they're in the state intended by the user before beginning:
SetModifierLRState(persistent_modifiers_for_this_SendKeys
, sSendMode ? sEventModifiersLR : GetModifierLRState()
, aTargetWindow, false, false); // It also does DoKeyDelay(g->PressDuration).
for (int i = 0; i < repeat_count; ++i)
{
// Don't tell it to save & restore modifiers because special keys like this one
// should have maximum flexibility (i.e. nothing extra should be done so that the
// user can have more control):
KeyEvent(event_type, vk, 0, aTargetWindow, true);
if (!sSendMode)
LONG_OPERATION_UPDATE_FOR_SENDKEYS
}
}
else if (key_text_length > 4 && !_tcsnicmp(aKeys, _T("ASC "), 4) && !aTargetWindow) // {ASC nnnnn}
{
// Include the trailing space in "ASC " to increase uniqueness (selectivity).
// Also, sending the ASC sequence to window doesn't work, so don't even try:
SendASC(omit_leading_whitespace(aKeys + 3));
// Do this only once at the end of the sequence:
DoKeyDelay(); // It knows not to do the delay for SM_INPUT.
}
else if (key_text_length > 2 && !_tcsnicmp(aKeys, _T("U+"), 2))
{
// L24: Send a unicode value as shown by Character Map.
UINT u_code = (UINT) _tcstol(aKeys + 2, NULL, 16);
wchar_t wc1, wc2;
if (u_code >= 0x10000)
{
// Supplementary characters are encoded as UTF-16 and split into two messages.
u_code -= 0x10000;
wc1 = 0xd800 + ((u_code >> 10) & 0x3ff);
wc2 = 0xdc00 + (u_code & 0x3ff);
}
else
{
wc1 = (wchar_t) u_code;
wc2 = 0;
}
if (aTargetWindow)
{
// Although MSDN says WM_CHAR uses UTF-16, PostMessageA appears to truncate it to 8-bit.
// This probably means it does automatic translation between ANSI and UTF-16. Since we
// specifically want to send a Unicode character value, use PostMessageW:
PostMessageW(aTargetWindow, WM_CHAR, wc1, 0);
if (wc2)
PostMessageW(aTargetWindow, WM_CHAR, wc2, 0);
}
else
{
// Use SendInput in unicode mode if available, otherwise fall back to SendASC.
// To know why the following requires sSendMode != SM_PLAY, see SendUnicodeChar.
if (sSendMode != SM_PLAY)
{
SendUnicodeChar(wc1, mods_for_next_key | persistent_modifiers_for_this_SendKeys);
if (wc2)
SendUnicodeChar(wc2, mods_for_next_key | persistent_modifiers_for_this_SendKeys);
}
else // Note that this method generally won't work with Unicode characters except
{ // with specific controls which support it, such as RichEdit (tested on WordPad).
TCHAR asc[8];
*asc = '0';
_itot(u_code, asc + 1, 10);
SendASC(asc);
}
}
DoKeyDelay();
}
//else do nothing since it isn't recognized as any of the above "else if" cases (see below).
// If what's between {} is unrecognized, such as {Bogus}, it's safest not to send
// the contents literally since that's almost certainly not what the user intended.
// In addition, reset the modifiers, since they were intended to apply only to
// the key inside {}. Also, the below is done even if repeat-count is zero.
brace_case_end: // This label is used to simplify the code without sacrificing performance.
aKeys = end_pos; // In prep for aKeys++ done by the loop.
mods_for_next_key = 0;
continue;
} // case '{'
} // switch()
} // if (!aSendRaw && strchr("^+!#{}", *aKeys))
else // Encountered a character other than ^+!#{} ... or we're in raw mode.
{
if (aSendRaw == SCM_RAW_TEXT)
{
// \b needs to produce VK_BACK for auto-replace hotstrings to work (this is more useful anyway).
// \r and \n need to produce VK_RETURN for decent compatibility. SendKeySpecial('\n') works for
// some controls (such as Scintilla) but has no effect in other common applications.
// \t has more utility if translated to VK_TAB. SendKeySpecial('\t') has no effect in many
// common cases, and seems to only work in cases where {tab} would work just as well.
switch (*aKeys)
{
case '\r': // Translate \r but ignore any trailing \n, since \r\n -> {Enter 2} is counter-intuitive.
if (aKeys[1] == '\n')
++aKeys;
// Fall through:
case '\n': vk = VK_RETURN; break;
case '\b': vk = VK_BACK; break;
case '\t': vk = VK_TAB; break;
default: vk = 0; break; // Send all other characters via SendKeySpecial()/WM_CHAR.
}
}
else
{
// Best to call this separately, rather than as first arg in SendKey, since it changes the
// value of modifiers and the updated value is *not* guaranteed to be passed.
// In other words, SendKey(TextToVK(...), modifiers, ...) would often send the old
// value for modifiers.
vk = CharToVKAndModifiers(*aKeys, &mods_for_next_key, sTargetKeybdLayout
, (mods_for_next_key | persistent_modifiers_for_this_SendKeys) != 0 && !aSendRaw); // v1.1.27.00: Disable the a-z to vk41-vk5A fallback translation when modifiers are present since it would produce the wrong printable characters.
// CharToVKAndModifiers() takes no measurable time compared to the amount of time SendKey takes.
}
if (vk)
{
SendKey(vk, 0, mods_for_next_key, persistent_modifiers_for_this_SendKeys, 1, KEYDOWNANDUP
, 0, aTargetWindow);
}
else // Try to send it by alternate means.
{
// In this mode, mods_for_next_key is ignored due to being unsupported.
if (aTargetWindow)
// Although MSDN says WM_CHAR uses UTF-16, it seems to really do automatic
// translation between ANSI and UTF-16; we rely on this for correct results:
PostMessage(aTargetWindow, WM_CHAR, *aKeys, 0);
else
SendKeySpecial(*aKeys, 1, mods_for_next_key | persistent_modifiers_for_this_SendKeys);
}
mods_for_next_key = 0; // Safest to reset this regardless of whether a key was sent.
}
} // for()
modLR_type mods_to_set;
if (sSendMode)
{
int final_key_delay = -1; // Set default.
if (!sAbortArraySend && sEventCount > 0) // Check for zero events for performance, but more importantly because playback hook will not operate correctly with zero.
{
// Add more events to the array (prior to sending) to support the following:
// Restore the modifiers to match those the user is physically holding down, but do it as *part*
// of the single SendInput/Play call. The reasons it's done here as part of the array are:
// 1) It avoids the need for #HotkeyModifierTimeout (and it's superior to it) for both SendInput
// and SendPlay.
// 2) The hook will not be present during the SendInput, nor can it be reinstalled in time to
// catch any physical events generated by the user during the Send. Consequently, there is no
// known way to reliably detect physical keystate changes.
// 3) Changes made to modifier state by SendPlay are seen only by the active window's thread.
// Thus, it would be inconsistent and possibly incorrect to adjust global modifier state
// after (or during) a SendPlay.
// So rather than resorting to #HotkeyModifierTimeout, we can restore the modifiers within the
// protection of SendInput/Play's uninterruptibility, allowing the user's buffered keystrokes
// (if any) to hit against the correct modifier state when the SendInput/Play completes.
// For example, if #c:: is a hotkey and the user releases Win during the SendInput/Play, that
// release would hit after SendInput/Play restores Win to the down position, and thus Win would
// not be stuck down. Furthermore, if the user didn't release Win, Win would be in the
// correct/intended position.
// This approach has a few weaknesses (but the strengths appear to outweigh them):
// 1) Hitting SendInput's 5000 char limit would omit the tail-end keystrokes, which would mess up
// all the assumptions here. But hitting that limit should be very rare, especially since it's
// documented and thus scripts will avoid it.
// 2) SendInput's assumed uninterruptibility is false if any other app or script has an LL hook
// installed. This too is documented, so scripts should generally avoid using SendInput when
// they know there are other LL hooks in the system. In any case, there's no known solution
// for it, so nothing can be done.
mods_to_set = persistent_modifiers_for_this_SendKeys
| sModifiersLR_remapped // Restore any modifiers which were put in the down state by remappings or {key DownR} prior to this Send.
| (sInBlindMode ? 0 : (mods_down_physically_orig & ~mods_down_physically_but_not_logically_orig)); // The last item is usually 0.
// Above: When in blind mode, don't restore physical modifiers. This is done to allow a hotkey
// such as the following to release Shift:
// +space::SendInput/Play {Blind}{Shift up}
// Note that SendPlay can make such a change only from the POV of the target window; i.e. it can
// release shift as seen by the target window, but not by any other thread; so the shift key would
// still be considered to be down for the purpose of firing hotkeys (it can't change global key state
// as seen by GetAsyncKeyState).
// For more explanation of above, see a similar section for the non-array/old Send below.
SetModifierLRState(mods_to_set, sEventModifiersLR, NULL, true, true); // Disguise in case user released or pressed Win/Alt during the Send (seems best to do it even for SendPlay, though it probably needs only Alt, not Win).
// mods_to_set is used further below as the set of modifiers that were explicitly put into effect at the tail end of SendInput.
SendEventArray(final_key_delay, mods_to_set);
}
CleanupEventArray(final_key_delay);
}
else // A non-array send is in effect, so a more elaborate adjustment to logical modifiers is called for.
{
// Determine (or use best-guess, if necessary) which modifiers are down physically now as opposed
// to right before the Send began.
modLR_type mods_down_physically; // As compared to mods_down_physically_orig.
if (g_KeybdHook)
mods_down_physically = g_modifiersLR_physical;
else // No hook, so consult g_HotkeyModifierTimeout to make the determination.
// Assume that the same modifiers that were phys+logically down before the Send are still
// physically down (though not necessarily logically, since the Send may have released them),
// but do this only if the timeout period didn't expire (or the user specified that it never
// times out; i.e. elapsed time < timeout-value; DWORD subtraction gives the right answer even if
// tick-count has wrapped around).
mods_down_physically = (g_HotkeyModifierTimeout < 0 // It never times out or...
|| (GetTickCount() - g_script.mThisHotkeyStartTime) < (DWORD)g_HotkeyModifierTimeout) // It didn't time out.
? mods_down_physically_orig : 0;
// Put any modifiers in sModifiersLR_remapped back into effect, as if they were physically down.
mods_down_physically |= sModifiersLR_remapped;
// Restore the state of the modifiers to be those the user is physically holding down right now.
// Any modifiers that are logically "persistent", as detected upon entrance to this function
// (e.g. due to something such as a prior "Send, {LWinDown}"), are also pushed down if they're not already.
// Don't press back down the modifiers that were used to trigger this hotkey if there's
// any doubt that they're still down, since doing so when they're not physically down
// would cause them to be stuck down, which might cause unwanted behavior when the unsuspecting
// user resumes typing.
// v1.0.42.04: Now that SendKey() is lazy about releasing Ctrl and/or Shift (but not Win/Alt),
// the section below also releases Ctrl/Shift if appropriate. See SendKey() for more details.
mods_to_set = persistent_modifiers_for_this_SendKeys; // Set default.
if (sInBlindMode) // This section is not needed for the array-sending modes because they exploit uninterruptibility to perform a more reliable restoration.
{
// At the end of a blind-mode send, modifiers are restored differently than normal. One
// reason for this is to support the explicit ability for a Send to turn off a hotkey's
// modifiers even if the user is still physically holding them down. For example:
// #space::Send {LWin up} ; Fails to release it, by design and for backward compatibility.
// #space::Send {Blind}{LWin up} ; Succeeds, allowing LWin to be logically up even though it's physically down.
modLR_type mods_changed_physically_during_send = mods_down_physically_orig ^ mods_down_physically;
// Fix for v1.0.42.04: To prevent keys from getting stuck down, compensate for any modifiers
// the user physically pressed or released during the Send (especially those released).
// Remove any modifiers physically released during the send so that they don't get pushed back down:
mods_to_set &= ~(mods_changed_physically_during_send & mods_down_physically_orig); // Remove those that changed from down to up.
// Conversely, add any modifiers newly, physically pressed down during the Send, because in
// most cases the user would want such modifiers to be logically down after the Send.
// Obsolete comment from v1.0.40: For maximum flexibility and minimum interference while
// in blind mode, never restore modifiers to the down position then.
mods_to_set |= mods_changed_physically_during_send & mods_down_physically; // Add those that changed from up to down.
}
else // Regardless of whether the keyboard hook is present, the following formula applies.
mods_to_set |= mods_down_physically & ~mods_down_physically_but_not_logically_orig; // The second item is usually 0.
// Above takes into account the fact that the user may have pressed and/or released some modifiers
// during the Send.
// So it includes all keys that are physically down except those that were down physically but not
// logically at the *start* of the send operation (since the send operation may have changed the
// logical state). In other words, we want to restore the keys to their former logical-down
// position to match the fact that the user is still holding them down physically. The
// previously-down keys we don't do this for are those that were physically but not logically down,
// such as a naked Control key that's used as a suffix without being a prefix. More details:
// mods_down_physically_but_not_logically_orig is used to distinguish between the following two cases,
// allowing modifiers to be properly restored to the down position when the hook is installed:
// 1) A naked modifier key used only as suffix: when the user phys. presses it, it isn't
// logically down because the hook suppressed it.
// 2) A modifier that is a prefix, that triggers a hotkey via a suffix, and that hotkey sends
// that modifier. The modifier will go back up after the SEND, so the key will be physically
// down but not logically.
// Use KEY_IGNORE_ALL_EXCEPT_MODIFIER to tell the hook to adjust g_modifiersLR_logical_non_ignored
// because these keys being put back down match the physical pressing of those same keys by the
// user, and we want such modifiers to be taken into account for the purpose of deciding whether
// other hotkeys should fire (or the same one again if auto-repeating):
// v1.0.42.04: A previous call to SendKey() might have left Shift/Ctrl in the down position
// because by procrastinating, extraneous keystrokes in examples such as "Send ABCD" are
// eliminated (previously, such that example released the shift key after sending each key,
// only to have to press it down again for the next one. For this reason, some modifiers
// might get released here in addition to any that need to get pressed down. That's why
// SetModifierLRState() is called rather than the old method of pushing keys down only,
// never releasing them.
// Put the modifiers in mods_to_set into effect. Although "true" is passed to disguise up-events,
// there generally shouldn't be any up-events for Alt or Win because SendKey() would have already
// released them. One possible exception to this is when the user physically released Alt or Win
// during the send (perhaps only during specific sensitive/vulnerable moments).
// g_modifiersLR_numpad_mask is used to work around an issue where our changes to shift-key state
// trigger the system's shift-numpad handling (usually in combination with actual user input),
// which in turn causes the Shift key to stick down. If non-zero, the Shift key is currently "up"
// but should be "released" anyway, since the system will inject Shift-down either before the next
// keyboard event or after the Numpad key is released. Find "fake shift" for more details.
SetModifierLRState(mods_to_set, GetModifierLRState() | g_modifiersLR_numpad_mask, aTargetWindow, true, true); // It also does DoKeyDelay(g->PressDuration).
} // End of non-array Send.
// For peace of mind and because that's how it was tested originally, the following is done
// only after adjusting the modifier state above (since that adjustment might be able to
// affect the global variables used below in a meaningful way).
if (g_KeybdHook)
{
// Ensure that g_modifiersLR_logical_non_ignored does not contain any down-modifiers
// that aren't down in g_modifiersLR_logical. This is done mostly for peace-of-mind,
// since there might be ways, via combinations of physical user input and the Send
// commands own input (overlap and interference) for one to get out of sync with the
// other. The below uses ^ to find the differences between the two, then uses & to
// find which are down in non_ignored that aren't in logical, then inverts those bits
// in g_modifiersLR_logical_non_ignored, which sets those keys to be in the up position:
g_modifiersLR_logical_non_ignored &= ~((g_modifiersLR_logical ^ g_modifiersLR_logical_non_ignored)
& g_modifiersLR_logical_non_ignored);
}
if (prior_capslock_state == TOGGLED_ON) // The current user setting requires us to turn it back on.
ToggleKeyState(VK_CAPITAL, TOGGLED_ON);
// Might be better to do this after changing capslock state, since having the threads attached
// tends to help with updating the global state of keys (perhaps only under Win9x in this case):
if (threads_are_attached)
AttachThreadInput(g_MainThreadID, target_thread, FALSE);
if (do_selective_blockinput && !blockinput_prev) // Turn it back off only if it was off before we started.
Line::ScriptBlockInput(false);
// The following MsgSleep(-1) solves unwanted buffering of hotkey activations while SendKeys is in progress
// in a non-Critical thread. Because SLEEP_WITHOUT_INTERRUPTION is used to perform key delays, any incoming
// hotkey messages would be left in the queue. It is not until the next interruptible sleep that hotkey
// messages may be processed, and potentially discarded due to #MaxThreadsPerHotkey (even #MaxThreadsBuffer
// should only allow one buffered activation). But if the hotkey thread just calls Send in a loop and then
// returns, it never performs an interruptible sleep, so the hotkey messages are processed one by one after
// each new hotkey thread returns, even though Critical was not used. Also note SLEEP_WITHOUT_INTERRUPTION
// causes g_script.mLastScriptRest to be reset, so it's unlikely that a sleep would occur between Send calls.
// To solve this, call MsgSleep(-1) now (unless no delays were performed, or the thread is uninterruptible):
if (aSendModeOrig == SM_EVENT && g_script.mLastScriptRest != orig_last_script_rest && IsInterruptible())
MsgSleep(-1);
// v1.0.43.03: Someone reported that when a non-autoreplace hotstring calls us to do its backspacing, the
// hotstring's subroutine can execute a command that activates another window owned by the script before
// the original window finished receiving its backspaces. Although I can't reproduce it, this behavior
// fits with expectations since our thread won't necessarily have a chance to process the incoming
// keystrokes before executing the command that comes after SendInput. If those command(s) activate
// another of this thread's windows, that window will most likely intercept the keystrokes (assuming
// that the message pump dispatches buffered keystrokes to whichever window is active at the time the