home *** CD-ROM | disk | FTP | other *** search
/ Computer Shopper 242 / Issue 242 - April 2008 - DPCS0408DVD.ISO / Open Source / AutoHotKey / Source / AutoHotkey104705_source.exe / source / window.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2007-05-24  |  95.0 KB  |  1,841 lines

  1. /*
  2. AutoHotkey
  3.  
  4. Copyright 2003-2007 Chris Mallett (support@autohotkey.com)
  5.  
  6. This program is free software; you can redistribute it and/or
  7. modify it under the terms of the GNU General Public License
  8. as published by the Free Software Foundation; either version 2
  9. of the License, or (at your option) any later version.
  10.  
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15. */
  16.  
  17. #include "stdafx.h" // pre-compiled headers
  18. #include "window.h"
  19. #include "util.h" // for strlcpy()
  20. #include "application.h" // for MsgSleep()
  21.  
  22.  
  23. HWND WinActivate(global_struct &aSettings, char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText
  24.     , bool aFindLastMatch, HWND aAlreadyVisited[], int aAlreadyVisitedCount)
  25. {
  26.     // If window is already active, be sure to leave it that way rather than activating some
  27.     // other window that may match title & text also.  NOTE: An explicit check is done
  28.     // for this rather than just relying on EnumWindows() to obey the z-order because
  29.     // EnumWindows() is *not* guaranteed to enumerate windows in z-order, thus the currently
  30.     // active window, even if it's an exact match, might become overlapped by another matching
  31.     // window.  Also, use the USE_FOREGROUND_WINDOW vs. IF_USE_FOREGROUND_WINDOW macro for
  32.     // this because the active window can sometimes be NULL (i.e. if it's a hidden window
  33.     // and DetectHiddenWindows is off):
  34.     HWND target_window;
  35.     if (USE_FOREGROUND_WINDOW(aTitle, aText, aExcludeTitle, aExcludeText))
  36.     {
  37.         // User asked us to activate the "active" window, which by definition already is.
  38.         // However, if the active (foreground) window is hidden and DetectHiddenWindows is
  39.         // off, the below will set target_window to be NULL, which seems like the most
  40.         // consistent result to use:
  41.         SET_TARGET_TO_ALLOWABLE_FOREGROUND(aSettings.DetectHiddenWindows)
  42.         return target_window;
  43.     }
  44.  
  45.     if (!aFindLastMatch && !*aTitle && !*aText && !*aExcludeTitle && !*aExcludeText)
  46.     {
  47.         // User passed no params, so use the window most recently found by WinExist():
  48.         if (   !(target_window = GetValidLastUsedWindow(aSettings))   )
  49.             return NULL;
  50.     }
  51.     else
  52.     {
  53.         /*
  54.         // Might not help avg. perfomance any?
  55.         if (!aFindLastMatch) // Else even if the windows is already active, we want the bottomost one.
  56.             if (hwnd = WinActive(aTitle, aText, aExcludeTitle, aExcludeText)) // Already active.
  57.                 return target_window;
  58.         */
  59.         // Don't activate in this case, because the top-most window might be an
  60.         // always-on-top but not-meant-to-be-activated window such as AutoIt's
  61.         // splash text:
  62.         if (   !(target_window = WinExist(aSettings, aTitle, aText, aExcludeTitle, aExcludeText, aFindLastMatch
  63.             , false, aAlreadyVisited, aAlreadyVisitedCount))   )
  64.             return NULL;
  65.     }
  66.     // Above has ensured that target_window is non-NULL, that it is a valid window, and that
  67.     // it is eligible due to g.DetectHiddenWindows being true or the window not being hidden
  68.     // (or being one of the script's GUI windows).
  69.     return SetForegroundWindowEx(target_window);
  70. }
  71.  
  72.  
  73.  
  74. #ifdef _DEBUG_WINACTIVATE
  75. #define LOGF "c:\\AutoHotkey SetForegroundWindowEx.txt"
  76. HWND AttemptSetForeground(HWND aTargetWindow, HWND aForeWindow, char *aTargetTitle)
  77. #else
  78. HWND AttemptSetForeground(HWND aTargetWindow, HWND aForeWindow)
  79. #endif
  80. // Returns NULL if aTargetWindow or its owned-window couldn't be brought to the foreground.
  81. // Otherwise, on success, it returns either aTargetWindow or an HWND owned by aTargetWindow.
  82. {
  83.     // Probably best not to trust its return value.  It's been shown to be unreliable at times.
  84.     // Example: I've confirmed that SetForegroundWindow() sometimes (perhaps about 10% of the time)
  85.     // indicates failure even though it succeeds.  So we specifically check to see if it worked,
  86.     // which helps to avoid using the keystroke (2-alts) method, because that may disrupt the
  87.     // desired state of the keys or disturb any menus that the user may have displayed.
  88.     // Also: I think the 2-alts last-resort may fire when the system is lagging a bit
  89.     // (i.e. a drive spinning up) and the window hasn't actually become active yet,
  90.     // even though it will soon become active on its own.  Also, SetForegroundWindow() sometimes
  91.     // indicates failure even though it succeeded, usually because the window didn't become
  92.     // active immediately -- perhaps because the system was under load -- but did soon become
  93.     // active on its own (after, say, 50ms or so).  UPDATE: If SetForegroundWindow() is called
  94.     // on a hung window, at least when AttachThreadInput is in effect and that window has
  95.     // a modal dialog (such as MSIE's find dialog), this call might never return, locking up
  96.     // our thread.  So now we do this fast-check for whether the window is hung first (and
  97.     // this call is indeed very fast: its worst case is at least 30x faster than the worst-case
  98.     // performance of the ABORT-IF-HUNG method used with SendMessageTimeout.
  99.     // UPDATE for v1.0.42.03: To avoid a very rare crashing issue, IsWindowHung() is no longer called
  100.     // here, but instead by our caller.  Search on "v1.0.42.03" for more comments.
  101.     BOOL result = SetForegroundWindow(aTargetWindow);
  102.     // Note: Increasing the sleep time below did not help with occurrences of "indicated success
  103.     // even though it failed", at least with metapad.exe being activated while command prompt
  104.     // and/or AutoIt2's InputBox were active or present on the screen:
  105.     SLEEP_WITHOUT_INTERRUPTION(SLEEP_INTERVAL); // Specify param so that it will try to specifically sleep that long.
  106.     HWND new_fore_window = GetForegroundWindow();
  107.     if (new_fore_window == aTargetWindow)
  108.     {
  109. #ifdef _DEBUG_WINACTIVATE
  110.         if (!result)
  111.         {
  112.             FileAppend(LOGF, "SetForegroundWindow() indicated failure even though it succeeded: ", false);
  113.             FileAppend(LOGF, aTargetTitle);
  114.         }
  115. #endif
  116.         return aTargetWindow;
  117.     }
  118.     if (new_fore_window != aForeWindow && aTargetWindow == GetWindow(new_fore_window, GW_OWNER))
  119.         // The window we're trying to get to the foreground is the owner of the new foreground window.
  120.         // This is considered to be a success because a window that owns other windows can never be
  121.         // made the foreground window, at least if the windows it owns are visible.
  122.         return new_fore_window;
  123.     // Otherwise, failure:
  124. #ifdef _DEBUG_WINACTIVATE
  125.     if (result)
  126.     {
  127.         FileAppend(LOGF, "SetForegroundWindow() indicated success even though it failed: ", false);
  128.         FileAppend(LOGF, aTargetTitle);
  129.     }
  130. #endif
  131.     return NULL;
  132. }
  133.  
  134.  
  135.  
  136. HWND SetForegroundWindowEx(HWND aTargetWindow)
  137. // Caller must have ensured that aTargetWindow is a valid window or NULL, since we
  138. // don't call IsWindow() here.
  139. {
  140.     if (!aTargetWindow)
  141.         return NULL;  // When called this way (as it is sometimes), do nothing.
  142.  
  143.     // v1.0.42.03: Calling IsWindowHung() once here rather than potentially more than once in AttemptSetForeground()
  144.     // solves a crash that is not fully understood, nor is it easily reproduced (it occurs only in release mode,
  145.     // not debug mode).  It's likely a bug in the API's IsHungAppWindow(), but that is far from confirmed.
  146.     DWORD target_thread = GetWindowThreadProcessId(aTargetWindow, NULL);
  147.     if (target_thread != g_MainThreadID && IsWindowHung(aTargetWindow)) // Calls to IsWindowHung should probably be avoided if the window belongs to our thread.  Relies upon short-circuit boolean order.
  148.         return NULL;
  149.  
  150. #ifdef _DEBUG_WINACTIVATE
  151.     char win_name[64];
  152.     GetWindowText(aTargetWindow, win_name, sizeof(win_name));
  153. #endif
  154.  
  155.     HWND orig_foreground_wnd = GetForegroundWindow();
  156.     // AutoIt3: If there is not any foreground window, then input focus is on the TaskBar.
  157.     // MY: It is definitely possible for GetForegroundWindow() to return NULL, even on XP.
  158.     if (!orig_foreground_wnd)
  159.         orig_foreground_wnd = FindWindow("Shell_TrayWnd", NULL);
  160.  
  161.     if (aTargetWindow == orig_foreground_wnd) // It's already the active window.
  162.         return aTargetWindow;
  163.  
  164.     if (IsIconic(aTargetWindow))
  165.         // This might never return if aTargetWindow is a hung window.  But it seems better
  166.         // to do it this way than to use the PostMessage() method, which might not work
  167.         // reliably with apps that don't handle such messages in a standard way.
  168.         // A minimized window must be restored or else SetForegroundWindow() always(?)
  169.         // won't work on it.  UPDATE: ShowWindowAsync() would prevent a hang, but
  170.         // probably shouldn't use it because we rely on the fact that the message
  171.         // has been acted on prior to trying to activate the window (and all Async()
  172.         // does is post a message to its queue):
  173.         ShowWindow(aTargetWindow, SW_RESTORE);
  174.  
  175.     // This causes more trouble than it's worth.  In fact, the AutoIt author said that
  176.     // he didn't think it even helped with the IE 5.5 related issue it was originally
  177.     // intended for, so it seems a good idea to NOT to this, especially since I'm 80%
  178.     // sure it messes up the Z-order in certain circumstances, causing an unexpected
  179.     // window to pop to the foreground immediately after a modal dialog is dismissed:
  180.     //BringWindowToTop(aTargetWindow); // AutoIt3: IE 5.5 related hack.
  181.  
  182.     HWND new_foreground_wnd;
  183.  
  184.     if (!g_WinActivateForce)
  185.     // if (g_os.IsWin95() || (!g_os.IsWin9x() && !g_os.IsWin2000orLater())))  // Win95 or NT
  186.         // Try a simple approach first for these two OS's, since they don't have
  187.         // any restrictions on focus stealing:
  188. #ifdef _DEBUG_WINACTIVATE
  189. #define IF_ATTEMPT_SET_FORE if (new_foreground_wnd = AttemptSetForeground(aTargetWindow, orig_foreground_wnd, win_name))
  190. #else
  191. #define IF_ATTEMPT_SET_FORE if (new_foreground_wnd = AttemptSetForeground(aTargetWindow, orig_foreground_wnd))
  192. #endif
  193.         IF_ATTEMPT_SET_FORE
  194.             return new_foreground_wnd;
  195.         // Otherwise continue with the more drastic methods below.
  196.  
  197.     // MY: The AttachThreadInput method, when used by itself, seems to always
  198.     // work the first time on my XP system, seemingly regardless of whether the
  199.     // "allow focus steal" change has been made via SystemParametersInfo()
  200.     // (but it seems a good idea to keep the SystemParametersInfo() in effect
  201.     // in case Win2k or Win98 needs it, or in case it really does help in rare cases).
  202.     // In many cases, this avoids the two SetForegroundWindow() attempts that
  203.     // would otherwise be needed; and those two attempts cause some windows
  204.     // to flash in the taskbar, such as Metapad and Excel (less frequently) whenever
  205.     // you quickly activate another window after activating it first (e.g. via hotkeys).
  206.     // So for now, it seems best just to use this method by itself.  The
  207.     // "two-alts" case never seems to fire on my system?  Maybe it will
  208.     // on Win98 sometimes.
  209.     // Note: In addition to the "taskbar button flashing" annoyance mentioned above
  210.     // any SetForegroundWindow() attempt made prior to the one below will,
  211.     // as a side-effect, sometimes trigger the need for the "two-alts" case
  212.     // below.  So that's another reason to just keep it simple and do it this way
  213.     // only.
  214.  
  215. #ifdef _DEBUG_WINACTIVATE
  216.     char buf[1024];
  217. #endif
  218.  
  219.     bool is_attached_my_to_fore = false, is_attached_fore_to_target = false;
  220.     DWORD fore_thread;
  221.     if (orig_foreground_wnd) // Might be NULL from above.
  222.     {
  223.         // Based on MSDN docs, these calls should always succeed due to the other
  224.         // checks done above (e.g. that none of the HWND's are NULL):
  225.         fore_thread = GetWindowThreadProcessId(orig_foreground_wnd, NULL);
  226.  
  227.         // MY: Normally, it's suggested that you only need to attach the thread of the
  228.         // foreground window to our thread.  However, I've confirmed that doing all three
  229.         // attaches below makes the attempt much more likely to succeed.  In fact, it
  230.         // almost always succeeds whereas the one-attach method hardly ever succeeds the first
  231.         // time (resulting in a flashing taskbar button due to having to invoke a second attempt)
  232.         // when one window is quickly activated after another was just activated.
  233.         // AutoIt3: Attach all our input threads, will cause SetForeground to work under 98/Me.
  234.         // MSDN docs: The AttachThreadInput function fails if either of the specified threads
  235.         // does not have a message queue (My: ok here, since any window's thread MUST have a
  236.         // message queue).  [It] also fails if a journal record hook is installed.  ... Note
  237.         // that key state, which can be ascertained by calls to the GetKeyState or
  238.         // GetKeyboardState function, is reset after a call to AttachThreadInput.  You cannot
  239.         // attach a thread to a thread in another desktop.  A thread cannot attach to itself.
  240.         // Therefore, idAttachTo cannot equal idAttach.  Update: It appears that of the three,
  241.         // this first call does not offer any additional benefit, at least on XP, so not
  242.         // using it for now:
  243.         //if (g_MainThreadID != target_thread) // Don't attempt the call otherwise.
  244.         //    AttachThreadInput(g_MainThreadID, target_thread, TRUE);
  245.         if (fore_thread && g_MainThreadID != fore_thread && !IsWindowHung(orig_foreground_wnd))
  246.             is_attached_my_to_fore = AttachThreadInput(g_MainThreadID, fore_thread, TRUE) != 0;
  247.         if (fore_thread && target_thread && fore_thread != target_thread) // IsWindowHung(aTargetWindow) was called earlier.
  248.             is_attached_fore_to_target = AttachThreadInput(fore_thread, target_thread, TRUE) != 0;
  249.     }
  250.  
  251.     // The log showed that it never seemed to need more than two tries.  But there's
  252.     // not much harm in trying a few extra times.  The number of tries needed might
  253.     // vary depending on how fast the CPU is:
  254.     for (int i = 0; i < 5; ++i)
  255.     {
  256.         IF_ATTEMPT_SET_FORE
  257.         {
  258. #ifdef _DEBUG_WINACTIVATE
  259.             if (i > 0) // More than one attempt was needed.
  260.             {
  261.                 snprintf(buf, sizeof(buf), "AttachThreadInput attempt #%d indicated success: %s"
  262.                     , i + 1, win_name);
  263.                 FileAppend(LOGF, buf);
  264.             }
  265. #endif
  266.             break;
  267.         }
  268.     }
  269.  
  270.     // I decided to avoid the quick minimize + restore method of activation.  It's
  271.     // not that much more effective (if at all), and there are some significant
  272.     // disadvantages:
  273.     // - This call will often hang our thread if aTargetWindow is a hung window: ShowWindow(aTargetWindow, SW_MINIMIZE)
  274.     // - Using SW_FORCEMINIMIZE instead of SW_MINIMIZE has at least one (and probably more)
  275.     // side effect: When the window is restored, at least via SW_RESTORE, it is no longer
  276.     // maximized even if it was before the minmize.  So don't use it.
  277.     if (!new_foreground_wnd) // Not successful yet.
  278.     {
  279.         // Some apps may be intentionally blocking us by having called the API function
  280.         // LockSetForegroundWindow(), for which MSDN says "The system automatically enables
  281.         // calls to SetForegroundWindow if the user presses the ALT key or takes some action
  282.         // that causes the system itself to change the foreground window (for example,
  283.         // clicking a background window)."  Also, it's probably best to avoid doing
  284.         // the 2-alts method except as a last resort, because I think it may mess up
  285.         // the state of menus the user had displayed.  And of course if the foreground
  286.         // app has special handling for alt-key events, it might get confused.
  287.         // My original note: "The 2-alts case seems to mess up on rare occasions,
  288.         // perhaps due to menu weirdness triggered by the alt key."
  289.         // AutoIt3: OK, this is not funny - bring out the extreme measures (usually for 2000/XP).
  290.         // Simulate two single ALT keystrokes.  UPDATE: This hardly ever succeeds.  Usually when
  291.         // it fails, the foreground window is NULL (none).  I'm going to try an Win-tab instead,
  292.         // which selects a task bar button.  This seems less invasive than doing an alt-tab
  293.         // because not only doesn't it activate some other window first, it also doesn't appear
  294.         // to change the Z-order, which is good because we don't want the alt-tab order
  295.         // that the user sees to be affected by this.  UPDATE: Win-tab isn't doing it, so try
  296.         // Alt-tab.  Alt-tab doesn't do it either.  The window itself (metapad.exe is the only
  297.         // culprit window I've found so far) seems to resist being brought to the foreground,
  298.         // but later, after the hotkey is released, it can be.  So perhaps this is being
  299.         // caused by the fact that the user has keys held down (logically or physically?)
  300.         // Releasing those keys with a key-up event might help, so try that sometime:
  301.         KeyEvent(KEYDOWNANDUP, VK_MENU);
  302.         KeyEvent(KEYDOWNANDUP, VK_MENU);
  303.         //KeyEvent(KEYDOWN, VK_LWIN);
  304.         //KeyEvent(KEYDOWN, VK_TAB);
  305.         //KeyEvent(KEYUP, VK_TAB);
  306.         //KeyEvent(KEYUP, VK_LWIN);
  307.         //KeyEvent(KEYDOWN, VK_MENU);
  308.         //KeyEvent(KEYDOWN, VK_TAB);
  309.         //KeyEvent(KEYUP, VK_TAB);
  310.         //KeyEvent(KEYUP, VK_MENU);
  311.         // Also replacing "2-alts" with "alt-tab" below, for now:
  312.  
  313. #ifndef _DEBUG_WINACTIVATE
  314.         new_foreground_wnd = AttemptSetForeground(aTargetWindow, orig_foreground_wnd);
  315. #else // debug mode
  316.         IF_ATTEMPT_SET_FORE
  317.             FileAppend(LOGF, "2-alts ok: ", false);
  318.         else
  319.         {
  320.             FileAppend(LOGF, "2-alts (which is the last resort) failed.  ", false);
  321.             HWND h = GetForegroundWindow();
  322.             if (h)
  323.             {
  324.                 char fore_name[64];
  325.                 GetWindowText(h, fore_name, sizeof(fore_name));
  326.                 FileAppend(LOGF, "Foreground: ", false);
  327.                 FileAppend(LOGF, fore_name, false);
  328.             }
  329.             FileAppend(LOGF, ".  Was trying to activate: ", false);
  330.         }
  331.         FileAppend(LOGF, win_name);
  332. #endif
  333.     } // if()
  334.  
  335.     // Very important to detach any threads whose inputs were attached above,
  336.     // prior to returning, otherwise the next attempt to attach thread inputs
  337.     // for these particular windows may result in a hung thread or other
  338.     // undesirable effect:
  339.     if (is_attached_my_to_fore)
  340.         AttachThreadInput(g_MainThreadID, fore_thread, FALSE);
  341.     if (is_attached_fore_to_target)
  342.         AttachThreadInput(fore_thread, target_thread, FALSE);
  343.  
  344.     // Finally.  This one works, solving the problem of the MessageBox window
  345.     // having the input focus and being the foreground window, but not actually
  346.     // being visible (even though IsVisible() and IsIconic() say it is)!  It may
  347.     // help with other conditions under which this function would otherwise fail.
  348.     // Here's the way the repeat the failure to test how the absence of this line
  349.     // affects things, at least on my XP SP1 system:
  350.     // y::MsgBox, test
  351.     // #e::(some hotkey that activates Windows Explorer)
  352.     // Now: Activate explorer with the hotkey, then invoke the MsgBox.  It will
  353.     // usually be activated but invisible.  Also: Whenever this invisible problem
  354.     // is about to occur, with or without this fix, it appears that the OS's z-order
  355.     // is a bit messed up, because when you dismiss the MessageBox, an unexpected
  356.     // window (probably the one two levels down) becomes active rather than the
  357.     // window that's only 1 level down in the z-order:
  358.     if (new_foreground_wnd) // success.
  359.     {
  360.         // Even though this is already done for the IE 5.5 "hack" above, must at
  361.         // a minimum do it here: The above one may be optional, not sure (safest
  362.         // to leave it unless someone can test with IE 5.5).
  363.         // Note: I suspect the two lines below achieve the same thing.  They may
  364.         // even be functionally identical.  UPDATE: This may no longer be needed
  365.         // now that the first BringWindowToTop(), above, has been disabled due to
  366.         // its causing more trouble than it's worth.  But seems safer to leave
  367.         // this one enabled in case it does resolve IE 5.5 related issues and
  368.         // possible other issues:
  369.         BringWindowToTop(aTargetWindow);
  370.         //SetWindowPos(aTargetWindow, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
  371.         return new_foreground_wnd; // Return this rather than aTargetWindow because it's more appropriate.
  372.     }
  373.     else
  374.         return NULL;
  375. }
  376.  
  377.  
  378.  
  379. HWND WinClose(global_struct &aSettings, char *aTitle, char *aText, int aTimeToWaitForClose
  380.     , char *aExcludeTitle, char *aExcludeText, bool aKillIfHung)
  381. // Return the HWND of any found-window to the caller so that it has the option of waiting
  382. // for it to become an invalid (closed) window.
  383. {
  384.     HWND target_window;
  385.     IF_USE_FOREGROUND_WINDOW(aSettings.DetectHiddenWindows, aTitle, aText, aExcludeTitle, aExcludeText)
  386.         // Close topmost (better than !F4 since that uses the alt key, effectively resetting
  387.         // its status to UP if it was down before.  Use WM_CLOSE rather than WM_EXIT because
  388.         // I think that's what Alt-F4 sends (and otherwise, app may quit without offering
  389.         // a chance to save).
  390.         // DON'T DISPLAY a MsgBox (e.g. debugging) before trying to close foreground window.
  391.         // Otherwise, it may close the owner of the dialog window (this app), perhaps due to
  392.         // split-second timing issues.
  393.     else if (*aTitle || *aText || *aExcludeTitle || *aExcludeText)
  394.     {
  395.         // Since EnumWindows() is *not* guaranteed to start proceed in z-order from topmost to
  396.         // bottomost (though it almost certainly does), do it this way to ensure that the
  397.         // topmost window is closed in preference to any other windows with the same <aTitle>
  398.         // and <aText>:
  399.         if (   !(target_window = WinActive(aSettings, aTitle, aText, aExcludeTitle, aExcludeText))   )
  400.             if (   !(target_window = WinExist(aSettings, aTitle, aText, aExcludeTitle, aExcludeText))   )
  401.                 return NULL;
  402.     }
  403.     else
  404.         target_window = GetValidLastUsedWindow(aSettings);
  405.  
  406.     return target_window ? WinClose(target_window, aTimeToWaitForClose, aKillIfHung) : NULL;
  407. }
  408.  
  409.  
  410.  
  411. HWND WinClose(HWND aWnd, int aTimeToWaitForClose, bool aKillIfHung)
  412. {
  413.     if (aKillIfHung) // This part is based on the AutoIt3 source.
  414.         // Update: Another reason not to wait a long time with the below is that WinKill
  415.         // is normally only used when the target window is suspected of being hung.  It
  416.         // seems bad to wait something like 2 seconds in such a case, when the caller
  417.         // probably already knows it's hung.
  418.         // Obsolete in light of dedicated hook thread: Because this app is much more sensitive to being
  419.         // in a "not-pumping-messages" state, due to the keyboard & mouse hooks, it seems better to wait
  420.         // for only 200 ms (e.g. in case the user is gaming and there's a script
  421.         // running in the background that uses WinKill, we don't want key and mouse events
  422.         // to freeze for a long time).
  423.         Util_WinKill(aWnd);
  424.     else // Don't kill.
  425.         // SC_CLOSE is the same as clicking a window's "X"(close) button or using Alt-F4.
  426.         // Although it's a more friendly way to close windows than WM_CLOSE (and thus
  427.         // avoids incompatibilities with apps such as MS Visual C++), apps that
  428.         // have disabled Alt-F4 processing will not be successfully closed.  It seems
  429.         // best not to send both SC_CLOSE and WM_CLOSE because some apps with an 
  430.         // "Unsaved.  Are you sure?" type dialog might close down completely rather than
  431.         // waiting for the user to confirm.  Anyway, it's extrememly rare for a window
  432.         // not to respond to Alt-F4 (though it is possible that it handles Alt-F4 in a
  433.         // non-standard way, i.e. that sending SC_CLOSE equivalent to Alt-F4
  434.         // for windows that handle Alt-F4 manually?)  But on the upside, this is nicer
  435.         // for apps that upon receiving Alt-F4 do some behavior other than closing, such
  436.         // as minimizing to the tray.  Such apps might shut down entirely if they received
  437.         // a true WM_CLOSE, which is probably not what the user would want.
  438.         // Update: Swithced back to using WM_CLOSE so that instances of AutoHotkey
  439.         // can be terminated via another instances use of the WinClose command:
  440.         //PostMessage(aWnd, WM_SYSCOMMAND, SC_CLOSE, 0);
  441.         PostMessage(aWnd, WM_CLOSE, 0, 0);
  442.  
  443.     if (aTimeToWaitForClose < 0)
  444.         aTimeToWaitForClose = 0;
  445.     if (!aTimeToWaitForClose)
  446.         return aWnd; // v1.0.30: EnumParentActUponAll() relies on the ability to do no delay at all.
  447.  
  448.     // Slight delay.  Might help avoid user having to modify script to use WinWaitClose()
  449.     // in many cases.  UPDATE: But this does a Sleep(0), which won't yield the remainder
  450.     // of our thread's timeslice unless there's another app trying to use 100% of the CPU?
  451.     // So, in reality it really doesn't accomplish anything because the window we just
  452.     // closed won't get any CPU time (unless, perhaps, it receives the close message in
  453.     // time to ask the OS for us to yield the timeslice).  Perhaps some finer tuning
  454.     // of this can be done in the future.  UPDATE: Testing of WinActivate, which also
  455.     // uses this to do a Sleep(0), reveals that it may in fact help even when the CPU
  456.     // isn't under load.  Perhaps this is because upon Sleep(0), the OS runs the
  457.     // WindowProc's of windows that have messages waiting for them so that appropriate
  458.     // action can be taken (which may often be nearly instantaneous, perhaps under
  459.     // 1ms for a Window to be logically destroyed even if it hasn't physically been
  460.     // removed from the screen?) prior to returning the CPU to our thread:
  461.     DWORD start_time = GetTickCount(); // Before doing any MsgSleeps, set this.
  462.     //MsgSleep(0); // Always do one small one, see above comments.
  463.     // UPDATE: It seems better just to always do one unspecified-interval sleep
  464.     // rather than MsgSleep(0), which often returns immediately, probably having
  465.     // no effect.
  466.  
  467.     // Remember that once the first call to MsgSleep() is done, a new hotkey subroutine
  468.     // may fire and suspend what we're doing here.  Such a subroutine might also overwrite
  469.     // the values our params, some of which may be in the deref buffer.  So be sure not
  470.     // to refer to those strings once MsgSleep() has been done, below:
  471.  
  472.     // This is the same basic code used for ACT_WINWAITCLOSE and such:
  473.     for (;;)
  474.     {
  475.         // Seems best to always do the first one regardless of the value 
  476.         // of aTimeToWaitForClose:
  477.         MsgSleep(INTERVAL_UNSPECIFIED);
  478.         if (!IsWindow(aWnd)) // It's gone, so we're done.
  479.             return aWnd;
  480.         // Must cast to int or any negative result will be lost due to DWORD type:
  481.         if ((int)(aTimeToWaitForClose - (GetTickCount() - start_time)) <= SLEEP_INTERVAL_HALF)
  482.             break;
  483.             // Last param 0 because we don't want it to restore the
  484.             // current active window after the time expires (in case
  485.             // it's suspended).  INTERVAL_UNSPECIFIED performs better.
  486.     }
  487.     return aWnd;  // Done waiting.
  488. }
  489.  
  490.  
  491.     
  492. HWND WinActive(global_struct &aSettings, char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText
  493.     , bool aUpdateLastUsed)
  494. // This function must be kept thread-safe because it may be called (indirectly) by hook thread too.
  495. // In addition, it must not change the value of anything in aSettings except when aUpdateLastUsed==true.
  496. {
  497.     HWND target_window;
  498.     if (USE_FOREGROUND_WINDOW(aTitle, aText, aExcludeTitle, aExcludeText))
  499.     {
  500.         // User asked us if the "active" window is active, which is true if it's not a
  501.         // hidden window, or if DetectHiddenWindows is ON:
  502.         SET_TARGET_TO_ALLOWABLE_FOREGROUND(aSettings.DetectHiddenWindows)
  503.         #define UPDATE_AND_RETURN_LAST_USED_WINDOW(hwnd) \
  504.         {\
  505.             if (aUpdateLastUsed && hwnd)\
  506.                 aSettings.hWndLastUsed = hwnd;\
  507.             return hwnd;\
  508.         }
  509.         UPDATE_AND_RETURN_LAST_USED_WINDOW(target_window)
  510.     }
  511.  
  512.     HWND fore_win = GetForegroundWindow();
  513.     if (!fore_win)
  514.         return NULL;
  515.  
  516.     if (!(*aTitle || *aText || *aExcludeTitle || *aExcludeText)) // Use the "last found" window.
  517.         return (fore_win == GetValidLastUsedWindow(aSettings)) ? fore_win : NULL;
  518.  
  519.     // Only after the above check should the below be done.  This is because "IfWinActive" (with no params)
  520.     // should be "true" if one of the script's GUI windows is active:
  521.     if (!(aSettings.DetectHiddenWindows || IsWindowVisible(fore_win))) // In this case, the caller's window can't be active.
  522.         return NULL;
  523.  
  524.     WindowSearch ws;
  525.     ws.SetCandidate(fore_win);
  526.  
  527.     if (ws.SetCriteria(aSettings, aTitle, aText, aExcludeTitle, aExcludeText) && ws.IsMatch()) // aSettings.DetectHiddenWindows was already checked above.
  528.         UPDATE_AND_RETURN_LAST_USED_WINDOW(fore_win) // This also does a "return".
  529.     else // If the line above didn't return, indicate that the specified window is not active.
  530.         return NULL;
  531. }
  532.  
  533.  
  534.  
  535. HWND WinExist(global_struct &aSettings, char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText
  536.     , bool aFindLastMatch, bool aUpdateLastUsed, HWND aAlreadyVisited[], int aAlreadyVisitedCount)
  537. // This function must be kept thread-safe because it may be called (indirectly) by hook thread too.
  538. // In addition, it must not change the value of anything in aSettings except when aUpdateLastUsed==true.
  539. {
  540.     HWND target_window;
  541.     if (USE_FOREGROUND_WINDOW(aTitle, aText, aExcludeTitle, aExcludeText))
  542.     {
  543.         // User asked us if the "active" window exists, which is true if it's not a
  544.         // hidden window or DetectHiddenWindows is ON:
  545.         SET_TARGET_TO_ALLOWABLE_FOREGROUND(aSettings.DetectHiddenWindows)
  546.         // Updating LastUsed to be hwnd even if it's NULL seems best for consistency?
  547.         // UPDATE: No, it's more flexible not to never set it to NULL, because there
  548.         // will be times when the old value is still useful:
  549.         UPDATE_AND_RETURN_LAST_USED_WINDOW(target_window);
  550.     }
  551.  
  552.     if (!(*aTitle || *aText || *aExcludeTitle || *aExcludeText))
  553.         // User passed no params, so use the window most recently found by WinExist().
  554.         // It's correct to do this even in this function because it's called by
  555.         // WINWAITCLOSE and IFWINEXIST specifically to discover if the Last-Used
  556.         // window still exists.
  557.         return GetValidLastUsedWindow(aSettings);
  558.  
  559.     WindowSearch ws;
  560.     if (!ws.SetCriteria(aSettings, aTitle, aText, aExcludeTitle, aExcludeText)) // No match is possible with these criteria.
  561.         return NULL;
  562.  
  563.     ws.mFindLastMatch = aFindLastMatch;
  564.     ws.mAlreadyVisited = aAlreadyVisited;
  565.     ws.mAlreadyVisitedCount = aAlreadyVisitedCount;
  566.  
  567.     if (ws.mCriteria & CRITERION_ID) // "ahk_id" will be satisified if that HWND still exists and is valid.
  568.     {
  569.         // Explicitly allow HWND_BROADCAST for all commands that use WinExist (which is just about all
  570.         // window commands), even though it's only valid with ScriptPostSendMessage().
  571.         // This is because HWND_BROADCAST is probably never used as the HWND for a real window, so there
  572.         // should be no danger of any reasonable script ever passing that value in as a real target window,
  573.         // which should thus minimize the chance of a crash due to calling various API functions
  574.         // with invalid window handles.
  575.         if (   ws.mCriterionHwnd != HWND_BROADCAST // It's not exempt from the other checks on the two lines below.
  576.             && (!IsWindow(ws.mCriterionHwnd)    // And it's either not a valid window...
  577.                 // ...or the window is not detectible (in v1.0.40.05, child windows are detectible even if hidden):
  578.                 || !(aSettings.DetectHiddenWindows || IsWindowVisible(ws.mCriterionHwnd)
  579.                     || (GetWindowLong(ws.mCriterionHwnd, GWL_STYLE) & WS_CHILD)))   )
  580.             return NULL;
  581.  
  582.         // Otherwise, the window is valid and detectible.
  583.         ws.SetCandidate(ws.mCriterionHwnd);
  584.         if (!ws.IsMatch()) // Checks if it matches any other criteria: WinTitle, WinText, ExcludeTitle, and anything in the aAlreadyVisited list.
  585.             return NULL;
  586.         //else fall through to the section below, since ws.mFoundCount and ws.mFoundParent were set by ws.IsMatch().
  587.     }
  588.     else // aWinTitle doesn't start with "ahk_id".  Try to find a matching window.
  589.         EnumWindows(EnumParentFind, (LPARAM)&ws);
  590.  
  591.     UPDATE_AND_RETURN_LAST_USED_WINDOW(ws.mFoundParent) // This also does a "return".
  592. }
  593.  
  594.  
  595.  
  596. HWND GetValidLastUsedWindow(global_struct &aSettings)
  597. // If the last found window is one of the script's own GUI windows, it is considered valid even if
  598. // DetectHiddenWindows is ON.  Note that this exemption does not apply to things like "IfWinExist,
  599. // My Gui Title", "WinActivate, ahk_id <gui id>", etc.
  600. // A GUI window can become the last found window while DetectHiddenWindows is ON in two ways:
  601. // Gui +LastFound
  602. // The launch of a GUI thread that explicitly set the last found window to be that GUI window.
  603. {
  604.     if (!aSettings.hWndLastUsed || !IsWindow(aSettings.hWndLastUsed))
  605.         return NULL;
  606.     if (   aSettings.DetectHiddenWindows || IsWindowVisible(aSettings.hWndLastUsed)
  607.         || (GetWindowLong(aSettings.hWndLastUsed, GWL_STYLE) & WS_CHILD)   ) // v1.0.40.05: Child windows (via ahk_id) are always detectible.
  608.         return aSettings.hWndLastUsed;
  609.     // Otherwise, DetectHiddenWindows is OFF and the window is not visible.  Return NULL
  610.     // unless this is a GUI window belonging to this particular script, in which case
  611.     // the setting of DetectHiddenWindows is ignored (as of v1.0.25.13).
  612.     return GuiType::FindGui(aSettings.hWndLastUsed) ? aSettings.hWndLastUsed : NULL;
  613. }
  614.  
  615.  
  616.  
  617. BOOL CALLBACK EnumParentFind(HWND aWnd, LPARAM lParam)
  618. // This function must be kept thread-safe because it may be called (indirectly) by hook thread too.
  619. // To continue enumeration, the function must return TRUE; to stop enumeration, it must return FALSE. 
  620. // It's a little strange, but I think EnumWindows() returns FALSE when the callback stopped
  621. // the enumeration prematurely by returning false to its caller.  Otherwise (the enumeration went
  622. // through every window), it returns TRUE:
  623. {
  624.     WindowSearch &ws = *(WindowSearch *)lParam;  // For performance and convenience.
  625.     // According to MSDN, GetWindowText() will hang only if it's done against
  626.     // one of your own app's windows and that window is hung.  I suspect
  627.     // this might not be true in Win95, and possibly not even Win98, but
  628.     // it's not really an issue because GetWindowText() has to be called
  629.     // eventually, either here or in an EnumWindowsProc.  The only way
  630.     // to prevent hangs (if indeed it does hang on Win9x) would be to
  631.     // call something like IsWindowHung() before every call to
  632.     // GetWindowText(), which might result in a noticeable delay whenever
  633.     // we search for a window via its title (or even worse: by the title
  634.     // of one of its controls or child windows).  UPDATE: Trying GetWindowTextTimeout()
  635.     // now, which might be the best compromise.  UPDATE: It's annoyingly slow,
  636.     // so went back to using the old method.
  637.     if (!(ws.mSettings->DetectHiddenWindows || IsWindowVisible(aWnd))) // Skip windows the script isn't supposed to detect.
  638.         return TRUE;
  639.     ws.SetCandidate(aWnd);
  640.     // If this window doesn't match, continue searching for more windows (via TRUE).  Likewise, if
  641.     // mFindLastMatch is true, continue searching even if this window is a match.  Otherwise, this
  642.     // first match is the one that's desired so stop here:
  643.     return ws.IsMatch() ? ws.mFindLastMatch : TRUE;
  644. }
  645.  
  646.  
  647.  
  648. BOOL CALLBACK EnumChildFind(HWND aWnd, LPARAM lParam)
  649. // This function must be kept thread-safe because it may be called (indirectly) by hook thread too.
  650. // Although this function could be rolled into a generalized version of the EnumWindowsProc(),
  651. // it will perform better this way because there's less checking required and no mode/flag indicator
  652. // is needed inside lParam to indicate which struct element should be searched for.  In addition,
  653. // it's more comprehensible this way.  lParam is a pointer to the struct rather than just a
  654. // string because we want to give back the HWND of any matching window.
  655. {
  656.     // Since WinText and ExcludeText are seldom used in typical scripts, the following buffer
  657.     // is put on the stack here rather than on our callers (inside the WindowSearch object),
  658.     // which should help conserve stack space on average.  Can't use the ws.mCandidateTitle
  659.     // buffer because ws.mFindLastMatch might be true, in which case the original title must
  660.     // be preserved.
  661.     char win_text[WINDOW_TEXT_SIZE];
  662.     WindowSearch &ws = *(WindowSearch *)lParam;  // For performance and convenience.
  663.  
  664.     if (!(ws.mSettings->DetectHiddenText || IsWindowVisible(aWnd))) // This text element should not be detectible by the script.
  665.         return TRUE;  // Skip this child and keep enumerating to try to find a match among the other children.
  666.  
  667.     // The below was formerly outsourced to the following function, but since it is only called from here,
  668.     // it has been moved inline:
  669.     // int GetWindowTextByTitleMatchMode(HWND aWnd, char *aBuf = NULL, int aBufSize = 0)
  670.     int text_length = ws.mSettings->TitleFindFast ? GetWindowText(aWnd, win_text, sizeof(win_text))
  671.         : GetWindowTextTimeout(aWnd, win_text, sizeof(win_text));  // The slower method that is able to get text from more types of controls (e.g. large edit controls).
  672.     // Older idea that for the above that was not adopted:
  673.     // Only if GetWindowText() gets 0 length would we try the other method (and of course, don't bother
  674.     // using GetWindowTextTimeout() at all if "fast" mode is in effect).  The problem with this is that
  675.     // many controls always return 0 length regardless of which method is used, so this would slow things
  676.     // down a little (but not too badly since GetWindowText() is so much faster than GetWindowTextTimeout()).
  677.     // Another potential problem is that some controls may return less text, or different text, when used
  678.     // with the fast mode vs. the slow mode (unverified).  So it seems best NOT to do this and stick with
  679.     // the simple approach above.
  680.     if (!text_length) // It has no text (or failure to fetch it).
  681.         return TRUE;  // Skip this child and keep enumerating to try to find a match among the other children.
  682.  
  683.     // For compatibility with AutoIt v2, strstr() is always used for control/child text elements.
  684.  
  685.     // EXCLUDE-TEXT: The following check takes precedence over the next, so it's done first:
  686.     if (*ws.mCriterionExcludeText) // For performance, avoid doing the checks below when blank.
  687.     {
  688.         if (ws.mSettings->TitleMatchMode == FIND_REGEX)
  689.         {
  690.             if (RegExMatch(win_text, ws.mCriterionExcludeText))
  691.                 return FALSE; // Parent can't be a match, so stop searching its children.
  692.         }
  693.         else // For backward compatibility, all modes other than RegEx behave as follows.
  694.             if (strstr(win_text, ws.mCriterionExcludeText))
  695.                 // Since this child window contains the specified ExcludeText anywhere inside its text,
  696.                 // the parent window is always a non-match.
  697.                 return FALSE; // Parent can't be a match, so stop searching its children.
  698.     }
  699.  
  700.     // WIN-TEXT:
  701.     if (!*ws.mCriterionText) // Match always found in this case. This check is for performance: it avoids doing the checks below when not needed, especially RegEx. Note: It's possible for mCriterionText to be blank, at least when mCriterionExcludeText isn't blank.
  702.     {
  703.         ws.mFoundChild = aWnd;
  704.         return FALSE; // Match found, so stop searching.
  705.     }
  706.     if (ws.mSettings->TitleMatchMode == FIND_REGEX)
  707.     {
  708.         if (RegExMatch(win_text, ws.mCriterionText)) // Match found.
  709.         {
  710.             ws.mFoundChild = aWnd;
  711.             return FALSE; // Match found, so stop searching.
  712.         }
  713.     }
  714.     else // For backward compatibility, all modes other than RegEx behave as follows.
  715.         if (strstr(win_text, ws.mCriterionText)) // Match found.
  716.         {
  717.             ws.mFoundChild = aWnd;
  718.             return FALSE; // Match found, so stop searching.
  719.         }
  720.  
  721.     // UPDATE to the below: The MSDN docs state that EnumChildWindows() already handles the
  722.     // recursion for us: "If a child window has created child windows of its own,
  723.     // EnumChildWindows() enumerates those windows as well."
  724.     // Mostly obsolete comments: Since this child doesn't match, make sure none of its
  725.     // children (recursive) match prior to continuing the original enumeration.  We don't
  726.     // discard the return value from EnumChildWindows() because it's FALSE in two cases:
  727.     // 1) The given HWND has no children.
  728.     // 2) The given EnumChildProc() stopped prematurely rather than enumerating all the windows.
  729.     // and there's no way to distinguish between the two cases without using the
  730.     // struct's hwnd because GetLastError() seems to return ERROR_SUCCESS in both
  731.     // cases.
  732.     //EnumChildWindows(aWnd, EnumChildFind, lParam);
  733.     // If matching HWND still hasn't been found, return TRUE to keep searching:
  734.     //return ws.mFoundChild == NULL;
  735.  
  736.     return TRUE; // Keep searching.
  737. }
  738.  
  739.  
  740.  
  741. ResultType StatusBarUtil(Var *aOutputVar, HWND aBarHwnd, int aPartNumber, char *aTextToWaitFor
  742.     , int aWaitTime, int aCheckInterval)
  743. // aOutputVar is allowed to be NULL if aTextToWaitFor isn't NULL or blank. aBarHwnd is allowed
  744. // to be NULL because in that case, the caller wants us to set ErrorLevel appropriately and also
  745. // make aOutputVar empty.
  746. {
  747.     if (aOutputVar)
  748.         aOutputVar->Assign(); // Init to blank in case of early return.
  749.     // Set default ErrorLevel, which is a special value (2 vs. 1) in the case of StatusBarWait:
  750.     g_ErrorLevel->Assign(aOutputVar ? ERRORLEVEL_ERROR : ERRORLEVEL_ERROR2);
  751.  
  752.     // Legacy: Waiting 500ms in place of a "0" seems more useful than a true zero, which doens't need
  753.     // to be supported because it's the same thing as something like "IfWinExist":
  754.     if (!aWaitTime)
  755.         aWaitTime = 500;
  756.     if (aCheckInterval < 1)
  757.         aCheckInterval = SB_DEFAULT_CHECK_INTERVAL; // Caller relies on us doing this.
  758.     if (aPartNumber < 1)
  759.         aPartNumber = 1;  // Caller relies on us to set default in this case.
  760.  
  761.     // Must have at least one of these.  UPDATE: We want to allow this so that the command can be
  762.     // used to wait for the status bar text to become blank:
  763.     //if (!aOutputVar && !*aTextToWaitFor) return OK;
  764.  
  765.     // Whenever using SendMessageTimeout(), our app will be unresponsive until
  766.     // the call returns, since our message loop isn't running.  In addition,
  767.     // if the keyboard or mouse hook is installed, the input events will lag during
  768.     // this call.  So keep the timeout value fairly short.  Update for v1.0.24:
  769.     // There have been at least two reports of the StatusBarWait command ending
  770.     // prematurely with an ErrorLevel of 2.  The most likely culprit is the below,
  771.     // which has now been increased from 100 to 2000:
  772.     #define SB_TIMEOUT 2000
  773.  
  774.     HANDLE handle;
  775.     LPVOID remote_buf;
  776.     LRESULT part_count; // The number of parts this status bar has.
  777.     if (!aBarHwnd  // These conditions rely heavily on short-circuit boolean order.
  778.         || !SendMessageTimeout(aBarHwnd, SB_GETPARTS, 0, 0, SMTO_ABORTIFHUNG, SB_TIMEOUT, (PDWORD_PTR)&part_count) // It failed or timed out.
  779.         || aPartNumber > part_count
  780.         || !(remote_buf = AllocInterProcMem(handle, WINDOW_TEXT_SIZE + 1, aBarHwnd))) // Alloc mem last.
  781.         return OK; // Let ErrorLevel tell the story.
  782.  
  783.     char buf_for_nt[WINDOW_TEXT_SIZE + 1]; // Needed only for NT/2k/XP: the local counterpart to the buf allocated remotely above.
  784.     bool is_win9x = g_os.IsWin9x();
  785.     char *local_buf = is_win9x ? (char *)remote_buf : buf_for_nt; // Local is the same as remote for Win9x.
  786.  
  787.     DWORD result, start_time;
  788.     --aPartNumber; // Convert to zero-based for use below.
  789.  
  790.     // Always do the first iteration so that at least one check is done.  Also,  start_time is initialized
  791.     // unconditionally in the name of code size reduction (it's a low overhead call):
  792.     for (*local_buf = '\0', start_time = GetTickCount();;)
  793.     {
  794.         // MSDN recommends always checking the length of the bar text.  It implies that the length is
  795.         // unrestricted, so a crash due to buffer overflow could otherwise occur:
  796.         if (SendMessageTimeout(aBarHwnd, SB_GETTEXTLENGTH, aPartNumber, 0, SMTO_ABORTIFHUNG, SB_TIMEOUT, &result))
  797.         {
  798.             // Testing confirms that LOWORD(result) [the length] does not include the zero terminator.
  799.             if (LOWORD(result) > WINDOW_TEXT_SIZE) // Text would be too large (very unlikely but good to check for security).
  800.                 break; // Abort the operation and leave ErrorLevel set to its default to indicate the problem.
  801.             // Retrieve the bar's text:
  802.             if (SendMessageTimeout(aBarHwnd, SB_GETTEXT, aPartNumber, (LPARAM)remote_buf, SMTO_ABORTIFHUNG, SB_TIMEOUT, &result))
  803.             {
  804.                 if (!is_win9x)
  805.                 {
  806.                     if (!ReadProcessMemory(handle, remote_buf, local_buf, LOWORD(result) + 1, NULL)) // +1 to include the terminator (verified: length doesn't include zero terminator).
  807.                     {
  808.                         // Fairly critical error (though rare) so seems best to abort.
  809.                         *local_buf = '\0';  // In case it changed the buf before failing.
  810.                         break;
  811.                     }
  812.                 }
  813.                 //else Win9x, in which case the local and remote buffers are the same (no copying is needed).
  814.  
  815.                 // Check if the retrieved text matches the caller's criteria. In addition to
  816.                 // normal/intuitive matching, a match is also achieved if both are empty strings.
  817.                 // In fact, IsTextMatch() yields "true" whenever aTextToWaitFor is the empty string:
  818.                 if (IsTextMatch(local_buf, aTextToWaitFor))
  819.                 {
  820.                     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate "match found".
  821.                     break;
  822.                 }
  823.             }
  824.             //else SB_GETTEXT msg timed out or failed.  Leave local_buf unaltered.  See comment below.
  825.         }
  826.         //else SB_GETTEXTLENGTH msg timed out or failed.  For v1.0.37, continue to wait (if other checks
  827.         // say its okay below) rather than aborting the operation.  This should help prevent an abort
  828.         // when the target window (or the entire system) is unresponsive for a long time, perhaps due
  829.         // to a drive spinning up, etc.
  830.  
  831.         // Only when above didn't break are the following secondary conditions checked.  When aOutputVar
  832.         // is non-NULL, the caller wanted a single check only (no waiting) [however, in most such cases,
  833.         // the checking above would already have done a "break" because of aTextToWaitFor being blank when
  834.         // passed to IsTextMatch()].  Also, don't continue to wait if the status bar no longer exists
  835.         // (which is usually caused by the parent window having been destroyed):
  836.         if (aOutputVar || !IsWindow(aBarHwnd))
  837.             break; // Leave ErrorLevel at its default to indicate bar text retrieval problem in both cases.
  838.  
  839.         // Since above didn't break, we're in "wait" mode (more than one iteration).
  840.         // In the following, must cast to int or any negative result will be lost due to DWORD type.
  841.         // Note: A negative aWaitTime means we're waiting indefinitely for a match to appear.
  842.         if (aWaitTime < 0 || (int)(aWaitTime - (GetTickCount() - start_time)) > SLEEP_INTERVAL_HALF)
  843.             MsgSleep(aCheckInterval);
  844.         else // Timed out.
  845.         {
  846.             g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Override default to indicate timeout vs. "other error".
  847.             break;
  848.         }
  849.     } // for()
  850.  
  851.     // Consider this to be always successful, even if aBarHwnd == NULL
  852.     // or the status bar didn't have the part number provided, unless the below fails.
  853.     // Note we use a temp buf rather than writing directly to the var contents above, because
  854.     // we don't know how long the text will be until after the above operation finishes.
  855.     ResultType result_to_return = aOutputVar ? aOutputVar->Assign(local_buf) : OK;
  856.     FreeInterProcMem(handle, remote_buf); // Don't free until after the above because above needs file mapping for Win9x.
  857.     return result_to_return;
  858. }
  859.  
  860.  
  861.  
  862. HWND ControlExist(HWND aParentWindow, char *aClassNameAndNum)
  863. // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  864. {
  865.     if (!aParentWindow)
  866.         return NULL;
  867.     if (!aClassNameAndNum || !*aClassNameAndNum)
  868.         return (GetWindowLong(aParentWindow, GWL_STYLE) & WS_CHILD) ? aParentWindow : GetTopChild(aParentWindow);
  869.         // Above: In v1.0.43.06, the parent window itself is returned if it's a child rather than its top child
  870.         // because it seems more useful and intuitive.  This change allows ahk_id %ControlHwnd% to always operate
  871.         // directly on the specified control's HWND rather than some sub-child.
  872.  
  873.     WindowSearch ws;
  874.     bool is_class_name = isdigit(aClassNameAndNum[strlen(aClassNameAndNum) - 1]);
  875.  
  876.     if (is_class_name)
  877.     {
  878.         // Tell EnumControlFind() to search by Class+Num.  Don't call ws.SetCriteria() because
  879.         // that has special handling for ahk_id, ahk_class, etc. in the first parameter.
  880.         strlcpy(ws.mCriterionClass, aClassNameAndNum, sizeof(ws.mCriterionClass));
  881.         ws.mCriterionText = "";
  882.     }
  883.     else // Tell EnumControlFind() to search the control's text.
  884.     {
  885.         *ws.mCriterionClass = '\0';
  886.         ws.mCriterionText = aClassNameAndNum;
  887.     }
  888.  
  889.     EnumChildWindows(aParentWindow, EnumControlFind, (LPARAM)&ws); // mFoundChild was initialized by the contructor.
  890.  
  891.     if (is_class_name && !ws.mFoundChild)
  892.     {
  893.         // To reduce problems with ambiguity (a class name and number of one control happens
  894.         // to match the title/text of another control), search again only after the search
  895.         // for the ClassNameAndNum didn't turn up anything.
  896.         // Tell EnumControlFind() to search the control's text.
  897.         *ws.mCriterionClass = '\0';
  898.         ws.mCriterionText = aClassNameAndNum;
  899.         EnumChildWindows(aParentWindow, EnumControlFind, (LPARAM)&ws); // ws.mFoundChild is already initialized to NULL due to the above check.
  900.     }
  901.  
  902.     return ws.mFoundChild;
  903. }
  904.  
  905.  
  906.  
  907. BOOL CALLBACK EnumControlFind(HWND aWnd, LPARAM lParam)
  908. {
  909.     WindowSearch &ws = *(WindowSearch *)lParam;  // For performance and convenience.
  910.     if (*ws.mCriterionClass) // Caller told us to search by class name and number.
  911.     {
  912.         int length = GetClassName(aWnd, ws.mCandidateTitle, WINDOW_CLASS_SIZE); // Restrict the length to a small fraction of the buffer's size (also serves to leave room to append the sequence number).
  913.         // Below: i.e. this control's title (e.g. List) in contained entirely
  914.         // within the leading part of the user specified title (e.g. ListBox).
  915.         // Even though this is incorrect, the appending of the sequence number
  916.         // in the second comparison will weed out any false matches.
  917.         // Note: since some controls end in a number (e.g. SysListView32),
  918.         // it would not be easy to parse out the user's sequence number to
  919.         // simplify/accelerate the search here.  So instead, use a method
  920.         // more certain to work even though it's a little ugly.  It's also
  921.         // necessary to do this in a way functionally identical to the below
  922.         // so that Window Spy's sequence numbers match the ones generated here:
  923.         // Concerning strnicmp(), see lstrcmpi note below for why a locale-insensitive match isn't done instead.
  924.         if (length && !strnicmp(ws.mCriterionClass, ws.mCandidateTitle, length)) // Preliminary match of base class name.
  925.         {
  926.             // mAlreadyVisitedCount was initialized to zero by WindowSearch's constructor.  It is used
  927.             // to accumulate how many quasi-matches on this class have been found so far.  Also,
  928.             // comparing ws.mAlreadyVisitedCount to atoi(ws.mCriterionClass + length) would not be
  929.             // the same as the below examples such as the following:
  930.             // Say the ClassNN being searched for is List01 (where List0 is the class name and 1
  931.             // is the sequence number). If a class called "List" exists in the parent window, it
  932.             // would be found above as a preliminary match.  The below would copy "1" into the buffer,
  933.             // which is correctly deemed not to match "01".  By contrast, the atoi() method would give
  934.             // the wrong result because the two numbers are numerically equal.
  935.             _itoa(++ws.mAlreadyVisitedCount, ws.mCandidateTitle, 10);  // Overwrite the buffer to contain only the count.
  936.             // lstrcmpi() is not used: 1) avoids breaking exisitng scripts; 2) provides consistent behavior
  937.             // across multiple locales:
  938.             if (!stricmp(ws.mCandidateTitle, ws.mCriterionClass + length)) // The counts match too, so it's a full match.
  939.             {
  940.                 ws.mFoundChild = aWnd; // Save this in here for return to the caller.
  941.                 return FALSE; // stop the enumeration.
  942.             }
  943.         }
  944.     }
  945.     else // Caller told us to search by the text of the control (e.g. the text printed on a button)
  946.     {
  947.         // Use GetWindowText() rather than GetWindowTextTimeout() because we don't want to find
  948.         // the name accidentally in the vast amount of text present in some edit controls (e.g.
  949.         // if the script's source code is open for editing in notepad, GetWindowText() would
  950.         // likely find an unwanted match for just about anything).  In addition,
  951.         // GetWindowText() is much faster.  Update: Yes, it seems better not to use
  952.         // GetWindowTextByTitleMatchMode() in this case, since control names tend to be so
  953.         // short (i.e. they would otherwise be very likely to be undesirably found in any large
  954.         // edit controls the target window happens to own).  Update: Changed from strstr()
  955.         // to strncmp() for greater selectivity.  Even with this degree of selectivity, it's
  956.         // still possible to have ambiguous situations where a control can't be found due
  957.         // to its title being entirely contained within that of another (e.g. a button
  958.         // with title "Connect" would be found in the title of a button "Connect All").
  959.         // The only way to address that would be to insist on an entire title match, but
  960.         // that might be tedious if the title of the control is very long.  As alleviation,
  961.         // the class name + seq. number method above can often be used instead in cases
  962.         // of such ambiguity.  Update: Using IsTextMatch() now so that user-specified
  963.         // TitleMatchMode will be in effect for this also.  Also, it's case sensitivity
  964.         // helps increase selectivity, which is helpful due to how common short or ambiguous
  965.         // control names tend to be:
  966.         GetWindowText(aWnd, ws.mCandidateTitle, sizeof(ws.mCandidateTitle));
  967.         if (IsTextMatch(ws.mCandidateTitle, ws.mCriterionText))
  968.         {
  969.             ws.mFoundChild = aWnd; // save this in here for return to the caller.
  970.             return FALSE;
  971.         }
  972.     }
  973.     // Note: The MSDN docs state that EnumChildWindows already handles the
  974.     // recursion for us: "If a child window has created child windows of its own,
  975.     // EnumChildWindows() enumerates those windows as well."
  976.     return TRUE; // Keep searching.
  977. }
  978.  
  979.  
  980.  
  981. int MsgBox(int aValue)
  982. {
  983.     char str[128];
  984.     snprintf(str, sizeof(str), "Value = %d (0x%X)", aValue, aValue);
  985.     return MsgBox(str);
  986. }
  987.  
  988.  
  989.  
  990. int MsgBox(char *aText, UINT uType, char *aTitle, double aTimeout, HWND aOwner)
  991. // Returns 0 if the attempt failed because of too many existing MessageBox windows,
  992. // or if MessageBox() itself failed.
  993. {
  994.     // Set the below globals so that any WM_TIMER messages dispatched by this call to
  995.     // MsgBox() (which may result in a recursive call back to us) know not to display
  996.     // any more MsgBoxes:
  997.     if (g_nMessageBoxes > MAX_MSGBOXES + 1)  // +1 for the final warning dialog.  Verified correct.
  998.         return 0;
  999.  
  1000.     if (g_nMessageBoxes == MAX_MSGBOXES)
  1001.     {
  1002.         // Do a recursive call to self so that it will be forced to the foreground.
  1003.         // But must increment this so that the recursive call allows the last MsgBox
  1004.         // to be displayed:
  1005.         ++g_nMessageBoxes;
  1006.         MsgBox("The maximum number of MsgBoxes has been reached.");
  1007.         --g_nMessageBoxes;
  1008.         return 0;
  1009.     }
  1010.  
  1011.     // Set these in case the caller explicitly called it with a NULL, overriding the default:
  1012.     if (!aText)
  1013.         aText = "";
  1014.     if (!aTitle || !*aTitle)
  1015.         // If available, the script's filename seems a much better title in case the user has
  1016.         // more than one script running:
  1017.         aTitle = (g_script.mFileName && *g_script.mFileName) ? g_script.mFileName : NAME_PV;
  1018.  
  1019.     // It doesn't feel safe to modify the contents of the caller's aText and aTitle,
  1020.     // even if the caller were to tell us it is modifiable.  This is because the text
  1021.     // might be the actual contents of a variable, which we wouldn't want to truncate,
  1022.     // even temporarily, since other hotkeys can fire while this hotkey subroutine is
  1023.     // suspended, and those subroutines may refer to the contents of this (now-altered)
  1024.     // variable.  In addition, the text may reside in the clipboard's locked memory
  1025.     // area, and altering that might result in the clipboard's contents changing
  1026.     // when MsgSleep() closes the clipboard for us (after we display our dialog here).
  1027.     // Even though testing reveals that the contents aren't altered (somehow), it
  1028.     // seems best to have our own local, limited length versions here:
  1029.     // Note: 8000 chars is about the max you could ever fit on-screen at 1024x768 on some
  1030.     // XP systems, but it will hold much more before refusing to display at all (i.e.
  1031.     // MessageBox() returning failure), perhaps about 150K:
  1032.     char text[MSGBOX_TEXT_SIZE];
  1033.     char title[DIALOG_TITLE_SIZE];
  1034.     strlcpy(text, aText, sizeof(text));
  1035.     strlcpy(title, aTitle, sizeof(title));
  1036.  
  1037.     uType |= MB_SETFOREGROUND;  // Always do these so that caller doesn't have to specify.
  1038.  
  1039.     // In the below, make the MsgBox owned by the topmost window rather than our main
  1040.     // window, in case there's another modal dialog already displayed.  The forces the
  1041.     // user to deal with the modal dialogs starting with the most recent one, which
  1042.     // is what we want.  Otherwise, if a middle dialog was dismissed, it probably
  1043.     // won't be able to return which button was pressed to its original caller.
  1044.     // UPDATE: It looks like these modal dialogs can't own other modal dialogs,
  1045.     // so disabling this:
  1046.     /*
  1047.     HWND topmost = GetTopWindow(g_hWnd);
  1048.     if (!topmost) // It has no child windows.
  1049.         topmost = g_hWnd;
  1050.     */
  1051.  
  1052.     // Unhide the main window, but have it minimized.  This creates a task
  1053.     // bar button so that it's easier the user to remember that a dialog
  1054.     // is open and waiting (there are probably better ways to handle
  1055.     // this whole thing).  UPDATE: This isn't done because it seems
  1056.     // best not to have the main window be inaccessible until the
  1057.     // dialogs are dismissed (in case ever want to use it to display
  1058.     // status info, etc).  It seems that MessageBoxes get their own
  1059.     // task bar button when they're not AppModal, which is one of the
  1060.     // main things I wanted, so that's good too):
  1061. //    if (!IsWindowVisible(g_hWnd) || !IsIconic(g_hWnd))
  1062. //        ShowWindowAsync(g_hWnd, SW_SHOWMINIMIZED);
  1063.  
  1064.     /*
  1065.     If the script contains a line such as "#y::MsgBox, test", and a hotkey is used
  1066.     to activate Windows Explorer and another hotkey is then used to invoke a MsgBox,
  1067.     that MsgBox will be psuedo-minimized or invisible, even though it does have the
  1068.     input focus.  This attempt to fix it didn't work, so something is probably checking
  1069.     the physical key state of LWIN/RWIN and seeing that they're down:
  1070.     modLR_type modLR_now = GetModifierLRState();
  1071.     modLR_type win_keys_down = modLR_now & (MOD_LWIN | MOD_RWIN);
  1072.     if (win_keys_down)
  1073.         SetModifierLRStateSpecific(win_keys_down, modLR_now, KEYUP);
  1074.     */
  1075.  
  1076.     // Note: Even though when multiple messageboxes exist, they might be
  1077.     // destroyed via a direct call to their WindowProc from our message pump's
  1078.     // DispatchMessage, or that of another MessageBox's message pump, it
  1079.     // appears that MessageBox() is designed to be called recursively like
  1080.     // this, since it always returns the proper result for the button on the
  1081.     // actual MessageBox it originally invoked.  In other words, if a bunch
  1082.     // of Messageboxes are displayed, and this user dismisses an older
  1083.     // one prior to dealing with a newer one, all the MessageBox()
  1084.     // return values will still wind up being correct anyway, at least
  1085.     // on XP.  The only downside to the way this is designed is that
  1086.     // the keyboard can't be used to navigate the buttons on older
  1087.     // messageboxes (only the most recent one).  This is probably because
  1088.     // the message pump of MessageBox() isn't designed to properly dispatch
  1089.     // keyboard messages to other MessageBox window instances.  I tried
  1090.     // to fix that by making our main message pump handle all messages
  1091.     // for all dialogs, but that turns out to be pretty complicated, so
  1092.     // I abandoned it for now.
  1093.  
  1094.     // Note: It appears that MessageBox windows, and perhaps all modal dialogs in general,
  1095.     // cannot own other windows.  That's too bad because it would have allowed each new
  1096.     // MsgBox window to be owned by any previously existing one, so that the user would
  1097.     // be forced to close them in order if they were APPL_MODAL.  But it's not too big
  1098.     // an issue since the only disadvantage is that the keyboard can't be use to
  1099.     // to navigate in MessageBoxes other than the most recent.  And it's actually better
  1100.     // the way it is now in the sense that the user can dismiss the messageboxes out of
  1101.     // order, which might (in rare cases) be desirable.
  1102.  
  1103.     if (aTimeout > 2147483) // This is approximately the max number of seconds that SetTimer can handle.
  1104.         aTimeout = 2147483;
  1105.     if (aTimeout < 0) // But it can be equal to zero to indicate no timeout at all.
  1106.         aTimeout = 0.1;  // A value that might cue the user that something is wrong.
  1107.     // For the above:
  1108.     // MsgBox's smart comma handling will usually prevent negatives due to the fact that it considers
  1109.     // a negative to be part of the text param.  But if it does happen, timeout after a short time,
  1110.     // which may signal the user that the script passed a bad parameter.
  1111.  
  1112.     // v1.0.33: The following is a workaround for the fact that an MsgBox with only an OK button
  1113.     // doesn't obey EndDialog()'s parameter:
  1114.     g.DialogHWND = NULL;
  1115.     g.MsgBoxTimedOut = false;
  1116.  
  1117.     // At this point, we know a dialog will be displayed.  See macro's comments for details:
  1118.     DIALOG_PREP // Must be done prior to POST_AHK_DIALOG() below.
  1119.     POST_AHK_DIALOG((DWORD)(aTimeout * 1000))
  1120.  
  1121.     ++g_nMessageBoxes;  // This value will also be used as the Timer ID if there's a timeout.
  1122.     g.MsgBoxResult = MessageBox(aOwner, text, title, uType);
  1123.     --g_nMessageBoxes;
  1124.     // Above's use of aOwner: MsgBox, FileSelectFile, and other dialogs seem to consider aOwner to be NULL
  1125.     // when aOwner is minimized or hidden.
  1126.  
  1127.     DIALOG_END
  1128.  
  1129.     // If there's a timer, kill it for performance reasons since it's no longer needed.
  1130.     // Actually, this isn't easy to do because we don't know what the HWND of the MsgBox
  1131.     // window was, so don't bother:
  1132.     //if (aTimeout != 0.0)
  1133.     //    KillTimer(...);
  1134.  
  1135. //    if (!g_nMessageBoxes)
  1136. //        ShowWindowAsync(g_hWnd, SW_HIDE);  // Hide the main window if it no longer has any child windows.
  1137. //    else
  1138.  
  1139.     // This is done so that the next message box of ours will be brought to the foreground,
  1140.     // to remind the user that they're still out there waiting, and for convenience.
  1141.     // Update: It seems bad to do this in cases where the user intentionally wants the older
  1142.     // messageboxes left in the background, to deal with them later.  So, in those cases,
  1143.     // we might be doing more harm than good because the user's foreground window would
  1144.     // be intrusively changed by this:
  1145.     //WinActivateOurTopDialog();
  1146.  
  1147.     // The following comment is apparently not always true -- sometimes the AHK_TIMEOUT from
  1148.     // EndDialog() is received correctly.  But I haven't discovered the circumstances of how
  1149.     // and why the behavior varies:
  1150.     // Unfortunately, it appears that MessageBox() will return zero rather
  1151.     // than AHK_TIMEOUT that was specified in EndDialog() at least under WinXP.
  1152.     if (g.MsgBoxTimedOut || (!g.MsgBoxResult && aTimeout > 0)) // v1.0.33: Added g.MsgBoxTimedOut, see comment higher above.
  1153.         // Assume it timed out rather than failed, since failure should be VERY rare.
  1154.         g.MsgBoxResult = AHK_TIMEOUT;
  1155.     // else let the caller handle the display of the error message because only it knows
  1156.     // whether to also tell the user something like "the script will not continue".
  1157.     return g.MsgBoxResult;
  1158. }
  1159.  
  1160.  
  1161.  
  1162. HWND FindOurTopDialog()
  1163. // Returns the HWND of our topmost MsgBox or FileOpen dialog (and perhaps other types of modal
  1164. // dialogs if they are of class #32770) even if it wasn't successfully brought to
  1165. // the foreground here.
  1166. // Using Enum() seems to be the only easy way to do this, since these modal MessageBoxes are
  1167. // *owned*, not children of the main window.  There doesn't appear to be any easier way to
  1168. // find out which windows another window owns.  GetTopWindow(), GetActiveWindow(), and GetWindow()
  1169. // do not work for this purpose.  And using FindWindow() discouraged because it can hang
  1170. // in certain circumtances (Enum is probably just as fast anyway).
  1171. {
  1172.     // The return value of EnumWindows() is probably a raw indicator of success or failure,
  1173.     // not whether the Enum found something or continued all the way through all windows.
  1174.     // So don't bother using it.
  1175.     pid_and_hwnd_type pid_and_hwnd;
  1176.     pid_and_hwnd.pid = GetCurrentProcessId();
  1177.     pid_and_hwnd.hwnd = NULL;  // Init.  Called function will set this for us if it finds a match.
  1178.     EnumWindows(EnumDialog, (LPARAM)&pid_and_hwnd);
  1179.     return pid_and_hwnd.hwnd;
  1180. }
  1181.  
  1182.  
  1183.  
  1184. BOOL CALLBACK EnumDialog(HWND aWnd, LPARAM lParam)
  1185. // lParam should be a pointer to a ProcessId (ProcessIds are always non-zero?)
  1186. // To continue enumeration, the function must return TRUE; to stop enumeration, it must return FALSE. 
  1187. {
  1188.     pid_and_hwnd_type &pah = *(pid_and_hwnd_type *)lParam;  // For performance and convenience.
  1189.     if (!lParam || !pah.pid) return FALSE;
  1190.     DWORD pid;
  1191.     GetWindowThreadProcessId(aWnd, &pid);
  1192.     if (pid == pah.pid)
  1193.     {
  1194.         char buf[32];
  1195.         GetClassName(aWnd, buf, sizeof(buf));
  1196.         // This is the class name for windows created via MessageBox(), GetOpenFileName(), and probably
  1197.         // other things that use modal dialogs:
  1198.         if (!strcmp(buf, "#32770"))
  1199.         {
  1200.             pah.hwnd = aWnd;  // An output value for the caller.
  1201.             return FALSE;  // We're done.
  1202.         }
  1203.     }
  1204.     return TRUE;  // Keep searching.
  1205. }
  1206.  
  1207.  
  1208.  
  1209. struct owning_struct {HWND owner_hwnd; HWND first_child;};
  1210. HWND WindowOwnsOthers(HWND aWnd)
  1211. // Only finds owned windows if they are visible, by design.
  1212. {
  1213.     owning_struct own = {aWnd, NULL};
  1214.     EnumWindows(EnumParentFindOwned, (LPARAM)&own);
  1215.     return own.first_child;
  1216. }
  1217.  
  1218.  
  1219.  
  1220. BOOL CALLBACK EnumParentFindOwned(HWND aWnd, LPARAM lParam)
  1221. {
  1222.     HWND owner_hwnd = GetWindow(aWnd, GW_OWNER);
  1223.     // Note: Many windows seem to own other invisible windows that have blank titles.
  1224.     // In our case, require that it be visible because we don't want to return an invisible
  1225.     // window to the caller because such windows aren't designed to be activated:
  1226.     if (owner_hwnd && owner_hwnd == ((owning_struct *)lParam)->owner_hwnd && IsWindowVisible(aWnd))
  1227.     {
  1228.         ((owning_struct *)lParam)->first_child = aWnd;
  1229.         return FALSE; // Match found, we're done.
  1230.     }
  1231.     return TRUE;  // Continue enumerating.
  1232. }
  1233.  
  1234.  
  1235.  
  1236. HWND GetNonChildParent(HWND aWnd)
  1237. // Returns the first ancestor of aWnd that isn't itself a child.  aWnd itself is returned if
  1238. // it is not a child.  Returns NULL only if aWnd is NULL.  Also, it should always succeed
  1239. // based on the axiom that any window with the WS_CHILD style (aka WS_CHILDWINDOW) must have
  1240. // a non-child ancestor somewhere up the line.
  1241. // This function doesn't do anything special with owned vs. unowned windows.  Despite what MSDN
  1242. // says, GetParent() does not return the owner window, at least in some cases on Windows XP
  1243. // (e.g. BulletProof FTP Server). It returns NULL instead. In any case, it seems best not to
  1244. // worry about owner windows for this function's caller (MouseGetPos()), since it might be
  1245. // desirable for that command to return the owner window even though it can't actually be
  1246. // activated.  This is because attempts to activate an owner window should automatically cause
  1247. // the OS to activate the topmost owned window instead.  In addition, the owner window may
  1248. // contain the actual title or text that the user is interested in.  UPDATE: Due to the fact
  1249. // that this function retrieves the first parent that's not a child window, it's likely that
  1250. // that window isn't its owner anyway (since the owner problem usually applies to a parent
  1251. // window being owned by some controlling window behind it).
  1252. {
  1253.     if (!aWnd) return aWnd;
  1254.     HWND parent, parent_prev;
  1255.     for (parent_prev = aWnd; ; parent_prev = parent)
  1256.     {
  1257.         if (!(GetWindowLong(parent_prev, GWL_STYLE) & WS_CHILD))  // Found the first non-child parent, so return it.
  1258.             return parent_prev;
  1259.         // Because Windows 95 doesn't support GetAncestor(), we'll use GetParent() instead:
  1260.         if (   !(parent = GetParent(parent_prev))   )
  1261.             return parent_prev;  // This will return aWnd if aWnd has no parents.
  1262.     }
  1263. }
  1264.  
  1265.  
  1266.  
  1267. HWND GetTopChild(HWND aParent)
  1268. {
  1269.     if (!aParent) return aParent;
  1270.     HWND hwnd_top, next_top;
  1271.     // Get the topmost window of the topmost window of...
  1272.     // i.e. Since child windows can also have children, we keep going until
  1273.     // we reach the "last topmost" window:
  1274.     for (hwnd_top = GetTopWindow(aParent)
  1275.         ; hwnd_top && (next_top = GetTopWindow(hwnd_top))
  1276.         ; hwnd_top = next_top);
  1277.  
  1278.     //if (!hwnd_top)
  1279.     //{
  1280.     //    MsgBox("no top");
  1281.     //    return FAIL;
  1282.     //}
  1283.     //else
  1284.     //{
  1285.     //    //if (GetTopWindow(hwnd_top))
  1286.     //    //    hwnd_top = GetTopWindow(hwnd_top);
  1287.     //    char class_name[64];
  1288.     //    GetClassName(next_top, class_name, sizeof(class_name));
  1289.     //    MsgBox(class_name);
  1290.     //}
  1291.  
  1292.     return hwnd_top ? hwnd_top : aParent;  // Caller relies on us never returning NULL if aParent is non-NULL.
  1293. }
  1294.  
  1295.  
  1296.  
  1297. bool IsWindowHung(HWND aWnd)
  1298. {
  1299.     if (!aWnd) return false;
  1300.  
  1301.     // OLD, SLOWER METHOD:
  1302.     // Don't want to use a long delay because then our messages wouldn't get processed
  1303.     // in a timely fashion.  But I'm not entirely sure if the 10ms delay used below
  1304.     // is even used by the function in this case?  Also, the docs aren't clear on whether
  1305.     // the function returns success or failure if the window is hung (probably failure).
  1306.     // If failure, perhaps you have to call GetLastError() to determine whether it failed
  1307.     // due to being hung or some other reason?  Does the output param dwResult have any
  1308.     // useful info in this case?  I expect what will happen is that in most cases, the OS
  1309.     // will already know that the window is hung.  However, if the window just became hung
  1310.     // in the last 5 seconds, I think it may take the remainder of the 5 seconds for the OS
  1311.     // to notice it.  However, allowing it the option of sleeping up to 5 seconds seems
  1312.     // really bad, since keyboard and mouse input would probably be frozen (actually it
  1313.     // would just be really laggy because the OS would bypass the hook during that time).
  1314.     // So some compromise value seems in order.  500ms seems about right.  UPDATE: Some
  1315.     // windows might need longer than 500ms because their threads are engaged in
  1316.     // heavy operations.  Since this method is only used as a fallback method now,
  1317.     // it seems best to give them the full 5000ms default, which is what (all?) Windows
  1318.     // OSes use as a cutoff to determine whether a window is "not responding":
  1319.     DWORD dwResult;
  1320.     #define Slow_IsWindowHung !SendMessageTimeout(aWnd, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, 5000, &dwResult)
  1321.  
  1322.     // NEW, FASTER METHOD:
  1323.     // This newer method's worst-case performance is at least 30x faster than the worst-case
  1324.     // performance of the old method that  uses SendMessageTimeout().
  1325.     // And an even worse case can be envisioned which makes the use of this method
  1326.     // even more compelling: If the OS considers a window NOT to be hung, but the
  1327.     // window's message pump is sluggish about responding to the SendMessageTimeout() (perhaps
  1328.     // taking 2000ms or more to respond due to heavy disk I/O or other activity), the old method
  1329.     // will take several seconds to return, causing mouse and keyboard lag if our hook(s)
  1330.     // are installed; not to mention making our app's windows, tray menu, and other GUI controls
  1331.     // unresponsive during that time).  But I believe in this case the new method will return
  1332.     // instantly, since the OS has been keeping track in the background, and can tell us
  1333.     // immediately that the window isn't hung.
  1334.     // Here are some seemingly contradictory statements uttered by MSDN.  Perhaps they're
  1335.     // not contradictory if the first sentence really means "by a different thread of the same
  1336.     // process":
  1337.     // "If the specified window was created by a different thread, the system switches to that
  1338.     // thread and calls the appropriate window procedure.  Messages sent between threads are
  1339.     // processed only when the receiving thread executes message retrieval code. The sending
  1340.     // thread is blocked until the receiving thread processes the message."
  1341.     if (g_os.IsWin9x())
  1342.     {
  1343.         typedef BOOL (WINAPI *MyIsHungThread)(DWORD);
  1344.         static MyIsHungThread IsHungThread = (MyIsHungThread)GetProcAddress(GetModuleHandle("user32")
  1345.             , "IsHungThread");
  1346.         // When function not available, fall back to the old method:
  1347.         return IsHungThread ? IsHungThread(GetWindowThreadProcessId(aWnd, NULL)) : Slow_IsWindowHung;
  1348.     }
  1349.  
  1350.     // Otherwise: NT/2k/XP/2003 or later, so try to use the newer method.
  1351.     // The use of IsHungAppWindow() (supported under Win2k+) is discouraged by MS,
  1352.     // but it's useful to prevent the script from getting hung when it tries to do something
  1353.     // to a hung window.
  1354.     typedef BOOL (WINAPI *MyIsHungAppWindow)(HWND);
  1355.     static MyIsHungAppWindow IsHungAppWindow = (MyIsHungAppWindow)GetProcAddress(GetModuleHandle("user32")
  1356.         , "IsHungAppWindow");
  1357.     return IsHungAppWindow ? IsHungAppWindow(aWnd) : Slow_IsWindowHung;
  1358. }
  1359.  
  1360.  
  1361.  
  1362. int GetWindowTextTimeout(HWND aWnd, char *aBuf, int aBufSize, UINT aTimeout)
  1363. // This function must be kept thread-safe because it may be called (indirectly) by hook thread too.
  1364. // aBufSize is an int so that any negative values passed in from caller are not lost.
  1365. // Returns the length of what would be copied (not including the zero terminator).
  1366. // In addition, if aBuf is not NULL, the window text is copied into aBuf (not to exceed aBufSize).
  1367. // AutoIt3 author indicates that using WM_GETTEXT vs. GetWindowText() sometimes yields more text.
  1368. // Perhaps this is because GetWindowText() has built-in protection against hung windows and
  1369. // thus isn't actually sending WM_GETTEXT.  The method here is hopefully the best of both worlds
  1370. // (protection against hung windows causing our thread to hang, and getting more text).
  1371. // Another tidbit from MSDN about SendMessage() that might be of use here sometime:
  1372. // "However, the sending thread will process incoming nonqueued (those sent directly to a window
  1373. // procedure) messages while waiting for its message to be processed. To prevent this, use
  1374. // SendMessageTimeout with SMTO_BLOCK set."  Currently not using SMTO_BLOCK because it
  1375. // doesn't seem necessary.
  1376. // Update: GetWindowText() is so much faster than SendMessage() and SendMessageTimeout(), at
  1377. // least on XP, so GetWindowTextTimeout() should probably only be used when getting the max amount
  1378. // of text is important (e.g. this function can fetch the text in a RichEdit20A control and
  1379. // other edit controls, whereas GetWindowText() doesn't).  This function is used to implement
  1380. // things like WinGetText and ControlGetText, in which getting the maximum amount and types
  1381. // of text is more important than performance.
  1382. {
  1383.     if (!aWnd || (aBuf && aBufSize < 1)) // No HWND or no room left in buffer (some callers rely on this check).
  1384.         return 0; // v1.0.40.04: Fixed to return 0 rather than setting aBuf to NULL and continuing (callers don't want that).
  1385.  
  1386.     // Override for Win95 because AutoIt3 author says it might crash otherwise:
  1387.     if (aBufSize > WINDOW_TEXT_SIZE && g_os.IsWin95())
  1388.         aBufSize = WINDOW_TEXT_SIZE;
  1389.  
  1390.     LRESULT result, length;
  1391.     if (aBuf)
  1392.     {
  1393.         *aBuf = '\0';  // Init just to get it out of the way in case of early return/error.
  1394.         if (aBufSize == 1) // Room only for the terminator, so go no further (some callers rely on this check).
  1395.             return 0;
  1396.  
  1397.         // Below demonstrated that GetWindowText() is dramatically faster than either SendMessage()
  1398.         // or SendMessageTimeout() (noticeably faster when you have hotkeys that activate
  1399.         // windows, or toggle between two windows):
  1400.         //return GetWindowText(aWnd, aBuf, aBufSize);
  1401.         //return (int)SendMessage(aWnd, WM_GETTEXT, (WPARAM)aBufSize, (LPARAM)aBuf);
  1402.  
  1403.         // Don't bother calling IsWindowHung() because the below call will return
  1404.         // nearly instantly if the OS already "knows" that the target window has
  1405.         // be unresponsive for 5 seconds or so (i.e. it keeps track of such things
  1406.         // on an ongoing basis, at least XP seems to).
  1407.         result = SendMessageTimeout(aWnd, WM_GETTEXT, (WPARAM)aBufSize, (LPARAM)aBuf
  1408.             , SMTO_ABORTIFHUNG, aTimeout, (LPDWORD)&length);
  1409.         if (length >= aBufSize) // Happens sometimes (at least ==aBufSize) for apps that wrongly include the terminator in the reported length.
  1410.             length = aBufSize - 1; // Override.
  1411.  
  1412.         // v1.0.40.04: The following check was added because when the text is too large to to fit in the
  1413.         // buffer, the OS (or at least certain applications such as AIM) return a length that *includes*
  1414.         // the zero terminator, violating the documented behavior of WM_GETTEXT.  In case the returned
  1415.         // length is too long by 1 (or even more than 1), calculate the length explicitly by checking if
  1416.         // there's another terminator to the left of the indicated length.  The following loop
  1417.         // is used in lieu of strlen() for performance reasons (because sometimes the text is huge).
  1418.         // It assumes that there will be no more than one additional terminator to the left of the
  1419.         // indicated length, which so far seems to be true:
  1420.         for (char *cp = aBuf + length; cp >= aBuf; --cp)
  1421.         {
  1422.             if (!*cp)
  1423.             {
  1424.                 // Keep going to the left until the last consecutive terminator is found.
  1425.                 // Necessary for AIM when compiled in release mode (but not in debug mode
  1426.                 // for some reason!):
  1427.                 for (; cp > aBuf && !cp[-1]; --cp); // Self-contained loop.  Verified correct.
  1428.                 length = cp - aBuf;
  1429.                 break;
  1430.             }
  1431.         }
  1432.         // If the above loop didn't "break", a terminator wasn't found.
  1433.         // Terminate explicitly because MSDN docs aren't clear that it will always be terminated automatically.
  1434.         // Update: This also protects against misbehaving apps that might handle the WM_GETTEXT message
  1435.         // rather than passing it to DefWindowProc() but that don't terminate the buffer.  This has been
  1436.         // confirmed to be necessary at least for AIM when aBufSize==1 (although 1 is no longer possible due
  1437.         // to a check that has been added further above):
  1438.         aBuf[length] = '\0';
  1439.     }
  1440.     else
  1441.     {
  1442.         result = SendMessageTimeout(aWnd, WM_GETTEXTLENGTH, 0, 0, SMTO_ABORTIFHUNG, aTimeout, (LPDWORD)&length);
  1443.         // The following can be temporarily uncommented out to demonstrate how some apps such as AIM's
  1444.         // write-an-instant-message window have some controls that respond to WM_GETTEXTLENGTH with a
  1445.         // length that's completely different than the length with which they respond to WM_GETTEXT.
  1446.         // Here are some of the discrepancies:
  1447.         // WM_GETTEXTLENGTH vs. WM_GETTEXT:
  1448.         // 92 vs. 318 (bigger)
  1449.         // 50 vs. 159 (bigger)
  1450.         // 3 vs. 0 (smaller)
  1451.         // 24 vs. 88 (etc.)
  1452.         // 80 vs. 188
  1453.         // 24 vs. 88
  1454.         // 80 vs. 188
  1455.         //char buf[32000];
  1456.         //LRESULT length2;
  1457.         //result = SendMessageTimeout(aWnd, WM_GETTEXT, (WPARAM)sizeof(buf), (LPARAM)buf
  1458.         //    , SMTO_ABORTIFHUNG, aTimeout, (LPDWORD)&length2);
  1459.         //if (length2 != length)
  1460.         //{
  1461.         //    int x = 0;  // Put breakpoint here.
  1462.         //}
  1463.         // An attempt to fix the size estimate to be larger for misbehaving apps like AIM, but it's ineffective
  1464.         // so commented out:
  1465.         //if (!length)
  1466.         //    length = GetWindowTextLength(aWnd);
  1467.     }
  1468.  
  1469.     // "length" contains the length of what was (or would have been) copied, not including the terminator:
  1470.     return result ? (int)length : 0;  // "result" is zero upon failure or timeout.
  1471. }
  1472.  
  1473.  
  1474.  
  1475. ///////////////////////////////////////////////////////////////////////////
  1476.  
  1477.  
  1478.  
  1479. ResultType WindowSearch::SetCriteria(global_struct &aSettings, char *aTitle, char *aText, char *aExcludeTitle, char *aExcludeText)
  1480. // Returns FAIL if the new criteria can't possibly match a window (due to ahk_id being in invalid
  1481. // window or the specfied ahk_group not existing).  Otherwise, it returns OK.
  1482. // Callers must ensure that aText, aExcludeTitle, and aExcludeText point to buffers whose contents
  1483. // will be available for the entire duration of the search.  In other words, the caller should not
  1484. // call MsgSleep() in a way that would allow another thread to launch and overwrite the contents
  1485. // of the sDeref buffer (which might contain the contents).  Things like mFoundHWND and mFoundCount
  1486. // are not initialized here because sometimes the caller changes the criteria without wanting to
  1487. // reset the search.
  1488. // This function must be kept thread-safe because it may be called (indirectly) by hook thread too.
  1489. {
  1490.     // Set any criteria which are not context sensitive.  It doesn't seem necessary to make copies of
  1491.     // mCriterionText, mCriterionExcludeTitle, and mCriterionExcludeText because they're never altered
  1492.     // here, nor does there seem to be a risk that deref buffer's contents will get overwritten
  1493.     // while this set of criteria is in effect because our callers never allow interrupting script-threads
  1494.     // *during* the duration of any one set of criteria.
  1495.     bool exclude_title_became_non_blank = *aExcludeTitle && !*mCriterionExcludeTitle;
  1496.     mCriterionExcludeTitle = aExcludeTitle;
  1497.     mCriterionExcludeTitleLength = strlen(mCriterionExcludeTitle); // Pre-calculated for performance.
  1498.     mCriterionText = aText;
  1499.     mCriterionExcludeText = aExcludeText;
  1500.     mSettings = &aSettings;
  1501.  
  1502.     DWORD orig_criteria = mCriteria;
  1503.     char *ahk_flag, *cp, buf[MAX_VAR_NAME_LENGTH + 1];
  1504.     int criteria_count;
  1505.     size_t size;
  1506.  
  1507.     for (mCriteria = 0, ahk_flag = aTitle, criteria_count = 0;; ++criteria_count, ahk_flag += 4) // +4 only since an "ahk_" string that isn't qualified may have been found.
  1508.     {
  1509.         if (   !(ahk_flag = strcasestr(ahk_flag, "ahk_"))   ) // No other special strings are present.
  1510.         {
  1511.             if (!criteria_count) // Since no special "ahk_" criteria were present, it is CRITERION_TITLE by default.
  1512.             {
  1513.                 mCriteria = CRITERION_TITLE; // In this case, there is only one criterion.
  1514.                 strlcpy(mCriterionTitle, aTitle, sizeof(mCriterionTitle));
  1515.                 mCriterionTitleLength = strlen(mCriterionTitle); // Pre-calculated for performance.
  1516.             }
  1517.             break;
  1518.         }
  1519.         // Since above didn't break, another instance of "ahk_" has been found. To reduce ambiguity,
  1520.         // the following requires that any "ahk_" criteria beyond the first be preceded by at least
  1521.         // one space or tab:
  1522.         if (criteria_count && !IS_SPACE_OR_TAB(ahk_flag[-1])) // Relies on short-circuit boolean order.
  1523.         {
  1524.             --criteria_count; // Decrement criteria_count to compensate for the loop's increment.
  1525.             continue;
  1526.         }
  1527.         // Since above didn't "continue", it meets the basic test.  But is it an exact match for one of the
  1528.         // special criteria strings?  If not, it's really part of the title criterion instead.
  1529.         cp = ahk_flag + 4;
  1530.         if (!strnicmp(cp, "id", 2))
  1531.         {
  1532.             cp += 2;
  1533.             mCriteria |= CRITERION_ID;
  1534.             mCriterionHwnd = (HWND)ATOU64(cp);
  1535.             // Note that this can validly be the HWND of a child window; i.e. ahk_id %ChildWindowHwnd% is supported.
  1536.             if (mCriterionHwnd != HWND_BROADCAST && !IsWindow(mCriterionHwnd)) // Checked here once rather than each call to IsMatch().
  1537.             {
  1538.                 mCriterionHwnd = NULL;
  1539.                 return FAIL; // Inform caller of invalid criteria.  No need to do anything else further below.
  1540.             }
  1541.         }
  1542.         else if (!strnicmp(cp, "pid", 3))
  1543.         {
  1544.             cp += 3;
  1545.             mCriteria |= CRITERION_PID;
  1546.             mCriterionPID = ATOU(cp);
  1547.         }
  1548.         else if (!strnicmp(cp, "class", 5))
  1549.         {
  1550.             cp += 5;
  1551.             mCriteria |= CRITERION_CLASS;
  1552.             // In the following line, it may have been preferable to skip only zero or one spaces rather than
  1553.             // calling omit_leading_whitespace().  But now this should probably be kept for backward compatibility.
  1554.             // Besides, even if it's possible for a class name to start with a space, a RegEx dot or other symbol
  1555.             // can be used to match it via SetTitleMatchMode RegEx.
  1556.             strlcpy(mCriterionClass, omit_leading_whitespace(cp), sizeof(mCriterionClass)); // Copy all of the remaining string to simplify the below.
  1557.             for (cp = mCriterionClass; cp = strstr(cp, "ahk_"); cp += 4)
  1558.             {
  1559.                 // This loop truncates any other criteria from the class criteria.  It's not a complete
  1560.                 // solution because it doesn't validate that what comes after the "ahk_" string is a
  1561.                 // valid criterion name. But for it not to be and yet also be part of some valid class
  1562.                 // name seems far too unlikely to worry about.  It would have to be a legitimate class name
  1563.                 // such as "ahk_class SomeClassName ahk_wrong".
  1564.                 if (cp == mCriterionClass) // This check prevents underflow in the next check.
  1565.                 {
  1566.                     *cp = '\0';
  1567.                     break;
  1568.                 }
  1569.                 else
  1570.                     if (IS_SPACE_OR_TAB(cp[-1]))
  1571.                     {
  1572.                         cp[-1] = '\0';
  1573.                         break;
  1574.                     }
  1575.                     //else assume this "ahk_" string is part of the literal text, continue looping in case
  1576.                     // there is a legitimate "ahk_" string after this one.
  1577.             } // for()
  1578.         }
  1579.         else if (!strnicmp(cp, "group", 5))
  1580.         {
  1581.             cp += 5;
  1582.             mCriteria |= CRITERION_GROUP;
  1583.             strlcpy(buf, omit_leading_whitespace(cp), sizeof(buf));
  1584.             if (cp = StrChrAny(buf, " \t")) // Group names can't contain spaces, so terminate at the first one to exclude any "ahk_" criteria that come afterward.
  1585.                 *cp = '\0';
  1586.             if (   !(mCriterionGroup = g_script.FindGroup(buf))   )
  1587.                 return FAIL; // No such group: Inform caller of invalid criteria.  No need to do anything else further below.
  1588.         }
  1589.         else // It doesn't qualify as a special criteria name even though it starts with "ahk_".
  1590.         {
  1591.             --criteria_count; // Decrement criteria_count to compensate for the loop's increment.
  1592.             continue;
  1593.         }
  1594.         // Since above didn't return or continue, a valid "ahk_" criterion has been discovered.
  1595.         // If this is the first such criterion, any text that lies to its left should be interpreted
  1596.         // as CRITERION_TITLE.  However, for backward compatibility it seems best to disqualify any title
  1597.         // consisting entirely of whitespace.  This is because some scripts might have a variable containing
  1598.         // whitespace followed by the string ahk_class, etc. (however, any such whitespace is included as a
  1599.         // literal part of the title criterion for flexibilty and backward compatibility).
  1600.         if (!criteria_count && ahk_flag > omit_leading_whitespace(aTitle))
  1601.         {
  1602.             mCriteria |= CRITERION_TITLE;
  1603.             // Omit exactly one space or tab from the title criterion. That space or tab is the one
  1604.             // required to delimit the special "ahk_" string.  Any other spaces or tabs to the left of
  1605.             // that one are considered literal (for flexibility):
  1606.             size = ahk_flag - aTitle; // This will always be greater than one due to other checks above, which will result in at least one non-whitespace character in the title criterion.
  1607.             if (size > sizeof(mCriterionTitle)) // Prevent overflow.
  1608.                 size = sizeof(mCriterionTitle);
  1609.             strlcpy(mCriterionTitle, aTitle, size); // Copy only the eligible substring as the criteria.
  1610.             mCriterionTitleLength = strlen(mCriterionTitle); // Pre-calculated for performance.
  1611.         }
  1612.     }
  1613.  
  1614.     // Since this function doesn't change mCandidateParent, there is no need to update the candidate's
  1615.     // attributes unless the type of criterion has changed or if mExcludeTitle became non-blank as
  1616.     // a result of our action above:
  1617.     if (mCriteria != orig_criteria || exclude_title_became_non_blank)
  1618.         UpdateCandidateAttributes(); // In case mCandidateParent isn't NULL, fetch different attributes based on what was set above.
  1619.     //else for performance reasons, avoid unnecessary updates.
  1620.     return OK;
  1621. }
  1622.  
  1623.  
  1624.  
  1625. void WindowSearch::UpdateCandidateAttributes()
  1626. // This function must be kept thread-safe because it may be called (indirectly) by hook thread too.
  1627. {
  1628.     // Nothing to do until SetCandidate() is called with a non-NULL candidate and SetCriteria()
  1629.     // has been called for the first time (otherwise, mCriterionExcludeTitle and other things
  1630.     // are not yet initialized:
  1631.     if (!mCandidateParent || !mCriteria)
  1632.         return;
  1633.     if ((mCriteria & CRITERION_TITLE) || *mCriterionExcludeTitle) // Need the window's title in both these cases.
  1634.         if (!GetWindowText(mCandidateParent, mCandidateTitle, sizeof(mCandidateTitle)))
  1635.             *mCandidateTitle = '\0'; // Failure or blank title is okay.
  1636.     if (mCriteria & CRITERION_PID) // In which case mCriterionPID should already be filled in, though it might be an explicitly specified zero.
  1637.         GetWindowThreadProcessId(mCandidateParent, &mCandidatePID);
  1638.     if (mCriteria & CRITERION_CLASS)
  1639.         GetClassName(mCandidateParent, mCandidateClass, sizeof(mCandidateClass)); // Limit to WINDOW_CLASS_SIZE in this case since that's the maximum that can be searched.
  1640.     // Nothing to do for these:
  1641.     //CRITERION_GROUP:    Can't be pre-processed at this stage.
  1642.     //CRITERION_ID:       It is mCandidateParent, which has already been set by SetCandidate().
  1643. }
  1644.  
  1645.  
  1646.  
  1647. HWND WindowSearch::IsMatch(bool aInvert)
  1648. // Caller must have called SetCriteria prior to calling this method, at least for the purpose of setting
  1649. // mSettings to a valid address (and possibly other reasons).
  1650. // This method returns the HWND of mCandidateParent if it matches the previously specified criteria
  1651. // (title/pid/id/class/group) or NULL otherwise.  Upon NULL, it doesn't reset mFoundParent or mFoundCount
  1652. // in case previous match(es) were found when mFindLastMatch is in effect.
  1653. // Thread-safety: With the following exception, this function must be kept thread-safe because it may be
  1654. // called (indirectly) by hook thread too: The hook thread must never call here directly or indirectly with
  1655. // mArrayStart!=NULL because the corresponding section below is probably not thread-safe.
  1656. {
  1657.     if (!mCandidateParent || !mCriteria) // Nothing to check, so no match.
  1658.         return NULL;
  1659.  
  1660.     if ((mCriteria & CRITERION_TITLE) && *mCriterionTitle) // For performance, avoid the calls below (especially RegEx) when mCriterionTitle is blank (assuming it's even possible for it to be blank under these conditions).
  1661.     {
  1662.         switch(mSettings->TitleMatchMode)
  1663.         {
  1664.         case FIND_ANYWHERE:
  1665.             if (!strstr(mCandidateTitle, mCriterionTitle)) // Suitable even if mCriterionTitle is blank, though that's already ruled out above.
  1666.                 return NULL;
  1667.             break;
  1668.         case FIND_IN_LEADING_PART:
  1669.             if (strncmp(mCandidateTitle, mCriterionTitle, mCriterionTitleLength)) // Suitable even if mCriterionTitle is blank, though that's already ruled out above. If it were possible, mCriterionTitleLength would be 0 and thus strncmp would yield 0 to indicate "strings are equal".
  1670.                 return NULL;
  1671.             break;
  1672.         case FIND_REGEX:
  1673.             if (!RegExMatch(mCandidateTitle, mCriterionTitle))
  1674.                 return NULL;
  1675.             break;
  1676.         default: // Exact match.
  1677.             if (strcmp(mCandidateTitle, mCriterionTitle))
  1678.                 return NULL;
  1679.         }
  1680.         // If above didn't return, it's a match so far so continue onward to the other checks.
  1681.     }
  1682.  
  1683.     if (mCriteria & CRITERION_CLASS) // mCriterionClass is probably always non-blank when CRITERION_CLASS is present (harmless even if it isn't), so *mCriterionClass isn't checked.
  1684.     {
  1685.         if (mSettings->TitleMatchMode == FIND_REGEX)
  1686.         {
  1687.             if (!RegExMatch(mCandidateClass, mCriterionClass))
  1688.                 return NULL;
  1689.         }
  1690.         else // For backward compatibility, all other modes use exact-match for Class.
  1691.             if (strcmp(mCandidateClass, mCriterionClass)) // Doesn't match the required class name.
  1692.                 return NULL;
  1693.         // If nothing above returned, it's a match so far so continue onward to the other checks.
  1694.     }
  1695.  
  1696.     // For the following, mCriterionPID would already be filled in, though it might be an explicitly specified zero.
  1697.     if ((mCriteria & CRITERION_PID) && mCandidatePID != mCriterionPID) // Doesn't match required PID.
  1698.         return NULL;
  1699.     //else it's a match so far, but continue onward in case there are other criteria.
  1700.  
  1701.     // The following also handles the fact that mCriterionGroup might be NULL if the specified group
  1702.     // does not exist or was never successfully created:
  1703.     if ((mCriteria & CRITERION_GROUP) && (!mCriterionGroup || !mCriterionGroup->IsMember(mCandidateParent, *mSettings)))
  1704.         return NULL; // Isn't a member of specified group.
  1705.     //else it's a match so far, but continue onward in case there are other criteria (a little strange in this case, but might be useful).
  1706.  
  1707.     // CRITERION_ID is listed last since in terms of actual calling frequency, this part is hardly ever
  1708.     // executed: It's only ever called this way from WinActive(), and possibly indirectly by an ahk_group
  1709.     // that contains an ahk_id specification.  It's also called by WinGetList()'s EnumWindows(), though
  1710.     // extremely rarely. It's also called this way from other places to determine whether an ahk_id window
  1711.     // matches the other criteria such as WinText, ExcludeTitle, and mAlreadyVisited.
  1712.     // mCriterionHwnd should already be filled in, though it might be an explicitly specified zero.
  1713.     // Note: IsWindow(mCriterionHwnd) was already called by SetCriteria().
  1714.     if ((mCriteria & CRITERION_ID) && mCandidateParent != mCriterionHwnd) // Doesn't match the required HWND.
  1715.         return NULL;
  1716.     //else it's a match so far, but continue onward in case there are other criteria.
  1717.  
  1718.     // The above would have returned if the candidate window isn't a match for what was specified by
  1719.     // the script's WinTitle parameter.  So now check that the ExcludeTitle criterion is satisfied.
  1720.     // This is done prior to checking WinText/ExcludeText for performance reasons:
  1721.  
  1722.     if (*mCriterionExcludeTitle)
  1723.     {
  1724.         switch(mSettings->TitleMatchMode)
  1725.         {
  1726.         case FIND_ANYWHERE:
  1727.             if (strstr(mCandidateTitle, mCriterionExcludeTitle))
  1728.                 return NULL;
  1729.             break;
  1730.         case FIND_IN_LEADING_PART:
  1731.             if (!strncmp(mCandidateTitle, mCriterionExcludeTitle, mCriterionExcludeTitleLength))
  1732.                 return NULL;
  1733.             break;
  1734.         case FIND_REGEX:
  1735.             if (RegExMatch(mCandidateTitle, mCriterionExcludeTitle))
  1736.                 return NULL;
  1737.             break;
  1738.         default: // Exact match.
  1739.             if (!strcmp(mCandidateTitle, mCriterionExcludeTitle))
  1740.                 return NULL;
  1741.         }
  1742.         // If above didn't return, WinTitle and ExcludeTitle are both satisified.  So continue
  1743.         // on below in case there is some WinText or ExcludeText to search.
  1744.     }
  1745.  
  1746.     if (!aInvert) // If caller specified aInvert==true, it will do the below instead of us.
  1747.         for (int i = 0; i < mAlreadyVisitedCount; ++i)
  1748.             if (mCandidateParent == mAlreadyVisited[i])
  1749.                 return NULL;
  1750.  
  1751.     if (*mCriterionText || *mCriterionExcludeText) // It's not quite a match yet since there are more criteria.
  1752.     {
  1753.         // Check the child windows for the specified criteria.
  1754.         // EnumChildWindows() will return FALSE (failure) in at least two common conditions:
  1755.         // 1) It's EnumChildProc callback returned false (i.e. it ended the enumeration prematurely).
  1756.         // 2) The specified parent has no children.
  1757.         // Since in both these cases GetLastError() returns ERROR_SUCCESS, we discard the return
  1758.         // value and just check mFoundChild to determine whether a match has been found:
  1759.         mFoundChild = NULL;  // Init prior to each call, in case mFindLastMatch is true.
  1760.         EnumChildWindows(mCandidateParent, EnumChildFind, (LPARAM)this);
  1761.         if (!mFoundChild) // This parent has no matching child, or no children at all.
  1762.             return NULL;
  1763.     }
  1764.  
  1765.     // Since the above didn't return or none of the checks above were needed, it's a complete match.
  1766.     // If mFindLastMatch is true, this new value for mFoundParent will stay in effect until
  1767.     // overridden by another matching window later:
  1768.     if (!aInvert)
  1769.     {
  1770.         mFoundParent = mCandidateParent;
  1771.         ++mFoundCount; // This must be done prior to the mArrayStart section below.
  1772.     }
  1773.     //else aInvert==true, which means caller doesn't want the above set.
  1774.  
  1775.     if (mArrayStart) // Probably not thread-safe due to FindOrAddVar(), so hook thread must call only with NULL mArrayStart.
  1776.     {
  1777.         // Make it longer than Max var name so that FindOrAddVar() will be able to spot and report
  1778.         // var names that are too long:
  1779.         char var_name[MAX_VAR_NAME_LENGTH + 20];
  1780.         // To help performance (in case the linked list of variables is huge), tell it where
  1781.         // to start the search.  Use the base array name rather than the preceding element because,
  1782.         // for example, Array19 is alphabetially less than Array2, so we can't rely on the
  1783.         // numerical ordering:
  1784.         Var *array_item = g_script.FindOrAddVar(var_name
  1785.             , snprintf(var_name, sizeof(var_name), "%s%u", mArrayStart->mName, mFoundCount)
  1786.             , mArrayStart->IsLocal() ? ALWAYS_USE_LOCAL : ALWAYS_USE_GLOBAL);
  1787.         if (array_item)
  1788.             array_item->AssignHWND(mFoundParent);
  1789.         //else no error reporting currently, since should be very rare.
  1790.     }
  1791.  
  1792.     // Fix for v1.0.30.01: Don't return mFoundParent because its NULL when aInvert is true.
  1793.     // At this stage, the candidate is a known match, so return it:
  1794.     return mCandidateParent;
  1795. }
  1796.  
  1797.  
  1798.  
  1799. ///////////////////////////////////////////////////////////////////
  1800.  
  1801.  
  1802.  
  1803. void SetForegroundLockTimeout()
  1804. {
  1805.     // Even though they may not help in all OSs and situations, this lends peace-of-mind.
  1806.     // (it doesn't appear to help on my XP?)
  1807.     if (g_os.IsWin98orLater() || g_os.IsWin2000orLater())
  1808.     {
  1809.         // Don't check for failure since this operation isn't critical, and don't want
  1810.         // users continually haunted by startup error if for some reason this doesn't
  1811.         // work on their system:
  1812.         if (SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, &g_OriginalTimeout, 0))
  1813.             if (g_OriginalTimeout) // Anti-focus stealing measure is in effect.
  1814.             {
  1815.                 // Set it to zero instead, disabling the measure:
  1816.                 SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, (PVOID)0, SPIF_SENDCHANGE);
  1817. //                if (!SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, (PVOID)0, SPIF_SENDCHANGE))
  1818. //                    MsgBox("Enable focus-stealing: set-call to SystemParametersInfo() failed.");
  1819.             }
  1820. //            else
  1821. //                MsgBox("Enable focus-stealing: it was already enabled.");
  1822. //        else
  1823. //            MsgBox("Enable focus-stealing: get-call to SystemParametersInfo() failed.");
  1824.     }
  1825. //    else
  1826. //        MsgBox("Enable focus-stealing: neither needed nor supported under Win95 and WinNT.");
  1827. }
  1828.  
  1829.  
  1830.  
  1831. bool DialogPrep()
  1832. // Having it as a function vs. macro should reduce code size due to expansion of macros inside.
  1833. {
  1834.     bool thread_was_critical = g.ThreadIsCritical;
  1835.     g.ThreadIsCritical = false;
  1836.     MAKE_THREAD_INTERRUPTIBLE
  1837.     if (HIWORD(GetQueueStatus(QS_ALLEVENTS)))
  1838.         MsgSleep(-1);
  1839.     return thread_was_critical; // Caller is responsible for using this to later restore g.ThreadIsCritical.
  1840. }
  1841.