• 大小: 0.98M
    文件类型: .rar
    金币: 1
    下载: 0 次
    发布日期: 2021-02-01
  • 语言: C/C++
  • 标签: 按键精灵  

资源简介

按键精灵之前版本


核心源码:

/*
AutoHotkey

Copyright 2003-2009 Chris Mallett (support@autohotkey.com)

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
*/

#include "stdafx.h" // pre-compiled headers
#include "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:
	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 or there are joystick hotkeys.
		// 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)
				// Notes for the macro further below:
				// 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).
				//
				// "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 (aSleepDuration > 0 && !g_nLayersNeedingTimer && !g_script.mTimerEnabledCount && !Hotkey::sJoyHotkeyCount)\
							KILL_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 g_MaxThreadsTotal 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_EMERGENCY // To avoid array overflow, this limit must by obeyed except where otherwise documented.
					|| !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.

			// UPDATE v1.0.48: The main timer is no longer killed because testing shows that
			// SetTimer() and/or KillTimer() are relatively slow calls.  Thus it is likely that
			// on average, it's better to receive some unnecessary WM_TIMER messages (especially
			// since WM_TIMER processing is fast when there's nothing to do) than it is to
			// KILL and then RE-SET the main timer for every new thread (especially rapid-fire
			// threads like some GUI threads can be).  This also makes the thread types that are
			// handled here more like other threads such as timers, callbacks, and OnMessage.
			// Also, not doing the following KILL_MAIN_TIMER avoids have to check whether
			// SET_MAIN_TIMER is needed in two places further below (e.g. RETURN_FROM_MSGSLEEP).
			// OLDER COMMENTS:
			// 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));
			// 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);
			global_struct &g = *::g; // ONLY AFTER above is it safe to "lock in". Reduces code size a bit (31 bytes currently) and may improve performance.  Eclipsing ::g with local g makes compiler remind/enforce the use of the right one.

			// Do this nearly last, right before launching the thread:
			// It seems best to reset mLinesExecutedThisCycle unconditionally (now done by InitNewThread),
			// because the user has pressed a hotkey or selected a custom menu item, so would expect
			// maximum responsiveness (e.g. in a game where split second timing can matter) rather than
			// the risk that a "rest" will be done immediately by ExecUntil() just because
			// mLinesExecutedThisCycle happens to be large some prior subroutine.  The same applies to
			// mLastScriptRest, which is why that is reset also:
			g_script.mLastScriptRest = g_script.mLastPeekTime = GetTickCount();
			// v1.0.38.04: The above now resets mLastPeekTime too to reduce situations in which a thread
			// doesn't even run one line before being interrupted by another thread.  Here's how that would
			// happen: ExecUntil() would see that a Peek() is due and call PeekMessage().  The Peek() will
			// yield if we have no messages and the CPU is under heavy load, and thus the script might not
			// get another timeslice for 20ms (or even longer if there is more than one other needy process).
			// Even if the Peek() doesn't yield (i.e. we have messages), those messages might take a long time
			// to process (such as WM_PAINT) even though the script is uninterruptible.  Either way, when the
			// Peek-check completes, a long time might have passed, and the thread might now be interruptible
			// due to the interruptible-timer having expired (which is probably possible only in the no-yield
			// scenario above, since in the case of yield, ExecUntil wouldn't check messages again after the
			// yield).  Thus, the Peek-check's MsgSleep() might launch an interrupting thread before the prior
			// thread had a chance to execute even one line.  Resetting mLastPeekTime above should alleviate that,
			// perhaps even completely resolve it due to the way tickcounts tend not to change early on in
			// a timeslice (perhaps because timeslices fall exactly upon tick-count boundaries).  If it doesn't
			// completely resolve it, mLastPeekTime could instead be set to zero as a special value that
			// ExecUntil recognizes to do the following processing, but this processing reduces performance
			// by 2.5% in a simple addition-loop benchmark:
			//if (g_script.mL
						  

资源截图

代码片段和文件信息

 属性            大小     日期    时间   名称
----------- ---------  ---------- -----  ----

     文件    1077929  2009-04-14 19:22  AutoHotkey_source.exe

----------- ---------  ---------- -----  ----

              1077929                    1


评论

共有 条评论