From 9b2908c411ae262b6c1b734f09cc9c8af5fc5ad3 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Tue, 3 Dec 2024 17:32:38 +0100 Subject: [PATCH] window_layouter: restrict focus to visible windows This patch restricts the focus switching via the keyboard (Super-Tab) to windows located at visible screens. Should the currently focused window become invible, switch the focus to the most recently focused visible window. Issue #5390 --- .../src/app/window_layouter/focus_history.h | 117 +++++++++++------- repos/gems/src/app/window_layouter/main.cc | 27 +++- 2 files changed, 100 insertions(+), 44 deletions(-) diff --git a/repos/gems/src/app/window_layouter/focus_history.h b/repos/gems/src/app/window_layouter/focus_history.h index 9e267e68030..0d4d5db961a 100644 --- a/repos/gems/src/app/window_layouter/focus_history.h +++ b/repos/gems/src/app/window_layouter/focus_history.h @@ -24,6 +24,11 @@ class Window_layouter::Focus_history { public: + struct Action : Interface, Noncopyable + { + virtual bool visible(Window_id) = 0; + }; + struct Entry : List::Element { Focus_history &focus_history; @@ -36,72 +41,100 @@ class Window_layouter::Focus_history private: + Action &_action; + List _entries { }; - Entry *_lookup(Window_id window_id) + Entry const *_next_in_cycle(Entry const &e) const { - for (Entry *e = _entries.first(); e; e = e->next()) - if (e->window_id == window_id) - return e; + return e.next() ? e.next() : _entries.first(); + } - return nullptr; + Entry const *_prev_in_cycle(Entry const &e) const + { + auto last_ptr = [&] + { + Entry const *ptr = _entries.first(); + for ( ; ptr && ptr->next(); ptr = ptr->next()); + return ptr; + }; + + auto prev_ptr = [&] (Entry const &e) -> Entry const * + { + for (Entry const *ptr = _entries.first(); ptr; ptr = ptr->next()) + if (ptr->next() == &e) + return ptr; + return nullptr; + }; + + Entry const * const ptr = prev_ptr(e); + return ptr ? ptr : last_ptr(); } - void _remove_if_present(Entry &entry) + Window_id _any_visible_or_none() const { - _entries.remove(&entry); + for (Entry const *ptr = _entries.first(); ptr; ptr = ptr->next()) + if (_action.visible(ptr->window_id)) + return ptr->window_id; + return Window_id(); + } + + Window_id _visible_neighbor(Window_id window_id, auto const &next_fn) const + { + auto entry_ptr_for_window = [&] () -> Entry const * + { + for (Entry const *e = _entries.first(); e; e = e->next()) + if (e->window_id == window_id) + return e; + return nullptr; + }; + + Entry const * const anchor_ptr = entry_ptr_for_window(); + if (!anchor_ptr) + return _any_visible_or_none(); + + Entry const *next_ptr = nullptr; + for (Entry const *ptr = anchor_ptr; ptr; ptr = next_ptr) { + next_ptr = next_fn(*ptr); + + bool const cycle_complete = (next_ptr == anchor_ptr); + if (cycle_complete) + return _any_visible_or_none(); + + if (next_ptr && _action.visible(next_ptr->window_id)) + return next_ptr->window_id; + } + return Window_id(); } public: + Focus_history(Action &action) : _action(action) { } + void focus(Window_id window_id) { - Entry * const entry = _lookup(window_id); - if (!entry) { + Entry *ptr = _entries.first(); + for (; ptr && ptr->window_id != window_id; ptr = ptr->next()); + + if (!ptr) { warning("unexpected lookup failure for focus history entry"); return; } - _remove_if_present(*entry); + _entries.remove(ptr); /* insert entry at the beginning (most recently focused) */ - _entries.insert(entry); + _entries.insert(ptr); } - Window_id next(Window_id window_id) + Window_id next(Window_id id) const { - Entry * const first = _entries.first(); - if (!first) - return Window_id(); - - Entry * const entry = _lookup(window_id); - if (!entry) - return first->window_id; - - Entry * const next = entry->next(); - return next ? next->window_id : first->window_id; + return _visible_neighbor(id, [&] (Entry const &e) { return _next_in_cycle(e); }); } - Window_id prev(Window_id window_id) + Window_id prev(Window_id id) const { - Entry *curr = _entries.first(); - if (!curr) - return Window_id(); - - /* if argument refers to the first window, cycle to the last one */ - if (curr->window_id == window_id) { - - /* determine last list element */ - for (; curr->next(); curr = curr->next()); - return curr->window_id; - } - - /* traverse list, looking for the predecessor of the window */ - for (; curr->next(); curr = curr->next()) - if (curr->next()->window_id == window_id) - return curr->window_id; - - return Window_id(); + return _visible_neighbor(id, [&] (Entry const &e) { return _prev_in_cycle(e); }); } }; @@ -117,7 +150,7 @@ Window_layouter::Focus_history::Entry::Entry(Focus_history &focus_history, Window_layouter::Focus_history::Entry::~Entry() { - focus_history._remove_if_present(*this); + focus_history._entries.remove(this); } #endif /* _FOCUS_HISTORY_H_ */ diff --git a/repos/gems/src/app/window_layouter/main.cc b/repos/gems/src/app/window_layouter/main.cc index 7396d5e4fd7..6845f657b1e 100644 --- a/repos/gems/src/app/window_layouter/main.cc +++ b/repos/gems/src/app/window_layouter/main.cc @@ -36,7 +36,8 @@ namespace Window_layouter { struct Main; } struct Window_layouter::Main : User_state::Action, Layout_rules::Action, - Window_list::Action + Window_list::Action, + Focus_history::Action { Env &_env; @@ -58,7 +59,7 @@ struct Window_layouter::Main : User_state::Action, unsigned _to_front_cnt = 1; - Focus_history _focus_history { }; + Focus_history _focus_history { *this }; Layout_rules _layout_rules { _env, _heap, *this }; @@ -124,6 +125,12 @@ struct Window_layouter::Main : User_state::Action, _assign_list.for_each_wildcard_assigned_window([&] (Window &window) { _to_front(window); }); + /* update focus if focused window became invisible */ + if (!visible(_user_state.focused_window_id())) { + _user_state.focused_window_id(_focus_history.next({})); + _gen_focus(); + } + _gen_window_layout(); if (_assign_list.matching_wildcards()) @@ -164,6 +171,22 @@ struct Window_layouter::Main : User_state::Action, User_state _user_state { *this, _focus_history }; + /** + * Focus_history::Action interface + */ + bool visible(Window_id const id) override + { + bool result = false; + _target_list.for_each([&] (Target const &target) { + if (target.visible) + _assign_list.for_each_visible(target.name, [&] (Assign const &assign) { + assign.for_each_member([&] (Assign::Member const &member) { + if (member.window.id == id) + result = true; }); }); }); + return result; + } + + /********************************** ** User_state::Action interface ** **********************************/