From 862ab82c195a8e101962837f06497f3c43cd7c43 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 15 Jul 2020 14:21:42 -0600 Subject: [PATCH] WindowServer: Fixes for modal windows This fixes a few problems with modal windows: * If any child window, or any child window further down the tree is considered modal, then all windows in that chain are modal. * When trying to activate a window blocked by a modal child bring the entire stack of modal windows to the front and activate the modal window. * A window is modal if it has a parent and it's flagged as modal, regardless of whether the ClientConnection has created modal windows. This technically supports diverging modal window trees as well, where two modal windows share the same parent, allowing both to be activated (including for input) but not the parent. And it should also support modal window stacks of arbitrary depth. --- Services/WindowServer/ClientConnection.cpp | 10 ---- Services/WindowServer/ClientConnection.h | 2 - Services/WindowServer/Window.cpp | 20 ++++--- Services/WindowServer/Window.h | 4 +- Services/WindowServer/WindowManager.cpp | 68 ++++++++++++++++------ Services/WindowServer/WindowManager.h | 1 - 6 files changed, 63 insertions(+), 42 deletions(-) diff --git a/Services/WindowServer/ClientConnection.cpp b/Services/WindowServer/ClientConnection.cpp index 9089ece782..d3eaf9ee4a 100644 --- a/Services/WindowServer/ClientConnection.cpp +++ b/Services/WindowServer/ClientConnection.cpp @@ -689,16 +689,6 @@ OwnPtr ClientConnection::handle(const Mes return make(client_id(), Screen::the().rect(), Gfx::current_system_theme_buffer_id()); } -bool ClientConnection::is_showing_modal_window() const -{ - for (auto& it : m_windows) { - auto& window = *it.value; - if (window.is_visible() && window.is_modal()) - return true; - } - return false; -} - void ClientConnection::handle(const Messages::WindowServer::WM_SetWindowTaskbarRect& message) { auto* client = ClientConnection::from_client_id(message.client_id()); diff --git a/Services/WindowServer/ClientConnection.h b/Services/WindowServer/ClientConnection.h index 331dd79327..7c5ff5aeca 100644 --- a/Services/WindowServer/ClientConnection.h +++ b/Services/WindowServer/ClientConnection.h @@ -61,8 +61,6 @@ public: MenuBar* app_menubar() { return m_app_menubar.ptr(); } - bool is_showing_modal_window() const; - void notify_about_new_screen_rect(const Gfx::IntRect&); void post_paint_message(Window&, bool ignore_occlusion = false); diff --git a/Services/WindowServer/Window.cpp b/Services/WindowServer/Window.cpp index d4488c47df..afb0f8d449 100644 --- a/Services/WindowServer/Window.cpp +++ b/Services/WindowServer/Window.cpp @@ -387,16 +387,20 @@ bool Window::is_active() const return WindowManager::the().active_window() == this; } -bool Window::is_blocked_by_modal_window() const +Window* Window::is_blocked_by_modal_window() { - bool is_any_modal = false; - const Window* next = this; - while (!is_any_modal && next) { - is_any_modal = next->is_modal(); - next = next->parent_window(); + // A window is blocked if any immediate child, or any child further + // down the chain is modal + for (auto& window: m_child_windows) { + if (window) { + if (window->is_modal()) + return window; + + if (auto* blocking_modal_window = window->is_blocked_by_modal_window()) + return blocking_modal_window; + } } - - return !is_any_modal && client() && client()->is_showing_modal_window(); + return nullptr; } void Window::set_default_icon() diff --git a/Services/WindowServer/Window.h b/Services/WindowServer/Window.h index bc1f124c6f..c7033853b8 100644 --- a/Services/WindowServer/Window.h +++ b/Services/WindowServer/Window.h @@ -116,7 +116,7 @@ public: WindowFrame& frame() { return m_frame; } const WindowFrame& frame() const { return m_frame; } - bool is_blocked_by_modal_window() const; + Window* is_blocked_by_modal_window(); bool listens_to_wm_events() const { return m_listens_to_wm_events; } @@ -145,7 +145,7 @@ public: bool is_visible() const { return m_visible; } void set_visible(bool); - bool is_modal() const { return m_modal; } + bool is_modal() const { return m_modal && m_parent_window; } Gfx::IntRect rect() const { return m_rect; } void set_rect(const Gfx::IntRect&); diff --git a/Services/WindowServer/WindowManager.cpp b/Services/WindowServer/WindowManager.cpp index afee322f74..97093a4a75 100644 --- a/Services/WindowServer/WindowManager.cpp +++ b/Services/WindowServer/WindowManager.cpp @@ -205,22 +205,46 @@ void WindowManager::add_window(Window& window) void WindowManager::move_to_front_and_make_active(Window& window) { - if (window.is_blocked_by_modal_window()) - return; - - bool make_active = true; - if (window.is_accessory()) { - auto* parent = window.parent_window(); - do_move_to_front(*parent, true, false); - make_active = false; - - for (auto& accessory_window : parent->accessory_windows()) { - if (accessory_window && accessory_window.ptr() != &window) - do_move_to_front(*accessory_window, false, false); + auto move_window_to_front = [&](Window& wnd, bool make_active, bool make_input) { + if (wnd.is_accessory()) { + auto* parent = wnd.parent_window(); + do_move_to_front(*parent, true, false); + make_active = false; + + for (auto& accessory_window : parent->accessory_windows()) { + if (accessory_window && accessory_window.ptr() != &wnd) + do_move_to_front(*accessory_window, false, false); + } } - } - do_move_to_front(window, make_active, true); + do_move_to_front(wnd, make_active, make_input); + }; + + auto* blocking_modal_window = window.is_blocked_by_modal_window(); + if (blocking_modal_window || window.is_modal()) { + // If a window that is currently blocked by a modal child is being + // brought to the front, bring the entire stack of modal windows + // to the front and activate the modal window. Also set the + // active input window to that same window (which would pull + // active input from any accessory window) + Vector modal_stack; + auto* modal_stack_top = blocking_modal_window ? blocking_modal_window : &window; + for (auto* parent = modal_stack_top->parent_window(); parent; parent = parent->parent_window()) { + if (parent->is_blocked_by_modal_window() != blocking_modal_window) + break; + modal_stack.append(parent); + if (!parent->is_modal()) + break; + } + if (!modal_stack.is_empty()) { + for (size_t i = modal_stack.size(); i > 0; i--) { + move_window_to_front(*modal_stack[i - 1], false, false); + } + } + move_window_to_front(*modal_stack_top, true, true); + } else { + move_window_to_front(window, true, true); + } } void WindowManager::do_move_to_front(Window& window, bool make_active, bool make_input) @@ -858,7 +882,7 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind HashTable windows_who_received_mouse_event_due_to_cursor_tracking; for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) { - if (!window->global_cursor_tracking() || !window->is_visible() || window->is_minimized()) + if (!window->global_cursor_tracking() || !window->is_visible() || window->is_minimized() || window->is_blocked_by_modal_window()) continue; windows_who_received_mouse_event_due_to_cursor_tracking.set(window); auto translated_event = event.translated(-window->position()); @@ -866,7 +890,7 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind } // FIXME: Now that the menubar has a dedicated window, is this special-casing really necessary? - if (MenuManager::the().has_open_menu() || (!active_window_is_modal() && menubar_rect().contains(event.position()))) { + if (MenuManager::the().has_open_menu() || menubar_rect().contains(event.position())) { clear_resize_candidate(); MenuManager::the().dispatch_event(event); return; @@ -939,7 +963,7 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind } hovered_window = &window; - if (!window.global_cursor_tracking() && !windows_who_received_mouse_event_due_to_cursor_tracking.contains(&window)) { + if (!window.global_cursor_tracking() && !windows_who_received_mouse_event_due_to_cursor_tracking.contains(&window) && !window.is_blocked_by_modal_window()) { auto translated_event = event.translated(-window.position()); deliver_mouse_event(window, translated_event); if (event.type() == Event::MouseDown) { @@ -1139,8 +1163,14 @@ Window* WindowManager::set_active_input_window(Window* window) void WindowManager::set_active_window(Window* window, bool make_input) { - if (window && window->is_blocked_by_modal_window()) - return; + if (window) { + if (auto* modal_window = window->is_blocked_by_modal_window()) { + ASSERT(modal_window->is_modal()); + ASSERT(modal_window != window); + window = modal_window; + make_input = true; + } + } if (window && !window_type_can_become_active(window->type())) return; diff --git a/Services/WindowServer/WindowManager.h b/Services/WindowServer/WindowManager.h index 190750d040..08f15c0b77 100644 --- a/Services/WindowServer/WindowManager.h +++ b/Services/WindowServer/WindowManager.h @@ -112,7 +112,6 @@ public: Window* active_input_window() { return m_active_input_window.ptr(); } const Window* active_input_window() const { return m_active_input_window.ptr(); } const ClientConnection* active_client() const; - bool active_window_is_modal() const { return m_active_window && m_active_window->is_modal(); } const Window* highlight_window() const { return m_highlight_window.ptr(); } void set_highlight_window(Window*);