Page 1 of 1


Posted: 12 Oct 2019, 22:16
by lexikos
Does anyone use GroupDeactivate?

I just tested it as part of some maintenance, and realized that it is quite broken on Windows 10 (and perhaps 8), since it does not exclude windows that are "cloaked" by the window manager but still have the WS_VISIBLE style, such as apps that are suspended or on a different virtual desktop. Since GroupDeactivate starts at the bottom, I currently have to cycle through 12 "hidden" windows before reaching the first visible one. This makes me think that perhaps no one actually uses this function.

A search of the forums turns up GroupAdd, GroupDelete, GroupTranspose, which defines some functions for dealing with mutable window groups and demonstrates one of them with GroupDeactivate (and GroupActivate).

There are currently only 18 other results, and most of them are incidental and not specifically relating to (or using) GroupDeactivate. I suppose that it is the sort of thing that could be used regularly in personal scripts but rarely in shared scripts.

I intend to change all v1 commands/functions to consider cloaked windows to be hidden (where DetectHiddenWindows applies). Cloaked windows (in general, not just for GroupDeactivate) seem like a big enough issue to risk breaking any scripts that rely on detecting these windows without DetectHiddenWindows On. This excludes most of the problematic windows.

On Windows 10.0.18995, the first two windows to be activated are generally always ahk_class WorkerW. They overlay the ahk_class Progman window and host the Desktop content (whereas that used to be Progman's job). One is owned by Progman and can be retrieved with DllCall("GetLastActivePopup", "ptr", DllCall("GetShellWindow", "ptr"), "ptr"), but is actually disabled (WS_DISABLED) and cannot be activated by mouse (WS_EX_NOACTIVATE). Despite this, it is activated by Win+D (and as a consequence, I cannot immediately use the keyboard to navigate/activate Desktop items). Clicking the Desktop (or attempting to activate ahk_class Progman) activates the other WorkerW window, which is not owned by Progman.

On Windows 7, there appears to be only one WorkerW - the one that isn't disabled - and it is actually hidden on Windows 7, so is already excluded. Oddly enough, Win+D activates this hidden window, but clicking the Desktop activates Progman.

It is trivial to add ahk_class WorkerW to the group so it will not be activated, but it shouldn't be necessary - GroupDeactivate is intended to exclude the Desktop, and does so successfully on older versions of Windows. That being the case, I intend to change v1 to exclude these two WorkerW windows. There are currently many other WorkerW windows on my system, but they are all hidden. I'm not sure whether they are used in any other visible context.

Currently Progman is excluded by the title "Program Manager", which means any other windows with that title are also excluded. GetShellWindow wasn't used originally since it does not exist on Win 9x/NT4. Now because of the WorkerW windows, using GetShellWindow wouldn't be overly helpful.

Windows with WS_EX_TOPMOST (such as the taskbar and Windows 7 start button) are already excluded. Windows with WS_EX_NOACTIVATE or WS_DISABLED could also be excluded easily. That such windows are not already excluded can probably be considered a bug.

Further changes should probably be limited for "backward-compatibility" (or to avoid surprising anyone who expects the current behaviour).

If GroupActivate is kept in v2:

I think that perhaps only windows that appear on the taskbar should be activated, like what happens when you close the active window; so windows with WS_EX_TOOLWINDOW should be excluded unless they have WS_EX_APPWINDOW. That would also efficiently rule out the "WorkerW" windows that always sit at the bottom of the z-order (just above Progman).

If all windows are included in the group or otherwise skipped, GroupDeactivate doesn't do anything. I think perhaps it should activate the Desktop if no eligible window is found, so that the group is actually deactivated. (At the moment, the Desktop/WorkerW window is eligible unless you add it to the group.)

More details

Re: GroupDeactivate

Posted: 12 Oct 2019, 23:49
by lexikos
An additional odd behaviour that I only found because I was looking for it (after reading the code) relates to how it handles owned windows.

Code: Select all

		// If the window we're about to activate owns other visible 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;
		// Probably best to do this before WinDelay in case another hotkey fires during the delay:
The assumptions made there are incorrect, and as a result, the behaviour is inconsistent with OS behaviour.

A window that is below other windows in the z-order can be active; this is obvious as the active window is nearly always below the taskbar in the z-order. Windows that own other windows can also be active in their own right, and usually are, except in the case of modal dialogs, where the main window is probably disabled.

For example:

While the Find dialog is visible in Notepad, SciTE, Notepad++, etc., GroupDeactivate will activate the Find dialog but never the main window. The dialog doesn't have its own taskbar button.

Paint.NET has several owned tool windows. GroupDeactivate cycles through all of its tool windows and never activates the main window. But Paint.NET is a bit of a special case since it automatically moves the focus to whichever one of its windows the mouse is over.

In any of these cases (except when the mouse is over Paint.NET), if I select the program's taskbar button, it activates whichever window was active last (the main window or dialog); probably whatever GetLastActivePopup returns.

I think that it would be best for GroupDeactivate to only consider windows that show in the taskbar, but to use GetLastActivePopup to determine whether to activate that window or one of its owned windows (if any).