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 128cd36e8b5715d5d9f3246ea2adea2aa2b2dc48
parent 607f71e28ef1518901db6ccfc26ebc92f3591b2b
Author: deurzen <max@deurzen.net>
Date:   Thu,  2 Jun 2022 12:36:15 +0200

adds conditional view jumping actions

Diffstat:
Minclude/kranewl/input/key-bindings.hh | 52+++++++++++++++++++++++++++++++++++++++++++++++++++-
Minclude/kranewl/model.hh | 6++++++
Ainclude/kranewl/search.hh | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kranewl/workspace.hh | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/kranewl/model.cc | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/kranewl/workspace.cc | 38++++++++++++++++++++++++++++++++++++++
6 files changed, 390 insertions(+), 1 deletion(-)

diff --git a/include/kranewl/input/key-bindings.hh b/include/kranewl/input/key-bindings.hh @@ -413,7 +413,7 @@ static const KeyBindings key_bindings = { CALL(activate_workspace_current_context(9)) }, -// workspace client movers +// workspace view movers { { XKB_KEY_braceright, MODKEY | WLR_MODIFIER_SHIFT }, CALL(move_focus_to_next_workspace(Direction::Forward)) }, @@ -492,6 +492,56 @@ static const KeyBindings key_bindings = { CALL(activate_context(9)) }, +// view jump criteria +{ { XKB_KEY_b, MODKEY }, + CALL(jump_view({ + SearchSelector::SelectionCriterium::ByAppIdEquals, + "qutebrowser" + })) +}, +{ { XKB_KEY_B, MODKEY | WLR_MODIFIER_SHIFT }, + CALL(jump_view({ + SearchSelector::SelectionCriterium::ByAppIdEquals, + "firefox" + })) +}, +{ { XKB_KEY_b, MODKEY | WLR_MODIFIER_CTRL }, + CALL(jump_view({ + SearchSelector::SelectionCriterium::ByAppIdContains, + "chromium" + })) +}, +{ { XKB_KEY_space, MODKEY | SECKEY }, + CALL(jump_view({ + SearchSelector::SelectionCriterium::ByAppIdEquals, + "spotify" + })) +}, +{ { XKB_KEY_e, MODKEY }, + CALL(jump_view({ + SearchSelector::SelectionCriterium::ByTitleContains, + "[vim]" + })) +}, +{ { XKB_KEY_comma, MODKEY }, + CALL(jump_view({ + model.mp_workspace->index(), + Workspace::ViewSelector::SelectionCriterium::AtFirst + })) +}, +{ { XKB_KEY_period, MODKEY }, + CALL(jump_view({ + model.mp_workspace->index(), + Workspace::ViewSelector::SelectionCriterium::AtMain + })) +}, +{ { XKB_KEY_slash, MODKEY }, + CALL(jump_view({ + model.mp_workspace->index(), + Workspace::ViewSelector::SelectionCriterium::AtLast + })) +}, + // external commands { { XKB_KEY_XF86AudioPlay, {} }, CALL_EXTERNAL(playerctl play-pause) diff --git a/include/kranewl/model.hh b/include/kranewl/model.hh @@ -7,6 +7,7 @@ #include <kranewl/layout.hh> #include <kranewl/placement.hh> #include <kranewl/tree/layer.hh> +#include <kranewl/search.hh> #include <kranewl/tree/view.hh> #include <optional> @@ -82,6 +83,10 @@ public: void refocus(); void place_view(Placement&); + bool view_matches_search(View_ptr, SearchSelector const&) const; + View_ptr search_view(SearchSelector const&); + void jump_view(SearchSelector const&); + void focus_output(Output_ptr); void cursor_interactive(Cursor::Mode, View_ptr); @@ -220,6 +225,7 @@ private: std::vector<View_ptr> m_sticky_views; View_ptr mp_focus; + View_ptr mp_jumped_from; const KeyBindings m_key_bindings; const CursorBindings m_cursor_bindings; diff --git a/include/kranewl/search.hh b/include/kranewl/search.hh @@ -0,0 +1,132 @@ +#pragma once + +#include <kranewl/common.hh> +#include <kranewl/tree/view.hh> +#include <kranewl/workspace.hh> + +#include <functional> +#include <string> +#include <utility> + +typedef class SearchSelector final +{ +public: + enum class SelectionCriterium + { + OnWorkspaceBySelector, + ByTitleEquals, + ByAppIdEquals, + ByTitleContains, + ByAppIdContains, + ForCondition, + }; + + SearchSelector(Index index, Workspace::ViewSelector::SelectionCriterium criterium) + : m_tag(SearchSelectorTag::OnWorkspaceBySelector), + m_workspace_selector(std::pair(index, criterium)) + {} + + SearchSelector(SelectionCriterium criterium, std::string&& str_) + : m_string(str_) + { + switch (criterium) { + case SelectionCriterium::ByTitleEquals: m_tag = SearchSelectorTag::ByTitleEquals; return; + case SelectionCriterium::ByAppIdEquals: m_tag = SearchSelectorTag::ByAppIdEquals; return; + case SelectionCriterium::ByTitleContains: m_tag = SearchSelectorTag::ByTitleContains; return; + case SelectionCriterium::ByAppIdContains: m_tag = SearchSelectorTag::ByAppIdContains; return; + default: return; + } + } + + SearchSelector(std::function<bool(const View_ptr)>&& filter) + : m_tag(SearchSelectorTag::ForCondition), + m_filter(filter) + {} + + ~SearchSelector() + { + switch (m_tag) { + case SearchSelectorTag::OnWorkspaceBySelector: + { + (&m_workspace_selector)->std::pair< + Index, + Workspace::ViewSelector::SelectionCriterium + >::pair::~pair(); + + return; + } + case SearchSelectorTag::ByTitleEquals: // fallthrough + case SearchSelectorTag::ByAppIdEquals: // fallthrough + case SearchSelectorTag::ByTitleContains: // fallthrough + case SearchSelectorTag::ByAppIdContains: + { + (&m_string)->std::string::~string(); + + return; + } + case SearchSelectorTag::ForCondition: + { + (&m_filter)->std::function<bool(const View_ptr)>::function::~function(); + + return; + } + default: return; + } + } + + SearchSelector(SearchSelector&&) = delete; + + SelectionCriterium + criterium() const + { + switch (m_tag) { + case SearchSelectorTag::OnWorkspaceBySelector: return SelectionCriterium::OnWorkspaceBySelector; + case SearchSelectorTag::ByTitleEquals: return SelectionCriterium::ByTitleEquals; + case SearchSelectorTag::ByAppIdEquals: return SelectionCriterium::ByAppIdEquals; + case SearchSelectorTag::ByTitleContains: return SelectionCriterium::ByTitleContains; + case SearchSelectorTag::ByAppIdContains: return SelectionCriterium::ByAppIdContains; + case SearchSelectorTag::ForCondition: return SelectionCriterium::ForCondition; + default: break; + } + + return {}; + } + + std::pair<Index, Workspace::ViewSelector::SelectionCriterium> const& + workspace_selector() const + { + return m_workspace_selector; + } + + std::string const& + string_value() const + { + return m_string; + } + + std::function<bool(const View_ptr)> const& + filter() const + { + return m_filter; + } + +private: + enum class SearchSelectorTag + { + OnWorkspaceBySelector, + ByTitleEquals, + ByAppIdEquals, + ByTitleContains, + ByAppIdContains, + ForCondition, + }; + + SearchSelectorTag m_tag; + union + { + std::pair<Index, Workspace::ViewSelector::SelectionCriterium> m_workspace_selector; + std::string m_string; + std::function<bool(const View_ptr)> m_filter; + }; + +}* SearchSelector_ptr; diff --git a/include/kranewl/workspace.hh b/include/kranewl/workspace.hh @@ -11,6 +11,66 @@ typedef struct View* View_ptr; typedef class Context* Context_ptr; typedef class Workspace final { public: + struct ViewSelector + { + enum class SelectionCriterium { + AtFirst, + AtLast, + AtMain, + AtIndex + }; + + ViewSelector(const SelectionCriterium criterium) + : m_index(std::nullopt) + { + switch (criterium) { + case SelectionCriterium::AtFirst: m_tag = ViewSelectorTag::AtFirst; return; + case SelectionCriterium::AtLast: m_tag = ViewSelectorTag::AtLast; return; + case SelectionCriterium::AtMain: m_tag = ViewSelectorTag::AtMain; return; + default: return; + } + } + + ViewSelector(const std::size_t index) + : m_tag(ViewSelectorTag::AtIndex), + m_index(index) + {} + + ~ViewSelector() = default; + + SelectionCriterium + criterium() const + { + switch (m_tag) { + case ViewSelectorTag::AtFirst: return SelectionCriterium::AtFirst; + case ViewSelectorTag::AtLast: return SelectionCriterium::AtLast; + case ViewSelectorTag::AtMain: return SelectionCriterium::AtMain; + case ViewSelectorTag::AtIndex: return SelectionCriterium::AtIndex; + default: break; + } + + return {}; + } + + std::size_t + index() const + { + return *m_index; + } + + private: + enum class ViewSelectorTag { + AtFirst, + AtLast, + AtMain, + AtIndex + }; + + ViewSelectorTag m_tag; + std::optional<std::size_t> m_index; + + }; + Workspace(Index index, std::string name, Context_ptr context) : m_index(index), m_name(name), @@ -53,6 +113,7 @@ public: View_ptr next_view() const; View_ptr prev_view() const; + std::optional<View_ptr> find_view(ViewSelector const&) const; void cycle(Direction); void drag(Direction); diff --git a/src/kranewl/model.cc b/src/kranewl/model.cc @@ -23,6 +23,7 @@ #include <algorithm> #include <iomanip> #include <optional> +#include <set> // https://github.com/swaywm/wlroots/issues/682 #include <pthread.h> @@ -54,6 +55,7 @@ Model::Model(Config const& config) m_fullscreen_map{}, m_sticky_views{}, mp_focus(nullptr), + mp_jumped_from(nullptr), m_key_bindings(Bindings::key_bindings), m_cursor_bindings(Bindings::cursor_bindings) { @@ -362,6 +364,106 @@ Model::place_view(Placement& placement) ); } +bool +Model::view_matches_search(View_ptr view, SearchSelector const& selector) const +{ + TRACE(); + + switch (selector.criterium()) { + case SearchSelector::SelectionCriterium::OnWorkspaceBySelector: + { + auto const& [index,selector_] = selector.workspace_selector(); + + if (index <= m_workspaces.size()) { + Workspace_ptr workspace = m_workspaces[index]; + std::optional<View_ptr> view_ = workspace->find_view(selector_); + + return view_ && view_ == view; + } + + return false; + } + case SearchSelector::SelectionCriterium::ByTitleEquals: + return view->m_title == selector.string_value(); + case SearchSelector::SelectionCriterium::ByAppIdEquals: + return view->m_app_id == selector.string_value(); + case SearchSelector::SelectionCriterium::ByTitleContains: + return view->m_title.find(selector.string_value()) != std::string::npos; + case SearchSelector::SelectionCriterium::ByAppIdContains: + return view->m_app_id.find(selector.string_value()) != std::string::npos; + case SearchSelector::SelectionCriterium::ForCondition: + return selector.filter()(view); + default: break; + } + + return false; +} + +View_ptr +Model::search_view(SearchSelector const& selector) +{ + TRACE(); + + static constexpr struct LastFocusedComparer final { + bool + operator()(const View_ptr lhs, const View_ptr rhs) const + { + return lhs->last_focused() < rhs->last_focused(); + } + } last_focused_comparer{}; + + std::set<View_ptr, LastFocusedComparer> views{{}, last_focused_comparer}; + + switch (selector.criterium()) { + case SearchSelector::SelectionCriterium::OnWorkspaceBySelector: + { + auto const& [index,selector_] = selector.workspace_selector(); + + if (index <= m_workspaces.size()) { + Workspace_ptr workspace = m_workspaces[index]; + std::optional<View_ptr> view = workspace->find_view(selector_); + + if (view && (*view)->managed()) + views.insert(*view); + } + + break; + } + default: + { + for (auto const&[_,view] : m_view_map) + if (view->managed() && view_matches_search(view, selector)) + views.insert(view); + + break; + } + } + + return views.empty() + ? nullptr + : *views.rbegin(); +} + +void +Model::jump_view(SearchSelector const& selector) +{ + TRACE(); + + View_ptr view = search_view(selector); + + if (view) { + if (view == mp_focus) { + if (mp_jumped_from && view != mp_jumped_from) + view = mp_jumped_from; + } + + if (mp_focus) + mp_jumped_from = mp_focus; + + focus_view(view); + } +} + void Model::cursor_interactive(Cursor::Mode mode, View_ptr view) { diff --git a/src/kranewl/workspace.cc b/src/kranewl/workspace.cc @@ -171,6 +171,44 @@ Workspace::prev_view() const return nullptr; } +std::optional<View_ptr> +Workspace::find_view(ViewSelector const& selector) const +{ + if (m_views.empty()) + return std::nullopt; + + switch (selector.criterium()) { + case ViewSelector::SelectionCriterium::AtFirst: + { + return m_views[0]; + } + case ViewSelector::SelectionCriterium::AtLast: + { + return m_views[Util::last_index(m_views.as_deque())]; + } + case ViewSelector::SelectionCriterium::AtMain: + { + std::size_t main_count = m_layout_handler.main_count(); + + if (main_count <= m_views.size()) + return m_views[main_count]; + + break; + } + case ViewSelector::SelectionCriterium::AtIndex: + { + std::size_t index = selector.index(); + + if (index <= m_views.size()) + return m_views[index]; + + break; + } + } + + return std::nullopt; +} + void Workspace::cycle(Direction direction) {