Merge pull request #107583 from bruvzg/non_ex_fs

[3.x] Backport nonexclusive fullscreen mode.
This commit is contained in:
lawnjelly 2025-08-29 15:32:49 +01:00 committed by GitHub
commit 5aed565de5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 113 additions and 11 deletions

View file

@ -403,6 +403,14 @@ bool _OS::is_window_fullscreen() const {
return OS::get_singleton()->is_window_fullscreen();
}
void _OS::set_window_use_nonexclusive_fullscreen(bool p_enabled) {
OS::get_singleton()->set_window_use_nonexclusive_fullscreen(p_enabled);
}
bool _OS::is_window_use_nonexclusive_fullscreen() const {
return OS::get_singleton()->is_window_use_nonexclusive_fullscreen();
}
void _OS::set_window_resizable(bool p_enabled) {
OS::get_singleton()->set_window_resizable(p_enabled);
}
@ -1371,6 +1379,8 @@ void _OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_display_cutouts"), &_OS::get_display_cutouts);
ClassDB::bind_method(D_METHOD("set_window_fullscreen", "enabled"), &_OS::set_window_fullscreen);
ClassDB::bind_method(D_METHOD("is_window_fullscreen"), &_OS::is_window_fullscreen);
ClassDB::bind_method(D_METHOD("set_window_use_nonexclusive_fullscreen", "enabled"), &_OS::set_window_use_nonexclusive_fullscreen);
ClassDB::bind_method(D_METHOD("is_window_use_nonexclusive_fullscreen"), &_OS::is_window_use_nonexclusive_fullscreen);
ClassDB::bind_method(D_METHOD("set_window_resizable", "enabled"), &_OS::set_window_resizable);
ClassDB::bind_method(D_METHOD("is_window_resizable"), &_OS::is_window_resizable);
ClassDB::bind_method(D_METHOD("set_window_minimized", "enabled"), &_OS::set_window_minimized);
@ -1568,6 +1578,7 @@ void _OS::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_borderless"), "set_borderless_window", "get_borderless_window");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_per_pixel_transparency_enabled"), "set_window_per_pixel_transparency_enabled", "get_window_per_pixel_transparency_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_fullscreen"), "set_window_fullscreen", "is_window_fullscreen");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_use_nonexclusive_fullscreen"), "set_window_use_nonexclusive_fullscreen", "is_window_use_nonexclusive_fullscreen");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_maximized"), "set_window_maximized", "is_window_maximized");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_minimized"), "set_window_minimized", "is_window_minimized");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_resizable"), "set_window_resizable", "is_window_resizable");

View file

@ -225,6 +225,8 @@ public:
virtual void set_window_size(const Size2 &p_size);
virtual void set_window_fullscreen(bool p_enabled);
virtual bool is_window_fullscreen() const;
virtual void set_window_use_nonexclusive_fullscreen(bool p_enabled);
virtual bool is_window_use_nonexclusive_fullscreen() const;
virtual void set_window_resizable(bool p_enabled);
virtual bool is_window_resizable() const;
virtual void set_window_minimized(bool p_enabled);

View file

@ -107,6 +107,7 @@ public:
struct VideoMode {
int width, height;
bool fullscreen;
bool non_ex_fs;
bool resizable;
bool borderless_window;
bool maximized;
@ -126,6 +127,7 @@ public:
use_vsync = p_use_vsync;
vsync_via_compositor = p_vsync_via_compositor;
layered = false;
non_ex_fs = false;
}
};
@ -282,6 +284,8 @@ public:
virtual void set_window_size(const Size2 p_size) {}
virtual void set_window_fullscreen(bool p_enabled) {}
virtual bool is_window_fullscreen() const { return true; }
virtual void set_window_use_nonexclusive_fullscreen(bool p_enabled) {}
virtual bool is_window_use_nonexclusive_fullscreen() const { return false; }
virtual void set_window_resizable(bool p_enabled) {}
virtual bool is_window_resizable() const { return false; }
virtual void set_window_minimized(bool p_enabled) {}

View file

@ -1082,6 +1082,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/size/resizable", true);
GLOBAL_DEF("display/window/size/borderless", false);
GLOBAL_DEF("display/window/size/fullscreen", false);
GLOBAL_DEF("display/window/size/use_nonexclusive_fullscreen", false);
GLOBAL_DEF("display/window/size/always_on_top", false);
GLOBAL_DEF("display/window/size/test_width", 0);
ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_width", PropertyInfo(Variant::INT, "display/window/size/test_width", PROPERTY_HINT_RANGE, "0,7680,1,or_greater")); // 8K resolution

