Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
SuperUserNameMan opened this issue Jul 9, 2024 · 14 comments
Labels
platform: Linux Linux platform windowing Issues about the window system

Comments

@SuperUserNameMan
Copy link
Contributor

SuperUserNameMan commented Jul 9, 2024

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

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 10, 2024

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.

@paulmelis
Copy link
Contributor

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.

@paulmelis
Copy link
Contributor

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

@SuperUserNameMan
Copy link
Contributor Author

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)

@paulmelis
Copy link
Contributor

paulmelis commented Jul 10, 2024

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.

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 10, 2024

@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

@paulmelis
Copy link
Contributor

paulmelis commented Jul 10, 2024

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

@SuperUserNameMan
Copy link
Contributor Author

Do you have FLAG_WINDOW_HIGHDPI enabled ?

@paulmelis
Copy link
Contributor

Do you have FLAG_WINDOW_HIGHDPI enabled ?

No

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 10, 2024

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

@paulmelis
Copy link
Contributor

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

@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 10, 2024

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 added a commit to SuperUserNameMan/raylib that referenced this issue Jul 10, 2024
should fix issue raysan5#4149

Linux MATE : tested
Macos : tested

Windows : need confirmation

---

thanks to @SoloByte and to @paulmelis for their help and efforts to solve this issue
@SuperUserNameMan
Copy link
Contributor Author

SuperUserNameMan commented Jul 10, 2024

@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
Copy link
Contributor

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

Still works for me (not using FLAG_WINDOW_HIGHDPI, btw)

@raysan5 raysan5 added windowing Issues about the window system platform: Linux Linux platform labels Aug 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform: Linux Linux platform windowing Issues about the window system
Projects
None yet
Development

No branches or pull requests

3 participants