From d198f0789050f2372b115f3d0c42b04d6b1fba49 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Tue, 2 Feb 2016 20:10:37 +0100 Subject: [PATCH] window layouter: keyboard actions This patch adds support for manipulating the window layout with keyboard actions. It supports the toggling of fullscreen (aka maximize), the raising of the currently focused window, and the focusing the next/previous window. --- .../src/app/floating_window_layouter/README | 84 ++++++++++++ .../floating_window_layouter/focus_history.h | 123 ++++++++++++++++++ .../src/app/floating_window_layouter/main.cc | 8 +- .../app/floating_window_layouter/user_state.h | 41 +++++- .../src/app/floating_window_layouter/window.h | 9 +- 5 files changed, 259 insertions(+), 6 deletions(-) create mode 100644 repos/gems/src/app/floating_window_layouter/README create mode 100644 repos/gems/src/app/floating_window_layouter/focus_history.h diff --git a/repos/gems/src/app/floating_window_layouter/README b/repos/gems/src/app/floating_window_layouter/README new file mode 100644 index 000000000..ec7be2ab4 --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/README @@ -0,0 +1,84 @@ +The window-layouter component complements the window manager (wm) with the +policy of how windows are positioned on screen and how windows behave when the +user interacts with window elements like the maximize button or the window +title. Whereas the decorator defines how windows look, the layouter defines how +they behave. + +By default, the window layouter presents each window as a floating window that +can be positioned by dragging the window title, or resized by dragging the +window border. + + +Configurable window placement +----------------------------- + +The policy of the window layouter can be adjusted via its configuration. For +a given window label, the window's initial position and its maximized state +can be defined as follows: + +! +! +! +! + + +Keyboard shortcuts +------------------ + +The window layouter is able to respond to key sequences. However, normally, +the layouter is not a regular nitpicker client but receives only those +input events that refer to the window decorations. It never owns the keyboard +focus. In order to propagate global key sequences to the layouter, nitpicker +must be explicitly configured to direct key sequences initiated with certain +keys to the decorator. For example, the following nitpicker configuration +routes key sequences starting with the left windows key to the decorator. The +window manager, in turn, forwards those events to the layouter. + +! +! ... +! +! ... +! +! ... +! +! ... +! + +The response of the window layouter to key sequences can be expressed in the +layouter configuration as follows: + +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! + +Each '' node defines the policy when the specified 'key' is pressed. +If can be equipped with an 'action' attribute that triggers a window action. +The supported window actions are: + +:next_window: Focus the next window in the focus history. +:prev_window: Focus the previous window in the focus history. +:raise_window: Bring the focused window to the front. +:toggle_fullscreen: Maximize/unmaximize the focused window. + +By nesting '' nodes, actions can be tied to key sequences. In the +example above, the 'next_window' action is executed only if TAB is pressed +while the left windows-key is kept pressed. Furthermore, key sequences can +contain specific release events. In the example above, the release of the left +windows key brings the focused window to front, but only if TAB was pressed +before. + + diff --git a/repos/gems/src/app/floating_window_layouter/focus_history.h b/repos/gems/src/app/floating_window_layouter/focus_history.h new file mode 100644 index 000000000..905e450c7 --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/focus_history.h @@ -0,0 +1,123 @@ +/* + * \brief Focus history, used for swiching between recently focused windows + * \author Norman Feske + * \date 2016-02-02 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _FOCUS_HISTORY_H_ +#define _FOCUS_HISTORY_H_ + +/* Genode includes */ +#include + +namespace Floating_window_layouter { class Focus_history; } + + +class Floating_window_layouter::Focus_history +{ + public: + + struct Entry : Genode::List::Element + { + Focus_history &focus_history; + Window_id const window_id; + + Entry(Focus_history &focus_history, Window_id window_id); + + inline ~Entry(); + }; + + private: + + Genode::List _entries; + + Entry *_lookup(Window_id window_id) + { + for (Entry *e = _entries.first(); e; e = e->next()) + if (e->window_id == window_id) + return e; + + return nullptr; + } + + void _remove_if_present(Entry &entry) + { + _entries.remove(&entry); + } + + public: + + void focus(Window_id window_id) + { + Entry * const entry = _lookup(window_id); + if (!entry) { + PWRN("unexpected lookup failure for focus history entry"); + return; + } + + _remove_if_present(*entry); + + /* insert entry at the beginning (most recently focused) */ + _entries.insert(entry); + } + + Window_id next(Window_id window_id) + { + Entry * const first = _entries.first(); + if (!first) + return Window_id(); + + Entry * const entry = _lookup(window_id); + if (!entry) + return Window_id(); + + Entry * const next = entry->next(); + return next ? next->window_id : first->window_id; + } + + Window_id prev(Window_id window_id) + { + 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(); + } +}; + + +Floating_window_layouter::Focus_history::Entry::Entry(Focus_history &focus_history, + Window_id window_id) +: + focus_history(focus_history), window_id(window_id) +{ + focus_history._entries.insert(this); +} + + +Floating_window_layouter::Focus_history::Entry::~Entry() +{ + focus_history._remove_if_present(*this); +} + +#endif /* _FOCUS_HISTORY_H_ */ diff --git a/repos/gems/src/app/floating_window_layouter/main.cc b/repos/gems/src/app/floating_window_layouter/main.cc index b20d3da94..74e86e526 100644 --- a/repos/gems/src/app/floating_window_layouter/main.cc +++ b/repos/gems/src/app/floating_window_layouter/main.cc @@ -65,6 +65,8 @@ struct Floating_window_layouter::Main : Operations List windows; + Focus_history focus_history; + Window *lookup_window_by_id(Window_id const id) { for (Window *w = windows.first(); w; w = w->next()) @@ -95,7 +97,7 @@ struct Floating_window_layouter::Main : Operations fn(*w); } - User_state _user_state { *this }; + User_state _user_state { *this, focus_history }; /************************** @@ -129,6 +131,7 @@ struct Floating_window_layouter::Main : Operations void focus(Window_id id) override { + generate_window_layout_model(); generate_focus_model(); } @@ -354,7 +357,8 @@ void Floating_window_layouter::Main::import_window_list(Xml_node window_list_xml Window *win = lookup_window_by_id(id); if (!win) { - win = new (env()->heap()) Window(id, maximized_window_geometry); + win = new (env()->heap()) + Window(id, maximized_window_geometry, focus_history); windows.insert(win); Point initial_position(150*id % 800, 30 + (100*id % 500)); diff --git a/repos/gems/src/app/floating_window_layouter/user_state.h b/repos/gems/src/app/floating_window_layouter/user_state.h index 1bdcf68ef..9a8b81d4d 100644 --- a/repos/gems/src/app/floating_window_layouter/user_state.h +++ b/repos/gems/src/app/floating_window_layouter/user_state.h @@ -73,6 +73,8 @@ class Floating_window_layouter::User_state Operations &_operations; + Focus_history &_focus_history; + bool _is_key(Input::Event const &ev) const { if (ev.type() != Input::Event::PRESS @@ -107,6 +109,7 @@ class Floating_window_layouter::User_state _dragged_window_id = _hovered_window_id; _focused_window_id = _hovered_window_id; + _focus_history.focus(_focused_window_id); _operations.toggle_fullscreen(_hovered_window_id); return; @@ -118,6 +121,7 @@ class Floating_window_layouter::User_state if (_focused_window_id != _hovered_window_id) { _focused_window_id = _hovered_window_id; + _focus_history.focus(_focused_window_id); _operations.to_front(_hovered_window_id); _operations.focus(_hovered_window_id); @@ -129,7 +133,10 @@ class Floating_window_layouter::User_state public: - User_state(Operations &operations) : _operations(operations) { } + User_state(Operations &operations, Focus_history &focus_history) + : + _operations(operations), _focus_history(focus_history) + { } void handle_input(Input::Event const events[], unsigned num_events, Xml_node const &config) @@ -181,6 +188,7 @@ class Floating_window_layouter::User_state && _hovered_window_id != last_hovered_window_id) { _focused_window_id = _hovered_window_id; + _focus_history.focus(_focused_window_id); _operations.focus(_focused_window_id); } } @@ -220,6 +228,7 @@ void Floating_window_layouter::User_state::_handle_event(Input::Event const &e, if (e.type() == Input::Event::PRESS) _key_cnt++; if (e.type() == Input::Event::RELEASE) _key_cnt--; + /* handle pointer click */ if (e.type() == Input::Event::PRESS && e.keycode() == Input::BTN_LEFT && _key_cnt == 1) { @@ -276,15 +285,43 @@ void Floating_window_layouter::User_state::_handle_event(Input::Event const &e, _pointer_clicked, _pointer_curr); } + /* handle key sequences */ if (_is_key(e)) { if (e.type() == Input::Event::PRESS && _key_cnt == 1) _key_sequence_tracker.reset(); _key_sequence_tracker.apply(e, config, [&] (Action action) { - PINF("trigger action %d", action.type()); + + switch (action.type()) { + + case Action::TOGGLE_FULLSCREEN: + _operations.toggle_fullscreen(_focused_window_id); + return; + + case Action::RAISE_WINDOW: + _operations.to_front(_focused_window_id); + return; + + case Action::NEXT_WINDOW: + _focused_window_id = _focus_history.next(_focused_window_id); + _operations.focus(_focused_window_id); + return; + + case Action::PREV_WINDOW: + _focused_window_id = _focus_history.prev(_focused_window_id); + _operations.focus(_focused_window_id); + return; + + default: + PWRN("action %d unhanded", action.type()); + } }); } + + /* update focus history after key/button action is completed */ + if (e.type() == Input::Event::RELEASE && _key_cnt == 0) + _focus_history.focus(_focused_window_id); } #endif /* _USER_STATE_H_ */ diff --git a/repos/gems/src/app/floating_window_layouter/window.h b/repos/gems/src/app/floating_window_layouter/window.h index 78607aca0..158d33c54 100644 --- a/repos/gems/src/app/floating_window_layouter/window.h +++ b/repos/gems/src/app/floating_window_layouter/window.h @@ -16,6 +16,7 @@ /* local includes */ #include "types.h" +#include "focus_history.h" namespace Floating_window_layouter { class Window; } @@ -113,6 +114,8 @@ class Floating_window_layouter::Window : public List::Element */ unsigned _topped_cnt = 0; + Focus_history::Entry _focus_history_entry; + bool _drag_left_border = false; bool _drag_right_border = false; bool _drag_top_border = false; @@ -176,9 +179,11 @@ class Floating_window_layouter::Window : public List::Element public: - Window(Window_id id, Rect &maximized_geometry) + Window(Window_id id, Rect &maximized_geometry, + Focus_history &focus_history) : - _id(id), _maximized_geometry(maximized_geometry) + _id(id), _maximized_geometry(maximized_geometry), + _focus_history_entry(focus_history, _id) { } bool has_id(Window_id id) const { return id == _id; }