View file

@ -1211,7 +1211,7 @@
[b]Note:[/b] Setting [code]window_borderless[/code] to [code]false[/code] disables per-pixel transparency.
</member>
<member name="window_fullscreen" type="bool" setter="set_window_fullscreen" getter="is_window_fullscreen" default="false">
If [code]true[/code], the window is fullscreen.
If [code]true[/code], the window is fullscreen. See also [member window_use_nonexclusive_fullscreen].
</member>
<member name="window_maximized" type="bool" setter="set_window_maximized" getter="is_window_maximized" default="false">
If [code]true[/code], the window is maximized.
@ -1234,6 +1234,11 @@
<member name="window_size" type="Vector2" setter="set_window_size" getter="get_window_size" default="Vector2( 0, 0 )">
The size of the window (without counting window manager decorations).
</member>
<member name="window_use_nonexclusive_fullscreen" type="bool" setter="set_window_use_nonexclusive_fullscreen" getter="is_window_use_nonexclusive_fullscreen" default="false">
If [code]true[/code] and [member window_fullscreen] is set, a full screen mode with full multi-window support is used.
If [code]false[/code] and [member window_fullscreen] is set, a single window fullscreen mode is used, this mode has less overhead, but only one window can be open on a given screen at a time (opening a application switching will trigger a full screen transition). This mode might not work with screen recording software.
[b]Note:[/b] This property is only implemented on Windows.
</member>
</members>
<constants>
<constant name="VIDEO_DRIVER_GLES2" value="1" enum="VideoDriver">

View file

@ -539,8 +539,9 @@
[b]Note:[/b] This setting is ignored on iOS, Android, and HTML5.
</member>
<member name="display/window/size/fullscreen" type="bool" setter="" getter="" default="false">
Sets the main window to full screen when the project starts. Note that this is not [i]exclusive[/i] fullscreen. On Windows and Linux, a borderless window is used to emulate fullscreen. On macOS, a new desktop is used to display the running project.
Sets the main window to full screen when the project starts. The display's video mode is not changed. See also [member display/window/size/use_nonexclusive_fullscreen].
Regardless of the platform, enabling fullscreen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling fullscreen mode.
[b]Note:[/b] On macOS, a new desktop is used to display the running project.
[b]Note:[/b] This setting is ignored on iOS, Android, and HTML5.
</member>
<member name="display/window/size/height" type="int" setter="" getter="" default="600">
@ -556,6 +557,11 @@
<member name="display/window/size/test_width" type="int" setter="" getter="" default="0">
If greater than zero, overrides the window width when running the game. Useful for testing stretch modes.
</member>
<member name="display/window/size/use_nonexclusive_fullscreen" type="bool" setter="" getter="" default="false">
If [code]true[/code] and [member display/window/size/fullscreen] is set, a full screen mode with full multi-window support is used.
If [code]false[/code] and [member display/window/size/fullscreen] is set, a single window fullscreen mode is used, this mode has less overhead, but only one window can be open on a given screen at a time (opening a application switching will trigger a full screen transition). This mode might not work with screen recording software.
[b]Note:[/b] This property is only implemented on Windows.
</member>
<member name="display/window/size/width" type="int" setter="" getter="" default="1024">
Sets the game's main viewport width. On desktop platforms, this is the default window size. Stretch mode settings also use this as a reference when enabled.
</member>

View file

