Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions core/bind/core_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down
2 changes: 2 additions & 0 deletions core/bind/core_bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ class _OS : public Object {
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);
Expand Down
4 changes: 4 additions & 0 deletions core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class OS {
struct VideoMode {
int width, height;
bool fullscreen;
bool non_ex_fs;
Copy link
Member

@lawnjelly lawnjelly Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this works, it seems a pity that all this wasn't done as a bitfield, with an enum for setting / disabling the various flags (especially as this one seems windows only for now). If we need in the future to increase this number of flags (to deal with e.g. special modes on Android with slots etc), and adding specific setter / getters to OS for each is kind of messy.

Whether this can be changed at this stage and maintaining backward compatibility I'm not sure.

We could switch to a bitfield internal system, and add the new functionality by using enums, e.g.:

enum DisplayFeature
{
DF_MAXIMIZED,
DF_RESIZABLE,
DF_BORDERLESS,
DF_NON_EXCLUSIVE_FULLSCREEN,
};

uint32_t _display_features = 0;

OS::set_display_feature(DisplayFeature p_feature, bool p_enable)
{
if (p_enable)
{
_display_features |= p_feature;
}
else
{
_display_feature &= ~p_feature;
}
... etc
}

Have the existing functions work through this backend, and mark them deprecated or something.

Depends what others think. Maybe on the other hand we are too far down this road to bother changing in 3.x. 🤷

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4.x system with separate mode enum (windowed, minimized, maximized, fullscreen, since all are mutually exclusive) and flags bitfield (resizable, borderless) make more sense. But I'm not how many changes in 3.x we should introduce, it's probably better to keep API fully backward compatible.

bool resizable;
bool borderless_window;
bool maximized;
Expand All @@ -126,6 +127,7 @@ class OS {
use_vsync = p_use_vsync;
vsync_via_compositor = p_vsync_via_compositor;
layered = false;
non_ex_fs = false;
}
};

Expand Down Expand Up @@ -282,6 +284,8 @@ class OS {
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) {}
Expand Down
1 change: 1 addition & 0 deletions core/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion doc/classes/OS.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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">
Expand Down
8 changes: 7 additions & 1 deletion doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand All @@ -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>
Expand Down
10 changes: 10 additions & 0 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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) {
Expand Down
78 changes: 69 additions & 9 deletions platform/windows/os_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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++) {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down
3 changes: 3 additions & 0 deletions platform/windows/os_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -486,6 +487,8 @@ class OS_Windows : public OS {
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);
Expand Down