diff --git a/repos/gems/recipes/pkg/themed_wm/runtime b/repos/gems/recipes/pkg/themed_wm/runtime
index 14d5a90859..fdee664b81 100644
--- a/repos/gems/recipes/pkg/themed_wm/runtime
+++ b/repos/gems/recipes/pkg/themed_wm/runtime
@@ -13,7 +13,7 @@
-
+
diff --git a/repos/gems/recipes/pkg/wm/archives b/repos/gems/recipes/pkg/wm/archives
index cdbdb871c4..e52aacc360 100644
--- a/repos/gems/recipes/pkg/wm/archives
+++ b/repos/gems/recipes/pkg/wm/archives
@@ -2,4 +2,4 @@ _/raw/wm
_/src/wm
_/src/report_rom
_/src/decorator
-_/src/floating_window_layouter
+_/src/window_layouter
diff --git a/repos/gems/recipes/pkg/wm/runtime b/repos/gems/recipes/pkg/wm/runtime
index d1d81704ce..c01a562751 100644
--- a/repos/gems/recipes/pkg/wm/runtime
+++ b/repos/gems/recipes/pkg/wm/runtime
@@ -8,7 +8,7 @@
-
+
diff --git a/repos/gems/recipes/raw/wm/layouter.config b/repos/gems/recipes/raw/wm/layouter.config
index ce59459a9c..06aaf9ae3f 100644
--- a/repos/gems/recipes/raw/wm/layouter.config
+++ b/repos/gems/recipes/raw/wm/layouter.config
@@ -1,18 +1,25 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repos/gems/recipes/raw/wm/wm.config b/repos/gems/recipes/raw/wm/wm.config
index 0d29da8e59..cd558c071d 100644
--- a/repos/gems/recipes/raw/wm/wm.config
+++ b/repos/gems/recipes/raw/wm/wm.config
@@ -27,6 +27,7 @@
+
@@ -56,7 +57,7 @@
-
+
@@ -64,6 +65,7 @@
+
diff --git a/repos/gems/recipes/src/floating_window_layouter/content.mk b/repos/gems/recipes/src/window_layouter/content.mk
similarity index 84%
rename from repos/gems/recipes/src/floating_window_layouter/content.mk
rename to repos/gems/recipes/src/window_layouter/content.mk
index 16cc6f6f47..6584a2af41 100644
--- a/repos/gems/recipes/src/floating_window_layouter/content.mk
+++ b/repos/gems/recipes/src/window_layouter/content.mk
@@ -1,4 +1,4 @@
-SRC_DIR := src/app/floating_window_layouter
+SRC_DIR := src/app/window_layouter
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
DECORATOR_INCLUDES := $(addprefix include/decorator/,xml_utils.h types.h)
diff --git a/repos/gems/recipes/src/floating_window_layouter/hash b/repos/gems/recipes/src/window_layouter/hash
similarity index 100%
rename from repos/gems/recipes/src/floating_window_layouter/hash
rename to repos/gems/recipes/src/window_layouter/hash
diff --git a/repos/gems/recipes/src/floating_window_layouter/used_apis b/repos/gems/recipes/src/window_layouter/used_apis
similarity index 100%
rename from repos/gems/recipes/src/floating_window_layouter/used_apis
rename to repos/gems/recipes/src/window_layouter/used_apis
diff --git a/repos/gems/run/launcher.run b/repos/gems/run/launcher.run
index 54c7887ec3..e49469c090 100644
--- a/repos/gems/run/launcher.run
+++ b/repos/gems/run/launcher.run
@@ -159,7 +159,7 @@ install_config {
-
+
diff --git a/repos/gems/run/leitzentrale.run b/repos/gems/run/leitzentrale.run
index 731526712d..a32dac9db2 100644
--- a/repos/gems/run/leitzentrale.run
+++ b/repos/gems/run/leitzentrale.run
@@ -35,7 +35,7 @@ import_from_depot [depot_user]/src/[base_src] \
[depot_user]/src/coreutils-minimal \
[depot_user]/src/e2fsprogs-minimal \
[depot_user]/src/gpt_write \
- [depot_user]/src/floating_window_layouter
+ [depot_user]/src/window_layouter
install_config {
diff --git a/repos/gems/src/app/floating_window_layouter/README b/repos/gems/src/app/floating_window_layouter/README
deleted file mode 100644
index 6c111eadbe..0000000000
--- a/repos/gems/src/app/floating_window_layouter/README
+++ /dev/null
@@ -1,84 +0,0 @@
-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.
-It 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/main.cc b/repos/gems/src/app/floating_window_layouter/main.cc
deleted file mode 100644
index 0ab5ff8e01..0000000000
--- a/repos/gems/src/app/floating_window_layouter/main.cc
+++ /dev/null
@@ -1,611 +0,0 @@
-/*
- * \brief Floating window layouter
- * \author Norman Feske
- * \date 2013-02-14
- */
-
-/*
- * Copyright (C) 2013-2017 Genode Labs GmbH
- *
- * This file is part of the Genode OS framework, which is distributed
- * under the terms of the GNU Affero General Public License version 3.
- */
-
-/* Genode includes */
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-/* local includes */
-#include "window.h"
-#include "user_state.h"
-#include "operations.h"
-
-namespace Floating_window_layouter {
-
- struct Main;
-
- static Xml_node xml_lookup_window_by_id(Xml_node node, Window_id const id)
- {
- char const *tag = "window";
- char const *id_attr = "id";
-
- for (node = node.sub_node(tag); ; node = node.next(tag))
- if (attribute(node, id_attr, 0UL) == id.value)
- return node;
-
- throw Xml_node::Nonexistent_sub_node();
- }
-
-
- /**
- * Return true if compound XML node contains a sub node with ID
- */
- static bool xml_contains_window_node_with_id(Xml_node node,
- Window_id const id)
- {
- try { xml_lookup_window_by_id(node, id.value); return true; }
- catch (Xml_node::Nonexistent_sub_node) { return false; }
- }
-}
-
-
-struct Floating_window_layouter::Main : Operations
-{
- Genode::Env &env;
-
- Genode::Attached_rom_dataspace config { env, "config" };
-
- Genode::Signal_handler config_dispatcher {
- env.ep(), *this, &Main::handle_config };
-
- Genode::Heap heap { env.ram(), env.rm() };
-
- Genode::Tslab window_slab { &heap };
-
- List windows;
-
- Focus_history focus_history;
-
- void handle_config()
- {
- config.update();
- }
-
- Window *lookup_window_by_id(Window_id const id)
- {
- for (Window *w = windows.first(); w; w = w->next())
- if (w->has_id(id))
- return w;
-
- return nullptr;
- }
-
- Window const *lookup_window_by_id(Window_id const id) const
- {
- for (Window const *w = windows.first(); w; w = w->next())
- if (w->has_id(id))
- return w;
-
- return nullptr;
- }
-
- /**
- * Apply functor to each window
- *
- * The functor is called with 'Window const &' as argument.
- */
- template
- void for_each_window(FUNC const &fn) const
- {
- for (Window const *w = windows.first(); w; w = w->next())
- fn(*w);
- }
-
- User_state _user_state { *this, focus_history };
-
-
- /**************************
- ** Operations interface **
- **************************/
-
- void close(Window_id id) override
- {
- Window *window = lookup_window_by_id(id);
- if (!window)
- return;
-
- window->close();
- generate_resize_request_model();
- generate_focus_model();
- }
-
- void to_front(Window_id id) override
- {
- Window *window = lookup_window_by_id(id);
- if (!window)
- return;
-
- if (window != windows.first()) {
- windows.remove(window);
- windows.insert(window);
- window->topped();
- generate_window_layout_model();
- }
- }
-
- void focus(Window_id id) override
- {
- generate_window_layout_model();
- generate_focus_model();
- }
-
- void toggle_fullscreen(Window_id id) override
- {
- /* make sure that the specified window is the front-most one */
- to_front(id);
-
- Window *window = lookup_window_by_id(id);
- if (!window)
- return;
-
- window->maximized(!window->maximized());
-
- generate_resize_request_model();
- }
-
- void drag(Window_id id, Window::Element element, Point clicked, Point curr) override
- {
- to_front(id);
-
- Window *window = lookup_window_by_id(id);
- if (!window)
- return;
-
- /*
- * The drag operation may result in a change of the window geometry.
- * We detect such a change be comparing the original geometry with
- * the geometry with the drag operation applied.
- */
- Point const last_pos = window->position();
- Area const last_requested_size = window->requested_size();
-
- window->drag(element, clicked, curr);
-
- if (last_pos != window->position())
- generate_window_layout_model();
-
- if (last_requested_size != window->requested_size())
- generate_resize_request_model();
- }
-
- void finalize_drag(Window_id id, Window::Element, Point clicked, Point final)
- {
- Window *window = lookup_window_by_id(id);
- if (!window)
- return;
-
- window->finalize_drag_operation();
-
- /*
- * Update window layout because highlighting may have changed after the
- * drag operation. E.g., if the window has not kept up with the
- * dragging of a resize handle, the resize handle is no longer hovered.
- */
- generate_window_layout_model();
- }
-
-
- /**
- * Install handler for responding to window-list changes
- */
- void handle_window_list_update();
-
- Signal_handler window_list_dispatcher = {
- env.ep(), *this, &Main::handle_window_list_update };
-
- Attached_rom_dataspace window_list { env, "window_list" };
-
-
- /**
- * Install handler for responding to focus requests
- */
- void handle_focus_request_update();
-
- void _apply_focus_request();
-
- int handled_focus_request_id = 0;
-
- Signal_handler focus_request_dispatcher = {
- env.ep(), *this, &Main::handle_focus_request_update };
-
- Attached_rom_dataspace focus_request { env, "focus_request" };
-
-
- /**
- * Install handler for responding to hover changes
- */
- void handle_hover_update();
-
- Signal_handler hover_dispatcher = {
- env.ep(), *this, &Main::handle_hover_update };
-
- Attached_rom_dataspace hover { env, "hover" };
-
-
- /**
- * Respond to decorator-margins information reported by the decorator
- */
- Attached_rom_dataspace decorator_margins { env, "decorator_margins" };
-
- void handle_decorator_margins_update()
- {
- decorator_margins.update();
-
- /* respond to change by adapting the maximized window geometry */
- handle_mode_change();
- }
-
- Signal_handler decorator_margins_dispatcher = {
- env.ep(), *this, &Main::handle_decorator_margins_update };
-
-
- /**
- * Install handler for responding to user input
- */
- void handle_input()
- {
- while (input.pending())
- _user_state.handle_input(input_ds.local_addr(),
- input.flush(), config.xml());
- }
-
- Signal_handler input_dispatcher {
- env.ep(), *this, &Main::handle_input };
-
- Nitpicker::Connection nitpicker { env };
-
- Rect maximized_window_geometry;
-
- void handle_mode_change()
- {
- /* determine maximized window geometry */
- Framebuffer::Mode const mode = nitpicker.mode();
-
- /* read decorator margins from the decorator's report */
- unsigned top = 0, bottom = 0, left = 0, right = 0;
- try {
- Xml_node const floating_xml = decorator_margins.xml().sub_node("floating");
-
- top = attribute(floating_xml, "top", 0UL);
- bottom = attribute(floating_xml, "bottom", 0UL);
- left = attribute(floating_xml, "left", 0UL);
- right = attribute(floating_xml, "right", 0UL);
-
- } catch (...) { };
-
- maximized_window_geometry = Rect(Point(left, top),
- Area(mode.width() - left - right,
- mode.height() - top - bottom));
- }
-
- Signal_handler mode_change_dispatcher = {
- env.ep(), *this, &Main::handle_mode_change };
-
-
- Input::Session_client input { env.rm(), nitpicker.input_session() };
-
- Attached_dataspace input_ds { env.rm(), input.dataspace() };
-
- Reporter window_layout_reporter = { env, "window_layout" };
- Reporter resize_request_reporter = { env, "resize_request" };
- Reporter focus_reporter = { env, "focus" };
-
-
- bool focused_window_maximized() const
- {
- Window const *w = lookup_window_by_id(_user_state.focused_window_id());
- return w && w->maximized();
- }
-
- void import_window_list(Xml_node);
- void generate_window_layout_model();
- void generate_resize_request_model();
- void generate_focus_model();
-
- /**
- * Constructor
- */
- Main(Genode::Env &env) : env(env)
- {
- nitpicker.mode_sigh(mode_change_dispatcher);
- handle_mode_change();
-
- window_list.sigh(window_list_dispatcher);
- focus_request.sigh(focus_request_dispatcher);
-
- hover.sigh(hover_dispatcher);
- decorator_margins.sigh(decorator_margins_dispatcher);
- input.sigh(input_dispatcher);
-
- window_layout_reporter.enabled(true);
- resize_request_reporter.enabled(true);
- focus_reporter.enabled(true);
-
- /* import initial state */
- handle_window_list_update();
-
- /* attach update handler for config */
- config.sigh(config_dispatcher);
- }
-};
-
-
-void Floating_window_layouter::Main::import_window_list(Xml_node window_list_xml)
-{
- char const *tag = "window";
-
- /*
- * Remove windows from layout that are no longer in the window list
- */
- for (Window *win = windows.first(), *next = 0; win; win = next) {
- next = win->next();
- if (!xml_contains_window_node_with_id(window_list_xml, win->id())) {
- windows.remove(win);
- destroy(window_slab, win);
- }
- }
-
- /*
- * Update window attributes, add new windows to the layout
- */
- try {
- for (Xml_node node = window_list_xml.sub_node(tag); ; node = node.next(tag)) {
-
- unsigned long id = 0;
- node.attribute("id").value(&id);
-
- Area const initial_size = area_attribute(node);
-
- Window *win = lookup_window_by_id(id);
- if (!win) {
- win = new (window_slab)
- Window(id, maximized_window_geometry, initial_size, focus_history);
- windows.insert(win);
-
- Point initial_position(150*id % 800, 30 + (100*id % 500));
-
- Window::Label const label = string_attribute(node, "label", Window::Label(""));
- win->label(label);
-
- /*
- * Evaluate policy configuration for the window label
- */
- try {
- Session_policy const policy(label, config.xml());
-
- if (policy.has_attribute("xpos") && policy.has_attribute("ypos"))
- initial_position = point_attribute(policy);
-
- win->maximized(policy.attribute_value("maximized", false));
-
- } catch (Genode::Session_policy::No_policy_defined) { }
-
- win->position(initial_position);
- }
-
- win->size(initial_size);
- win->title(string_attribute(node, "title", Window::Title("")));
- win->has_alpha( node.attribute_value("has_alpha", false));
- win->hidden( node.attribute_value("hidden", false));
- win->resizeable(node.attribute_value("resizeable", false));
- }
- } catch (...) { }
-}
-
-
-void Floating_window_layouter::Main::generate_window_layout_model()
-{
- Reporter::Xml_generator xml(window_layout_reporter, [&] ()
- {
- for (Window *w = windows.first(); w; w = w->next()) {
-
- bool const hovered = w->has_id(_user_state.hover_state().window_id);
- bool const focused = w->has_id(_user_state.focused_window_id());
-
- Window::Element const highlight =
- hovered ? _user_state.hover_state().element : Window::Element::UNDEFINED;
-
- w->serialize(xml, focused, highlight);
- }
- });
-}
-
-
-void Floating_window_layouter::Main::generate_resize_request_model()
-{
- Reporter::Xml_generator xml(resize_request_reporter, [&] ()
- {
- for_each_window([&] (Window const &window) {
-
- Area const requested_size = window.requested_size();
- if (requested_size != window.size()) {
- xml.node("window", [&] () {
- xml.attribute("id", window.id().value);
- xml.attribute("width", requested_size.w());
- xml.attribute("height", requested_size.h());
- });
- }
- });
- });
-}
-
-
-void Floating_window_layouter::Main::generate_focus_model()
-{
- Reporter::Xml_generator xml(focus_reporter, [&] ()
- {
- xml.node("window", [&] () {
- xml.attribute("id", _user_state.focused_window_id().value);
- });
- });
-}
-
-
-/**
- * Determine window element that corresponds to hover model
- */
-static Floating_window_layouter::Window::Element
-element_from_hover_model(Genode::Xml_node hover_window_xml)
-{
- typedef Floating_window_layouter::Window::Element::Type Type;
-
- bool const left_sizer = hover_window_xml.has_sub_node("left_sizer"),
- right_sizer = hover_window_xml.has_sub_node("right_sizer"),
- top_sizer = hover_window_xml.has_sub_node("top_sizer"),
- bottom_sizer = hover_window_xml.has_sub_node("bottom_sizer");
-
- if (left_sizer && top_sizer) return Type::TOP_LEFT;
- if (left_sizer && bottom_sizer) return Type::BOTTOM_LEFT;
- if (left_sizer) return Type::LEFT;
-
- if (right_sizer && top_sizer) return Type::TOP_RIGHT;
- if (right_sizer && bottom_sizer) return Type::BOTTOM_RIGHT;
- if (right_sizer) return Type::RIGHT;
-
- if (top_sizer) return Type::TOP;
- if (bottom_sizer) return Type::BOTTOM;
-
- if (hover_window_xml.has_sub_node("title")) return Type::TITLE;
- if (hover_window_xml.has_sub_node("closer")) return Type::CLOSER;
- if (hover_window_xml.has_sub_node("maximizer")) return Type::MAXIMIZER;
- if (hover_window_xml.has_sub_node("minimizer")) return Type::MINIMIZER;
-
- return Type::UNDEFINED;
-}
-
-
-void Floating_window_layouter::Main::handle_window_list_update()
-{
- window_list.update();
-
- try {
- import_window_list(window_list.xml()); }
- catch (...) {
- Genode::error("could not import window list"); }
-
- generate_window_layout_model();
-}
-
-
-void Floating_window_layouter::Main::_apply_focus_request()
-{
- Window::Label const label =
- focus_request.xml().attribute_value("label", Window::Label(""));
-
- int const id = focus_request.xml().attribute_value("id", 0L);
-
- /* don't apply the same focus request twice */
- if (id == handled_focus_request_id)
- return;
-
- bool focus_redefined = false;
-
- /*
- * Move all windows that match the requested label to the front while
- * maintaining their ordering.
- */
- Window *at = nullptr;
- for (Window *w = windows.first(); w; w = w->next()) {
-
- if (!w->label_matches(label))
- continue;
-
- focus_redefined = true;
-
- /*
- * Move window to behind the previous window that we moved to
- * front. If 'w' is the first window that matches the selector,
- * move it to the front ('at' argument of 'insert' is 0).
- */
- windows.remove(w);
- windows.insert(w, at);
-
- /*
- * Bring top-most window to the front of nitpicker's global view
- * stack and set the focus to the top-most window.
- */
- if (at == nullptr) {
- w->topped();
-
- _user_state.focused_window_id(w->id());
- generate_focus_model();
- }
-
- at = w;
- }
-
- if (focus_redefined)
- handled_focus_request_id = id;
-}
-
-
-void Floating_window_layouter::Main::handle_focus_request_update()
-{
- focus_request.update();
-
- _apply_focus_request();
-
- generate_window_layout_model();
-}
-
-
-void Floating_window_layouter::Main::handle_hover_update()
-{
- hover.update();
-
- try {
- Xml_node const hover_window_xml = hover.xml().sub_node("window");
-
- _user_state.hover(attribute(hover_window_xml, "id", 0UL),
- element_from_hover_model(hover_window_xml));
- }
-
- /*
- * An exception may occur during the 'Xml_node' construction if the hover
- * model is malformed. Under this condition, we invalidate the hover state.
- */
- catch (...) {
-
- _user_state.reset_hover();
-
- /*
- * Don't generate a focus-model update here. In a situation where the
- * pointer has moved over a native nitpicker view (outside the realm of
- * the window manager), the hover model as generated by the decorator
- * naturally becomes empty. If we posted a focus update, this would
- * steal the focus away from the native nitpicker view.
- */
- }
-
- /* propagate changed hovering to the decorator */
- generate_window_layout_model();
-}
-
-
-/***************
- ** Component **
- ***************/
-
-void Component::construct(Genode::Env &env) {
- static Floating_window_layouter::Main application(env); }
diff --git a/repos/gems/src/app/floating_window_layouter/target.mk b/repos/gems/src/app/floating_window_layouter/target.mk
deleted file mode 100644
index 1002ce1123..0000000000
--- a/repos/gems/src/app/floating_window_layouter/target.mk
+++ /dev/null
@@ -1,6 +0,0 @@
-TARGET = floating_window_layouter
-SRC_CC = main.cc
-LIBS = base
-
-
-CC_CXX_WARN_STRICT =
diff --git a/repos/gems/src/app/window_layouter/README b/repos/gems/src/app/window_layouter/README
new file mode 100644
index 0000000000..fd1a674553
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/README
@@ -0,0 +1,164 @@
+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.
+
+
+Layout rules
+------------
+
+The window layouter positions windows according to rules defined by the
+component's configuration. The rules consist of two parts, the definition
+of the screen's layout and the assignment of client windows to the defined
+parts of the screen's layout.
+
+!
+!
+!
+! ...definition of screen layout...
+!
+!
+! ,,,
+!
+! ...
+!
+
+The '' node can host any number of '' nodes, which partition
+the screen horizontally into columns. By default, each column has the same
+size. By specifying an optional 'weight' attribute, column sizes can be
+weighted relative to one another. The default weight is '1'. Alternatively,
+the 'width' of a column can be explicitly specified in pixels.
+Each column can host any number of '' nodes, which subdivide the column
+vertically. Analogously to columns, rows can be dimensioned via an optional
+'weight' attribute or an explicit 'height' in pixels. A '' can, in turn,
+contain '' nodes, thereby further subdividing the screen.
+Each '' or '' can be used as window-placement target when
+equipped with a 'name' attribute. Each name must occur only once within
+the ''. In the following, a named column or row is referred to as
+_target_. Each target can host an optional 'layer' attribute. If not
+specified, the layer 9999 is assumed. A target with a lower layer overlaps
+targets with higher layers.
+
+The assignment of windows to targets is defined via '' nodes. Each
+'' node must be equipped with a 'label', 'label_prefix', or
+'label_suffix' attribute, which is used to match window labels. For a given
+window, the first matching '' node takes effect.
+
+Each '' node must have a 'target' attribute that refers to the name
+of a column or row. By default, the window is sized to fit the target area.
+However, it is possible to position the window relative to the target area by
+specifying the 'xpos', 'ypos', 'width', and 'height' attributes together with
+the 'maximized="no"' attribute.
+
+If multiple windows are assigned to the same target area, the order of their
+'' rules defines their stacking order. The window with earliest
+'' rule is displayed in front.
+
+
+Dynamic layouts
+---------------
+
+The window layouter is able to respond to rule changes at runtime.
+
+By specifying the '' attribute 'rules="rom"', the window layouter
+tries to obtain the layout rules from a distinct ROM module. Should the ROM
+module not contain valid rules, the '' sub node of the '' comes
+into effect.
+
+Any window-layout change such as the movement of a floating window is
+realized as a change of the window-layout rules. To support interactive
+adjustments of the window layout, the layouter responds to certain user
+interactions by generating new rules by itself in the form of a "rules"
+report. The generation of such rules can be enabled via the '' sub
+node of the configuration:
+
+!
+!
+! ...
+!
+
+By feeding back the rules generated by the window layouter into the window
+layouter itself via a 'report_rom' service, the window layout becomes
+adjustable interactively. As the rules entail the complete state of the
+present window layout, it is possible to save/restore the layout state.
+
+
+Dynamic rule-generation mechanism
+---------------------------------
+
+Whenever a new window appears that solely matches a wildcard '' rule
+(one that uses a 'label_prefix' or 'label_suffix'), the layouter generates a
+new '' rule with the window's label as 'label' attribute. The
+explicitly labeled '' rules appear before any wildcard ''
+rules.
+
+If the user brings a window to front, the window layouter will change the order
+of the explicit '' rules such that the window's '' rule comes
+first. When moving or resizing a window, the 'xpos', 'ypos', 'width', and
+'height' attribute of the window's assign rule are updated. When maximizing
+or unmaximizing a window, the 'maximized' attribute of its '' rule is
+toggled.
+
+
+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.
+It 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/action.h b/repos/gems/src/app/window_layouter/action.h
similarity index 93%
rename from repos/gems/src/app/floating_window_layouter/action.h
rename to repos/gems/src/app/window_layouter/action.h
index 1c137ac609..2ef592482d 100644
--- a/repos/gems/src/app/floating_window_layouter/action.h
+++ b/repos/gems/src/app/window_layouter/action.h
@@ -14,13 +14,13 @@
#ifndef _ACTION_H_
#define _ACTION_H_
-namespace Floating_window_layouter { class Action; }
+namespace Window_layouter { class Action; }
/**
* Result of the application of a key event to the key-sequence tracker
*/
-class Floating_window_layouter::Action
+class Window_layouter::Action
{
public:
diff --git a/repos/gems/src/app/window_layouter/assign.h b/repos/gems/src/app/window_layouter/assign.h
new file mode 100644
index 0000000000..c70c02eb2a
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/assign.h
@@ -0,0 +1,205 @@
+/*
+ * \brief Assignment of window-manager clients to target area
+ * \author Norman Feske
+ * \date 2018-09-27
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _ASSIGN_H_
+#define _ASSIGN_H_
+
+/* Genode includes */
+#include
+#include
+#include
+
+/* local includes */
+#include
+#include
+#include
+
+namespace Window_layouter { class Assign; }
+
+
+class Window_layouter::Assign : public List_model::Element
+{
+ public:
+
+ /*
+ * Used for associating windows with assignments. Hosted in 'Window'
+ * objects.
+ */
+ struct Member : Registry::Element
+ {
+ Window &window;
+
+ Member(Registry ®istry, Window &window)
+ : Registry::Element(registry, *this), window(window) { }
+ };
+
+ typedef String<80> Label;
+
+ private:
+
+ Registry _members { };
+
+ Label const _label;
+ Label const _label_prefix;
+ Label const _label_suffix;
+
+ Target::Name _target_name { };
+
+ bool _pos_defined = false;
+ bool _xpos_any = false;
+ bool _ypos_any = false;
+ bool _size_defined = false;
+ bool _maximized = false;
+
+ Point _pos { };
+ Area _size { };
+
+ public:
+
+ Assign(Xml_node assign)
+ :
+ _label (assign.attribute_value("label", Label())),
+ _label_prefix(assign.attribute_value("label_prefix", Label())),
+ _label_suffix(assign.attribute_value("label_suffix", Label()))
+ { }
+
+ void update(Xml_node assign)
+ {
+ _target_name = assign.attribute_value("target", Target::Name());
+ _pos_defined = assign.has_attribute("xpos") && assign.has_attribute("ypos");
+ _size_defined = assign.has_attribute("width") && assign.has_attribute("height");
+ _maximized = assign.attribute_value("maximized", false);
+ _xpos_any = assign.attribute_value("xpos", String<20>()) == "any";
+ _ypos_any = assign.attribute_value("ypos", String<20>()) == "any";
+ _pos = point_attribute(assign);
+ _size = area_attribute(assign);
+ }
+
+ /*
+ * Used by 'Assign_list::update_from_xml'
+ */
+ bool matches(Xml_node node) const
+ {
+ return node.attribute_value("label", Label()) == _label
+ && node.attribute_value("label_prefix", Label()) == _label_prefix
+ && node.attribute_value("label_suffix", Label()) == _label_suffix;
+ }
+
+ /**
+ * Calculate window geometry
+ */
+ Rect window_geometry(unsigned win_id, Area client_size, Rect target_geometry,
+ Decorator_margins const &decorator_margins) const
+ {
+ if (_maximized || !_pos_defined)
+ return target_geometry;
+
+ Point const any_pos(150*win_id % 800, 30 + (100*win_id % 500));
+
+ Point const pos(_xpos_any ? any_pos.x() : _pos.x(),
+ _ypos_any ? any_pos.y() : _pos.y());
+
+ /* floating non-maximized window */
+ Rect const inner(pos, _size_defined ? _size : client_size);
+ Rect const outer = decorator_margins.outer_geometry(inner);
+
+ return Rect(outer.p1() + target_geometry.p1(), outer.area());
+ }
+
+ /**
+ * Call 'fn' with 'Registry' if label matches assignment
+ *
+ * This method is used for associating assignments to windows.
+ */
+ template
+ void with_matching_members_registry(Label const &label, FN const &fn)
+ {
+ bool const label_matches = (_label.valid() && label == _label);
+
+ bool const prefix_matches =
+ _label_prefix.valid() &&
+ !strcmp(label.string(),
+ _label_prefix.string(), _label_prefix.length() - 1);
+
+ bool suffix_matches = false;
+ if (label.length() >= _label_suffix.length()) {
+ unsigned const offset = label.length() - _label_suffix.length();
+ suffix_matches = !strcmp(_label.string() + offset, _label_suffix.string());
+ }
+
+ bool const wildcard_matches = !_label.valid()
+ && (!_label_prefix.valid() || prefix_matches)
+ && (!_label_suffix.valid() || suffix_matches);
+
+ if (label_matches || wildcard_matches)
+ fn(_members);
+ }
+
+ Target::Name target_name() const { return _target_name; }
+
+ /**
+ * Generate nodes of windows captured via wildcard
+ */
+ template
+ void for_each_wildcard_member(FN const &fn) const
+ {
+ /* skip non-wildcards */
+ if (_label.valid())
+ return;
+
+ _members.for_each([&] (Assign::Member const &member) { fn(member); });
+ }
+
+ bool floating() const { return _pos_defined; }
+
+ bool wildcard() const { return !_label.valid(); }
+
+ /**
+ * Generate node
+ */
+ void gen_assign_attr(Xml_generator &xml) const
+ {
+ if (_label.valid()) xml.attribute("label", _label);
+ if (_label_prefix.valid()) xml.attribute("label_prefix", _label_prefix);
+ if (_label_suffix.valid()) xml.attribute("label_suffix", _label_suffix);
+
+ xml.attribute("target", _target_name);
+ }
+
+ void gen_geometry_attr(Xml_generator &xml) const
+ {
+ if (_pos_defined) {
+ if (_xpos_any) xml.attribute("xpos", "any");
+ else xml.attribute("xpos", _pos.x());
+
+ if (_ypos_any) xml.attribute("ypos", "any");
+ else xml.attribute("ypos", _pos.y());
+ }
+
+ if (_size_defined) {
+ xml.attribute("width", _size.w());
+ xml.attribute("height", _size.h());
+ }
+
+ if (_maximized)
+ xml.attribute("maximized", true);
+ }
+
+ template
+ void for_each_member(FN const &fn) { _members.for_each(fn); }
+
+ template
+ void for_each_member(FN const &fn) const { _members.for_each(fn); }
+};
+
+#endif /* _ASSIGN_H_ */
diff --git a/repos/gems/src/app/window_layouter/assign_list.h b/repos/gems/src/app/window_layouter/assign_list.h
new file mode 100644
index 0000000000..63fa84bbae
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/assign_list.h
@@ -0,0 +1,114 @@
+/*
+ * \brief List of assignments
+ * \author Norman Feske
+ * \date 2018-09-27
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _ASSIGN_LIST_H_
+#define _ASSIGN_LIST_H_
+
+/* local includes */
+#include
+#include
+
+namespace Window_layouter { class Assign_list; }
+
+
+class Window_layouter::Assign_list : Noncopyable
+{
+ private:
+
+ Allocator &_alloc;
+
+ List_model _assignments { };
+
+ struct Update_policy : List_model::Update_policy
+ {
+ Allocator &_alloc;
+
+ Update_policy(Allocator &alloc) : _alloc(alloc) { }
+
+ void destroy_element(Assign &elem) { destroy(_alloc, &elem); }
+
+ Assign &create_element(Xml_node node)
+ {
+ return *new (_alloc) Assign(node);
+ }
+
+ void update_element(Assign &assign, Xml_node node)
+ {
+ assign.update(node);
+ }
+
+ static bool element_matches_xml_node(Assign const &elem, Xml_node node)
+ {
+ return elem.matches(node);
+ }
+
+ bool node_is_element(Xml_node node)
+ {
+ return node.has_type("assign");
+ }
+ };
+
+ public:
+
+ Assign_list(Allocator &alloc) : _alloc(alloc) { }
+
+ void update_from_xml(Xml_node node)
+ {
+ Update_policy policy(_alloc);
+ _assignments.update_from_xml(policy, node);
+ }
+
+ void assign_windows(Window_list &windows)
+ {
+ _assignments.for_each([&] (Assign &assign) {
+
+ windows.for_each_window([&] (Window &window) {
+
+ auto fn = [&] (Registry ®istry) {
+ window.assignment(registry); };
+
+ assign.with_matching_members_registry(window.label(), fn);
+ });
+ });
+ }
+
+ template
+ void for_each_wildcard_member(FN const &fn) const
+ {
+ _assignments.for_each([&] (Assign const &assign) {
+ assign.for_each_wildcard_member([&] (Assign::Member const &member) {
+ fn(assign, member); }); });
+ }
+
+ /**
+ * Return true if any window is assigned via a wildcard
+ *
+ * In this case, a new 'rules' report should be generated that turns
+ * the wildcard matches into exact assignments.
+ */
+ bool matching_wildcards() const
+ {
+ bool result = false;
+ for_each_wildcard_member([&] (Assign const &, Assign::Member const &) {
+ result = true; });
+ return result;
+ }
+
+ template
+ void for_each(FN const &fn) { _assignments.for_each(fn); }
+
+ template
+ void for_each(FN const &fn) const { _assignments.for_each(fn); }
+};
+
+#endif /* _ASSIGN_LIST_H_ */
diff --git a/repos/gems/src/app/window_layouter/decorator_margins.h b/repos/gems/src/app/window_layouter/decorator_margins.h
new file mode 100644
index 0000000000..ad21e6b1af
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/decorator_margins.h
@@ -0,0 +1,54 @@
+/*
+ * \brief Decoration size information
+ * \author Norman Feske
+ * \date 2018-09-28
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _DECORATOR_MARGINS_H_
+#define _DECORATOR_MARGINS_H_
+
+/* local includes */
+#include
+
+namespace Window_layouter { struct Decorator_margins; }
+
+
+struct Window_layouter::Decorator_margins
+{
+ unsigned top, bottom, left, right;
+
+ Decorator_margins(Xml_node node)
+ :
+ top (node.attribute_value("top", 0UL)),
+ bottom(node.attribute_value("bottom", 0UL)),
+ left (node.attribute_value("left", 0UL)),
+ right (node.attribute_value("right", 0UL))
+ { }
+
+ /**
+ * Convert outer geometry to inner geometry
+ */
+ Rect inner_geometry(Rect outer) const
+ {
+ return Rect(outer.p1() + Point(left, top),
+ outer.p2() - Point(right, bottom));
+ }
+
+ /**
+ * Convert inner geometry to outer geometry
+ */
+ Rect outer_geometry(Rect inner) const
+ {
+ return Rect(inner.p1() - Point(left, top),
+ inner.p2() + Point(right, bottom));
+ }
+};
+
+#endif /* _DECORATOR_MARGINS_H_ */
diff --git a/repos/gems/src/app/floating_window_layouter/focus_history.h b/repos/gems/src/app/window_layouter/focus_history.h
similarity index 82%
rename from repos/gems/src/app/floating_window_layouter/focus_history.h
rename to repos/gems/src/app/window_layouter/focus_history.h
index 8fad8d8d7a..3cd8af3d0c 100644
--- a/repos/gems/src/app/floating_window_layouter/focus_history.h
+++ b/repos/gems/src/app/window_layouter/focus_history.h
@@ -17,14 +17,14 @@
/* Genode includes */
#include
-namespace Floating_window_layouter { class Focus_history; }
+namespace Window_layouter { class Focus_history; }
-class Floating_window_layouter::Focus_history
+class Window_layouter::Focus_history
{
public:
- struct Entry : Genode::List::Element
+ struct Entry : List::Element
{
Focus_history &focus_history;
Window_id const window_id;
@@ -36,7 +36,7 @@ class Floating_window_layouter::Focus_history
private:
- Genode::List _entries;
+ List _entries { };
Entry *_lookup(Window_id window_id)
{
@@ -58,7 +58,7 @@ class Floating_window_layouter::Focus_history
{
Entry * const entry = _lookup(window_id);
if (!entry) {
- Genode::warning("unexpected lookup failure for focus history entry");
+ warning("unexpected lookup failure for focus history entry");
return;
}
@@ -106,8 +106,8 @@ class Floating_window_layouter::Focus_history
};
-Floating_window_layouter::Focus_history::Entry::Entry(Focus_history &focus_history,
- Window_id window_id)
+Window_layouter::Focus_history::Entry::Entry(Focus_history &focus_history,
+ Window_id window_id)
:
focus_history(focus_history), window_id(window_id)
{
@@ -115,7 +115,7 @@ Floating_window_layouter::Focus_history::Entry::Entry(Focus_history &focus_histo
}
-Floating_window_layouter::Focus_history::Entry::~Entry()
+Window_layouter::Focus_history::Entry::~Entry()
{
focus_history._remove_if_present(*this);
}
diff --git a/repos/gems/src/app/floating_window_layouter/key_sequence_tracker.h b/repos/gems/src/app/window_layouter/key_sequence_tracker.h
similarity index 94%
rename from repos/gems/src/app/floating_window_layouter/key_sequence_tracker.h
rename to repos/gems/src/app/window_layouter/key_sequence_tracker.h
index 30c40bf850..a2601d922c 100644
--- a/repos/gems/src/app/floating_window_layouter/key_sequence_tracker.h
+++ b/repos/gems/src/app/window_layouter/key_sequence_tracker.h
@@ -17,10 +17,10 @@
/* local includes */
#include "action.h"
-namespace Floating_window_layouter { class Key_sequence_tracker; }
+namespace Window_layouter { class Key_sequence_tracker; }
-class Floating_window_layouter::Key_sequence_tracker
+class Window_layouter::Key_sequence_tracker
{
private:
@@ -75,7 +75,7 @@ class Floating_window_layouter::Key_sequence_tracker
entries[pos++] = entry;
if (pos == MAX_ENTRIES) {
- Genode::warning("too long key sequence, dropping information");
+ warning("too long key sequence, dropping information");
pos = MAX_ENTRIES - 1;
}
}
@@ -109,14 +109,14 @@ class Floating_window_layouter::Key_sequence_tracker
void reset() { pos = 0; }
};
- Stack _stack;
+ Stack _stack { };
Xml_node _matching_sub_node(Xml_node curr, Stack::Entry entry)
{
char const *node_type = entry.type == Stack::Entry::PRESS
? "press" : "release";
- typedef Genode::String<32> Key_name;
+ typedef String<32> Key_name;
Key_name const key(Input::key_name(entry.keycode));
Xml_node result("");
@@ -179,7 +179,7 @@ class Floating_window_layouter::Key_sequence_tracker
typedef String<32> Action;
Action action = node.attribute_value("action", Action());
- func(Floating_window_layouter::Action(action));
+ func(Window_layouter::Action(action));
}
public:
diff --git a/repos/gems/src/app/window_layouter/layout_rules.h b/repos/gems/src/app/window_layouter/layout_rules.h
new file mode 100644
index 0000000000..230458d4d1
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/layout_rules.h
@@ -0,0 +1,125 @@
+/*
+ * \brief Layout rules
+ * \author Norman Feske
+ * \date 2018-09-26
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _LAYOUT_RULES_H_
+#define _LAYOUT_RULES_H_
+
+/* Genode includes */
+#include
+
+/* local includes */
+#include
+#include
+
+namespace Window_layouter { class Layout_rules; }
+
+
+class Window_layouter::Layout_rules : Noncopyable
+{
+ public:
+
+ struct Change_handler : Interface
+ {
+ virtual void layout_rules_changed() = 0;
+ };
+
+ private:
+
+ Env &_env;
+
+ Allocator &_alloc;
+
+ Change_handler &_change_handler;
+
+ Constructible _config_rules { };
+
+ struct Rom_rules
+ {
+ static char const *node_type() { return "rules"; }
+
+ Attached_rom_dataspace rom;
+
+ Change_handler &_change_handler;
+
+ void _handle()
+ {
+ rom.update();
+ _change_handler.layout_rules_changed();
+ }
+
+ Signal_handler _handler;
+
+ Rom_rules(Env &env, Change_handler &change_handler)
+ :
+ rom(env, node_type()), _change_handler(change_handler),
+ _handler(env.ep(), *this, &Rom_rules::_handle)
+ {
+ rom.sigh(_handler);
+ _handle();
+ }
+ };
+
+ Constructible _rom_rules { };
+
+ public:
+
+ Layout_rules(Env &env, Allocator &alloc, Change_handler &change_handler)
+ :
+ _env(env), _alloc(alloc), _change_handler(change_handler)
+ { }
+
+ void update_config(Xml_node config)
+ {
+ bool const use_rules_from_rom =
+ (config.attribute_value(Rom_rules::node_type(), String<10>()) == "rom");
+
+ _rom_rules.conditional(use_rules_from_rom, _env, _change_handler);
+
+ _config_rules.destruct();
+
+ if (config.has_sub_node(Rom_rules::node_type()))
+ _config_rules.construct(_alloc, config.sub_node(Rom_rules::node_type()));
+
+ _change_handler.layout_rules_changed();
+ }
+
+ /**
+ * Call 'fn' with XML node of active layout rules as argument
+ *
+ * The rules are either provided as a dedicated "rules" ROM module
+ * or as '' sub node of the configuration. The former is
+ * enabled via the 'rules="rom"' config attribute. If both are
+ * definitions are present, the rules ROM - if valid - takes
+ * precedence over the configuration's '' node.
+ */
+ template
+ void with_rules(FN const &fn) const
+ {
+ if (_rom_rules.constructed()) {
+ Xml_node const rules = _rom_rules->rom.xml();
+ if (rules.type() == Rom_rules::node_type()) {
+ fn(rules);
+ return;
+ }
+ }
+
+ if (_config_rules.constructed()) {
+ fn(_config_rules->xml());
+ return;
+ }
+
+ fn(Xml_node(""));
+ }
+};
+
+#endif /* _LAYOUT_RULES_H_ */
diff --git a/repos/gems/src/app/window_layouter/main.cc b/repos/gems/src/app/window_layouter/main.cc
new file mode 100644
index 0000000000..9cc35274e7
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/main.cc
@@ -0,0 +1,613 @@
+/*
+ * \brief Window layouter
+ * \author Norman Feske
+ * \date 2013-02-14
+ */
+
+/*
+ * Copyright (C) 2013-2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* local includes */
+#include
+#include
+#include
+#include
+#include
+
+namespace Window_layouter { struct Main; }
+
+
+struct Window_layouter::Main : Operations,
+ Layout_rules::Change_handler,
+ Window_list::Change_handler
+{
+ Env &_env;
+
+ Attached_rom_dataspace _config { _env, "config" };
+
+ Signal_handler _config_handler {
+ _env.ep(), *this, &Main::_handle_config };
+
+ Heap _heap { _env.ram(), _env.rm() };
+
+ Area _screen_size { };
+
+ unsigned _to_front_cnt = 1;
+
+ Focus_history _focus_history { };
+
+ Layout_rules _layout_rules { _env, _heap, *this };
+
+ Decorator_margins _decorator_margins { Xml_node("") };
+
+ Window_list _window_list {
+ _env, _heap, *this, _screen_size, _focus_history, _decorator_margins };
+
+ Assign_list _assign_list { _heap };
+
+ Target_list _target_list { _heap };
+
+ void _update_window_layout()
+ {
+ _window_list.dissolve_windows_from_assignments();
+
+ _layout_rules.with_rules([&] (Xml_node rules) {
+ _assign_list.update_from_xml(rules);
+ _target_list.update_from_xml(rules, _screen_size);
+ });
+
+ _assign_list.assign_windows(_window_list);
+
+ /* position windows */
+ _assign_list.for_each([&] (Assign &assign) {
+ _target_list.for_each([&] (Target const &target) {
+
+ if (target.name() != assign.target_name())
+ return;
+
+ assign.for_each_member([&] (Assign::Member &member) {
+
+ Rect const rect = member.window.maximized()
+ ? target.geometry()
+ : assign.window_geometry(member.window.id().value,
+ member.window.client_size(),
+ target.geometry(),
+ _decorator_margins);
+ member.window.outer_geometry(rect);
+ });
+ });
+ });
+
+ _gen_window_layout();
+
+ if (_assign_list.matching_wildcards())
+ _gen_rules();
+
+ _gen_resize_request();
+ }
+
+ /**
+ * Layout_rules::Change_handler interface
+ */
+ void layout_rules_changed() override { _update_window_layout(); }
+
+ /**
+ * Window_list::Change_handler interface
+ */
+ void window_list_changed() override { _update_window_layout(); }
+
+ void _handle_config()
+ {
+ _config.update();
+
+ if (_config.xml().has_sub_node("report")) {
+ Xml_node const report = _config.xml().sub_node("report");
+ _rules_reporter.conditional(report.attribute_value("rules", false),
+ _env, "rules", "rules");
+ }
+
+ _layout_rules.update_config(_config.xml());
+ }
+
+ User_state _user_state { *this, _focus_history };
+
+
+ /**************************
+ ** Operations interface **
+ **************************/
+
+ void close(Window_id id) override
+ {
+ _window_list.with_window(id, [&] (Window &window) {
+ window.close(); });
+
+ _gen_resize_request();
+ _gen_focus();
+ }
+
+ void to_front(Window_id id) override
+ {
+ bool stacking_order_changed = false;
+
+ _window_list.with_window(id, [&] (Window &window) {
+
+ if (window.to_front_cnt() != _to_front_cnt) {
+ _to_front_cnt++;
+ window.to_front_cnt(_to_front_cnt);
+ stacking_order_changed = true;
+ };
+ });
+
+ if (stacking_order_changed)
+ _gen_rules();
+ }
+
+ void focus(Window_id) override
+ {
+ _gen_window_layout();
+ _gen_focus();
+ }
+
+ void toggle_fullscreen(Window_id id) override
+ {
+ /* make sure that the specified window is the front-most one */
+ to_front(id);
+
+ _window_list.with_window(id, [&] (Window &window) {
+ window.maximized(!window.maximized()); });
+
+ _update_window_layout();
+ _gen_resize_request();
+ }
+
+ void drag(Window_id id, Window::Element element, Point clicked, Point curr) override
+ {
+ to_front(id);
+
+ bool window_geometry_changed = false;
+
+ _window_list.with_window(id, [&] (Window &window) {
+
+ Rect const orig_geometry = window.effective_inner_geometry();
+ window.drag(element, clicked, curr);
+ Rect const next_geometry = window.effective_inner_geometry();
+
+ window_geometry_changed = orig_geometry.p1() != next_geometry.p1()
+ || orig_geometry.p2() != next_geometry.p2();
+ });
+
+ if (window_geometry_changed)
+ _gen_window_layout();
+
+ _gen_resize_request();
+ }
+
+ void finalize_drag(Window_id id, Window::Element, Point, Point) override
+ {
+ /*
+ * Update window layout because highlighting may have changed after the
+ * drag operation. E.g., if the window has not kept up with the
+ * dragging of a resize handle, the resize handle is no longer hovered.
+ */
+ _gen_window_layout();
+ _gen_rules();
+
+ _window_list.with_window(id, [&] (Window &window) {
+ window.finalize_drag_operation(); });
+
+ _gen_resize_request();
+ }
+
+
+ /**
+ * Install handler for responding to hover changes
+ */
+ void _handle_hover();
+
+ Signal_handler _hover_handler {
+ _env.ep(), *this, &Main::_handle_hover};
+
+ Attached_rom_dataspace _hover { _env, "hover" };
+
+
+ void _handle_focus_request();
+
+ Signal_handler _focus_request_handler {
+ _env.ep(), *this, &Main::_handle_focus_request };
+
+ Attached_rom_dataspace _focus_request { _env, "focus_request" };
+
+ int _handled_focus_request_id = 0;
+
+
+ /**
+ * Respond to decorator-margins information reported by the decorator
+ */
+ Attached_rom_dataspace _decorator_margins_rom { _env, "decorator_margins" };
+
+ void _handle_decorator_margins()
+ {
+ _decorator_margins_rom.update();
+
+ Xml_node const margins = _decorator_margins_rom.xml();
+ if (margins.has_sub_node("floating"))
+ _decorator_margins = Decorator_margins(margins.sub_node("floating"));
+
+ /* respond to change by adapting the maximized window geometry */
+ _handle_mode_change();
+ }
+
+ Signal_handler _decorator_margins_handler {
+ _env.ep(), *this, &Main::_handle_decorator_margins};
+
+
+ /**
+ * Install handler for responding to user input
+ */
+ void _handle_input()
+ {
+ while (_input.pending())
+ _user_state.handle_input(_input_ds.local_addr(),
+ _input.flush(), _config.xml());
+ }
+
+ Signal_handler _input_handler {
+ _env.ep(), *this, &Main::_handle_input };
+
+ Nitpicker::Connection _nitpicker { _env };
+
+ void _handle_mode_change()
+ {
+ /* determine maximized window geometry */
+ Framebuffer::Mode const mode = _nitpicker.mode();
+
+ _screen_size = Area(mode.width(), mode.height());
+
+ _update_window_layout();
+ }
+
+ Signal_handler _mode_change_handler {
+ _env.ep(), *this, &Main::_handle_mode_change };
+
+
+ Input::Session_client _input { _env.rm(), _nitpicker.input_session() };
+
+ Attached_dataspace _input_ds { _env.rm(), _input.dataspace() };
+
+ Expanding_reporter _window_layout_reporter { _env, "window_layout", "window_layout"};
+ Expanding_reporter _resize_request_reporter { _env, "resize_request", "resize_request"};
+ Expanding_reporter _focus_reporter { _env, "focus", "focus" };
+
+ Constructible _rules_reporter { };
+
+ bool _focused_window_maximized() const
+ {
+ bool result = false;
+ Window_id const id = _user_state.focused_window_id();
+ _window_list.with_window(id, [&] (Window const &window) {
+ result = window.maximized(); });
+ return result;
+ }
+
+ void _import_window_list(Xml_node);
+ void _gen_window_layout();
+ void _gen_resize_request();
+ void _gen_focus();
+ void _gen_rules();
+
+ template
+ void _gen_rules_assignments(Xml_generator &, FN const &);
+
+ /**
+ * Constructor
+ */
+ Main(Env &env) : _env(env)
+ {
+ _nitpicker.mode_sigh(_mode_change_handler);
+ _handle_mode_change();
+
+ _hover.sigh(_hover_handler);
+ _decorator_margins_rom.sigh(_decorator_margins_handler);
+ _input.sigh(_input_handler);
+ _focus_request.sigh(_focus_request_handler);
+
+ _window_list.initial_import();
+ _handle_focus_request();
+
+ /* attach update handler for config */
+ _config.sigh(_config_handler);
+ _handle_config();
+
+ /* reflect initial rules configuration */
+ _gen_rules();
+ }
+};
+
+
+void Window_layouter::Main::_gen_window_layout()
+{
+ /* update hover and focus state of each window */
+ _window_list.for_each_window([&] (Window &window) {
+
+ window.focused(window.has_id(_user_state.focused_window_id()));
+
+ bool const hovered = window.has_id(_user_state.hover_state().window_id);
+ window.hovered(hovered ? _user_state.hover_state().element
+ : Window::Element::UNDEFINED);
+ });
+
+ _window_layout_reporter.generate([&] (Xml_generator &xml) {
+ _target_list.gen_layout(xml, _assign_list); });
+}
+
+
+void Window_layouter::Main::_gen_resize_request()
+{
+ bool resize_needed = false;
+ _window_list.for_each_window([&] (Window const &window) {
+ if (window.client_size() != window.requested_size())
+ resize_needed = true; });
+
+ if (!resize_needed)
+ return;
+
+ _resize_request_reporter.generate([&] (Xml_generator &xml) {
+
+ _window_list.for_each_window([&] (Window const &window) {
+
+ Area const requested_size = window.requested_size();
+ if (requested_size != window.client_size()) {
+ xml.node("window", [&] () {
+ xml.attribute("id", window.id().value);
+ xml.attribute("width", requested_size.w());
+ xml.attribute("height", requested_size.h());
+ });
+ }
+ });
+ });
+}
+
+
+void Window_layouter::Main::_gen_focus()
+{
+ _focus_reporter.generate([&] (Xml_generator &xml) {
+ xml.node("window", [&] () {
+ xml.attribute("id", _user_state.focused_window_id().value); }); });
+}
+
+
+template
+void Window_layouter::Main::_gen_rules_assignments(Xml_generator &xml, FN const &filter)
+{
+ auto gen_window_geometry = [] (Xml_generator &xml,
+ Assign const &assign, Window const &window) {
+ if (!assign.floating())
+ return;
+
+ if (window.maximized())
+ assign.gen_geometry_attr(xml);
+ else
+ window.gen_inner_geometry(xml);
+ };
+
+ /* turn wildcard assignments into exact assignments */
+ auto fn = [&] (Assign const &assign, Assign::Member const &member) {
+
+ if (!filter(member.window))
+ return;
+
+ xml.node("assign", [&] () {
+ xml.attribute("label", member.window.label());
+ xml.attribute("target", assign.target_name());
+ gen_window_geometry(xml, assign, member.window);
+ });
+ };
+ _assign_list.for_each_wildcard_member(fn);
+
+ /*
+ * Generate existing exact assignments of floating windows,
+ * update attributes according to the current window state.
+ */
+ _assign_list.for_each([&] (Assign const &assign) {
+
+ if (assign.wildcard())
+ return;
+
+ /*
+ * Determine current geometry of window. If multiple windows
+ * are present with the same label, use the geometry of any of
+ * them as they cannot be distinguished based on their label.
+ */
+ bool geometry_generated = false;
+
+ assign.for_each_member([&] (Assign::Member const &member) {
+
+ if (geometry_generated || !filter(member.window))
+ return;
+
+ xml.node("assign", [&] () {
+ assign.gen_assign_attr(xml);
+ gen_window_geometry(xml, assign, member.window);
+ });
+ geometry_generated = true;
+ });
+ });
+}
+
+
+void Window_layouter::Main::_gen_rules()
+{
+ if (!_rules_reporter.constructed())
+ return;
+
+ _rules_reporter->generate([&] (Xml_generator &xml) {
+
+ _target_list.gen_screens(xml);
+
+ /*
+ * Generate exact nodes for present windows.
+ *
+ * The nodes are generated such that front-most windows appear
+ * before all other windows. The change of the stacking order
+ * is applied when the generated rules are imported the next time.
+ */
+ auto front_most = [&] (Window const &window) {
+ return (window.to_front_cnt() == _to_front_cnt); };
+
+ auto behind_front = [&] (Window const &window) {
+ return !front_most(window); };
+
+ _gen_rules_assignments(xml, front_most);
+ _gen_rules_assignments(xml, behind_front);
+
+ /* keep attributes of wildcards and (currently) unused assignments */
+ _assign_list.for_each([&] (Assign const &assign) {
+
+ bool no_window_assigned = true;
+ assign.for_each_member([&] (Assign::Member const &) {
+ no_window_assigned = false; });
+
+ /*
+ * If a window is present that matches the assignment, the
+ * node was already generated by '_gen_rules_assignments' above.
+ */
+ if (assign.wildcard() || no_window_assigned) {
+
+ xml.node("assign", [&] () {
+ assign.gen_assign_attr(xml);
+
+ if (assign.floating())
+ assign.gen_geometry_attr(xml);
+ });
+ }
+ });
+ });
+}
+
+
+/**
+ * Determine window element that corresponds to hover model
+ */
+static Window_layouter::Window::Element
+_element_from_hover_model(Genode::Xml_node hover_window_xml)
+{
+ typedef Window_layouter::Window::Element::Type Type;
+
+ bool const left_sizer = hover_window_xml.has_sub_node("left_sizer"),
+ right_sizer = hover_window_xml.has_sub_node("right_sizer"),
+ top_sizer = hover_window_xml.has_sub_node("top_sizer"),
+ bottom_sizer = hover_window_xml.has_sub_node("bottom_sizer");
+
+ if (left_sizer && top_sizer) return Type::TOP_LEFT;
+ if (left_sizer && bottom_sizer) return Type::BOTTOM_LEFT;
+ if (left_sizer) return Type::LEFT;
+
+ if (right_sizer && top_sizer) return Type::TOP_RIGHT;
+ if (right_sizer && bottom_sizer) return Type::BOTTOM_RIGHT;
+ if (right_sizer) return Type::RIGHT;
+
+ if (top_sizer) return Type::TOP;
+ if (bottom_sizer) return Type::BOTTOM;
+
+ if (hover_window_xml.has_sub_node("title")) return Type::TITLE;
+ if (hover_window_xml.has_sub_node("closer")) return Type::CLOSER;
+ if (hover_window_xml.has_sub_node("maximizer")) return Type::MAXIMIZER;
+ if (hover_window_xml.has_sub_node("minimizer")) return Type::MINIMIZER;
+
+ return Type::UNDEFINED;
+}
+
+
+void Window_layouter::Main::_handle_hover()
+{
+ _hover.update();
+
+ try {
+ Xml_node const hover_window_xml = _hover.xml().sub_node("window");
+
+ _user_state.hover(attribute(hover_window_xml, "id", 0UL),
+ _element_from_hover_model(hover_window_xml));
+ }
+
+ /*
+ * An exception may occur during the 'Xml_node' construction if the hover
+ * model is malformed. Under this condition, we invalidate the hover state.
+ */
+ catch (...) {
+
+ _user_state.reset_hover();
+
+ /*
+ * Don't generate a focus-model update here. In a situation where the
+ * pointer has moved over a native nitpicker view (outside the realm of
+ * the window manager), the hover model as generated by the decorator
+ * naturally becomes empty. If we posted a focus update, this would
+ * steal the focus away from the native nitpicker view.
+ */
+ }
+
+ /* propagate changed hovering to the decorator */
+ _gen_window_layout();
+}
+
+
+void Window_layouter::Main::_handle_focus_request()
+{
+ _focus_request.update();
+
+ int const id = _focus_request.xml().attribute_value("id", 0L);
+
+ /* don't apply the same focus request twice */
+ if (id == _handled_focus_request_id)
+ return;
+
+ _handled_focus_request_id = id;
+
+ Window::Label const prefix =
+ _focus_request.xml().attribute_value("label", Window::Label(""));
+
+ unsigned const next_to_front_cnt = _to_front_cnt + 1;
+
+ bool stacking_order_changed = false;
+
+ auto label_matches = [] (Window::Label const &prefix, Window::Label const &label) {
+ return !strcmp(label.string(), prefix.string(), prefix.length() - 1); };
+
+ _window_list.for_each_window([&] (Window &window) {
+
+ if (label_matches(prefix, window.label())) {
+ window.to_front_cnt(next_to_front_cnt);
+ _user_state.focused_window_id(window.id());
+ stacking_order_changed = true;
+ }
+ });
+
+ if (stacking_order_changed) {
+ _to_front_cnt++;
+ _gen_focus();
+ _gen_rules();
+ }
+}
+
+
+void Component::construct(Genode::Env &env)
+{
+ static Window_layouter::Main application(env);
+}
diff --git a/repos/gems/src/app/floating_window_layouter/operations.h b/repos/gems/src/app/window_layouter/operations.h
similarity index 75%
rename from repos/gems/src/app/floating_window_layouter/operations.h
rename to repos/gems/src/app/window_layouter/operations.h
index 5950a00213..883267fdb4 100644
--- a/repos/gems/src/app/floating_window_layouter/operations.h
+++ b/repos/gems/src/app/window_layouter/operations.h
@@ -1,11 +1,11 @@
/*
- * \brief Floating window layouter
+ * \brief Window layouter
* \author Norman Feske
* \date 2015-12-31
*/
/*
- * Copyright (C) 2015-2017 Genode Labs GmbH
+ * Copyright (C) 2015-2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
@@ -14,13 +14,16 @@
#ifndef _OPERATIONS_H_
#define _OPERATIONS_H_
+/* Genode includes */
+#include
+
/* local includes */
#include "window.h"
-namespace Floating_window_layouter { struct Operations; }
+namespace Window_layouter { struct Operations; }
-struct Floating_window_layouter::Operations
+struct Window_layouter::Operations : Interface
{
virtual void close(Window_id) = 0;
virtual void toggle_fullscreen(Window_id) = 0;
diff --git a/repos/gems/src/app/window_layouter/target.h b/repos/gems/src/app/window_layouter/target.h
new file mode 100644
index 0000000000..a8272a2454
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/target.h
@@ -0,0 +1,52 @@
+/*
+ * \brief Layout target
+ * \author Norman Feske
+ * \date 2018-09-27
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _TARGET_H_
+#define _TARGET_H_
+
+/* local includes */
+#include
+
+namespace Window_layouter { class Target; }
+
+
+class Window_layouter::Target : Noncopyable
+{
+ public:
+
+ typedef String<64> Name;
+
+ private:
+
+ Name const _name;
+ unsigned const _layer;
+ Rect const _geometry;
+
+ public:
+
+ Target(Xml_node target, Rect geometry)
+ :
+ _name (target.attribute_value("name", Name())),
+ _layer(target.attribute_value("layer", 9999UL)),
+ _geometry(geometry)
+ { }
+
+ /* needed to use class as 'Registered' */
+ virtual ~Target() { }
+
+ Name name() const { return _name; }
+ unsigned layer() const { return _layer; }
+ Rect geometry() const { return _geometry; }
+};
+
+#endif /* _TARGET_H_ */
diff --git a/repos/gems/src/app/window_layouter/target.mk b/repos/gems/src/app/window_layouter/target.mk
new file mode 100644
index 0000000000..3f3d2bd15b
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/target.mk
@@ -0,0 +1,4 @@
+TARGET = window_layouter
+SRC_CC = main.cc
+INC_DIR += $(PRG_DIR)
+LIBS = base
diff --git a/repos/gems/src/app/window_layouter/target_list.h b/repos/gems/src/app/window_layouter/target_list.h
new file mode 100644
index 0000000000..69f63e6d7c
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/target_list.h
@@ -0,0 +1,238 @@
+/*
+ * \brief List of target areas where windows may be placed
+ * \author Norman Feske
+ * \date 2018-09-26
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _TARGET_LIST_H_
+#define _TARGET_LIST_H_
+
+/* local includes */
+#include
+
+namespace Window_layouter { class Target_list; }
+
+
+class Window_layouter::Target_list
+{
+ private:
+
+ Allocator &_alloc;
+
+ Registry > _targets { };
+
+ /*
+ * Keep information of the rules internally to reproduce this
+ * information in 'gen_screens'.
+ */
+ Constructible _rules { };
+
+ /**
+ * Calculate layout and populate '_targets'
+ *
+ * \param row true of 'node' is a row, false if 'node' is a column
+ */
+ void _process_rec(Xml_node node, Rect avail, bool row)
+ {
+ unsigned long const avail_px = row ? avail.w() : avail.h();
+
+ char const *sub_node_type = row ? "column" : "row";
+ char const *px_size_attr = row ? "width" : "height";
+
+ unsigned long px_pos = row ? avail.x1() : avail.y1();
+
+ unsigned long const default_weight = 1;
+
+ /*
+ * Determinine space reserved in pixels, the total weight, and
+ * number of weighted rows/columns.
+ */
+ unsigned long preserved_pixels = 0;
+ unsigned long total_weight = 0;
+ unsigned long num_weighted = 0;
+
+ /* ignore weight if pixel size is provided */
+ auto weight_attr_value = [&] (Xml_node node) {
+ return node.has_attribute(px_size_attr)
+ ? 0 : node.attribute_value("weight", default_weight); };
+
+ node.for_each_sub_node(sub_node_type, [&] (Xml_node child) {
+ preserved_pixels += child.attribute_value(px_size_attr, 0UL);
+ total_weight += weight_attr_value(child);
+ num_weighted += child.has_attribute(px_size_attr) ? 0 : 1;
+ });
+
+ if (preserved_pixels > avail_px) {
+ warning("layout does not fit in available area ", avail_px, ":", node);
+ return;
+ }
+
+ /* amount of pixels we can use for weighed columns */
+ unsigned long const weigthed_avail = avail_px - preserved_pixels;
+
+ /*
+ * Calculate positions
+ */
+ unsigned long count_weighted = 0;
+ unsigned long used_weighted = 0;
+ node.for_each_sub_node(sub_node_type, [&] (Xml_node child) {
+
+ auto calc_px_size = [&] () {
+
+ unsigned long const px = child.attribute_value(px_size_attr, 0UL);
+ if (px)
+ return px;
+
+ unsigned long const weight = weight_attr_value(child);
+ if (weight && total_weight)
+ return (((weight << 16)*weigthed_avail)/total_weight) >> 16;
+
+ return 0UL;
+ };
+
+ bool const weighted = !child.has_attribute(px_size_attr);
+
+ if (weighted)
+ count_weighted++;
+
+ /* true if target is the last weigthed column or row */
+ bool const last_weighted = weighted
+ && (count_weighted == num_weighted);
+
+ unsigned long const px_size = last_weighted
+ ? (weigthed_avail - used_weighted)
+ : calc_px_size();
+
+ if (weighted)
+ used_weighted += px_size;
+
+ Rect const sub_rect = row ? Rect(Point(px_pos, avail.y1()),
+ Area(px_size, avail.h()))
+ : Rect(Point(avail.x1(), px_pos),
+ Area(avail.w(), px_size));
+
+ _process_rec(child, sub_rect, !row);
+
+ if (child.attribute_value("name", Target::Name()).valid())
+ new (_alloc)
+ Registered(_targets, child, sub_rect);
+
+ px_pos += px_size;
+ });
+ }
+
+ static constexpr unsigned MAX_LAYER = 9999;
+
+ /**
+ * Generate windows for the top-most layer, starting at 'min_layer'
+ *
+ * \return layer that was processed by the method
+ */
+ unsigned _gen_top_most_layer(Xml_generator &xml, unsigned min_layer,
+ Assign_list const &assignments) const
+ {
+ /* search targets for next matching layer */
+ unsigned layer = MAX_LAYER;
+ _targets.for_each([&] (Target const &target) {
+ if (target.layer() >= min_layer && target.layer() <= layer)
+ layer = target.layer(); });
+
+ /* visit all windows on the layer */
+ assignments.for_each([&] (Assign const &assign) {
+
+ Target::Name const target_name = assign.target_name();
+
+ /* search target by name */
+ _targets.for_each([&] (Target const &target) {
+
+ if (target.name() != target_name)
+ return;
+
+ if (target.layer() != layer)
+ return;
+
+ /* found target area, iterate though all assigned windows */
+ assign.for_each_member([&] (Assign::Member const &member) {
+ member.window.generate(xml); });
+ });
+ });
+
+ return layer;
+ }
+
+ public:
+
+ Target_list(Allocator &alloc) : _alloc(alloc) { }
+
+ /*
+ * The 'rules' XML node is expected to contain at least one
+ * node. Subseqent nodes are ignored. The node may
+ * contain any number of nodes. Each node may contain
+ * any number of nodes, which, in turn, can contain
+ * nodes.
+ */
+ void update_from_xml(Xml_node rules, Area screen_size)
+ {
+ _targets.for_each([&] (Registered &target) {
+ destroy(_alloc, &target); });
+
+ _rules.construct(_alloc, rules);
+
+ if (!rules.has_sub_node("screen"))
+ return;
+
+ Xml_node const screen = rules.sub_node("screen");
+
+ Rect const avail(Point(0, 0), screen_size);
+
+ if (screen.attribute_value("name", Target::Name()).valid())
+ new (_alloc)
+ Registered(_targets, screen, avail);
+
+ _process_rec(screen, avail, true);
+ }
+
+ void gen_layout(Xml_generator &xml, Assign_list const &assignments) const
+ {
+ unsigned min_layer = 0;
+
+ /* iterate over layers, starting at top-most layer (0) */
+ for (;;) {
+
+ unsigned const layer =
+ _gen_top_most_layer(xml, min_layer, assignments);
+
+ if (layer == MAX_LAYER)
+ break;
+
+ /* skip layer in next iteration */
+ min_layer = layer + 1;
+ }
+ }
+
+ /**
+ * Generate screen-layout definitions for the 'rules' report
+ */
+ void gen_screens(Xml_generator &xml) const
+ {
+ if (!_rules.constructed())
+ return;
+
+ _rules->xml().for_each_sub_node("screen", [&] (Xml_node screen) {
+ xml.append(screen.addr(), screen.size());
+ xml.append("\n");
+ });
+ }
+
+ template
+ void for_each(FN const &fn) const { _targets.for_each(fn); }
+};
+
+#endif /* _TARGET_LIST_H_ */
diff --git a/repos/gems/src/app/floating_window_layouter/types.h b/repos/gems/src/app/window_layouter/types.h
similarity index 86%
rename from repos/gems/src/app/floating_window_layouter/types.h
rename to repos/gems/src/app/window_layouter/types.h
index 67abc24c69..203a8d3fa6 100644
--- a/repos/gems/src/app/floating_window_layouter/types.h
+++ b/repos/gems/src/app/window_layouter/types.h
@@ -1,11 +1,11 @@
/*
- * \brief Floating window layouter
+ * \brief Window layouter
* \author Norman Feske
* \date 2015-12-31
*/
/*
- * Copyright (C) 2015-2017 Genode Labs GmbH
+ * Copyright (C) 2015-2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
@@ -16,8 +16,9 @@
/* Genode includes */
#include
+#include
-namespace Floating_window_layouter {
+namespace Window_layouter {
using namespace Genode;
@@ -49,6 +50,8 @@ namespace Floating_window_layouter {
return other.value == value;
}
};
+
+ class Window;
}
#endif /* _TYPES_H_ */
diff --git a/repos/gems/src/app/floating_window_layouter/user_state.h b/repos/gems/src/app/window_layouter/user_state.h
similarity index 93%
rename from repos/gems/src/app/floating_window_layouter/user_state.h
rename to repos/gems/src/app/window_layouter/user_state.h
index feaa906c78..0a1aef1d14 100644
--- a/repos/gems/src/app/floating_window_layouter/user_state.h
+++ b/repos/gems/src/app/window_layouter/user_state.h
@@ -1,5 +1,5 @@
/*
- * \brief Floating window layouter
+ * \brief Window layouter
* \author Norman Feske
* \date 2013-02-14
*/
@@ -18,10 +18,10 @@
#include "operations.h"
#include "key_sequence_tracker.h"
-namespace Floating_window_layouter { class User_state; }
+namespace Window_layouter { class User_state; }
-class Floating_window_layouter::User_state
+class Window_layouter::User_state
{
public:
@@ -38,13 +38,13 @@ class Floating_window_layouter::User_state
private:
- Window_id _hovered_window_id;
- Window_id _focused_window_id;
- Window_id _dragged_window_id;
+ Window_id _hovered_window_id { };
+ Window_id _focused_window_id { };
+ Window_id _dragged_window_id { };
unsigned _key_cnt = 0;
- Key_sequence_tracker _key_sequence_tracker;
+ Key_sequence_tracker _key_sequence_tracker { };
Window::Element _hovered_element = Window::Element::UNDEFINED;
Window::Element _dragged_element = Window::Element::UNDEFINED;
@@ -64,12 +64,12 @@ class Floating_window_layouter::User_state
/*
* Pointer position at the beginning of a drag operation
*/
- Point _pointer_clicked;
+ Point _pointer_clicked { };
/*
* Current pointer position
*/
- Point _pointer_curr;
+ Point _pointer_curr { };
Operations &_operations;
@@ -121,6 +121,9 @@ class Floating_window_layouter::User_state
_focus_history.focus(_focused_window_id);
_operations.toggle_fullscreen(_hovered_window_id);
+
+ _hovered_element = Window::Element::UNDEFINED;
+ _hovered_window_id = Window_id();
return;
}
@@ -220,8 +223,8 @@ class Floating_window_layouter::User_state
};
-void Floating_window_layouter::User_state::_handle_event(Input::Event const &e,
- Xml_node config)
+void Window_layouter::User_state::_handle_event(Input::Event const &e,
+ Xml_node config)
{
e.handle_absolute_motion([&] (int x, int y) {
_pointer_curr = Point(x, y); });
@@ -319,7 +322,7 @@ void Floating_window_layouter::User_state::_handle_event(Input::Event const &e,
return;
default:
- Genode::warning("action ", (int)action.type(), " unhanded");
+ warning("action ", (int)action.type(), " unhanded");
}
});
}
diff --git a/repos/gems/src/app/floating_window_layouter/window.h b/repos/gems/src/app/window_layouter/window.h
similarity index 52%
rename from repos/gems/src/app/floating_window_layouter/window.h
rename to repos/gems/src/app/window_layouter/window.h
index dda35dbcc7..890d339c4e 100644
--- a/repos/gems/src/app/floating_window_layouter/window.h
+++ b/repos/gems/src/app/window_layouter/window.h
@@ -1,5 +1,5 @@
/*
- * \brief Floating window layouter
+ * \brief Window layouter
* \author Norman Feske
* \date 2013-02-14
*/
@@ -14,14 +14,19 @@
#ifndef _WINDOW_H_
#define _WINDOW_H_
+/* Genode includes */
+#include
+
/* local includes */
-#include "types.h"
-#include "focus_history.h"
+#include
+#include
+#include
+#include
-namespace Floating_window_layouter { class Window; }
+namespace Window_layouter { class Window; }
-class Floating_window_layouter::Window : public List::Element
+class Window_layouter::Window : public List_model::Element
{
public:
@@ -66,16 +71,25 @@ class Floating_window_layouter::Window : public List::Element
Window_id const _id;
- Title _title;
+ Title _title { };
- Label _label;
+ Label const _label;
- Rect _geometry;
+ Decorator_margins const &_decorator_margins;
+
+ Rect _geometry { };
/**
* Window geometry at the start of the current drag operation
*/
- Rect _orig_geometry;
+ Rect _orig_geometry { };
+
+ /**
+ * Destined window geometry as defined by the user's drag operation
+ */
+ Rect _drag_geometry { };
+
+ Area _client_size;
/**
* Size as desired by the user during resize drag operations
@@ -83,13 +97,6 @@ class Floating_window_layouter::Window : public List::Element
Area _requested_size;
/**
- * Backup of the original geometry while the window is maximized
- */
- Rect _unmaximized_geometry;
-
- Rect const &_maximized_geometry;
-
- /**
* Window may be partially transparent
*/
bool _has_alpha = false;
@@ -105,14 +112,22 @@ class Floating_window_layouter::Window : public List::Element
bool _dragged = false;
+ bool _focused = false;
+
+ Element _hovered { Element::UNDEFINED };
+
/*
- * Number of times the window has been topped. This value is used by
- * the decorator to detect the need for bringing the window to the
- * front of nitpicker's global view stack even if the stacking order
- * stays the same within the decorator instance. This is important in
- * the presence of more than a single decorator.
+ * Value that keeps track when the window has been moved to front
+ * most recently. It is used as a criterion for the order of the
+ * generated rules.
+ *
+ * This value is also used by the decorator to detect the need for
+ * bringing the window to the front of nitpicker's global view stack
+ * even if the stacking order stays the same within the decorator
+ * instance. This is important in the presence of more than a single
+ * decorator.
*/
- unsigned _topped_cnt = 0;
+ unsigned _to_front_cnt = 0;
Focus_history::Entry _focus_history_entry;
@@ -143,6 +158,7 @@ class Floating_window_layouter::Window : public List::Element
|| (element.type == Window::Element::BOTTOM_RIGHT);
_orig_geometry = _geometry;
+ _drag_geometry = _geometry;
_requested_size = _geometry.area();
@@ -154,18 +170,25 @@ class Floating_window_layouter::Window : public List::Element
*/
void _apply_drag_operation(Point offset)
{
- if (!_drag_border())
- position(_orig_geometry.p1() + offset);
+ /* move window */
+ if (!_drag_border()) {
+ _drag_geometry = Rect(_orig_geometry.p1() + offset,
+ _orig_geometry.area());
+ return;
+ }
- int requested_w = _orig_geometry.w(),
- requested_h = _orig_geometry.h();
+ /* resize window */
+ int x1 = _orig_geometry.x1(), y1 = _orig_geometry.y1(),
+ x2 = _orig_geometry.x2(), y2 = _orig_geometry.y2();
- if (_drag_left_border) requested_w -= offset.x();
- if (_drag_right_border) requested_w += offset.x();
- if (_drag_top_border) requested_h -= offset.y();
- if (_drag_bottom_border) requested_h += offset.y();
+ if (_drag_left_border) x1 = min(x1 + offset.x(), x2);
+ if (_drag_right_border) x2 = max(x2 + offset.x(), x1);
+ if (_drag_top_border) y1 = min(y1 + offset.y(), y2);
+ if (_drag_bottom_border) y2 = max(y2 + offset.y(), y1);
- _requested_size = Area(max(1, requested_w), max(1, requested_h));
+ _drag_geometry = Rect(Point(x1, y1), Point(x2, y2));
+
+ _requested_size = _drag_geometry.area();
}
/**
@@ -177,14 +200,18 @@ class Floating_window_layouter::Window : public List::Element
|| _drag_top_border || _drag_bottom_border;
}
+ Constructible _assign_member { };
+
public:
- Window(Window_id id, Rect &maximized_geometry, Area initial_size,
- Focus_history &focus_history)
+ Window(Window_id id, Label const &label, Area initial_size,
+ Focus_history &focus_history,
+ Decorator_margins const &decorator_margins)
:
- _id(id),
+ _id(id), _label(label),
+ _decorator_margins(decorator_margins),
+ _client_size(initial_size),
_requested_size(initial_size),
- _maximized_geometry(maximized_geometry),
_focus_history_entry(focus_history, _id)
{ }
@@ -194,14 +221,58 @@ class Floating_window_layouter::Window : public List::Element
void title(Title const &title) { _title = title; }
- void label(Label const &label) { _label = label; }
+ Label label() const { return _label; }
- void geometry(Rect geometry) { _geometry = geometry; }
+ Rect effective_inner_geometry() const
+ {
+ if (!_dragged)
+ return _geometry;
+
+ int x1 = _orig_geometry.x1(), y1 = _orig_geometry.y1(),
+ x2 = _orig_geometry.x2(), y2 = _orig_geometry.y2();
+
+ /* move window */
+ if (!_drag_border())
+ return _drag_geometry;
+
+ /* resize window */
+ if (_drag_left_border) x1 = x2 - _client_size.w();
+ if (_drag_right_border) x2 = x1 + _client_size.w();
+ if (_drag_top_border) y1 = y2 - _client_size.h();
+ if (_drag_bottom_border) y2 = y1 + _client_size.h();
+
+ return Rect(Point(x1, y1), Point(x2, y2));
+ }
+
+ /**
+ * Place window
+ *
+ * \param geometry outer geometry available to the window
+ */
+ void outer_geometry(Rect outer)
+ {
+ /* drop attempts to apply layout while dragging the window */
+ if (_dragged)
+ return;
+
+ _geometry = _decorator_margins.inner_geometry(outer);
+
+ _requested_size = _geometry.area();
+ }
+
+ Rect outer_geometry() const
+ {
+ return _decorator_margins.outer_geometry(_geometry);
+ }
+
+ Area client_size() const { return _client_size; }
+
+ void focused(bool focused) { _focused = focused; }
+
+ void hovered(Element hovered) { _hovered = hovered; }
Point position() const { return _geometry.p1(); }
- void position(Point pos) { _geometry = Rect(pos, _geometry.area()); }
-
void has_alpha(bool has_alpha) { _has_alpha = has_alpha; }
void hidden(bool hidden) { _hidden = hidden; }
@@ -215,40 +286,27 @@ class Floating_window_layouter::Window : public List::Element
*
* This function is called when the window-list model changes.
*/
- void size(Area size)
+ void client_size(Area size) { _client_size = size; }
+
+ /*
+ * Called for generating the 'rules' report
+ */
+ void gen_inner_geometry(Xml_generator &xml) const
{
- if (_maximized) {
- _geometry = Rect(_maximized_geometry.p1(), size);
- return;
- }
+ Rect const inner = effective_inner_geometry();
- if (!_drag_border()) {
- _geometry = Rect(_geometry.p1(), size);
- return;
- }
+ xml.attribute("xpos", inner.x1());
+ xml.attribute("ypos", inner.y1());
+ xml.attribute("width", inner.w());
+ xml.attribute("height", inner.h());
- Point p1 = _geometry.p1(), p2 = _geometry.p2();
-
- if (_drag_left_border)
- p1 = Point(p2.x() - size.w() + 1, p1.y());
-
- if (_drag_right_border)
- p2 = Point(p1.x() + size.w() - 1, p2.y());
-
- if (_drag_top_border)
- p1 = Point(p1.x(), p2.y() - size.h() + 1);
-
- if (_drag_bottom_border)
- p2 = Point(p2.x(), p1.y() + size.h() - 1);
-
- _geometry = Rect(p1, p2);
+ if (_maximized)
+ xml.attribute("maximized", true);
}
- Area size() const { return _geometry.area(); }
-
Area requested_size() const { return _requested_size; }
- void serialize(Xml_generator &xml, bool focused, Element highlight)
+ void generate(Xml_generator &xml) const
{
/* omit window from the layout if hidden */
if (_hidden)
@@ -260,29 +318,27 @@ class Floating_window_layouter::Window : public List::Element
/* present concatenation of label and title in the window's title bar */
{
- bool const has_title = Genode::strlen(_title.string()) > 0;
+ bool const has_title = strlen(_title.string()) > 0;
- char buf[Label::capacity()];
- Genode::snprintf(buf, sizeof(buf), "%s%s%s",
- _label.string(),
- has_title ? " " : "",
- _title.string());
+ String const
+ title(_label, (has_title ? " " : ""), _title);
- xml.attribute("title", buf);
+ xml.attribute("title", title);
}
- xml.attribute("xpos", _geometry.x1());
- xml.attribute("ypos", _geometry.y1());
- xml.attribute("width", _geometry.w());
- xml.attribute("height", _geometry.h());
- xml.attribute("topped", _topped_cnt);
+ Rect const rect = effective_inner_geometry();
- if (focused)
+ xml.attribute("xpos", rect.x1());
+ xml.attribute("ypos", rect.y1());
+ xml.attribute("width", rect.w());
+ xml.attribute("height", rect.h());
+
+ if (_focused)
xml.attribute("focused", "yes");
- if (highlight.type != Element::UNDEFINED) {
+ if (_hovered.type != Element::UNDEFINED) {
xml.node("highlight", [&] () {
- xml.node(highlight.name());
+ xml.node(_hovered.name());
});
}
@@ -310,35 +366,36 @@ class Floating_window_layouter::Window : public List::Element
void finalize_drag_operation()
{
- _requested_size = _geometry.area();
- _dragged = false;
+ _dragged = false;
_drag_left_border = false;
_drag_right_border = false;
_drag_top_border = false;
_drag_bottom_border = false;
+ _requested_size = effective_inner_geometry().area();
}
- void topped() { _topped_cnt++; }
+ void to_front_cnt(unsigned to_front_cnt) { _to_front_cnt = to_front_cnt; }
+
+ unsigned to_front_cnt() const { return _to_front_cnt; }
void close() { _requested_size = Area(0, 0); }
bool maximized() const { return _maximized; }
- void maximized(bool maximized)
+ void maximized(bool maximized) { _maximized = maximized; }
+
+ void dissolve_from_assignment() { _assign_member.destruct(); }
+
+ /**
+ * Associate window with a definition
+ */
+ void assignment(Registry ®istry)
{
- /* enter maximized state */
- if (!_maximized && maximized) {
- _unmaximized_geometry = _geometry;
- _requested_size = _maximized_geometry.area();
- }
+ /* retain first matching assignment only */
+ if (_assign_member.constructed())
+ return;
- /* leave maximized state */
- if (_maximized && !maximized) {
- _requested_size = _unmaximized_geometry.area();
- _geometry = Rect(_unmaximized_geometry.p1(), _geometry.area());
- }
-
- _maximized = maximized;
+ _assign_member.construct(registry, *this);
}
};
diff --git a/repos/gems/src/app/window_layouter/window_list.h b/repos/gems/src/app/window_layouter/window_list.h
new file mode 100644
index 0000000000..1a7ca22b64
--- /dev/null
+++ b/repos/gems/src/app/window_layouter/window_list.h
@@ -0,0 +1,152 @@
+/*
+ * \brief Set of present windows
+ * \author Norman Feske
+ * \date 2018-09-26
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _WINDOW_LIST_H_
+#define _WINDOW_LIST_H_
+
+/* local includes */
+#include
+#include
+
+namespace Window_layouter { class Window_list; }
+
+
+class Window_layouter::Window_list
+{
+ public:
+
+ struct Change_handler : Interface
+ {
+ virtual void window_list_changed() = 0;
+ };
+
+ private:
+
+ Env &_env;
+ Allocator &_alloc;
+ Change_handler &_change_handler;
+ Area const &_screen_size;
+ Focus_history &_focus_history;
+ Decorator_margins const &_decorator_margins;
+
+ List_model _list { };
+
+ Attached_rom_dataspace _rom { _env, "window_list" };
+
+ Signal_handler _rom_handler {
+ _env.ep(), *this, &Window_list::_handle_rom };
+
+ void _handle_rom()
+ {
+ _rom.update();
+
+ /* import window-list changes */
+ Update_policy policy(*this);
+ _list.update_from_xml(policy, _rom.xml());
+
+ /* notify main program */
+ _change_handler.window_list_changed();
+ }
+
+ struct Update_policy : List_model::Update_policy
+ {
+ Window_list &_window_list;
+
+ Update_policy(Window_list &window_list)
+ : _window_list(window_list) { }
+
+ void destroy_element(Window &elem)
+ {
+ destroy(_window_list._alloc, &elem);
+ }
+
+ Window &create_element(Xml_node node)
+ {
+ unsigned long const id = node.attribute_value("id", 0UL);
+ Area const initial_size = area_attribute(node);
+
+ Window::Label const label =
+ node.attribute_value("label",Window::Label());
+
+ return *new (_window_list._alloc)
+ Window(id, label, initial_size,
+ _window_list._focus_history,
+ _window_list._decorator_margins);
+ }
+
+ void update_element(Window &win, Xml_node node)
+ {
+ win.client_size(area_attribute(node));
+ win.title(string_attribute(node, "title", Window::Title("")));
+ win.has_alpha( node.attribute_value("has_alpha", false));
+ win.hidden( node.attribute_value("hidden", false));
+ win.resizeable(node.attribute_value("resizeable", false));
+ }
+
+ static bool element_matches_xml_node(Window const &elem, Xml_node node)
+ {
+ return elem.has_id(node.attribute_value("id", 0UL));
+ }
+ };
+
+ public:
+
+ Window_list(Env &env,
+ Allocator &alloc,
+ Change_handler &change_handler,
+ Area const &screen_size,
+ Focus_history &focus_history,
+ Decorator_margins const &decorator_margins)
+ :
+ _env(env),
+ _alloc(alloc),
+ _change_handler(change_handler),
+ _screen_size(screen_size),
+ _focus_history(focus_history),
+ _decorator_margins(decorator_margins)
+ {
+ _rom.sigh(_rom_handler);
+ }
+
+ void initial_import() { _handle_rom(); }
+
+ void dissolve_windows_from_assignments()
+ {
+ _list.for_each([&] (Window &win) {
+ win.dissolve_from_assignment(); });
+ }
+
+ template
+ void with_window(Window_id id, FN const &fn)
+ {
+ _list.for_each([&] (Window &win) {
+ if (win.has_id(id))
+ fn(win); });
+ }
+
+ template
+ void with_window(Window_id id, FN const &fn) const
+ {
+ _list.for_each([&] (Window const &win) {
+ if (win.has_id(id))
+ fn(win); });
+ }
+
+ template
+ void for_each_window(FN const &fn) { _list.for_each(fn); }
+
+ template
+ void for_each_window(FN const &fn) const { _list.for_each(fn); }
+};
+
+#endif /* _WINDOW_LIST_H_ */
diff --git a/repos/libports/run/qt5_common.inc b/repos/libports/run/qt5_common.inc
index 979ca45275..45e4f9afac 100644
--- a/repos/libports/run/qt5_common.inc
+++ b/repos/libports/run/qt5_common.inc
@@ -12,7 +12,7 @@ create_boot_directory
import_from_depot [depot_user]/src/[base_src] \
[depot_user]/src/decorator \
[depot_user]/src/expat \
- [depot_user]/src/floating_window_layouter \
+ [depot_user]/src/window_layouter \
[depot_user]/src/freetype \
[depot_user]/src/init \
[depot_user]/src/jpeg \
@@ -149,7 +149,7 @@ proc qt5_start_nodes { feature_arg } {
-
+