From e1bf1ea37f95e85de989c82c4b11f25bdda0b89e Mon Sep 17 00:00:00 2001 From: Campbell Jones Date: Sat, 4 Nov 2023 15:53:52 -0400 Subject: [PATCH] Implement fullscreening Also wire up maximization in Xwayland --- src/foreign_toplevel.cpp | 24 ++++--- src/foreign_toplevel.hpp | 9 +-- src/output.cpp | 10 +++ src/output.hpp | 1 + src/surface/view.cpp | 121 ++++++++++++++++++++++++---------- src/surface/view.hpp | 14 +++- src/surface/xdg_view.cpp | 32 +++++---- src/surface/xwayland_view.cpp | 33 ++++++++-- src/types.hpp | 6 ++ 9 files changed, 186 insertions(+), 64 deletions(-) diff --git a/src/foreign_toplevel.cpp b/src/foreign_toplevel.cpp index 99d9f6f5e..174d96131 100644 --- a/src/foreign_toplevel.cpp +++ b/src/foreign_toplevel.cpp @@ -8,8 +8,16 @@ static void foreign_toplevel_handle_request_maximize_notify(wl_listener* listene const ForeignToplevelHandle& handle = magpie_container_of(listener, handle, request_activate); auto& event = *static_cast(data); - handle.view.set_minimized(false); - handle.view.set_maximized(event.maximized); + auto placement = event.maximized ? VIEW_PLACEMENT_MAXIMIZED : VIEW_PLACEMENT_STACKING; + handle.view.set_placement(placement); +} + +static void foreign_toplevel_handle_request_fullscreen_notify(wl_listener* listener, void* data) { + const ForeignToplevelHandle& handle = magpie_container_of(listener, handle, request_activate); + auto& event = *static_cast(data); + + auto placement = event.maximized ? VIEW_PLACEMENT_FULLSCREEN : VIEW_PLACEMENT_STACKING; + handle.view.set_placement(placement); } static void foreign_toplevel_handle_request_minimize_notify(wl_listener* listener, void* data) { @@ -24,12 +32,7 @@ static void foreign_toplevel_handle_request_activate_notify(wl_listener* listene (void) data; handle.view.set_minimized(false); - handle.view.get_server().focus_view(&handle.view, handle.view.get_wlr_surface()); -} - -static void foreign_toplevel_handle_request_fullscreen_notify(wl_listener* listener, void* data) { - (void) listener; - (void) data; + handle.view.get_server().focus_view(&handle.view); } static void foreign_toplevel_handle_request_close_notify(wl_listener* listener, void* data) { @@ -89,6 +92,11 @@ void ForeignToplevelHandle::set_parent(std::optionalget().handle); } +void ForeignToplevelHandle::set_placement(const ViewPlacement placement) { + set_maximized(placement == VIEW_PLACEMENT_MAXIMIZED); + set_fullscreen(placement == VIEW_PLACEMENT_FULLSCREEN); +} + void ForeignToplevelHandle::set_maximized(const bool maximized) { wlr_foreign_toplevel_handle_v1_set_maximized(&handle, maximized); } diff --git a/src/foreign_toplevel.hpp b/src/foreign_toplevel.hpp index b8805b36c..e41042428 100644 --- a/src/foreign_toplevel.hpp +++ b/src/foreign_toplevel.hpp @@ -36,10 +36,11 @@ class ForeignToplevelHandle { void set_title(const char* title); void set_app_id(const char* app_id); void set_parent(std::optional> parent); - void set_maximized(bool maximized); - void set_minimized(bool minimized); - void set_activated(bool activated); - void set_fullscreen(bool fullscreen); + void set_placement(const ViewPlacement placement); + void set_maximized(const bool maximized); + void set_fullscreen(const bool fullscreen); + void set_minimized(const bool minimized); + void set_activated(const bool activated); void output_enter(const Output& output); void output_leave(const Output& output); }; diff --git a/src/output.cpp b/src/output.cpp index b6ac55861..5b255b826 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -94,6 +94,16 @@ void Output::update_layout() { } } +wlr_box Output::full_area_in_layout_coords() const { + double layout_x = 0, layout_y = 0; + wlr_output_layout_output_coords(server.output_layout, &wlr, &layout_x, &layout_y); + + wlr_box box = full_area; + box.x += layout_x; + box.y += layout_y; + return box; +} + wlr_box Output::usable_area_in_layout_coords() const { double layout_x = 0, layout_y = 0; wlr_output_layout_output_coords(server.output_layout, &wlr, &layout_x, &layout_y); diff --git a/src/output.hpp b/src/output.hpp index 27e0d33fc..a90671ef5 100644 --- a/src/output.hpp +++ b/src/output.hpp @@ -39,6 +39,7 @@ class Output { ~Output() noexcept; void update_layout(); + wlr_box full_area_in_layout_coords() const; wlr_box usable_area_in_layout_coords() const; }; diff --git a/src/surface/view.cpp b/src/surface/view.cpp index b245457d7..97c8cd0f1 100644 --- a/src/surface/view.cpp +++ b/src/surface/view.cpp @@ -5,6 +5,7 @@ #include "output.hpp" #include "server.hpp" +#include "types.hpp" #include "wlr-wrap-start.hpp" #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include "wlr-wrap-end.hpp" const std::optional View::find_output_for_maximize() { @@ -94,8 +96,10 @@ void View::begin_interactive(const CursorMode mode, const uint32_t edges) { } void View::set_position(const int new_x, const int new_y) { - previous.x = current.x; - previous.y = current.y; + if (curr_placement == VIEW_PLACEMENT_STACKING) { + previous.x = current.x; + previous.y = current.y; + } current.x = new_x; current.y = new_y; wlr_scene_node_set_position(scene_node, new_x, new_y); @@ -103,6 +107,12 @@ void View::set_position(const int new_x, const int new_y) { } void View::set_size(const int new_width, const int new_height) { + if (curr_placement == VIEW_PLACEMENT_STACKING) { + previous.width = current.width; + previous.height = current.height; + } + current.width = new_width; + current.height = new_height; impl_set_size(new_width, new_height); } @@ -114,47 +124,78 @@ void View::set_activated(const bool activated) { } } -void View::set_maximized(const bool maximized) { +void View::set_placement(const ViewPlacement new_placement, const bool force) { Server& server = get_server(); - if (this->is_maximized == maximized) { - /* Don't honor request if already maximized. */ - return; - } - wlr_surface* focused_surface = server.seat->wlr->pointer_state.focused_surface; - if (get_wlr_surface() != wlr_surface_get_root_surface(focused_surface)) { - /* Deny maximize requests from unfocused clients. */ - return; + if (!force) { + if (curr_placement == new_placement) { + return; + } + + wlr_surface* focused_surface = server.seat->wlr->pointer_state.focused_surface; + if (focused_surface == nullptr || get_wlr_surface() != wlr_surface_get_root_surface(focused_surface)) { + /* Deny placement requests from unfocused clients. */ + return; + } } - if (this->is_maximized) { - set_size(previous.width, previous.height); - impl_set_maximized(false); - current.x = previous.x; - current.y = previous.y; - wlr_scene_node_set_position(scene_node, current.x, current.y); - } else { - previous = get_geometry(); - previous.x = current.x; - previous.y = current.y; + bool res = true; + + switch (new_placement) { + case VIEW_PLACEMENT_STACKING: + stack(); + break; + case VIEW_PLACEMENT_MAXIMIZED: + res = maximize(); + break; + case VIEW_PLACEMENT_FULLSCREEN: + res = fullscreen(); + break; + } - auto best_output = find_output_for_maximize(); - if (!best_output.has_value()) { - return; + if (res) { + prev_placement = curr_placement; + curr_placement = new_placement; + if (toplevel_handle.has_value()) { + toplevel_handle->set_placement(new_placement); } + } +} + +void View::stack() { + set_size(previous.width, previous.height); + impl_set_maximized(false); + impl_set_fullscreen(false); + set_position(previous.x, previous.y); +} - wlr_box output_box = best_output.value()->usable_area_in_layout_coords(); - set_size(output_box.width, output_box.height); - impl_set_maximized(true); - current.x = output_box.x; - current.y = output_box.y; - wlr_scene_node_set_position(scene_node, current.x, current.y); +bool View::maximize() { + auto best_output = find_output_for_maximize(); + if (!best_output.has_value()) { + return false; } - this->is_maximized = maximized; - if (toplevel_handle.has_value()) { - toplevel_handle->set_maximized(maximized); + wlr_box output_box = best_output.value()->usable_area_in_layout_coords(); + set_size(output_box.width, output_box.height); + impl_set_fullscreen(false); + impl_set_maximized(true); + set_position(output_box.x, output_box.y); + + return true; +} + +bool View::fullscreen() { + auto best_output = find_output_for_maximize(); + if (!best_output.has_value()) { + return false; } + + wlr_box output_box = best_output.value()->full_area_in_layout_coords(); + set_size(output_box.width, output_box.height); + impl_set_fullscreen(true); + set_position(output_box.x, output_box.y); + + return true; } void View::set_minimized(const bool minimized) { @@ -175,3 +216,17 @@ void View::set_minimized(const bool minimized) { map(); } } + +void View::toggle_maximize() { + if (curr_placement != VIEW_PLACEMENT_FULLSCREEN) { + set_placement(curr_placement != VIEW_PLACEMENT_MAXIMIZED ? VIEW_PLACEMENT_MAXIMIZED : VIEW_PLACEMENT_STACKING); + } +} + +void View::toggle_fullscreen() { + if (curr_placement == VIEW_PLACEMENT_FULLSCREEN) { + set_placement(prev_placement); + } else { + set_placement(VIEW_PLACEMENT_FULLSCREEN); + } +} diff --git a/src/surface/view.hpp b/src/surface/view.hpp index 6da292598..dea9f5e7c 100644 --- a/src/surface/view.hpp +++ b/src/surface/view.hpp @@ -15,8 +15,9 @@ #include "wlr-wrap-end.hpp" struct View : public Surface { - bool is_maximized; - bool is_minimized; + ViewPlacement prev_placement = VIEW_PLACEMENT_STACKING; + ViewPlacement curr_placement = VIEW_PLACEMENT_STACKING; + bool is_minimized = false; wlr_box current; wlr_box pending; wlr_box previous; @@ -35,11 +36,16 @@ struct View : public Surface { void set_position(const int new_x, const int new_y); void set_size(const int new_width, const int new_height); void set_activated(const bool activated); - void set_maximized(const bool maximized); + void set_placement(const ViewPlacement placement, const bool force = false); void set_minimized(const bool minimized); + void toggle_maximize(); + void toggle_fullscreen(); private: const std::optional find_output_for_maximize(); + void stack(); + bool maximize(); + bool fullscreen(); protected: virtual void impl_set_position(const int new_x, const int new_y) = 0; @@ -106,6 +112,8 @@ class XWaylandView : public View { wl_listener request_configure; wl_listener request_move; wl_listener request_resize; + wl_listener request_maximize; + wl_listener request_fullscreen; wl_listener set_geometry; wl_listener set_title; wl_listener set_class; diff --git a/src/surface/xdg_view.cpp b/src/surface/xdg_view.cpp index 1e9edb16b..e370cc116 100644 --- a/src/surface/xdg_view.cpp +++ b/src/surface/xdg_view.cpp @@ -48,7 +48,7 @@ static void xdg_toplevel_request_move_notify(wl_listener* listener, void* data) XdgView& view = magpie_container_of(listener, view, request_move); (void) data; - view.set_maximized(false); + view.set_placement(VIEW_PLACEMENT_STACKING); view.begin_interactive(MAGPIE_CURSOR_MOVE, 0); } @@ -61,7 +61,7 @@ static void xdg_toplevel_request_resize_notify(wl_listener* listener, void* data XdgView& view = magpie_container_of(listener, view, request_resize); auto* event = static_cast(data); - view.set_maximized(false); + view.set_placement(VIEW_PLACEMENT_STACKING); view.begin_interactive(MAGPIE_CURSOR_RESIZE, event->edges); } @@ -72,23 +72,23 @@ static void xdg_toplevel_request_maximize_notify(wl_listener* listener, void* da XdgView& view = magpie_container_of(listener, view, request_maximize); (void) data; - view.set_maximized(!view.is_maximized); + view.toggle_maximize(); wlr_xdg_surface_schedule_configure(view.xdg_toplevel.base); } -static void xdg_toplevel_request_minimize_notify(wl_listener* listener, void* data) { - XdgView& view = magpie_container_of(listener, view, request_minimize); +static void xdg_toplevel_request_fullscreen_notify(wl_listener* listener, void* data) { + XdgView& view = magpie_container_of(listener, view, request_fullscreen); (void) data; - view.set_minimized(!view.is_minimized); + view.toggle_fullscreen(); wlr_xdg_surface_schedule_configure(view.xdg_toplevel.base); } -static void xdg_toplevel_request_fullscreen_notify(wl_listener* listener, void* data) { - XdgView& view = magpie_container_of(listener, view, request_fullscreen); +static void xdg_toplevel_request_minimize_notify(wl_listener* listener, void* data) { + XdgView& view = magpie_container_of(listener, view, request_minimize); (void) data; - /* We must send a configure here, even on a no-op. */ + view.set_minimized(!view.is_minimized); wlr_xdg_surface_schedule_configure(view.xdg_toplevel.base); } @@ -126,8 +126,9 @@ XdgView::XdgView(Server& server, wlr_xdg_toplevel& toplevel) noexcept auto* scene_tree = wlr_scene_xdg_surface_create(&server.scene->tree, toplevel.base); scene_node = &scene_tree->node; - wlr_xdg_toplevel_set_wm_capabilities( - &toplevel, WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE | WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE); + wlr_xdg_toplevel_set_wm_capabilities(&toplevel, WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE | + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE | + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); scene_node->data = this; toplevel.base->surface->data = this; @@ -214,7 +215,14 @@ void XdgView::map() { } wlr_scene_node_set_enabled(scene_node, true); - is_maximized = xdg_toplevel.current.maximized; + if (xdg_toplevel.current.fullscreen) { + set_placement(VIEW_PLACEMENT_FULLSCREEN); + } else if (xdg_toplevel.current.maximized) { + set_placement(VIEW_PLACEMENT_MAXIMIZED); + } else { + set_placement(VIEW_PLACEMENT_STACKING); + } + server.focus_view(this); } diff --git a/src/surface/xwayland_view.cpp b/src/surface/xwayland_view.cpp index f2471c358..a8614c450 100644 --- a/src/surface/xwayland_view.cpp +++ b/src/surface/xwayland_view.cpp @@ -76,7 +76,7 @@ static void xwayland_surface_request_move_notify(wl_listener* listener, void* da XWaylandView& view = magpie_container_of(listener, view, request_move); (void) data; - wlr_xwayland_surface_set_maximized(&view.xwayland_surface, false); + view.set_placement(VIEW_PLACEMENT_STACKING); view.begin_interactive(MAGPIE_CURSOR_MOVE, 0); } @@ -87,12 +87,26 @@ static void xwayland_surface_request_move_notify(wl_listener* listener, void* da * client, to prevent the client from requesting this whenever they want. */ static void xwayland_surface_request_resize_notify(wl_listener* listener, void* data) { XWaylandView& view = magpie_container_of(listener, view, request_resize); - auto* event = static_cast(data); - wlr_xwayland_surface_set_maximized(&view.xwayland_surface, false); + + view.set_placement(VIEW_PLACEMENT_STACKING); view.begin_interactive(MAGPIE_CURSOR_RESIZE, event->edges); } +static void xwayland_surface_request_maximize_notify(wl_listener* listener, void* data) { + XWaylandView& view = magpie_container_of(listener, view, request_maximize); + (void) data; + + view.toggle_maximize(); +} + +static void xwayland_surface_request_fullscreen_notify(wl_listener* listener, void* data) { + XWaylandView& view = magpie_container_of(listener, view, request_fullscreen); + (void) data; + + view.toggle_fullscreen(); +} + static void xwayland_surface_set_title_notify(wl_listener* listener, void* data) { XWaylandView& view = magpie_container_of(listener, view, set_title); (void) data; @@ -148,6 +162,10 @@ XWaylandView::XWaylandView(Server& server, wlr_xwayland_surface& xwayland_surfac wl_signal_add(&xwayland_surface.events.request_move, &listeners.request_move); listeners.request_resize.notify = xwayland_surface_request_resize_notify; wl_signal_add(&xwayland_surface.events.request_resize, &listeners.request_resize); + listeners.request_maximize.notify = xwayland_surface_request_maximize_notify; + wl_signal_add(&xwayland_surface.events.request_maximize, &listeners.request_maximize); + listeners.request_fullscreen.notify = xwayland_surface_request_fullscreen_notify; + wl_signal_add(&xwayland_surface.events.request_fullscreen, &listeners.request_fullscreen); listeners.set_geometry.notify = xwayland_surface_set_geometry_notify; wl_signal_add(&xwayland_surface.events.set_geometry, &listeners.set_geometry); listeners.set_title.notify = xwayland_surface_set_title_notify; @@ -208,7 +226,14 @@ void XWaylandView::map() { } } - wlr_scene_node_set_position(scene_node, current.x, current.y); + wlr_scene_node_set_enabled(scene_node, true); + if (xwayland_surface.fullscreen) { + set_placement(VIEW_PLACEMENT_FULLSCREEN); + } else if (xwayland_surface.maximized_horz && xwayland_surface.maximized_vert) { + set_placement(VIEW_PLACEMENT_MAXIMIZED); + } else { + set_placement(VIEW_PLACEMENT_STACKING); + } server.views.insert(server.views.begin(), this); server.focus_view(this); diff --git a/src/types.hpp b/src/types.hpp index c166e1597..0c871ee5e 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -20,6 +20,12 @@ class Popup; class ForeignToplevelHandle; +enum ViewPlacement { + VIEW_PLACEMENT_STACKING, + VIEW_PLACEMENT_MAXIMIZED, + VIEW_PLACEMENT_FULLSCREEN, +}; + #define magpie_container_of(ptr, sample, member) \ (__extension__({ \ std::remove_reference::type::Listeners* container = wl_container_of(ptr, container, member); \