Game window stuck partially offscreen

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
mankrip
Posts: 924
Joined: Fri Jul 04, 2008 3:02 am

Game window stuck partially offscreen

Post by mankrip »

I'm trying to solve this really old WinQuake problem. When the window is dragged partially offscreen, the only ways to bring it back are to change the vid_window_x and vid_window_y cvars and changing the resolution.

In the qbismSuper8 builds thread there's some talk about it, but I've tried the proposed solution, and moving the VID_CheckWindowXY call didn't fix it. I've also tried messing around with various things in VID_RememberWindowPos, VID_Update and MainWndProc.

Does anyone know why exactly this bug happens?
Ph'nglui mglw'nafh mankrip Hell's end wgah'nagl fhtagn.
==-=-=-=-=-=-=-=-=-=-==
Dev blog / Twitter / YouTube
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: Game window stuck partially offscreen

Post by qbism »

In build 88 there, the window was snapped back to screen center whenever it went off the edge. I think it worked but was annoying for it to go all the way back. In the current build it is supposed to come back just enough to get the whole window on screen. I recall it worked at the time but I see it's getting stuck again. I may have done something in build 109 that breaks it. Or could it be some windows or driver update?

BTW, I tried to browse super8 svn on sourceforge but their new interface is missing dozens of commits. But everything is there using
TortoiseSVN.
mankrip
Posts: 924
Joined: Fri Jul 04, 2008 3:02 am

Re: Game window stuck partially offscreen

Post by mankrip »

I've figured it out:

Code: Select all

void VID_CheckWindowXY (void)
{
	// mankrip - begin
	RECT	rect;
//	qboolean changed = false;

	if (GetWindowRect (mainwindow, &rect))
	{
		if (rect.right - rect.left > GetSystemMetrics (SM_CXSCREEN)) // window is wider than the screen (may happen due to window borders)
		{
//			if ( (int)vid_window_x.value != (GetSystemMetrics (SM_CXSCREEN) - (rect.right - rect.left)) / 2)
			{
				Cvar_SetValue ("vid_window_x", (GetSystemMetrics (SM_CXSCREEN) - (rect.right - rect.left)) / 2);
//				changed = true;
			}
		}
		else if (rect.left < 0)
		{
			Cvar_SetValue ("vid_window_x", 0.0f);
//			changed = true;
		}
		else if (rect.right > GetSystemMetrics (SM_CXSCREEN))
		{
			Cvar_SetValue ("vid_window_x", GetSystemMetrics (SM_CXSCREEN) - (rect.right - rect.left));
//			changed = true;
		}

		if (rect.bottom - rect.top > GetSystemMetrics (SM_CYSCREEN)) // window is taller than the screen (may happen due to window borders)
		{
//			if ( (int)vid_window_y.value != (GetSystemMetrics (SM_CYSCREEN) - (rect.bottom - rect.top)) / 2)
			{
				Cvar_SetValue ("vid_window_y", (GetSystemMetrics (SM_CYSCREEN) - (rect.bottom - rect.top)) / 2);
//				changed = true;
			}
		}
		else if (rect.top < 0)
		{
			Cvar_SetValue ("vid_window_y", 0.0f);
//			changed = true;
		}
		else if (rect.bottom > GetSystemMetrics (SM_CYSCREEN))
		{
			Cvar_SetValue ("vid_window_y", GetSystemMetrics (SM_CYSCREEN) - (rect.bottom - rect.top));
//			changed = true;
		}

//		if (changed)
//			SetWindowPos (mainwindow, NULL, (int) vid_window_x.value, (int) vid_window_y.value, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW | SWP_DRAWFRAME);
	}
//	return changed;
	// mankrip - end
}
The lines that are commented out are just so the function can be turned into a qboolean type and return a value if needed for refactoring the rest of the code. Also, the SetWindowPos line most likely shouldn't be there, even if the rest of the code is refactored, but I've left it in there and commented it out anyway.

