commit 128cd36e8b5715d5d9f3246ea2adea2aa2b2dc48
parent 607f71e28ef1518901db6ccfc26ebc92f3591b2b
Author: deurzen <max@deurzen.net>
Date: Thu, 2 Jun 2022 12:36:15 +0200
adds conditional view jumping actions
Diffstat:
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)
{