2018-09-26 11:36:36 +02:00
|
|
|
/*
|
|
|
|
* \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>
|
2019-03-14 14:15:58 +01:00
|
|
|
#include <timer_session/connection.h>
|
2018-09-26 11:36:36 +02:00
|
|
|
|
|
|
|
/* 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 };
|
|
|
|
|
2019-03-14 14:15:58 +01:00
|
|
|
Timer::Connection _drop_timer { _env };
|
|
|
|
|
window layouter: reduce snap-back artifact
When resizing windows of clients that respond very slowly to resize
requests, the window's size sometimes snapped back to its original size
immediately after finishing the drag operation.
The problem was caused by the interplay of the layout rules (obtained
via the 'rules' ROM, generated by the 'rules' report) and the
temporary interactive state that occurs during drag operations.
The rules are updated only at the time of releasing the button to keep
the overhead while dragging the window low. However, when releasing the
mouse, the (now outdated) rules kicked back into effect, triggering
resize requests for the window to its old size.
The patch solves this problem by decoupling the dragged state of a
window from the physical release of the button. The button release
triggers a transition from a DRAGGING to a SETTLING state and programs
a timer. In the SETTLING state, the windows behave as in DRAGGING state,
giving the interactive geometry precedence over the rules-dictated
geometry. During this state, further responses of window-resize requests
may come in and are handled like dragging was still in progress. After a
timeout, however, the current window layout is conserved as a new rules
report and the state goes back to IDLE.
For clients that takes a very long time (in particular, VirtualBox when
resizing the desktop, which takes sometimes multiple seconds), the
snap-back artifact can still occur, but the effect is reduced.
2019-06-18 17:05:17 +02:00
|
|
|
enum class Drag_state { IDLE, DRAGGING, SETTLING };
|
|
|
|
|
|
|
|
Drag_state _drag_state { Drag_state::IDLE };
|
2019-03-14 14:15:58 +01:00
|
|
|
|
|
|
|
Signal_handler<Main> _drop_timer_handler {
|
|
|
|
_env.ep(), *this, &Main::_handle_drop_timer };
|
|
|
|
|
2018-09-26 11:36:36 +02:00
|
|
|
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 };
|
|
|
|
|
2018-11-27 14:04:20 +01:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2018-09-26 11:36:36 +02:00
|
|
|
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) {
|
|
|
|
|
2020-03-27 15:01:41 +01:00
|
|
|
if (target.name() != assign.target_name())
|
|
|
|
return;
|
|
|
|
|
2018-09-26 11:36:36 +02:00
|
|
|
assign.for_each_member([&] (Assign::Member &member) {
|
|
|
|
|
2019-03-16 23:50:09 +01:00
|
|
|
member.window.floating(assign.floating());
|
|
|
|
member.window.target_geometry(target.geometry());
|
|
|
|
|
|
|
|
Rect const rect = assign.window_geometry(member.window.id().value,
|
2018-09-26 11:36:36 +02:00
|
|
|
member.window.client_size(),
|
|
|
|
target.geometry(),
|
|
|
|
_decorator_margins);
|
|
|
|
member.window.outer_geometry(rect);
|
2019-06-17 14:38:47 +02:00
|
|
|
member.window.maximized(assign.maximized());
|
2018-09-26 11:36:36 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-11-27 14:04:20 +01:00
|
|
|
/* bring new windows that solely match a wildcard assignment to the front */
|
|
|
|
_assign_list.for_each_wildcard_assigned_window([&] (Window &window) {
|
|
|
|
_to_front(window); });
|
|
|
|
|
2018-09-26 11:36:36 +02:00
|
|
|
_gen_window_layout();
|
|
|
|
|
|
|
|
if (_assign_list.matching_wildcards())
|
|
|
|
_gen_rules();
|
|
|
|
|
|
|
|
_gen_resize_request();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Layout_rules::Change_handler interface
|
|
|
|
*/
|
2019-03-14 14:15:58 +01:00
|
|
|
void layout_rules_changed() override
|
|
|
|
{
|
|
|
|
_update_window_layout();
|
|
|
|
|
window layouter: reduce snap-back artifact
When resizing windows of clients that respond very slowly to resize
requests, the window's size sometimes snapped back to its original size
immediately after finishing the drag operation.
The problem was caused by the interplay of the layout rules (obtained
via the 'rules' ROM, generated by the 'rules' report) and the
temporary interactive state that occurs during drag operations.
The rules are updated only at the time of releasing the button to keep
the overhead while dragging the window low. However, when releasing the
mouse, the (now outdated) rules kicked back into effect, triggering
resize requests for the window to its old size.
The patch solves this problem by decoupling the dragged state of a
window from the physical release of the button. The button release
triggers a transition from a DRAGGING to a SETTLING state and programs
a timer. In the SETTLING state, the windows behave as in DRAGGING state,
giving the interactive geometry precedence over the rules-dictated
geometry. During this state, further responses of window-resize requests
may come in and are handled like dragging was still in progress. After a
timeout, however, the current window layout is conserved as a new rules
report and the state goes back to IDLE.
For clients that takes a very long time (in particular, VirtualBox when
resizing the desktop, which takes sometimes multiple seconds), the
snap-back artifact can still occur, but the effect is reduced.
2019-06-18 17:05:17 +02:00
|
|
|
_gen_resize_request();
|
2019-03-14 14:15:58 +01:00
|
|
|
}
|
2018-09-26 11:36:36 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2018-11-27 14:04:20 +01:00
|
|
|
stacking_order_changed = _to_front(window); });
|
2018-09-26 11:36:36 +02:00
|
|
|
|
|
|
|
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()); });
|
|
|
|
|
2019-03-16 23:50:09 +01:00
|
|
|
_gen_rules();
|
2018-09-26 11:36:36 +02:00
|
|
|
_gen_resize_request();
|
|
|
|
}
|
|
|
|
|
2020-01-09 12:59:05 +01:00
|
|
|
void screen(Target::Name const & name) override
|
|
|
|
{
|
|
|
|
_gen_rules_with_frontmost_screen(name);
|
|
|
|
}
|
|
|
|
|
2018-09-26 11:36:36 +02:00
|
|
|
void drag(Window_id id, Window::Element element, Point clicked, Point curr) override
|
|
|
|
{
|
window layouter: reduce snap-back artifact
When resizing windows of clients that respond very slowly to resize
requests, the window's size sometimes snapped back to its original size
immediately after finishing the drag operation.
The problem was caused by the interplay of the layout rules (obtained
via the 'rules' ROM, generated by the 'rules' report) and the
temporary interactive state that occurs during drag operations.
The rules are updated only at the time of releasing the button to keep
the overhead while dragging the window low. However, when releasing the
mouse, the (now outdated) rules kicked back into effect, triggering
resize requests for the window to its old size.
The patch solves this problem by decoupling the dragged state of a
window from the physical release of the button. The button release
triggers a transition from a DRAGGING to a SETTLING state and programs
a timer. In the SETTLING state, the windows behave as in DRAGGING state,
giving the interactive geometry precedence over the rules-dictated
geometry. During this state, further responses of window-resize requests
may come in and are handled like dragging was still in progress. After a
timeout, however, the current window layout is conserved as a new rules
report and the state goes back to IDLE.
For clients that takes a very long time (in particular, VirtualBox when
resizing the desktop, which takes sometimes multiple seconds), the
snap-back artifact can still occur, but the effect is reduced.
2019-06-18 17:05:17 +02:00
|
|
|
if (_drag_state == Drag_state::SETTLING)
|
|
|
|
return;
|
|
|
|
|
|
|
|
_drag_state = Drag_state::DRAGGING;
|
|
|
|
|
2018-09-26 11:36:36 +02:00
|
|
|
to_front(id);
|
|
|
|
|
2018-12-27 19:11:20 +01:00
|
|
|
bool window_layout_changed = false;
|
2018-09-26 11:36:36 +02:00
|
|
|
|
|
|
|
_window_list.with_window(id, [&] (Window &window) {
|
|
|
|
|
2018-12-27 19:11:20 +01:00
|
|
|
bool const orig_dragged = window.dragged();
|
2018-09-26 11:36:36 +02:00
|
|
|
Rect const orig_geometry = window.effective_inner_geometry();
|
|
|
|
window.drag(element, clicked, curr);
|
2018-12-27 19:11:20 +01:00
|
|
|
bool const next_dragged = window.dragged();
|
2018-09-26 11:36:36 +02:00
|
|
|
Rect const next_geometry = window.effective_inner_geometry();
|
|
|
|
|
2018-12-27 19:11:20 +01:00
|
|
|
window_layout_changed = orig_geometry.p1() != next_geometry.p1()
|
|
|
|
|| orig_geometry.p2() != next_geometry.p2()
|
|
|
|
|| orig_dragged != next_dragged;
|
2018-09-26 11:36:36 +02:00
|
|
|
});
|
|
|
|
|
2018-12-27 19:11:20 +01:00
|
|
|
if (window_layout_changed)
|
2018-09-26 11:36:36 +02:00
|
|
|
_gen_window_layout();
|
|
|
|
|
|
|
|
_gen_resize_request();
|
|
|
|
}
|
|
|
|
|
2019-03-14 14:15:58 +01:00
|
|
|
void _handle_drop_timer()
|
|
|
|
{
|
window layouter: reduce snap-back artifact
When resizing windows of clients that respond very slowly to resize
requests, the window's size sometimes snapped back to its original size
immediately after finishing the drag operation.
The problem was caused by the interplay of the layout rules (obtained
via the 'rules' ROM, generated by the 'rules' report) and the
temporary interactive state that occurs during drag operations.
The rules are updated only at the time of releasing the button to keep
the overhead while dragging the window low. However, when releasing the
mouse, the (now outdated) rules kicked back into effect, triggering
resize requests for the window to its old size.
The patch solves this problem by decoupling the dragged state of a
window from the physical release of the button. The button release
triggers a transition from a DRAGGING to a SETTLING state and programs
a timer. In the SETTLING state, the windows behave as in DRAGGING state,
giving the interactive geometry precedence over the rules-dictated
geometry. During this state, further responses of window-resize requests
may come in and are handled like dragging was still in progress. After a
timeout, however, the current window layout is conserved as a new rules
report and the state goes back to IDLE.
For clients that takes a very long time (in particular, VirtualBox when
resizing the desktop, which takes sometimes multiple seconds), the
snap-back artifact can still occur, but the effect is reduced.
2019-06-18 17:05:17 +02:00
|
|
|
_drag_state = Drag_state::IDLE;
|
|
|
|
|
|
|
|
_gen_rules();
|
|
|
|
|
|
|
|
_window_list.for_each_window([&] (Window &window) {
|
|
|
|
window.finalize_drag_operation(); });
|
2019-03-14 14:15:58 +01:00
|
|
|
}
|
|
|
|
|
window layouter: reduce snap-back artifact
When resizing windows of clients that respond very slowly to resize
requests, the window's size sometimes snapped back to its original size
immediately after finishing the drag operation.
The problem was caused by the interplay of the layout rules (obtained
via the 'rules' ROM, generated by the 'rules' report) and the
temporary interactive state that occurs during drag operations.
The rules are updated only at the time of releasing the button to keep
the overhead while dragging the window low. However, when releasing the
mouse, the (now outdated) rules kicked back into effect, triggering
resize requests for the window to its old size.
The patch solves this problem by decoupling the dragged state of a
window from the physical release of the button. The button release
triggers a transition from a DRAGGING to a SETTLING state and programs
a timer. In the SETTLING state, the windows behave as in DRAGGING state,
giving the interactive geometry precedence over the rules-dictated
geometry. During this state, further responses of window-resize requests
may come in and are handled like dragging was still in progress. After a
timeout, however, the current window layout is conserved as a new rules
report and the state goes back to IDLE.
For clients that takes a very long time (in particular, VirtualBox when
resizing the desktop, which takes sometimes multiple seconds), the
snap-back artifact can still occur, but the effect is reduced.
2019-06-18 17:05:17 +02:00
|
|
|
void finalize_drag(Window_id, Window::Element, Point, Point) override
|
2018-09-26 11:36:36 +02:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 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();
|
|
|
|
|
window layouter: reduce snap-back artifact
When resizing windows of clients that respond very slowly to resize
requests, the window's size sometimes snapped back to its original size
immediately after finishing the drag operation.
The problem was caused by the interplay of the layout rules (obtained
via the 'rules' ROM, generated by the 'rules' report) and the
temporary interactive state that occurs during drag operations.
The rules are updated only at the time of releasing the button to keep
the overhead while dragging the window low. However, when releasing the
mouse, the (now outdated) rules kicked back into effect, triggering
resize requests for the window to its old size.
The patch solves this problem by decoupling the dragged state of a
window from the physical release of the button. The button release
triggers a transition from a DRAGGING to a SETTLING state and programs
a timer. In the SETTLING state, the windows behave as in DRAGGING state,
giving the interactive geometry precedence over the rules-dictated
geometry. During this state, further responses of window-resize requests
may come in and are handled like dragging was still in progress. After a
timeout, however, the current window layout is conserved as a new rules
report and the state goes back to IDLE.
For clients that takes a very long time (in particular, VirtualBox when
resizing the desktop, which takes sometimes multiple seconds), the
snap-back artifact can still occur, but the effect is reduced.
2019-06-18 17:05:17 +02:00
|
|
|
_drag_state = Drag_state::SETTLING;
|
2018-09-26 11:36:36 +02:00
|
|
|
|
2019-03-14 14:15:58 +01:00
|
|
|
_drop_timer.trigger_once(250*1000);
|
2018-09-26 11:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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();
|
2020-01-09 12:59:05 +01:00
|
|
|
void _gen_rules_with_frontmost_screen(Target::Name const &);
|
|
|
|
|
|
|
|
void _gen_rules()
|
|
|
|
{
|
|
|
|
/* keep order of screens unmodified */
|
|
|
|
_gen_rules_with_frontmost_screen(Target::Name());
|
|
|
|
}
|
2018-09-26 11:36:36 +02:00
|
|
|
|
|
|
|
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();
|
|
|
|
|
2019-03-14 14:15:58 +01:00
|
|
|
_drop_timer.sigh(_drop_timer_handler);
|
|
|
|
|
2018-09-26 11:36:36 +02:00
|
|
|
_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;
|
2019-03-16 23:50:09 +01:00
|
|
|
_assign_list.for_each([&] (Assign const &assign) {
|
|
|
|
assign.for_each_member([&] (Assign::Member const &member) {
|
|
|
|
if (member.window.resize_request_needed())
|
|
|
|
resize_needed = true; }); });
|
2018-09-26 11:36:36 +02:00
|
|
|
|
|
|
|
if (!resize_needed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
_resize_request_reporter.generate([&] (Xml_generator &xml) {
|
|
|
|
_window_list.for_each_window([&] (Window const &window) {
|
2018-11-27 11:25:56 +01:00
|
|
|
window.gen_resize_request(xml); }); });
|
2018-09-26 11:36:36 +02:00
|
|
|
|
2018-11-27 11:25:56 +01:00
|
|
|
/* prevent superfluous resize requests for the same size */
|
|
|
|
_window_list.for_each_window([&] (Window &window) {
|
|
|
|
window.resize_request_updated(); });
|
2018-09-26 11:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-03-16 23:50:09 +01:00
|
|
|
assign.gen_geometry_attr(xml, { .geometry = window.effective_inner_geometry(),
|
|
|
|
.maximized = window.maximized() });
|
2018-09-26 11:36:36 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-01-09 12:59:05 +01:00
|
|
|
void Window_layouter::Main::_gen_rules_with_frontmost_screen(Target::Name const &screen)
|
2018-09-26 11:36:36 +02:00
|
|
|
{
|
|
|
|
if (!_rules_reporter.constructed())
|
|
|
|
return;
|
|
|
|
|
|
|
|
_rules_reporter->generate([&] (Xml_generator &xml) {
|
|
|
|
|
2020-01-09 12:59:05 +01:00
|
|
|
_target_list.gen_screens(xml, screen);
|
2018-09-26 11:36:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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();
|
|
|
|
|
2019-01-02 15:07:52 +01:00
|
|
|
User_state::Hover_state const orig_hover_state = _user_state.hover_state();
|
|
|
|
|
2018-09-26 11:36:36 +02:00
|
|
|
try {
|
|
|
|
Xml_node const hover_window_xml = _hover.xml().sub_node("window");
|
|
|
|
|
2019-01-21 15:46:48 +01:00
|
|
|
_user_state.hover(hover_window_xml.attribute_value("id", 0UL),
|
2018-09-26 11:36:36 +02:00
|
|
|
_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.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
2019-01-02 15:07:52 +01:00
|
|
|
/*
|
|
|
|
* 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();
|
2018-09-26 11:36:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|