资源简介
按键精灵之前版本
核心源码:
/* 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++给图片增加水印
评论
共有 条评论