671 lines
17 KiB
C++
671 lines
17 KiB
C++
/*
|
|
* \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>
|
|
#include <timer_session/connection.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 };
|
|
|
|
Timer::Connection _drop_timer { _env };
|
|
|
|
enum class Drag_state { IDLE, DRAGGING, SETTLING };
|
|
|
|
Drag_state _drag_state { Drag_state::IDLE };
|
|
|
|
Signal_handler<Main> _drop_timer_handler {
|
|
_env.ep(), *this, &Main::_handle_drop_timer };
|
|
|
|
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 };
|
|
|
|
/**
|
|
* Bring window to front, return true if the stacking order changed
|
|
*/
|
|
bool _to_front(Window &window)
|
|
{
|
|
bool stacking_order_changed = false;
|
|
|
|
if (window.to_front_cnt() != _to_front_cnt) {
|
|
_to_front_cnt++;
|
|
window.to_front_cnt(_to_front_cnt);
|
|
stacking_order_changed = true;
|
|
};
|
|
|
|
return stacking_order_changed;
|
|
}
|
|
|
|
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) {
|
|
|
|
assign.for_each_member([&] (Assign::Member &member) {
|
|
|
|
member.window.floating(assign.floating());
|
|
member.window.target_geometry(target.geometry());
|
|
|
|
Rect const rect = assign.window_geometry(member.window.id().value,
|
|
member.window.client_size(),
|
|
target.geometry(),
|
|
_decorator_margins);
|
|
member.window.outer_geometry(rect);
|
|
member.window.maximized(assign.maximized());
|
|
});
|
|
});
|
|
});
|
|
|
|
/* bring new windows that solely match a wildcard assignment to the front */
|
|
_assign_list.for_each_wildcard_assigned_window([&] (Window &window) {
|
|
_to_front(window); });
|
|
|
|
_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();
|
|
|
|
_gen_resize_request();
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
stacking_order_changed = _to_front(window); });
|
|
|
|
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()); });
|
|
|
|
_gen_rules();
|
|
_gen_resize_request();
|
|
}
|
|
|
|
void screen(Target::Name const & name) override
|
|
{
|
|
_gen_rules_with_frontmost_screen(name);
|
|
}
|
|
|
|
void drag(Window_id id, Window::Element element, Point clicked, Point curr) override
|
|
{
|
|
if (_drag_state == Drag_state::SETTLING)
|
|
return;
|
|
|
|
_drag_state = Drag_state::DRAGGING;
|
|
|
|
to_front(id);
|
|
|
|
bool window_layout_changed = false;
|
|
|
|
_window_list.with_window(id, [&] (Window &window) {
|
|
|
|
bool const orig_dragged = window.dragged();
|
|
Rect const orig_geometry = window.effective_inner_geometry();
|
|
window.drag(element, clicked, curr);
|
|
bool const next_dragged = window.dragged();
|
|
Rect const next_geometry = window.effective_inner_geometry();
|
|
|
|
window_layout_changed = orig_geometry.p1() != next_geometry.p1()
|
|
|| orig_geometry.p2() != next_geometry.p2()
|
|
|| orig_dragged != next_dragged;
|
|
});
|
|
|
|
if (window_layout_changed)
|
|
_gen_window_layout();
|
|
|
|
_gen_resize_request();
|
|
}
|
|
|
|
void _handle_drop_timer()
|
|
{
|
|
_drag_state = Drag_state::IDLE;
|
|
|
|
_gen_rules();
|
|
|
|
_window_list.for_each_window([&] (Window &window) {
|
|
window.finalize_drag_operation(); });
|
|
}
|
|
|
|
void finalize_drag(Window_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();
|
|
|
|
_drag_state = Drag_state::SETTLING;
|
|
|
|
_drop_timer.trigger_once(250*1000);
|
|
}
|
|
|
|
/**
|
|
* 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_with_frontmost_screen(Target::Name const &);
|
|
|
|
void _gen_rules()
|
|
{
|
|
/* keep order of screens unmodified */
|
|
_gen_rules_with_frontmost_screen(Target::Name());
|
|
}
|
|
|
|
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();
|
|
|
|
_drop_timer.sigh(_drop_timer_handler);
|
|
|
|
_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;
|
|
_assign_list.for_each([&] (Assign const &assign) {
|
|
assign.for_each_member([&] (Assign::Member const &member) {
|
|
if (member.window.resize_request_needed())
|
|
resize_needed = true; }); });
|
|
|
|
if (!resize_needed)
|
|
return;
|
|
|
|
_resize_request_reporter.generate([&] (Xml_generator &xml) {
|
|
_window_list.for_each_window([&] (Window const &window) {
|
|
window.gen_resize_request(xml); }); });
|
|
|
|
/* prevent superfluous resize requests for the same size */
|
|
_window_list.for_each_window([&] (Window &window) {
|
|
window.resize_request_updated(); });
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
assign.gen_geometry_attr(xml, { .geometry = window.effective_inner_geometry(),
|
|
.maximized = window.maximized() });
|
|
};
|
|
|
|
/* 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_with_frontmost_screen(Target::Name const &screen)
|
|
{
|
|
if (!_rules_reporter.constructed())
|
|
return;
|
|
|
|
_rules_reporter->generate([&] (Xml_generator &xml) {
|
|
|
|
_target_list.gen_screens(xml, screen);
|
|
|
|
/*
|
|
* 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();
|
|
|
|
User_state::Hover_state const orig_hover_state = _user_state.hover_state();
|
|
|
|
try {
|
|
Xml_node const hover_window_xml = _hover.xml().sub_node("window");
|
|
|
|
_user_state.hover(hover_window_xml.attribute_value("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
|
|
*
|
|
* Should a decorator issue a hover update with the unchanged information,
|
|
* avoid triggering a superfluous window-layout update. This can happen in
|
|
* corner cases such as the completion of a window-drag operation.
|
|
*/
|
|
if (_user_state.hover_state() != orig_hover_state)
|
|
_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);
|
|
}
|