raylib icon indicating copy to clipboard operation
raylib copied to clipboard

[rcore][desktop_GLFW][X11] `ToggleBorderlessWindowed()` can't restore decorations

Open SuperUserNameMan opened this issue 1 year ago • 14 comments

EDIT : a new version of ToggleBorderlessWindowed() might solve this issue, see code below

the issue :

ToggleBorderlessWindowed() can't restore window decorations when toggling back to windowed mode on my LinuxMint + MATE desktop.

After many testing and digging, for yet unknown reason, it appears that this bug only occurs when ToggleBorderlessWindowed() set the position of the borderless window at { 0 , 0 } with a width and height exactly equal to the video mode width and height. (see code implementation below)

If one of these parameters is increased or decreased by just one pixel, it works. I tried disabling many other settings, like GL_FLOATING etc, and found no direct correlation.

It is possible that this issue actually be caused by GLFW or X11.

However, the GLFW documentation suggest that current Raylib implementation of ToggleBorderlessWindowed() might be invalid, andmake me think that the issue could be solved on Raylib side if the implementation was changed. (see below)

Current ToggleBorderlessWindowed() implementation :

https://github.com/raysan5/raylib/blob/74680748b9abe05ceda5d412b61ff99addf72807/src/platforms/rcore_desktop_glfw.c#L191-L263

GLFW_FLOATING not intended for fullscreen implementation?

The current implementation of ToggleBorderlessWindowed() makes use of GLFW_FLOATING to implement this "borderless fullscreen window". However, according to the official GLFW documentation, it should not be used for this purpose :

https://www.glfw.org/docs/latest/window_guide.html#window_hints_wnd

GLFW_FLOATING specifies whether the windowed mode window will be floating above other regular windows, also called topmost or always-on-top. This is intended primarily for debugging purposes and cannot be used to implement proper full screen windows. Possible values are GLFW_TRUE and GLFW_FALSE. This hint is ignored for full screen windows.

This could suggest that despite the bug is not directly related to GLFW_FLOATING, the current implementation of ToggleBorderlessWindowed() is an invalid way of achieving its purpose, and that an alternative implementation should be preferred.

But i don't know yet, which one yet.


Similar X11 issue previously mentionned in Raylib code :

https://github.com/raysan5/raylib/blob/74680748b9abe05ceda5d412b61ff99addf72807/src/platforms/rcore_desktop_glfw.c#L1392-L1400

SuperUserNameMan avatar Jul 09 '24 18:07 SuperUserNameMan

As reported by @paulmelis here https://github.com/raysan5/raylib/issues/4147#issuecomment-2219636084

The behavior is slightly different depending on where the desktop menu bar is located.

My current tests confirm that when the menu bar is at the top, the position of the borderless window is put just under the menu without covering it despite ToggleBorderlessWindowed() asked it to be at { 0 , 0 }

The previous description is when the menu bar is located at the bottom.

SuperUserNameMan avatar Jul 10 '24 06:07 SuperUserNameMan

This sounds a lot like https://discourse.glfw.org/t/turning-on-off-window-decorations-while-in-full-screen-wont-work-properly/1780.

But that was years ago and was fixed in https://github.com/glfw/glfw/commit/4afa227a056681d2628894b0893527bf69496a41on the 3.4 branch. However, the function _glfwPlatformSetWindowMonitor() to which the patch was applied, no longer appears in the final src/x11_window.c as included in GLFW 3.4. The code seems to have moved to _glfwSetWindowMonitorX11(), and is included in raylib/src/external/glfw/src/x11_window.c.

paulmelis avatar Jul 10 '24 06:07 paulmelis

Ugh, https://github.com/glfw/glfw/issues/1741, seems to suggest there might also be a window manager influence.

paulmelis avatar Jul 10 '24 06:07 paulmelis

Menu bar position Borderless Window position (1st toggle) Window restoration (2nd toggle) comment
bottom { 0 , 0 } decoration not restored toggling a 3rd time and more does not move the borderless window to { 0 ,0 } again
top { 0 , menuHeight } decoration restored other toggles work like expected
left { menuWidth , 0 } decoration restored other toggles work like expected
right { 0 , 0 } decoration NOT restored toggling a 3rd time and more does not move the borderless window to { 0 ,0 } again

Can you confirm ?

