[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 :
raylib/src/platforms/rcore_desktop_glfw.c
Lines 191 to 263 in 7468074
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 :
raylib/src/platforms/rcore_desktop_glfw.c
Lines 1392 to 1400 in 7468074
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)