kranewl

A wlroots-based dynamic Wayland compositor, written in C++, configurable with Lua
git clone git://git.deurzen.net/kranewl
Log | Files | Refs | LICENSE

commit 9471ef55526f246a02fa1a2fe6ec49ad08f063f0
parent 6a77cc476d48ddd97f226a440e019c1e24762f01
Author: deurzen <max@deurzen.net>
Date:   Fri, 27 May 2022 10:45:36 +0200

adds focus and state manipulation bindings

Diffstat:
Minclude/kranewl/input/keybindings.hh | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Minclude/kranewl/model.hh | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kranewl/tree/client.hh | 1+
Minclude/kranewl/tree/view.hh | 5++++-
Msrc/kranewl/model.cc | 953+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/kranewl/tree/client.cc | 5+++--
Msrc/kranewl/tree/output.cc | 1+
Msrc/kranewl/tree/view.cc | 14++++++++++----
Msrc/kranewl/tree/xdg_view.cc | 35+++++++++++++++++++++++++----------
9 files changed, 1193 insertions(+), 23 deletions(-)

diff --git a/include/kranewl/input/keybindings.hh b/include/kranewl/input/keybindings.hh @@ -32,22 +32,155 @@ static const KeyBindings key_bindings = { { { XKB_KEY_Q, MODKEY | CTRL | SHIFT }, CALL(exit()) }, + +// view state modifiers +{ { XKB_KEY_c, MODKEY }, + CALL(kill_focus()) +}, +{ { XKB_KEY_space, MODKEY | SHIFT }, + CALL(set_floating_focus(Toggle::Reverse)) +}, +{ { XKB_KEY_f, MODKEY }, + CALL(set_fullscreen_focus(Toggle::Reverse)) +}, +{ { XKB_KEY_x, MODKEY }, + CALL(set_sticky_focus(Toggle::Reverse)) +}, +{ { XKB_KEY_f, MODKEY | SECKEY | CTRL }, + CALL(set_contained_focus(Toggle::Reverse)) +}, +{ { XKB_KEY_i, MODKEY | SECKEY | CTRL }, + CALL(set_invincible_focus(Toggle::Reverse)) +}, +{ { XKB_KEY_y, MODKEY }, + CALL(set_iconify_focus(Toggle::Reverse)) +}, +{ { XKB_KEY_u, MODKEY }, + CALL(pop_deiconify()) +}, +{ { XKB_KEY_u, MODKEY | SECKEY }, + CALL(deiconify_all()) +}, + +// view arrangers +{ { XKB_KEY_space, MODKEY | CTRL }, + CALL(center_focus()) +}, +{ { XKB_KEY_h, MODKEY | CTRL }, + [](Model& model) { + View_ptr focus = model.focused_view(); + + if (focus && model.is_free(focus)) + model.nudge_focus(Edge::Left, 15); + else + model.shuffle_main(Direction::Backward); + } +}, +{ { XKB_KEY_j, MODKEY | CTRL }, + [](Model& model) { + View_ptr focus = model.focused_view(); + + if (focus && model.is_free(focus)) + model.nudge_focus(Edge::Bottom, 15); + else + model.shuffle_stack(Direction::Forward); + } +}, +{ { XKB_KEY_k, MODKEY | CTRL }, + [](Model& model) { + View_ptr focus = model.focused_view(); + + if (focus && model.is_free(focus)) + model.nudge_focus(Edge::Top, 15); + else + model.shuffle_stack(Direction::Backward); + } +}, +{ { XKB_KEY_l, MODKEY | CTRL }, + [](Model& model) { + View_ptr focus = model.focused_view(); + + if (focus && model.is_free(focus)) + model.nudge_focus(Edge::Right, 15); + else + model.shuffle_main(Direction::Forward); + } +}, +{ { XKB_KEY_H, MODKEY | CTRL | SHIFT }, + CALL(stretch_focus(Edge::Left, 15)) +}, +{ { XKB_KEY_J, MODKEY | CTRL | SHIFT }, + CALL(stretch_focus(Edge::Bottom, 15)) +}, +{ { XKB_KEY_K, MODKEY | CTRL | SHIFT }, + CALL(stretch_focus(Edge::Top, 15)) +}, +{ { XKB_KEY_L, MODKEY | CTRL | SHIFT }, + CALL(stretch_focus(Edge::Right, 15)) +}, +{ { XKB_KEY_Y, MODKEY | CTRL | SHIFT }, + CALL(stretch_focus(Edge::Left, -15)) +}, +{ { XKB_KEY_U, MODKEY | CTRL | SHIFT }, + CALL(stretch_focus(Edge::Bottom, -15)) +}, +{ { XKB_KEY_I, MODKEY | CTRL | SHIFT }, + CALL(stretch_focus(Edge::Top, -15)) +}, +{ { XKB_KEY_O, MODKEY | CTRL | SHIFT }, + CALL(stretch_focus(Edge::Right, -15)) +}, +{ { XKB_KEY_leftarrow, MODKEY | CTRL }, + CALL(snap_focus(Edge::Left)) +}, +{ { XKB_KEY_downarrow, MODKEY | CTRL }, + CALL(snap_focus(Edge::Bottom)) +}, +{ { XKB_KEY_uparrow, MODKEY | CTRL }, + CALL(snap_focus(Edge::Top)) +}, +{ { XKB_KEY_rightarrow, MODKEY | CTRL }, + CALL(snap_focus(Edge::Right)) +}, +{ { XKB_KEY_j, MODKEY }, + CALL(cycle_focus(Direction::Forward)) +}, +{ { XKB_KEY_k, MODKEY }, + CALL(cycle_focus(Direction::Backward)) +}, +{ { XKB_KEY_J, MODKEY | SHIFT }, + CALL(drag_focus(Direction::Forward)) +}, +{ { XKB_KEY_K, MODKEY | SHIFT }, + CALL(drag_focus(Direction::Backward)) +}, +{ { XKB_KEY_r, MODKEY }, + CALL(reverse_views()) +}, +{ { XKB_KEY_semicolon, MODKEY | SHIFT }, + CALL(rotate_views(Direction::Forward)) +}, +{ { XKB_KEY_comma, MODKEY | SHIFT }, + CALL(rotate_views(Direction::Backward)) +}, + +// workspace layout modifiers { { XKB_KEY_F, MODKEY | SHIFT }, CALL(set_layout(LayoutHandler::LayoutKind::Float)) }, { { XKB_KEY_L, MODKEY | SHIFT }, CALL(set_layout(LayoutHandler::LayoutKind::FramelessFloat)) }, -{ { XKB_KEY_Z, MODKEY }, +{ { XKB_KEY_z, MODKEY }, CALL(set_layout(LayoutHandler::LayoutKind::SingleFloat)) }, { { XKB_KEY_Z, MODKEY | SHIFT }, CALL(set_layout(LayoutHandler::LayoutKind::FramelessSingleFloat)) }, -{ { XKB_KEY_M, MODKEY }, +{ { XKB_KEY_m, MODKEY }, CALL(set_layout(LayoutHandler::LayoutKind::Monocle)) }, -{ { XKB_KEY_D, MODKEY | CTRL }, +{ { XKB_KEY_d, MODKEY | CTRL }, CALL(set_layout(LayoutHandler::LayoutKind::MainDeck)) }, { { XKB_KEY_D, MODKEY | SHIFT }, @@ -56,10 +189,10 @@ static const KeyBindings key_bindings = { { { XKB_KEY_D, MODKEY | CTRL | SHIFT }, CALL(set_layout(LayoutHandler::LayoutKind::DoubleDeck)) }, -{ { XKB_KEY_G, MODKEY }, +{ { XKB_KEY_g, MODKEY }, CALL(set_layout(LayoutHandler::LayoutKind::Center)) }, -{ { XKB_KEY_T, MODKEY }, +{ { XKB_KEY_t, MODKEY }, CALL(set_layout(LayoutHandler::LayoutKind::DoubleStack)) }, { { XKB_KEY_T, MODKEY | SHIFT }, @@ -74,7 +207,7 @@ static const KeyBindings key_bindings = { { { XKB_KEY_Y, MODKEY | SHIFT }, CALL(set_layout(LayoutHandler::LayoutKind::HorizontalStack)) }, -{ { XKB_KEY_Y, MODKEY | CTRL }, +{ { XKB_KEY_y, MODKEY | CTRL }, CALL(set_layout(LayoutHandler::LayoutKind::CompactHorizontalStack)) }, { { XKB_KEY_V, MODKEY | SHIFT }, diff --git a/include/kranewl/model.hh b/include/kranewl/model.hh @@ -37,6 +37,8 @@ public: void register_server(Server_ptr); void exit(); + const View_ptr focused_view() const; + KeyBindings const& key_bindings() const; Output_ptr create_output(struct wlr_output*, struct wlr_scene_output*, Region const&&); @@ -61,6 +63,13 @@ public: void place_view(Placement&); void sync_focus(); + void cycle_focus(Direction); + void drag_focus(Direction); + + void reverse_views(); + void rotate_views(Direction); + void shuffle_main(Direction); + void shuffle_stack(Direction); void move_view_to_workspace(View_ptr, Index); void move_view_to_workspace(View_ptr, Workspace_ptr); @@ -81,9 +90,57 @@ public: void set_layout(LayoutHandler::LayoutKind); void set_layout_retain_region(LayoutHandler::LayoutKind); + void toggle_layout_data(); + void cycle_layout_data(Direction); + void copy_data_from_prev_layout(); + + void change_gap_size(Util::Change<int>); + void change_main_count(Util::Change<int>); + void change_main_factor(Util::Change<float>); + void change_margin(Util::Change<int>); + void change_margin(Edge, Util::Change<int>); + void reset_gap_size(); + void reset_margin(); + void reset_layout_data(); + + void save_layout(std::size_t) const; + void load_layout(std::size_t); + void apply_layout(Index); void apply_layout(Workspace_ptr); + void kill_focus(); + void kill_view(View_ptr); + + void set_floating_focus(Toggle); + void set_floating_view(Toggle, View_ptr); + void set_fullscreen_focus(Toggle); + void set_fullscreen_view(Toggle, View_ptr); + void set_sticky_focus(Toggle); + void set_sticky_view(Toggle, View_ptr); + void set_contained_focus(Toggle); + void set_contained_view(Toggle, View_ptr); + void set_invincible_focus(Toggle); + void set_invincible_view(Toggle, View_ptr); + void set_iconifyable_focus(Toggle); + void set_iconifyable_view(Toggle, View_ptr); + void set_iconify_focus(Toggle); + void set_iconify_view(Toggle, View_ptr); + + void center_focus(); + void center_view(View_ptr); + void nudge_focus(Edge, Util::Change<std::size_t>); + void nudge_view(Edge, Util::Change<std::size_t>, View_ptr); + void stretch_focus(Edge, Util::Change<int>); + void stretch_view(Edge, Util::Change<int>, View_ptr); + void inflate_focus(Util::Change<int>); + void inflate_view(Util::Change<int>, View_ptr); + void snap_focus(Edge); + void snap_view(Edge, View_ptr); + + void pop_deiconify(); + void deiconify_all(); + bool is_free(View_ptr) const; void spawn_external(std::string&&) const; diff --git a/include/kranewl/tree/client.hh b/include/kranewl/tree/client.hh @@ -122,6 +122,7 @@ typedef struct Client final { bool m_attaching; std::chrono::time_point<std::chrono::steady_clock> m_last_focused; + std::chrono::time_point<std::chrono::steady_clock> m_last_touched; std::chrono::time_point<std::chrono::steady_clock> m_managed_since; struct wl_listener ml_commit; diff --git a/include/kranewl/tree/view.hh b/include/kranewl/tree/view.hh @@ -71,6 +71,8 @@ typedef struct View { void map(struct wlr_surface*, bool, struct wlr_output*, bool); void unmap(); + void touch(); + virtual void focus(bool) = 0; virtual void moveresize(Region const&, Extents const&, bool) = 0; @@ -143,9 +145,10 @@ typedef struct View { bool m_disowned; std::chrono::time_point<std::chrono::steady_clock> m_last_focused; + std::chrono::time_point<std::chrono::steady_clock> m_last_touched; std::chrono::time_point<std::chrono::steady_clock> m_managed_since; -private: +protected: void set_inner_region(Region const&); void set_active_region(Region const&); diff --git a/src/kranewl/model.cc b/src/kranewl/model.cc @@ -18,6 +18,7 @@ #include <spdlog/spdlog.h> +#include <algorithm> #include <iomanip> #include <optional> @@ -119,6 +120,12 @@ Model::exit() mp_server->terminate(); } +const View_ptr +Model::focused_view() const +{ + return mp_focus; +} + KeyBindings const& Model::key_bindings() const { @@ -310,6 +317,8 @@ Model::place_view(Placement& placement) void Model::sync_focus() { + TRACE(); + View_ptr active = mp_workspace->active(); if (active && active != mp_focus) @@ -321,6 +330,136 @@ Model::sync_focus() } void +Model::cycle_focus(Direction direction) +{ + TRACE(); + + if (mp_workspace->size() <= 1) + return; + + mp_workspace->cycle(direction); + sync_focus(); +} + +void +Model::drag_focus(Direction direction) +{ + TRACE(); + + if (mp_workspace->size() <= 1) + return; + + mp_workspace->drag(direction); + sync_focus(); + + apply_layout(mp_workspace); +} + +void +Model::reverse_views() +{ + TRACE(); + + if (mp_workspace->size() <= 1) + return; + + mp_workspace->reverse(); + mp_workspace->active()->focus(true); + sync_focus(); + + apply_layout(mp_workspace); +} + +void +Model::rotate_views(Direction direction) +{ + TRACE(); + + if (mp_workspace->size() <= 1) + return; + + mp_workspace->rotate(direction); + mp_workspace->active()->focus(true); + sync_focus(); + + apply_layout(mp_workspace); +} + +void +Model::shuffle_main(Direction direction) +{ + TRACE(); + + std::size_t main_count + = std::min(static_cast<std::size_t>(mp_workspace->main_count()), mp_workspace->size()); + + if (main_count <= 1) + return; + + Index focus_index = *mp_workspace->views().index(); + std::optional<Index> last_touched_index = std::nullopt; + + if (focus_index >= main_count) { + View_ptr last_touched = *std::max_element( + mp_workspace->begin(), + mp_workspace->begin() + main_count, + [](View_ptr view1, View_ptr view2) -> bool { + return view1->m_last_touched < view2->m_last_touched; + } + ); + + last_touched_index + = mp_workspace->views().index_of_element(last_touched); + } + + mp_workspace->shuffle_main(direction); + mp_workspace->active()->focus(true); + sync_focus(); + + if (last_touched_index) + (*mp_workspace)[*last_touched_index]->touch(); + + apply_layout(mp_workspace); +} + +void +Model::shuffle_stack(Direction direction) +{ + TRACE(); + + std::size_t main_count + = std::min(static_cast<std::size_t>(mp_workspace->main_count()), mp_workspace->size()); + + if ((mp_workspace->size() - main_count) <= 1) + return; + + Index focus_index = *mp_workspace->views().index(); + std::optional<Index> last_touched_index = std::nullopt; + + if (focus_index < main_count) { + View_ptr last_touched = *std::max_element( + mp_workspace->begin() + main_count, + mp_workspace->end(), + [](View_ptr view1, View_ptr view2) -> bool { + return view1->m_last_touched < view2->m_last_touched; + } + ); + + last_touched_index + = mp_workspace->views().index_of_element(last_touched); + } + + mp_workspace->shuffle_stack(direction); + mp_workspace->active()->focus(true); + sync_focus(); + + if (last_touched_index) + (*mp_workspace)[*last_touched_index]->touch(); + + apply_layout(mp_workspace); +} + +void Model::move_view_to_workspace(View_ptr view, Index index) { TRACE(); @@ -529,6 +668,8 @@ Model::set_layout(LayoutHandler::LayoutKind layout) void Model::set_layout_retain_region(LayoutHandler::LayoutKind layout) { + TRACE(); + Cycle<View_ptr> const& views = mp_workspace->views(); std::vector<Region> regions; @@ -560,6 +701,122 @@ Model::set_layout_retain_region(LayoutHandler::LayoutKind layout) } void +Model::toggle_layout_data() +{ + TRACE(); + + mp_workspace->toggle_layout_data(); + apply_layout(mp_workspace); +} + +void +Model::cycle_layout_data(Direction direction) +{ + TRACE(); + + mp_workspace->cycle_layout_data(direction); + apply_layout(mp_workspace); +} + +void +Model::copy_data_from_prev_layout() +{ + TRACE(); + + mp_workspace->copy_data_from_prev_layout(); + apply_layout(mp_workspace); +} + +void +Model::change_gap_size(Util::Change<int> change) +{ + TRACE(); + + mp_workspace->change_gap_size(change); + apply_layout(mp_workspace); +} + +void +Model::change_main_count(Util::Change<int> change) +{ + TRACE(); + + mp_workspace->change_main_count(change); + apply_layout(mp_workspace); +} + +void +Model::change_main_factor(Util::Change<float> change) +{ + TRACE(); + + mp_workspace->change_main_factor(change); + apply_layout(mp_workspace); +} + +void +Model::change_margin(Util::Change<int> change) +{ + TRACE(); + + mp_workspace->change_margin(change); + apply_layout(mp_workspace); +} + +void +Model::change_margin(Edge edge, Util::Change<int> change) +{ + TRACE(); + + mp_workspace->change_margin(edge, change); + apply_layout(mp_workspace); +} + +void +Model::reset_gap_size() +{ + TRACE(); + + mp_workspace->reset_gap_size(); + apply_layout(mp_workspace); +} + +void +Model::reset_margin() +{ + TRACE(); + + mp_workspace->reset_margin(); + apply_layout(mp_workspace); +} + +void +Model::reset_layout_data() +{ + TRACE(); + + mp_workspace->reset_layout_data(); + apply_layout(mp_workspace); +} + +void +Model::save_layout(std::size_t number) const +{ + TRACE(); + + mp_workspace->save_layout(number); +} + +void +Model::load_layout(std::size_t number) +{ + TRACE(); + + mp_workspace->load_layout(number); + apply_layout(mp_workspace); +} + +void Model::apply_layout(Index index) { TRACE(); @@ -583,6 +840,702 @@ Model::apply_layout(Workspace_ptr workspace) place_view(placement); } +void +Model::kill_focus() +{ + TRACE(); + + if (mp_focus) + kill_view(mp_focus); +} + +void +Model::kill_view(View_ptr view) +{ + TRACE(); + + if (!view->m_invincible) { + // TODO: kill view + } +} + +void +Model::set_floating_focus(Toggle toggle) +{ + TRACE(); + + if (mp_focus) + set_floating_view(toggle, mp_focus); +} + +void +Model::set_floating_view(Toggle toggle, View_ptr view) +{ + TRACE(); + + switch (toggle) { + case Toggle::On: view->m_floating = true; break; + case Toggle::Off: view->m_floating = false; break; + case Toggle::Reverse: view->m_floating = !view->m_floating; break; + default: return; + } + + apply_layout(view->mp_workspace); +} + +void +Model::set_fullscreen_focus(Toggle toggle) +{ + TRACE(); + + if (mp_focus) + set_fullscreen_view(toggle, mp_focus); +} + +void +Model::set_fullscreen_view(Toggle toggle, View_ptr view) +{ + TRACE(); + + switch (toggle) { + case Toggle::On: + { + if (view->m_fullscreen) + return; + + view->m_fullscreen = true; + + // TODO: set fullscreen state + + Workspace_ptr workspace = view->mp_workspace; + apply_layout(workspace); + + m_fullscreen_map[view] = view->m_free_region; + + return; + } + case Toggle::Off: + { + if (!view->m_fullscreen) + return; + + if (!view->m_contained) + view->set_free_region(m_fullscreen_map.at(view)); + + view->m_fullscreen = false; + + // TODO: unset fullscreen state + + Workspace_ptr workspace = view->mp_workspace; + apply_layout(workspace); + + m_fullscreen_map.erase(view); + + return; + } + case Toggle::Reverse: + { + set_fullscreen_view( + view->m_fullscreen + ? Toggle::Off + : Toggle::On, + view + ); + + return; + } + default: return; + } +} + +void +Model::set_sticky_focus(Toggle toggle) +{ + TRACE(); + + if (mp_focus) + set_sticky_view(toggle, mp_focus); +} + +void +Model::set_sticky_view(Toggle toggle, View_ptr view) +{ + TRACE(); + + switch (toggle) { + case Toggle::On: + { + if (view->m_sticky) + return; + + if (view->m_iconified) + set_iconify_view(Toggle::Off, view); + + std::for_each( + m_workspaces.begin(), + m_workspaces.end(), + [view](Workspace_ptr workspace) { + if (workspace != view->mp_workspace) + workspace->add_view(view); + } + ); + + // TODO: set sticky state + + Workspace_ptr workspace = view->mp_workspace; + + // TODO: view->stick(); + apply_layout(workspace); + + return; + } + case Toggle::Off: + { + if (!view->m_sticky) + return; + + std::for_each( + m_workspaces.begin(), + m_workspaces.end(), + [=,this](Workspace_ptr workspace) { + if (workspace != mp_workspace) { + workspace->remove_view(view); + workspace->remove_icon(view); + workspace->remove_disowned(view); + } else + view->mp_workspace = workspace; + } + ); + + // TODO: unset sticky state + + // TODO: view->unstick(); + apply_layout(mp_workspace); + + return; + } + case Toggle::Reverse: + { + set_sticky_view( + view->m_sticky + ? Toggle::Off + : Toggle::On, + view + ); + + return; + } + default: return; + } +} + +void +Model::set_contained_focus(Toggle toggle) +{ + TRACE(); + + if (mp_focus) + set_contained_view(toggle, mp_focus); +} + +void +Model::set_contained_view(Toggle toggle, View_ptr view) +{ + TRACE(); + + switch (toggle) { + case Toggle::On: + { + view->m_contained = true; + + Workspace_ptr workspace = view->mp_workspace; + + apply_layout(workspace); + return; + } + case Toggle::Off: + { + view->m_contained = false; + + Workspace_ptr workspace = view->mp_workspace; + + apply_layout(workspace); + return; + } + case Toggle::Reverse: + { + set_contained_view( + view->m_contained + ? Toggle::Off + : Toggle::On, + view + ); + + return; + } + default: return; + } +} + +void +Model::set_invincible_focus(Toggle toggle) +{ + TRACE(); + + if (mp_focus) + set_invincible_view(toggle, mp_focus); +} + +void +Model::set_invincible_view(Toggle toggle, View_ptr view) +{ + TRACE(); + + if (toggle == Toggle::Reverse) + set_invincible_view( + view->m_invincible + ? Toggle::Off + : Toggle::On, + view + ); + else + view->m_invincible + = toggle == Toggle::On ? true : false; +} + +void +Model::set_iconifyable_focus(Toggle toggle) +{ + TRACE(); + + if (mp_focus) + set_iconifyable_view(toggle, mp_focus); +} + +void +Model::set_iconifyable_view(Toggle toggle, View_ptr view) +{ + TRACE(); + + if (toggle == Toggle::Reverse) + set_iconifyable_view( + view->m_iconifyable + ? Toggle::Off + : Toggle::On, + view + ); + else + view->m_iconifyable + = toggle == Toggle::On ? true : false; +} + +void +Model::set_iconify_focus(Toggle toggle) +{ + TRACE(); + + if (mp_focus) + set_iconify_view(toggle, mp_focus); +} + +void +Model::set_iconify_view(Toggle toggle, View_ptr view) +{ + TRACE(); + + switch (toggle) { + case Toggle::On: + { + if (view->m_iconified || view->m_sticky) + return; + + Workspace_ptr workspace = view->mp_workspace; + workspace->view_to_icon(view); + + // TODO: set iconify state + + unmap_view(view); + + apply_layout(workspace); + sync_focus(); + + view->m_iconified = true; + + return; + } + case Toggle::Off: + { + if (!view->m_iconified) + return; + + Workspace_ptr workspace = view->mp_workspace; + workspace->icon_to_view(view); + + // TODO: unset iconify state + + view->m_iconified = false; + + apply_layout(workspace); + sync_focus(); + + return; + } + case Toggle::Reverse: + { + set_iconify_view( + view->m_iconified + ? Toggle::Off + : Toggle::On, + view + ); + + return; + } + default: return; + } +} + +void +Model::center_focus() +{ + TRACE(); + + if (mp_focus) + center_view(mp_focus); +} + +void +Model::center_view(View_ptr view) +{ + TRACE(); + + if (!is_free(view)) + return; + + Region region = view->m_free_region; + const Region screen_region + = view->mp_context->output()->placeable_region(); + + region.pos.x = screen_region.pos.x + + (screen_region.dim.w - region.dim.w) / 2; + + region.pos.y = screen_region.pos.y + + (screen_region.dim.h - region.dim.h) / 2; + + Placement placement = Placement { + Placement::PlacementMethod::Free, + view, + view->m_free_decoration, + region + }; + + place_view(placement); +} + +void +Model::nudge_focus(Edge edge, Util::Change<std::size_t> change) +{ + TRACE(); + + if (mp_focus) + nudge_view(edge, change, mp_focus); +} + +void +Model::nudge_view(Edge edge, Util::Change<std::size_t> change, View_ptr view) +{ + TRACE(); + + if (!is_free(view)) + return; + + Region region = view->m_free_region; + + switch (edge) { + case Edge::Left: + { + region.pos.x -= change; + break; + } + case Edge::Top: + { + region.pos.y -= change; + break; + } + case Edge::Right: + { + region.pos.x += change; + break; + } + case Edge::Bottom: + { + region.pos.y += change; + break; + } + default: return; + } + + Placement placement = Placement { + Placement::PlacementMethod::Free, + view, + view->m_free_decoration, + region + }; + + place_view(placement); +} + +void +Model::stretch_focus(Edge edge, Util::Change<int> change) +{ + if (mp_focus) + stretch_view(edge, change, mp_focus); +} + +void +Model::stretch_view(Edge edge, Util::Change<int> change, View_ptr view) +{ + TRACE(); + + if (!is_free(view)) + return; + + Decoration decoration = view->m_free_decoration; + Extents extents = Extents { 0, 0, 0, 0 }; + + if (decoration.frame) { + extents.left += decoration.frame->extents.left; + extents.top += decoration.frame->extents.top; + extents.right += decoration.frame->extents.right; + extents.bottom += decoration.frame->extents.bottom; + } + + Region region = view->m_free_region; + region.remove_extents(extents); + + switch (edge) { + case Edge::Left: + { + if (!(change < 0 && -change >= region.dim.h)) { + if (region.dim.w + change <= View::MIN_VIEW_DIM.w) { + region.pos.x -= View::MIN_VIEW_DIM.w - region.dim.w; + region.dim.w = View::MIN_VIEW_DIM.w; + } else { + region.pos.x -= change; + region.dim.w += change; + } + } + + break; + } + case Edge::Top: + { + if (!(change < 0 && -change >= region.dim.h)) { + if (region.dim.h + change <= View::MIN_VIEW_DIM.h) { + region.pos.y -= View::MIN_VIEW_DIM.h - region.dim.h; + region.dim.h = View::MIN_VIEW_DIM.h; + } else { + region.pos.y -= change; + region.dim.h += change; + } + } + + break; + } + case Edge::Right: + { + if (!(change < 0 && -change >= region.dim.w)) { + if (region.dim.w + change <= View::MIN_VIEW_DIM.w) + region.dim.w = View::MIN_VIEW_DIM.w; + else + region.dim.w += change; + } + + break; + } + case Edge::Bottom: + { + if (!(change < 0 && -change >= region.dim.h)) { + if (region.dim.h + change <= View::MIN_VIEW_DIM.h) + region.dim.h = View::MIN_VIEW_DIM.h; + else + region.dim.h += change; + } + + break; + } + default: return; + } + + region.apply_extents(extents); + + Placement placement = Placement { + Placement::PlacementMethod::Free, + view, + view->m_free_decoration, + region + }; + + place_view(placement); +} + +void +Model::inflate_focus(Util::Change<int> change) +{ + TRACE(); + + if (mp_focus) + inflate_view(change, mp_focus); +} + +void +Model::inflate_view(Util::Change<int> change, View_ptr view) +{ + TRACE(); + + if (!is_free(view)) + return; + + Decoration decoration = view->m_free_decoration; + Extents extents = Extents { 0, 0, 0, 0 }; + + if (decoration.frame) { + extents.left += decoration.frame->extents.left; + extents.top += decoration.frame->extents.top; + extents.right += decoration.frame->extents.right; + extents.bottom += decoration.frame->extents.bottom; + } + + Region region = view->m_free_region; + region.remove_extents(extents); + + double ratio = static_cast<double>(region.dim.w) + / static_cast<double>(region.dim.w + region.dim.h); + + double width_inc = ratio * change; + double height_inc = change - width_inc; + + int dw = std::lround(width_inc); + int dh = std::lround(height_inc); + + if ((dw < 0 && -dw >= region.dim.w) + || (dh < 0 && -dh >= region.dim.h) + || (region.dim.w + dw <= View::MIN_VIEW_DIM.w) + || (region.dim.h + dh <= View::MIN_VIEW_DIM.h)) + { + return; + } + + region.dim.w += dw; + region.dim.h += dh; + + region.apply_extents(extents); + + int dx = region.dim.w - view->m_free_region.dim.w; + int dy = region.dim.h - view->m_free_region.dim.h; + + dx = std::lround(dx / static_cast<double>(2)); + dy = std::lround(dy / static_cast<double>(2)); + + region.pos.x -= dx; + region.pos.y -= dy; + + view->set_free_region(region); + + Placement placement = Placement { + Placement::PlacementMethod::Free, + view, + view->m_free_decoration, + region + }; + + place_view(placement); +} + +void +Model::snap_focus(Edge edge) +{ + TRACE(); + + if (mp_focus) + snap_view(edge, mp_focus); +} + +void +Model::snap_view(Edge edge, View_ptr view) +{ + TRACE(); + + if (!is_free(view)) + return; + + Region region = view->m_free_region; + const Region screen_region + = view->mp_context->output()->placeable_region(); + + switch (edge) { + case Edge::Left: + { + region.pos.x = screen_region.pos.x; + + break; + } + case Edge::Top: + { + region.pos.y = screen_region.pos.y; + + break; + } + case Edge::Right: + { + region.pos.x = std::max( + 0, + (screen_region.dim.w + screen_region.pos.x) - region.dim.w + ); + + break; + } + case Edge::Bottom: + { + region.pos.y = std::max( + 0, + (screen_region.dim.h + screen_region.pos.y) - region.dim.h + ); + + break; + } + } + + Placement placement = Placement { + Placement::PlacementMethod::Free, + view, + view->m_free_decoration, + region + }; + + place_view(placement); +} + +void +Model::pop_deiconify() +{ + TRACE(); + + std::optional<View_ptr> icon = mp_workspace->pop_icon(); + + if (icon) + set_iconify_view(Toggle::Off, *icon); +} + +void +Model::deiconify_all() +{ + TRACE(); + + for (std::size_t i = 0; i < mp_workspace->size(); ++i) + pop_deiconify(); +} + XDGView_ptr Model::create_xdg_shell_view( struct wlr_xdg_surface* wlr_xdg_surface, diff --git a/src/kranewl/tree/client.cc b/src/kranewl/tree/client.cc @@ -50,8 +50,9 @@ Client::Client( m_disowned(false), m_producing(true), m_attaching(false), - m_last_focused{std::chrono::steady_clock::now()}, - m_managed_since{std::chrono::steady_clock::now()}, + m_last_focused(std::chrono::steady_clock::now()), + m_last_touched(std::chrono::steady_clock::now()), + m_managed_since(std::chrono::steady_clock::now()), m_outside_state{OutsideState::Unfocused} {} diff --git a/src/kranewl/tree/output.cc b/src/kranewl/tree/output.cc @@ -2,6 +2,7 @@ #include <kranewl/model.hh> #include <kranewl/server.hh> +#include <kranewl/workspace.hh> #include <kranewl/tree/output.hh> // https://github.com/swaywm/wlroots/issues/682 diff --git a/src/kranewl/tree/view.cc b/src/kranewl/tree/view.cc @@ -45,9 +45,9 @@ View::View( mp_workspace(nullptr), mp_wlr_surface(wlr_surface), m_alpha(1.f), - m_tile_decoration{FREE_DECORATION}, - m_free_decoration{FREE_DECORATION}, - m_active_decoration{FREE_DECORATION}, + m_tile_decoration(FREE_DECORATION), + m_free_decoration(FREE_DECORATION), + m_active_decoration(FREE_DECORATION), m_minimum_dim({}), m_preferred_dim({}), m_free_region({}), @@ -69,6 +69,7 @@ View::View( m_iconified(false), m_disowned(false), m_last_focused(std::chrono::steady_clock::now()), + m_last_touched(std::chrono::steady_clock::now()), m_managed_since(std::chrono::steady_clock::now()), ml_foreign_activate_request({ .notify = handle_foreign_activate_request }), ml_foreign_fullscreen_request({ .notify = handle_foreign_fullscreen_request }), @@ -200,10 +201,15 @@ View::unmap() { TRACE(); - wlr_scene_node_destroy(mp_scene_surface); wlr_scene_node_destroy(mp_scene); } +void +View::touch() +{ + m_last_touched = std::chrono::steady_clock::now(); +} + static uint32_t extents_to_wlr_edges(Extents const& extents) { diff --git a/src/kranewl/tree/xdg_view.cc b/src/kranewl/tree/xdg_view.cc @@ -66,13 +66,13 @@ XDGView::focus(bool raise) { TRACE(); - struct wlr_surface* focused_surface + struct wlr_surface* prev_focus = mp_seat->mp_wlr_seat->keyboard_state.focused_surface; if (raise) wlr_scene_node_raise_to_top(mp_scene); - if (focused_surface == mp_wlr_surface) + if (prev_focus == mp_wlr_surface) return; mp_model->focus_view(this); @@ -83,10 +83,10 @@ XDGView::focus(bool raise) m_active_decoration.colorscheme.focused.values ); - if (focused_surface && focused_surface != mp_wlr_surface) { - if (wlr_surface_is_layer_surface(focused_surface)) { + if (prev_focus && prev_focus != mp_wlr_surface) { + if (wlr_surface_is_layer_surface(prev_focus)) { struct wlr_layer_surface_v1* wlr_layer_surface - = wlr_layer_surface_v1_from_wlr_surface(focused_surface); + = wlr_layer_surface_v1_from_wlr_surface(prev_focus); if (wlr_layer_surface->mapped && ( wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP || @@ -95,19 +95,20 @@ XDGView::focus(bool raise) return; } } else { + View_ptr prev_view; struct wlr_scene_node* node - = reinterpret_cast<struct wlr_scene_node*>(focused_surface->data); + = reinterpret_cast<struct wlr_scene_node*>(prev_focus->data); - if (focused_surface->role_data && node->data) + if (prev_focus->role_data && (prev_view = reinterpret_cast<View_ptr>(node->data))) for (std::size_t i = 0; i < 4; ++i) wlr_scene_rect_set_color( - m_protrusions[i], + prev_view->m_protrusions[i], m_active_decoration.colorscheme.unfocused.values ); struct wlr_xdg_surface* wlr_xdg_surface; - if (wlr_surface_is_xdg_surface(focused_surface) - && (wlr_xdg_surface = wlr_xdg_surface_from_wlr_surface(focused_surface)) + if (wlr_surface_is_xdg_surface(prev_focus) + && (wlr_xdg_surface = wlr_xdg_surface_from_wlr_surface(prev_focus)) && wlr_xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { wlr_xdg_toplevel_set_activated(wlr_xdg_surface, false); @@ -205,6 +206,10 @@ XDGView::handle_commit(struct wl_listener* listener, void* data) { TRACE(); + XDGView_ptr view = wl_container_of(listener, view, ml_commit); + + if (view->m_resize && view->m_resize <= view->mp_wlr_xdg_surface->current.configure_serial) + view->m_resize = 0; } void @@ -233,6 +238,9 @@ XDGView::handle_set_title(struct wl_listener* listener, void* data) { TRACE(); + XDGView_ptr view = wl_container_of(listener, view, ml_set_title); + view->m_title = view->mp_wlr_xdg_toplevel->title; + view->m_title_formatted = view->m_title; } void @@ -240,6 +248,8 @@ XDGView::handle_set_app_id(struct wl_listener* listener, void* data) { TRACE(); + XDGView_ptr view = wl_container_of(listener, view, ml_set_app_id); + view->m_app_id = view->mp_wlr_xdg_toplevel->app_id; } void @@ -343,8 +353,10 @@ XDGView::handle_destroy(struct wl_listener* listener, void* data) XDGView_ptr view = wl_container_of(listener, view, ml_destroy); + view->mp_wlr_xdg_toplevel = nullptr; view->mp_model->unregister_view(view); + wl_list_remove(&view->m_events.unmap.listener_list); wl_list_remove(&view->ml_commit.link); wl_list_remove(&view->ml_new_popup.link); wl_list_remove(&view->ml_request_fullscreen.link); @@ -352,4 +364,7 @@ XDGView::handle_destroy(struct wl_listener* listener, void* data) wl_list_remove(&view->ml_request_resize.link); wl_list_remove(&view->ml_set_title.link); wl_list_remove(&view->ml_set_app_id.link); + wl_list_remove(&view->ml_map.link); + wl_list_remove(&view->ml_unmap.link); + wl_list_remove(&view->ml_destroy.link); }