gems: flexible window layouter

This commit replaces the former floating_window_layouter with a new
window_layouter component that supports the subdivision of screen space
into columns and rows, the concept of layers, and the principle ability
to store window layout information across reboots. The latter is
accomplished by reflecting the component's internal state as a 'rules'
report to the outside.

Fixes #3031
This commit is contained in:
Norman Feske 2018-09-26 11:36:36 +02:00 committed by Christian Helmuth
parent 5bb5a62d37
commit a973d9902b
31 changed files with 1954 additions and 859 deletions

View File

@ -13,7 +13,7 @@
<rom label="zlib.lib.so"/>
<rom label="report_rom"/>
<rom label="themed_decorator"/>
<rom label="floating_window_layouter"/>
<rom label="window_layouter"/>
<rom label="wm"/>
<rom label="wm.config"/>
<rom label="decorator_init.config"/>

View File

@ -2,4 +2,4 @@ _/raw/wm
_/src/wm
_/src/report_rom
_/src/decorator
_/src/floating_window_layouter
_/src/window_layouter

View File

@ -8,7 +8,7 @@
<rom label="ld.lib.so"/>
<rom label="report_rom"/>
<rom label="decorator"/>
<rom label="floating_window_layouter"/>
<rom label="window_layouter"/>
<rom label="wm"/>
<rom label="wm.config"/>
<rom label="decorator_init.config"/>

View File

@ -1,4 +1,11 @@
<config>
<config rules="rom">
<report rules="yes"/>
<rules>
<screen name="screen"/>
<assign label_prefix="" target="screen" xpos="any" ypos="any"/>
</rules>
<press key="KEY_SCREEN">
<press key="KEY_TAB" action="next_window">
<release key="KEY_TAB">

View File

@ -27,6 +27,7 @@
<config>
<policy label="layouter -> window_list" report="wm -> window_list"/>
<policy label="layouter -> focus_request" report="wm -> focus_request"/>
<policy label="layouter -> rules" report="layouter -> rules"/>
<policy label="decorator -> window_layout" report="layouter -> window_layout"/>
<policy label="wm -> resize_request" report="layouter -> resize_request"/>
<policy label="decorator -> pointer" report="wm -> pointer"/>
@ -56,7 +57,7 @@
</start>
<start name="layouter">
<binary name="floating_window_layouter"/>
<binary name="window_layouter"/>
<resource name="RAM" quantum="4M"/>
<route>
<service name="ROM" label="config"> <parent label="layouter.config"/> </service>
@ -64,6 +65,7 @@
<service name="ROM" label="focus_request"> <child name="report_rom"/> </service>
<service name="ROM" label="hover"> <child name="report_rom"/> </service>
<service name="ROM" label="decorator_margins"> <child name="report_rom"/> </service>
<service name="ROM" label="rules"> <child name="report_rom"/> </service>
<service name="Report"> <child name="report_rom"/> </service>
<any-service>
<child name="wm"/> <parent/> <any-child/>

View File

@ -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)

View File

