diff --git a/repos/gems/recipes/pkg/themed_wm/runtime b/repos/gems/recipes/pkg/themed_wm/runtime index 14d5a90859..fdee664b81 100644 --- a/repos/gems/recipes/pkg/themed_wm/runtime +++ b/repos/gems/recipes/pkg/themed_wm/runtime @@ -13,7 +13,7 @@ - + diff --git a/repos/gems/recipes/pkg/wm/archives b/repos/gems/recipes/pkg/wm/archives index cdbdb871c4..e52aacc360 100644 --- a/repos/gems/recipes/pkg/wm/archives +++ b/repos/gems/recipes/pkg/wm/archives @@ -2,4 +2,4 @@ _/raw/wm _/src/wm _/src/report_rom _/src/decorator -_/src/floating_window_layouter +_/src/window_layouter diff --git a/repos/gems/recipes/pkg/wm/runtime b/repos/gems/recipes/pkg/wm/runtime index d1d81704ce..c01a562751 100644 --- a/repos/gems/recipes/pkg/wm/runtime +++ b/repos/gems/recipes/pkg/wm/runtime @@ -8,7 +8,7 @@ - + diff --git a/repos/gems/recipes/raw/wm/layouter.config b/repos/gems/recipes/raw/wm/layouter.config index ce59459a9c..06aaf9ae3f 100644 --- a/repos/gems/recipes/raw/wm/layouter.config +++ b/repos/gems/recipes/raw/wm/layouter.config @@ -1,18 +1,25 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/gems/recipes/raw/wm/wm.config b/repos/gems/recipes/raw/wm/wm.config index 0d29da8e59..cd558c071d 100644 --- a/repos/gems/recipes/raw/wm/wm.config +++ b/repos/gems/recipes/raw/wm/wm.config @@ -27,6 +27,7 @@ + @@ -56,7 +57,7 @@ - + @@ -64,6 +65,7 @@ + diff --git a/repos/gems/recipes/src/floating_window_layouter/content.mk b/repos/gems/recipes/src/window_layouter/content.mk similarity index 84% rename from repos/gems/recipes/src/floating_window_layouter/content.mk rename to repos/gems/recipes/src/window_layouter/content.mk index 16cc6f6f47..6584a2af41 100644 --- a/repos/gems/recipes/src/floating_window_layouter/content.mk +++ b/repos/gems/recipes/src/window_layouter/content.mk @@ -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) diff --git a/repos/gems/recipes/src/floating_window_layouter/hash b/repos/gems/recipes/src/window_layouter/hash similarity index 100% rename from repos/gems/recipes/src/floating_window_layouter/hash rename to repos/gems/recipes/src/window_layouter/hash diff --git a/repos/gems/recipes/src/floating_window_layouter/used_apis b/repos/gems/recipes/src/window_layouter/used_apis similarity index 100% rename from repos/gems/recipes/src/floating_window_layouter/used_apis rename to repos/gems/recipes/src/window_layouter/used_apis diff --git a/repos/gems/run/launcher.run b/repos/gems/run/launcher.run index 54c7887ec3..e49469c090 100644 --- a/repos/gems/run/launcher.run +++ b/repos/gems/run/launcher.run @@ -159,7 +159,7 @@ install_config { - + diff --git a/repos/gems/run/leitzentrale.run b/repos/gems/run/leitzentrale.run index 731526712d..a32dac9db2 100644 --- a/repos/gems/run/leitzentrale.run +++ b/repos/gems/run/leitzentrale.run @@ -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 { diff --git a/repos/gems/src/app/floating_window_layouter/README b/repos/gems/src/app/floating_window_layouter/README deleted file mode 100644 index 6c111eadbe..0000000000 --- a/repos/gems/src/app/floating_window_layouter/README +++ /dev/null @@ -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: - -! -! -! -! - - -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. - -! -! ... -! -! ... -! -! ... -! -! ... -! - -The response of the window layouter to key sequences can be expressed in the -layouter configuration as follows: - -! -! -! -! -! -! -! -! -! -! -! -! -! -! -! -! -! - -Each '' 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 '' 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. - - diff --git a/repos/gems/src/app/floating_window_layouter/main.cc b/repos/gems/src/app/floating_window_layouter/main.cc deleted file mode 100644 index 0ab5ff8e01..0000000000 --- a/repos/gems/src/app/floating_window_layouter/main.cc +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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
config_dispatcher { - env.ep(), *this, &Main::handle_config }; - - Genode::Heap heap { env.ram(), env.rm() }; - - Genode::Tslab window_slab { &heap }; - - List 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 - 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
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
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
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
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.flush(), config.xml()); - } - - Signal_handler
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
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); } diff --git a/repos/gems/src/app/floating_window_layouter/target.mk b/repos/gems/src/app/floating_window_layouter/target.mk deleted file mode 100644 index 1002ce1123..0000000000 --- a/repos/gems/src/app/floating_window_layouter/target.mk +++ /dev/null @@ -1,6 +0,0 @@ -TARGET = floating_window_layouter -SRC_CC = main.cc -LIBS = base - - -CC_CXX_WARN_STRICT = diff --git a/repos/gems/src/app/window_layouter/README b/repos/gems/src/app/window_layouter/README new file mode 100644 index 0000000000..fd1a674553 --- /dev/null +++ b/repos/gems/src/app/window_layouter/README @@ -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. + +! +! +! +! ...definition of screen layout... +! +! +! ,,, +! +! ... +! + +The '' node can host any number of '' 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 '' 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 '' can, in turn, +contain '' nodes, thereby further subdividing the screen. +Each '' or '' can be used as window-placement target when +equipped with a 'name' attribute. Each name must occur only once within +the ''. 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 '' nodes. Each +'' 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 '' node takes effect. + +Each '' 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 +'' rules defines their stacking order. The window with earliest +'' rule is displayed in front. + + +Dynamic layouts +--------------- + +The window layouter is able to respond to rule changes at runtime. + +By specifying the '' 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 '' sub node of the '' 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 '' sub +node of the configuration: + +! +! +! ... +! + +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 '' rule +(one that uses a 'label_prefix' or 'label_suffix'), the layouter generates a +new '' rule with the window's label as 'label' attribute. The +explicitly labeled '' rules appear before any wildcard '' +rules. + +If the user brings a window to front, the window layouter will change the order +of the explicit '' rules such that the window's '' 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 '' 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. + +! +! ... +! +! ... +! +! ... +! +! ... +! + +The response of the window layouter to key sequences can be expressed in the +layouter configuration as follows: + +! +! ... +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! + +Each '' 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 '' 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. + + diff --git a/repos/gems/src/app/floating_window_layouter/action.h b/repos/gems/src/app/window_layouter/action.h similarity index 93% rename from repos/gems/src/app/floating_window_layouter/action.h rename to repos/gems/src/app/window_layouter/action.h index 1c137ac609..2ef592482d 100644 --- a/repos/gems/src/app/floating_window_layouter/action.h +++ b/repos/gems/src/app/window_layouter/action.h @@ -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: diff --git a/repos/gems/src/app/window_layouter/assign.h b/repos/gems/src/app/window_layouter/assign.h new file mode 100644 index 0000000000..c70c02eb2a --- /dev/null +++ b/repos/gems/src/app/window_layouter/assign.h @@ -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 +#include +#include + +/* local includes */ +#include +#include +#include + +namespace Window_layouter { class Assign; } + + +class Window_layouter::Assign : public List_model::Element +{ + public: + + /* + * Used for associating windows with assignments. Hosted in 'Window' + * objects. + */ + struct Member : Registry::Element + { + Window &window; + + Member(Registry ®istry, Window &window) + : Registry::Element(registry, *this), window(window) { } + }; + + typedef String<80> Label; + + private: + + Registry _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' if label matches assignment + * + * This method is used for associating assignments to windows. + */ + template + 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 nodes of windows captured via wildcard + */ + template + 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 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 + void for_each_member(FN const &fn) { _members.for_each(fn); } + + template + void for_each_member(FN const &fn) const { _members.for_each(fn); } +}; + +#endif /* _ASSIGN_H_ */ diff --git a/repos/gems/src/app/window_layouter/assign_list.h b/repos/gems/src/app/window_layouter/assign_list.h new file mode 100644 index 0000000000..63fa84bbae --- /dev/null +++ b/repos/gems/src/app/window_layouter/assign_list.h @@ -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 +#include + +namespace Window_layouter { class Assign_list; } + + +class Window_layouter::Assign_list : Noncopyable +{ + private: + + Allocator &_alloc; + + List_model _assignments { }; + + struct Update_policy : List_model::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 ®istry) { + window.assignment(registry); }; + + assign.with_matching_members_registry(window.label(), fn); + }); + }); + } + + template + 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 + void for_each(FN const &fn) { _assignments.for_each(fn); } + + template + void for_each(FN const &fn) const { _assignments.for_each(fn); } +}; + +#endif /* _ASSIGN_LIST_H_ */ diff --git a/repos/gems/src/app/window_layouter/decorator_margins.h b/repos/gems/src/app/window_layouter/decorator_margins.h new file mode 100644 index 0000000000..ad21e6b1af --- /dev/null +++ b/repos/gems/src/app/window_layouter/decorator_margins.h @@ -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 + +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_ */ diff --git a/repos/gems/src/app/floating_window_layouter/focus_history.h b/repos/gems/src/app/window_layouter/focus_history.h similarity index 82% rename from repos/gems/src/app/floating_window_layouter/focus_history.h rename to repos/gems/src/app/window_layouter/focus_history.h index 8fad8d8d7a..3cd8af3d0c 100644 --- a/repos/gems/src/app/floating_window_layouter/focus_history.h +++ b/repos/gems/src/app/window_layouter/focus_history.h @@ -17,14 +17,14 @@ /* Genode includes */ #include -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::Element + struct Entry : List::Element { Focus_history &focus_history; Window_id const window_id; @@ -36,7 +36,7 @@ class Floating_window_layouter::Focus_history private: - Genode::List _entries; + List _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); } diff --git a/repos/gems/src/app/floating_window_layouter/key_sequence_tracker.h b/repos/gems/src/app/window_layouter/key_sequence_tracker.h similarity index 94% rename from repos/gems/src/app/floating_window_layouter/key_sequence_tracker.h rename to repos/gems/src/app/window_layouter/key_sequence_tracker.h index 30c40bf850..a2601d922c 100644 --- a/repos/gems/src/app/floating_window_layouter/key_sequence_tracker.h +++ b/repos/gems/src/app/window_layouter/key_sequence_tracker.h @@ -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(""); @@ -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: diff --git a/repos/gems/src/app/window_layouter/layout_rules.h b/repos/gems/src/app/window_layouter/layout_rules.h new file mode 100644 index 0000000000..230458d4d1 --- /dev/null +++ b/repos/gems/src/app/window_layouter/layout_rules.h @@ -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 + +/* local includes */ +#include +#include + +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 _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 _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 { }; + + 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 '' 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 '' node. + */ + template + 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("")); + } +}; + +#endif /* _LAYOUT_RULES_H_ */ diff --git a/repos/gems/src/app/window_layouter/main.cc b/repos/gems/src/app/window_layouter/main.cc new file mode 100644 index 0000000000..9cc35274e7 --- /dev/null +++ b/repos/gems/src/app/window_layouter/main.cc @@ -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 +#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 }; + + 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 }; + + 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
_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(); + + template + 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 +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 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(); + + 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); +} diff --git a/repos/gems/src/app/floating_window_layouter/operations.h b/repos/gems/src/app/window_layouter/operations.h similarity index 75% rename from repos/gems/src/app/floating_window_layouter/operations.h rename to repos/gems/src/app/window_layouter/operations.h index 5950a00213..883267fdb4 100644 --- a/repos/gems/src/app/floating_window_layouter/operations.h +++ b/repos/gems/src/app/window_layouter/operations.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. @@ -14,13 +14,16 @@ #ifndef _OPERATIONS_H_ #define _OPERATIONS_H_ +/* Genode includes */ +#include + /* 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; diff --git a/repos/gems/src/app/window_layouter/target.h b/repos/gems/src/app/window_layouter/target.h new file mode 100644 index 0000000000..a8272a2454 --- /dev/null +++ b/repos/gems/src/app/window_layouter/target.h @@ -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 + +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' */ + virtual ~Target() { } + + Name name() const { return _name; } + unsigned layer() const { return _layer; } + Rect geometry() const { return _geometry; } +}; + +#endif /* _TARGET_H_ */ diff --git a/repos/gems/src/app/window_layouter/target.mk b/repos/gems/src/app/window_layouter/target.mk new file mode 100644 index 0000000000..3f3d2bd15b --- /dev/null +++ b/repos/gems/src/app/window_layouter/target.mk @@ -0,0 +1,4 @@ +TARGET = window_layouter +SRC_CC = main.cc +INC_DIR += $(PRG_DIR) +LIBS = base diff --git a/repos/gems/src/app/window_layouter/target_list.h b/repos/gems/src/app/window_layouter/target_list.h new file mode 100644 index 0000000000..69f63e6d7c --- /dev/null +++ b/repos/gems/src/app/window_layouter/target_list.h @@ -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 + +namespace Window_layouter { class Target_list; } + + +class Window_layouter::Target_list +{ + private: + + Allocator &_alloc; + + Registry > _targets { }; + + /* + * Keep information of the rules internally to reproduce this + * information in 'gen_screens'. + */ + Constructible _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(_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 + * node. Subseqent nodes are ignored. The node may + * contain any number of nodes. Each node may contain + * any number of nodes, which, in turn, can contain + * nodes. + */ + void update_from_xml(Xml_node rules, Area screen_size) + { + _targets.for_each([&] (Registered &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(_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 + void for_each(FN const &fn) const { _targets.for_each(fn); } +}; + +#endif /* _TARGET_LIST_H_ */ diff --git a/repos/gems/src/app/floating_window_layouter/types.h b/repos/gems/src/app/window_layouter/types.h similarity index 86% rename from repos/gems/src/app/floating_window_layouter/types.h rename to repos/gems/src/app/window_layouter/types.h index 67abc24c69..203a8d3fa6 100644 --- a/repos/gems/src/app/floating_window_layouter/types.h +++ b/repos/gems/src/app/window_layouter/types.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 +#include -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_ */ diff --git a/repos/gems/src/app/floating_window_layouter/user_state.h b/repos/gems/src/app/window_layouter/user_state.h similarity index 93% rename from repos/gems/src/app/floating_window_layouter/user_state.h rename to repos/gems/src/app/window_layouter/user_state.h index feaa906c78..0a1aef1d14 100644 --- a/repos/gems/src/app/floating_window_layouter/user_state.h +++ b/repos/gems/src/app/window_layouter/user_state.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"); } }); } diff --git a/repos/gems/src/app/floating_window_layouter/window.h b/repos/gems/src/app/window_layouter/window.h similarity index 52% rename from repos/gems/src/app/floating_window_layouter/window.h rename to repos/gems/src/app/window_layouter/window.h index dda35dbcc7..890d339c4e 100644 --- a/repos/gems/src/app/floating_window_layouter/window.h +++ b/repos/gems/src/app/window_layouter/window.h @@ -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 + /* local includes */ -#include "types.h" -#include "focus_history.h" +#include +#include +#include +#include -namespace Floating_window_layouter { class Window; } +namespace Window_layouter { class Window; } -class Floating_window_layouter::Window : public List::Element +class Window_layouter::Window : public List_model::Element { public: @@ -66,16 +71,25 @@ class Floating_window_layouter::Window : public List::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::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::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 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::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::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::Element || _drag_top_border || _drag_bottom_border; } + Constructible _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::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::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::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 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::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 definition + */ + void assignment(Registry ®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); } }; diff --git a/repos/gems/src/app/window_layouter/window_list.h b/repos/gems/src/app/window_layouter/window_list.h new file mode 100644 index 0000000000..1a7ca22b64 --- /dev/null +++ b/repos/gems/src/app/window_layouter/window_list.h @@ -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 +#include + +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 _list { }; + + Attached_rom_dataspace _rom { _env, "window_list" }; + + Signal_handler _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::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 + void with_window(Window_id id, FN const &fn) + { + _list.for_each([&] (Window &win) { + if (win.has_id(id)) + fn(win); }); + } + + template + void with_window(Window_id id, FN const &fn) const + { + _list.for_each([&] (Window const &win) { + if (win.has_id(id)) + fn(win); }); + } + + template + void for_each_window(FN const &fn) { _list.for_each(fn); } + + template + void for_each_window(FN const &fn) const { _list.for_each(fn); } +}; + +#endif /* _WINDOW_LIST_H_ */ diff --git a/repos/libports/run/qt5_common.inc b/repos/libports/run/qt5_common.inc index 979ca45275..45e4f9afac 100644 --- a/repos/libports/run/qt5_common.inc +++ b/repos/libports/run/qt5_common.inc @@ -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 } { - +