(i'm going to read your links now)

SuperUserNameMan avatar Jul 10 '24 06:07 SuperUserNameMan

Can you confirm ?

I can only test with the menu bar at the top (not going to risk screwing up my work-setup here). But I suspect (and tested some values) that with all Y-positions != 0 the window decoration gets properly restored. It's the Y=0 case that has issues.

paulmelis avatar Jul 10 '24 06:07 paulmelis

@paulmelis : If you can recompile Raylib, you might want to test that version :

// Toggle borderless windowed mode
void ToggleBorderlessWindowed(void)
{
    // Leave fullscreen before attempting to set borderless windowed mode and get screen position from it
    bool wasOnFullscreen = false;
    if (CORE.Window.fullscreen)
    {
        CORE.Window.previousPosition = CORE.Window.position;
        ToggleFullscreen();
        wasOnFullscreen = true;
    }

    const int monitor = GetCurrentMonitor();
    int monitorCount;
    GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);

    if ((monitor >= 0) && (monitor < monitorCount))
    {
        const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);

        if (mode)
        {
            if (!IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE))
            {
                // Store screen position and size
                // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here
                if (!wasOnFullscreen) glfwGetWindowPos(platform.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y);
                CORE.Window.previousScreen = CORE.Window.screen;

                // Get monitor position and size

                glfwSetWindowMonitor(platform.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate);

                // Let's not wait for GLFW to call WindowSizeCallback to update these values :
                CORE.Window.screen.width = mode->width ;
                CORE.Window.screen.height = mode->height ;

                // Refocus window
                glfwFocusWindow(platform.handle);

                CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE;
            }
            else
            {
                // Return previous screen size and position
                int prevPosX = CORE.Window.previousPosition.x ;
                int prevPosY = CORE.Window.previousPosition.y ;
                int prevWidth = CORE.Window.previousScreen.width ;
                int prevHeight = CORE.Window.previousScreen.height ;
                glfwSetWindowMonitor(platform.handle, NULL, prevPosX , prevPosY, prevWidth, prevHeight, GLFW_DONT_CARE);

                // Let's not wait for GLFW to call WindowSizeCallback to update these values :
                CORE.Window.screen.width = prevWidth ;
                CORE.Window.screen.height = prevHeight ;

                // Refocus window
                glfwFocusWindow(platform.handle);

                CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE;
            }
        }
        else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor");
    }
    else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
}

edit code updated 2 times

SuperUserNameMan avatar Jul 10 '24 08:07 SuperUserNameMan

I was already trying it, saw the update in #4147 :) Here it makes ToggleBorderlessWindowed() work fine as well, not even needing the set-position call.

Edit: it does still return the incorrect (previous) screen resolution

paulmelis avatar Jul 10 '24 08:07 paulmelis

Do you have FLAG_WINDOW_HIGHDPI enabled ?

SuperUserNameMan avatar Jul 10 '24 08:07 SuperUserNameMan

Do you have FLAG_WINDOW_HIGHDPI enabled ?

No

paulmelis avatar Jul 10 '24 08:07 paulmelis

Edit: it does still return the incorrect (previous) screen resolution

As long as this flag is off, I dont notice anything wrong, even with other flags on or off. Could you be more specific ?

Here is the test code i'm using :

#include "raylib.h"


void update();
void draw();

int main( int argc , char **argv )
{
//	SetWindowState( FLAG_MSAA_4X_HINT );
	
//	SetConfigFlags(FLAG_WINDOW_HIGHDPI); // <======= ???

	InitWindow( 640 , 512 , "Test" );

//	ClearWindowState( FLAG_VSYNC_HINT );

	SetTargetFPS( 60 );

//	SetWindowState( FLAG_WINDOW_RESIZABLE );

	while( ! WindowShouldClose() )
	{
		update();

		BeginDrawing();
		{
			ClearBackground( RAYWHITE );
			draw();
		}
		EndDrawing();
	}

	CloseWindow();
}

void update()
{
	if ( IsKeyPressed( KEY_F ) )
	{
//		ToggleFullscreen();
	}
	else 
	if ( IsKeyPressed( KEY_B ) )
	{
		ToggleBorderlessWindowed();
	}
}

