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 0000000000..ec7be2ab4e
--- /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 0000000000..905e450c79
--- /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 b20d3da946..74e86e526b 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 1bdcf68ef1..9a8b81d4d2 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 78607aca04..158d33c543 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; }