@ -151,6 +151,7 @@ HashMap<Main::CLIScope, Vector<String>> forwardable_cli_arguments;
static OS::VideoMode video_mode;
static int init_screen = -1;
static bool init_fullscreen = false;
static bool init_non_ex_fs = false;
static bool init_maximized = false;
static bool init_windowed = false;
static bool init_always_on_top = false;
@ -641,6 +642,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
} else if (I->get() == "-f" || I->get() == "--fullscreen") { // force fullscreen
init_fullscreen = true;
} else if (I->get() == "--nonexclusive-fullscreen") { // force fullscreen
init_fullscreen = true;
init_non_ex_fs = true;
} else if (I->get() == "-m" || I->get() == "--maximized") { // force maximized window
init_maximized = true;
@ -1141,6 +1146,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (editor || project_manager) {
Engine::get_singleton()->set_editor_hint(true);
use_custom_res = false;
init_non_ex_fs = true;
input_map->load_default(); //keys for editor
} else {
input_map->load_from_globals(); //keys for game
@ -1192,6 +1198,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
video_mode.resizable = GLOBAL_GET("display/window/size/resizable");
video_mode.borderless_window = GLOBAL_GET("display/window/size/borderless");
video_mode.fullscreen = GLOBAL_GET("display/window/size/fullscreen");
video_mode.non_ex_fs = GLOBAL_GET("display/window/size/use_nonexclusive_fullscreen");
video_mode.always_on_top = GLOBAL_GET("display/window/size/always_on_top");
}
@ -1487,6 +1494,9 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
if (init_screen != -1) {
OS::get_singleton()->set_current_screen(init_screen);
}
if (init_non_ex_fs) {
OS::get_singleton()->set_window_use_nonexclusive_fullscreen(true);
}
if (init_windowed) {
//do none..
} else if (init_maximized) {

View file

@ -71,6 +71,8 @@ __declspec(dllexport) void NoHotPatch() {} // Disable Nahimic code injection.
#define GetProcAddress (void *)GetProcAddress
#endif
int constexpr FS_TRANSP_BORDER = 2;
typedef struct {
int count;
int screen;
@ -1430,6 +1432,7 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int
WindowRect.bottom = current.dmPelsHeight;
*/
int off_x = video_mode.non_ex_fs ? FS_TRANSP_BORDER : 0;
// Get the primary monitor without providing hwnd
// Solution from https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
@ -1440,7 +1443,7 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int
EnumSizeData data = { 0, primary_data.screen, Size2() };
EnumDisplayMonitors(NULL, NULL, _MonitorEnumProcSize, (LPARAM)&data);
WindowRect.right = data.size.width;
WindowRect.right = data.size.width + off_x;
WindowRect.bottom = data.size.height;
/* DEVMODE dmScreenSettings;
@ -1706,6 +1709,7 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int
}
update_real_mouse_position();
_update_window_mouse_passthrough();
return OK;
}
@ -1869,9 +1873,11 @@ void OS_Windows::set_mouse_mode(MouseMode p_mode) {
void OS_Windows::_set_mouse_mode_impl(MouseMode p_mode) {
if (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN) {
int off_x = (video_mode.fullscreen && video_mode.non_ex_fs) ? FS_TRANSP_BORDER : 0;
// Mouse is grabbed (captured or confined).
RECT clipRect;
GetClientRect(hWnd, &clipRect);
clipRect.right -= off_x;
ClientToScreen(hWnd, (POINT *)&clipRect.left);
ClientToScreen(hWnd, (POINT *)&clipRect.right);
ClipCursor(&clipRect);
@ -1952,7 +1958,16 @@ void OS_Windows::set_window_mouse_passthrough(const PoolVector2Array &p_region)
void OS_Windows::_update_window_mouse_passthrough() {
if (mpath.size() == 0) {
if (video_mode.non_ex_fs && video_mode.fullscreen) {
int cs = get_current_screen();
Size2 size = get_screen_size(cs);
HRGN region = CreateRectRgn(0, 0, size.width, size.height);
SetWindowRgn(hWnd, region, FALSE);
DeleteObject(region);
} else {
SetWindowRgn(hWnd, NULL, TRUE);
}
} else {
POINT *points = (POINT *)memalloc(sizeof(POINT) * mpath.size());
for (int i = 0; i < mpath.size(); i++) {
@ -1966,7 +1981,15 @@ void OS_Windows::_update_window_mouse_passthrough() {
}
HRGN region = CreatePolygonRgn(points, mpath.size(), ALTERNATE);
SetWindowRgn(hWnd, region, TRUE);
if (video_mode.non_ex_fs && video_mode.fullscreen) {
int cs = get_current_screen();
Size2 size = get_screen_size(cs);
HRGN region_clip = CreateRectRgn(0, 0, size.width, size.height);
CombineRgn(region, region, region_clip, RGN_AND);
DeleteObject(region_clip);
}
SetWindowRgn(hWnd, region, FALSE);
DeleteObject(region);
memfree(points);
}
@ -2007,8 +2030,9 @@ void OS_Windows::set_current_screen(int p_screen) {
}
Point2 pos = get_screen_position(p_screen);
Size2 size = get_screen_size(p_screen);
int off_x = (video_mode.non_ex_fs) ? FS_TRANSP_BORDER : 0;
MoveWindow(hWnd, pos.x, pos.y, size.width, size.height, TRUE);
MoveWindow(hWnd, pos.x, pos.y, size.width + off_x, size.height, TRUE);
} else {
Vector2 ofs = get_window_position() - get_screen_position(get_current_screen());
set_window_position(ofs + get_screen_position(p_screen));
@ -2137,7 +2161,9 @@ Size2 OS_Windows::get_window_size() const {
RECT r;
if (GetClientRect(hWnd, &r)) { // Only area inside of window border
return Size2(r.right - r.left, r.bottom - r.top);
int off_x = (video_mode.fullscreen && video_mode.non_ex_fs) ? FS_TRANSP_BORDER : 0;
return Size2(r.right - r.left - off_x, r.bottom - r.top);
}
return Size2();
}
@ -2169,7 +2195,9 @@ void OS_Windows::set_max_window_size(const Size2 p_size) {
Size2 OS_Windows::get_real_window_size() const {
RECT r;
if (GetWindowRect(hWnd, &r)) { // Includes area of the window border
return Size2(r.right - r.left, r.bottom - r.top);
int off_x = (video_mode.fullscreen && video_mode.non_ex_fs) ? FS_TRANSP_BORDER : 0;
return Size2(r.right - r.left - off_x, r.bottom - r.top);
}
return Size2();
}
@ -2208,8 +2236,9 @@ void OS_Windows::set_window_size(const Size2 p_size) {
}
}
void OS_Windows::set_window_fullscreen(bool p_enabled) {
if (video_mode.fullscreen == p_enabled)
if (video_mode.fullscreen == p_enabled) {
return;
}
if (layered_window)
set_window_per_pixel_transparency_enabled(false);
@ -2228,8 +2257,9 @@ void OS_Windows::set_window_fullscreen(bool p_enabled) {
video_mode.fullscreen = true;
_update_window_style(false);
int off_x = (video_mode.non_ex_fs) ? FS_TRANSP_BORDER : 0;
MoveWindow(hWnd, pos.x, pos.y, size.width, size.height, TRUE);
MoveWindow(hWnd, pos.x, pos.y, size.width + off_x, size.height, TRUE);
SystemParametersInfoA(SPI_GETMOUSETRAILS, 0, &restore_mouse_trails, 0);
if (restore_mouse_trails > 1) {
@ -2259,10 +2289,39 @@ void OS_Windows::set_window_fullscreen(bool p_enabled) {
SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, 0, 0);
}
}
_update_window_mouse_passthrough();
}
void OS_Windows::set_window_use_nonexclusive_fullscreen(bool p_enabled) {
if (video_mode.non_ex_fs == p_enabled) {
return;
}
video_mode.non_ex_fs = p_enabled;
if (video_mode.fullscreen) {
int cs = get_current_screen();
Point2 pos = get_screen_position(cs);
Size2 size = get_screen_size(cs);
video_mode.fullscreen = true;
_update_window_style(false);
int off_x = (video_mode.non_ex_fs) ? FS_TRANSP_BORDER : 0;
MoveWindow(hWnd, pos.x, pos.y, size.width + off_x, size.height, TRUE);
_update_window_mouse_passthrough();
}
}
bool OS_Windows::is_window_fullscreen() const {
return video_mode.fullscreen;
}
bool OS_Windows::is_window_use_nonexclusive_fullscreen() const {
return video_mode.non_ex_fs;
}
void OS_Windows::set_window_resizable(bool p_enabled) {
if (video_mode.resizable == p_enabled)
return;
@ -2402,7 +2461,8 @@ void OS_Windows::_update_window_style(bool p_repaint, bool p_maximized) {
if (p_repaint) {
RECT rect;
GetWindowRect(hWnd, &rect);
MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
int off_x = (video_mode.fullscreen && video_mode.non_ex_fs) ? FS_TRANSP_BORDER : 0;
MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left + off_x, rect.bottom - rect.top, TRUE);
}
}

View file

@ -344,6 +344,7 @@ class OS_Windows : public OS {
Size2 window_rect;
VideoMode video_mode;
bool non_ex_fs = false;
bool preserve_window_size = false;
MainLoop *main_loop;
@ -486,6 +487,8 @@ public:
virtual void set_window_size(const Size2 p_size);
virtual void set_window_fullscreen(bool p_enabled);
virtual bool is_window_fullscreen() const;
virtual void set_window_use_nonexclusive_fullscreen(bool p_enabled);
virtual bool is_window_use_nonexclusive_fullscreen() const;
virtual void set_window_resizable(bool p_enabled);
virtual bool is_window_resizable() const;
virtual void set_window_minimized(bool p_enabled);