/* * \brief Window layouter * \author Norman Feske * \date 2013-02-14 */ /* * Copyright (C) 2013-2018 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ /* Genode includes */ #include #include #include #include #include #include #include #include #include #include #include #include /* local includes */ #include #include #include #include #include namespace Window_layouter { struct Main; } struct Window_layouter::Main : Operations, Layout_rules::Change_handler, Window_list::Change_handler { Env &_env; Attached_rom_dataspace _config { _env, "config" }; Signal_handler
_config_handler { _env.ep(), *this, &Main::_handle_config }; Timer::Connection _drop_timer { _env }; enum class Drag_state { IDLE, DRAGGING, SETTLING }; Drag_state _drag_state { Drag_state::IDLE }; Signal_handler
_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("") }; 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) { if (target.name() != assign.target_name()) return; 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
_hover_handler { _env.ep(), *this, &Main::_handle_hover}; Attached_rom_dataspace _hover { _env, "hover" }; void _handle_focus_request(); Signal_handler
_focus_request_handler { _env.ep(), *this, &Main::_handle_focus_request }; Attached_rom_dataspace _focus_request { _env, "focus_request" }; int _handled_focus_request_id = 0; /** * Respond to decorator-margins information reported by the decorator */ Attached_rom_dataspace _decorator_margins_rom { _env, "decorator_margins" }; void _handle_decorator_margins() { _decorator_margins_rom.update(); Xml_node const margins = _decorator_margins_rom.xml(); if (margins.has_sub_node("floating")) _decorator_margins = Decorator_margins(margins.sub_node("floating")); /* respond to change by adapting the maximized window geometry */ _handle_mode_change(); } Signal_handler
_decorator_margins_handler { _env.ep(), *this, &Main::_handle_decorator_margins}; /** * Install handler for responding to user input */ void _handle_input() { while (_input.pending()) _user_state.handle_input(_input_ds.local_addr(), _input.flush(), _config.xml()); } Signal_handler
_input_handler { _env.ep(), *this, &Main::_handle_input }; Nitpicker::Connection _nitpicker { _env }; void _handle_mode_change() { /* determine maximized window geometry */ Framebuffer::Mode const mode = _nitpicker.mode(); _screen_size = Area(mode.width(), mode.height()); _update_window_layout(); } Signal_handler
_mode_change_handler { _env.ep(), *this, &Main::_handle_mode_change }; Input::Session_client _input { _env.rm(), _nitpicker.input_session() }; Attached_dataspace _input_ds { _env.rm(), _input.dataspace() }; Expanding_reporter _window_layout_reporter { _env, "window_layout", "window_layout"}; Expanding_reporter _resize_request_reporter { _env, "resize_request", "resize_request"}; Expanding_reporter _focus_reporter { _env, "focus", "focus" }; Constructible _rules_reporter { }; bool _focused_window_maximized() const { bool result = false; Window_id const id = _user_state.focused_window_id(); _window_list.with_window(id, [&] (Window const &window) { result = window.maximized(); }); return result; } void _import_window_list(Xml_node); void _gen_window_layout(); void _gen_resize_request(); void _gen_focus(); void _gen_rules_with_frontmost_screen(Target::Name const &); void _gen_rules() { /* keep order of screens unmodified */ _gen_rules_with_frontmost_screen(Target::Name()); } template 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 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 nodes for present windows. * * The nodes are generated such that front-most windows appear * before all other windows. The change of the stacking order * is applied when the generated rules are imported the next time. */ auto front_most = [&] (Window const &window) { return (window.to_front_cnt() == _to_front_cnt); }; auto behind_front = [&] (Window const &window) { return !front_most(window); }; _gen_rules_assignments(xml, front_most); _gen_rules_assignments(xml, behind_front); /* keep attributes of wildcards and (currently) unused assignments */ _assign_list.for_each([&] (Assign const &assign) { bool no_window_assigned = true; assign.for_each_member([&] (Assign::Member const &) { no_window_assigned = false; }); /* * If a window is present that matches the assignment, the * node was already generated by '_gen_rules_assignments' above. */ if (assign.wildcard() || no_window_assigned) { xml.node("assign", [&] () { assign.gen_assign_attr(xml); if (assign.floating()) assign.gen_geometry_attr(xml); }); } }); }); } /** * Determine window element that corresponds to hover model */ static Window_layouter::Window::Element _element_from_hover_model(Genode::Xml_node hover_window_xml) { typedef Window_layouter::Window::Element::Type Type; bool const left_sizer = hover_window_xml.has_sub_node("left_sizer"), right_sizer = hover_window_xml.has_sub_node("right_sizer"), top_sizer = hover_window_xml.has_sub_node("top_sizer"), bottom_sizer = hover_window_xml.has_sub_node("bottom_sizer"); if (left_sizer && top_sizer) return Type::TOP_LEFT; if (left_sizer && bottom_sizer) return Type::BOTTOM_LEFT; if (left_sizer) return Type::LEFT; if (right_sizer && top_sizer) return Type::TOP_RIGHT; if (right_sizer && bottom_sizer) return Type::BOTTOM_RIGHT; if (right_sizer) return Type::RIGHT; if (top_sizer) return Type::TOP; if (bottom_sizer) return Type::BOTTOM; if (hover_window_xml.has_sub_node("title")) return Type::TITLE; if (hover_window_xml.has_sub_node("closer")) return Type::CLOSER; if (hover_window_xml.has_sub_node("maximizer")) return Type::MAXIMIZER; if (hover_window_xml.has_sub_node("minimizer")) return Type::MINIMIZER; return Type::UNDEFINED; } void Window_layouter::Main::_handle_hover() { _hover.update(); 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); }