void draw()
{
	int sw = GetScreenWidth();
	int sh = GetScreenHeight();

	int rw = GetRenderWidth();
	int rh = GetRenderHeight();

	Vector2 dpi = GetWindowScaleDPI();

	int monitor = GetCurrentMonitor();

	int mw = GetMonitorWidth( monitor );
	int mh = GetMonitorHeight( monitor );

	Rectangle screenRect = { 0.0 , 0.0 , sw , sh };
	Rectangle renderRect = { 0.0 , 0.0 , rw , rh };

	// Draw the border of the screen :
	DrawRectangleLinesEx( screenRect , 4.0f , RED );
	DrawRectangleLinesEx( screenRect , 4.0f , GREEN );

	// Draw the text NOT in the center :

	DrawText( TextFormat( "Screen : %d x %d" , sw , sh ) , 10 , 10 , 30 , BROWN );
	DrawText( TextFormat( "Render : %d x %d" , rw , rh ) , 10 , 40 , 30 , DARKGREEN );
	DrawText( TextFormat( "Monitor[%d] : %d x %d" , monitor , mw , mh ) , 10 , 70 , 30 , DARKBLUE );
	DrawText( TextFormat( "DPI : %f x %f" , dpi.x , dpi.y ) , 10 , 100 , 30 , BLACK );

	DrawText( TextFormat( "infoRect : %f x %f" , screenRect.width , screenRect.height ) , 10 , 140 , 30 , RED ); // <===
	DrawText( TextFormat( "infoRect : %f x %f" , renderRect.width , renderRect.height ) , 10 , 170 , 30 , GREEN ); // <===
}

SuperUserNameMan avatar Jul 10 '24 09:07 SuperUserNameMan

As long as this flag is off, I dont notice anything wrong, even with other flags on or off. Could you be more specific ?

Ah, it might be that the values for GetScreenWidth() and GetScreenHeight() are invalid outside of the BeginDrawing() ... EndDrawing() block. If I read them immediately after ToggleBorderlessWindowed() they are incorrect. E.g.

    if ( IsKeyPressed( KEY_B ) )
    {
        ToggleBorderlessWindowed();
        printf("Toggled, screen now %d x %d\n", GetScreenWidth(), GetScreenHeight());
    }

paulmelis avatar Jul 10 '24 09:07 paulmelis

The values returned by these funcs are updated when WindowSizeCallback() is called by GLFW sometimes later.

We could set them directly into the ToggleBorderlessWindowed() function without further delay though :

// Toggle borderless windowed mode
void ToggleBorderlessWindowed(void)
{
    // Leave fullscreen before attempting to set borderless windowed mode and get screen position from it
    bool wasOnFullscreen = false;
    if (CORE.Window.fullscreen)
    {
        CORE.Window.previousPosition = CORE.Window.position;
        ToggleFullscreen();
        wasOnFullscreen = true;
    }

    const int monitor = GetCurrentMonitor();
    int monitorCount;
    GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);

    if ((monitor >= 0) && (monitor < monitorCount))
    {
        const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);

        if (mode)
        {
            if (!IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE))
            {
                // Store screen position and size
                // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here
                if (!wasOnFullscreen) glfwGetWindowPos(platform.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y);
                CORE.Window.previousScreen = CORE.Window.screen;

                // Get monitor position and size

                glfwSetWindowMonitor(platform.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate);

                // Let's not wait for GLFW to call WindowSizeCallback to update these values :
                CORE.Window.screen.width = mode->width ;
                CORE.Window.screen.height = mode->height ;

                // Refocus window
                glfwFocusWindow(platform.handle);

                CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE;
            }
            else
            {
                // Return previous screen size and position
                int prevPosX = CORE.Window.previousPosition.x ;
                int prevPosY = CORE.Window.previousPosition.y ;
                int prevWidth = CORE.Window.previousScreen.width ;
                int prevHeight = CORE.Window.previousScreen.height ;
                glfwSetWindowMonitor(platform.handle, NULL, prevPosX , prevPosY, prevWidth, prevHeight, GLFW_DONT_CARE);

                // Let's not wait for GLFW to call WindowSizeCallback to update these values :
                CORE.Window.screen.width = prevWidth ;
                CORE.Window.screen.height = prevHeight ;

                // Refocus window
                glfwFocusWindow(platform.handle);

                CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE;
            }
        }
        else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor");
    }
    else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
}

SuperUserNameMan avatar Jul 10 '24 09:07 SuperUserNameMan

@paulmelis : could you please test the new version of this PR #4151 ?

(it should work with FLAG_WINDOW_HIGHDPI enabled.)

(and with resizable window too)

SuperUserNameMan avatar Jul 10 '24 15:07 SuperUserNameMan

@paulmelis : could you please test the new version of this PR #4151 ?

Still works for me (not using FLAG_WINDOW_HIGHDPI, btw)

paulmelis avatar Jul 10 '24 18:07 paulmelis