diff --git a/repos/gems/run/launcher.run b/repos/gems/run/launcher.run new file mode 100644 index 0000000000..a643cae848 --- /dev/null +++ b/repos/gems/run/launcher.run @@ -0,0 +1,177 @@ +# +# Build +# + +set build_components { + core init drivers/timer drivers/framebuffer drivers/input drivers/pci + server/dynamic_rom server/nitpicker server/report_rom + app/pointer app/menu_view + app/scout app/launchpad app/launcher test/nitpicker + server/nit_fader +} + +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 ps2] config { + + + + } + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +# +# Boot modules +# + +# generic modules +set boot_modules { + core init timer dynamic_rom nitpicker pointer menu_view + ld.lib.so libpng.lib.so libc.lib.so libm.lib.so zlib.lib.so + menu_view_styles.tar + scout launchpad testnit + nit_fader report_rom launcher +} + +# 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 + +build_boot_image $boot_modules + +run_genode_until forever diff --git a/repos/gems/src/app/launcher/context_dialog.h b/repos/gems/src/app/launcher/context_dialog.h new file mode 100644 index 0000000000..d9c0f03ba7 --- /dev/null +++ b/repos/gems/src/app/launcher/context_dialog.h @@ -0,0 +1,230 @@ +/* + * \brief Context dialog + * \author Norman Feske + * \date 2014-10-04 + */ + +/* + * 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 _CONTEXT_DIALOG_H_ +#define _CONTEXT_DIALOG_H_ + +/* local includes */ +#include + +namespace Launcher { struct Context_dialog; } + + +class Launcher::Context_dialog : Input_event_handler, Dialog_generator, + Hover_handler, Dialog_model +{ + public: + + struct Response_handler + { + virtual void handle_context_kill() = 0; + virtual void handle_context_hide() = 0; + }; + + private: + + struct Element : List::Element + { + Label label; + + bool hovered = false; + bool touched = false; + bool selected = false; + + Element(char const *label) : label(label) { } + }; + + Label _hovered() const + { + for (Element const *e = _elements.first(); e; e = e->next()) + if (e->hovered) + return e->label; + + return Label(""); + } + + List _elements; + + void _generate_dialog_elements(Xml_generator &xml) + { + for (Element const *e = _elements.first(); e; e = e->next()) { + + xml.node("button", [&] () { + xml.attribute("name", e->label.string()); + + if ((e->hovered && !_click_in_progress) + || (e->hovered && e->touched)) + xml.attribute("hovered", "yes"); + + if (e->selected || e->touched) + xml.attribute("selected", "yes"); + + xml.node("label", [&] () { + xml.attribute("text", e->label.string()); + }); + }); + } + } + + void _touch(Label const &label) + { + for (Element *e = _elements.first(); e; e = e->next()) + e->touched = (e->label == label); + } + + void _reset_hover() + { + for (Element *e = _elements.first(); e; e = e->next()) + e->hovered = false; + } + + Element _hide_button { "Hide" }; + Element _kill_button { "Kill" }; + + Fading_dialog _dialog; + + unsigned _key_cnt = 0; + Label _clicked; + bool _click_in_progress = false; + + Label _subsystem; + + Response_handler &_response_handler; + + public: + + Context_dialog(Server::Entrypoint &ep, Cap_session &cap, Ram_session &ram, + Report_rom_slave &report_rom_slave, + Response_handler &response_handler) + : + _dialog(ep, cap, ram, report_rom_slave, "context_dialog", "context_hover", + *this, *this, *this, *this, + Fading_dialog::Position(364, 64)), + _response_handler(response_handler) + { + _elements.insert(&_hide_button); + _elements.insert(&_kill_button); + + _dialog.update(); + } + + /** + * Dialog_generator interface + */ + void generate_dialog(Xml_generator &xml) override + { + xml.node("frame", [&] () { + xml.node("vbox", [&] () { + _generate_dialog_elements(xml); + }); + }); + } + + /** + * Hover_handler interface + */ + void hover_changed(Xml_node hover) override + { + Label const old_hovered = _hovered(); + + _reset_hover(); + + try { + Xml_node button = hover.sub_node("dialog") + .sub_node("frame") + .sub_node("vbox") + .sub_node("button"); + + for (Element *e = _elements.first(); e; e = e->next()) { + + Label const label = + Decorator::string_attribute(button, "name", Label("")); + + if (e->label == label) + e->hovered = true; + } + } catch (Xml_node::Nonexistent_sub_node) { } + + Label const new_hovered = _hovered(); + + if (old_hovered != new_hovered) + dialog_changed(); + } + + /** + * Input_event_handler interface + */ + bool handle_input_event(Input::Event const &ev) override + { + if (ev.type() == Input::Event::MOTION) + return true; + + if (ev.type() == Input::Event::PRESS) _key_cnt++; + if (ev.type() == Input::Event::RELEASE) _key_cnt--; + + if (ev.type() == Input::Event::PRESS + && ev.keycode() == Input::BTN_LEFT + && _key_cnt == 1) { + + Label const hovered = _hovered(); + + _click_in_progress = true; + _clicked = hovered; + _touch(hovered); + dialog_changed(); + } + + if (ev.type() == Input::Event::RELEASE + && _click_in_progress && _key_cnt == 0) { + + Label const hovered = _hovered(); + + if (_clicked == hovered) { + + if (_kill_button.hovered) + _response_handler.handle_context_kill(); + + if (_hide_button.hovered) + _response_handler.handle_context_hide(); + } else { + _touch(""); + } + + _clicked = Label(""); + _click_in_progress = false; + dialog_changed(); + } + + return false; + } + + void visible(bool visible) + { + /* reset touch state when (re-)opening the context dialog */ + if (visible) { + _touch(""); + _reset_hover(); + dialog_changed(); + _dialog.update(); + } + + _dialog.visible(visible); + } + + void position(Fading_dialog::Position position) + { + _dialog.position(position); + } +}; + +#endif /* _CONTEXT_DIALOG_H_ */ diff --git a/repos/gems/src/app/launcher/dialog_nitpicker.h b/repos/gems/src/app/launcher/dialog_nitpicker.h new file mode 100644 index 0000000000..f6f51842f7 --- /dev/null +++ b/repos/gems/src/app/launcher/dialog_nitpicker.h @@ -0,0 +1,123 @@ +/* + * \brief Local nitpicker service provided to dialog slaves + * \author Norman Feske + * \date 2014-10-01 + * + * This implementation of the nitpicker interface intercepts the input events + * of a dialog slave to let the launcher respond to events (like mouse clicks) + * directly. + */ + +/* + * 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 _DIALOG_NITPICKER_H_ +#define _DIALOG_NITPICKER_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include + +/* gems includes */ +#include + +/* local includes */ +#include + +namespace Launcher { + + struct Dialog_nitpicker_session; + struct Dialog_nitpicker_service; +} + + +struct Launcher::Dialog_nitpicker_session : Wrapped_nitpicker_session +{ + struct Input_event_handler + { + /** + * Handle input event + * + * \return true if the event should be propagated to the wrapped + * nitpicker session + */ + virtual bool handle_input_event(Input::Event const &ev) = 0; + }; + + Input_event_handler &_input_event_handler; + + Server::Entrypoint &_ep; + + Nitpicker::Session &_nitpicker_session; + + Input::Session_client _nitpicker_input { _nitpicker_session.input_session() }; + + Attached_dataspace _nitpicker_input_ds { _nitpicker_input.dataspace() }; + + Signal_rpc_member + _input_dispatcher { _ep, *this, &Dialog_nitpicker_session::_input_handler }; + + Input::Session_component _input_session; + + Capability _input_session_cap { _ep.manage(_input_session) }; + + /** + * Constructor + */ + Dialog_nitpicker_session(Nitpicker::Session &nitpicker_session, + Server::Entrypoint &ep, + Input_event_handler &input_event_handler) + : + Wrapped_nitpicker_session(nitpicker_session), + _input_event_handler(input_event_handler), + _ep(ep), + _nitpicker_session(nitpicker_session) + { + _nitpicker_input.sigh(_input_dispatcher); + + _input_session.event_queue().enabled(true); + } + + ~Dialog_nitpicker_session() + { + _ep.dissolve(_input_session); + } + + 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(); + for (size_t i = 0; i < num_events; i++) { + + Input::Event const &ev = events[i]; + + if (_input_event_handler.handle_input_event(ev)) + _input_session.submit(ev); + } + } + } + + + /********************************* + ** Nitpicker session interface ** + *********************************/ + + Input::Session_capability input_session() override + { + return _input_session_cap; + } +}; + +#endif /* _DIALOG_NITPICKER_H_ */ diff --git a/repos/gems/src/app/launcher/fading_dialog.h b/repos/gems/src/app/launcher/fading_dialog.h new file mode 100644 index 0000000000..09e2e9489b --- /dev/null +++ b/repos/gems/src/app/launcher/fading_dialog.h @@ -0,0 +1,254 @@ +/* + * \brief Dialog + * \author Norman Feske + * \date 2014-10-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 _FADING_DIALOG_H_ +#define _FADING_DIALOG_H_ + +/* gems includes */ +#include +#include + +/* local includes */ +#include +#include +#include +#include + +namespace Launcher +{ + struct Dialog_generator { virtual void generate_dialog(Xml_generator &) = 0; }; + + struct Hover_handler { virtual void hover_changed(Xml_node hover) = 0; }; + + typedef Dialog_nitpicker_session::Input_event_handler Input_event_handler; + + class Fading_dialog; + + class Dialog_model + { + private: + + bool _up_to_date = true; + + friend class Fading_dialog; + + public: + + void dialog_changed() { _up_to_date = false; } + }; +} + + + +class Launcher::Fading_dialog : private Input_event_handler +{ + private: + + Rom_session_capability _dialog_rom; + + /* dialog reported locally */ + Capability _dialog_report; + + Rom_session_client _hover_rom; + + Lazy_volatile_object _hover_ds; + + /* hovered element reported by menu view */ + Capability _hover_report; + + Local_reporter _dialog_reporter { "dialog", _dialog_report }; + + Input_event_handler &_dialog_input_event_handler; + + Hover_handler &_hover_handler; + + Dialog_generator &_dialog_generator; + + Dialog_model &_dialog_model; + + void _update_dialog() + { + bool const dialog_needs_update = !_dialog_model._up_to_date; + + _dialog_model._up_to_date = false; + + if (dialog_needs_update) { + Local_reporter::Xml_generator xml(_dialog_reporter, [&] () + { + _dialog_generator.generate_dialog(xml); + }); + } + } + + /** + * Input_event_handler interface + */ + bool handle_input_event(Input::Event const &ev) override + { + bool const forward_event = _dialog_input_event_handler.handle_input_event(ev); + _update_dialog(); + return forward_event; + } + + void _handle_hover_update(unsigned) + { + try { + if (!_hover_ds.is_constructed() || _hover_rom.update() == false) { + if (_hover_ds.is_constructed()) + _hover_ds->invalidate(); + _hover_ds.construct(_hover_rom.dataspace()); + } + + Xml_node hover(_hover_ds->local_addr()); + + _hover_handler.hover_changed(hover); + + bool const dialog_needs_update = !_dialog_model._up_to_date; + + _dialog_model._up_to_date = true; + + if (dialog_needs_update) { + Local_reporter::Xml_generator xml(_dialog_reporter, [&] () + { + _dialog_generator.generate_dialog(xml); + }); + } + + } catch (...) { + PWRN("no menu hover model available"); + } + } + + Signal_rpc_member _hover_update_dispatcher; + + private: + + /** + * Local nitpicker service to be handed out to the menu view slave + */ + struct Nitpicker_service : Genode::Service + { + Server::Entrypoint &ep; + Rpc_entrypoint &child_ep; + + /* connection to real nitpicker */ + Nitpicker::Connection connection { "menu" }; + + Dialog_nitpicker_session wrapper_session; + + Capability session_cap { child_ep.manage(&wrapper_session) }; + + Nitpicker_service(Server::Entrypoint &ep, + Rpc_entrypoint &child_ep, + Dialog_nitpicker_session::Input_event_handler &ev_handler) + : + Genode::Service(Nitpicker::Session::service_name()), + ep(ep), child_ep(child_ep), + wrapper_session(connection, ep, ev_handler) + { } + + /******************************* + ** Genode::Service interface ** + *******************************/ + + Genode::Session_capability + session(const char *, Genode::Affinity const &) override + { + return session_cap; + } + + void upgrade(Genode::Session_capability, const char *args) override + { + PDBG("upgrade called args: '%s'", args); + } + + void close(Genode::Session_capability) override { } + }; + + /* + * Entrypoint for the fader slave + * + * This entrypoint is used for handling the parent interface for the + * fader slave and for providing the wrapped nitpicker service to the + * slave. The latter cannot be provided by the main entrypoint because + * during the construction of the 'nit_fader_slave' (executed in the + * context of the main entrypoint), the slave tries to create a + * nitpicker session (to be answered with the wrapped session). + */ + size_t const _fader_slave_ep_stack_size = 4*1024*sizeof(addr_t); + Rpc_entrypoint _fader_slave_ep; + + Nitpicker_service _nitpicker_service; + Nit_fader_slave _nit_fader_slave; + Menu_view_slave _menu_view_slave; + + + public: + + typedef Menu_view_slave::Position Position; + + /** + * Constructor + * + * \param ep main entrypoint, used for managing the local input + * session provided (indirectly through the wrapped + * nitpicker session) to the menu view + * \param cap capability session to be used for creating the + * slave entrypoints + * \param ram RAM session where to draw the memory for providing + * configuration data to the slave processes + */ + Fading_dialog(Server::Entrypoint &ep, + Cap_session &cap, + Ram_session &ram, + Report_rom_slave &report_rom_slave, + char const *dialog_name, + char const *hover_name, + Input_event_handler &input_event_handler, + Hover_handler &hover_handler, + Dialog_generator &dialog_generator, + Dialog_model &dialog_model, + Position initial_position) + : + _dialog_rom(report_rom_slave.rom_session(dialog_name)), + _dialog_report(report_rom_slave.report_session(dialog_name)), + _hover_rom(report_rom_slave.rom_session(hover_name)), + _hover_report(report_rom_slave.report_session(hover_name)), + _dialog_input_event_handler(input_event_handler), + _hover_handler(hover_handler), + _dialog_generator(dialog_generator), + _dialog_model(dialog_model), + _hover_update_dispatcher(ep, *this, &Fading_dialog::_handle_hover_update), + _fader_slave_ep(&cap, _fader_slave_ep_stack_size, "nit_fader"), + _nitpicker_service(ep, _fader_slave_ep, *this), + _nit_fader_slave(_fader_slave_ep, ram, _nitpicker_service), + _menu_view_slave(cap, ram, _nit_fader_slave.nitpicker_session("menu"), + _dialog_rom, _hover_report, initial_position) + { + Rom_session_client(_hover_rom).sigh(_hover_update_dispatcher); + } + + void update() + { + Local_reporter::Xml_generator xml(_dialog_reporter, [&] () + { + _dialog_generator.generate_dialog(xml); + }); + } + + void visible(bool visible) { _nit_fader_slave.visible(visible); } + + void position(Position position) { _menu_view_slave.position(position); } +}; + +#endif /* _FADING_DIALOG_H_ */ diff --git a/repos/gems/src/app/launcher/main.cc b/repos/gems/src/app/launcher/main.cc new file mode 100644 index 0000000000..d7ef593628 --- /dev/null +++ b/repos/gems/src/app/launcher/main.cc @@ -0,0 +1,147 @@ +/* + * \brief Launcher + * \author Norman Feske + * \date 2014-09-30 + */ + +/* + * 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 + + +namespace Launcher { struct Main; } + +struct Launcher::Main +{ + Server::Entrypoint ep; + + Genode::Cap_connection cap; + + char const *report_rom_config = + " " + " " + " " + " " + " " + " "; + + Report_rom_slave report_rom_slave = { cap, *env()->ram_session(), report_rom_config }; + + + /** + * Nitpicker session used to perform session-control operations on the + * subsystem's nitpicker sessions. + */ + Nitpicker::Connection nitpicker; + + Subsystem_manager subsystem_manager { ep, cap }; + + Menu_dialog menu_dialog { ep, cap, *env()->ram_session(), report_rom_slave, + subsystem_manager, nitpicker }; + + + Lazy_volatile_object xray_rom_ds; + + enum Visibility { VISIBILITY_ALWAYS, VISIBILITY_XRAY }; + + Visibility visibility = VISIBILITY_ALWAYS; + + void handle_config(unsigned); + + Genode::Signal_rpc_member
xray_update_dispatcher = + { ep, *this, &Main::handle_xray_update }; + + void handle_xray_update(unsigned); + + + /** + * Constructor + */ + Main(Server::Entrypoint &ep) : ep(ep) + { + handle_config(0); + + if (visibility == VISIBILITY_ALWAYS) + menu_dialog.visible(true); + } +}; + + +void Launcher::Main::handle_config(unsigned) +{ + config()->reload(); + + /* set default visibility */ + visibility = VISIBILITY_ALWAYS; + + /* obtain model about nitpicker's xray mode */ + if (config()->xml_node().has_attribute("visibility")) { + if (config()->xml_node().attribute("visibility").has_value("xray")) { + xray_rom_ds.construct("xray"); + xray_rom_ds->sigh(xray_update_dispatcher); + + visibility = VISIBILITY_XRAY; + + /* manually import the initial xray state */ + handle_xray_update(0); + } + } + + menu_dialog.update(); +} + + +void Launcher::Main::handle_xray_update(unsigned) +{ + xray_rom_ds->update(); + if (!xray_rom_ds->is_valid()) { + PWRN("could not access xray info"); + menu_dialog.visible(false); + return; + } + + Xml_node xray(xray_rom_ds->local_addr()); + + bool const visible = xray.has_attribute("enabled") + && xray.attribute("enabled").has_value("yes"); + + menu_dialog.visible(visible); +} + + +/************ + ** Server ** + ************/ + +namespace Server { + + char const *name() { return "desktop_ep"; } + + size_t stack_size() { return 4*1024*sizeof(long); } + + void construct(Entrypoint &ep) + { + /* look for dynamic linker */ + try { + static Rom_connection rom("ld.lib.so"); + Process::dynamic_linker(rom.dataspace()); + } catch (...) { } + + static Launcher::Main desktop(ep); + } +} diff --git a/repos/gems/src/app/launcher/menu_dialog.h b/repos/gems/src/app/launcher/menu_dialog.h new file mode 100644 index 0000000000..0ff7e137e2 --- /dev/null +++ b/repos/gems/src/app/launcher/menu_dialog.h @@ -0,0 +1,387 @@ +/* + * \brief Menu dialog + * \author Norman Feske + * \date 2014-10-04 + */ + +/* + * 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 _MENU_DIALOG_H_ +#define _MENU_DIALOG_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include +#include + +namespace Launcher { struct Menu_dialog; } + + +class Launcher::Menu_dialog : Input_event_handler, Dialog_generator, + Hover_handler, Dialog_model, + Context_dialog::Response_handler +{ + private: + + typedef String<128> Title; + + struct Element : List::Element + { + Label label; + Title title; + + bool hovered = false; + bool touched = false; + bool running = false; + + Element(Xml_node node) + : + label(Decorator::string_attribute(node, "name", Label(""))), + title(Decorator::string_attribute(node, "title", Title(label.string()))) + { } + }; + + List _elements; + + void _generate_dialog_elements(Xml_generator &xml) + { + for (Element const *e = _elements.first(); e; e = e->next()) { + + xml.node("button", [&] () { + xml.attribute("name", e->label.string()); + + if ((e->hovered && !_click_in_progress) + || (e->hovered && e->touched)) + xml.attribute("hovered", "yes"); + + if (e->running || e->touched) + xml.attribute("selected", "yes"); + + xml.node("label", [&] () { + xml.attribute("text", e->title.string()); + }); + }); + } + } + + class Lookup_failed { }; + + Element const &_lookup_const(Label const &label) const + { + for (Element const *e = _elements.first(); e; e = e->next()) + if (e->label == label) + return *e; + + throw Lookup_failed(); + } + + Element &_lookup(Label const &label) + { + for (Element *e = _elements.first(); e; e = e->next()) + if (e->label == label) + return *e; + + throw Lookup_failed(); + } + + Fading_dialog::Position _position { 32, 32 }; + + Timer::Connection _timer; + Subsystem_manager &_subsystem_manager; + Nitpicker::Session &_nitpicker; + Fading_dialog _dialog; + + Rect _hovered_rect; + + unsigned _key_cnt = 0; + Label _clicked; + bool _click_in_progress = false; + + Signal_rpc_member _timer_dispatcher; + + Label _context_subsystem; + Context_dialog _context_dialog; + + Label _hovered() const + { + for (Element const *e = _elements.first(); e; e = e->next()) + if (e->hovered) + return e->label; + + return Label(""); + } + + bool _running(Label const &label) const + { + try { return _lookup_const(label).running; } + catch (Lookup_failed) { return false; } + } + + void _running(Label const &label, bool running) + { + try { _lookup(label).running = running; } + catch (Lookup_failed) { } + } + + void _touch(Label const &label) + { + for (Element *e = _elements.first(); e; e = e->next()) + e->touched = (e->label == label); + } + + /** + * Lookup subsystem in config + */ + static Xml_node _subsystem(Xml_node config, char const *name) + { + Xml_node node = config.sub_node("subsystem"); + for (;; node = node.next("subsystem")) { + if (node.attribute("name").has_value(name)) + return node; + } + } + + void _start(Label const &label) + { + try { + _subsystem_manager.start(_subsystem(config()->xml_node(), + label.string())); + _running(label, true); + + dialog_changed(); + + } catch (Xml_node::Nonexistent_sub_node) { + PERR("no subsystem config found for \"%s\"", label.string()); + } catch (Subsystem_manager::Invalid_config) { + PERR("invalid subsystem configuration for \"%s\"", label.string()); + } + } + + void _kill(Label const &label) + { + _subsystem_manager.kill(label.string()); + _running(label, false); + dialog_changed(); + _dialog.update(); + + _context_dialog.visible(false); + } + + void _hide(Label const &label) + { + _nitpicker.session_control(selector(label.string()), + Nitpicker::Session::SESSION_CONTROL_HIDE); + + _context_dialog.visible(false); + } + + void _handle_timer(unsigned) + { + if (_click_in_progress && _hovered() == _clicked) { + + _touch(""); + + Fading_dialog::Position position(_hovered_rect.p2().x(), + _hovered_rect.p1().y() - 44); + _context_subsystem = _clicked; + _context_dialog.position(_position + position); + _context_dialog.visible(true); + } + + _click_in_progress = false; + } + + public: + + Menu_dialog(Server::Entrypoint &ep, Cap_session &cap, Ram_session &ram, + Report_rom_slave &report_rom_slave, + Subsystem_manager &subsystem_manager, + Nitpicker::Session &nitpicker) + : + _subsystem_manager(subsystem_manager), + _nitpicker(nitpicker), + _dialog(ep, cap, ram, report_rom_slave, "menu_dialog", "menu_hover", + *this, *this, *this, *this, + _position), + _timer_dispatcher(ep, *this, &Menu_dialog::_handle_timer), + _context_dialog(ep, cap, ram, report_rom_slave, *this) + { + _timer.sigh(_timer_dispatcher); + } + + /** + * Dialog_generator interface + */ + void generate_dialog(Xml_generator &xml) override + { + xml.node("frame", [&] () { + xml.node("vbox", [&] () { + _generate_dialog_elements(xml); + }); + }); + } + + Rect _hovered_button_rect(Xml_node hover) const + { + Point p(0, 0); + + for (;; hover = hover.sub_node()) { + + p = p + Point(point_attribute(hover)); + + if (hover.has_type("button")) + return Rect(p, area_attribute(hover)); + + if (!hover.num_sub_nodes()) + break; + } + + return Rect(); + } + + /** + * Hover_handler interface + */ + void hover_changed(Xml_node hover) override + { + Label const old_hovered = _hovered(); + + for (Element *e = _elements.first(); e; e = e->next()) + e->hovered = false; + + try { + Xml_node button = hover.sub_node("dialog") + .sub_node("frame") + .sub_node("vbox") + .sub_node("button"); + + for (Element *e = _elements.first(); e; e = e->next()) { + + Label const label = + Decorator::string_attribute(button, "name", Label("")); + + if (e->label == label) { + e->hovered = true; + + _hovered_rect = _hovered_button_rect(hover); + } + } + } catch (Xml_node::Nonexistent_sub_node) { } + + Label const new_hovered = _hovered(); + + if (old_hovered != new_hovered) + dialog_changed(); + } + + /** + * Input_event_handler interface + */ + bool handle_input_event(Input::Event const &ev) override + { + if (ev.type() == Input::Event::MOTION) + return true; + + if (ev.type() == Input::Event::PRESS) _key_cnt++; + if (ev.type() == Input::Event::RELEASE) _key_cnt--; + + if (ev.type() == Input::Event::PRESS + && ev.keycode() == Input::BTN_LEFT + && _key_cnt == 1) { + + _context_dialog.visible(false); + + Label const hovered = _hovered(); + + _click_in_progress = true; + _clicked = hovered; + _touch(hovered); + + enum { CONTEXT_DELAY = 500 }; + + if (_running(hovered)) { + _nitpicker.session_control(selector(hovered.string()), + Nitpicker::Session::SESSION_CONTROL_TO_FRONT); + _nitpicker.session_control(selector(hovered.string()), + Nitpicker::Session::SESSION_CONTROL_SHOW); + _timer.trigger_once(CONTEXT_DELAY*1000); + } + } + + if (ev.type() == Input::Event::RELEASE + && _click_in_progress && _key_cnt == 0) { + + Label const hovered = _hovered(); + + if (_clicked == hovered) { + + if (!_running(hovered)) + _start(hovered); + } else { + _touch(""); + } + + _clicked = Label(""); + _click_in_progress = false; + } + + return false; + } + + /** + * Context_dialog::Response_handler interface + */ + void handle_context_kill() override + { + _kill(_context_subsystem); + } + + /** + * Context_dialog::Response_handler interface + */ + void handle_context_hide() override + { + _hide(_context_subsystem); + } + + void visible(bool visible) + { + _dialog.visible(visible); + + if (!visible) + _context_dialog.visible(false); + } + + void update() + { + if (_elements.first()) { + PERR("subsequent updates are not supported"); + return; + } + + Element *last = nullptr; + + Xml_node subsystems = config()->xml_node(); + + subsystems.for_each_sub_node("subsystem", + [&] (Xml_node subsystem) + { + Element * const e = new (env()->heap()) Element(subsystem); + + _elements.insert(e, last); + last = e; + }); + + _dialog.update(); + } +}; + +#endif /* _MENU_DIALOG_H_ */ diff --git a/repos/gems/src/app/launcher/menu_view_slave.h b/repos/gems/src/app/launcher/menu_view_slave.h new file mode 100644 index 0000000000..06b92db91d --- /dev/null +++ b/repos/gems/src/app/launcher/menu_view_slave.h @@ -0,0 +1,161 @@ +/* + * \brief Slave used for presenting the menu + * \author Norman Feske + * \date 2014-09-30 + */ + +/* + * 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 _MENU_VIEW_SLAVE_H_ +#define _MENU_VIEW_SLAVE_H_ + +/* Genode includes */ +#include +#include + +/* gems includes */ +#include + +/* local includes */ +#include + +namespace Launcher { class Menu_view_slave; } + + +class Launcher::Menu_view_slave +{ + public: + + typedef Surface_base::Point Position; + + private: + + class Policy : public Genode::Slave_policy + { + private: + + Lock mutable _nitpicker_root_lock { Lock::LOCKED }; + Capability _nitpicker_root_cap; + + Single_session_service _nitpicker_service; + Single_session_service _dialog_rom_service; + Single_session_service _hover_report_service; + + Position _position; + + protected: + + char const **_permitted_services() const + { + static char const *permitted_services[] = { + "ROM", "CAP", "LOG", "SIGNAL", "RM", "Timer", 0 }; + + return permitted_services; + }; + + private: + + void _configure(Position pos) + { + char config[1024]; + + snprintf(config, sizeof(config), + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", + pos.x(), pos.y()); + + configure(config); + } + + public: + + Policy(Genode::Rpc_entrypoint &entrypoint, + Genode::Ram_session &ram, + Capability nitpicker_session, + Capability dialog_rom_session, + Capability hover_report_session, + Position position) + : + Slave_policy("menu_view", entrypoint, &ram), + _nitpicker_service(Nitpicker::Session::service_name(), nitpicker_session), + _dialog_rom_service(Rom_session::service_name(), dialog_rom_session), + _hover_report_service(Report::Session::service_name(), hover_report_session), + _position(position) + { + _configure(position); + } + + void position(Position pos) + { + _configure(pos); + } + + 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, "menu_view -> dialog") == 0) + return &_dialog_rom_service; + } + + if (strcmp(service_name, "Report") == 0) { + + if (strcmp(label, "menu_view -> 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 ep entrypoint used for child thread + * \param ram RAM session used to allocate the configuration + * dataspace + */ + Menu_view_slave(Genode::Cap_session &cap, Genode::Ram_session &ram, + Capability nitpicker_session, + Capability dialog_rom_session, + Capability hover_report_session, + Position initial_position) + : + _ep(&cap, _ep_stack_size, "nit_fader"), + _policy(_ep, ram, nitpicker_session, dialog_rom_session, + hover_report_session, initial_position), + _slave(_ep, _policy, _quota) + { } + + void position(Position position) { _policy.position(position); } +}; + +#endif /* _NIT_FADER_SLAVE_H_ */ diff --git a/repos/gems/src/app/launcher/nit_fader_slave.h b/repos/gems/src/app/launcher/nit_fader_slave.h new file mode 100644 index 0000000000..7c55dd16be --- /dev/null +++ b/repos/gems/src/app/launcher/nit_fader_slave.h @@ -0,0 +1,153 @@ +/* + * \brief Slave used for toggling the visibility of a nitpicker session + * \author Norman Feske + * \date 2014-09-30 + */ + +/* + * 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 _NIT_FADER_SLAVE_H_ +#define _NIT_FADER_SLAVE_H_ + +/* Genode includes */ +#include +#include + +/* local includes */ +#include + +namespace Launcher { class Nit_fader_slave; } + + +class Launcher::Nit_fader_slave +{ + private: + + class Policy : public Slave_policy + { + private: + + Genode::Service &_nitpicker_service; + Lock mutable _nitpicker_root_lock { Lock::LOCKED }; + Capability _nitpicker_root_cap; + + protected: + + char const **_permitted_services() const + { + static char const *permitted_services[] = { + "CAP", "LOG", "SIGNAL", "RM", "Timer", 0 }; + + return permitted_services; + }; + + public: + + Policy(Rpc_entrypoint &entrypoint, + Ram_session &ram, + Genode::Service &nitpicker_service) + : + Slave_policy("nit_fader", entrypoint, &ram), + _nitpicker_service(nitpicker_service) + { + visible(false); + } + + void visible(bool visible) + { + char config[256]; + snprintf(config, sizeof(config), + "", visible ? 255 : 0); + configure(config, strlen(config) + 1); + } + + bool announce_service(const char *service_name, + Root_capability root, + Allocator *, + Genode::Server *) + { + if (strcmp(service_name, "Nitpicker") == 0) + _nitpicker_root_cap = root; + else + return false; + + if (_nitpicker_root_cap.valid()) + _nitpicker_root_lock.unlock(); + + return true; + } + + Genode::Service *resolve_session_request(const char *service_name, + const char *args) override + { + if (Genode::strcmp(service_name, "Nitpicker") == 0) + return &_nitpicker_service; + + return Genode::Slave_policy::resolve_session_request(service_name, args); + } + + Root_capability nitpicker_root() const + { + Lock::Guard guard(_nitpicker_root_lock); + return _nitpicker_root_cap; + } + }; + + Policy _policy; + size_t const _quota = 2*1024*1024; + Slave _slave; + Root_client _nitpicker_root; + + public: + + /** + * Constructor + * + * \param ep entrypoint used for nitpicker child thread + * \param ram RAM session used to allocate the configuration + * dataspace + */ + Nit_fader_slave(Rpc_entrypoint &ep, Ram_session &ram, + Genode::Service &nitpicker_service) + : + _policy(ep, ram, nitpicker_service), + _slave(ep, _policy, _quota), + _nitpicker_root(_policy.nitpicker_root()) + { + visible(false); + } + + Capability nitpicker_session(char const *label) + { + enum { ARGBUF_SIZE = 128 }; + char argbuf[ARGBUF_SIZE]; + argbuf[0] = 0; + + /* + * Declare ram-quota donation + */ + enum { SESSION_METADATA = 8*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 = _nitpicker_root.session(argbuf, Affinity()); + + return static_cap_cast(session_cap); + } + + void visible(bool visible) + { + _policy.visible(visible); + } +}; + +#endif /* _NIT_FADER_SLAVE_H_ */ diff --git a/repos/gems/src/app/launcher/subsystem_manager.h b/repos/gems/src/app/launcher/subsystem_manager.h new file mode 100644 index 0000000000..3f90f4fb90 --- /dev/null +++ b/repos/gems/src/app/launcher/subsystem_manager.h @@ -0,0 +1,237 @@ +/* + * \brief Management of subsystems + * \author Norman Feske + * \date 2014-10-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 _SUBSYSTEM_MANAGER_H_ +#define _SUBSYSTEM_MANAGER_H_ + +/* Genode includes */ +#include +#include + +/* CLI-monitor includes */ +#include + +namespace Launcher { + + class Subsystem_manager; + using Decorator::string_attribute; +} + +/*************** + ** Utilities ** + ***************/ + +/* + * XXX copied from 'cli_monitor/main.cc' + */ +static Genode::size_t ram_preservation_from_config() +{ + Genode::Number_of_bytes ram_preservation = 0; + try { + Genode::Xml_node node = + Genode::config()->xml_node().sub_node("preservation"); + + if (node.attribute("name").has_value("RAM")) + node.attribute("quantum").value(&ram_preservation); + } catch (...) { } + + return ram_preservation; +} + + +class Launcher::Subsystem_manager +{ + public: + + /** + * Exception types + */ + class Invalid_config { }; + + private: + + Server::Entrypoint &_ep; + Cap_session &_cap; + + struct Child : Child_base, List::Element + { + typedef String<128> Binary_name; + + Child(Ram &ram, + Label const &label, + Binary_name const &binary, + Cap_session &cap_session, + size_t ram_quota, + size_t ram_limit, + Signal_context_capability yield_response_sig_cap) + : + Child_base(ram, + label.string(), + binary.string(), + cap_session, + ram_quota, + ram_limit, + yield_response_sig_cap) + { } + }; + + List _children; + + void _try_response_to_resource_request() + { + for (Child *child = _children.first(); child; child = child->next()) + child->try_response_to_resource_request(); + } + + Genode::Signal_rpc_member _yield_broadcast_dispatcher = + { _ep, *this, &Subsystem_manager::_handle_yield_broadcast }; + + void _handle_yield_broadcast(unsigned) + { + _try_response_to_resource_request(); + + /* + * XXX copied from 'cli_monitor/main.cc' + */ + + /* + * Compute argument of yield request to be broadcasted to all + * processes. + */ + size_t amount = 0; + + /* amount needed to reach preservation limit */ + Ram::Status ram_status = _ram.status(); + if (ram_status.avail < ram_status.preserve) + amount += ram_status.preserve - ram_status.avail; + + /* sum of pending resource requests */ + for (Child *child = _children.first(); child; child = child->next()) + amount += child->requested_ram_quota(); + + for (Child *child = _children.first(); child; child = child->next()) + child->yield(amount, true); + } + + Genode::Signal_rpc_member _resource_avail_dispatcher = + { _ep, *this, &Subsystem_manager::_handle_resource_avail }; + + void _handle_resource_avail(unsigned) + { + _try_response_to_resource_request(); + } + + Genode::Signal_rpc_member _yield_response_dispatcher = + { _ep, *this, &Subsystem_manager::_handle_yield_response }; + + void _handle_yield_response(unsigned) + { + _try_response_to_resource_request(); + } + + Ram _ram { ram_preservation_from_config(), + _yield_broadcast_dispatcher, + _resource_avail_dispatcher }; + + static Child::Binary_name _binary_name(Xml_node subsystem) + { + try { + return string_attribute(subsystem.sub_node("binary"), + "name", Child::Binary_name("")); + } catch (Xml_node::Nonexistent_sub_node) { + PERR("missing definition"); + throw Invalid_config(); + } + } + + struct Ram_config { Number_of_bytes quantum, limit; }; + + static Ram_config _ram_config(Xml_node subsystem) + { + Number_of_bytes quantum = 0, limit = 0; + try { + subsystem.for_each_sub_node("resource", [&] (Xml_node rsc) { + if (rsc.attribute("name").has_value("RAM")) { + + rsc.attribute("quantum").value(&quantum); + + if (rsc.has_attribute("limit")) + rsc.attribute("limit").value(&limit); + } + }); + } catch (...) { + PERR("invalid RAM resource declaration"); + throw Invalid_config(); + } + + return Ram_config { quantum, limit }; + } + + public: + + Subsystem_manager(Server::Entrypoint &ep, Cap_session &cap) + : + _ep(ep), _cap(cap) + { } + + /** + * Start subsystem + * + * \throw Invalid_config + */ + void start(Xml_node subsystem) + { + Child::Binary_name const binary_name = _binary_name(subsystem); + + Child::Label const label = string_attribute(subsystem, "name", + Child::Label("")); + + Ram_config const ram_config = _ram_config(subsystem); + + PINF("starting child '%s'", label.string()); + + try { + Child *child = new (env()->heap()) + Child(_ram, label, binary_name.string(), _cap, + ram_config.quantum, ram_config.limit, + _yield_broadcast_dispatcher); + + /* configure child */ + try { + Xml_node config_node = subsystem.sub_node("config"); + child->configure(config_node.addr(), config_node.size()); + } catch (...) { } + + _children.insert(child); + + child->start(); + + } catch (Rom_connection::Rom_connection_failed) { + PERR("binary \"%s\" is missing", binary_name.string()); + throw Invalid_config(); + } + } + + void kill(char const *label) + { + for (Child *c = _children.first(); c; c = c->next()) { + if (c->label() == Child::Label(label)) { + _children.remove(c); + destroy(env()->heap(), c); + return; + } + } + } +}; + +#endif /* _SUBSYSTEM_MANAGER_H_ */ diff --git a/repos/gems/src/app/launcher/target.mk b/repos/gems/src/app/launcher/target.mk new file mode 100644 index 0000000000..a1a5d82d98 --- /dev/null +++ b/repos/gems/src/app/launcher/target.mk @@ -0,0 +1,4 @@ +TARGET = launcher +SRC_CC = main.cc +LIBS = base server config +INC_DIR += $(PRG_DIR) diff --git a/repos/gems/src/app/launcher/types.h b/repos/gems/src/app/launcher/types.h new file mode 100644 index 0000000000..a22f7c4083 --- /dev/null +++ b/repos/gems/src/app/launcher/types.h @@ -0,0 +1,45 @@ +/* + * \brief Common types for launcher + * \author Norman Feske + * \date 2014-09-30 + */ + +/* + * 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 _TYPES_H_ +#define _TYPES_H_ + +/* Genode includes */ +#include +#include + +namespace Launcher { + using namespace Genode; + + typedef String<128> Label; + + static Nitpicker::Session::Label selector(Label label) + { + /* + * Append label separator to uniquely identify the subsystem. + * Otherwise, the selector may be ambiguous if the label of one + * subsystem starts with the label of another subsystem. + */ + char selector[Nitpicker::Session::Label::size()]; + snprintf(selector, sizeof(selector), "%s ->", label.string()); + return Nitpicker::Session::Label(selector); + } + + using Decorator::area_attribute; + using Decorator::point_attribute; + + typedef Nitpicker::Point Point; + typedef Nitpicker::Rect Rect; +} + +#endif /* _TYPES_H_ */