"Fossies" - the Fresh Open Source Software Archive

Member "AutoHotkey_L-1.1.33.09/source/WinGroup.cpp" (8 May 2021, 25050 Bytes) of package /windows/misc/AutoHotkey_L-1.1.33.09.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "WinGroup.cpp" see the Fossies "Dox" file reference documentation.

    1 /*
    2 AutoHotkey
    3 
    4 Copyright 2003-2009 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 "WinGroup.h"
   19 #include "window.h" // for several lower level window functions
   20 #include "globaldata.h" // for DoWinDelay
   21 #include "application.h" // for DoWinDelay's MsgSleep()
   22 
   23 // Define static members data:
   24 WinGroup *WinGroup::sGroupLastUsed = NULL;
   25 HWND *WinGroup::sAlreadyVisited = NULL;
   26 int WinGroup::sAlreadyVisitedCount = 0;
   27 
   28 
   29 ResultType WinGroup::AddWindow(LPTSTR aTitle, LPTSTR aText, LPTSTR aExcludeTitle, LPTSTR aExcludeText)
   30 // Caller should ensure that at least one param isn't NULL/blank.
   31 // GroupActivate will tell its caller to jump to aJumpToLabel if a WindowSpec isn't found.
   32 // This function is not thread-safe because it adds an entry to the list of window specs.
   33 // In addition, if this function is being called by one thread while another thread is calling IsMember(),
   34 // the thread-safety notes in IsMember() apply.
   35 {
   36     // v1.0.41: If a window group can ever be deleted (or its window specs), that might defeat the
   37     // thread-safety of WinExist/WinActive.
   38     // v1.0.36.05: If all four window parameters are blank, allow it to be added but provide
   39     // a non-blank ExcludeTitle so that the window-finding routines won't see it as the
   40     // "last found window".  99.99% of the time, it is undesirable to have Program Manager
   41     // in a window group of any kind, so that is used as the placeholder:
   42     if (!(*aTitle || *aText || *aExcludeTitle || *aExcludeText))
   43         aExcludeTitle = _T("Program Manager");
   44 
   45     // Though the documentation is clear on this, some users will still probably execute
   46     // each GroupAdd statement more than once.  Thus, to prevent more and more memory
   47     // from being allocated for duplicates, do not add the window specification if it
   48     // already exists in the group:
   49     if (mFirstWindow) // Traverse the circular linked-list to look for a match.
   50         for (WindowSpec *win = mFirstWindow
   51             ; win != NULL; win = (win->mNextWindow == mFirstWindow) ? NULL : win->mNextWindow)
   52             if (!_tcscmp(win->mTitle, aTitle) && !_tcscmp(win->mText, aText) // All are case sensitive.
   53                 && !_tcscmp(win->mExcludeTitle, aExcludeTitle) && !_tcscmp(win->mExcludeText, aExcludeText))
   54                 return OK;
   55 
   56     // SimpleHeap::Malloc() will set these new vars to the constant empty string if their
   57     // corresponding params are blank:
   58     LPTSTR new_title, new_text, new_exclude_title, new_exclude_text;
   59     if (!(new_title = SimpleHeap::Malloc(aTitle))) return FAIL; // It already displayed the error for us.
   60     if (!(new_text = SimpleHeap::Malloc(aText)))return FAIL;
   61     if (!(new_exclude_title = SimpleHeap::Malloc(aExcludeTitle))) return FAIL;
   62     if (!(new_exclude_text = SimpleHeap::Malloc(aExcludeText)))   return FAIL;
   63 
   64     // The precise method by which the follows steps are done should be thread-safe even if
   65     // some other thread calls IsMember() in the middle of the operation.  But any changes
   66     // must be carefully reviewed:
   67     WindowSpec *the_new_win = new WindowSpec(new_title, new_text, new_exclude_title, new_exclude_text);
   68     if (the_new_win == NULL)
   69         return g_script.ScriptError(ERR_OUTOFMEM);
   70     if (mFirstWindow == NULL)
   71         mFirstWindow = the_new_win;
   72     else
   73         mLastWindow->mNextWindow = the_new_win; // Formerly it pointed to First, so nothing is lost here.
   74     // This must be done after the above:
   75     mLastWindow = the_new_win;
   76     // Make it circular: Last always points to First.  It's okay if it points to itself:
   77     mLastWindow->mNextWindow = mFirstWindow;
   78     ++mWindowCount;
   79     return OK;
   80 }
   81 
   82 
   83 
   84 ResultType WinGroup::ActUponAll(ActionTypeType aActionType, int aTimeToWaitForClose)
   85 {
   86     if (IsEmpty())
   87         return OK;  // OK since this is the expected behavior in this case.
   88     // Don't need to call Update() in this case.
   89     WindowSearch ws;
   90     ws.mFirstWinSpec = mFirstWindow; // Act upon all windows that match any WindowSpec in the group.
   91     ws.mActionType = aActionType;    // Set the type of action to be performed on each window.
   92     ws.mTimeToWaitForClose = aTimeToWaitForClose;  // Only relevant for WinClose and WinKill.
   93     EnumWindows(EnumParentActUponAll, (LPARAM)&ws);
   94     if (ws.mFoundParent) // It acted upon least one window.
   95         DoWinDelay;
   96     return OK;
   97 }
   98 
   99 
  100 
  101 ResultType WinGroup::CloseAndGoToNext(bool aStartWithMostRecent)
  102 // If the foreground window is a member of this group, close it and activate
  103 // the next member.
  104 {
  105     if (IsEmpty())
  106         return OK;  // OK since this is the expected behavior in this case.
  107     // Otherwise:
  108     // Don't call Update(), let (De)Activate() do that.
  109 
  110     HWND fore_win = GetForegroundWindow();
  111     // Even if it's NULL, don't return since the legacy behavior is to continue on to the final part below.
  112 
  113     WindowSpec *win_spec = IsMember(fore_win, *g);
  114     if (   (mIsModeActivate && win_spec) || (!mIsModeActivate && !win_spec)   )
  115     {
  116         // If the user is using a GroupActivate hotkey, we don't want to close
  117         // the foreground window if it's not a member of the group.  Conversely,
  118         // if the user is using GroupDeactivate, we don't want to close a
  119         // member of the group.  This precaution helps prevent accidental closing
  120         // of windows that suddenly pop up to the foreground just as you've
  121         // realized (too late) that you pressed the "close" hotkey.
  122         // MS Visual Studio/C++ gets messed up when it is directly sent a WM_CLOSE,
  123         // probably because the wrong window (it has two mains) is being sent the close.
  124         // But since that's the only app I've ever found that doesn't work right,
  125         // it seems best not to change our close method just for it because sending
  126         // keys is a fairly high overhead operation, and not without some risk due to
  127         // not knowing exactly what keys the user may have physically held down.
  128         // Also, we'd have to make this module dependent on the keyboard module,
  129         // which would be another drawback.
  130         // Try to wait for it to close, otherwise the same window may be activated
  131         // again before it has been destroyed, defeating the purpose of the
  132         // "ActivateNext" part of this function's job:
  133         // SendKeys("!{F4}");
  134         if (fore_win)
  135         {
  136             WinClose(fore_win, 500);
  137             DoWinDelay;
  138         }
  139     }
  140     //else do the activation below anyway, even though no close was done.
  141     return mIsModeActivate ? Activate(aStartWithMostRecent, win_spec) : Deactivate(aStartWithMostRecent);
  142 }
  143 
  144 
  145 
  146 ResultType WinGroup::Activate(bool aStartWithMostRecent, WindowSpec *aWinSpec, Label **aJumpToLabel)
  147 {
  148     if (aJumpToLabel) // Initialize early in case of early return.
  149         *aJumpToLabel = NULL;
  150     if (IsEmpty())
  151         return OK;  // OK since this is the expected behavior in this case.
  152     // Otherwise:
  153     if (!Update(true)) // Update our private member vars.
  154         return FAIL;  // It already displayed the error for us.
  155     WindowSpec *win, *win_to_activate_next = aWinSpec;
  156     bool group_is_active = false; // Set default.
  157     HWND activate_win, active_window = GetForegroundWindow(); // This value is used in more than one place.
  158     if (win_to_activate_next)
  159     {
  160         // The caller told us which WindowSpec to start off trying to activate.
  161         // If the foreground window matches that WindowSpec, do nothing except
  162         // marking it as visited, because we want to stay on this window under
  163         // the assumption that it was newly revealed due to a window on top
  164         // of it having just been closed:
  165         if (win_to_activate_next == IsMember(active_window, *g))
  166         {
  167             group_is_active = true;
  168             MarkAsVisited(active_window);
  169             return OK;
  170         }
  171         // else don't mark as visited even if it's a member of the group because
  172         // we're about to attempt to activate a different window: the next
  173         // unvisited member of this same WindowSpec.  If the below doesn't
  174         // find any of those, it continue on through the list normally.
  175     }
  176     else // Caller didn't tell us which, so determine it.
  177     {
  178         if (win_to_activate_next = IsMember(active_window, *g)) // Foreground window is a member of this group.
  179         {
  180             // Set it to activate this same WindowSpec again in case there's
  181             // more than one that matches (e.g. multiple notepads).  But first,
  182             // mark the current window as having been visited if it hasn't
  183             // already by marked by a prior iteration.  Update: This method
  184             // doesn't work because if a unvisited matching window became the
  185             // foreground window by means other than using GroupActivate
  186             // (e.g. launching a new instance of the app: now there's another
  187             // matching window in the foreground).  So just call it straight
  188             // out.  It has built-in dupe-checking which should prevent the
  189             // list from filling up with dupes if there are any special
  190             // situations in which that might otherwise happen:
  191             //if (!sAlreadyVisitedCount)
  192             group_is_active = true;
  193             MarkAsVisited(active_window);
  194         }
  195         else // It's not a member.
  196         {
  197             win_to_activate_next = mFirstWindow;  // We're starting fresh, so start at the first window.
  198             // Reset the list of visited windows:
  199             sAlreadyVisitedCount = 0;
  200         }
  201     }
  202 
  203     // Activate any unvisited window that matches the win_to_activate_next spec.
  204     // If none, activate the next window spec in the series that does have an
  205     // existing window:
  206     // If the spec we're starting at already has some windows marked as visited,
  207     // set this variable so that we know to retry the first spec again in case
  208     // a full circuit is made through the window specs without finding a window
  209     // to activate.  Note: Using >1 vs. >0 might protect against any infinite-loop
  210     // conditions that may be lurking:
  211     bool retry_starting_win_spec = (sAlreadyVisitedCount > 1);
  212     bool retry_is_in_effect = false;
  213     for (win = win_to_activate_next;;)
  214     {
  215         // Call this in the mode to find the last match, which  makes things nicer
  216         // because when the sequence wraps around to the beginning, the windows will
  217         // occur in the same order that they did the first time, rather than going
  218         // backwards through the sequence (which is counterintuitive for the user):
  219         if (   activate_win = WinActivate(*g, win->mTitle, win->mText, win->mExcludeTitle, win->mExcludeText
  220             // This next line is whether to find last or first match.  We always find the oldest
  221             // (bottommost) match except when the user has specifically asked to start with the
  222             // most recent.  But it only makes sense to start with the most recent if the
  223             // group isn't currently active (i.e. we're starting fresh), because otherwise
  224             // windows would be activated in an order different from what was already shown
  225             // the first time through the enumeration, which doesn't seem to be ever desirable:
  226             , !aStartWithMostRecent || group_is_active
  227             , sAlreadyVisited, sAlreadyVisitedCount)   )
  228         {
  229             // We found a window to activate, so we're done.
  230             // Probably best to do this before WinDelay in case another hotkey fires during the delay:
  231             MarkAsVisited(activate_win);
  232             DoWinDelay;
  233             //MsgBox(win->mText, 0, win->mTitle);
  234             break;
  235         }
  236         // Otherwise, no window was found to activate.
  237         if (retry_is_in_effect)
  238             // This was the final attempt because we've already gone all the
  239             // way around the circular linked list of WindowSpecs.  This check
  240             // must be done, otherwise an infinite loop might result if the windows
  241             // that formed the basis for determining the value of
  242             // retry_starting_win_spec have since been destroyed:
  243             break;
  244         // Otherwise, go onto the next one in the group:
  245         win = win->mNextWindow;
  246         // Even if the above didn't change the value of <win> (because there's only
  247         // one WinSpec in the list), it's still correct to reset this count because
  248         // we want to start the fresh again after all the windows have been
  249         // visited.  Note: The only purpose of sAlreadyVisitedCount as used by
  250         // this function is to indicate which windows in a given WindowSpec have
  251         // been visited, not which windows altogether (i.e. it's not necessary to
  252         // remember which windows have been visited once we move on to a new
  253         // WindowSpec).
  254         sAlreadyVisitedCount = 0;
  255         if (win == win_to_activate_next)
  256         {
  257             // We've made one full circuit of the circular linked list without
  258             // finding an existing window to activate. At this point, the user
  259             // has pressed a hotkey to do a GroupActivate, but nothing has happened
  260             // yet.  We always want something to happen unless there's absolutely
  261             // no existing windows to activate, or there's only a single window in
  262             // the system that matches the group and it's already active.
  263             if (retry_starting_win_spec)
  264             {
  265                 // Mark the foreground window as visited so that it won't be
  266                 // mistakenly activated again by the next iteration:
  267                 MarkAsVisited(active_window);
  268                 retry_is_in_effect = true;
  269                 // Now continue with the next iteration of the loop so that it
  270                 // will activate a different instance of this WindowSpec rather
  271                 // than getting stuck on this one.
  272             }
  273             else 
  274             {
  275                 if (aJumpToLabel && mJumpToLabel)
  276                 {
  277                     // Caller asked us to return in this case, so that it can
  278                     // use this value to execute a user-specified Gosub:
  279                     *aJumpToLabel = mJumpToLabel;  // Set output param for the caller.
  280                 }
  281                 return FAIL; // Let GroupActivate set ErrorLevel to indicate what happened.
  282             }
  283         }
  284     }
  285     return OK;
  286 }
  287 
  288 
  289 
  290 ResultType WinGroup::Deactivate(bool aStartWithMostRecent)
  291 {
  292     if (IsEmpty())
  293         return OK;  // OK since this is the expected behavior in this case.
  294     // Otherwise:
  295     if (!Update(false)) // Update our private member vars.
  296         return FAIL;  // It already displayed the error for us.
  297 
  298     HWND active_window = GetForegroundWindow();
  299     if (IsMember(active_window, *g))
  300         sAlreadyVisitedCount = 0;
  301 
  302     // Activate the next unvisited non-member:
  303     WindowSearch ws;
  304     ws.mFindLastMatch = !aStartWithMostRecent || sAlreadyVisitedCount;
  305     ws.mAlreadyVisited = sAlreadyVisited;
  306     ws.mAlreadyVisitedCount = sAlreadyVisitedCount;
  307     ws.mFirstWinSpec = mFirstWindow;
  308 
  309     EnumWindows(EnumParentFindAnyExcept, (LPARAM)&ws);
  310 
  311     if (ws.mFoundParent)
  312     {
  313         // If the window we're about to activate owns other visible parent windows, it can
  314         // never truly be activated because it must always be below them in the z-order.
  315         // Thus, instead of activating it, activate the first (and usually the only?)
  316         // visible window that it owns.  Doing this makes things nicer for some apps that
  317         // have a pair of main windows, such as MS Visual Studio (and probably many more),
  318         // because it avoids activating such apps twice in a row as the user progresses
  319         // through the sequence:
  320         HWND first_visible_owned = WindowOwnsOthers(ws.mFoundParent);
  321         if (first_visible_owned)
  322         {
  323             MarkAsVisited(ws.mFoundParent);  // Must mark owner as well as the owned window.
  324             // Activate the owned window instead of the owner because it usually
  325             // (probably always, given the comments above) is the real main window:
  326             ws.mFoundParent = first_visible_owned;
  327         }
  328         SetForegroundWindowEx(ws.mFoundParent);
  329         // Probably best to do this before WinDelay in case another hotkey fires during the delay:
  330         MarkAsVisited(ws.mFoundParent);
  331         DoWinDelay;
  332     }
  333     else // No window was found to activate (they have all been visited).
  334     {
  335         if (sAlreadyVisitedCount)
  336         {
  337             bool wrap_around = (sAlreadyVisitedCount > 1);
  338             sAlreadyVisitedCount = 0;
  339             if (wrap_around)
  340             {
  341                 // The user pressed a hotkey to do something, yet nothing has happened yet.
  342                 // We want something to happen every time if there's a qualifying
  343                 // "something" that we can do.  And in this case there is: we can start
  344                 // over again through the list, excluding the foreground window (which
  345                 // the user has already had a chance to review):
  346                 MarkAsVisited(active_window);
  347                 // Make a recursive call to self.  This can't result in an infinite
  348                 // recursion (stack fault) because the called layer will only
  349                 // recurse a second time if sAlreadyVisitedCount > 1, which is
  350                 // impossible with the current logic:
  351                 Deactivate(false); // Seems best to ignore aStartWithMostRecent in this case?
  352             }
  353         }
  354     }
  355     // Even if a window wasn't found, we've done our job so return OK:
  356     return OK;
  357 }
  358 
  359 
  360 
  361 inline ResultType WinGroup::Update(bool aIsModeActivate)
  362 {
  363     mIsModeActivate = aIsModeActivate;
  364     if (sGroupLastUsed != this)
  365     {
  366         sGroupLastUsed = this;
  367         sAlreadyVisitedCount = 0; // Since it's a new group, reset the array to start fresh.
  368     }
  369     if (!sAlreadyVisited) // Allocate the array on first use.
  370         // Getting it from SimpleHeap reduces overhead for the avg. case (i.e. the first
  371         // block of SimpleHeap is usually never fully used, and this array won't even
  372         // be allocated for short scripts that don't even using window groups.
  373         if (   !(sAlreadyVisited = (HWND *)SimpleHeap::Malloc(MAX_ALREADY_VISITED * sizeof(HWND)))   )
  374             return FAIL;  // It already displayed the error for us.
  375     return OK;
  376 }
  377 
  378 
  379 
  380 WindowSpec *WinGroup::IsMember(HWND aWnd, global_struct &aSettings)
  381 // Thread-safety: This function is thread-safe even when the main thread happens to be calling AddWindow()
  382 // and changing the linked list while it's being traversed here by the hook thread.  However, any subsequent
  383 // changes to this function or AddWindow() must be carefully reviewed.
  384 // Although our caller may be a WindowSearch method, and thus we might make
  385 // a recursive call back to that same method, things have been reviewed to ensure that
  386 // thread-safety is maintained, even if the calling thread is the hook.
  387 {
  388     if (!aWnd)
  389         return NULL;  // Some callers on this.
  390     WindowSearch ws;
  391     ws.SetCandidate(aWnd);
  392     for (WindowSpec *win = mFirstWindow; win != NULL;)  // v1.0.41: "win != NULL" was added for thread-safety.
  393     {
  394         if (ws.SetCriteria(aSettings, win->mTitle, win->mText, win->mExcludeTitle, win->mExcludeText) && ws.IsMatch())
  395             return win;
  396         // Otherwise, no match, so go onto the next one:
  397         win = win->mNextWindow;
  398         if (!win || win == mFirstWindow) // v1.0.41: The check of !win was added for thread-safety.
  399             // We've made one full circuit of the circular linked list,
  400             // discovering that the foreground window isn't a member
  401             // of the group:
  402             break;
  403     }
  404     return NULL;  // Because it would have returned already if a match was found.
  405 }
  406 
  407 
  408 /////////////////////////////////////////////////////////////////////////
  409 
  410 
  411 BOOL CALLBACK EnumParentFindAnyExcept(HWND aWnd, LPARAM lParam)
  412 // Find the first parent window that doesn't match any of the WindowSpecs in
  413 // the linked list, and that hasn't already been visited.
  414 {
  415     // Since the following two sections apply only to GroupDeactivate (since that's our only caller),
  416     // they both seem okay even in light of the ahk_group method.
  417 
  418     if (!IsWindowVisible(aWnd) || IsWindowCloaked(aWnd))
  419         // Skip these because we always want them to stay invisible, regardless
  420         // of the setting for g->DetectHiddenWindows:
  421         return TRUE;
  422 
  423     // UPDATE: Because the window of class Shell_TrayWnd (the taskbar) is also always-on-top,
  424     // the below prevents it from ever being activated too, which is almost always desirable.
  425     // However, this prevents the addition of WS_DISABLED as an extra criteria for skipping
  426     // a window.  Maybe that's best for backward compatibility anyway.
  427     // Skip always-on-top windows, such as SplashText, because probably shouldn't
  428     // be activated (especially in this mode, which is often used to visit the user's
  429     // "non-favorite" windows).  In addition, they're already visible so the user already
  430     // knows about them, so there's no need to have them presented for review.
  431     DWORD ex_style = GetWindowLong(aWnd, GWL_EXSTYLE);
  432     if (ex_style & WS_EX_TOPMOST)
  433         return TRUE;
  434 
  435     // Skip "Program Manager" (the Desktop) because the user probably doesn't want it
  436     // activated (although that would serve the purpose of deactivating any other window),
  437     // and for backward-compatibility.  For consistency, also skip the "ahk_class WorkerW"
  438     // window which is the Desktop on Windows 7 and later (but is hidden on Windows 7).
  439     // Since the class name is ambiguous, also check for WS_EX_TOOLWINDOW.
  440     TCHAR class_name[9];
  441     if (   GetClassName(aWnd, class_name, _countof(class_name))
  442         && (!_tcsicmp(class_name, _T("Progman"))
  443             || (ex_style & WS_EX_TOOLWINDOW) && !_tcsicmp(class_name, _T("WorkerW")))   )
  444         return TRUE;
  445 
  446     WindowSearch &ws = *(WindowSearch *)lParam;  // For performance and convenience.
  447     ws.SetCandidate(aWnd);
  448 
  449     // Check this window's attributes against each set of criteria present in the group.  If
  450     // it's a match for any set of criteria, it's a member of the group and thus should be
  451     // excluded since we want only NON-members:
  452     for (WindowSpec *win = ws.mFirstWinSpec;;)
  453     {
  454         // For each window in the linked list, check if aWnd is a match for it:
  455         if (ws.SetCriteria(*g, win->mTitle, win->mText, win->mExcludeTitle, win->mExcludeText) && ws.IsMatch(true))
  456             // Match found, so aWnd is a member of the group. But we want to find non-members only,
  457             // so keep searching:
  458             return TRUE;
  459         // Otherwise, no match, but keep checking until aWnd has been compared against
  460         // all the WindowSpecs in the group:
  461         win = win->mNextWindow;
  462         if (win == ws.mFirstWinSpec)
  463         {
  464             // We've made one full circuit of the circular linked list without
  465             // finding a match.  So aWnd is the one we're looking for unless
  466             // it's in the list of exceptions:
  467             for (int i = 0; i < ws.mAlreadyVisitedCount; ++i)
  468                 if (aWnd == ws.mAlreadyVisited[i])
  469                     return TRUE; // It's an exception, so keep searching.
  470             // Otherwise, this window meets the criteria, so return it to the caller and
  471             // stop the enumeration.  UPDATE: Rather than stopping the enumeration,
  472             // continue on through all windows so that the last match is found.
  473             // That makes things nicer because when the sequence wraps around to the
  474             // beginning, the windows will occur in the same order that they did
  475             // the first time, rather than going backwards through the sequence
  476             // (which is counterintuitive for the user):
  477             ws.mFoundParent = aWnd; // No need to increment ws.mFoundCount in this case.
  478             return ws.mFindLastMatch;
  479         }
  480     } // The loop above is infinite unless a "return" is encountered inside.
  481 }
  482 
  483 
  484 
  485 BOOL CALLBACK EnumParentActUponAll(HWND aWnd, LPARAM lParam)
  486 // Caller must have ensured that lParam isn't NULL and that it contains a non-NULL mFirstWinSpec.
  487 {
  488     WindowSearch &ws = *(WindowSearch *)lParam;  // For performance and convenience.
  489 
  490     // Skip windows the command isn't supposed to detect.  ACT_WINSHOW is exempt because
  491     // hidden windows are always detected by the WinShow command:
  492     if (!(ws.mActionType == ACT_WINSHOW || g->DetectWindow(aWnd)))
  493         return TRUE;
  494 
  495     int nCmdShow;
  496     ws.SetCandidate(aWnd);
  497 
  498     for (WindowSpec *win = ws.mFirstWinSpec;;)
  499     {
  500         // For each window in the linked list, check if aWnd is a match for it:
  501         if (ws.SetCriteria(*g, win->mTitle, win->mText, win->mExcludeTitle, win->mExcludeText) && ws.IsMatch())
  502         {
  503             // Match found, so aWnd is a member of the group.  In addition, IsMatch() has set
  504             // the value of ws.mFoundParent to tell our caller that at least one window was acted upon.
  505             // See Line::PerformShowWindow() for comments about the following section.
  506             nCmdShow = SW_NONE; // Set default each time.
  507             switch (ws.mActionType)
  508             {
  509             case ACT_WINCLOSE:
  510             case ACT_WINKILL:
  511                 // mTimeToWaitForClose is not done in a very efficient way here: to keep code size
  512                 // in check, it closes each window individually rather than sending WM_CLOSE to all
  513                 // the windows simultaneously and then waiting until all have vanished:
  514                 WinClose(aWnd, ws.mTimeToWaitForClose, ws.mActionType == ACT_WINKILL); // DoWinDelay is done by our caller.
  515                 return TRUE; // All done with the current window, so fetch the next one.
  516 
  517             case ACT_WINMINIMIZE:
  518                 if (IsWindowHung(aWnd))
  519                     nCmdShow = SW_FORCEMINIMIZE;
  520                 else
  521                     nCmdShow = SW_MINIMIZE;
  522                 break;
  523 
  524             case ACT_WINMAXIMIZE: if (!IsWindowHung(aWnd)) nCmdShow = SW_MAXIMIZE; break;
  525             case ACT_WINRESTORE:  if (!IsWindowHung(aWnd)) nCmdShow = SW_RESTORE;  break;
  526             case ACT_WINHIDE: nCmdShow = SW_HIDE; break;
  527             case ACT_WINSHOW: nCmdShow = SW_SHOW; break;
  528             }
  529 
  530             if (nCmdShow != SW_NONE)
  531                 ShowWindow(aWnd, nCmdShow);
  532                 // DoWinDelay is not done here because our caller will do it once only, which
  533                 // seems best when there are a lot of windows being acted upon here.
  534 
  535             // Now that this matching window has been acted upon (or avoided due to being hung),
  536             // continue the enumeration to get the next candidate window:
  537             return TRUE;
  538         }
  539         // Otherwise, no match, keep checking until aWnd has been compared against all the WindowSpecs in the group:
  540         win = win->mNextWindow;
  541         if (win == ws.mFirstWinSpec)
  542             // We've made one full circuit of the circular linked list without
  543             // finding a match, so aWnd is not a member of the group and
  544             // should not be closed.
  545             return TRUE; // Continue the enumeration.
  546     }
  547 }