From 62e7304ffe38897084fd68ca4e95436826fd92cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Mon, 16 Jun 2025 10:25:08 +0300 Subject: [PATCH] [3.x] Backport nonexclusive fullscreen mode. --- core/bind/core_bind.cpp | 11 +++++ core/bind/core_bind.h | 2 + core/os/os.h | 4 ++ core/project_settings.cpp | 1 + doc/classes/OS.xml | 7 ++- doc/classes/ProjectSettings.xml | 8 +++- main/main.cpp | 10 +++++ platform/windows/os_windows.cpp | 78 +++++++++++++++++++++++++++++---- platform/windows/os_windows.h | 3 ++ 9 files changed, 113 insertions(+), 11 deletions(-) diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index 443437ed90c..1524be06f33 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -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"); diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index 9deb342702f..b6bd3e42aba 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -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); diff --git a/core/os/os.h b/core/os/os.h index d67e9d61250..a042fc20a32 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -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) {} diff --git a/core/project_settings.cpp b/core/project_settings.cpp index 513feef688b..bba3f779ca1 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -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 diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index fc8a2fbad96..100b4357de0 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -1211,7 +1211,7 @@ [b]Note:[/b] Setting [code]window_borderless[/code] to [code]false[/code] disables per-pixel transparency. - If [code]true[/code], the window is fullscreen. + If [code]true[/code], the window is fullscreen. See also [member window_use_nonexclusive_fullscreen]. If [code]true[/code], the window is maximized. @@ -1234,6 +1234,11 @@ The size of the window (without counting window manager decorations). + + 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. + diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index efb76f6f726..f6fffa08960 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -539,8 +539,9 @@ [b]Note:[/b] This setting is ignored on iOS, Android, and HTML5. - 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. @@ -556,6 +557,11 @@ If greater than zero, overrides the window width when running the game. Useful for testing stretch modes. + + 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. + 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. diff --git a/main/main.cpp b/main/main.cpp index bc021d5a04c..49a38e9a59a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -151,6 +151,7 @@ HashMap> 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) { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index f4b9c1b40a3..9fc55a8a815 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -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) { - SetWindowRgn(hWnd, NULL, TRUE); + 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); } } diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 066911be4da..6e1219795d3 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -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);