@ -159,7 +159,7 @@ install_config {
</start>
<start name="layouter">
<binary name="floating_window_layouter"/>
<binary name="window_layouter"/>
<resource name="RAM" quantum="4M"/>
<route>
<service name="ROM" label="window_list"> <child name="report_rom"/> </service>

View File

@ -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 {
<config>

View File

@ -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:
! <config>
! <policy label_prefix="mupdf" maximized="yes"/>
! <policy label_prefix="nit_fb" xpos="50" ypos="50"/>
! </config>
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.
! <start name="nitpicker">
! ...
! <config>
! ...
! <global-key name="KEY_LEFTMETA" label="wm -> decorator" />
! ...
! </config>
! ...
! </start>
The response of the window layouter to key sequences can be expressed in the
layouter configuration as follows:
! <config>
! <press key="KEY_LEFTMETA">
! <press key="KEY_TAB" action="next_window">
! <release key="KEY_TAB">
! <release key="KEY_LEFTMETA" action="raise_window"/>
! </release>
! </press>
! <press key="KEY_LEFTSHIFT">
! <press key="KEY_TAB" action="prev_window">
! <release key="KEY_TAB">
! <release key="KEY_LEFTMETA" action="raise_window"/>
! </release>
! </press>
! </press>
! <press key="KEY_ENTER" action="toggle_fullscreen"/>
! </press>
! </config>
Each '<press>' 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 '<press>' 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.

View File

@ -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 <base/log.h>
#include <base/signal.h>
#include <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <base/tslab.h>
#include <os/reporter.h>
#include <os/session_policy.h>
#include <nitpicker_session/connection.h>
#include <input_session/client.h>
#include <input/event.h>
#include <input/keycodes.h>
#include <decorator/xml_utils.h>
/* 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<Main> config_dispatcher {
env.ep(), *this, &Main::handle_config };
Genode::Heap heap { env.ram(), env.rm() };
Genode::Tslab<Window,4096> window_slab { &heap };
List<Window> 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 <typename FUNC>
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<Main> 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<Main> 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<Main> 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<Main> 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::Event>(),
input.flush(), config.xml());
}
Signal_handler<Main> 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<Main> 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); }

View File

@ -1,6 +0,0 @@
TARGET = floating_window_layouter
SRC_CC = main.cc
LIBS = base
CC_CXX_WARN_STRICT =

View File

@ -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.
! <config>
! <rules>
! <screen>
! ...definition of screen layout...
! </screen>
! <assign label_prefix="..." target="..."/>
! ,,,
! </rules>
! ...
! </config>
The '<screen>' node can host any number of '<column>' 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 '<row>' 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 '<row>' can, in turn,
contain '<column>' nodes, thereby further subdividing the screen.
Each '<column>' or '<row>' can be used as window-placement target when
equipped with a 'name' attribute. Each name must occur only once within
the '<screen>'. 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 '<assign>' nodes. Each
'<assign>' 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 '<assign>' node takes effect.
Each '<assign>' 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
'<assign>' rules defines their stacking order. The window with earliest
'<assign>' rule is displayed in front.
Dynamic layouts
---------------
The window layouter is able to respond to rule changes at runtime.
By specifying the '<config>' 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 '<rules>' sub node of the '<config>' 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 '<report>' sub
node of the configuration:
! <config>
! <report rules="yes"/>
! ...
! </config>
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 '<assign>' rule
(one that uses a 'label_prefix' or 'label_suffix'), the layouter generates a
new '<assign>' rule with the window's label as 'label' attribute. The
explicitly labeled '<assign>' rules appear before any wildcard '<assign>'
rules.
If the user brings a window to front, the window layouter will change the order
of the explicit '<assign>' rules such that the window's '<assign>' 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 '<assign>' 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.
! <start name="nitpicker">
! ...
! <config>
! ...
! <global-key name="KEY_LEFTMETA" label="wm -> decorator" />
! ...
! </config>
! ...
! </start>
The response of the window layouter to key sequences can be expressed in the
layouter configuration as follows:
! <config>
! ...
! <press key="KEY_LEFTMETA">
! <press key="KEY_TAB" action="next_window">
! <release key="KEY_TAB">
! <release key="KEY_LEFTMETA" action="raise_window"/>
! </release>
! </press>
! <press key="KEY_LEFTSHIFT">
! <press key="KEY_TAB" action="prev_window">
! <release key="KEY_TAB">
! <release key="KEY_LEFTMETA" action="raise_window"/>
! </release>
! </press>
! </press>
! <press key="KEY_ENTER" action="toggle_fullscreen"/>
! </press>
! </config>
Each '<press>' 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 '<press>' 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.

View File

@ -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:

View File

@ -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 <util/list_model.h>
#include <base/registry.h>
#include <os/buffered_xml.h>
/* local includes */
#include <types.h>
#include <target.h>
#include <decorator_margins.h>
namespace Window_layouter { class Assign; }
class Window_layouter::Assign : public List_model<Assign>::Element
{
public:
/*
* Used for associating windows with assignments. Hosted in 'Window'
* objects.
*/
struct Member : Registry<Member>::Element
{
Window &window;
Member(Registry<Member> &registry, Window &window)
: Registry<Member>::Element(registry, *this), window(window) { }
};
typedef String<80> Label;
private:
Registry<Member> _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<Member>' if label matches assignment
*
* This method is used for associating assignments to windows.
*/
template <typename FN>
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 <assign> nodes of windows captured via wildcard
*/
template <typename FN>
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 <assign> 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 <typename FN>
void for_each_member(FN const &fn) { _members.for_each(fn); }
template <typename FN>
void for_each_member(FN const &fn) const { _members.for_each(fn); }
};
#endif /* _ASSIGN_H_ */

View File

@ -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 <types.h>
#include <assign.h>
namespace Window_layouter { class Assign_list; }
class Window_layouter::Assign_list : Noncopyable
{
private:
Allocator &_alloc;
List_model<Assign> _assignments { };
struct Update_policy : List_model<Assign>::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<Assign::Member> &registry) {
window.assignment(registry); };
assign.with_matching_members_registry(window.label(), fn);
});
});
}
template <typename FN>
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 <typename FN>
void for_each(FN const &fn) { _assignments.for_each(fn); }
template <typename FN>
void for_each(FN const &fn) const { _assignments.for_each(fn); }
};
#endif /* _ASSIGN_LIST_H_ */

