资源简介
按键精灵之前版本
核心源码:
/*
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
----------- --------- ---------- ----- ----
文件 1077929 2009-04-14 19:22 AutoHotkey_source.exe
----------- --------- ---------- ----- ----
1077929 1
- 上一篇:c++自定义对话框
- 下一篇:c++给图片增加水印
评论
共有 条评论