-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathapplication.cpp
2123 lines (1994 loc) · 130 KB
/
application.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 ([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 "application.h"
#include "globaldata.h" // for access to g_clip, the "g" global struct, etc.
#include "window.h" // for serveral MsgBox and window functions
#include "util.h" // for strlcpy()
#include "resources/resource.h" // For ID_TRAY_OPEN.
bool MsgSleep(int aSleepDuration, MessageMode aMode)
// Returns true if it launched at least one thread, and false otherwise.
// aSleepDuration can be be zero to do a true Sleep(0), or less than 0 to avoid sleeping or
// waiting at all (i.e. messages are checked and if there are none, the function will return
// immediately). aMode is either RETURN_AFTER_MESSAGES (default) or WAIT_FOR_MESSAGES.
// If the caller doesn't specify aSleepDuration, this function will return after a
// time less than or equal to SLEEP_INTERVAL (i.e. the exact amount of the sleep
// isn't important to the caller). This mode is provided for performance reasons
// (it avoids calls to GetTickCount and the TickCount math). However, if the
// caller's script subroutine is suspended due to action by us, an unknowable
// amount of time may pass prior to finally returning to the caller.
{
bool we_turned_on_defer = false; // Set default.
if (aMode == RETURN_AFTER_MESSAGES_SPECIAL_FILTER)
{
aMode = RETURN_AFTER_MESSAGES; // To simplify things further below, eliminate the mode RETURN_AFTER_MESSAGES_SPECIAL_FILTER from further consideration.
// g_DeferMessagesForUnderlyingPump is a global because the instance of MsgSleep on the calls stack
// that set it to true could launch new thread(s) that call MsgSleep again (i.e. a new layer), and a global
// is the easiest way to inform all such MsgSleeps that there's a non-standard msg pump beneath them on the
// call stack.
if (!g_DeferMessagesForUnderlyingPump)
{
g_DeferMessagesForUnderlyingPump = true;
we_turned_on_defer = true;
}
// So now either we turned it on or some layer beneath us did. Therefore, we know there's at least one
// non-standard msg pump beneath us on the call stack.
}
// The following is done here for performance reasons. UPDATE: This probably never needs
// to close the clipboard now that Line::ExecUntil() also calls CLOSE_CLIPBOARD_IF_OPEN:
CLOSE_CLIPBOARD_IF_OPEN;
// While in mode RETURN_AFTER_MESSAGES, there are different things that can happen:
// 1) We launch a new hotkey subroutine, interrupting/suspending the old one. But
// subroutine calls this function again, so now it's recursed. And thus the
// new subroutine can be interrupted yet again.
// 2) We launch a new hotkey subroutine, but it returns before any recursed call
// to this function discovers yet another hotkey waiting in the queue. In this
// case, this instance/recursion layer of the function should process the
// hotkey messages linearly rather than recursively? No, this doesn't seem
// necessary, because we can just return from our instance/layer and let the
// caller handle any messages waiting in the queue. Eventually, the queue
// should be emptied, especially since most hotkey subroutines will run
// much faster than the user could press another hotkey, with the possible
// exception of the key-repeat feature triggered by holding a key down.
// Even in that case, the worst that would happen is that messages would
// get dropped off the queue because they're too old (I think that's what
// happens).
// Based on the above, when mode is RETURN_AFTER_MESSAGES, we process
// all messages until a hotkey message is encountered, at which time we
// launch that subroutine only and then return when it returns to us, letting
// the caller handle any additional messages waiting on the queue. This avoids
// the need to have a "run the hotkeys linearly" mode in a single iteration/layer
// of this function. Note: The WM_QUIT message does not receive any higher
// precedence in the queue than other messages. Thus, if there's ever concern
// that that message would be lost, as a future change perhaps can use PeekMessage()
// with a filter to explicitly check to see if our queue has a WM_QUIT in it
// somewhere, prior to processing any messages that might take result in
// a long delay before the remainder of the queue items are processed (there probably
// aren't any such conditions now, so nothing to worry about?)
// Above is somewhat out-of-date. The objective now is to spend as much time
// inside GetMessage() as possible, since it's the keystroke/mouse engine
// whenever the hooks are installed. Any time we're not in GetMessage() for
// any length of time (say, more than 20ms), keystrokes and mouse events
// will be lagged. PeekMessage() is probably almost as good, but it probably
// only clears out any waiting keys prior to returning. CONFIRMED: PeekMessage()
// definitely routes to the hook, perhaps only if called regularly (i.e. a single
// isolated call might not help much).
// This var allows us to suspend the currently-running subroutine and run any
// hotkey events waiting in the message queue (if there are more than one, they
// will be executed in sequence prior to resuming the suspended subroutine).
// Never static because we could be recursed (e.g. when one hotkey iterruptes
// a hotkey that has already been interrupted) and each recursion layer should
// have it's own value for this:
global_struct global_saved;
char ErrorLevel_saved[ERRORLEVEL_SAVED_SIZE];
// Decided to support a true Sleep(0) for aSleepDuration == 0, as well
// as no delay at all if aSleepDuration < 0. This is needed to implement
// "SetKeyDelay, 0" and possibly other things. I believe a Sleep(0)
// is always <= Sleep(1) because both of these will wind up waiting
// a full timeslice if the CPU is busy.
// Reminder for anyone maintaining or revising this code:
// Giving each subroutine its own thread rather than suspending old ones is
// probably not a good idea due to the exclusive nature of the GUI
// (i.e. it's probably better to suspend existing subroutines rather than
// letting them continue to run because they might activate windows and do
// other stuff that would interfere with the window automation activities of
// other threads)
// If caller didn't specify, the exact amount of the Sleep() isn't
// critical to it, only that we handles messages and do Sleep()
// a little.
// Most of this initialization section isn't needed if aMode == WAIT_FOR_MESSAGES,
// but it's done anyway for consistency:
bool allow_early_return;
if (aSleepDuration == INTERVAL_UNSPECIFIED)
{
aSleepDuration = SLEEP_INTERVAL; // Set interval to be the default length.
allow_early_return = true;
}
else
// The timer resolution makes waiting for half or less of an
// interval too chancy. The correct thing to do on average
// is some kind of rounding, which this helps with:
allow_early_return = (aSleepDuration <= SLEEP_INTERVAL_HALF);
// Record the start time when the caller first called us so we can keep
// track of how much time remains to sleep (in case the caller's subroutine
// is suspended until a new subroutine is finished). But for small sleep
// intervals, don't worry about it.
// Note: QueryPerformanceCounter() has very high overhead compared to GetTickCount():
DWORD start_time = allow_early_return ? 0 : GetTickCount();
// This check is also done even if the main timer will be set (below) so that
// an initial check is done rather than waiting 10ms more for the first timer
// message to come in. Some of our many callers would want this, and although some
// would not need it, there are so many callers that it seems best to just do it
// unconditionally, especially since it's not a high overhead call (e.g. it returns
// immediately if the tickcount is still the same as when it was last run).
// Another reason for doing this check immediately is that our msg queue might
// contains a time-consuming msg prior to our WM_TIMER msg, e.g. a hotkey msg.
// In that case, the hotkey would be processed and launched without us first having
// emptied the queue to discover the WM_TIMER msg. In other words, WM_TIMER msgs
// might get buried in the queue behind others, so doing this check here should help
// ensure that timed subroutines are checked often enough to keep them running at
// their specified frequencies.
// Note that ExecUntil() no longer needs to call us solely for prevention of lag
// caused by the keyboard & mouse hooks, so checking the timers early, rather than
// immediately going into the GetMessage() state, should not be a problem:
POLL_JOYSTICK_IF_NEEDED // Do this first since it's much faster.
bool return_value = false; // Set default. Also, this is used by the macro below.
CHECK_SCRIPT_TIMERS_IF_NEEDED
// Because this function is called recursively: for now, no attempt is
// made to improve performance by setting the timer interval to be
// aSleepDuration rather than a standard short interval. That would cause
// a problem if this instance of the function invoked a new subroutine,
// suspending the one that called this instance. The new subroutine
// might need a timer of a longer interval, which would mess up
// this layer. One solution worth investigating is to give every
// layer/instance its own timer (the ID of the timer can be determined
// from info in the WM_TIMER message). But that can be a real mess
// because what if a deeper recursion level receives our first
// WM_TIMER message because we were suspended too long? Perhaps in
// that case we wouldn't our WM_TIMER pulse because upon returning
// from those deeper layers, we would check to see if the current
// time is beyond our finish time. In addition, having more timers
// might be worse for overall system performance than having a single
// timer that pulses very frequently (because the system must keep
// them all up-to-date). UPDATE: Timer is now also needed whenever an
// aSleepDuration greater than 0 is about to be done and there are some
// script timers that need to be watched (this happens when aMode == WAIT_FOR_MESSAGES).
// UPDATE: Make this a macro so that it is dynamically resolved every time, in case
// the value of g_script.mTimerEnabledCount changes on-the-fly.
// UPDATE #2: The below has been changed in light of the fact that the main timer is
// now kept always-on whenever there is at least one enabled timed subroutine.
// This policy simplifies ExecUntil() and long-running commands such as FileSetAttrib.
// UPDATE #3: Use aMode == RETURN_AFTER_MESSAGES, not g_nThreads > 0, because the
// "Edit This Script" menu item (and possibly other places) might result in an indirect
// call to us and we will need the timer to avoid getting stuck in the GetMessageState()
// with hotkeys being disallowed due to filtering:
bool this_layer_needs_timer = (aSleepDuration > 0 && aMode == RETURN_AFTER_MESSAGES);
if (this_layer_needs_timer)
{
++g_nLayersNeedingTimer; // IsCycleComplete() is responsible for decrementing this for us.
SET_MAIN_TIMER
// Reasons why the timer might already have been on:
// 1) g_script.mTimerEnabledCount is greater than zero.
// 2) another instance of MsgSleep() (beneath us in the stack) needs it (see the comments
// in IsCycleComplete() near KILL_MAIN_TIMER for details).
}
// Only used when aMode == RETURN_AFTER_MESSAGES:
// True if the current subroutine was interrupted by another:
//bool was_interrupted = false;
bool sleep0_was_done = false;
bool empty_the_queue_via_peek = false;
int i, gui_count;
bool msg_was_handled;
HWND fore_window, focused_control, focused_parent, criterion_found_hwnd;
char wnd_class_name[32], gui_action_errorlevel[16], *walk;
UserMenuItem *menu_item;
Hotkey *hk;
HotkeyVariant *variant;
ActionTypeType type_of_first_line;
int priority;
Hotstring *hs;
GuiType *pgui; // This is just a temp variable and should not be referred to once the below has been determined.
GuiControlType *pcontrol, *ptab_control;
GuiIndexType gui_control_index, gui_index; // gui_index is needed to avoid using pgui in cases where that pointer becomes invalid (e.g. if ExecUntil() executes "Gui Destroy").
GuiEventType gui_action;
DWORD gui_event_info, gui_size;
bool *pgui_label_is_running, event_is_control_generated, peek_was_done, do_special_msg_filter;
Label *gui_label;
HDROP hdrop_to_free;
DWORD tick_before, tick_after, peek1_time;
LRESULT msg_reply;
BOOL peek_result;
MSG msg;
for (;;) // Main event loop.
{
tick_before = GetTickCount();
if (aSleepDuration > 0 && !empty_the_queue_via_peek && !g_DeferMessagesForUnderlyingPump) // g_Defer: Requires a series of Peeks to handle non-contingous ranges, which is why GetMessage() can't be used.
{
// The following comment is mostly obsolete as of v1.0.39 (which introduces a thread
// dedicated to the hooks). However, using GetMessage() is still superior to
// PeekMessage() for performance reason. Add to that the risk of breaking things
// and it seems clear that it's best to retain GetMessage().
// Older comment:
// Use GetMessage() whenever possible -- rather than PeekMessage() or a technique such
// MsgWaitForMultipleObjects() -- because it's the "engine" that passes all keyboard
// and mouse events immediately to the low-level keyboard and mouse hooks
// (if they're installed). Otherwise, there's greater risk of keyboard/mouse lag.
// PeekMessage(), depending on how, and how often it's called, will also do this, but
// I'm not as confident in it.
if (GetMessage(&msg, NULL, 0, MSG_FILTER_MAX) == -1) // -1 is an error, 0 means WM_QUIT
continue; // Error probably happens only when bad parameters were passed to GetMessage().
//else let any WM_QUIT be handled below.
// The below was added for v1.0.20 to solve the following issue: If BatchLines is 10ms
// (its default) and there are one or more 10ms script-timers active, those timers would
// actually only run about every 20ms. In addition to solving that problem, the below
// might also improve reponsiveness of hotkeys, menus, buttons, etc. when the CPU is
// under heavy load:
tick_after = GetTickCount();
if (tick_after - tick_before > 3) // 3 is somewhat arbitrary, just want to make sure it rested for a meaningful amount of time.
g_script.mLastScriptRest = tick_after;
}
else // aSleepDuration < 1 || empty_the_queue_via_peek || g_DeferMessagesForUnderlyingPump
{
peek_was_done = false; // Set default.
// Check the active window in each iteration in case a signficant amount of time has passed since
// the previous iteration (due to launching threads, etc.)
if (g_DeferMessagesForUnderlyingPump && (fore_window = GetForegroundWindow()) != NULL // There is a foreground window.
&& GetWindowThreadProcessId(fore_window, NULL) == g_MainThreadID) // And it belongs to our main thread (the main thread is the only one that owns any windows).
{
do_special_msg_filter = false; // Set default.
if (g_nFileDialogs) // v1.0.44.12: Also do the special Peek/msg filter below for FileSelectFile because testing shows that frequently-running timers disrupt the ability to double-click.
{
GetClassName(fore_window, wnd_class_name, sizeof(wnd_class_name));
do_special_msg_filter = !strcmp(wnd_class_name, "#32770"); // Due to checking g_nFileDialogs above, this means that this dialog is probably FileSelectFile rather than MsgBox/InputBox/FileSelectFolder (even if this guess is wrong, it seems fairly inconsequential to filter the messages since other pump beneath us on the call-stack will handle them ok).
}
if (!do_special_msg_filter && (focused_control = GetFocus()))
{
GetClassName(focused_control, wnd_class_name, sizeof(wnd_class_name));
do_special_msg_filter = !stricmp(wnd_class_name, "SysTreeView32"); // A TreeView owned by our thread has focus (includes FileSelectFolder's TreeView).
}
if (do_special_msg_filter)
{
// v1.0.44.12: Below now applies to FileSelectFile dialogs too (see reason above).
// v1.0.44.11: Since one of our thread's TreeViews has focus (even in FileSelectFolder), this
// section is a work-around for the fact that the TreeView's message pump (somewhere beneath
// us on the call stack) is apparently designed to process some mouse messages directly rather
// than receiving them indirectly (in its WindowProc) via our call to DispatchMessage() here
// in this pump. The symptoms of this issue are an inability of a user to reliably select
// items in a TreeView (the selection sometimes snaps back to the previously selected item),
// which can be reproduced by showing a TreeView while a 10ms script timer is running doing
// a trivial single line such as x=1.
// NOTE: This happens more often in FileSelectFolder dialogs, I believe because it's msg
// pump is ALWAYS running but that of a GUI TreeView is running only during mouse capture
// (i.e. when left/right button is down).
// This special handling for TreeView can someday be broadened so that focused control's
// class isn't checked: instead, could check whether left and/or right mouse button is
// logically down (which hasn't yet been tested). Or it could be broadened to include
// other system dialogs and/or common controls that have unusual processing in their
// message pumps -- processing that requires them to directly receive certain messages
// rather than having them dispatched directly to their WindowProc.
peek_was_done = true;
// Peek() must be used instead of Get(), and Peek() must be called more than once to handle
// the two ranges on either side of the mouse messages. But since it would be improper
// to process messages out of order (and might lead to side-effects), force the retrieval
// to be in chronological order by checking the timestamps of each Peek first message, and
// then fetching the one that's oldest (since it should be the one that's been waiting the
// longest and thus generally should be ahead of the other Peek's message in the queue):
#define PEEK1(mode) PeekMessage(&msg, NULL, 0, WM_MOUSEFIRST-1, mode) // Relies on the fact that WM_MOUSEFIRST < MSG_FILTER_MAX
#define PEEK2(mode) PeekMessage(&msg, NULL, WM_MOUSELAST+1, MSG_FILTER_MAX, mode)
if (!PEEK1(PM_NOREMOVE)) // Since no message in Peek1, safe to always use Peek2's (even if it has no message either).
peek_result = PEEK2(PM_REMOVE);
else // Peek1 has a message. So if Peek2 does too, compare their timestamps.
{
peek1_time = msg.time; // Save it due to overwrite in next line.
if (!PEEK2(PM_NOREMOVE)) // Since no message in Peek2, use Peek1's.
peek_result = PEEK1(PM_REMOVE);
else // Both Peek2 and Peek1 have a message waiting, so to break the tie, retrieve the oldest one.
{
// In case tickcount has wrapped, compare it the better way (must cast to int to avoid
// loss of negative values):
peek_result = ((int)(msg.time - peek1_time) > 0) // Peek2 is newer than Peek1, so treat peak1 as oldest and thus first in queue.
? PEEK1(PM_REMOVE) : PEEK2(PM_REMOVE);
}
}
}
}
if (!peek_was_done) // Since above didn't Peek(), fall back to doing the Peek with the standard filter.
peek_result = PeekMessage(&msg, NULL, 0, MSG_FILTER_MAX, PM_REMOVE);
if (!peek_result) // No more messages
{
// Since the Peek() didn't find any messages, our timeslice may have just been
// yielded if the CPU is under heavy load (update: this yielding effect is now diffcult
// to reproduce, so might be a thing of past service packs). If so, it seems best to count
// that as a "rest" so that 10ms script-timers will run closer to the desired frequency
// (see above comment for more details).
// These next few lines exact match the ones above, so keep them in sync:
tick_after = GetTickCount();
if (tick_after - tick_before > 3)
g_script.mLastScriptRest = tick_after;
// UPDATE: The section marked "OLD" below is apparently not quite true: although Peek() has been
// caught yielding our timeslice, it's now difficult to reproduce. Perhaps it doesn't consistently
// yield (maybe it depends on the relative priority of competing processes) and even when/if it
// does yield, it might somehow not as long or as good as Sleep(0). This is evidenced by the fact
// that some of my script's WinWaitClose's finish too quickly when the Sleep(0) is omitted after a
// Peek() that returned FALSE.
// OLD (mostly obsolete in light of above): It is not necessary to actually do the Sleep(0) when
// aSleepDuration == 0 because the most recent PeekMessage() has just yielded our prior timeslice.
// This is because when Peek() doesn't find any messages, it automatically behaves as though it
// did a Sleep(0).
if (aSleepDuration == 0 && !sleep0_was_done)
{
Sleep(0);
sleep0_was_done = true;
// Now start a new iteration of the loop that will see if we
// received any messages during the up-to-20ms delay (perhaps even more)
// that just occurred. It's done this way to minimize keyboard/mouse
// lag (if the hooks are installed) that will occur if any key or
// mouse events are generated during that 20ms. Note: It seems that
// the OS knows not to yield our timeslice twice in a row: once for
// the Sleep(0) above and once for the upcoming PeekMessage() (if that
// PeekMessage() finds no messages), so it does not seem necessary
// to check HIWORD(GetQueueStatus(QS_ALLEVENTS)). This has been confirmed
// via the following test, which shows that while BurnK6 (CPU maxing program)
// is foreground, a Sleep(0) really does a Sleep(60). But when it's not
// foreground, it only does a Sleep(20). This behavior is UNAFFECTED by
// the added presence of of a HIWORD(GetQueueStatus(QS_ALLEVENTS)) check here:
//SplashTextOn,,, xxx
//WinWait, xxx ; set last found window
//Loop
//{
// start = %a_tickcount%
// Sleep, 0
// elapsed = %a_tickcount%
// elapsed -= %start%
// WinSetTitle, %elapsed%
//}
continue;
}
// Otherwise: aSleepDuration is non-zero or we already did the Sleep(0)
// Macro notes:
// Must decrement prior to every RETURN to balance it.
// Do this prior to checking whether timer should be killed, below.
// Kill the timer only if we're about to return OK to the caller since the caller
// would still need the timer if FAIL was returned above. But don't kill it if
// there are any enabled timed subroutines, because the current policy it to keep
// the main timer always-on in those cases. UPDATE: Also avoid killing the timer
// if there are any script threads running. To do so might cause a problem such
// as in this example scenario: MsgSleep() is called for any reason with a delay
// large enough to require the timer. The timer is set. But a msg arrives that
// MsgSleep() dispatches to MainWindowProc(). If it's a hotkey or custom menu,
// MsgSleep() is called recursively with a delay of -1. But when it finishes via
// IsCycleComplete(), the timer would be wrongly killed because the underlying
// instance of MsgSleep still needs it. Above is even more wide-spread because if
// MsgSleep() is called recursively for any reason, even with a duration >10, it will
// wrongly kill the timer upon returning, in some cases. For example, if the first call to
// MsgSleep(-1) finds a hotkey or menu item msg, and executes the corresponding subroutine,
// that subroutine could easily call MsgSleep(10+) for any number of reasons, which
// would then kill the timer.
// Also require that aSleepDuration > 0 so that MainWindowProc()'s receipt of a
// WM_HOTKEY msg, to which it responds by turning on the main timer if the script
// is uninterruptible, is not defeated here. In other words, leave the timer on so
// that when the script becomes interruptible once again, the hotkey will take effect
// almost immediately rather than having to wait for the displayed dialog to be
// dismissed (if there is one).
// If timer doesn't exist, the new-thread-launch routine of MsgSleep() relies on this
// to turn it back on whenever a layer beneath us needs it. Since the timer
// is never killed while g_script.mTimerEnabledCount is >0, it shouldn't be necessary
// to check g_script.mTimerEnabledCount here.
//
// "we_turned_on_defer" is necessary to prevent us from turning it off if some other
// instance of MsgSleep beneath us on the calls stack turned it on. Only it should
// turn it off because it might still need the "true" value for further processing.
#define RETURN_FROM_MSGSLEEP \
{\
if (we_turned_on_defer)\
g_DeferMessagesForUnderlyingPump = false;\
if (this_layer_needs_timer)\
--g_nLayersNeedingTimer;\
if (g_MainTimerExists)\
{\
if (aSleepDuration > 0 && !g_nLayersNeedingTimer && !g_script.mTimerEnabledCount && !Hotkey::sJoyHotkeyCount)\
KILL_MAIN_TIMER \
}\
else if (g_nLayersNeedingTimer)\
SET_MAIN_TIMER \
return return_value;\
}
// IsCycleComplete should always return OK in this case. Also, was_interrupted
// will always be false because if this "aSleepDuration < 1" call really
// was interrupted, it would already have returned in the hotkey cases
// of the switch(). UPDATE: was_interrupted can now the hotkey case in
// the switch() doesn't return, relying on us to do it after making sure
// the queue is empty.
// The below is checked here rather than in IsCycleComplete() because
// that function is sometimes called more than once prior to returning
// (e.g. empty_the_queue_via_peek) and we only want this to be decremented once:
if (IsCycleComplete(aSleepDuration, start_time, allow_early_return)) // v1.0.44.11: IsCycleComplete() must be called for all modes, but now its return value is checked due to the new g_DeferMessagesForUnderlyingPump mode.
RETURN_FROM_MSGSLEEP
// Otherwise (since above didn't return) combined logic has ensured that all of the following are true:
// 1) aSleepDuration > 0
// 2) !empty_the_queue_via_peek
// 3) The above two combined with logic above means that g_DeferMessagesForUnderlyingPump==true.
Sleep(5); // Since Peek() didn't find a message, avoid maxing the CPU. This is a somewhat arbitrary value: the intent of a value below 10 is to avoid yielding more than one timeslice on all systems even if they have unusual timeslice sizes / system timers.
continue;
}
// else Peek() found a message, so process it below.
} // PeekMessage() vs. GetMessage()
// Since above didn't return or "continue", a message has been received that is eligible
// for further processing.
// For max. flexibility, it seems best to allow the message filter to have the first
// crack at looking at the message, before even TRANSLATE_AHK_MSG:
if (g_MsgMonitorCount && MsgMonitor(msg.hwnd, msg.message, msg.wParam, msg.lParam, &msg, msg_reply)) // Count is checked here to avoid function-call overhead.
{
continue; // MsgMonitor has returned "true", indicating that this message should be omitted from further processing.
// NOTE: Above does "continue" and ignores msg_reply. This is because testing shows that
// messages received via Get/PeekMessage() were always sent via PostMessage. If an
// another thread sends ours a message, MSDN implies that Get/PeekMessage() internally
// calls the message's WindowProc directly and sends the reply back to the other thread.
// That makes sense because it seems unlikely that DispatchMessage contains any means
// of replying to a message because it has no way of knowing whether the MSG struct
// arrived via Post vs. SendMessage.
}
// If this message might be for one of our GUI windows, check that before doing anything
// else with the message. This must be done first because some of the standard controls
// also use WM_USER messages, so we must not assume they're generic thread messages just
// because they're >= WM_USER. The exception is AHK_GUI_ACTION should always be handled
// here rather than by IsDialogMessage(). Note: sGuiCount is checked first to help
// performance, since all messages must come through this bottleneck.
if (GuiType::sGuiCount && msg.hwnd && msg.hwnd != g_hWnd && !(msg.message == AHK_GUI_ACTION || msg.message == AHK_USER_MENU))
{
if (msg.message == WM_KEYDOWN)
{
// Relies heavily on short-circuit boolean order:
if ( (msg.wParam == VK_NEXT || msg.wParam == VK_PRIOR || msg.wParam == VK_TAB
|| msg.wParam == VK_LEFT || msg.wParam == VK_RIGHT)
&& (focused_control = GetFocus()) && (focused_parent = GetNonChildParent(focused_control))
&& (pgui = GuiType::FindGui(focused_parent)) && pgui->mTabControlCount
&& (pcontrol = pgui->FindControl(focused_control)) && pcontrol->type != GUI_CONTROL_HOTKEY )
{
ptab_control = NULL; // Set default.
if (pcontrol->type == GUI_CONTROL_TAB) // The focused control is a tab control itself.
{
ptab_control = pcontrol;
// For the below, note that Alt-left and Alt-right are automatically excluded,
// as desired, since any key modified only by alt would be WM_SYSKEYDOWN vs. WM_KEYDOWN.
if (msg.wParam == VK_LEFT || msg.wParam == VK_RIGHT)
{
pgui->SelectAdjacentTab(*ptab_control, msg.wParam == VK_RIGHT, false, false);
// Pass false for both the above since that's the whole point of having arrow
// keys handled separately from the below: Focus should stay on the tabs
// rather than jumping to the first control of the tab, it focus should not
// wrap around to the beginning or end (to conform to standard behavior for
// arrow keys).
continue; // Suppress this key even if the above failed (probably impossible in this case).
}
//else fall through to the next part.
}
// If focus is in a multiline edit control, don't act upon Control-Tab (and
// shift-control-tab -> for simplicity & consistency) since Control-Tab is a special
// keystroke that inserts a literal tab in the edit control:
if ( msg.wParam != VK_LEFT && msg.wParam != VK_RIGHT
&& (GetKeyState(VK_CONTROL) & 0x8000) // Even if other modifiers are down, it still qualifies. Use GetKeyState() vs. GetAsyncKeyState() because the former's definition is more suitable.
&& (msg.wParam != VK_TAB || pcontrol->type != GUI_CONTROL_EDIT
|| !(GetWindowLong(pcontrol->hwnd, GWL_STYLE) & ES_MULTILINE)) )
{
// If ptab_control wasn't determined above, check if focused control is owned by a tab control:
if (!ptab_control && !(ptab_control = pgui->FindTabControl(pcontrol->tab_control_index)) )
// Fall back to the first tab control (for consistency & simplicty, seems best
// to always use the first rather than something fancier such as "nearest in z-order".
ptab_control = pgui->FindTabControl(0);
if (ptab_control)
{
pgui->SelectAdjacentTab(*ptab_control
, msg.wParam == VK_NEXT || (msg.wParam == VK_TAB && !(GetKeyState(VK_SHIFT) & 0x8000)) // Use GetKeyState() vs. GetAsyncKeyState() because the former's definition is more suitable.
, true, true);
// Update to the below: Must suppress the tab key at least, to prevent it
// from navigating *and* changing the tab. And since this one is suppressed,
// might as well suppress the others for consistency.
// Older: Since WM_KEYUP is not handled/suppressed here, it seems best not to
// suppress this WM_KEYDOWN either (it should do nothing in this case
// anyway, but for balance this seems best): Fall through to the next section.
continue;
}
//else fall through to the below.
}
//else fall through to the below.
} // Interception of keystrokes for navigation in tab control.
// v1.0.34: Fix for the fact that a multiline edit control will send WM_CLOSE to its parent
// when user presses ESC while it has focus. The following check is similar to the block's above.
// The alternative to this approach would have been to override the edit control's WindowProc,
// but the following seemed to be less code. Although this fix is only necessary for multiline
// edits, its done for all edits since it doesn't do any harm. In addition, there is no need to
// check what modifiers are down because we never receive the keystroke for Ctrl-Esc and Alt-Esc
// (the OS handles those beforehand) and both Win-Esc and Shift-Esc are identical to a naked Esc
// inside an edit. The following check relies heavily on short-circuit eval. order.
if ( (msg.wParam == VK_ESCAPE || msg.wParam == VK_TAB // v1.0.38.03: Added VK_TAB handling for "WantTab".
|| (msg.wParam == 'A' && (GetKeyState(VK_CONTROL) & 0x8000))) // v1.0.44: Added support for "WantCtrlA".
&& (focused_control = GetFocus()) && (focused_parent = GetNonChildParent(focused_control))
&& (pgui = GuiType::FindGui(focused_parent)) && (pcontrol = pgui->FindControl(focused_control))
&& pcontrol->type == GUI_CONTROL_EDIT)
{
switch(msg.wParam)
{
case 'A': // v1.0.44: Support for Ctrl-A to select all text.
if (!(pcontrol->attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT)) // i.e. presence of AltSubmit bit DISABLES Ctrl-A handling.
{
SendMessage(pcontrol->hwnd, EM_SETSEL, 0, -1); // Select all text.
continue; // Omit this keystroke from any further processing.
}
break;
case VK_ESCAPE:
pgui->Escape();
continue; // Omit this keystroke from any further processing.
default: // VK_TAB
if (pcontrol->attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) // It has the "WantTab" property.
{
// For flexibility, do this even for single-line edit controls, though in that
// case the tab keystroke will produce an "empty box" character.
// Strangely, if a message pump other than this one (MsgSleep) is running,
// such as that of a MsgBox, "WantTab" is already in effect unconditionally,
// perhaps because MsgBox and others respond to WM_GETDLGCODE with DLGC_WANTTAB.
SendMessage(pcontrol->hwnd, EM_REPLACESEL, TRUE, (LPARAM)"\t");
continue; // Omit this keystroke from any further processing.
}
} // switch()
}
if (GuiType::sTreeWithEditInProgress)
{
if (msg.wParam == VK_RETURN)
{
TreeView_EndEditLabelNow(GuiType::sTreeWithEditInProgress, FALSE); // Save changes to label/text.
continue;
}
else if (msg.wParam == VK_ESCAPE)
{
TreeView_EndEditLabelNow(GuiType::sTreeWithEditInProgress, TRUE); // Cancel without saving.
continue;
}
}
} // if (msg.message == WM_KEYDOWN)
for (i = 0, gui_count = 0, msg_was_handled = false; i < MAX_GUI_WINDOWS; ++i)
{
// Note: indications are that IsDialogMessage() should not be called with NULL as
// its first parameter (perhaps as an attempt to get allow dialogs owned by our
// thread to be handled at once). Although it might work on some versions of Windows,
// it's undocumented and shouldn't be relied on.
// Also, can't call IsDialogMessage against msg.hwnd because that is not a complete
// solution: at the very least, tab key navigation will not work in GUI windows.
// There are probably other side-effects as well.
if (g_gui[i])
{
if (g_gui[i]->mHwnd)
{
g.CalledByIsDialogMessageOrDispatch = true;
g.CalledByIsDialogMessageOrDispatchMsg = msg.message; // Added in v1.0.44.11 because it's known that IsDialogMessage can change the message number (e.g. WM_KEYDOWN->WM_NOTIFY for UpDowns)
if (IsDialogMessage(g_gui[i]->mHwnd, &msg))
{
msg_was_handled = true;
g.CalledByIsDialogMessageOrDispatch = false;
break;
}
g.CalledByIsDialogMessageOrDispatch = false;
}
if (GuiType::sGuiCount == ++gui_count) // No need to keep searching.
break;
}
}
if (msg_was_handled) // This message was handled by IsDialogMessage() above.
continue; // Continue with the main message loop.
}
// v1.0.44: There's no reason to call TRANSLATE_AHK_MSG here because all WM_COMMNOTIFY messages
// are sent to g_hWnd. Thus, our call to DispatchMessage() later below will route such messages to
// MainWindowProc(), which will then call TRANSLATE_AHK_MSG().
//TRANSLATE_AHK_MSG(msg.message, msg.wParam)
switch(msg.message)
{
// MSG_FILTER_MAX should prevent us from receiving this first group of messages whenever g_AllowInterruption or
// g.AllowThreadToBeInterrupted is false.
case AHK_HOOK_HOTKEY: // Sent from this app's keyboard or mouse hook.
case AHK_HOTSTRING: // Sent from keybd hook to activate a non-auto-replace hotstring.
case AHK_CLIPBOARD_CHANGE:
// This extra handling is present because common controls and perhaps other OS features tend
// to use WM_USER+NN messages, a practice that will probably be even more common in the future.
// To cut down on message conflicts, dispatch such messages whenever their HWND isn't a what
// it should be for a message our own code generated. That way, the target control (if any)
// will still get the msg.
if (msg.hwnd && msg.hwnd != g_hWnd) // v1.0.44: It's wasn't sent by our code; perhaps by a common control's code.
break; // Dispatch it vs. discarding it, in case it's for a control.
//ELSE FALL THROUGH:
case AHK_GUI_ACTION: // The user pressed a button on a GUI window, or some other actionable event. Listed before the below for performance.
case WM_HOTKEY: // As a result of this app having previously called RegisterHotkey(), or from TriggerJoyHotkeys().
case AHK_USER_MENU: // The user selected a custom menu item.
hdrop_to_free = NULL; // Set default for this message's processing (simplifies code).
switch(msg.message)
{
case AHK_GUI_ACTION: // Listed first for performance.
// Assume that it is possible that this message's GUI window has been destroyed
// (and maybe even recreated) since the time the msg was posted. If this can happen,
// that's another reason for finding which GUI this control is associate with (it also
// needs to be found so that we can call the correct GUI window object to perform
// the action):
if ( !(pgui = GuiType::FindGui(msg.hwnd)) ) // No associated GUI object, so ignore this event.
// v1.0.44: Dispatch vs. continue/discard since it's probably for a common control
// whose msg number happens to be AHK_GUI_ACTION. Do this *only* when HWND isn't recognized,
// not when msg content is inavalid, because dispatching a msg whose HWND is one of our own
// GUI windows might cause GuiWindowProc to fwd it back to us, creating an infinite loop.
goto break_out_of_main_switch; // Goto seems preferably in this case for code size & performance.
gui_index = pgui->mWindowIndex; // Stored in case ExecUntil() performs "Gui Destroy" further below.
gui_event_info = (DWORD)msg.lParam;
gui_action = LOWORD(msg.wParam);
gui_control_index = HIWORD(msg.wParam); // Caller has set it to NO_CONTROL_INDEX if it isn't applicable.
if (gui_action == GUI_EVENT_RESIZE) // This section be done after above but before pcontrol below.
{
gui_size = gui_event_info; // Temp storage until the "g" struct becomes available for the new thread.
gui_event_info = gui_control_index; // SizeType is stored in index in this case.
gui_control_index = NO_CONTROL_INDEX;
}
// Below relies on the GUI_EVENT_RESIZE section above having been done:
pcontrol = gui_control_index < pgui->mControlCount ? pgui->mControl + gui_control_index : NULL; // Set for use in other places below.
pgui_label_is_running = NULL; // Set default (in cases other than AHK_GUI_ACTION it is not used, so not initialized).
event_is_control_generated = false; // Set default.
switch(gui_action)
{
case GUI_EVENT_RESIZE: // This is the signal to run the window's OnEscape label. Listed first for performance.
if ( !(gui_label = pgui->mLabelForSize) ) // In case it became NULL since the msg was posted.
continue;
pgui_label_is_running = &pgui->mLabelForSizeIsRunning;
break;
case GUI_EVENT_CLOSE: // This is the signal to run the window's OnClose label.
if ( !(gui_label = pgui->mLabelForClose) ) // In case it became NULL since the msg was posted.
continue;
pgui_label_is_running = &pgui->mLabelForCloseIsRunning;
break;
case GUI_EVENT_ESCAPE: // This is the signal to run the window's OnEscape label.
if ( !(gui_label = pgui->mLabelForEscape) ) // In case it became NULL since the msg was posted.
continue;
pgui_label_is_running = &pgui->mLabelForEscapeIsRunning;
break;
case GUI_EVENT_CONTEXTMENU:
if ( !(gui_label = pgui->mLabelForContextMenu) ) // In case it became NULL since the msg was posted.
continue;
// UPDATE: Must allow multiple threads because otherwise the user cannot right-click twice
// consecutively (the second click is blocked because the menu is still displayed at the
// instant of the click. The following older reason is probably not entirely correct because
// the display of a popup menu via "Menu, MyMenu, Show" will spin off a new thread if the
// user selects an item in the menu:
// Unlike most other Gui labels, it seems best by default to allow GuiContextMenu to be
// launched multiple times so that multiple items in the menu can be running simultaneously
// as separate threads. Therefore, leave pgui_label_is_running at its default of NULL.
break;
case GUI_EVENT_DROPFILES: // This is the signal to run the window's DropFiles label.
hdrop_to_free = pgui->mHdrop; // This variable simplifies the code further below.
if ( !(gui_label = pgui->mLabelForDropFiles) // In case it became NULL since the msg was posted.
|| !hdrop_to_free // Checked just in case, so that the below can query it.
|| !(gui_event_info = DragQueryFile(hdrop_to_free, 0xFFFFFFFF, NULL, 0)) ) // Probably impossible, but if it ever can happen, seems best to ignore it.
{
if (hdrop_to_free) // Checked again in case short-circuit boolean above never checked it.
{
DragFinish(hdrop_to_free); // Since the drop-thread will not be launched, free the memory.
pgui->mHdrop = NULL; // Indicate that this GUI window is ready for another drop.
}
continue;
}
// It is not necessary to check if the label is running in this case because
// the caller who posted this message to us has ensured that it's the only message in the queue
// or in progress (by virtue of pgui->mHdrop being NULL at the time the message was posted).
// Therefore, leave pgui_label_is_running at its default of NULL.
break;
default: // This is an action from a particular control in the GUI window.
if (!pcontrol) // gui_control_index was beyond the quantity of controls, possibly due to parent window having been destroyed since the msg was sent (or bogus msg).
continue; // Discarding an invalid message here is relied upon both other sections below.
if ( !(gui_label = pcontrol->jump_to_label) )
{
// On if there's no label is the implicit action considered.
if (pcontrol->attrib & GUI_CONTROL_ATTRIB_IMPLICIT_CANCEL)
pgui->Cancel();
continue; // Fully handled by the above; or there was no label.
// This event might lack both a label and an action if its control was changed to be
// non-actionable since the time the msg was posted.
}
// Above has confirmed it has a label, so now it's valid to check if that label is already running.
// It seems best by default not to allow multiple threads for the same control.
// Such events are discarded because it seems likely that most script designers
// would want to see the effects of faulty design (e.g. long running timers or
// hotkeys that interrupt gui threads) rather than having events for later,
// when they might suddenly take effect unexpectedly:
if (pcontrol->attrib & GUI_CONTROL_ATTRIB_LABEL_IS_RUNNING)
continue;
event_is_control_generated = true; // As opposed to a drag-and-drop or context-menu event that targets a specific control.
// And leave pgui_label_is_running at its default of NULL because it doesn't apply to these.
} // switch(gui_action)
type_of_first_line = gui_label->mJumpToLine->mActionType; // Above would already have discarded this message if it there was no label.
break; // case AHK_GUI_ACTION
case AHK_USER_MENU: // user-defined menu item
if ( !(menu_item = g_script.FindMenuItemByID((UINT)msg.lParam)) ) // Item not found.
continue; // ignore the msg
// And just in case a menu item that lacks a label (such as a separator) is ever
// somehow selected (perhaps via someone sending direct messages to us, bypassing
// the menu):
if (!menu_item->mLabel)
continue;
type_of_first_line = menu_item->mLabel->mJumpToLine->mActionType;
break;
case AHK_HOTSTRING:
if (msg.wParam >= Hotstring::sHotstringCount) // Invalid hotstring ID (perhaps spoofed by external app)
continue; // Do nothing.
hs = Hotstring::shs[msg.wParam]; // For performance and convenience.
if (hs->mHotCriterion)
{
// For details, see comments in the hotkey section of this switch().
if ( !(criterion_found_hwnd = HotCriterionAllowsFiring(hs->mHotCriterion, hs->mHotWinTitle, hs->mHotWinText)) )
// Hotstring is no longer eligible to fire even though it was when the hook sent us
// the message. Abort the firing even though the hook may have already started
// executing the hotstring by suppressing the final end-character or other actions.
// It seems preferable to abort midway through the execution than to continue sending
// keystrokes to the wrong window, or when the hotstring has become suspended.
continue;
// For details, see comments in the hotkey section of this switch().
if (!(hs->mHotCriterion == HOT_IF_ACTIVE || hs->mHotCriterion == HOT_IF_EXIST))
criterion_found_hwnd = NULL; // For "NONE" and "NOT", there is no last found window.
}
else // No criterion, so it's a global hotstring. It can always fire, but it has no "last found window".
criterion_found_hwnd = NULL;
// Do a simple replacement for the hotstring if that's all that's called for.
// Don't create a new quasi-thread or any of that other complexity done further
// below. But also do the backspacing (if specified) for a non-autoreplace hotstring,
// even if it can't launch due to MaxThreads, MaxThreadsPerHotkey, or some other reason:
hs->DoReplace(msg.lParam); // Does only the backspacing if it's not an auto-replace hotstring.
if (*hs->mReplacement) // Fully handled by the above; i.e. it's an auto-replace hotstring.
continue;
// Otherwise, continue on and let a new thread be created to handle this hotstring.
// Since this isn't an auto-replace hotstring, set this value to support
// the built-in variable A_EndChar:
g_script.mEndChar = (char)LOWORD(msg.lParam);
type_of_first_line = hs->mJumpToLabel->mJumpToLine->mActionType;
break;
case AHK_CLIPBOARD_CHANGE: // Due to the presence of an OnClipboardChange label in the script.
// Caller has ensured that mOnClipboardChangeLabel is a non-NULL, valid pointer.
type_of_first_line = g_script.mOnClipboardChangeLabel->mJumpToLine->mActionType;
break;
default: // hotkey
if (msg.wParam >= Hotkey::sHotkeyCount) // Invalid hotkey ID.
continue;
hk = Hotkey::shk[msg.wParam];
// Check if criterion allows firing.
// For maintainability, this is done here rather than a little further down
// past the MAX_THREADS_LIMIT and thread-priority checks. Those checks hardly
// ever abort a hotkey launch anyway.
//
// If message is WM_HOTKEY, it's either:
// 1) A joystick hotkey from TriggerJoyHotkeys(), in which case the lParam is ignored.
// 2) A hotkey message sent by the OS, in which case lParam contains currently-unused info set by the OS.
//
// An incoming WM_HOTKEY can be subject to #IfWin at this stage under the following conditions:
// 1) Joystick hotkey, because it relies on us to do the check so that the check is done only
// once rather than twice.
// 2) Win9x's support for #IfWin, which never uses the hook but instead simply does nothing if
// none of the hotkey's criteria is satisfied.
// 3) #IfWin keybd hotkeys that were made non-hook because they have a non-suspended, global variant.
//
// If message is AHK_HOOK_HOTKEY:
// Rather than having the hook pass the qualified variant to us, it seems preferable
// to search through all the criteria again and rediscover it. This is because conditions
// may have changed since the message was posted, and although the hotkey might still be
// eligible for firing, a different variant might now be called for (e.g. due to a change
// in the active window). Since most criteria hotkeys have at most only a few criteria,
// and since most such criteria are #IfWinActive rather than Exist, the performance will
// typically not be reduced much at all. Futhermore, trading performance for greater
// reliability seems worth it in this case.
//
// The inefficiency of calling HotCriterionAllowsFiring() twice for each hotkey --
// once in the hook and again here -- seems justifed for the following reasons:
// - It only happens twice if the hotkey a hook hotkey (multi-variant keyboard hotkeys
// that have a global variant are usually non-hook, even on NT/2k/XP).
// - The hook avoids doing its first check of WinActive/Exist if it sees that the hotkey
// has a non-suspended, global variant. That way, hotkeys that are hook-hotkeys for
// reasons other than #IfWin (such as mouse, overriding OS hotkeys, or hotkeys
// that are too fancy for RegisterHotkey) will not have to do the check twice.
// - It provides the ability to set the last-found-window for #IfWinActive/Exist
// (though it's not needed for the "Not" counterparts). This HWND could be passed
// via the message, but that would require malloc-there and free-here, and might
// result in memory leaks if its ever possible for messages to get discarded by the OS.
// - It allows hotkeys that were eligible for firing at the time the message was
// posted but that have since become ineligible to be aborted. This seems like a
// good precaution for most users/situations because such hotkey subroutines will
// often assume (for scripting simplicity) that the specified window is active or
// exists when the subroutine executes its first line.
// - Most criterion hotkeys use #IfWinActive, which is a very fast call. Also, although
// WinText and/or "SetTitleMatchMode Slow" slow down window searches, those are rarely
// used too.
if ( !(variant = hk->CriterionAllowsFiring(&criterion_found_hwnd)) )
continue; // No criterion is eligible, so ignore this hotkey event (see other comments).
// If this is AHK_HOOK_HOTKEY, criterion was eligible at time message was posted,
// but not now. Seems best to abort (see other comments).
// Now that above has ensured variant is non-NULL:
if (!(variant->mHotCriterion == HOT_IF_ACTIVE || variant->mHotCriterion == HOT_IF_EXIST))
criterion_found_hwnd = NULL; // For "NONE" and "NOT", there is no last found window.
type_of_first_line = variant->mJumpToLabel->mJumpToLine->mActionType;
} // switch(msg.message)
if (g_nThreads >= g_MaxThreadsTotal)
{
// The below allows 1 thread beyond the limit in case the script's configured
// #MaxThreads is exactly equal to the absolute limit. This is because we want
// subroutines whose first line is something like ExitApp to take effect even
// when we're at the absolute limit:
if (g_nThreads > MAX_THREADS_LIMIT || !ACT_IS_ALWAYS_ALLOWED(type_of_first_line))
{
// Allow only a limited number of recursion levels to avoid any chance of
// stack overflow. So ignore this message. Later, can devise some way
// to support "queuing up" these launch-thread events for use later when
// there is "room" to run them, but that might cause complications because
// in some cases, the user didn't intend to hit the key twice (e.g. due to
// "fat fingers") and would have preferred to have it ignored. Doing such
// might also make "infinite key loops" harder to catch because the rate
// of incoming hotkeys would be slowed down to prevent the subroutines from
// running concurrently.
if (hdrop_to_free) // This is only non-NULL when pgui is non-NULL and gui_action==GUI_EVENT_DROPFILES
{
DragFinish(hdrop_to_free); // Since the drop-thread will not be launched, free the memory.
pgui->mHdrop = NULL; // Indicate that this GUI window is ready for another drop.
}
continue;
}
// If the above "continued", it seems best not to re-queue/buffer the key since
// it might be a while before the number of threads drops back below the limit.
}
// Find out the new thread's priority will be so that it can be checked against the current thread's:
switch(msg.message)
{
case AHK_GUI_ACTION: // Listed first for performance.
if (pgui_label_is_running && *pgui_label_is_running) // GuiSize/Close/Escape/etc. These subroutines are currently limited to one thread each.
continue; // hdrop_to_free: Not necessary to check it because it's always NULL when pgui_label_is_running is non-NULL.
//else the check wasn't needed because it was done elsewhere (GUI_EVENT_DROPFILES) or the
// action is not thread-restricted (GUI_EVENT_CONTEXTMENU).
// And since control-specific events were already checked for "already running" higher above, this
// event is now eligible to start a new thread.
priority = 0; // Always use default for now.
break;
case AHK_USER_MENU: // user-defined menu item
// Ignore/discard a hotkey or custom menu item event if the current thread's priority
// is higher than it's:
priority = menu_item->mPriority;
// Below: the menu type is passed with the message so that its value will be in sync
// with the timestamp of the message (in case this message has been stuck in the
// queue for a long time):
if (msg.wParam < MAX_GUI_WINDOWS) // Poster specified that this menu item was from a gui's menu bar (since wParam is unsigned, any incoming -1 is seen as greater than max).
{
// msg.wParam is the index rather than a pointer to avoid any chance of problems with
// a gui object or its window having been destroyed while the msg was waiting in the queue.
if (!(pgui = g_gui[msg.wParam]) // Not a GUI's menu bar item...
&& msg.hwnd && msg.hwnd != g_hWnd) // ...and not a script menu item.
goto break_out_of_main_switch; // See "goto break_out_of_main_switch" higher above for complete explanation.
}
else
pgui = NULL; // Set for use in later sections.
break;
case AHK_HOTSTRING:
priority = hs->mPriority;
break;
case AHK_CLIPBOARD_CHANGE: // Due to the presence of an OnClipboardChange label in the script.
if (g_script.mOnClipboardChangeIsRunning)
continue;
priority = 0; // Always use default for now.
break;
default: // hotkey
// Due to the key-repeat feature and the fact that most scripts use a value of 1
// for their #MaxThreadsPerHotkey, this check will often help average performance
// by avoiding a lot of unncessary overhead that would otherwise occur:
if (!hk->PerformIsAllowed(*variant))
{
// The key is buffered in this case to boost the responsiveness of hotkeys
// that are being held down by the user to activate the keyboard's key-repeat
// feature. This way, there will always be one extra event waiting in the queue,
// which will be fired almost the instant the previous iteration of the subroutine
// finishes (this above descript applies only when MaxThreadsPerHotkey is 1,
// which it usually is).
hk->RunAgainAfterFinished(*variant); // Wheel notch count (g.EventInfo below) should be okay because subsequent launches reuse the same thread attributes to do the repeats.
continue;
}
priority = variant->mPriority;
}
// Discard the event if it's priority is lower than that of the current thread:
if (priority < g.Priority)
{
if (hdrop_to_free) // This is only non-NULL when pgui is non-NULL and gui_action==GUI_EVENT_DROPFILES
{
DragFinish(hdrop_to_free); // Since the drop-thread will not be launched, free the memory.
pgui->mHdrop = NULL; // Indicate that this GUI window is ready for another drop.
}
continue;
}
// Now it is certain that the new thread will be launched, so set everything up.
// Perform the new thread's subroutine:
return_value = true; // We will return this value to indicate that we launched at least one new thread.
// Always kill the main timer, for performance reasons and for simplicity of design,
// prior to embarking on new subroutine whose duration may be long (e.g. if BatchLines
// is very high or infinite, the called subroutine may not return to us for seconds,
// minutes, or more; during which time we don't want the timer running because it will
// only fill up the queue with WM_TIMER messages and thus hurt performance).
// UPDATE: But don't kill it if it should be always-on to support the existence of
// at least one enabled timed subroutine or joystick hotkey:
if (!g_script.mTimerEnabledCount && !Hotkey::sJoyHotkeyCount)
KILL_MAIN_TIMER;
switch(msg.message)
{
case AHK_GUI_ACTION: // Listed first for performance.
case AHK_CLIPBOARD_CHANGE:
break; // Do nothing at this stage.
case AHK_USER_MENU: // user-defined menu item
// Safer to make a full copies than point to something potentially volatile.
strlcpy(g_script.mThisMenuItemName, menu_item->mName, sizeof(g_script.mThisMenuItemName));
strlcpy(g_script.mThisMenuName, menu_item->mMenu->mName, sizeof(g_script.mThisMenuName));
break;
default: // hotkey or hotstring
// Just prior to launching the hotkey, update these values to support built-in
// variables such as A_TimeSincePriorHotkey:
g_script.mPriorHotkeyName = g_script.mThisHotkeyName;
g_script.mPriorHotkeyStartTime = g_script.mThisHotkeyStartTime;
// Unlike hotkeys -- which can have a name independent of their label by being created or updated
// with the HOTKEY command -- a hot string's unique name is always its label since that includes
// the options that distinguish between (for example) :c:ahk:: and ::ahk::
g_script.mThisHotkeyName = (msg.message == AHK_HOTSTRING) ? hs->mJumpToLabel->mName : hk->mName;
g_script.mThisHotkeyStartTime = GetTickCount(); // Fixed for v1.0.35.10 to not happen for GUI threads.
}
// Also save the ErrorLevel of the subroutine that's about to be suspended.
// Current limitation: If the user put something big in ErrorLevel (very unlikely
// given its nature, but allowed) it will be truncated by this, if too large.
// Also: Don't use var->Get() because need better control over the size:
strlcpy(ErrorLevel_saved, g_ErrorLevel->Contents(), sizeof(ErrorLevel_saved));
// v1.0.37.06: The following must be done regardless of aMode because the idle thread is now
// resumed via ResumeUnderlyingThread():
CopyMemory(&global_saved, &g, sizeof(global_struct));
// Make every newly launched subroutine start off with the global default values that
// the user set up in the auto-execute part of the script (e.g. KeyDelay, WinDelay, etc.).
// However, we do not set ErrorLevel to anything special here (except for GUI threads, later
// below) because it's more flexible that way (i.e. the user may want one hotkey subroutine
// to use the value of ErrorLevel set by another):
InitNewThread(priority, false, true, type_of_first_line);
// Fix for v1.0.37.06: Must do the following only after InitNewThread() has updated