View File

@ -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 <types.h>
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_ */

View File

@ -17,14 +17,14 @@
/* Genode includes */
#include <util/list.h>
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<Entry>::Element
struct Entry : List<Entry>::Element
{
Focus_history &focus_history;
Window_id const window_id;
@ -36,7 +36,7 @@ class Floating_window_layouter::Focus_history
private:
Genode::List<Entry> _entries;
List<Entry> _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,7 +106,7 @@ class Floating_window_layouter::Focus_history
};
Floating_window_layouter::Focus_history::Entry::Entry(Focus_history &focus_history,
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);
}

View File

@ -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("<none/>");
@ -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:

View File

@ -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 <os/buffered_xml.h>
/* local includes */
#include <types.h>
#include <window.h>
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<Buffered_xml> _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<Rom_rules> _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> _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 '<rules>' 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 '<rules>' node.
*/
template <typename FN>
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("<rules/>"));
}
};
#endif /* _LAYOUT_RULES_H_ */

View File

@ -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 <base/log.h>
#include <base/signal.h>
#include <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <os/reporter.h>
#include <os/session_policy.h>
#include <nitpicker_session/connection.h>
#include <input_session/client.h>
#include <input/event.h>
#include <input/keycodes.h>
/* local includes */
#include <window_list.h>
#include <assign_list.h>
#include <target_list.h>
#include <user_state.h>
#include <operations.h>
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<Main> _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("<floating/>") };
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<Main> _hover_handler {
_env.ep(), *this, &Main::_handle_hover};
Attached_rom_dataspace _hover { _env, "hover" };
void _handle_focus_request();
Signal_handler<Main> _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<Main> _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::Event>(),
_input.flush(), _config.xml());
}
Signal_handler<Main> _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<Main> _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<Expanding_reporter> _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 <typename FN>
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 <typename FN>
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 <assign> 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 <assign>
* 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);
}

View File

@ -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 <util/interface.h>
/* 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;

View File

@ -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 <types.h>
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<Target>' */
virtual ~Target() { }
Name name() const { return _name; }
unsigned layer() const { return _layer; }
Rect geometry() const { return _geometry; }
};
#endif /* _TARGET_H_ */

View File

