Hi Stefan,
I did more tests (and hacks) to see what Windows does. I'm not entirely sure how to fix it honestly—so I need some tips after I detail it. I'll first try to answer your random thoughts and then give more details/message sequences I observed on Windows.
On 01/10/2020 23:35, Stefan Dösinger wrote:
Hi,
Henri asked me to add my thoughts here because I once wrote most of the focus handling code but so far I am lost. This entire thing is fragile on both Windows and Wine.
I'm just dumping some random thoughts here. Maybe it gives you some idea, or some of your answers help me remember some bits:
*) I am wondering why the SetWindowPos in wined3d_swapchain_activate in the activate=true case doesn't use SWP_SHOWWINDOW or why there's no ShowWindow(SW_RESTORE) call.
According to a hack test I did, Windows' d3d9 definitely doesn't restore the window here, so I guess it's correct as far as this is concerned.
*) When testing messages on Windows, be aware that messages that d3d9 generates in response to WM_ACTIVATEAPP appear to be send *before* WM_ACTIVATEAPP because the calls that generate them happen before d3d9 forwards the WM_ACTIVATEAPP message to the original wndproc.
Right, I noticed it now and that does seem to be the case. It doesn't help the game though, because all of this happens while it's minimized.
*) The test shows that a WM_WINDOWPOSCHANGED message arrives after the window has been put out of iconic state, but the exact sequence isn't clear. Is this message produced by d3d9 or is it e.g. triggered by the taskbar.
I think it's both. It's only one message, but it is the size of the fullscreen (swapchain's backbuffer), *not* the size of the restored window, and unfortunately this seems to be important…
*) Something that may be tricky to answer: What happens if a non-taskbar event brings the window of HOMM 5 back into the foreground, e.g. a separate process calling SetForegroundWindow? I think alt-tab behaves very similarly to the taskbar.
Ok so I tested both of these. Alt-tab has the same sequence as clicking on the taskbar (which I'll show below), while if I used a hack and SetForegroundWindow, it doesn't ever restore the window at all. It does receive the WM_ACTIVATEAPP of course, and the window pos messages before it.
This happens even if I "unminimize" the window by clearing the WS_MINIMIZE style; it doesn't change anything.
*) Is it possible that there is some WM_SYSCOMMAND specific behavior? d3d9 does hook this particular message.
Yeah, and we already hook/handle it (and my patch does, but now I know it's wrong).
*) On which Linux environment does the regression happen? There may be window manager specific behavior...
I don't think that matters here, and I doubt it's a problem, but I tested it on Compiz with XFCE. Anyway, clearly our current behavior is wrong wrt to Windows, even if the patch that caused this regression is actually correct in that sense. It used to work by luck before.
So let me provide some details. First, the requirements: HOMM V expects the window pos messages to arrive after it's been unminimized, and they need to set it to the fullscreen size. In sequences below, it's 1920x1080 (window non-fullscreen size is 640x480).
WM_ACTIVATEAPP *does* send window pos messages; it worked before, because we unminimized the window first before activating it. But that's wrong (for other games, like Project CARS, which depend on it). For some reason it sends with SWP_NOMOVE and SWP_NOSIZE.
Regardless, I want to touch the current code as little as possible outside of this scenario, since there's also wined3d flags so I assume the behavior is not the same across all versions, and I don't want to mess those up.
So here's the sequences. First, I click on the taskbar:
WM_WINDOWPOSCHANGING, hwndInsertAfter 00000000, x 0, y 0, cx 0, cy 0, flags SWP_NOSIZE | SWP_NOMOVE WM_WINDOWPOSCHANGED, hwndInsertAfter 00000000, x -32000, y -32000, cx 160, cy 24, flags 00001803 WM_ACTIVATEAPP, wParam 00000001, lParam 00000718 WM_NCACTIVATE, wParam 00200001, lParam 00000000 WM_GETTEXT, wParam 000001FE, lParam 0022F130 WM_ACTIVATE, wParam 00200001, lParam 00000000 WM_SYSCOMMAND, wParam SC_RESTORE, lParam 00000000 WM_QUERYOPEN, wParam 00000000, lParam 00000000 WM_WINDOWPOSCHANGING, hwndInsertAfter 00000000, x 0, y 0, cx 1920, cy 1080, flags SWP_STATECHANGED | SWP_NOCOPYBITS | SWP_FRAMECHANGED WM_GETMINMAXINFO, wParam 00000000, lParam 0022F624 WM_NCCALCSIZE, wParam 00000001, lParam 0022F97C WM_NCPAINT, wParam 00000001, lParam 00000000 WM_GETTEXT, wParam 000001FE, lParam 0022EE20 WM_ERASEBKGND, wParam 01010052, lParam 00000000 WM_WINDOWPOSCHANGED, hwndInsertAfter 00000000, x 0, y 0, cx 1920, cy 1080, flags 00008124 WM_MOVE, wParam 00000000, lParam 00170004 WM_SIZE, wParam 00000000, lParam 041D0778 WM_SETFOCUS, wParam 00000000, lParam 00000000 WM_ACTIVATE, wParam 00000001, lParam 00000000
Now I sent a WM_SYSCOMMAND SC_RESTORE directly and I got:
WM_SYSCOMMAND, wParam SC_RESTORE, lParam 00000000 WM_QUERYOPEN, wParam 00000000, lParam 00000000 WM_WINDOWPOSCHANGING, hwndInsertAfter 00000000, x 0, y 0, cx 1920, cy 1080, flags SWP_STATECHANGED | SWP_NOCOPYBITS | SWP_FRAMECHANGED WM_GETMINMAXINFO, wParam 00000000, lParam 0022F5D4 WM_NCCALCSIZE, wParam 00000001, lParam 0022F92C WM_ACTIVATEAPP, wParam 00000001, lParam 00000000 WM_NCACTIVATE, wParam 00000001, lParam 00000000 WM_GETTEXT, wParam 000001FE, lParam 0022EDD0 WM_ACTIVATE, wParam 00000001, lParam 00000000 WM_SETFOCUS, wParam 00000000, lParam 00000000 WM_NCPAINT, wParam 00000001, lParam 00000000 WM_GETTEXT, wParam 000001FE, lParam 0022EDD0 WM_ERASEBKGND, wParam 01010054, lParam 00000000 WM_WINDOWPOSCHANGED, hwndInsertAfter 00000000, x 0, y 0, cx 1920, cy 1080, flags 00008120 WM_MOVE, wParam 00000000, lParam 00170004 WM_SIZE, wParam 00000000, lParam 041D0778 WM_ACTIVATE, wParam 00000001, lParam 00000000
Then I cleared the WS_MINIMIZE style and SetForegroundWindow:
WM_WINDOWPOSCHANGING, hwndInsertAfter 00000000, x 0, y 0, cx 0, cy 0, flags SWP_NOSIZE | SWP_NOMOVE WM_WINDOWPOSCHANGED, hwndInsertAfter 00000000, x -32000, y -32000, cx 160, cy 24, flags 00001803 WM_ACTIVATEAPP, wParam 00000001, lParam 000005F4 WM_NCACTIVATE, wParam 00000001, lParam 00000000 WM_GETTEXT, wParam 000001FE, lParam 0022F130 WM_ACTIVATE, wParam 00000001, lParam 00000000 WM_SETFOCUS, wParam 00000000, lParam 00000000
It's interesting to note that not clearing the WS_MINIMIZE style changed nothing at all, except the wParam in WM_ACTIVATE which kept the "minimized" flag (note how this flag also happens in first test, when WM_ACTIVATE is received while it's still minimized).
Also note that WM_ACTIVATEAPP in the second case (WM_SYSCOMMAND sent manually) doesn't generate any window pos messages at all. In fact, the behavior prior to the patch that caused this regression works by luck as you can see: we only sent WM_SYSCOMMAND without activating the window first, so WM_ACTIVATEAPP's window pos message worked correctly.
———————————————
My problem now is, I don't know how to fix this. Obviously I'll have to somehow handle this in our SC_RESTORE hook (which we already do, we just call DefWindowProc). However, that would restore the window to its normal restored size (640x480) and not fullscreen. My current patch changes its position again, but that's wrong, as on Windows it's only sent once.
Should I hook WM_WINDOWPOSCHANGING and change the position and size to match under this scenario? (i.e. change them to 1920x1080 from 640x480 in that example above)
And my other question is: how should I detect in WM_ACTIVATEAPP that we send it with SWP_NOMOVE | SWP_NOSIZE (so it doesn't ruin other versions). Furthermore: WM_ACTIVATEAPP doesn't SetWindowPos again if SYSCOMMAND is sent directly—how to best detect this? Do I use a wined3d flag? A new field, boolean? Or is that too ugly/too much of a hack?
Checking for WS_MINIMIZE or IsIconic doesn't work properly, since Windows completely ignores that, so that's a no-go.
Thanks, Gabriel