Skip to content

Commit

Permalink
window_layouter: restrict focus to visible windows
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nfeske committed Jan 30, 2025
1 parent b8147af commit 9855dcb
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 44 deletions.
111 changes: 69 additions & 42 deletions repos/gems/src/app/window_layouter/focus_history.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,70 +38,97 @@ class Window_layouter::Focus_history

List<Entry> _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();
}

Window_id _any_suitable_or_none(auto const &cond_fn) const
{
for (Entry const *ptr = _entries.first(); ptr; ptr = ptr->next())
if (cond_fn(ptr->window_id))
return ptr->window_id;
return Window_id();
}

void _remove_if_present(Entry &entry)
Window_id _neighbor(Window_id window_id, auto const &cond_fn, auto const &next_fn) const
{
_entries.remove(&entry);
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_suitable_or_none(cond_fn);

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_suitable_or_none(cond_fn);

if (next_ptr && cond_fn(next_ptr->window_id))
return next_ptr->window_id;
}
return Window_id();
}

public:

void focus(Window_id window_id)
{
Entry * const entry = _lookup(window_id);
if (!entry) {
if (window_id.value == 0)
return;

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, auto const &cond_fn) 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 _neighbor(id, cond_fn, [&] (Entry const &e) { return _next_in_cycle(e); });
}

Window_id prev(Window_id window_id)
Window_id prev(Window_id id, auto const &cond_fn) 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 _neighbor(id, cond_fn, [&] (Entry const &e) { return _prev_in_cycle(e); });
}
};

Expand All @@ -117,7 +144,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_ */
42 changes: 42 additions & 0 deletions repos/gems/src/app/window_layouter/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ 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())) {
auto window_visible = [&] (Window_id id) { return visible(id); };
_user_state.focused_window_id(_focus_history.next({}, window_visible));
_gen_focus();
}

_gen_window_layout();

if (_assign_list.matching_wildcards())
Expand Down Expand Up @@ -163,6 +170,26 @@ struct Window_layouter::Main : User_state::Action,

User_state _user_state { *this, _focus_history };

bool _visible(Window_id const id, auto const &target_cond_fn)
{
bool result = false;
_target_list.for_each([&] (Target const &target) {
if (target_cond_fn(target))
_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
*/
bool visible(Window_id const id) override
{
return _visible(id, [&] (Target const &target) { return target.visible; });
}


/**********************************
** User_state::Action interface **
Expand Down Expand Up @@ -242,6 +269,21 @@ struct Window_layouter::Main : User_state::Action,
if (from.rect == to.rect)
_retarget_window(_drag.window_id, from, to); });

/* repeated activation of screen moves focus to the screen */
bool already_visible = false;
_target_list.with_target(name, [&] (Target const &target) {
already_visible = target.visible; });

auto visible_on_screen = [&] (Window_id id)
{
return _visible(id, [&] (Target const &target) { return target.name == name; });
};

if (already_visible && !_drag.dragging()) {
_user_state.focused_window_id(_focus_history.next({}, visible_on_screen));
_gen_focus();
}

_gen_rules_with_frontmost_screen(name);
}

Expand Down
7 changes: 5 additions & 2 deletions repos/gems/src/app/window_layouter/user_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Window_layouter::User_state

struct Action : Interface
{
virtual bool visible(Window_id) = 0;
virtual void close(Window_id) = 0;
virtual void toggle_fullscreen(Window_id) = 0;
virtual void focus(Window_id) = 0;
Expand Down Expand Up @@ -296,6 +297,8 @@ void Window_layouter::User_state::_handle_event(Input::Event const &e,
if (e.press() && _key_cnt == 1)
_key_sequence_tracker.reset();

auto visible = [&] (Window_id id) { return _action.visible(id); };

_key_sequence_tracker.apply(e, config, [&] (Command const &command) {

switch (command.type) {
Expand All @@ -309,12 +312,12 @@ void Window_layouter::User_state::_handle_event(Input::Event const &e,
return;

case Command::NEXT_WINDOW:
_focused_window_id = _focus_history.next(_focused_window_id);
_focused_window_id = _focus_history.next(_focused_window_id, visible);
_action.focus(_focused_window_id);
return;

case Command::PREV_WINDOW:
_focused_window_id = _focus_history.prev(_focused_window_id);
_focused_window_id = _focus_history.prev(_focused_window_id, visible);
_action.focus(_focused_window_id);
return;

Expand Down

0 comments on commit 9855dcb

Please sign in to comment.