diff --git a/Base/etc/WindowServer.ini b/Base/etc/WindowServer.ini index 62843c13dca..18a20ac35df 100644 --- a/Base/etc/WindowServer.ini +++ b/Base/etc/WindowServer.ini @@ -50,3 +50,6 @@ Mode=stretch [Applet] Order=CPUGraph,MemoryGraph,Network,ClipboardHistory,Audio +[VirtualDesktops] +Rows=2 +Columns=2 diff --git a/Userland/Libraries/LibGUI/Desktop.cpp b/Userland/Libraries/LibGUI/Desktop.cpp index 6bc688c6bab..3363c448e8d 100644 --- a/Userland/Libraries/LibGUI/Desktop.cpp +++ b/Userland/Libraries/LibGUI/Desktop.cpp @@ -25,7 +25,7 @@ Desktop::Desktop() { } -void Desktop::did_receive_screen_rects(Badge, const Vector& rects, size_t main_screen_index) +void Desktop::did_receive_screen_rects(Badge, const Vector& rects, size_t main_screen_index, unsigned virtual_desktop_rows, unsigned virtual_desktop_columns) { m_main_screen_index = main_screen_index; m_rects = rects; @@ -36,6 +36,9 @@ void Desktop::did_receive_screen_rects(Badge, const Vect } else { m_bounding_rect = {}; } + + m_virtual_desktop_rows = virtual_desktop_rows; + m_virtual_desktop_columns = virtual_desktop_columns; } void Desktop::set_background_color(const StringView& background_color) diff --git a/Userland/Libraries/LibGUI/Desktop.h b/Userland/Libraries/LibGUI/Desktop.h index a915ea4bac6..bdcda749739 100644 --- a/Userland/Libraries/LibGUI/Desktop.h +++ b/Userland/Libraries/LibGUI/Desktop.h @@ -36,14 +36,19 @@ public: const Vector& rects() const { return m_rects; } size_t main_screen_index() const { return m_main_screen_index; } + unsigned virtual_desktop_rows() const { return m_virtual_desktop_rows; } + unsigned virtual_desktop_columns() const { return m_virtual_desktop_columns; } + int taskbar_height() const { return TaskbarWindow::taskbar_height(); } - void did_receive_screen_rects(Badge, const Vector&, size_t); + void did_receive_screen_rects(Badge, const Vector&, size_t, unsigned, unsigned); private: Vector m_rects; size_t m_main_screen_index { 0 }; Gfx::IntRect m_bounding_rect; + unsigned m_virtual_desktop_rows { 1 }; + unsigned m_virtual_desktop_columns { 1 }; }; } diff --git a/Userland/Libraries/LibGUI/Event.h b/Userland/Libraries/LibGUI/Event.h index 92035a8d05c..446e020beb6 100644 --- a/Userland/Libraries/LibGUI/Event.h +++ b/Userland/Libraries/LibGUI/Event.h @@ -64,6 +64,7 @@ public: WM_AppletAreaSizeChanged, WM_SuperKeyPressed, WM_SuperSpaceKeyPressed, + WM_VirtualDesktopChanged, __End_WM_Events, }; @@ -135,13 +136,15 @@ public: class WMWindowStateChangedEvent : public WMEvent { public: - WMWindowStateChangedEvent(int client_id, int window_id, int parent_client_id, int parent_window_id, const StringView& title, const Gfx::IntRect& rect, bool is_active, bool is_modal, WindowType window_type, bool is_minimized, bool is_frameless, Optional progress) + WMWindowStateChangedEvent(int client_id, int window_id, int parent_client_id, int parent_window_id, const StringView& title, const Gfx::IntRect& rect, unsigned virtual_desktop_row, unsigned virtual_desktop_column, bool is_active, bool is_modal, WindowType window_type, bool is_minimized, bool is_frameless, Optional progress) : WMEvent(Event::Type::WM_WindowStateChanged, client_id, window_id) , m_parent_client_id(parent_client_id) , m_parent_window_id(parent_window_id) , m_title(title) , m_rect(rect) , m_window_type(window_type) + , m_virtual_desktop_row(virtual_desktop_row) + , m_virtual_desktop_column(virtual_desktop_column) , m_active(is_active) , m_modal(is_modal) , m_minimized(is_minimized) @@ -160,6 +163,8 @@ public: bool is_minimized() const { return m_minimized; } bool is_frameless() const { return m_frameless; } Optional progress() const { return m_progress; } + unsigned virtual_desktop_row() const { return m_virtual_desktop_row; } + unsigned virtual_desktop_column() const { return m_virtual_desktop_column; } private: int m_parent_client_id; @@ -167,6 +172,8 @@ private: String m_title; Gfx::IntRect m_rect; WindowType m_window_type; + unsigned m_virtual_desktop_row; + unsigned m_virtual_desktop_column; bool m_active; bool m_modal; bool m_minimized; @@ -202,6 +209,23 @@ private: RefPtr m_bitmap; }; +class WMVirtualDesktopChangedEvent : public WMEvent { +public: + explicit WMVirtualDesktopChangedEvent(int client_id, unsigned current_row, unsigned current_column) + : WMEvent(Event::Type::WM_VirtualDesktopChanged, client_id, 0) + , m_current_row(current_row) + , m_current_column(current_column) + { + } + + unsigned current_row() const { return m_current_row; } + unsigned current_column() const { return m_current_column; } + +private: + const unsigned m_current_row; + const unsigned m_current_column; +}; + class MultiPaintEvent final : public Event { public: explicit MultiPaintEvent(const Vector& rects, const Gfx::IntSize& window_size) diff --git a/Userland/Libraries/LibGUI/WindowManagerServerConnection.cpp b/Userland/Libraries/LibGUI/WindowManagerServerConnection.cpp index b3bc3016244..b9b2f7c84cd 100644 --- a/Userland/Libraries/LibGUI/WindowManagerServerConnection.cpp +++ b/Userland/Libraries/LibGUI/WindowManagerServerConnection.cpp @@ -20,11 +20,12 @@ WindowManagerServerConnection& WindowManagerServerConnection::the() } void WindowManagerServerConnection::window_state_changed(i32 wm_id, i32 client_id, i32 window_id, - i32 parent_client_id, i32 parent_window_id, bool is_active, bool is_minimized, bool is_modal, - bool is_frameless, i32 window_type, String const& title, Gfx::IntRect const& rect, Optional const& progress) + i32 parent_client_id, i32 parent_window_id, u32 virtual_desktop_row, u32 virtual_desktop_column, + bool is_active, bool is_minimized, bool is_modal, bool is_frameless, i32 window_type, + String const& title, Gfx::IntRect const& rect, Optional const& progress) { if (auto* window = Window::from_window_id(wm_id)) - Core::EventLoop::current().post_event(*window, make(client_id, window_id, parent_client_id, parent_window_id, title, rect, is_active, is_modal, static_cast(window_type), is_minimized, is_frameless, progress)); + Core::EventLoop::current().post_event(*window, make(client_id, window_id, parent_client_id, parent_window_id, title, rect, virtual_desktop_row, virtual_desktop_column, is_active, is_modal, static_cast(window_type), is_minimized, is_frameless, progress)); } void WindowManagerServerConnection::applet_area_size_changed(i32 wm_id, const Gfx::IntSize& size) @@ -63,4 +64,11 @@ void WindowManagerServerConnection::super_space_key_pressed(i32 wm_id) if (auto* window = Window::from_window_id(wm_id)) Core::EventLoop::current().post_event(*window, make(wm_id)); } + +void WindowManagerServerConnection::virtual_desktop_changed(i32 wm_id, u32 row, u32 column) +{ + if (auto* window = Window::from_window_id(wm_id)) + Core::EventLoop::current().post_event(*window, make(wm_id, row, column)); +} + } diff --git a/Userland/Libraries/LibGUI/WindowManagerServerConnection.h b/Userland/Libraries/LibGUI/WindowManagerServerConnection.h index 990dbd18419..b91d4e9005c 100644 --- a/Userland/Libraries/LibGUI/WindowManagerServerConnection.h +++ b/Userland/Libraries/LibGUI/WindowManagerServerConnection.h @@ -27,12 +27,13 @@ public: private: virtual void window_removed(i32, i32, i32) override; - virtual void window_state_changed(i32, i32, i32, i32, i32, bool, bool, bool, bool, i32, String const&, Gfx::IntRect const&, Optional const&) override; + virtual void window_state_changed(i32, i32, i32, i32, i32, u32, u32, bool, bool, bool, bool, i32, String const&, Gfx::IntRect const&, Optional const&) override; virtual void window_icon_bitmap_changed(i32, i32, i32, Gfx::ShareableBitmap const&) override; virtual void window_rect_changed(i32, i32, i32, Gfx::IntRect const&) override; virtual void applet_area_size_changed(i32, Gfx::IntSize const&) override; virtual void super_key_pressed(i32) override; virtual void super_space_key_pressed(i32) override; + virtual void virtual_desktop_changed(i32, u32, u32) override; }; } diff --git a/Userland/Libraries/LibGUI/WindowServerConnection.cpp b/Userland/Libraries/LibGUI/WindowServerConnection.cpp index a0ddbf0e98b..e91d244f76b 100644 --- a/Userland/Libraries/LibGUI/WindowServerConnection.cpp +++ b/Userland/Libraries/LibGUI/WindowServerConnection.cpp @@ -48,12 +48,12 @@ WindowServerConnection::WindowServerConnection() // All we have to do is wait for it to arrive. This avoids a round-trip during application startup. auto message = wait_for_specific_message(); set_system_theme_from_anonymous_buffer(message->theme_buffer()); - Desktop::the().did_receive_screen_rects({}, message->screen_rects(), message->main_screen_index()); + Desktop::the().did_receive_screen_rects({}, message->screen_rects(), message->main_screen_index(), message->virtual_desktop_rows(), message->virtual_desktop_columns()); Gfx::FontDatabase::set_default_font_query(message->default_font_query()); Gfx::FontDatabase::set_fixed_width_font_query(message->fixed_width_font_query()); } -void WindowServerConnection::fast_greet(Vector const&, u32, Core::AnonymousBuffer const&, String const&, String const&) +void WindowServerConnection::fast_greet(Vector const&, u32, u32, u32, Core::AnonymousBuffer const&, String const&, String const&) { // NOTE: This message is handled in the constructor. } @@ -311,9 +311,9 @@ void WindowServerConnection::menu_item_left(i32 menu_id, u32 identifier) Core::EventLoop::current().post_event(*app, make(GUI::Event::ActionLeave, *action)); } -void WindowServerConnection::screen_rects_changed(Vector const& rects, u32 main_screen_index) +void WindowServerConnection::screen_rects_changed(Vector const& rects, u32 main_screen_index, u32 virtual_desktop_rows, u32 virtual_desktop_columns) { - Desktop::the().did_receive_screen_rects({}, rects, main_screen_index); + Desktop::the().did_receive_screen_rects({}, rects, main_screen_index, virtual_desktop_rows, virtual_desktop_columns); Window::for_each_window({}, [&](auto& window) { Core::EventLoop::current().post_event(window, make(rects, main_screen_index)); }); diff --git a/Userland/Libraries/LibGUI/WindowServerConnection.h b/Userland/Libraries/LibGUI/WindowServerConnection.h index f8df378dd84..d68aa7aad25 100644 --- a/Userland/Libraries/LibGUI/WindowServerConnection.h +++ b/Userland/Libraries/LibGUI/WindowServerConnection.h @@ -23,7 +23,7 @@ public: private: WindowServerConnection(); - virtual void fast_greet(Vector const&, u32, Core::AnonymousBuffer const&, String const&, String const&) override; + virtual void fast_greet(Vector const&, u32, u32, u32, Core::AnonymousBuffer const&, String const&, String const&) override; virtual void paint(i32, Gfx::IntSize const&, Vector const&) override; virtual void mouse_move(i32, Gfx::IntPoint const&, u32, u32, u32, i32, bool, Vector const&) override; virtual void mouse_down(i32, Gfx::IntPoint const&, u32, u32, u32, i32) override; @@ -44,7 +44,7 @@ private: virtual void menu_item_entered(i32, u32) override; virtual void menu_item_left(i32, u32) override; virtual void menu_visibility_did_change(i32, bool) override; - virtual void screen_rects_changed(Vector const&, u32) override; + virtual void screen_rects_changed(Vector const&, u32, u32, u32) override; virtual void set_wallpaper_finished(bool) override; virtual void drag_dropped(i32, Gfx::IntPoint const&, String const&, HashMap const&) override; virtual void drag_accepted() override; diff --git a/Userland/Services/WindowServer/ClientConnection.cpp b/Userland/Services/WindowServer/ClientConnection.cpp index b7ad467f53a..19461657fa9 100644 --- a/Userland/Services/WindowServer/ClientConnection.cpp +++ b/Userland/Services/WindowServer/ClientConnection.cpp @@ -53,7 +53,8 @@ ClientConnection::ClientConnection(NonnullRefPtr client_socke s_connections = new HashMap>; s_connections->set(client_id, *this); - async_fast_greet(Screen::rects(), Screen::main().index(), Gfx::current_system_theme_buffer(), Gfx::FontDatabase::default_font_query(), Gfx::FontDatabase::fixed_width_font_query()); + auto& wm = WindowManager::the(); + async_fast_greet(Screen::rects(), Screen::main().index(), wm.window_stack_rows(), wm.window_stack_columns(), Gfx::current_system_theme_buffer(), Gfx::FontDatabase::default_font_query(), Gfx::FontDatabase::fixed_width_font_query()); } ClientConnection::~ClientConnection() @@ -84,9 +85,10 @@ void ClientConnection::die() }); } -void ClientConnection::notify_about_new_screen_rects(Vector const& rects, size_t main_screen_index) +void ClientConnection::notify_about_new_screen_rects() { - async_screen_rects_changed(rects, main_screen_index); + auto& wm = WindowManager::the(); + async_screen_rects_changed(Screen::rects(), Screen::main().index(), wm.window_stack_rows(), wm.window_stack_columns()); } void ClientConnection::create_menubar(i32 menubar_id) @@ -323,6 +325,20 @@ Messages::WindowServer::SaveScreenLayoutResponse ClientConnection::save_screen_l return { success, move(error_msg) }; } +Messages::WindowServer::ApplyVirtualDesktopSettingsResponse ClientConnection::apply_virtual_desktop_settings(u32 rows, u32 columns, bool save) +{ + if (rows == 0 || columns == 0 || rows > WindowManager::max_window_stack_rows || columns > WindowManager::max_window_stack_columns) + return { false }; + + return { WindowManager::the().apply_virtual_desktop_settings(rows, columns, save) }; +} + +Messages::WindowServer::GetVirtualDesktopSettingsResponse ClientConnection::get_virtual_desktop_settings() +{ + auto& wm = WindowManager::the(); + return { (unsigned)wm.window_stack_rows(), (unsigned)wm.window_stack_columns(), WindowManager::max_window_stack_rows, WindowManager::max_window_stack_columns }; +} + void ClientConnection::show_screen_numbers(bool show) { if (m_show_screen_number == show) diff --git a/Userland/Services/WindowServer/ClientConnection.h b/Userland/Services/WindowServer/ClientConnection.h index 40e27ca1bd5..6214a3f696f 100644 --- a/Userland/Services/WindowServer/ClientConnection.h +++ b/Userland/Services/WindowServer/ClientConnection.h @@ -42,7 +42,7 @@ public: static ClientConnection* from_client_id(int client_id); static void for_each_client(Function); - void notify_about_new_screen_rects(const Vector&, size_t); + void notify_about_new_screen_rects(); void post_paint_message(Window&, bool ignore_occlusion = false); Menu* find_menu_by_id(int menu_id) @@ -130,6 +130,8 @@ private: virtual Messages::WindowServer::SetScreenLayoutResponse set_screen_layout(ScreenLayout const&, bool) override; virtual Messages::WindowServer::GetScreenLayoutResponse get_screen_layout() override; virtual Messages::WindowServer::SaveScreenLayoutResponse save_screen_layout() override; + virtual Messages::WindowServer::ApplyVirtualDesktopSettingsResponse apply_virtual_desktop_settings(u32, u32, bool) override; + virtual Messages::WindowServer::GetVirtualDesktopSettingsResponse get_virtual_desktop_settings() override; virtual void show_screen_numbers(bool) override; virtual void set_window_cursor(i32, i32) override; virtual void set_window_custom_cursor(i32, Gfx::ShareableBitmap const&) override; diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index f6bbb4458fd..91d81357e63 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -1287,11 +1287,7 @@ void Compositor::update_animations(Screen& screen, Gfx::DisjointRectSet& flush_r void Compositor::create_window_stack_switch_overlay(WindowStack& target_stack) { - if (m_stack_switch_overlay_timer) { - // Cancel any timer, we're going to delete the overlay - m_stack_switch_overlay_timer->stop(); - m_stack_switch_overlay_timer = nullptr; - } + stop_window_stack_switch_overlay_timer(); Screen::for_each([&](auto& screen) { auto& screen_data = m_screen_data[screen.index()]; screen_data.m_window_stack_switch_overlay = nullptr; // delete it first @@ -1301,20 +1297,45 @@ void Compositor::create_window_stack_switch_overlay(WindowStack& target_stack) }); } +void Compositor::remove_window_stack_switch_overlays() +{ + Screen::for_each([&](auto& screen) { + auto& screen_data = m_screen_data[screen.index()]; + screen_data.m_window_stack_switch_overlay = nullptr; + return IterationDecision::Continue; + }); +} + +void Compositor::stop_window_stack_switch_overlay_timer() +{ + if (m_stack_switch_overlay_timer) { + // Cancel any timer, we're going to delete the overlay + m_stack_switch_overlay_timer->stop(); + m_stack_switch_overlay_timer = nullptr; + } +} + void Compositor::start_window_stack_switch_overlay_timer() { if (m_stack_switch_overlay_timer) { m_stack_switch_overlay_timer->stop(); m_stack_switch_overlay_timer = nullptr; } + bool have_overlay = false; + Screen::for_each([&](auto& screen) { + auto& screen_data = m_screen_data[screen.index()]; + if (screen_data.m_window_stack_switch_overlay) { + have_overlay = true; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + if (!have_overlay) + return; m_stack_switch_overlay_timer = Core::Timer::create_single_shot( 500, [this] { - Screen::for_each([&](auto& screen) { - auto& screen_data = m_screen_data[screen.index()]; - screen_data.m_window_stack_switch_overlay = nullptr; - return IterationDecision::Continue; - }); + remove_window_stack_switch_overlays(); }, this); m_stack_switch_overlay_timer->start(); @@ -1349,7 +1370,26 @@ void Compositor::finish_window_stack_switch() start_window_stack_switch_overlay_timer(); } -void Compositor::switch_to_window_stack(WindowStack& new_window_stack) +void Compositor::set_current_window_stack_no_transition(WindowStack& new_window_stack) +{ + if (m_transitioning_to_window_stack) { + finish_window_stack_switch(); + VERIFY(!m_window_stack_transition_animation); + VERIFY(!m_transitioning_to_window_stack); + } + if (m_current_window_stack == &new_window_stack) + return; + m_current_window_stack = &new_window_stack; + invalidate_for_window_stack_merge_or_change(); +} + +void Compositor::invalidate_for_window_stack_merge_or_change() +{ + invalidate_occlusions(); + invalidate_screen(); +} + +void Compositor::switch_to_window_stack(WindowStack& new_window_stack, bool show_overlay) { if (m_transitioning_to_window_stack) { if (m_transitioning_to_window_stack == &new_window_stack) @@ -1363,8 +1403,13 @@ void Compositor::switch_to_window_stack(WindowStack& new_window_stack) if (&new_window_stack == m_current_window_stack) { // So that the user knows which stack they're on, show the overlay briefly - create_window_stack_switch_overlay(*m_current_window_stack); - start_window_stack_switch_overlay_timer(); + if (show_overlay) { + create_window_stack_switch_overlay(*m_current_window_stack); + start_window_stack_switch_overlay_timer(); + } else { + stop_window_stack_switch_overlay_timer(); + remove_window_stack_switch_overlays(); + } return; } VERIFY(!m_transitioning_to_window_stack); @@ -1387,8 +1432,13 @@ void Compositor::switch_to_window_stack(WindowStack& new_window_stack) m_transitioning_to_window_stack->set_transition_offset({}, { -delta_x, -delta_y }); m_current_window_stack->set_transition_offset({}, {}); - create_window_stack_switch_overlay(*m_transitioning_to_window_stack); - // We start the timer when the animation ends! + if (show_overlay) { + // We start the timer when the animation ends! + create_window_stack_switch_overlay(*m_transitioning_to_window_stack); + } else { + stop_window_stack_switch_overlay_timer(); + remove_window_stack_switch_overlays(); + } VERIFY(!m_window_stack_transition_animation); m_window_stack_transition_animation = Animation::create(); diff --git a/Userland/Services/WindowServer/Compositor.h b/Userland/Services/WindowServer/Compositor.h index f869d9d51f5..9e8f5db827f 100644 --- a/Userland/Services/WindowServer/Compositor.h +++ b/Userland/Services/WindowServer/Compositor.h @@ -112,7 +112,9 @@ public: } bool is_switching_window_stacks() const { return m_transitioning_to_window_stack != nullptr; } - void switch_to_window_stack(WindowStack&); + void switch_to_window_stack(WindowStack&, bool); + void set_current_window_stack_no_transition(WindowStack&); + void invalidate_for_window_stack_merge_or_change(); void did_construct_window_manager(Badge); @@ -142,6 +144,8 @@ private: Gfx::IntPoint window_transition_offset(Window&); void update_animations(Screen&, Gfx::DisjointRectSet& flush_rects); void create_window_stack_switch_overlay(WindowStack&); + void remove_window_stack_switch_overlays(); + void stop_window_stack_switch_overlay_timer(); void start_window_stack_switch_overlay_timer(); void finish_window_stack_switch(); @@ -227,7 +231,6 @@ private: WindowStack* m_current_window_stack { nullptr }; WindowStack* m_transitioning_to_window_stack { nullptr }; RefPtr m_window_stack_transition_animation; - OwnPtr m_stack_switch_overlay; RefPtr m_stack_switch_overlay_timer; size_t m_show_screen_number_count { 0 }; diff --git a/Userland/Services/WindowServer/Window.cpp b/Userland/Services/WindowServer/Window.cpp index 76f92ac1c1a..c070e3bf273 100644 --- a/Userland/Services/WindowServer/Window.cpp +++ b/Userland/Services/WindowServer/Window.cpp @@ -1204,4 +1204,5 @@ String Window::computed_title() const return String::formatted("{} (Not responding)", title); return title; } + } diff --git a/Userland/Services/WindowServer/Window.h b/Userland/Services/WindowServer/Window.h index f6728da7f5b..8af13a8a5bc 100644 --- a/Userland/Services/WindowServer/Window.h +++ b/Userland/Services/WindowServer/Window.h @@ -35,6 +35,7 @@ enum WMEventMask { WindowStateChanges = 1 << 1, WindowIconChanges = 1 << 2, WindowRemovals = 1 << 3, + VirtualDesktopChanges = 1 << 4, }; enum class WindowTileType { diff --git a/Userland/Services/WindowServer/WindowClient.ipc b/Userland/Services/WindowServer/WindowClient.ipc index 28fcee32624..9971f821981 100644 --- a/Userland/Services/WindowServer/WindowClient.ipc +++ b/Userland/Services/WindowServer/WindowClient.ipc @@ -3,7 +3,7 @@ endpoint WindowClient { - fast_greet(Vector screen_rects, u32 main_screen_index, Core::AnonymousBuffer theme_buffer, String default_font_query, String fixed_width_font_query) =| + fast_greet(Vector screen_rects, u32 main_screen_index, u32 virtual_desktop_rows, u32 virtual_desktop_columns, Core::AnonymousBuffer theme_buffer, String default_font_query, String fixed_width_font_query) =| paint(i32 window_id, Gfx::IntSize window_size, Vector rects) =| mouse_move(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta, bool is_drag, Vector mime_types) =| @@ -28,7 +28,7 @@ endpoint WindowClient menu_item_left(i32 menu_id, u32 identifier) =| menu_visibility_did_change(i32 menu_id, bool visible) =| - screen_rects_changed(Vector rects, u32 main_screen_index) =| + screen_rects_changed(Vector rects, u32 main_screen_index, u32 virtual_desktop_rows, u32 virtual_desktop_columns) =| set_wallpaper_finished(bool success) =| diff --git a/Userland/Services/WindowServer/WindowManager.cpp b/Userland/Services/WindowServer/WindowManager.cpp index 039efac1bc7..0dd823016c7 100644 --- a/Userland/Services/WindowServer/WindowManager.cpp +++ b/Userland/Services/WindowServer/WindowManager.cpp @@ -40,23 +40,13 @@ WindowManager::WindowManager(Gfx::PaletteImpl const& palette) s_the = this; { - // Create the default window stacks - auto row_count = m_window_stacks.capacity(); - VERIFY(row_count > 0); - for (size_t row_index = 0; row_index < row_count; row_index++) { - auto row = adopt_own(*new RemoveReference()); - auto column_count = row->capacity(); - VERIFY(column_count > 0); - for (size_t column_index = 0; column_index < column_count; column_index++) - row->append(adopt_own(*new WindowStack(row_index, column_index))); - m_window_stacks.append(move(row)); - } - - m_current_window_stack = &m_window_stacks[0][0]; - for (auto& row : m_window_stacks) { - for (auto& stack : row) - stack.set_stationary_window_stack(*m_current_window_stack); - } + // If we haven't created any window stacks, at least create the stationary/main window stack + auto row = adopt_own(*new RemoveReference()); + auto main_window_stack = adopt_own(*new WindowStack(0, 0)); + main_window_stack->set_stationary_window_stack(*main_window_stack); + m_current_window_stack = main_window_stack.ptr(); + row->append(move(main_window_stack)); + m_window_stacks.append(move(row)); } reload_config(); @@ -78,6 +68,14 @@ void WindowManager::reload_config() { m_config = Core::ConfigFile::open("/etc/WindowServer.ini"); + unsigned virtual_desktop_rows = (unsigned)m_config->read_num_entry("VirtualDesktop", "Rows", default_window_stack_rows); + unsigned virtual_desktop_columns = (unsigned)m_config->read_num_entry("VirtualDesktop", "Columns", default_window_stack_columns); + if (virtual_desktop_rows == 0 || virtual_desktop_columns == 0 || virtual_desktop_rows > max_window_stack_rows || virtual_desktop_columns > max_window_stack_columns) { + virtual_desktop_rows = default_window_stack_rows; + virtual_desktop_columns = default_window_stack_columns; + } + apply_virtual_desktop_settings(virtual_desktop_rows, virtual_desktop_columns, false); + m_double_click_speed = m_config->read_num_entry("Input", "DoubleClickSpeed", 250); auto* current_cursor = Compositor::the().current_cursor(); @@ -139,9 +137,7 @@ bool WindowManager::set_screen_layout(ScreenLayout&& screen_layout, bool save, S Compositor::the().screen_resolution_changed(); - ClientConnection::for_each_client([&](ClientConnection& client) { - client.notify_about_new_screen_rects(Screen::rects(), Screen::main().index()); - }); + tell_wms_screen_rects_changed(); for_each_window_stack([&](auto& window_stack) { window_stack.for_each_window([](Window& window) { @@ -171,6 +167,113 @@ bool WindowManager::save_screen_layout(String& error_msg) return true; } +bool WindowManager::apply_virtual_desktop_settings(unsigned rows, unsigned columns, bool save) +{ + VERIFY(rows != 0); + VERIFY(rows <= max_window_stack_rows); + VERIFY(columns != 0); + VERIFY(columns <= max_window_stack_columns); + + auto current_rows = window_stack_rows(); + auto current_columns = window_stack_columns(); + if (rows != current_rows || columns != current_columns) { + auto& current_window_stack = this->current_window_stack(); + auto current_stack_row = current_window_stack.row(); + auto current_stack_column = current_window_stack.column(); + bool need_rerender = false; + bool removing_current_stack = current_stack_row > rows - 1 || current_stack_column > columns - 1; + auto new_current_row = min(current_stack_row, rows - 1); + auto new_current_column = min(current_stack_column, columns - 1); + + // Collect all windows that were moved. We can't tell the wms at this point because + // the current window stack may not be valid anymore, until after the move is complete + Vector windows_moved; + + auto merge_window_stack = [&](WindowStack& from_stack, WindowStack& into_stack) { + auto move_to = WindowStack::MoveAllWindowsTo::Back; + + // TODO: Figure out a better algorithm. We basically always layer it on top, unless + // it's either being moved to window stack we're viewing or that we will be viewing. + // In that case we would want to make sure the window stack ends up on top (with no + // change to the active window) + bool moving_to_new_current_stack = into_stack.row() == new_current_row && into_stack.column() == new_current_column; + if (moving_to_new_current_stack) + move_to = WindowStack::MoveAllWindowsTo::Front; + from_stack.move_all_windows(into_stack, windows_moved, move_to); + }; + + // While we have too many rows, merge each row too many into the new bottom row + while (current_rows > rows) { + auto& row = m_window_stacks[rows]; + for (size_t column_index = 0; column_index < row.size(); column_index++) { + merge_window_stack(row[column_index], m_window_stacks[rows - 1][column_index]); + if (rows - 1 == current_stack_row && column_index == current_stack_column) + need_rerender = true; + } + m_window_stacks.remove(rows); + current_rows--; + } + // While we have too many columns, merge each column too many into the new right most column + while (current_columns > columns) { + for (size_t row_index = 0; row_index < current_rows; row_index++) { + auto& row = m_window_stacks[row_index]; + merge_window_stack(row[columns], row[columns - 1]); + if (row_index == current_stack_row && columns - 1 == current_stack_column) + need_rerender = true; + row.remove(columns); + } + current_columns--; + } + // Add more rows if necessary + while (rows > current_rows) { + auto row = adopt_own(*new RemoveReference()); + for (size_t column_index = 0; column_index < columns; column_index++) { + auto window_stack = adopt_own(*new WindowStack(current_rows, column_index)); + window_stack->set_stationary_window_stack(m_window_stacks[0][0]); + row->append(move(window_stack)); + } + m_window_stacks.append(move(row)); + current_rows++; + } + // Add more columns if necessary + while (columns > current_columns) { + for (size_t row_index = 0; row_index < current_rows; row_index++) { + auto& row = m_window_stacks[row_index]; + while (row.size() < columns) { + auto window_stack = adopt_own(*new WindowStack(row_index, row.size())); + window_stack->set_stationary_window_stack(m_window_stacks[0][0]); + row.append(move(window_stack)); + } + } + current_columns++; + } + + if (removing_current_stack) { + // If we're on a window stack that was removed, we need to move... + m_current_window_stack = &m_window_stacks[new_current_row][new_current_column]; + Compositor::the().set_current_window_stack_no_transition(*m_current_window_stack); + need_rerender = false; // The compositor already called invalidate_for_window_stack_merge_or_change for us + } + + for (auto* window_moved : windows_moved) + WindowManager::the().tell_wms_window_state_changed(*window_moved); + + tell_wms_screen_rects_changed(); // updates the available virtual desktops + if (current_stack_row != new_current_row || current_stack_column != new_current_column) + tell_wms_current_window_stack_changed(); + + if (need_rerender) + Compositor::the().invalidate_for_window_stack_merge_or_change(); + } + + if (save) { + m_config->write_num_entry("VirtualDesktop", "Rows", window_stack_rows()); + m_config->write_num_entry("VirtualDesktop", "Columns", window_stack_columns()); + return m_config->sync(); + } + return true; +} + void WindowManager::set_acceleration_factor(double factor) { ScreenInput::the().set_acceleration_factor(factor); @@ -322,6 +425,8 @@ void WindowManager::greet_window_manager(WMClientConnection& conn) if (conn.window_id() < 0) return; + tell_wm_about_current_window_stack(conn); + for_each_window_stack([&](auto& window_stack) { window_stack.for_each_window([&](Window& other_window) { //if (conn.window_id() != other_window.window_id()) { @@ -345,7 +450,12 @@ void WindowManager::tell_wm_about_window(WMClientConnection& conn, Window& windo if (window.is_internal()) return; auto* parent = window.parent_window(); - conn.async_window_state_changed(conn.window_id(), window.client_id(), window.window_id(), parent ? parent->client_id() : -1, parent ? parent->window_id() : -1, window.is_active(), window.is_minimized(), window.is_modal_dont_unparent(), window.is_frameless(), (i32)window.type(), window.computed_title(), window.rect(), window.progress()); + auto* window_stack = ¤t_window_stack(); + if (!is_stationary_window_type(window.type())) { + if (auto* stack = window.outer_stack()) + window_stack = stack; + } + conn.async_window_state_changed(conn.window_id(), window.client_id(), window.window_id(), parent ? parent->client_id() : -1, parent ? parent->window_id() : -1, window_stack->row(), window_stack->column(), window.is_active(), window.is_minimized(), window.is_modal_dont_unparent(), window.is_frameless(), (i32)window.type(), window.computed_title(), window.rect(), window.progress()); } void WindowManager::tell_wm_about_window_rect(WMClientConnection& conn, Window& window) @@ -370,6 +480,16 @@ void WindowManager::tell_wm_about_window_icon(WMClientConnection& conn, Window& conn.async_window_icon_bitmap_changed(conn.window_id(), window.client_id(), window.window_id(), window.icon().to_shareable_bitmap()); } +void WindowManager::tell_wm_about_current_window_stack(WMClientConnection& conn) +{ + if (conn.window_id() < 0) + return; + if (!(conn.event_mask() & WMEventMask::VirtualDesktopChanges)) + return; + auto& window_stack = current_window_stack(); + conn.async_virtual_desktop_changed(conn.window_id(), window_stack.row(), window_stack.column()); +} + void WindowManager::tell_wms_window_state_changed(Window& window) { for_each_window_manager([&](WMClientConnection& conn) { @@ -394,6 +514,13 @@ void WindowManager::tell_wms_window_rect_changed(Window& window) }); } +void WindowManager::tell_wms_screen_rects_changed() +{ + ClientConnection::for_each_client([&](ClientConnection& client) { + client.notify_about_new_screen_rects(); + }); +} + void WindowManager::tell_wms_applet_area_size_changed(Gfx::IntSize const& size) { for_each_window_manager([&](WMClientConnection& conn) { @@ -427,6 +554,14 @@ void WindowManager::tell_wms_super_space_key_pressed() }); } +void WindowManager::tell_wms_current_window_stack_changed() +{ + for_each_window_manager([&](WMClientConnection& conn) { + tell_wm_about_current_window_stack(conn); + return IterationDecision::Continue; + }); +} + static bool window_type_has_title(WindowType type) { return type == WindowType::Normal || type == WindowType::ToolWindow; @@ -1283,7 +1418,7 @@ bool WindowManager::is_window_in_modal_stack(Window& window_in_modal_stack, Wind return result == IterationDecision::Break; } -void WindowManager::switch_to_window_stack(WindowStack& window_stack, Window* carry_window) +void WindowManager::switch_to_window_stack(WindowStack& window_stack, Window* carry_window, bool show_overlay) { m_carry_window_to_new_stack.clear(); m_switching_to_window_stack = &window_stack; @@ -1334,7 +1469,7 @@ void WindowManager::switch_to_window_stack(WindowStack& window_stack, Window* ca m_current_window_stack = &window_stack; } - Compositor::the().switch_to_window_stack(window_stack); + Compositor::the().switch_to_window_stack(window_stack, show_overlay); } void WindowManager::did_switch_window_stack(Badge, WindowStack& previous_stack, WindowStack& new_stack) @@ -1343,7 +1478,6 @@ void WindowManager::did_switch_window_stack(Badge, WindowStack& prev // We are being notified by the compositor, it should not be switching right now! VERIFY(!Compositor::the().is_switching_window_stacks()); - VERIFY(¤t_window_stack() == &new_stack); if (m_switching_to_window_stack == &new_stack) { m_switching_to_window_stack = nullptr; @@ -1352,8 +1486,10 @@ void WindowManager::did_switch_window_stack(Badge, WindowStack& prev // carried to when the user rapidly tries to switch stacks, so make sure to // only reset the moving flag if we arrived at our final destination for (auto& window_ref : m_carry_window_to_new_stack) { - if (auto* window = window_ref.ptr()) + if (auto* window = window_ref.ptr()) { window->set_moving_to_another_stack(false); + tell_wms_window_state_changed(*window); + } } m_carry_window_to_new_stack.clear(); } @@ -1378,6 +1514,8 @@ void WindowManager::did_switch_window_stack(Badge, WindowStack& prev pick_new_active_window(nullptr); reevaluate_hovered_window(); + + tell_wms_current_window_stack_changed(); } void WindowManager::process_key_event(KeyEvent& event) diff --git a/Userland/Services/WindowServer/WindowManager.h b/Userland/Services/WindowServer/WindowManager.h index 6d1b64e5724..803657f2051 100644 --- a/Userland/Services/WindowServer/WindowManager.h +++ b/Userland/Services/WindowServer/WindowManager.h @@ -60,6 +60,13 @@ class WindowManager : public Core::Object { friend class WindowSwitcher; public: + static constexpr size_t default_window_stack_rows = 2; + static constexpr size_t default_window_stack_columns = 2; + static_assert(default_window_stack_rows >= 1); + static_assert(default_window_stack_columns >= 1); + static constexpr unsigned max_window_stack_rows = 16; + static constexpr unsigned max_window_stack_columns = 16; + static WindowManager& the(); explicit WindowManager(Gfx::PaletteImpl const&); @@ -173,9 +180,11 @@ public: void tell_wms_window_state_changed(Window&); void tell_wms_window_icon_changed(Window&); void tell_wms_window_rect_changed(Window&); + void tell_wms_screen_rects_changed(); void tell_wms_applet_area_size_changed(Gfx::IntSize const&); void tell_wms_super_key_pressed(); void tell_wms_super_space_key_pressed(); + void tell_wms_current_window_stack_changed(); bool is_active_window_or_accessory(Window&) const; @@ -251,11 +260,13 @@ public: void reevaluate_hovered_window(Window* = nullptr); Window* hovered_window() const { return m_hovered_window.ptr(); } - void switch_to_window_stack(WindowStack&, Window* = nullptr); + void switch_to_window_stack(WindowStack&, Window* = nullptr, bool show_overlay = true); size_t window_stack_rows() const { return m_window_stacks.size(); } size_t window_stack_columns() const { return m_window_stacks[0].size(); } + bool apply_virtual_desktop_settings(unsigned rows, unsigned columns, bool save); + WindowStack& current_window_stack() { VERIFY(m_current_window_stack); @@ -324,6 +335,7 @@ private: void tell_wm_about_window(WMClientConnection& conn, Window&); void tell_wm_about_window_icon(WMClientConnection& conn, Window&); void tell_wm_about_window_rect(WMClientConnection& conn, Window&); + void tell_wm_about_current_window_stack(WMClientConnection&); bool pick_new_active_window(Window*); void do_move_to_front(Window&, bool, bool); @@ -350,7 +362,7 @@ private: RefPtr m_overlay_rect_shadow; // Setup 2 rows 1 column by default - NonnullOwnPtrVector, 2> m_window_stacks; + NonnullOwnPtrVector, default_window_stack_rows> m_window_stacks; WindowStack* m_current_window_stack { nullptr }; struct DoubleClickInfo { diff --git a/Userland/Services/WindowServer/WindowManagerClient.ipc b/Userland/Services/WindowServer/WindowManagerClient.ipc index 220a2d6543a..615f93e2a77 100644 --- a/Userland/Services/WindowServer/WindowManagerClient.ipc +++ b/Userland/Services/WindowServer/WindowManagerClient.ipc @@ -3,10 +3,11 @@ endpoint WindowManagerClient { window_removed(i32 wm_id, i32 client_id, i32 window_id) =| - window_state_changed(i32 wm_id, i32 client_id, i32 window_id, i32 parent_client_id, i32 parent_window_id, bool is_active, bool is_minimized, bool is_modal, bool is_frameless, i32 window_type, [UTF8] String title, Gfx::IntRect rect, Optional progress) =| + window_state_changed(i32 wm_id, i32 client_id, i32 window_id, i32 parent_client_id, i32 parent_window_id, u32 virtual_desktop_row, u32 virtual_desktop_column, bool is_active, bool is_minimized, bool is_modal, bool is_frameless, i32 window_type, [UTF8] String title, Gfx::IntRect rect, Optional progress) =| window_icon_bitmap_changed(i32 wm_id, i32 client_id, i32 window_id, Gfx::ShareableBitmap bitmap) =| window_rect_changed(i32 wm_id, i32 client_id, i32 window_id, Gfx::IntRect rect) =| applet_area_size_changed(i32 wm_id, Gfx::IntSize size) =| super_key_pressed(i32 wm_id) =| super_space_key_pressed(i32 wm_id) =| + virtual_desktop_changed(i32 wm_id, u32 row, u32 column) =| } diff --git a/Userland/Services/WindowServer/WindowServer.ipc b/Userland/Services/WindowServer/WindowServer.ipc index d3d81b6ef93..06536074722 100644 --- a/Userland/Services/WindowServer/WindowServer.ipc +++ b/Userland/Services/WindowServer/WindowServer.ipc @@ -102,6 +102,9 @@ endpoint WindowServer save_screen_layout() => (bool success, String error_msg) show_screen_numbers(bool show) =| + apply_virtual_desktop_settings(u32 rows, u32 columns, bool save) => (bool success) + get_virtual_desktop_settings() => (u32 rows, u32 columns, u32 max_rows, u32 max_columns) + set_window_icon_bitmap(i32 window_id, Gfx::ShareableBitmap icon) =| get_wallpaper() => (String path) diff --git a/Userland/Services/WindowServer/WindowStack.cpp b/Userland/Services/WindowServer/WindowStack.cpp index f10a9641b2d..261f2bb6df5 100644 --- a/Userland/Services/WindowServer/WindowStack.cpp +++ b/Userland/Services/WindowServer/WindowStack.cpp @@ -26,6 +26,13 @@ void WindowStack::add(Window& window) window.set_outer_stack({}, this); } +void WindowStack::add_to_back(Window& window) +{ + VERIFY(window.outer_stack() == nullptr); + m_windows.prepend(window); + window.set_outer_stack({}, this); +} + void WindowStack::remove(Window& window) { VERIFY(window.outer_stack() == this); @@ -47,6 +54,27 @@ void WindowStack::move_to_front(Window& window) m_windows.append(window); } +void WindowStack::move_all_windows(WindowStack& new_window_stack, Vector& windows_moved, MoveAllWindowsTo move_to) +{ + VERIFY(this != &new_window_stack); + if (move_to == MoveAllWindowsTo::Front) { + while (auto* window = m_windows.take_first()) { + window->set_outer_stack({}, nullptr); + new_window_stack.add(*window); + windows_moved.append(window); + } + } else { + while (auto* window = m_windows.take_last()) { + window->set_outer_stack({}, nullptr); + new_window_stack.add_to_back(*window); + windows_moved.append(window); + } + } + m_active_window = nullptr; + m_active_input_window = nullptr; + m_active_input_tracking_window = nullptr; +} + Window* WindowStack::window_at(Gfx::IntPoint const& position, IncludeWindowFrame include_window_frame) const { auto result = hit_test(position); diff --git a/Userland/Services/WindowServer/WindowStack.h b/Userland/Services/WindowServer/WindowStack.h index a4aaec944ea..513ed3c3cac 100644 --- a/Userland/Services/WindowServer/WindowStack.h +++ b/Userland/Services/WindowServer/WindowStack.h @@ -19,9 +19,16 @@ public: bool is_empty() const { return m_windows.is_empty(); } void add(Window&); + void add_to_back(Window&); void remove(Window&); void move_to_front(Window&); + enum class MoveAllWindowsTo { + Front, + Back + }; + void move_all_windows(WindowStack&, Vector&, MoveAllWindowsTo); + enum class IncludeWindowFrame { Yes, No, @@ -38,6 +45,8 @@ public: template void for_each_window(Callback); + template + IterationDecision for_each_window_from_back_to_front(Callback); Window::List& windows() { return m_windows; } @@ -145,6 +154,17 @@ inline void WindowStack::for_each_window(Callback callback) } } +template +inline IterationDecision WindowStack::for_each_window_from_back_to_front(Callback callback) +{ + for (auto& window : m_windows) { + IterationDecision decision = callback(window); + if (decision != IterationDecision::Break) + return decision; + } + return IterationDecision::Continue; +} + template inline IterationDecision WindowStack::for_each_window_of_type_from_front_to_back(WindowType type, Callback callback, bool ignore_highlight) {