@ -0,0 +1,4 @@
TARGET = window_layouter
SRC_CC = main.cc
INC_DIR += $(PRG_DIR)
LIBS = base

View File

@ -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 <target.h>
namespace Window_layouter { class Target_list; }
class Window_layouter::Target_list
{
private:
Allocator &_alloc;
Registry<Registered<Target> > _targets { };
/*
* Keep information of the rules internally to reproduce this
* information in 'gen_screens'.
*/
Constructible<Buffered_xml> _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<Target>(_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 <screen>
* node. Subseqent <screen> nodes are ignored. The <screen> node may
* contain any number of <column> nodes. Each <column> node may contain
* any number of <row> nodes, which, in turn, can contain <column>
* nodes.
*/
void update_from_xml(Xml_node rules, Area screen_size)
{
_targets.for_each([&] (Registered<Target> &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<Target>(_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 <typename FN>
void for_each(FN const &fn) const { _targets.for_each(fn); }
};
#endif /* _TARGET_LIST_H_ */

View File

@ -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 <decorator/types.h>
#include <decorator/xml_utils.h>
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_ */

View File

@ -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,7 +223,7 @@ class Floating_window_layouter::User_state
};
void Floating_window_layouter::User_state::_handle_event(Input::Event const &e,
void Window_layouter::User_state::_handle_event(Input::Event const &e,
Xml_node config)
{
e.handle_absolute_motion([&] (int x, int 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");
}
});
}

View File

@ -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 <util/list_model.h>
/* local includes */
#include "types.h"
#include "focus_history.h"
#include <types.h>
#include <focus_history.h>
#include <assign.h>
#include <decorator_margins.h>
namespace Floating_window_layouter { class Window; }
namespace Window_layouter { class Window; }
class Floating_window_layouter::Window : public List<Window>::Element
class Window_layouter::Window : public List_model<Window>::Element
{
public:
@ -66,29 +71,31 @@ class Floating_window_layouter::Window : public List<Window>::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
*/
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
*/
@ -105,14 +112,22 @@ class Floating_window_layouter::Window : public List<Window>::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 <assign> 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<Window>::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<Window>::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<Window>::Element
|| _drag_top_border || _drag_bottom_border;
}
Constructible<Assign::Member> _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<Window>::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<Window>::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();
xml.attribute("xpos", inner.x1());
xml.attribute("ypos", inner.y1());
xml.attribute("width", inner.w());
xml.attribute("height", inner.h());
if (_maximized)
xml.attribute("maximized", true);
}
if (!_drag_border()) {
_geometry = Rect(_geometry.p1(), size);
return;
}
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);
}
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<Window>::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<Label::capacity()> 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<Window>::Element
void finalize_drag_operation()
{
_requested_size = _geometry.area();
_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 <assign> definition
*/
void assignment(Registry<Assign::Member> &registry)
{
/* 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);
}
};

View File

@ -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 <window.h>
#include <layout_rules.h>
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<Window> _list { };
Attached_rom_dataspace _rom { _env, "window_list" };
Signal_handler<Window_list> _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<Window>::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 <typename FN>
void with_window(Window_id id, FN const &fn)
{
_list.for_each([&] (Window &win) {
if (win.has_id(id))
fn(win); });
}
template <typename FN>
void with_window(Window_id id, FN const &fn) const
{
_list.for_each([&] (Window const &win) {
if (win.has_id(id))
fn(win); });
}
template <typename FN>
void for_each_window(FN const &fn) { _list.for_each(fn); }
template <typename FN>
void for_each_window(FN const &fn) const { _list.for_each(fn); }
};
#endif /* _WINDOW_LIST_H_ */

View File

@ -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 } {
</start>
<start name="layouter">
<binary name="floating_window_layouter"/>
<binary name="window_layouter"/>
<resource name="RAM" quantum="4M"/>
<route>
<service name="ROM" label="window_list"> <child name="report_rom"/> </service>