From 12f8c78231de1694bcafe68f9eccddb0f5615c9b Mon Sep 17 00:00:00 2001 From: Haoyu Qiu Date: Sun, 28 Sep 2025 11:30:50 +0800 Subject: [PATCH] X11: Fix minimization of maximized windows --- platform/linuxbsd/x11/display_server_x11.cpp | 129 ++++++++++++------- platform/linuxbsd/x11/display_server_x11.h | 3 +- 2 files changed, 83 insertions(+), 49 deletions(-) diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 0be999cc867..00016873dc8 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -1915,9 +1915,22 @@ void DisplayServerX11::show_window(WindowID p_id) { DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, p_id); + // Setup initial minimize/maximize state. + // `_NET_WM_STATE` can be set directly when the window is unmapped. + LocalVector hints; + if (wd.maximized) { + hints.push_back(XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False)); + hints.push_back(XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False)); + } + if (wd.minimized) { + hints.push_back(XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False)); + } + XChangeProperty(x11_display, wd.x11_window, XInternAtom(x11_display, "_NET_WM_STATE", False), XA_ATOM, 32, PropModeReplace, (unsigned char *)hints.ptr(), hints.size()); + XMapWindow(x11_display, wd.x11_window); XSync(x11_display, False); - _validate_mode_on_map(p_id); + + _validate_fullscreen_on_map(p_id); if (p_id == MAIN_WINDOW_ID) { // Get main window size for boot splash drawing. @@ -2446,6 +2459,52 @@ void DisplayServerX11::_update_actions_hints(WindowID p_window) { } } +void DisplayServerX11::_update_wm_state_hints(WindowID p_window) { + WindowData &wd = windows[p_window]; + + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + + int result = XGetWindowProperty( + x11_display, + wd.x11_window, + XInternAtom(x11_display, "_NET_WM_STATE", False), + 0, + 1024, + False, + XA_ATOM, + &type, + &format, + &len, + &remaining, + &data); + if (result != Success) { + return; + } + + LocalVector hints; + if (data) { + hints.resize(len); + Atom *atoms = (Atom *)data; + for (unsigned long i = 0; i < len; i++) { + hints[i] = atoms[i]; + } + XFree(data); + } + + Atom fullscreen_atom = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False); + Atom maximized_horz_atom = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + Atom maximized_vert_atom = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False); + Atom hidden_atom = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); + + wd.fullscreen = hints.has(fullscreen_atom); + wd.maximized = hints.has(maximized_horz_atom) && hints.has(maximized_vert_atom); + wd.minimized = hints.has(hidden_atom); +} + Point2i DisplayServerX11::window_get_position(WindowID p_window) const { _THREAD_SAFE_METHOD_ @@ -2853,15 +2912,11 @@ bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const { return retval; } -void DisplayServerX11::_validate_mode_on_map(WindowID p_window) { +void DisplayServerX11::_validate_fullscreen_on_map(WindowID p_window) { // Check if we applied any window modes that didn't take effect while unmapped const WindowData &wd = windows[p_window]; if (wd.fullscreen && !_window_fullscreen_check(p_window)) { _set_wm_fullscreen(p_window, true, wd.exclusive_fullscreen); - } else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) { - _set_wm_maximized(p_window, true); - } else if (wd.minimized && !_window_minimize_check(p_window)) { - _set_wm_minimized(p_window, true); } if (wd.on_top) { @@ -3060,13 +3115,15 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_MAXIMIZED: { - _set_wm_maximized(p_window, false); + // Varies between target modes, so do nothing here. } break; } switch (p_mode) { case WINDOW_MODE_WINDOWED: { - //do nothing + if (wd.maximized) { + _set_wm_maximized(p_window, false); + } } break; case WINDOW_MODE_MINIMIZED: { _set_wm_minimized(p_window, true); @@ -3100,28 +3157,21 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED); const WindowData &wd = windows[p_window]; - if (wd.fullscreen) { //if fullscreen, it's not in another mode - if (wd.exclusive_fullscreen) { - return WINDOW_MODE_EXCLUSIVE_FULLSCREEN; - } else { - return WINDOW_MODE_FULLSCREEN; - } + if (_window_minimize_check(p_window)) { + return WINDOW_MODE_MINIMIZED; + } + + if (wd.fullscreen) { + if (wd.exclusive_fullscreen) { + return WINDOW_MODE_EXCLUSIVE_FULLSCREEN; + } + return WINDOW_MODE_FULLSCREEN; } - // Test maximized. - // Using EWMH -- Extended Window Manager Hints if (_window_maximize_check(p_window, "_NET_WM_STATE")) { return WINDOW_MODE_MAXIMIZED; } - { - if (_window_minimize_check(p_window)) { - return WINDOW_MODE_MINIMIZED; - } - } - - // All other discarded, return windowed. - return WINDOW_MODE_WINDOWED; } @@ -4400,11 +4450,6 @@ void DisplayServerX11::_window_changed(XEvent *event) { return; } - // Query display server about a possible new window state. - wd.fullscreen = _window_fullscreen_check(window_id); - wd.maximized = _window_maximize_check(window_id, "_NET_WM_STATE") && !wd.fullscreen; - wd.minimized = _window_minimize_check(window_id) && !wd.fullscreen && !wd.maximized; - // Readjusting the window position if the window is being reparented by the window manager for decoration Window root, parent, *children; unsigned int nchildren; @@ -5035,7 +5080,7 @@ void DisplayServerX11::process_events() { } // Have we failed to set fullscreen while the window was unmapped? - _validate_mode_on_map(window_id); + _validate_fullscreen_on_map(window_id); // On KDE Plasma, when the parent window of an embedded process is restored after being minimized, // only the embedded window receives the Map notification, causing it to @@ -5056,24 +5101,6 @@ void DisplayServerX11::process_events() { Main::force_redraw(); } break; - case NoExpose: { - DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id); - if (ime_window_event) { - break; - } - - windows[window_id].minimized = true; - } break; - - case VisibilityNotify: { - DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state); - if (ime_window_event) { - break; - } - - windows[window_id].minimized = _window_minimize_check(window_id); - } break; - case LeaveNotify: { DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); if (ime_window_event) { @@ -5217,6 +5244,12 @@ void DisplayServerX11::process_events() { _window_changed(&event); } break; + case PropertyNotify: { + if (event.xproperty.atom == XInternAtom(x11_display, "_NET_WM_STATE", False)) { + _update_wm_state_hints(window_id); + } + } break; + case ButtonPress: case ButtonRelease: { if (ime_window_event || ignore_events) { diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index e155248a3da..d979c814c1f 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -352,9 +352,10 @@ class DisplayServerX11 : public DisplayServer { bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; bool _window_fullscreen_check(WindowID p_window) const; bool _window_minimize_check(WindowID p_window) const; - void _validate_mode_on_map(WindowID p_window); + void _validate_fullscreen_on_map(WindowID p_window); void _update_size_hints(WindowID p_window); void _update_actions_hints(WindowID p_window); + void _update_wm_state_hints(WindowID p_window); void _set_wm_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive); void _set_wm_maximized(WindowID p_window, bool p_enabled); void _set_wm_minimized(WindowID p_window, bool p_enabled);