raysan5 / raylib

A simple and easy-to-use library to enjoy videogames programming

Home Page:http://www.raylib.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

SuperUserNameMan opened this issue · 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 :

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;
// Set undecorated and topmost modes and flags
glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_FALSE);
CORE.Window.flags |= FLAG_WINDOW_UNDECORATED;
glfwSetWindowAttrib(platform.handle, GLFW_FLOATING, GLFW_TRUE);
CORE.Window.flags |= FLAG_WINDOW_TOPMOST;
// Get monitor position and size
int monitorPosX = 0;
int monitorPosY = 0;
glfwGetMonitorPos(monitors[monitor], &monitorPosX, &monitorPosY);
const int monitorWidth = mode->width;
const int monitorHeight = mode->height;
// Set screen position and size
glfwSetWindowPos(platform.handle, monitorPosX, monitorPosY);
glfwSetWindowSize(platform.handle, monitorWidth, monitorHeight);
// Refocus window
glfwFocusWindow(platform.handle);
CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE;
}
else
{
// Remove topmost and undecorated modes and flags
glfwSetWindowAttrib(platform.handle, GLFW_FLOATING, GLFW_FALSE);
CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST;
glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE);
CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED;
// Return previous screen size and position
// NOTE: The order matters here, it must set size first, then set position, otherwise the screen will be positioned incorrectly
glfwSetWindowSize(platform.handle, CORE.Window.previousScreen.width, CORE.Window.previousScreen.height);
glfwSetWindowPos(platform.handle, CORE.Window.previousPosition.x, CORE.Window.previousPosition.y);
// 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");
}

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 :

// Remember center for switching from fullscreen to window
if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width))
{
// If screen width/height equal to the display, we can't calculate the window pos for toggling full-screened/windowed.
// Toggling full-screened/windowed with pos(0, 0) can cause problems in some platforms, such as X11.
CORE.Window.position.x = CORE.Window.display.width/4;
CORE.Window.position.y = CORE.Window.display.height/4;
}
else

As reported by @paulmelis here #4147 (comment)

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.

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 glfw/glfw@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.

Ugh, glfw/glfw#1741, seems to suggest there might also be a window manager influence.

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)

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 :
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

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

Do you have FLAG_WINDOW_HIGHDPI enabled ?

Do you have FLAG_WINDOW_HIGHDPI enabled ?

No

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 ); // <===
}

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());
    }

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");
}

@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)

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

Still works for me (not using FLAG_WINDOW_HIGHDPI, btw)