From 0f2cf6d14bcd42463d11ea5daffccaccd91fb412 Mon Sep 17 00:00:00 2001 From: Fireclaw Date: Sat, 20 Jun 2020 23:00:52 +0200 Subject: [PATCH] Add maximized property to WindowProperties, plus implementation (#809) Closes #809 Co-authored-by: rdb --- panda/src/cocoadisplay/cocoaGraphicsWindow.h | 1 + panda/src/cocoadisplay/cocoaGraphicsWindow.mm | 54 ++++++++++++- panda/src/display/config_display.cxx | 7 ++ panda/src/display/config_display.h | 1 + panda/src/display/graphicsWindow.cxx | 1 + panda/src/display/windowProperties.I | 39 ++++++++++ panda/src/display/windowProperties.cxx | 7 ++ panda/src/display/windowProperties.h | 9 +++ panda/src/windisplay/winGraphicsWindow.cxx | 72 ++++++++++++++---- panda/src/x11display/x11GraphicsPipe.cxx | 2 + panda/src/x11display/x11GraphicsPipe.h | 2 + panda/src/x11display/x11GraphicsWindow.cxx | 76 ++++++++++++++++++- tests/display/test_winprops.py | 20 +++++ 13 files changed, 275 insertions(+), 16 deletions(-) diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.h b/panda/src/cocoadisplay/cocoaGraphicsWindow.h index 1381339630..092696442a 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.h +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.h @@ -51,6 +51,7 @@ public: void handle_move_event(); void handle_resize_event(); void handle_minimize_event(bool minimized); + void handle_maximize_event(bool maximized); void handle_foreground_event(bool foreground); bool handle_close_request(); void handle_close_event(); diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index 1b941a5d84..fd25fa0041 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -405,6 +405,9 @@ open_window() { if (!_properties.has_minimized()) { _properties.set_minimized(false); } + if (!_properties.has_maximized()) { + _properties.set_maximized(false); + } if (!_properties.has_z_order()) { _properties.set_z_order(WindowProperties::Z_normal); } @@ -713,7 +716,7 @@ close_window() { if (_window != nil) { [_window close]; - + // Process events once more so any pending NSEvents are cleared. Not doing // this causes the window to stick around after calling [_window close]. process_events(); @@ -925,6 +928,14 @@ set_properties_now(WindowProperties &properties) { properties.clear_origin(); } + if (properties.has_maximized() && _window != nil) { + _properties.set_maximized(properties.get_maximized()); + if (properties.get_maximized() != !![_window isZoomed]) { + [_window zoom:nil]; + } + properties.clear_maximized(); + } + if (properties.has_title() && _window != nil) { _properties.set_title(properties.get_title()); [_window setTitle:[NSString stringWithUTF8String:properties.get_title().c_str()]]; @@ -1404,10 +1415,12 @@ handle_resize_event() { NSRect frame = [_view convertRect:[_view bounds] toView:nil]; + WindowProperties properties; + bool changed = false; + if (frame.size.width != _properties.get_x_size() || frame.size.height != _properties.get_y_size()) { - WindowProperties properties; properties.set_size(frame.size.width, frame.size.height); if (cocoadisplay_cat.is_spam()) { @@ -1415,6 +1428,18 @@ handle_resize_event() { << "Window changed size to (" << frame.size.width << ", " << frame.size.height << ")\n"; } + changed = true; + } + + if (_window != nil) { + bool is_maximized = [_window isZoomed]; + if (is_maximized != _properties.get_maximized()) { + properties.set_maximized(is_maximized); + changed = true; + } + } + + if (changed) { system_changed_properties(properties); } @@ -1444,6 +1469,31 @@ handle_minimize_event(bool minimized) { system_changed_properties(properties); } +/** + * Called by the window delegate when the window is maximized or + * demaximized. + */ +void CocoaGraphicsWindow:: +handle_maximize_event(bool maximized) { + if (maximized == _properties.get_maximized()) { + return; + } + + if (cocoadisplay_cat.is_debug()) { + if (maximized) { + cocoadisplay_cat.debug() << "Window was maximized\n"; + } else { + cocoadisplay_cat.debug() << "Window was demaximized\n"; + } + } + + WindowProperties properties; + properties.set_maximized(maximized); + system_changed_properties(properties); +} + + + /** * Called by the window delegate when the window has become the key window or * resigned that status. diff --git a/panda/src/display/config_display.cxx b/panda/src/display/config_display.cxx index 965d68f4e3..23bbf44c9c 100644 --- a/panda/src/display/config_display.cxx +++ b/panda/src/display/config_display.cxx @@ -328,6 +328,13 @@ ConfigVariableInt win_origin ConfigVariableBool fullscreen ("fullscreen", false); +ConfigVariableBool maximized +("maximized", false, + PRC_DESC("Start the window in a maximized state as handled by the window" + "manager. In comparison to the fullscreen setting, this will" + "usually not remove the window decoration and not occupy the" + "whole screen space.")); + ConfigVariableBool undecorated ("undecorated", false, PRC_DESC("This specifies the default value of the 'undecorated' window " diff --git a/panda/src/display/config_display.h b/panda/src/display/config_display.h index 21f1acb510..c425894389 100644 --- a/panda/src/display/config_display.h +++ b/panda/src/display/config_display.h @@ -73,6 +73,7 @@ extern EXPCL_PANDA_DISPLAY ConfigVariableBool old_alpha_blend; extern EXPCL_PANDA_DISPLAY ConfigVariableInt win_size; extern EXPCL_PANDA_DISPLAY ConfigVariableInt win_origin; extern EXPCL_PANDA_DISPLAY ConfigVariableBool fullscreen; +extern EXPCL_PANDA_DISPLAY ConfigVariableBool maximized; extern EXPCL_PANDA_DISPLAY ConfigVariableBool undecorated; extern EXPCL_PANDA_DISPLAY ConfigVariableBool win_fixed_size; extern EXPCL_PANDA_DISPLAY ConfigVariableBool cursor_hidden; diff --git a/panda/src/display/graphicsWindow.cxx b/panda/src/display/graphicsWindow.cxx index ee686528da..bc1ac40ddc 100644 --- a/panda/src/display/graphicsWindow.cxx +++ b/panda/src/display/graphicsWindow.cxx @@ -54,6 +54,7 @@ GraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _properties.set_undecorated(false); _properties.set_fullscreen(false); _properties.set_minimized(false); + _properties.set_maximized(false); _properties.set_cursor_hidden(false); request_properties(WindowProperties::get_default()); diff --git a/panda/src/display/windowProperties.I b/panda/src/display/windowProperties.I index d134ea2be5..71a5132224 100644 --- a/panda/src/display/windowProperties.I +++ b/panda/src/display/windowProperties.I @@ -407,6 +407,45 @@ clear_minimized() { _flags &= ~F_minimized; } +/** + * Specifies whether the window should be created maximized (true), or normal + * (false). + */ +INLINE void WindowProperties:: +set_maximized(bool maximized) { + if (maximized) { + _flags |= F_maximized; + } else { + _flags &= ~F_maximized; + } + _specified |= S_maximized; +} + +/** + * Returns true if the window is maximized. + */ +INLINE bool WindowProperties:: +get_maximized() const { + return (_flags & F_maximized) != 0; +} + +/** + * Returns true if set_maximized() has been specified. + */ +INLINE bool WindowProperties:: +has_maximized() const { + return ((_specified & S_maximized) != 0); +} + +/** + * Removes the maximized specification from the properties. + */ +INLINE void WindowProperties:: +clear_maximized() { + _specified &= ~S_maximized; + _flags &= ~F_maximized; +} + /** * Specifies whether the window should read the raw mouse devices. */ diff --git a/panda/src/display/windowProperties.cxx b/panda/src/display/windowProperties.cxx index 3d6ebeb91b..7d15b0ba16 100644 --- a/panda/src/display/windowProperties.cxx +++ b/panda/src/display/windowProperties.cxx @@ -69,6 +69,7 @@ get_config_properties() { props.set_fullscreen(fullscreen); props.set_undecorated(undecorated); props.set_fixed_size(win_fixed_size); + props.set_maximized(maximized); props.set_cursor_hidden(cursor_hidden); if (!icon_filename.empty()) { props.set_icon_filename(icon_filename); @@ -240,6 +241,9 @@ add_properties(const WindowProperties &other) { if (other.has_minimized()) { set_minimized(other.get_minimized()); } + if (other.has_maximized()) { + set_maximized(other.get_maximized()); + } if (other.has_raw_mice()) { set_raw_mice(other.get_raw_mice()); } @@ -296,6 +300,9 @@ output(ostream &out) const { if (has_minimized()) { out << (get_minimized() ? "minimized " : "!minimized "); } + if (has_maximized()) { + out << (get_maximized() ? "maximized " : "!maximized "); + } if (has_raw_mice()) { out << (get_raw_mice() ? "raw_mice " : "!raw_mice "); } diff --git a/panda/src/display/windowProperties.h b/panda/src/display/windowProperties.h index 0d9b162c6c..4a19834072 100644 --- a/panda/src/display/windowProperties.h +++ b/panda/src/display/windowProperties.h @@ -132,6 +132,13 @@ PUBLISHED: MAKE_PROPERTY2(minimized, has_minimized, get_minimized, set_minimized, clear_minimized); + INLINE void set_maximized(bool maximized); + INLINE bool get_maximized() const; + INLINE bool has_maximized() const; + INLINE void clear_maximized(); + MAKE_PROPERTY2(maximized, has_maximized, get_maximized, + set_maximized, clear_maximized); + INLINE void set_raw_mice(bool raw_mice); INLINE bool get_raw_mice() const; INLINE bool has_raw_mice() const; @@ -202,6 +209,7 @@ private: S_mouse_mode = 0x02000, S_parent_window = 0x04000, S_raw_mice = 0x08000, + S_maximized = 0x10000, }; // This bitmask represents the truefalse settings for various boolean flags @@ -211,6 +219,7 @@ private: F_fullscreen = S_fullscreen, F_foreground = S_foreground, F_minimized = S_minimized, + F_maximized = S_maximized, F_open = S_open, F_cursor_hidden = S_cursor_hidden, F_fixed_size = S_fixed_size, diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 242729d9bf..fd6364cea5 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -306,22 +306,29 @@ set_properties_now(WindowProperties &properties) { LPoint2i bottom_right = top_left + _properties.get_size(); DWORD window_style = make_style(_properties); + DWORD current_style = GetWindowLong(_hWnd, GWL_STYLE); SetWindowLong(_hWnd, GWL_STYLE, window_style); - // Now calculate the proper size and origin with the new window style. - RECT view_rect; - SetRect(&view_rect, top_left[0], top_left[1], - bottom_right[0], bottom_right[1]); - WINDOWINFO wi; - GetWindowInfo(_hWnd, &wi); - AdjustWindowRectEx(&view_rect, wi.dwStyle, FALSE, wi.dwExStyle); + // If we switched to/from undecorated, calculate the new size. + if (((window_style ^ current_style) & WS_CAPTION) != 0) { + RECT view_rect; + SetRect(&view_rect, top_left[0], top_left[1], + bottom_right[0], bottom_right[1]); + WINDOWINFO wi; + GetWindowInfo(_hWnd, &wi); + AdjustWindowRectEx(&view_rect, wi.dwStyle, FALSE, wi.dwExStyle); - // We need to call this to ensure that the style change takes effect. - SetWindowPos(_hWnd, HWND_NOTOPMOST, view_rect.left, view_rect.top, - view_rect.right - view_rect.left, - view_rect.bottom - view_rect.top, - SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | - SWP_NOSENDCHANGING | SWP_SHOWWINDOW); + SetWindowPos(_hWnd, HWND_NOTOPMOST, view_rect.left, view_rect.top, + view_rect.right - view_rect.left, + view_rect.bottom - view_rect.top, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | + SWP_NOSENDCHANGING | SWP_SHOWWINDOW); + } else { + // We need to call this to ensure that the style change takes effect. + SetWindowPos(_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | + SWP_FRAMECHANGED | SWP_NOSENDCHANGING | SWP_SHOWWINDOW); + } } if (properties.has_title()) { @@ -402,6 +409,23 @@ set_properties_now(WindowProperties &properties) { properties.clear_minimized(); } + if (properties.has_maximized()) { + if (_properties.get_maximized() != properties.get_maximized()) { + if (properties.get_maximized()) { + ShowWindow(_hWnd, SW_MAXIMIZE); + } else { + ShowWindow(_hWnd, SW_RESTORE); + } + _properties.set_maximized(properties.get_maximized()); + + if (_properties.get_minimized()) { + // Immediately minimize it again + ShowWindow(_hWnd, SW_MINIMIZE); + } + } + properties.clear_maximized(); + } + if (properties.has_fullscreen()) { if (properties.get_fullscreen() && !is_fullscreen()) { if (do_fullscreen_switch()){ @@ -507,6 +531,7 @@ open_window() { } bool want_foreground = (!_properties.has_foreground() || _properties.get_foreground()); bool want_minimized = (_properties.has_minimized() && _properties.get_minimized()) && !want_foreground; + bool want_maximized = (_properties.has_maximized() && _properties.get_maximized()) && want_foreground; HWND old_foreground_window = GetForegroundWindow(); @@ -533,6 +558,9 @@ open_window() { if (want_minimized) { ShowWindow(_hWnd, SW_MINIMIZE); ShowWindow(_hWnd, SW_MINIMIZE); + } else if (want_maximized) { + ShowWindow(_hWnd, SW_MAXIMIZE); + ShowWindow(_hWnd, SW_MAXIMIZE); } else { ShowWindow(_hWnd, SW_SHOWNORMAL); ShowWindow(_hWnd, SW_SHOWNORMAL); @@ -854,6 +882,21 @@ handle_reshape() { << "," << properties.get_y_size() << ")\n"; } + // Check whether the window has been maximized or unmaximized. + WINDOWPLACEMENT pl; + pl.length = sizeof(WINDOWPLACEMENT); + if (GetWindowPlacement(_hWnd, &pl)) { + if (pl.showCmd == SW_SHOWMAXIMIZED || (pl.flags & WPF_RESTORETOMAXIMIZED) != 0) { + properties.set_maximized(true); + } else { + properties.set_maximized(false); + } + } + else if (windisplay_cat.is_debug()) { + windisplay_cat.debug() + << "GetWindowPlacement() failed in handle_reshape. Ignoring.\n"; + } + adjust_z_order(); system_changed_properties(properties); } @@ -1083,6 +1126,9 @@ calculate_metrics(bool fullscreen, DWORD window_style, WINDOW_METRICS &metrics, bool WinGraphicsWindow:: open_graphic_window() { DWORD window_style = make_style(_properties); + if (_properties.get_maximized()) { + window_style |= WS_MAXIMIZE; + } wstring title; if (_properties.has_title()) { diff --git a/panda/src/x11display/x11GraphicsPipe.cxx b/panda/src/x11display/x11GraphicsPipe.cxx index 985360adc6..9d874a673d 100644 --- a/panda/src/x11display/x11GraphicsPipe.cxx +++ b/panda/src/x11display/x11GraphicsPipe.cxx @@ -363,6 +363,8 @@ x11GraphicsPipe(const std::string &display) : _net_wm_state_add = XInternAtom(_display, "_NET_WM_STATE_ADD", false); _net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false); _net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false); + _net_wm_state_maximized_vert = XInternAtom(_display, "_NET_WM_STATE_MAXIMIZED_VERT", false); + _net_wm_state_maximized_horz = XInternAtom(_display, "_NET_WM_STATE_MAXIMIZED_HORZ", false); } /** diff --git a/panda/src/x11display/x11GraphicsPipe.h b/panda/src/x11display/x11GraphicsPipe.h index 260ec941d6..eb47376388 100644 --- a/panda/src/x11display/x11GraphicsPipe.h +++ b/panda/src/x11display/x11GraphicsPipe.h @@ -177,6 +177,8 @@ public: Atom _net_wm_state_add; Atom _net_wm_state_remove; Atom _net_wm_bypass_compositor; + Atom _net_wm_state_maximized_vert; + Atom _net_wm_state_maximized_horz; // Extension functions. typedef int (*pfn_XcursorGetDefaultSize)(X11_Display *); diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 4372ce59ec..51a1a3eff7 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -309,6 +309,9 @@ process_events() { WindowProperties properties; bool changed_properties = false; + XPropertyEvent property_event; + bool got_net_wm_state_change = false; + while (XCheckIfEvent(_display, &event, check_event, (char *)this)) { if (got_keyrelease_event) { // If a keyrelease event is immediately followed by a matching keypress @@ -362,6 +365,19 @@ process_events() { case ReparentNotify: break; + case PropertyNotify: + //std::cout << "PropertyNotify event: atom = " << event.xproperty.atom << std::endl; + x11GraphicsPipe *x11_pipe; + DCAST_INTO_V(x11_pipe, _pipe); + if (event.xproperty.atom == x11_pipe->_net_wm_state) { + // currently we're only interested in the net_wm_state type of + // changes and only need to gather property informations once at + // the end after the while loop + property_event = event.xproperty; + got_net_wm_state_change = true; + } + break; + case ConfigureNotify: // When resizing or moving the window, multiple ConfigureNotify events // may be sent in rapid succession. We only respond to the last one. @@ -585,6 +601,45 @@ process_events() { } } + if (got_net_wm_state_change) { + // some wm state properties have been changed, check their values + // once in this part instead of multiple times in the while loop + + // Check if this window is maximized or not + bool is_maximized = false; + Atom wmState = property_event.atom; + Atom type; + int format; + unsigned long nItem, bytesAfter; + unsigned char *new_window_properties = NULL; + // gather all properties from the active dispplay and window + XGetWindowProperty(_display, _xwindow, wmState, 0, LONG_MAX, false, AnyPropertyType, &type, &format, &nItem, &bytesAfter, &new_window_properties); + if (nItem > 0) { + x11GraphicsPipe *x11_pipe; + DCAST_INTO_V(x11_pipe, _pipe); + // run through all found items + for (unsigned long iItem = 0; iItem < nItem; ++iItem) { + unsigned long item = reinterpret_cast(new_window_properties)[iItem]; + // check if the item is one of the maximized states + if (item == x11_pipe->_net_wm_state_maximized_horz || + item == x11_pipe->_net_wm_state_maximized_vert) { + // The window was maximized + is_maximized = true; + } + } + } + + // Debug entry + if (x11display_cat.is_debug()) { + x11display_cat.debug() + << "set maximized to: " << is_maximized << "\n"; + } + + // Now make sure the property will get stored correctly + properties.set_maximized(is_maximized); + changed_properties = true; + } + if (changed_properties) { system_changed_properties(properties); } @@ -791,6 +846,12 @@ set_properties_now(WindowProperties &properties) { properties.clear_fullscreen(); } + // Same for maximized. + if (properties.has_maximized()) { + _properties.set_maximized(properties.get_maximized()); + properties.clear_maximized(); + } + // The size and position of an already-open window are changed via explicit // X calls. These may still get intercepted by the window manager. Rather // than changing _properties immediately, we'll wait for the ConfigureNotify @@ -1101,7 +1162,8 @@ open_window() { KeyPressMask | KeyReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | - FocusChangeMask | StructureNotifyMask; + FocusChangeMask | StructureNotifyMask | + PropertyChangeMask; // Initialize window attributes XSetWindowAttributes wa; @@ -1269,6 +1331,18 @@ set_wm_properties(const WindowProperties &properties, bool already_mapped) { SetAction set_data[max_set_data]; int next_set_data = 0; + if (properties.has_maximized()) { + if (properties.get_maximized()) { + state_data[next_state_data++] = x11_pipe->_net_wm_state_maximized_vert; + set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_maximized_vert, 1); + state_data[next_state_data++] = x11_pipe->_net_wm_state_maximized_horz; + set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_maximized_horz, 1); + } else { + set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_maximized_vert, 0); + set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_maximized_horz, 0); + } + } + if (properties.has_fullscreen()) { if (properties.get_fullscreen()) { // For a "fullscreen" request, we pass this through, hoping the window diff --git a/tests/display/test_winprops.py b/tests/display/test_winprops.py index 4634e54c04..7d49f29683 100644 --- a/tests/display/test_winprops.py +++ b/tests/display/test_winprops.py @@ -66,3 +66,23 @@ def test_winprops_size_property(): # Test clear props.size = None assert not props.has_size() + + +def test_winprops_maximized_property(): + props = WindowProperties() + + # Test get + props.set_maximized(True) + assert props.maximized == True + + # Test has + props.clear_maximized() + assert props.maximized is None + + # Test set + props.maximized = True + assert props.get_maximized() == True + + # Test clear + props.maximized = None + assert not props.has_maximized()