/* * \brief Floating window layouter * \author Norman Feske * \date 2013-02-14 */ /* * Copyright (C) 2013-2014 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ /* Genode includes */ #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 { Signal_receiver &sig_rec; List windows; 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 }; /************************** ** 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_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->is_maximized(!window->is_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(unsigned); Signal_dispatcher
window_list_dispatcher = { sig_rec, *this, &Main::handle_window_list_update }; Attached_rom_dataspace window_list { "window_list" }; /** * Install handler for responding to focus requests */ void handle_focus_request_update(unsigned); void _apply_focus_request(); int handled_focus_request_id = 0; Signal_dispatcher
focus_request_dispatcher = { sig_rec, *this, &Main::handle_focus_request_update }; Attached_rom_dataspace focus_request { "focus_request" }; /** * Install handler for responding to hover changes */ void handle_hover_update(unsigned); Signal_dispatcher
hover_dispatcher = { sig_rec, *this, &Main::handle_hover_update }; Attached_rom_dataspace hover { "hover" }; /** * Install handler for responding to user input */ void handle_input(unsigned) { while (input.is_pending()) _user_state.handle_input(input_ds.local_addr(), input.flush()); } Signal_dispatcher
input_dispatcher = { sig_rec, *this, &Main::handle_input }; Nitpicker::Connection nitpicker; Rect maximized_window_geometry; void handle_mode_change(unsigned) { /* determine maximized window geometry */ Framebuffer::Mode const mode = nitpicker.mode(); /* * XXX obtain decorator constraints dynamically */ enum { PAD_LEFT = 4, PAD_RIGHT = 4, PAD_TOP = 20, PAD_BOTTOM = 4 }; maximized_window_geometry = Rect(Point(PAD_LEFT, PAD_TOP), Area(mode.width() - PAD_LEFT - PAD_RIGHT, mode.height() - PAD_TOP - PAD_BOTTOM)); } Signal_dispatcher
mode_change_dispatcher = { sig_rec, *this, &Main::handle_mode_change }; Input::Session_client input { nitpicker.input_session() }; Attached_dataspace input_ds { input.dataspace() }; Reporter window_layout_reporter = { "window_layout" }; Reporter resize_request_reporter = { "resize_request" }; Reporter focus_reporter = { "focus" }; bool focused_window_is_maximized() const { Window const *w = lookup_window_by_id(_user_state.focused_window_id()); return w && w->is_maximized(); } void import_window_list(Xml_node); void generate_window_layout_model(); void generate_resize_request_model(); void generate_focus_model(); /** * Constructor */ Main(Signal_receiver &sig_rec) : sig_rec(sig_rec) { nitpicker.mode_sigh(mode_change_dispatcher); handle_mode_change(0); window_list.sigh(window_list_dispatcher); focus_request.sigh(focus_request_dispatcher); hover.sigh(hover_dispatcher); input.sigh(input_dispatcher); window_layout_reporter.enabled(true); resize_request_reporter.enabled(true); focus_reporter.enabled(true); } }; 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(env()->heap(), 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); Window *win = lookup_window_by_id(id); if (!win) { win = new (env()->heap()) Window(id, maximized_window_geometry); 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); if (policy.has_attribute("xpos") && policy.has_attribute("ypos")) initial_position = point_attribute(node); win->is_maximized(policy.attribute_value("maximized", false)); } catch (Genode::Session_policy::No_policy_defined) { } win->position(initial_position); } win->size(area_attribute(node)); win->title(string_attribute(node, "title", Window::Title(""))); win->has_alpha(node.has_attribute("has_alpha") && node.attribute("has_alpha").has_value("yes")); win->is_hidden(node.has_attribute("hidden") && node.attribute("hidden").has_value("yes")); win->is_resizeable(node.has_attribute("resizeable") && node.attribute("resizeable").has_value("yes")); } } 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 is_hovered = w->has_id(_user_state.hover_state().window_id); bool const is_focused = w->has_id(_user_state.focused_window_id()); Window::Element const highlight = is_hovered ? _user_state.hover_state().element : Window::Element::UNDEFINED; w->serialize(xml, is_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(unsigned) { window_list.update(); try { import_window_list(Xml_node(window_list.local_addr())); } catch (...) { PERR("Error while importing window list"); } generate_window_layout_model(); } void Floating_window_layouter::Main::_apply_focus_request() { try { Xml_node node(focus_request.local_addr()); Window::Label const label = node.attribute_value("label", Window::Label("")); int const id = node.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; } catch (...) { PERR("Error while handling focus request"); } } void Floating_window_layouter::Main::handle_focus_request_update(unsigned) { focus_request.update(); _apply_focus_request(); generate_window_layout_model(); } void Floating_window_layouter::Main::handle_hover_update(unsigned) { hover.update(); try { Xml_node const hover_xml(hover.local_addr()); 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 missing or 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(); } int main(int argc, char **argv) { static Genode::Signal_receiver sig_rec; static Floating_window_layouter::Main application(sig_rec); /* import initial state */ application.handle_window_list_update(0); /* process incoming signals */ for (;;) { using namespace Genode; Signal sig = sig_rec.wait_for_signal(); Signal_dispatcher_base *dispatcher = dynamic_cast(sig.context()); if (dispatcher) dispatcher->dispatch(sig.num()); } }