This code also centers the window horizontally if it's wider than the screen, and vertically if taller. So, by setting vid_config_x and vid_config_y to the same dimensions of the desktop resolution, the game is essentially displayed in fullscreen without switching the desktop/taskbar/etc. away.
Ph'nglui mglw'nafh mankrip Hell's end wgah'nagl fhtagn.
==-=-=-=-=-=-=-=-=-=-==
Dev blog / Twitter / YouTube
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: Game window stuck partially offscreen

Post by qbism »

That looks like it should work, but it didn't for me. I'm going to double check to see if I've got the check is in the right place.
Here's the code from super8 build 109, which also doesn't work. But I'm pretty sure it used to work, strange. It's got a guess for the window border size because I don't know how to determine it.

Code: Select all

void VID_CheckWindowXY (void)
{
    if ((int) vid_window_x.value > GetSystemMetrics (SM_CXSCREEN) - dd_window_width -10)  //qbism HACK - what is actual?
        Cvar_SetValue ("vid_window_x", GetSystemMetrics (SM_CXSCREEN) - dd_window_width -15);

    if ((int) vid_window_y.value > GetSystemMetrics (SM_CYSCREEN) - dd_window_height -35)
        Cvar_SetValue ("vid_window_y", GetSystemMetrics (SM_CYSCREEN) - dd_window_height -40);

    if ((int) vid_window_x.value < 0)
        Cvar_SetValue ("vid_window_x", 0.0);

    if ((int) vid_window_y.value < 0)
        Cvar_SetValue ("vid_window_y", 0.0);
}
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Game window stuck partially offscreen

Post by Baker »

The above isn't quite the "right way" to center a window.

Code: Select all

