mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-19 05:37:54 +00:00
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:
parent
5bb5a62d37
commit
a973d9902b
@ -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"/>
|
||||
|
@ -2,4 +2,4 @@ _/raw/wm
|
||||
_/src/wm
|
||||
_/src/report_rom
|
||||
_/src/decorator
|
||||
_/src/floating_window_layouter
|
||||
_/src/window_layouter
|
||||
|
@ -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"/>
|
||||
|
@ -1,18 +1,25 @@
|
||||
<config>
|
||||
<press key="KEY_SCREEN">
|
||||
<press key="KEY_TAB" action="next_window">
|
||||
<release key="KEY_TAB">
|
||||
<release key="KEY_SCREEN" action="raise_window"/>
|
||||
</release>
|
||||
<release key="KEY_SCREEN" action="raise_window"/>
|
||||
</press>
|
||||
<press key="KEY_LEFTSHIFT">
|
||||
<press key="KEY_TAB" action="prev_window">
|
||||
<release key="KEY_TAB">
|
||||
<release key="KEY_SCREEN" action="raise_window"/>
|
||||
</release>
|
||||
</press>
|
||||
</press>
|
||||
<press key="KEY_ENTER" action="toggle_fullscreen"/>
|
||||
</press>
|
||||
<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">
|
||||
<release key="KEY_SCREEN" action="raise_window"/>
|
||||
</release>
|
||||
<release key="KEY_SCREEN" action="raise_window"/>
|
||||
</press>
|
||||
<press key="KEY_LEFTSHIFT">
|
||||
<press key="KEY_TAB" action="prev_window">
|
||||
<release key="KEY_TAB">
|
||||
<release key="KEY_SCREEN" action="raise_window"/>
|
||||
</release>
|
||||
</press>
|
||||
</press>
|
||||
<press key="KEY_ENTER" action="toggle_fullscreen"/>
|
||||
</press>
|
||||
</config>
|
||||
|
@ -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/>
|
||||
|
@ -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)
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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); }
|
@ -1,6 +0,0 @@
|
||||
TARGET = floating_window_layouter
|
||||
SRC_CC = main.cc
|
||||
LIBS = base
|
||||
|
||||
|
||||
CC_CXX_WARN_STRICT =
|
164
repos/gems/src/app/window_layouter/README
Normal file
164
repos/gems/src/app/window_layouter/README
Normal 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.
|
||||
|
||||
|
@ -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:
|
||||
|
205
repos/gems/src/app/window_layouter/assign.h
Normal file
205
repos/gems/src/app/window_layouter/assign.h
Normal 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> ®istry, 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_ */
|
114
repos/gems/src/app/window_layouter/assign_list.h
Normal file
114
repos/gems/src/app/window_layouter/assign_list.h
Normal 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> ®istry) {
|
||||
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_ */
|
54
repos/gems/src/app/window_layouter/decorator_margins.h
Normal file
54
repos/gems/src/app/window_layouter/decorator_margins.h
Normal 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_ */
|
@ -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,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);
|
||||
}
|
@ -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:
|
125
repos/gems/src/app/window_layouter/layout_rules.h
Normal file
125
repos/gems/src/app/window_layouter/layout_rules.h
Normal 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_ */
|
613
repos/gems/src/app/window_layouter/main.cc
Normal file
613
repos/gems/src/app/window_layouter/main.cc
Normal 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);
|
||||
}
|
@ -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;
|
52
repos/gems/src/app/window_layouter/target.h
Normal file
52
repos/gems/src/app/window_layouter/target.h
Normal 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_ */
|
4
repos/gems/src/app/window_layouter/target.mk
Normal file
4
repos/gems/src/app/window_layouter/target.mk
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = window_layouter
|
||||
SRC_CC = main.cc
|
||||
INC_DIR += $(PRG_DIR)
|
||||
LIBS = base
|
238
repos/gems/src/app/window_layouter/target_list.h
Normal file
238
repos/gems/src/app/window_layouter/target_list.h
Normal 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_ */
|
@ -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_ */
|
@ -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");
|
||||
}
|
||||
});
|
||||
}
|
@ -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,16 +71,25 @@ 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
|
||||
@ -83,13 +97,6 @@ class Floating_window_layouter::Window : public List<Window>::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<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();
|
||||
|
||||
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<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;
|
||||
_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> ®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);
|
||||
}
|
||||
};
|
||||
|
152
repos/gems/src/app/window_layouter/window_list.h
Normal file
152
repos/gems/src/app/window_layouter/window_list.h
Normal 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_ */
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user