From acd9599890644ea80f0eec2ab0b3bb6942fb0071 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Mon, 6 Jan 2014 19:15:14 +0100 Subject: [PATCH] Window manager The window manager provides a nitpicker session interface. In contrast to the nitpicker server, which leaves the view layout up to the client, the window manager organizes the views on screen according to a policy provided by a window layouter. Furthermore, it equips views with window decorations as provided by a window decorator. Both layouter and decorator are independent programs. --- repos/gems/run/wm.run | 224 ++++ .../src/app/floating_window_layouter/main.cc | 683 +++++++++++ .../app/floating_window_layouter/target.mk | 4 + .../gems/src/server/wm/decorator_nitpicker.h | 473 ++++++++ repos/gems/src/server/wm/decorator_slave.h | 128 +++ repos/gems/src/server/wm/local_reporter.h | 52 + repos/gems/src/server/wm/main.cc | 185 +++ repos/gems/src/server/wm/nitpicker.h | 1005 +++++++++++++++++ repos/gems/src/server/wm/report_rom_slave.h | 173 +++ .../src/server/wm/single_session_service.h | 41 + repos/gems/src/server/wm/target.mk | 4 + .../src/server/wm/window_layouter_slave.h | 137 +++ repos/gems/src/server/wm/window_registry.h | 188 +++ 13 files changed, 3297 insertions(+) create mode 100644 repos/gems/run/wm.run create mode 100644 repos/gems/src/app/floating_window_layouter/main.cc create mode 100644 repos/gems/src/app/floating_window_layouter/target.mk create mode 100644 repos/gems/src/server/wm/decorator_nitpicker.h create mode 100644 repos/gems/src/server/wm/decorator_slave.h create mode 100644 repos/gems/src/server/wm/local_reporter.h create mode 100644 repos/gems/src/server/wm/main.cc create mode 100644 repos/gems/src/server/wm/nitpicker.h create mode 100644 repos/gems/src/server/wm/report_rom_slave.h create mode 100644 repos/gems/src/server/wm/single_session_service.h create mode 100644 repos/gems/src/server/wm/target.mk create mode 100644 repos/gems/src/server/wm/window_layouter_slave.h create mode 100644 repos/gems/src/server/wm/window_registry.h diff --git a/repos/gems/run/wm.run b/repos/gems/run/wm.run new file mode 100644 index 000000000..7bc8fdc0e --- /dev/null +++ b/repos/gems/run/wm.run @@ -0,0 +1,224 @@ +# +# Build +# + +set build_components { + core init + drivers/timer + server/wm app/decorator app/floating_window_layouter + server/nitpicker app/pointer server/report_rom + drivers/framebuffer drivers/pci drivers/input + test/nitpicker + app/launchpad + server/nit_fb +} + +lappend_if [have_spec usb] build_components drivers/usb +lappend_if [have_spec gpio] build_components drivers/gpio +lappend_if [have_spec imx53] build_components drivers/platform +lappend_if [have_spec exynos5] build_components drivers/platform +lappend_if [have_spec platform_rpi] build_components drivers/platform + +build $build_components + +create_boot_directory + +# +# Generate config +# + +append config { + + + + + + + + + + + + + + + + + } + +append_if [have_spec sdl] config { + + + + + + + } + +append_if [have_spec pci] config { + + + + } + +append_if [have_spec framebuffer] config { + + + + } + +append_if [have_spec gpio] config { + + + + + } + +append_if [have_spec exynos5] config { + + + + + } + +append_if [have_spec platform_rpi] config { + + + + + } + +append_if [have_spec imx53] config { + + + + + + + + + + } + +append_if [have_spec ps2] config { + + + + } + +append_if [expr ![have_spec ps2] && [have_spec usb]] config { + + + + + } + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + + +# +# Boot modules +# + +# generic modules +set boot_modules { + core init + timer + wm decorator + floating_window_layouter + nitpicker pointer report_rom + testnit launchpad nit_fb +} + +# platform-specific modules +lappend_if [have_spec linux] boot_modules fb_sdl +lappend_if [have_spec pci] boot_modules pci_drv +lappend_if [have_spec ps2] boot_modules ps2_drv +lappend_if [have_spec framebuffer] boot_modules fb_drv +lappend_if [have_spec usb] boot_modules usb_drv +lappend_if [have_spec gpio] boot_modules gpio_drv +lappend_if [have_spec imx53] boot_modules platform_drv +lappend_if [have_spec exynos5] boot_modules platform_drv +lappend_if [have_spec platform_rpi] boot_modules platform_drv +lappend_if [have_spec imx53] boot_modules input_drv + +build_boot_image $boot_modules + +append qemu_args " -m 256 " + +run_genode_until forever diff --git a/repos/gems/src/app/floating_window_layouter/main.cc b/repos/gems/src/app/floating_window_layouter/main.cc new file mode 100644 index 000000000..81bb0cf9d --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/main.cc @@ -0,0 +1,683 @@ +/* + * \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 + + +namespace Floating_window_layouter { + + using namespace Genode; + struct Main; + class Window; + + typedef Decorator::Point Point; + typedef Decorator::Area Area; + typedef Decorator::Rect Rect; + + using Decorator::attribute; + using Decorator::string_attribute; + using Decorator::area_attribute; + + + static Xml_node xml_lookup_window_by_id(Xml_node node, unsigned 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) + 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, + unsigned const id) + { + try { xml_lookup_window_by_id(node, id); return true; } + catch (Xml_node::Nonexistent_sub_node) { return false; } + } +} + + +class Floating_window_layouter::Window : public List::Element +{ + public: + + typedef String<256> Title; + + struct Element + { + enum Type { UNDEFINED, TITLE, LEFT, RIGHT, TOP, BOTTOM, + TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; + + Type type; + + char const *name() const + { + switch (type) { + case UNDEFINED: return ""; + case TITLE: return "title"; + case LEFT: return "left"; + case RIGHT: return "right"; + case TOP: return "top"; + case BOTTOM: return "bottom"; + case TOP_LEFT: return "top_left"; + case TOP_RIGHT: return "top_right"; + case BOTTOM_LEFT: return "bottom_left"; + case BOTTOM_RIGHT: return "bottom_right"; + } + return ""; + } + + Element(Type type) : type(type) { } + + bool operator != (Element const &other) const { return other.type != type; } + }; + + private: + + unsigned const _id = 0; + + Title _title; + + Rect _geometry; + + /** + * Window geometry at the start of the current drag operation + */ + Rect _orig_geometry; + + /** + * Size as desired by the user during resize drag operations + */ + Area _requested_size; + + /* + * 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. + */ + unsigned _topped_cnt = 0; + + bool _drag_left_border = false; + bool _drag_right_border = false; + bool _drag_top_border = false; + bool _drag_bottom_border = false; + + public: + + Window(unsigned id) : _id(id) { } + + bool has_id(unsigned id) const { return id == _id; } + + unsigned id() const { return _id; } + + void title(Title const &title) { _title = title; } + + void geometry(Rect geometry) { _geometry = geometry; } + + Point position() const { return _geometry.p1(); } + + void position(Point pos) { _geometry = Rect(pos, _geometry.area()); } + + /** + * Return true if user drags a window border + */ + bool _drag_border() const + { + return _drag_left_border || _drag_right_border + || _drag_top_border || _drag_bottom_border; + } + + /** + * Define window size + * + * This function is called when the window-list model changes. + */ + void size(Area size) + { + if (!_drag_border()) { + _geometry = Rect(_geometry.p1(), size); + return; + } + + 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); + } + + Area size() const { return _geometry.area(); } + + Area requested_size() const { return _requested_size; } + + void serialize(Xml_generator &xml, bool focused, Element highlight) + { + xml.node("window", [&]() { + xml.attribute("id", _id); + xml.attribute("title", _title.string()); + 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); + + if (focused) + xml.attribute("focused", "yes"); + + if (highlight.type != Element::UNDEFINED) { + xml.node("highlight", [&] () { + xml.node(highlight.name()); + }); + } + }); + } + + /** + * Called when the user starts dragging a window element + */ + void initiate_drag_operation(Window::Element element) + { + _drag_left_border = (element.type == Window::Element::LEFT) + || (element.type == Window::Element::TOP_LEFT) + || (element.type == Window::Element::BOTTOM_LEFT); + + _drag_right_border = (element.type == Window::Element::RIGHT) + || (element.type == Window::Element::TOP_RIGHT) + || (element.type == Window::Element::BOTTOM_RIGHT); + + _drag_top_border = (element.type == Window::Element::TOP) + || (element.type == Window::Element::TOP_LEFT) + || (element.type == Window::Element::TOP_RIGHT); + + _drag_bottom_border = (element.type == Window::Element::BOTTOM) + || (element.type == Window::Element::BOTTOM_LEFT) + || (element.type == Window::Element::BOTTOM_RIGHT); + + _orig_geometry = _geometry; + + _requested_size = _geometry.area(); + } + + void apply_drag_operation(Point offset) + { + if (!_drag_border()) + position(_orig_geometry.p1() + offset); + + int requested_w = _orig_geometry.w(), + requested_h = _orig_geometry.h(); + + 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(); + + _requested_size = Area(max(1, requested_w), max(1, requested_h)); + } + + void finalize_drag_operation() + { + _requested_size = _geometry.area(); + } + + void topped() { _topped_cnt++; } +}; + + +struct Floating_window_layouter::Main +{ + Signal_receiver &sig_rec; + + List windows; + + unsigned focused_window_id = 0; + unsigned key_cnt = 0; + + Window::Element hovered_element = Window::Element::UNDEFINED; + + bool drag_state = false; + + Window *lookup_window_by_id(unsigned id) + { + for (Window *w = windows.first(); w; w = w->next()) + if (w->has_id(id)) + return w; + + return nullptr; + } + + + /** + * 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 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); + + Signal_dispatcher
input_dispatcher = { + sig_rec, *this, &Main::handle_input }; + + Input::Connection input; + + Attached_dataspace input_ds { input.dataspace() }; + + Reporter window_layout_reporter = { "window_layout" }; + Reporter resize_request_reporter = { "resize_request" }; + Reporter focus_reporter = { "focus" }; + + unsigned dragged_window_id = 0; + + Point pointer_clicked; + Point pointer_last; + Point pointer_curr; + + void import_window_list(Xml_node); + void generate_window_layout_model(); + void generate_resize_request_model(); + void generate_focus_model(); + void initiate_window_drag(Window &window); + + /** + * Constructor + */ + Main(Signal_receiver &sig_rec) : sig_rec(sig_rec) + { + window_list.sigh(window_list_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); + windows.insert(win); + + /* + * Define initial window position + */ + win->position(Point(150*id % 800, 30 + (100*id % 500))); + } + + win->size(area_attribute(node)); + win->title(string_attribute(node, "title", Window::Title("untitled"))); + } + } 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_focused = w->has_id(focused_window_id); + + Window::Element const highlight = + is_focused ? hovered_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, [&] () + { + Window const *dragged_window = lookup_window_by_id(dragged_window_id); + if (dragged_window) { + + Area const requested_size = dragged_window->requested_size(); + if (requested_size != dragged_window->size()) { + xml.node("window", [&] () { + xml.attribute("id", dragged_window_id); + 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", focused_window_id); + }); + }); +} + + +/** + * 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; + + return Type::UNDEFINED; +} + + +void Floating_window_layouter::Main::initiate_window_drag(Window &window) +{ + window.initiate_drag_operation(hovered_element); + + /* bring focused window to front */ + if (&window != windows.first()) { + windows.remove(&window); + windows.insert(&window); + } + + window.topped(); +} + + +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::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"); + + unsigned const id = attribute(hover_window_xml, "id", 0UL); + + Window::Element hovered = element_from_hover_model(hover_window_xml); + + /* + * Check if we have just received an update while already being in + * dragged state. + * + * This can happen when the user selects a new nitpicker domain by + * clicking on a window decoration. Prior the click, the new session is + * not aware of the current mouse position. So the hover model is not + * up to date. As soon as nitpicker assigns the focus to the new + * session and delivers the corresponding press event, we enter the + * drag state (in the 'handle_input' function. But we don't know which + * window is dragged until the decorator updates the hover model. Now, + * when the model is updated and we are still in dragged state, we can + * finally initiate the window-drag operation for the now-known window. + */ + if (id && drag_state && dragged_window_id == 0) + { + dragged_window_id = id; + focused_window_id = id; + + Window *window = lookup_window_by_id(id); + if (window) { + initiate_window_drag(*window); + generate_window_layout_model(); + generate_focus_model(); + } + } + + if (!drag_state && (id != focused_window_id || hovered != hovered_element)) { + + focused_window_id = id; + hovered_element = hovered; + generate_window_layout_model(); + generate_focus_model(); + } + } catch (...) { + + /* reset focused window if pointer does not hover over any window */ + if (!drag_state) { + hovered_element = Window::Element::UNDEFINED; + generate_window_layout_model(); + generate_focus_model(); + } + } +} + + +void Floating_window_layouter::Main::handle_input(unsigned) +{ + bool need_regenerate_window_layout_model = false; + bool need_regenerate_resize_request_model = false; + + Window *focused_window = lookup_window_by_id(focused_window_id); + + while (input.is_pending()) { + + size_t const num_events = input.flush(); + + Input::Event const * const ev = input_ds.local_addr(); + + for (size_t i = 0; i < num_events; i++) { + + Input::Event e = ev[i]; + + if (e.type() == Input::Event::MOTION + || e.type() == Input::Event::FOCUS) + pointer_curr = Point(e.ax(), e.ay()); + + /* track number of pressed buttons/keys */ + if (e.type() == Input::Event::PRESS) key_cnt++; + if (e.type() == Input::Event::RELEASE) key_cnt--; + + if (e.type() == Input::Event::PRESS + && e.keycode() == Input::BTN_LEFT) { + + drag_state = true; + dragged_window_id = focused_window_id; + pointer_clicked = pointer_curr; + pointer_last = pointer_clicked; + + /* + * If the focused window is known at the time of the press + * event, we can initiate the drag operation immediately. + * Otherwise, we the initiation is deferred to the next + * update of the hover model. + */ + if (focused_window) { + initiate_window_drag(*focused_window); + need_regenerate_window_layout_model = true; + } + } + + /* detect end of drag operation */ + if (e.type() == Input::Event::RELEASE) { + if (key_cnt == 0) { + drag_state = false; + generate_focus_model(); + + Window *dragged_window = lookup_window_by_id(dragged_window_id); + if (dragged_window) { + + Area const last_requested_size = dragged_window->requested_size(); + dragged_window->finalize_drag_operation(); + + if (last_requested_size != dragged_window->requested_size()) + need_regenerate_resize_request_model = true; + + /* + * 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. + */ + handle_hover_update(0); + } + } + } + } + } + + if (drag_state && (pointer_curr != pointer_last)) { + + pointer_last = pointer_curr; + + Window *dragged_window = lookup_window_by_id(dragged_window_id); + if (dragged_window) { + + Point const last_pos = dragged_window->position(); + Area const last_requested_size = dragged_window->requested_size(); + + dragged_window->apply_drag_operation(pointer_curr - pointer_clicked); + + if (last_pos != dragged_window->position()) + need_regenerate_window_layout_model = true; + + if (last_requested_size != dragged_window->requested_size()) + need_regenerate_resize_request_model = true; + } + } + + if (need_regenerate_window_layout_model) + generate_window_layout_model(); + + if (need_regenerate_resize_request_model) + generate_resize_request_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()); + } +} diff --git a/repos/gems/src/app/floating_window_layouter/target.mk b/repos/gems/src/app/floating_window_layouter/target.mk new file mode 100644 index 000000000..af9ad4b9c --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/target.mk @@ -0,0 +1,4 @@ +TARGET = floating_window_layouter +SRC_CC = main.cc +LIBS = base cxx + diff --git a/repos/gems/src/server/wm/decorator_nitpicker.h b/repos/gems/src/server/wm/decorator_nitpicker.h new file mode 100644 index 000000000..03b9d2a87 --- /dev/null +++ b/repos/gems/src/server/wm/decorator_nitpicker.h @@ -0,0 +1,473 @@ +/* + * \brief Local nitpicker service provided to decorator + * \author Norman Feske + * \date 2014-02-14 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _DECORATOR_NITPICKER_H_ +#define _DECORATOR_NITPICKER_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include + +/* local includes */ +#include + +namespace Wm { class Main; + using Genode::size_t; + using Genode::Allocator; + using Server::Entrypoint; + using Genode::Ram_session_client; + using Genode::Ram_session_capability; + using Genode::Arg_string; + using Genode::Object_pool; + using Genode::Attached_dataspace; + using Genode::Attached_ram_dataspace; + using Genode::Signal_rpc_member; +} + + +namespace Wm { + + struct Decorator_nitpicker_session; + struct Decorator_nitpicker_service; + struct Decorator_content_callback; + struct Decorator_content_registry; +} + + +struct Wm::Decorator_content_callback +{ + virtual void content_geometry(Window_registry::Id win_id, Rect rect) = 0; + + virtual Nitpicker::View_capability content_view(Window_registry::Id win_id) = 0; + + virtual void update_content_child_views(Window_registry::Id win_id) = 0; +}; + + +class Wm::Decorator_content_registry +{ + public: + + /** + * Exception type + */ + struct Lookup_failed { }; + + private: + + struct Entry : List::Element + { + Nitpicker::Session::View_handle const decorator_view_handle; + Window_registry::Id const win_id; + + Entry(Nitpicker::Session::View_handle decorator_view_handle, + Window_registry::Id win_id) + : + decorator_view_handle(decorator_view_handle), + win_id(win_id) + { } + }; + + List _list; + Allocator &_entry_alloc; + + Entry const &_lookup(Nitpicker::Session::View_handle view_handle) const + { + for (Entry const *e = _list.first(); e; e = e->next()) { + if (e->decorator_view_handle == view_handle) + return *e; + } + + throw Lookup_failed(); + } + + void _remove(Entry const &e) + { + _list.remove(&e); + destroy(_entry_alloc, const_cast(&e)); + } + + public: + + Decorator_content_registry(Allocator &entry_alloc) + : + _entry_alloc(entry_alloc) + { } + + ~Decorator_content_registry() + { + while (Entry *e = _list.first()) + _remove(*e); + } + + void insert(Nitpicker::Session::View_handle decorator_view_handle, + Window_registry::Id win_id) + { + Entry *e = new (_entry_alloc) Entry(decorator_view_handle, win_id); + _list.insert(e); + } + + /** + * Lookup window ID for a given decorator content view + * + * \throw Lookup_failed + */ + Window_registry::Id lookup(Nitpicker::Session::View_handle view_handle) const + { + return _lookup(view_handle).win_id; + } + + /** + * Remove entry + * + * \throw Lookup_failed + */ + void remove(Nitpicker::Session::View_handle view_handle) + { + _remove(_lookup(view_handle)); + } +}; + + +struct Wm::Decorator_nitpicker_session : Genode::Rpc_object +{ + typedef Nitpicker::View_capability View_capability; + typedef Nitpicker::Session::View_handle View_handle; + + Ram_session_client _ram; + + Nitpicker::Connection _nitpicker_session { "decorator" }; + + typedef Nitpicker::Session::Command_buffer Command_buffer; + + Attached_ram_dataspace _command_ds { &_ram, sizeof(Command_buffer) }; + + Command_buffer &_command_buffer = *_command_ds.local_addr(); + + Input::Session_client _nitpicker_input { _nitpicker_session.input_session() }; + + Attached_dataspace _nitpicker_input_ds { _nitpicker_input.dataspace() }; + + Local_reporter &_pointer_reporter; + + Input::Session_component &_window_layouter_input; + + Decorator_content_callback &_content_callback; + + /* XXX don't allocate content-registry entries from heap */ + Decorator_content_registry _content_registry { *Genode::env()->heap() }; + + Entrypoint &_ep; + + Allocator &_md_alloc; + + Signal_rpc_member + _input_dispatcher { _ep, *this, &Decorator_nitpicker_session::_input_handler }; + + /** + * Constructor + * + * \param ep entrypoint used for dispatching signals + */ + Decorator_nitpicker_session(Ram_session_capability ram, + Entrypoint &ep, Allocator &md_alloc, + Local_reporter &pointer_reporter, + Input::Session_component &window_layouter_input, + Decorator_content_callback &content_callback) + : + _ram(ram), + _pointer_reporter(pointer_reporter), + _window_layouter_input(window_layouter_input), + _content_callback(content_callback), + _ep(ep), _md_alloc(md_alloc) + { + _nitpicker_input.sigh(_input_dispatcher); + } + + void _input_handler(unsigned) + { + Input::Event const * const events = + _nitpicker_input_ds.local_addr(); + + while (_nitpicker_input.is_pending()) { + + size_t const num_events = _nitpicker_input.flush(); + + /* we trust nitpicker to return a valid number of events */ + + for (size_t i = 0; i < num_events; i++) { + + Input::Event const &ev = events[i]; + + if (ev.type() == Input::Event::MOTION) { + Local_reporter::Xml_generator xml(_pointer_reporter, [&] () + { + xml.attribute("xpos", ev.ax()); + xml.attribute("ypos", ev.ay()); + }); + } + + if (ev.type() == Input::Event::LEAVE) { + + Local_reporter::Xml_generator xml(_pointer_reporter, [&] () + { + /* report empty pointer model */ + }); + } + + _window_layouter_input.submit(ev); + } + } + } + + void _execute_command(Command const &cmd) + { + switch (cmd.opcode) { + + case Command::OP_TITLE: + { + unsigned long id = 0; + Genode::ascii_to(cmd.title.title.string(), &id); + + if (id > 0) + _content_registry.insert(cmd.title.view, + Window_registry::Id(id)); + return; + } + + case Command::OP_TO_FRONT: + + try { + + /* + * If the content view is re-stacked, replace it by the real + * window content. + * + * The lookup fails with an exception for non-content views. + * In this case, forward the command. + */ + Window_registry::Id win_id = _content_registry.lookup(cmd.to_front.view); + + /* + * Replace content view originally created by the decorator + * by view that shows the real window content. + */ + Nitpicker::View_capability view_cap = + _content_callback.content_view(win_id); + + _nitpicker_session.view_handle(view_cap, + cmd.to_front.view); + + _nitpicker_session.enqueue(cmd); + _nitpicker_session.execute(); + + /* + * Now that the physical content view exists, it is time + * to revisit the child views. + */ + _content_callback.update_content_child_views(win_id); + + } catch (Decorator_content_registry::Lookup_failed) { + + _nitpicker_session.enqueue(cmd); + } + + return; + + case Command::OP_GEOMETRY: + + try { + + /* + * If the content view changes position, propagate the new + * position to the nitpicker service to properly transform + * absolute input coordinates. + */ + Window_registry::Id win_id = _content_registry.lookup(cmd.geometry.view); + + _content_callback.content_geometry(win_id, cmd.geometry.rect); + } + catch (Decorator_content_registry::Lookup_failed) { } + + /* forward command */ + _nitpicker_session.enqueue(cmd); + return; + + case Command::OP_OFFSET: + + try { + + /* + * If non-content views change their offset (if the lookup + * fails), propagate the event + */ + _content_registry.lookup(cmd.geometry.view); + } + catch (Decorator_content_registry::Lookup_failed) { + _nitpicker_session.enqueue(cmd); + } + return; + + case Command::OP_TO_BACK: + case Command::OP_BACKGROUND: + case Command::OP_NOP: + + _nitpicker_session.enqueue(cmd); + return; + } + } + + void upgrade(const char *args) + { + Genode::env()->parent()->upgrade(_nitpicker_session, args); + } + + + /********************************* + ** Nitpicker session interface ** + *********************************/ + + Framebuffer::Session_capability framebuffer_session() override + { + return _nitpicker_session.framebuffer_session(); + } + + Input::Session_capability input_session() override + { + /* + * Deny input to the decorator. User input referring to the + * window decorations is routed to the window manager. + */ + return Input::Session_capability(); + } + + View_handle create_view(View_handle parent) override + { + return _nitpicker_session.create_view(parent); + } + + void destroy_view(View_handle view) override + { + _nitpicker_session.destroy_view(view); + } + + View_handle view_handle(View_capability view_cap, View_handle handle) override + { + return _nitpicker_session.view_handle(view_cap, handle); + } + + View_capability view_capability(View_handle view) override + { + return _nitpicker_session.view_capability(view); + } + + void release_view_handle(View_handle view) override + { + /* XXX dealloc View_ptr */ + _nitpicker_session.release_view_handle(view); + } + + Genode::Dataspace_capability command_dataspace() override + { + return _command_ds.cap(); + } + + void execute() override + { + for (unsigned i = 0; i < _command_buffer.num(); i++) { + try { + _execute_command(_command_buffer.get(i)); + } + catch (...) { + PWRN("unhandled exception while processing command from decorator"); + } + } + _nitpicker_session.execute(); + } + + Framebuffer::Mode mode() override + { + return _nitpicker_session.mode(); + } + + void mode_sigh(Genode::Signal_context_capability sigh) override + { + _nitpicker_session.mode_sigh(sigh); + } + + void buffer(Framebuffer::Mode mode, bool use_alpha) override + { + _nitpicker_session.buffer(mode, use_alpha); + } + + void focus(Genode::Capability) { } +}; + + +struct Wm::Decorator_nitpicker_service : Genode::Service, Genode::Noncopyable +{ + private: + + Entrypoint &_ep; + Allocator &_md_alloc; + Ram_session_capability _ram; + Local_reporter &_pointer_reporter; + Input::Session_component &_window_layouter_input; + Decorator_content_callback &_content_callback; + + + public: + + Decorator_nitpicker_service(Entrypoint &ep, Allocator &md_alloc, + Ram_session_capability ram, + Local_reporter &pointer_reporter, + Input::Session_component &window_layouter_input, + Decorator_content_callback &content_callback) + : + Service("Nitpicker"), + _ep(ep), _md_alloc(md_alloc), + _ram(ram), _pointer_reporter(pointer_reporter), + _window_layouter_input(window_layouter_input), + _content_callback(content_callback) + { } + + Genode::Session_capability + session(const char *, Genode::Affinity const &) override + { + Decorator_nitpicker_session *s = new (_md_alloc) + Decorator_nitpicker_session(_ram, _ep, _md_alloc, + _pointer_reporter, + _window_layouter_input, + _content_callback); + + return _ep.manage(*s); + } + + void upgrade(Genode::Session_capability session, const char *args) override + { + typedef typename Object_pool::Guard Object_guard; + Object_guard np_session(_ep.rpc_ep().lookup_and_lock(session)); + + if (np_session) + np_session->upgrade(args); + } + + void close(Genode::Session_capability) { } +}; + +#endif /* _DECORATOR_NITPICKER_H_ */ diff --git a/repos/gems/src/server/wm/decorator_slave.h b/repos/gems/src/server/wm/decorator_slave.h new file mode 100644 index 000000000..f8dbb5590 --- /dev/null +++ b/repos/gems/src/server/wm/decorator_slave.h @@ -0,0 +1,128 @@ +/* + * \brief Slave for drawing window decorations + * \author Norman Feske + * \date 2014-02-14 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _DECORATOR_SLAVE_H_ +#define _DECORATOR_SLAVE_H_ + +namespace Wm { + + class Decorator_slave; + + using Genode::Rom_session_capability; + using Genode::Capability; +} + + +class Wm::Decorator_slave +{ + private: + + Genode::Ram_session &_ram; + + class Policy : public Genode::Slave_policy + { + private: + + Genode::Service &_nitpicker_service; + + Single_session_service _window_layout_rom_service; + Single_session_service _pointer_rom_service; + Single_session_service _hover_report_service; + + protected: + + char const **_permitted_services() const + { + static char const *permitted_services[] = { + "CAP", "LOG", "SIGNAL", "RM", 0 }; + + return permitted_services; + }; + + public: + + Policy(Genode::Rpc_entrypoint &entrypoint, + Genode::Ram_session &ram, + Genode::Service &nitpicker_service, + Rom_session_capability window_layout_rom, + Rom_session_capability pointer_rom, + Genode::Capability hover_report) + : + Slave_policy("decorator", entrypoint, &ram), + _nitpicker_service(nitpicker_service), + _window_layout_rom_service("ROM", window_layout_rom), + _pointer_rom_service("ROM", pointer_rom), + _hover_report_service("Report", hover_report) + + { } + + Genode::Service *resolve_session_request(const char *service_name, + const char *args) override + { + using Genode::strcmp; + + if (strcmp(service_name, "Nitpicker") == 0) + return &_nitpicker_service; + + char label[128]; + Arg_string::find_arg(args, "label").string(label, sizeof(label), ""); + + if (strcmp(service_name, "ROM") == 0) { + + if (strcmp(label, "decorator -> window_layout") == 0) + return &_window_layout_rom_service; + + if (strcmp(label, "decorator -> pointer") == 0) + return &_pointer_rom_service; + } + + if (strcmp(service_name, "Report") == 0) { + + if (strcmp(label, "decorator -> hover") == 0) + return &_hover_report_service; + } + + return Genode::Slave_policy::resolve_session_request(service_name, args); + } + }; + + Genode::size_t const _ep_stack_size = 4*1024*sizeof(Genode::addr_t); + Genode::Rpc_entrypoint _ep; + Policy _policy; + Genode::size_t const _quota = 4*1024*1024; + Genode::Slave _slave; + + public: + + /** + * Constructor + * + * \param ram RAM session for paying nitpicker sessions created + * by the decorator + */ + Decorator_slave(Genode::Cap_session &cap, + Genode::Service &nitpicker_service, + Genode::Ram_session &ram, + Rom_session_capability window_layout_rom, + Rom_session_capability pointer_rom, + Genode::Capability hover_report) + : + _ram(ram), + _ep(&cap, _ep_stack_size, "decorator"), + _policy(_ep, ram, nitpicker_service, window_layout_rom, + pointer_rom, hover_report), + _slave(_ep, _policy, _quota) + { } +}; + +#endif /* _DECORATOR_SLAVE_H_ */ diff --git a/repos/gems/src/server/wm/local_reporter.h b/repos/gems/src/server/wm/local_reporter.h new file mode 100644 index 000000000..1dade34c0 --- /dev/null +++ b/repos/gems/src/server/wm/local_reporter.h @@ -0,0 +1,52 @@ +/* + * \brief Utility for producing reports to a report session + * \author Norman Feske + * \date 2014-02-14 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _LOCAL_REPORTER_H_ +#define _LOCAL_REPORTER_H_ + +#include +#include +#include + + +namespace Wm { struct Local_reporter; } + +struct Wm::Local_reporter +{ + Report::Session_client _session; + + Genode::Attached_dataspace _ds; + + char const *_name; + + Local_reporter(char const *name, Genode::Capability session_cap) + : + _session(session_cap), _ds(_session.dataspace()), _name(name) + { } + + struct Xml_generator : public Genode::Xml_generator + { + template + Xml_generator(Local_reporter &reporter, FUNC const &func) + : + Genode::Xml_generator(reporter._ds.local_addr(), + reporter._ds.size(), + reporter._name, + func) + { + reporter._session.submit(used()); + } + }; +}; + +#endif /* _LOCAL_REPORTER_H_ */ diff --git a/repos/gems/src/server/wm/main.cc b/repos/gems/src/server/wm/main.cc new file mode 100644 index 000000000..c0bd2ca28 --- /dev/null +++ b/repos/gems/src/server/wm/main.cc @@ -0,0 +1,185 @@ +/* + * \brief Window manager + * \author Norman Feske + * \date 2014-01-06 + */ + +/* + * Copyright (C) 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 + +/* local includes */ +#include +#include +#include +#include +#include +#include +#include + +namespace Wm { + + class Main; + + using Genode::size_t; + using Genode::env; + using Genode::Rom_session_client; + using Genode::Rom_connection; + using Genode::Xml_node; +} + + +struct Wm::Main +{ + Server::Entrypoint ep; + + Genode::Cap_connection cap; + + Report_rom_slave report_rom_slave = { cap, *env()->ram_session() }; + + Rom_session_capability window_list_rom = report_rom_slave.rom_session("window_list"); + Rom_session_capability window_layout_rom = report_rom_slave.rom_session("window_layout"); + Rom_session_capability pointer_rom = report_rom_slave.rom_session("pointer"); + Rom_session_capability hover_rom = report_rom_slave.rom_session("hover"); + + Rom_session_client focus_rom { report_rom_slave.rom_session("focus") }; + Rom_session_client resize_request_rom { report_rom_slave.rom_session("resize_request") }; + + /* pointer position reported by nitpicker */ + Capability pointer_report = report_rom_slave.report_session("pointer"); + Local_reporter pointer_reporter = { "pointer", pointer_report }; + + /* hovered element reported by decorator */ + Capability hover_report = report_rom_slave.report_session("hover"); + + Capability window_list_report = report_rom_slave.report_session("window_list"); + Local_reporter window_list_reporter = { "window_list", window_list_report }; + + Capability window_layout_report = report_rom_slave.report_session("window_layout"); + Capability resize_request_report = report_rom_slave.report_session("resize_request"); + Capability focus_report = report_rom_slave.report_session("focus"); + + Input::Session_component window_layouter_input; + + Window_registry window_registry { *env()->heap(), window_list_reporter }; + + Nitpicker::Root nitpicker_root { ep, window_registry, + *env()->heap(), env()->ram_session_cap() }; + + Decorator_nitpicker_service decorator_nitpicker_service { + ep, *env()->heap(), env()->ram_session_cap(), pointer_reporter, + window_layouter_input, nitpicker_root }; + + Window_layouter_slave window_layouter_slave = { + cap, *env()->ram_session(), window_list_rom, hover_rom, + ep.manage(window_layouter_input), window_layout_report, + resize_request_report, focus_report }; + + Decorator_slave decorator_slave = { + cap, decorator_nitpicker_service, *env()->ram_session(), + window_layout_rom, pointer_rom, hover_report }; + + Genode::Lazy_volatile_object focus_ds; + + Nitpicker::Connection focus_nitpicker_session; + + void handle_focus_update(unsigned) + { + try { + if (!focus_ds.is_constructed() || focus_rom.update() == false) + focus_ds.construct(focus_rom.dataspace()); + + unsigned long win_id = 0; + + Xml_node(focus_ds->local_addr()).sub_node("window") + .attribute("id").value(&win_id); + + if (win_id) { + Nitpicker::Session_capability session_cap = + nitpicker_root.lookup_nitpicker_session(win_id); + + focus_nitpicker_session.focus(session_cap); + } + + } catch (...) { + PWRN("no focus model available"); + } + } + + Genode::Signal_rpc_member
focus_dispatcher = { ep, *this, &Main::handle_focus_update }; + + Genode::Lazy_volatile_object resize_request_ds; + + void handle_resize_request_update(unsigned) + { + try { + if (!resize_request_ds.is_constructed() + || resize_request_rom.update() == false) + resize_request_ds.construct(resize_request_rom.dataspace()); + + char const * const node_type = "window"; + + Xml_node window = + Xml_node(resize_request_ds->local_addr()).sub_node(node_type); + + for (;;) { + unsigned long win_id = 0, width = 0, height = 0; + + window.attribute("id") .value(&win_id); + window.attribute("width") .value(&width); + window.attribute("height").value(&height); + + nitpicker_root.request_resize(win_id, Area(width, height)); + + if (window.is_last(node_type)) + break; + + window = window.next(node_type); + } + + } catch (...) { /* no resize-request model available */ } + } + + Genode::Signal_rpc_member
resize_request_dispatcher = + { ep, *this, &Main::handle_resize_request_update }; + + Main(Server::Entrypoint &ep) : ep(ep) + { + window_layouter_input.event_queue().enabled(true); + + /* initially report an empty window list */ + Local_reporter::Xml_generator xml(window_list_reporter, [&] () { }); + + focus_rom.sigh(focus_dispatcher); + resize_request_rom.sigh(resize_request_dispatcher); + } +}; + + +/************ + ** Server ** + ************/ + +namespace Server { + + char const *name() { return "desktop_ep"; } + + size_t stack_size() { return 4*1024*sizeof(long); } + + void construct(Entrypoint &ep) + { + static Wm::Main desktop(ep); + } +} diff --git a/repos/gems/src/server/wm/nitpicker.h b/repos/gems/src/server/wm/nitpicker.h new file mode 100644 index 000000000..65c9616dd --- /dev/null +++ b/repos/gems/src/server/wm/nitpicker.h @@ -0,0 +1,1005 @@ +/* + * \brief Virtualized nitpicker service announced to the outside world + * \author Norman Feske + * \date 2014-02-14 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _NITPICKER_H_ +#define _NITPICKER_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* local includes */ +#include + + +namespace Wm { + + using Genode::Rpc_object; + using Genode::List; + using Genode::Allocator; + using Genode::Affinity; + using Genode::static_cap_cast; + using Genode::Signal_rpc_member; + using Genode::Ram_session_capability; + using Genode::Weak_ptr; + using Genode::Locked_ptr; + using Genode::Tslab; + using Genode::Attached_ram_dataspace; + using Genode::Signal_context_capability; + using Genode::Signal_transmitter; +} + +namespace Wm { namespace Nitpicker { + + using namespace ::Nitpicker; + + class View_handle_ctx; + class View; + class Top_level_view; + class Child_view; + class Direct_view; + class Session_component; + class Root; + + typedef Genode::Surface_base::Rect Rect; + typedef Genode::Surface_base::Point Point; + typedef Genode::Session_label Session_label; +} } + + +struct Nitpicker::View { GENODE_RPC_INTERFACE(); }; + + +class Wm::Nitpicker::View : public Genode::Weak_object, + public Genode::Rpc_object< ::Nitpicker::View> +{ + protected: + + typedef Genode::String<100> Title; + typedef Nitpicker::Session::Command Command; + typedef Nitpicker::Session::View_handle View_handle; + + Session_label _session_label; + Nitpicker::Session_client &_real_nitpicker; + View_handle _real_handle; + Title _title; + Rect _geometry; + Point _buffer_offset; + Weak_ptr _neighbor_ptr; + bool _neighbor_behind; + + View(Nitpicker::Session_client &real_nitpicker, + Session_label const &session_label) + : + _session_label(session_label), _real_nitpicker(real_nitpicker) + { } + + /** + * Propagate cached view geometry to the physical nitpicker view + */ + virtual void _propagate_view_geometry() = 0; + + /** + * Apply cached view state to the physical nitpicker view + */ + void _apply_view_config() + { + if (!_real_handle.valid()) + return; + + _propagate_view_geometry(); + _real_nitpicker.enqueue(_real_handle, _buffer_offset); + _real_nitpicker.enqueue (_real_handle, _title.string()); + + View_handle real_neighbor_handle; + + Locked_ptr neighbor(_neighbor_ptr); + if (neighbor.is_valid()) + real_neighbor_handle = _real_nitpicker.view_handle(neighbor->real_view_cap()); + + if (_neighbor_behind) + _real_nitpicker.enqueue(_real_handle, real_neighbor_handle); + else + _real_nitpicker.enqueue(_real_handle, real_neighbor_handle); + + _real_nitpicker.execute(); + + if (real_neighbor_handle.valid()) + _real_nitpicker.release_view_handle(real_neighbor_handle); + } + + public: + + ~View() + { + if (_real_handle.valid()) + _real_nitpicker.destroy_view(_real_handle); + } + + Point virtual_position() const { return _geometry.p1(); } + + virtual bool belongs_to_win_id(Window_registry::Id id) const = 0; + + virtual void geometry(Rect geometry) + { + _geometry = geometry; + + /* + * Propagate new size to real nitpicker view but + */ + if (_real_handle.valid()) { + _propagate_view_geometry(); + _real_nitpicker.execute(); + } + } + + virtual void title(char const *title) + { + _title = Title(title); + + if (_real_handle.valid()) { + _real_nitpicker.enqueue(_real_handle, title); + _real_nitpicker.execute(); + } + } + + virtual Point input_anchor_position() const = 0; + + virtual void stack(Weak_ptr neighbor_ptr, bool behind) { } + + View_handle real_handle() const { return _real_handle; } + + View_capability real_view_cap() + { + return _real_nitpicker.view_capability(_real_handle); + } + + void buffer_offset(Point buffer_offset) + { + _buffer_offset = buffer_offset; + + if (_real_handle.valid()) { + _real_nitpicker.enqueue(_real_handle, _buffer_offset); + _real_nitpicker.execute(); + } + } +}; + + +class Wm::Nitpicker::Top_level_view : public View, + public List::Element +{ + private: + + Window_registry::Id _win_id; + + Window_registry &_window_registry; + + /* + * Geometry of window-content view, which corresponds to the location + * of the window content as known by the decorator. + */ + Rect _content_geometry; + + /* + * The window title is the concatenation of the session label with + * view title. + */ + struct Window_title : Title + { + Window_title(Session_label const session_label, Title const &title) + { + bool const has_title = Genode::strlen(title.string()) > 0; + char buf[256]; + Genode::snprintf(buf, sizeof(buf), "%s%s%s", + session_label.string(), + has_title ? " " : "", title.string()); + + *static_cast(this) = Title(buf); + } + } _window_title; + + typedef Nitpicker::Session::Command Command; + + public: + + Top_level_view(Nitpicker::Session_client &real_nitpicker, + Session_label const &session_label, + Window_registry &window_registry) + : + View(real_nitpicker, session_label), + _window_registry(window_registry), + _window_title(session_label, "") + { } + + ~Top_level_view() + { + if (_win_id.valid()) + _window_registry.destroy(_win_id); + } + + void _propagate_view_geometry() override { } + + void geometry(Rect geometry) override + { + /* + * Add window to the window-list model on the first call. We + * defer the creation of the window ID until the time when the + * initial geometry is known. + */ + if (!_win_id.valid()) { + _win_id = _window_registry.create(); + _window_registry.title(_win_id, _window_title.string()); + } + + _window_registry.size(_win_id, geometry.area()); + + View::geometry(geometry); + } + + Area size() const { return _geometry.area(); } + + void title(char const *title) override + { + View::title(title); + + _window_title = Window_title(_session_label, title); + + if (_win_id.valid()) + _window_registry.title(_win_id, _window_title.string()); + } + + bool has_win_id(Window_registry::Id id) const { return id == _win_id; } + + bool belongs_to_win_id(Window_registry::Id id) const override + { + return has_win_id(id); + } + + Point input_anchor_position() const override + { + return _content_geometry.p1(); + } + + void content_geometry(Rect rect) { _content_geometry = rect; } + + View_capability content_view() + { + if (!_real_handle.valid()) { + + /* + * Create and configure physical nitpicker view. + */ + _real_handle = _real_nitpicker.create_view(); + + _real_nitpicker.enqueue<Command::Offset>(_real_handle, _buffer_offset); + _real_nitpicker.enqueue<Command::Title> (_real_handle, _title.string()); + _real_nitpicker.execute(); + } + + return _real_nitpicker.view_capability(_real_handle); + } +}; + + +class Wm::Nitpicker::Child_view : public View, + public List<Child_view>::Element +{ + private: + + Weak_ptr<View> mutable _parent; + + public: + + Child_view(Nitpicker::Session_client &real_nitpicker, + Session_label const &session_label, + Weak_ptr<View> parent) + : + View(real_nitpicker, session_label), _parent(parent) + { + try_to_init_real_view(); + } + + void _propagate_view_geometry() override + { + _real_nitpicker.enqueue<Command::Geometry>(_real_handle, _geometry); + } + + void stack(Weak_ptr<View> neighbor_ptr, bool behind) override + { + _neighbor_ptr = neighbor_ptr; + _neighbor_behind = behind; + + _apply_view_config(); + } + + bool belongs_to_win_id(Window_registry::Id id) const override + { + Locked_ptr<View> parent(_parent); + return parent.is_valid() && parent->belongs_to_win_id(id); + } + + Point input_anchor_position() const override + { + Locked_ptr<View> parent(_parent); + if (parent.is_valid()) + return parent->input_anchor_position(); + + return Point(); + } + + void try_to_init_real_view() + { + if (_real_handle.valid()) + return; + + Locked_ptr<View> parent(_parent); + if (!parent.is_valid()) + return; + + View_handle parent_handle = _real_nitpicker.view_handle(parent->real_view_cap()); + if (!parent_handle.valid()) + return; + + _real_handle = _real_nitpicker.create_view(parent_handle); + + _real_nitpicker.release_view_handle(parent_handle); + + _apply_view_config(); + } + + void update_child_stacking() + { + _apply_view_config(); + } +}; + + +class Wm::Nitpicker::Direct_view : public View +{ + public: + + Direct_view(Nitpicker::Session_client &real_nitpicker, + Session_label const &session_label, + bool const direct) + : + View(real_nitpicker, session_label) + { + if (!direct) + return; + + typedef Nitpicker::Session::Command Command; + _real_handle = _real_nitpicker.create_view(); + _real_nitpicker.enqueue<Command::Geometry>(_real_handle, + Rect(Point(0, 0), Area(0, 0))); + _real_nitpicker.enqueue<Command::To_back>(_real_handle); + _real_nitpicker.execute(); + } + + bool belongs_to_win_id(Window_registry::Id id) const override { return false; } + + void _propagate_view_geometry() override { } + + Point input_anchor_position() const override { return Point(); } +}; + + +class Wm::Nitpicker::Session_component : public Genode::Rpc_object<Session>, + public List<Session_component>::Element +{ + private: + + typedef Nitpicker::Session::View_handle View_handle; + + Session_label _session_label; + Ram_session_client _ram; + Nitpicker::Connection _session { _session_label.string() }; + + Direct_view _direct_view; + Window_registry &_window_registry; + Entrypoint &_ep; + Tslab<Top_level_view, 4000> _top_level_view_alloc; + Tslab<Child_view, 4000> _child_view_alloc; + List<Top_level_view> _top_level_views; + List<Child_view> _child_views; + Input::Session_component _input_session; + Input::Session_capability _input_session_cap; + Signal_context_capability _mode_sigh; + Area _requested_size; + + /* + * Command buffer + */ + typedef Nitpicker::Session::Command_buffer Command_buffer; + + Attached_ram_dataspace _command_ds { &_ram, sizeof(Command_buffer) }; + + Command_buffer &_command_buffer = *_command_ds.local_addr<Command_buffer>(); + + /* + * View handle registry + */ + typedef Genode::Handle_registry<View_handle, View> + View_handle_registry; + + View_handle_registry _view_handle_registry; + + /* + * Input + */ + Input::Session_client _nitpicker_input { _session.input_session() }; + Attached_dataspace _nitpicker_input_ds { _nitpicker_input.dataspace() }; + + Signal_rpc_member<Session_component> _input_dispatcher { + _ep, *this, &Session_component::_input_handler }; + + Point _input_origin() const + { + if (Top_level_view const *v = _top_level_views.first()) + return v->virtual_position() - v->input_anchor_position(); + + if (Child_view const *v = _child_views.first()) + return Point(0, 0) - v->input_anchor_position(); + + return Point(); + } + + /** + * Translate input event to the client's coordinate system + */ + Input::Event _translate_event(Input::Event const ev, Point const input_origin) + { + switch (ev.type()) { + + case Input::Event::MOTION: + case Input::Event::PRESS: + case Input::Event::RELEASE: + case Input::Event::FOCUS: + case Input::Event::LEAVE: + { + Point abs_pos = Point(ev.ax(), ev.ay()) + input_origin; + return Input::Event(ev.type(), ev.code(), + abs_pos.x(), abs_pos.y(), 0, 0); + } + + case Input::Event::INVALID: + case Input::Event::WHEEL: + return ev; + } + return Input::Event(); + } + + void _input_handler(unsigned) + { + Point const input_origin = _input_origin(); + + Input::Event const * const events = + _nitpicker_input_ds.local_addr<Input::Event>(); + + try { + while (_nitpicker_input.is_pending()) { + + size_t const num_events = _nitpicker_input.flush(); + + /* we trust nitpicker to return a valid number of events */ + + for (size_t i = 0; i < num_events; i++) + _input_session.submit(_translate_event(events[i], input_origin)); + } + } catch (Input::Event_queue::Overflow) { + + PWRN("client \"%s\" does not respond to user input", + _session_label.string()); + + _input_session.event_queue().reset(); + } + } + + View &_create_view_object(View_handle parent_handle) + { + /* + * If the session operates in direct mode, we subordinate all + * top-level views of the session to the 'direct_parent' pseudo + * view, which is located at the screen origin. + */ + if (!parent_handle.valid() && _direct_view.real_handle().valid()) { + + Child_view *view = new (_child_view_alloc) + Child_view(_session, _session_label, _direct_view.weak_ptr()); + + _child_views.insert(view); + return *view; + } + + /* + * Create child view + */ + if (parent_handle.valid()) { + + Weak_ptr<View> parent_ptr = _view_handle_registry.lookup(parent_handle); + + Child_view *view = new (_child_view_alloc) + Child_view(_session, _session_label, parent_ptr); + + _child_views.insert(view); + return *view; + } + + /* + * Create top-level view + */ + else { + Top_level_view *view = new (_top_level_view_alloc) + Top_level_view(_session, _session_label, _window_registry); + + _top_level_views.insert(view); + return *view; + } + } + + void _destroy_top_level_view(Top_level_view &view) + { + _top_level_views.remove(&view); + _ep.dissolve(view); + Genode::destroy(&_top_level_view_alloc, &view); + } + + void _destroy_child_view(Child_view &view) + { + _child_views.remove(&view); + _ep.dissolve(view); + Genode::destroy(&_child_view_alloc, &view); + } + + void _destroy_view_object(View &view) + { + if (Top_level_view *v = dynamic_cast<Top_level_view *>(&view)) + _destroy_top_level_view(*v); + + if (Child_view *v = dynamic_cast<Child_view *>(&view)) + _destroy_child_view(*v); + } + + void _execute_command(Command const &command) + { + switch (command.opcode) { + + case Command::OP_GEOMETRY: + { + Locked_ptr<View> view(_view_handle_registry.lookup(command.geometry.view)); + if (view.is_valid()) + view->geometry(command.geometry.rect); + return; + } + + case Command::OP_OFFSET: + { + Locked_ptr<View> view(_view_handle_registry.lookup(command.offset.view)); + if (view.is_valid()) + view->buffer_offset(command.offset.offset); + + return; + } + + case Command::OP_TO_FRONT: + { + Locked_ptr<View> view(_view_handle_registry.lookup(command.to_front.view)); + if (!view.is_valid()) + return; + + /* bring to front if no neighbor is specified */ + if (!command.to_front.neighbor.valid()) { + view->stack(Weak_ptr<View>(), true); + return; + } + + /* stack view relative to neighbor */ + view->stack(_view_handle_registry.lookup(command.to_front.neighbor), + true); + return; + } + + case Command::OP_TO_BACK: + { + PDBG("OP_TO_BACK not implemented"); + return; + } + + case Command::OP_BACKGROUND: + { + PDBG("OP_BACKGROUND not implemented"); + return; + } + + case Command::OP_TITLE: + { + Locked_ptr<View> view(_view_handle_registry.lookup(command.title.view)); + if (view.is_valid()) + view->title(command.title.title.string()); + + return; + } + + case Command::OP_NOP: + return; + } + } + + public: + + /** + * Constructor + * + * \param nitpicker real nitpicker service + * \param ep entrypoint used for managing the views + */ + Session_component(Ram_session_capability ram, + Window_registry &window_registry, + Entrypoint &ep, + Allocator &session_alloc, + Session_label const &session_label, + bool const direct) + : + _session_label(session_label), + _ram(ram), + _direct_view(_session, session_label, direct), + _window_registry(window_registry), + _ep(ep), + _top_level_view_alloc(&session_alloc), + _child_view_alloc(&session_alloc), + _input_session_cap(_ep.manage(_input_session)), + _view_handle_registry(session_alloc) + { + _nitpicker_input.sigh(_input_dispatcher); + _input_session.event_queue().enabled(true); + } + + ~Session_component() + { + while (Top_level_view *view = _top_level_views.first()) + _destroy_view_object(*view); + + while (Child_view *view = _child_views.first()) + _destroy_view_object(*view); + + _ep.dissolve(_input_session); + } + + void upgrade(char const *args) + { + Genode::env()->parent()->upgrade(_session, args); + } + + void try_to_init_real_child_views() + { + for (Child_view *v = _child_views.first(); v; v = v->next()) + v->try_to_init_real_view(); + } + + void update_stacking_order_of_children(Window_registry::Id id) + { + for (Child_view *v = _child_views.first(); v; v = v->next()) + if (v->belongs_to_win_id(id)) + v->update_child_stacking(); + } + + void content_geometry(Window_registry::Id id, Rect rect) + { + for (Top_level_view *v = _top_level_views.first(); v; v = v->next()) { + if (!v->has_win_id(id)) + continue; + + v->content_geometry(rect); + break; + } + } + + View_capability content_view(Window_registry::Id id) + { + for (Top_level_view *v = _top_level_views.first(); v; v = v->next()) + if (v->has_win_id(id.value)) + return v->content_view(); + + return View_capability(); + } + + bool has_win_id(unsigned id) const + { + for (Top_level_view const *v = _top_level_views.first(); v; v = v->next()) + if (v->has_win_id(id)) + return true; + + return false; + } + + void request_resize(Area size) + { + _requested_size = size; + + /* notify client */ + if (_mode_sigh.valid()) + Signal_transmitter(_mode_sigh).submit(); + } + + /** + * Return session capability to real nitpicker session + */ + Capability<Session> session() { return _session; } + + + /********************************* + ** Nitpicker session interface ** + *********************************/ + + Framebuffer::Session_capability framebuffer_session() override + { + return _session.framebuffer_session(); + } + + Input::Session_capability input_session() override + { + return _input_session_cap; + } + + View_handle create_view(View_handle parent) override + { + try { + View &view = _create_view_object(parent); + _ep.manage(view); + return _view_handle_registry.alloc(view); + } + catch (View_handle_registry::Lookup_failed) { + return View_handle(); } + } + + void destroy_view(View_handle handle) override + { + try { + Locked_ptr<View> view(_view_handle_registry.lookup(handle)); + if (view.is_valid()) + _destroy_view_object(*view); + } + catch (View_handle_registry::Lookup_failed) { } + + _view_handle_registry.free(handle); + } + + View_handle view_handle(View_capability view_cap, View_handle handle) override + { + View *view = dynamic_cast<View *>(_ep.rpc_ep().lookup_and_lock(view_cap)); + if (!view) return View_handle(); + + Object_pool<Rpc_object_base>::Guard guard(view); + + return _view_handle_registry.alloc(*view, handle); + } + + View_capability view_capability(View_handle handle) override + { + Locked_ptr<View> view(_view_handle_registry.lookup(handle)); + + return view.is_valid() ? view->cap() : View_capability(); + } + + void release_view_handle(View_handle handle) override + { + try { + _view_handle_registry.free(handle); } + + catch (View_handle_registry::Lookup_failed) { + PWRN("view lookup failed while releasing view handle"); + return; + } + } + + Genode::Dataspace_capability command_dataspace() override + { + return _command_ds.cap(); + } + + void execute() override + { + for (unsigned i = 0; i < _command_buffer.num(); i++) { + try { + _execute_command(_command_buffer.get(i)); } + catch (View_handle_registry::Lookup_failed) { + PWRN("view lookup failed during command execution"); } + } + } + + Framebuffer::Mode mode() override + { + Framebuffer::Mode const real_mode = _session.mode(); + + /* + * While resizing the window, return requested window size as + * mode + */ + if (_requested_size.valid()) + return Framebuffer::Mode(_requested_size.w(), + _requested_size.h(), + real_mode.format()); + + /* + * If the first top-level view has a defined size, use it + * as the size of the virtualized nitpicker session. + */ + if (Top_level_view const *v = _top_level_views.first()) + if (v->size().valid()) + return Framebuffer::Mode(v->size().w(), + v->size().h(), + real_mode.format()); + + /* + * If top-level view has yet been defined, return the real mode. + */ + return real_mode; + } + + void mode_sigh(Genode::Signal_context_capability sigh) override + { + _mode_sigh = sigh; + } + + void buffer(Framebuffer::Mode mode, bool use_alpha) override + { + _session.buffer(mode, use_alpha); + } + + void focus(Genode::Capability<Nitpicker::Session>) { } +}; + + +class Wm::Nitpicker::Root : public Genode::Root_component<Session_component>, + public Decorator_content_callback +{ + private: + + Entrypoint &_ep; + + Ram_session_capability _ram; + + enum { STACK_SIZE = 1024*sizeof(long) }; + + Window_registry &_window_registry; + + List<Session_component> _sessions; + + protected: + + Session_component *_create_session(const char *args) override + { + bool direct = false; + + Session_label session_label(args); + + /* + * Determine session policy + */ + try { + Genode::Xml_node policy = Genode::Session_policy(session_label); + direct = policy.attribute("direct").has_value("yes"); + } + catch (...) { } + + Session_component *session = new (md_alloc()) + Session_component(_ram, _window_registry, + _ep, *md_alloc(), session_label, direct); + + _sessions.insert(session); + + return session; + } + + void _destroy_session(Session_component *session) override + { + _sessions.remove(session); + Root_component<Session_component>::_destroy_session(session); + } + + void _upgrade_session(Session_component *s, const char *args) override + { + s->upgrade(args); + } + + public: + + /** + * Constructor + */ + Root(Entrypoint &ep, + Window_registry &window_registry, Allocator &md_alloc, + Ram_session_capability ram) + : + Root_component<Session_component>(&ep.rpc_ep(), &md_alloc), + _ep(ep), _ram(ram), _window_registry(window_registry) + { + Genode::env()->parent()->announce(_ep.manage(*this)); + } + + + /****************************************** + ** Decorator_content_callback interface ** + ******************************************/ + + /* + * This function is called once the decorator has produced the content + * view for a new window, or when a window is brought to the front. + */ + View_capability content_view(Window_registry::Id id) override + { + /* + * Propagate the request to the sessions. It will be picked up + * by the session to which the specified window ID belongs. + * The real content view will be created as a side effect of + * calling 's->content_view'. + */ + for (Session_component *s = _sessions.first(); s; s = s->next()) + if (s->has_win_id(id.value)) + return s->content_view(id.value); + + return View_capability(); + } + + void update_content_child_views(Window_registry::Id id) override + { + /* + * Try to create physical views for its child views. + */ + for (Session_component *s = _sessions.first(); s; s = s->next()) + s->try_to_init_real_child_views(); + + /* + * Apply the stacking order to the child views that belong to the + * given window ID. I.e., when the window was brought to the front, + * we need to restack its child views such that they end up in + * front of the top-level view. Otherwise, the top-level view + * will obstruct the child views. + */ + for (Session_component *s = _sessions.first(); s; s = s->next()) + s->update_stacking_order_of_children(id); + } + + void content_geometry(Window_registry::Id id, Rect rect) override + { + for (Session_component *s = _sessions.first(); s; s = s->next()) + s->content_geometry(id, rect); + } + + Capability<Session> lookup_nitpicker_session(unsigned win_id) + { + for (Session_component *s = _sessions.first(); s; s = s->next()) + if (s->has_win_id(win_id)) + return s->session(); + + return Capability<Session>(); + } + + void request_resize(unsigned win_id, Area size) + { + for (Session_component *s = _sessions.first(); s; s = s->next()) + if (s->has_win_id(win_id)) + return s->request_resize(size); + } +}; + +#endif /* _NITPICKER_H_ */ diff --git a/repos/gems/src/server/wm/report_rom_slave.h b/repos/gems/src/server/wm/report_rom_slave.h new file mode 100644 index 000000000..fd75ab323 --- /dev/null +++ b/repos/gems/src/server/wm/report_rom_slave.h @@ -0,0 +1,173 @@ +/* + * \brief Report-ROM slave + * \author Norman Feske + * \date 2014-02-14 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _REPORT_ROM_SLAVE_H_ +#define _REPORT_ROM_SLAVE_H_ + +/* Genode includes */ +#include <base/lock.h> +#include <os/slave.h> +#include <report_session/report_session.h> +#include <rom_session/capability.h> +#include <root/client.h> + + +class Report_rom_slave : public Genode::Noncopyable +{ + private: + + class Policy : public Genode::Slave_policy + { + private: + + Genode::Root_capability _report_root_cap; + Genode::Root_capability _rom_root_cap; + bool _announced; + Genode::Lock mutable _lock; /* used to wait for announcement */ + + protected: + + char const **_permitted_services() const + { + static char const *permitted_services[] = { + "CAP", "LOG", "SIGNAL", "RM", 0 }; + + return permitted_services; + }; + + public: + + Policy(Genode::Rpc_entrypoint &entrypoint, + Genode::Ram_session &ram) + : + Slave_policy("report_rom", entrypoint, &ram), + _lock(Genode::Lock::LOCKED) + { + configure("<config> <rom>" + " <policy label=\"window_list\" report=\"window_list\"/>" + " <policy label=\"window_layout\" report=\"window_layout\"/>" + " <policy label=\"resize_request\" report=\"resize_request\"/>" + " <policy label=\"pointer\" report=\"pointer\"/>" + " <policy label=\"hover\" report=\"hover\"/>" + " <policy label=\"focus\" report=\"focus\"/>" + "</rom> </config>"); + } + + bool announce_service(const char *service_name, + Genode::Root_capability root, + Genode::Allocator *, + Genode::Server *) + { + if (Genode::strcmp(service_name, "ROM") == 0) + _rom_root_cap = root; + else if (Genode::strcmp(service_name, "Report") == 0) + _report_root_cap = root; + else + return false; + + if (_rom_root_cap.valid() && _report_root_cap.valid()) + _lock.unlock(); + + return true; + } + + Genode::Root_capability report_root() const + { + Genode::Lock::Guard guard(_lock); + return _report_root_cap; + } + + Genode::Root_capability rom_root() const + { + Genode::Lock::Guard guard(_lock); + return _rom_root_cap; + } + }; + + Genode::size_t const _ep_stack_size = 4*1024*sizeof(Genode::addr_t); + Genode::Rpc_entrypoint _ep; + Policy _policy; + Genode::size_t const _quota = 1024*1024; + Genode::Slave _slave; + Genode::Root_client _rom_root; + Genode::Root_client _report_root; + + public: + + /** + * Constructor + * + * \param ep entrypoint used for nitpicker child thread + * \param ram RAM session used to allocate the configuration + * dataspace + */ + Report_rom_slave(Genode::Cap_session &cap, Genode::Ram_session &ram) + : + _ep(&cap, _ep_stack_size, "report_rom"), + _policy(_ep, ram), + _slave(_ep, _policy, _quota), + _rom_root(_policy.rom_root()), + _report_root(_policy.report_root()) + { } + + Genode::Rom_session_capability rom_session(char const *label) + { + using namespace Genode; + + enum { ARGBUF_SIZE = 128 }; + char argbuf[ARGBUF_SIZE]; + argbuf[0] = 0; + + /* + * Declare ram-quota donation + */ + enum { SESSION_METADATA = 4*1024 }; + Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", SESSION_METADATA); + + /* + * Set session label + */ + Arg_string::set_arg(argbuf, sizeof(argbuf), "label", label); + + Session_capability session_cap = _rom_root.session(argbuf, Affinity()); + + return static_cap_cast<Genode::Rom_session>(session_cap); + } + + Genode::Capability<Report::Session> report_session(char const *label) + { + using namespace Genode; + + enum { ARGBUF_SIZE = 128 }; + char argbuf[ARGBUF_SIZE]; + argbuf[0] = 0; + + /* + * Declare ram-quota donation + */ + enum { BUFFER_SIZE = 4096, SESSION_METADATA = BUFFER_SIZE + 8*1024 }; + Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", SESSION_METADATA); + Arg_string::set_arg(argbuf, sizeof(argbuf), "buffer_size", BUFFER_SIZE); + + /* + * Set session label + */ + Arg_string::set_arg(argbuf, sizeof(argbuf), "label", label); + + Session_capability session_cap = _report_root.session(argbuf, Affinity()); + + return static_cap_cast<Report::Session>(session_cap); + } +}; + +#endif /* _REPORT_ROM_SLAVE_H_ */ diff --git a/repos/gems/src/server/wm/single_session_service.h b/repos/gems/src/server/wm/single_session_service.h new file mode 100644 index 000000000..f2aad6f6e --- /dev/null +++ b/repos/gems/src/server/wm/single_session_service.h @@ -0,0 +1,41 @@ +/* + * \brief Utility for implementing a local service with a single session + * \author Norman Feske + * \date 2014-02-14 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _SINGLE_SESSION_SERVICE_H_ +#define _SINGLE_SESSION_SERVICE_H_ + +#include <base/service.h> + +namespace Wm { class Single_session_service; } + +struct Wm::Single_session_service : Genode::Service +{ + Genode::Session_capability session_cap; + + Single_session_service(char const *service_name, + Genode::Session_capability session_cap) + : + Service(service_name), session_cap(session_cap) + { } + + Genode::Session_capability + session(const char *, Genode::Affinity const &) override + { + return session_cap; + } + + void upgrade(Genode::Session_capability, const char *) override { } + void close(Genode::Session_capability) override { } +}; + +#endif /* _SINGLE_SESSION_SERVICE_H_ */ diff --git a/repos/gems/src/server/wm/target.mk b/repos/gems/src/server/wm/target.mk new file mode 100644 index 000000000..1d59dfe22 --- /dev/null +++ b/repos/gems/src/server/wm/target.mk @@ -0,0 +1,4 @@ +TARGET = wm +SRC_CC = main.cc +LIBS = base server config +INC_DIR += $(PRG_DIR) diff --git a/repos/gems/src/server/wm/window_layouter_slave.h b/repos/gems/src/server/wm/window_layouter_slave.h new file mode 100644 index 000000000..63de576a3 --- /dev/null +++ b/repos/gems/src/server/wm/window_layouter_slave.h @@ -0,0 +1,137 @@ +/* + * \brief Slave for managing the window layout + * \author Norman Feske + * \date 2014-02-14 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _WINDOW_LAYOUTER_SLAVE_H_ +#define _WINDOW_LAYOUTER_SLAVE_H_ + +namespace Wm { + + class Window_layouter_slave; + + using Genode::Rom_session_capability; + using Genode::Capability; +} + + +class Wm::Window_layouter_slave +{ + private: + + Genode::Ram_session &_ram; + + class Policy : public Genode::Slave_policy + { + private: + + Single_session_service _window_list_rom_service; + Single_session_service _hover_rom_service; + Single_session_service _input_service; + Single_session_service _window_layout_report_service; + Single_session_service _resize_request_report_service; + Single_session_service _focus_report_service; + + protected: + + char const **_permitted_services() const + { + static char const *permitted_services[] = { + "CAP", "LOG", "SIGNAL", "RM", "Timer", 0 }; + + return permitted_services; + }; + + public: + + Policy(Genode::Rpc_entrypoint &entrypoint, + Genode::Ram_session &ram, + Rom_session_capability window_list_rom, + Rom_session_capability hover_rom, + Input::Session_capability input, + Capability<Report::Session> window_layout_report, + Capability<Report::Session> resize_request_report, + Capability<Report::Session> focus_report) + : + Slave_policy("floating_window_layouter", entrypoint, &ram), + _window_list_rom_service("ROM", window_list_rom), + _hover_rom_service("ROM", hover_rom), + _input_service("Input", input), + _window_layout_report_service("Report", window_layout_report), + _resize_request_report_service("Report", resize_request_report), + _focus_report_service("Report", focus_report) + { } + + Genode::Service *resolve_session_request(const char *service_name, + const char *args) override + { + using Genode::strcmp; + + char label[128]; + Arg_string::find_arg(args, "label").string(label, sizeof(label), ""); + + if (strcmp(service_name, "ROM") == 0) { + + if (strcmp(label, "floating_window_layouter -> window_list") == 0) + return &_window_list_rom_service; + + if (strcmp(label, "floating_window_layouter -> hover") == 0) + return &_hover_rom_service; + } + + if (strcmp(service_name, "Report") == 0) { + + if (strcmp(label, "floating_window_layouter -> window_layout") == 0) + return &_window_layout_report_service; + + if (strcmp(label, "floating_window_layouter -> resize_request") == 0) + return &_resize_request_report_service; + + if (strcmp(label, "floating_window_layouter -> focus") == 0) + return &_focus_report_service; + } + + if (strcmp(service_name, "Input") == 0) + return &_input_service; + + return Genode::Slave_policy::resolve_session_request(service_name, args); + } + }; + + Genode::size_t const _ep_stack_size = 4*1024*sizeof(Genode::addr_t); + Genode::Rpc_entrypoint _ep; + Policy _policy; + Genode::size_t const _quota = 1*1024*1024; + Genode::Slave _slave; + + public: + + /** + * Constructor + */ + Window_layouter_slave(Genode::Cap_session &cap, + Genode::Ram_session &ram, + Rom_session_capability window_list_rom, + Rom_session_capability hover_rom, + Input::Session_capability input, + Capability<Report::Session> window_layout_report, + Capability<Report::Session> resize_request_report, + Capability<Report::Session> focus_report) + : + _ram(ram), + _ep(&cap, _ep_stack_size, "floating_window_layouter"), + _policy(_ep, ram, window_list_rom, hover_rom, input, + window_layout_report, resize_request_report, focus_report), + _slave(_ep, _policy, _quota) + { } +}; + +#endif /* _WINDOW_LAYOUTER_SLAVE_H_ */ diff --git a/repos/gems/src/server/wm/window_registry.h b/repos/gems/src/server/wm/window_registry.h new file mode 100644 index 000000000..d416fe8f5 --- /dev/null +++ b/repos/gems/src/server/wm/window_registry.h @@ -0,0 +1,188 @@ +/* + * \brief Window registry + * \author Norman Feske + * \date 2014-05-02 + */ + +/* + * Copyright (C) 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. + */ + +#ifndef _WINDOW_REGISTRY_H_ +#define _WINDOW_REGISTRY_H_ + +/* Genode includes */ +#include <util/bit_allocator.h> +#include <util/list.h> +#include <util/xml_generator.h> +#include <base/allocator.h> +#include <os/surface.h> + +/* local includes */ +#include <local_reporter.h> + + +namespace Wm { class Window_registry; } + + +namespace Wm { + using Genode::Allocator; + using Genode::List; + using Genode::Xml_generator; + + typedef Genode::Surface_base::Area Area; + typedef Genode::Surface_base::Point Point; + typedef Genode::Surface_base::Rect Rect; +} + + +class Wm::Window_registry +{ + public: + + struct Id + { + unsigned value; + + Id(unsigned value) : value(value) { } + + Id() /* invalid */ : value(0) { } + + bool operator == (Id const &other) const { return value == other.value; } + + bool valid() const { return value != 0; } + }; + + class Window : public List<Window>::Element + { + public: + + typedef Genode::String<200> Title; + + private: + + Id const _id; + + Title _title; + + Area _size; + + friend class Window_registry; + + Window(Id id) : _id(id) { } + + public: + + Id id() const { return _id; } + + /* + * Accessors for setting attributes + */ + void attr(Title const &title) { _title = title; } + void attr(Area size) { _size = size; } + + void generate_window_list_entry_xml(Xml_generator &xml) const + { + xml.node("window", [&] () { + xml.attribute("id", _id.value); + xml.attribute("title", _title.string()); + xml.attribute("width", _size.w()); + xml.attribute("height", _size.h()); + }); + } + }; + + private: + + Allocator &_alloc; + Local_reporter &_window_list_reporter; + + enum { MAX_WINDOWS = 1024 }; + + Genode::Bit_allocator<MAX_WINDOWS> _window_ids; + + List<Window> _windows; + + Window *_lookup(Id id) + { + for (Window *w = _windows.first(); w; w = w->next()) + if (w->id() == id) + return w; + + return 0; + } + + void _report_updated_window_list_model() const + { + Local_reporter::Xml_generator xml(_window_list_reporter, [&] () + { + for (Window const *w = _windows.first(); w; w = w->next()) + w->generate_window_list_entry_xml(xml); + }); + } + + template <typename ATTR> + void _set_attr(Id const id, ATTR const &value) + { + Window * const win = _lookup(id); + + if (!win) { + PWRN("lookup for window ID %d failed", id.value); + return; + } + + win->attr(value); + + _report_updated_window_list_model(); + } + + public: + + Window_registry(Allocator &alloc, Local_reporter &window_list_reporter) + : + _alloc(alloc), _window_list_reporter(window_list_reporter) + { + /* preserve ID 0 to represent an invalid ID */ + _window_ids.alloc(); + } + + Id create() + { + Window * const win = new (_alloc) Window(_window_ids.alloc()); + + _windows.insert(win); + + /* + * Even though we change the window-list model by adding a + * window, we don't call '_report_updated_window_list_model' here + * because the window does not have any useful properties before + * the 'size' function has been called. + * + * XXX should we pass the initial size as argument to this function? + */ + + return win->id(); + } + + void destroy(Id id) + { + Window * const win = _lookup(id); + if (!win) + return; + + _windows.remove(win); + + Genode::destroy(&_alloc, win); + + _report_updated_window_list_model(); + } + + void size(Id id, Area size) { _set_attr(id, size); } + + void title(Id id, Window::Title title) { _set_attr(id, title); } +}; + +#endif /* _WINDOW_REGISTRY_H_ */