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