X11: Fix minimization of maximized windows

This commit is contained in:
Haoyu Qiu 2025-09-28 11:30:50 +08:00
parent b4472f4670
commit 12f8c78231
2 changed files with 83 additions and 49 deletions

View file

@ -1915,9 +1915,22 @@ void DisplayServerX11::show_window(WindowID p_id) {
DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, 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<Atom> 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); XMapWindow(x11_display, wd.x11_window);
XSync(x11_display, False); XSync(x11_display, False);
_validate_mode_on_map(p_id);
_validate_fullscreen_on_map(p_id);
if (p_id == MAIN_WINDOW_ID) { if (p_id == MAIN_WINDOW_ID) {
// Get main window size for boot splash drawing. // 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<Atom> 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 { Point2i DisplayServerX11::window_get_position(WindowID p_window) const {
_THREAD_SAFE_METHOD_ _THREAD_SAFE_METHOD_
@ -2853,15 +2912,11 @@ bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const {
return retval; 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 // Check if we applied any window modes that didn't take effect while unmapped
const WindowData &wd = windows[p_window]; const WindowData &wd = windows[p_window];
if (wd.fullscreen && !_window_fullscreen_check(p_window)) { if (wd.fullscreen && !_window_fullscreen_check(p_window)) {
_set_wm_fullscreen(p_window, true, wd.exclusive_fullscreen); _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) { if (wd.on_top) {
@ -3060,13 +3115,15 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {
} break; } break;
case WINDOW_MODE_MAXIMIZED: { case WINDOW_MODE_MAXIMIZED: {
_set_wm_maximized(p_window, false); // Varies between target modes, so do nothing here.
} break; } break;
} }
switch (p_mode) { switch (p_mode) {
case WINDOW_MODE_WINDOWED: { case WINDOW_MODE_WINDOWED: {
//do nothing if (wd.maximized) {
_set_wm_maximized(p_window, false);
}
} break; } break;
case WINDOW_MODE_MINIMIZED: { case WINDOW_MODE_MINIMIZED: {
_set_wm_minimized(p_window, true); _set_wm_minimized(p_window, true);
@ -3100,27 +3157,20 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c
ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED); ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED);
const WindowData &wd = windows[p_window]; 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;
}
}
// 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)) { if (_window_minimize_check(p_window)) {
return WINDOW_MODE_MINIMIZED; return WINDOW_MODE_MINIMIZED;
} }
if (wd.fullscreen) {
if (wd.exclusive_fullscreen) {
return WINDOW_MODE_EXCLUSIVE_FULLSCREEN;
}
return WINDOW_MODE_FULLSCREEN;
} }
// All other discarded, return windowed. if (_window_maximize_check(p_window, "_NET_WM_STATE")) {
return WINDOW_MODE_MAXIMIZED;
}
return WINDOW_MODE_WINDOWED; return WINDOW_MODE_WINDOWED;
} }
@ -4400,11 +4450,6 @@ void DisplayServerX11::_window_changed(XEvent *event) {
return; 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 // Readjusting the window position if the window is being reparented by the window manager for decoration
Window root, parent, *children; Window root, parent, *children;
unsigned int nchildren; unsigned int nchildren;
@ -5035,7 +5080,7 @@ void DisplayServerX11::process_events() {
} }
// Have we failed to set fullscreen while the window was unmapped? // 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, // 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 // only the embedded window receives the Map notification, causing it to
@ -5056,24 +5101,6 @@ void DisplayServerX11::process_events() {
Main::force_redraw(); Main::force_redraw();
} break; } 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: { case LeaveNotify: {
DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode);
if (ime_window_event) { if (ime_window_event) {
@ -5217,6 +5244,12 @@ void DisplayServerX11::process_events() {
_window_changed(&event); _window_changed(&event);
} break; } break;
case PropertyNotify: {
if (event.xproperty.atom == XInternAtom(x11_display, "_NET_WM_STATE", False)) {
_update_wm_state_hints(window_id);
}
} break;
case ButtonPress: case ButtonPress:
case ButtonRelease: { case ButtonRelease: {
if (ime_window_event || ignore_events) { if (ime_window_event || ignore_events) {

View file

@ -352,9 +352,10 @@ class DisplayServerX11 : public DisplayServer {
bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const;
bool _window_fullscreen_check(WindowID p_window) const; bool _window_fullscreen_check(WindowID p_window) const;
bool _window_minimize_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_size_hints(WindowID p_window);
void _update_actions_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_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive);
void _set_wm_maximized(WindowID p_window, bool p_enabled); void _set_wm_maximized(WindowID p_window, bool p_enabled);
void _set_wm_minimized(WindowID p_window, bool p_enabled); void _set_wm_minimized(WindowID p_window, bool p_enabled);