RECT workarea;
iSystemParametersInfo (SPI_GETWORKAREA, 0, &workarea, 0)
Use SPI_GETWORKAREA gets the actual usable desktop area for the primary monitor (the taskbar, etc. aren't in the resulting rectangle).

This ends being a bit more important than in the past as displays any more have less vertical space.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: Game window stuck partially offscreen

Post by qbism »

SPI_GETWORKAREA is good to know. Is there a way to retrieve the window size including borders from the API? If the ddraw area is 640x480, the total window is around 660x520.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Game window stuck partially offscreen

Post by Baker »

qbism wrote:SPI_GETWORKAREA is good to know. Is there a way to retrieve the window size including borders from the API? If the ddraw area is 640x480, the total window is around 660x520.
It's not convenient, but yeah ... (SCROLL a little to see the important part)

Code: Select all

		WINDOWINFO windowinfo;
		RECT twindowArea;
		
		windowinfo.cbSize = sizeof (WINDOWINFO);

		GetWindowRect (w32_plat.mainwindow, &twindowArea); // Total window area + borders
		GetWindowInfo (w32_plat.mainwindow, &windowinfo); // Total client area.  Take window area and subtract client area, obv

		host.windowArea.top		= twindowArea.top;
		host.windowArea.bottom	= twindowArea.bottom;
		host.windowArea.left	= twindowArea.left;
		host.windowArea.right	= twindowArea.right;

		host.clientArea.top		= windowinfo.rcClient.top;
		host.clientArea.bottom	= windowinfo.rcClient.bottom;
		host.clientArea.left	= windowinfo.rcClient.left;
		host.clientArea.right	= windowinfo.rcClient.right;

		host.mouse_center_x		= host.clientArea.left + (RECT_WIDTH(host.clientArea) >> 1);
		host.mouse_center_y		= host.clientArea.top + (RECT_HEIGHT(host.clientArea) >> 1);
		host.borderAreaWidth	= RECT_WIDTH(host.windowArea) - RECT_WIDTH(host.clientArea); /// <-------
		host.borderAreaHeight	= RECT_HEIGHT(host.windowArea) - RECT_HEIGHT(host.clientArea); /// <---------- here
/ The RECT_WIDTH macro is simply ....

#define RECT_WIDTH(x) (x.right - x.left)
#define RECT_HEIGHT(x) (x.bottom - x.top)

I believe the above code has the bonus of working around something where Vista and maybe Windows 7+ lies about something (a pixel or 2 off), I was probably poking through an MH engine to see that note somewhere.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mankrip
Posts: 924
Joined: Fri Jul 04, 2008 3:02 am

Re: Game window stuck partially offscreen

Post by mankrip »

qbism wrote:Is there a way to retrieve the window size including borders from the API? If the ddraw area is 640x480, the total window is around 660x520.
As Baker pointed out, my code does; it's in the (rect.right - rect.left) and (rect.bottom - rect.top) parts.
Baker wrote:The above isn't quite the "right way" to center a window.
[...]
Use SPI_GETWORKAREA gets the actual usable desktop area for the primary monitor (the taskbar, etc. aren't in the resulting rectangle).
I'm aware of that, but in this specific case, this is not what I want; I want the window's contents to fill the whole screen, including the taskbar area (even if the taskbar is displayed in front of it).

Anyway, your new info helped me to improve it a bit. This new version will also take the title bar in consideration:

Code: Select all

void VID_CheckWindowXY (void)
{
	// mankrip - begin
	RECT	rect;

	if (GetWindowRect (mainwindow, &rect))
	{
		int
			border
		,	titlebar
			;
		// Baker - begin
		WINDOWINFO windowinfo;
		windowinfo.cbSize = sizeof (WINDOWINFO);
		GetWindowInfo (mainwindow, &windowinfo); // Total client area.  Take window area and subtract client area, obv
		// Baker - end
		border = (rect.right - rect.left) - (windowinfo.rcClient.right - windowinfo.rcClient.left); // both borders; one on the left, and one on the right
		titlebar = (rect.bottom - rect.top) - (windowinfo.rcClient.bottom - windowinfo.rcClient.top) - border; // title bar, without top and bottom borders
		border /= 2; // convert to a single border

		if (rect.right - rect.left > GetSystemMetrics (SM_CXSCREEN)) // window is wider than the screen (may happen due to window borders)
			Cvar_SetValue ("vid_window_x", (float) (border * -1));
		else if (rect.left < 0)
			Cvar_SetValue ("vid_window_x", 0.0f);
		else if (rect.right > GetSystemMetrics (SM_CXSCREEN))
			Cvar_SetValue ("vid_window_x", (float) (GetSystemMetrics (SM_CXSCREEN) - (rect.right - rect.left)));

		if (rect.bottom - rect.top > GetSystemMetrics (SM_CYSCREEN)) // window is taller than the screen (may happen due to window borders)
			Cvar_SetValue ("vid_window_y", (float) (-1 * (border + titlebar)));
		else if (rect.top < 0)
			Cvar_SetValue ("vid_window_y", 0.0f);
		else if (rect.bottom > GetSystemMetrics (SM_CYSCREEN))
			Cvar_SetValue ("vid_window_y", (float) (GetSystemMetrics (SM_CYSCREEN) - (rect.bottom - rect.top)));
	}
	// mankrip - end
}
:D It's perfect now. Try setting vid_config_x and vid_config_y to the desktop resolution, and then switching to a fullscreen mode using the same resolution. This is exactly what I wanted.

And here's my Vid_Update; it also has a couple more fixes:

Code: Select all

void VID_Update (vrect_t *rects)
{
	vrect_t	rect;
	RECT	trect;

	if (!vid_palettized && palette_changed)
	{
		palette_changed = false;
		rect.x = 0;
		rect.y = 0;
		rect.width = vid.width;
		rect.height = vid.height;
		rect.pnext = NULL;
		rects = &rect;
	}

	VID_CheckWindowXY (); //qbism- put the check here, and nowhere else
	if (firstupdate)
	{
		if ((_vid_default_mode_win.value != vid_default) &&
				(!startwindowed || (_vid_default_mode_win.value < MODE_FULLSCREEN_DEFAULT)))
		{
			firstupdate = 0;

			if (COM_CheckParm ("-resetwinpos"))
			{
				Cvar_SetValue ("vid_window_x", 0.0);
				Cvar_SetValue ("vid_window_y", 0.0);
			}

			if ((_vid_default_mode_win.value < 0) ||
					(_vid_default_mode_win.value >= nummodes))
			{
				Cvar_SetValue ("_vid_default_mode_win", windowed_default);
			}

			Cvar_SetValue ("vid_mode", _vid_default_mode_win.value);
		}
	}

	// We've drawn the frame; copy it to the screen
	FlipScreen (rects);

	if (vid_testingmode)
	{
		if (realtime >= vid_testendtime)
		{
			VID_SetMode (vid_realmode, vid_curpal);
			vid_testingmode = 0;
		}
	}
	else
	{
		if ((int) vid_mode.value != vid_realmode)
		{
			VID_SetMode ((int) vid_mode.value, vid_curpal);
			Cvar_SetValue ("vid_mode", (float) vid_modenum);
			// so if mode set fails, we don't keep on
			//  trying to set that mode
			vid_realmode = vid_modenum;
		}
		// mankrip - begin
		else if (vid_realmode == MODE_SETTABLE_WINDOW)
		{
			if (modelist[MODE_SETTABLE_WINDOW].width  != (int) vid_config_x.value
			||	modelist[MODE_SETTABLE_WINDOW].height != (int) vid_config_y.value
				)
				VID_SetMode (vid_realmode, vid_curpal);
		}
		// mankrip - end
	}

	// handle the mouse state when windowed if that's changed
	if (modestate == MS_WINDOWED)
	{
		// mankrip - begin
		// the window should be moved after VID_SetMode is run, not before
		if (firstupdate)
		{
			GetWindowRect (mainwindow, &trect);
			if ((trect.left != (int) vid_window_x.value)
			||	(trect.top  != (int) vid_window_y.value))
				SetWindowPos (mainwindow, NULL, (int) vid_window_x.value, (int) vid_window_y.value, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW | SWP_DRAWFRAME);
		}
		// mankrip - end
		{
			if (key_dest != key_menu && !cl.paused && !cls.demoplayback) // mankrip
			{
				IN_ActivateMouse ();
				IN_HideMouse ();
			}
			else
			{
				IN_DeactivateMouse ();
				IN_ShowMouse ();
			}

		}
	}
}
Ph'nglui mglw'nafh mankrip Hell's end wgah'nagl fhtagn.
==-=-=-=-=-=-=-=-=-=-==
Dev blog / Twitter / YouTube
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: Game window stuck partially offscreen

Post by qbism »

This updated VID_CheckWindowXY is working great. Suggested change to Vid_Update. EDIT - this is a hack if window is getting stuck when partially off-screen.

Code: Select all

void VID_Update (vrect_t *rects)
{
    vrect_t   rect;
    RECT   trect;

    if (!vid_palettized && palette_changed)
    {
        palette_changed = false;
        rect.x = 0;
        rect.y = 0;
        rect.width = vid.width;
        rect.height = vid.height;
        rect.pnext = NULL;
        rects = &rect;
    }

    if (firstupdate)
    {
        if ((vid_default_mode_win.value != vid_default) &&
                (!startwindowed || (vid_default_mode_win.value < MODE_FULLSCREEN_DEFAULT)))
        {
            firstupdate = 0;

            if (COM_CheckParm ("-resetwinpos"))
            {
                Cvar_SetValue ("vid_window_x", 0.0);
                Cvar_SetValue ("vid_window_y", 0.0);
            }

            if ((vid_default_mode_win.value < 0) ||
                    (vid_default_mode_win.value >= nummodes))
            {
                Cvar_SetValue ("vid_default_mode_win", windowed_default);
            }

            Cvar_SetValue ("vid_mode", vid_default_mode_win.value);
        }
    }
    // handle the mouse state when windowed if that's changed
    if (modestate == MS_WINDOWED)
    {
        VID_CheckWindowXY (); //qbism- put the check here, and nowhere else
        GetWindowRect (mainwindow, &trect);
        if ((trect.left != (int) vid_window_x.value)
                ||   (trect.top  != (int) vid_window_y.value))
            SetWindowPos (mainwindow, NULL, (int) vid_window_x.value, (int) vid_window_y.value, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW | SWP_DRAWFRAME);

        {
            if (key_dest != key_menu && !cl.paused && !cls.demoplayback) // mankrip
            {
                IN_ActivateMouse ();
                IN_HideMouse ();
            }
            else
            {
                IN_DeactivateMouse ();
                IN_ShowMouse ();
            }

        }
    }

    // We've drawn the frame; copy it to the screen
    FlipScreen (rects);

    if (vid_testingmode)
    {
        if (realtime >= vid_testendtime)
        {
            VID_SetMode (vid_realmode, vid_curpal);
            vid_testingmode = 0;
        }
    }
    else
    {
        if ((int) vid_mode.value != vid_realmode)
        {
            VID_SetMode ((int) vid_mode.value, vid_curpal);
            Cvar_SetValue ("vid_mode", (float) vid_modenum);
            // so if mode set fails, we don't keep on
            //  trying to set that mode
            vid_realmode = vid_modenum;
        }
        // mankrip - begin
        else if (vid_realmode == MODE_SETTABLE_WINDOW)
        {
            if (modelist[MODE_SETTABLE_WINDOW].width  != (int) vid_config_x.value
                    ||   modelist[MODE_SETTABLE_WINDOW].height != (int) vid_config_y.value
               )
                VID_SetMode (vid_realmode, vid_curpal);
        }
        // mankrip - end
    }
}
Last edited by qbism on Tue Dec 18, 2012 1:32 am, edited 1 time in total.
mankrip
Posts: 924
Joined: Fri Jul 04, 2008 3:02 am

Re: Game window stuck partially offscreen

Post by mankrip »

Code: Select all

      // mankrip - begin
      // the window should be moved after VID_SetMode is run, not before
The reason why I did that is because if all of the vid_window_x, vid_window_y and vid_mode cvars are changed at once (which happens during startup, and also when reloading the config file from disk through the Options menu), the window will be moved before the new video mode is set. So, by only running SetWindowPos after VID_SetMode, everything happens at once.
Ph'nglui mglw'nafh mankrip Hell's end wgah'nagl fhtagn.
==-=-=-=-=-=-=-=-=-=-==
Dev blog / Twitter / YouTube
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: Game window stuck partially offscreen

Post by qbism »

A light just dawned. The change I proposed repositions the window every frame as a work-around for getting stuck. I assumed every windowed winquake was having the same problem. But I tried a vanilla mh ddraw vid_win.c build and it did not get stuck. [EDIT - oops, turned out to be GDI version not ddraw] So I'm going to take a step back to that and build back up...
Last edited by qbism on Tue Dec 18, 2012 3:36 am, edited 1 time in total.
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Re: Game window stuck partially offscreen

Post by qbism »

...ACK, I take that back, I grabbed a GDI version of mh vid_win.c. At this point I'm going with the idea that it's a directdraw thing, keeping the hack, but moving it down below Vid_SetMode if possible.

The issue with GDI is that it is multiple-of-4 only and won't detect native resolutions that don't comply.
mankrip
Posts: 924
Joined: Fri Jul 04, 2008 3:02 am

Re: Game window stuck partially offscreen

Post by mankrip »

qbism wrote:The issue with GDI is that it is multiple-of-4 only and won't detect native resolutions that don't comply.
Ouch; I didn't know about that. This means I'm going to have to implement the DirectDraw version later.

I preferred the GDI-only version because it has less code to maintain, and also because I remember reading about DirectDraw being deprecated.
Ph'nglui mglw'nafh mankrip Hell's end wgah'nagl fhtagn.
==-=-=-=-=-=-=-=-=-=-==
Dev blog / Twitter / YouTube
Post Reply