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; }