diff --git a/repos/gems/run/launcher.run b/repos/gems/run/launcher.run
index c2c0cd52a5..b23112c162 100644
--- a/repos/gems/run/launcher.run
+++ b/repos/gems/run/launcher.run
@@ -7,7 +7,9 @@ set build_components {
server/dynamic_rom server/nitpicker server/report_rom
app/pointer app/menu_view
app/scout app/launchpad app/launcher test/nitpicker
- server/nit_fader
+ server/nit_fader server/rom_filter server/wm app/decorator
+ app/floating_window_layouter app/status_bar server/nit_fb
+ app/backdrop app/xray_trigger
}
source ${genode_dir}/repos/base/run/platform_drv.inc
@@ -47,6 +49,7 @@ append_if [have_spec sdl] config {
+
}
append_platform_drv_config
@@ -73,20 +76,23 @@ append config {
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
@@ -100,18 +106,129 @@ append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -136,20 +253,28 @@ append config {
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
}
@@ -166,7 +291,8 @@ set boot_modules {
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
+ nit_fader report_rom launcher rom_filter xray_trigger
+ decorator wm floating_window_layouter status_bar nit_fb backdrop
}
# platform-specific modules
diff --git a/repos/gems/src/app/launcher/context_dialog.h b/repos/gems/src/app/launcher/context_dialog.h
index d9c0f03ba7..a7e811ce9a 100644
--- a/repos/gems/src/app/launcher/context_dialog.h
+++ b/repos/gems/src/app/launcher/context_dialog.h
@@ -93,6 +93,8 @@ class Launcher::Context_dialog : Input_event_handler, Dialog_generator,
Fading_dialog _dialog;
+ bool _open = false;
+
unsigned _key_cnt = 0;
Label _clicked;
bool _click_in_progress = false;
@@ -166,8 +168,23 @@ class Launcher::Context_dialog : Input_event_handler, Dialog_generator,
*/
bool handle_input_event(Input::Event const &ev) override
{
- if (ev.type() == Input::Event::MOTION)
+ if (ev.type() == Input::Event::MOTION) {
+
+ /*
+ * Re-enable the visibility of the menu if we detect motion
+ * events over the menu. This way, it reappears in situations
+ * where the pointer temporarily leaves the view and returns.
+ */
+ if (_open)
+ visible(true);
+
return true;
+ }
+
+ if (ev.type() == Input::Event::LEAVE) {
+ visible(false);
+ return true;
+ }
if (ev.type() == Input::Event::PRESS) _key_cnt++;
if (ev.type() == Input::Event::RELEASE) _key_cnt--;
@@ -210,8 +227,12 @@ class Launcher::Context_dialog : Input_event_handler, Dialog_generator,
void visible(bool visible)
{
+ if (visible == _dialog.visible())
+ return;
+
/* reset touch state when (re-)opening the context dialog */
if (visible) {
+ _open = true;
_touch("");
_reset_hover();
dialog_changed();
@@ -221,6 +242,12 @@ class Launcher::Context_dialog : Input_event_handler, Dialog_generator,
_dialog.visible(visible);
}
+ void close()
+ {
+ _open = false;
+ visible(false);
+ }
+
void position(Fading_dialog::Position position)
{
_dialog.position(position);
diff --git a/repos/gems/src/app/launcher/fading_dialog.h b/repos/gems/src/app/launcher/fading_dialog.h
index 09e2e9489b..2853f324d5 100644
--- a/repos/gems/src/app/launcher/fading_dialog.h
+++ b/repos/gems/src/app/launcher/fading_dialog.h
@@ -192,6 +192,7 @@ class Launcher::Fading_dialog : private Input_event_handler
Nit_fader_slave _nit_fader_slave;
Menu_view_slave _menu_view_slave;
+ bool _visible = false;
public:
@@ -246,7 +247,13 @@ class Launcher::Fading_dialog : private Input_event_handler
});
}
- void visible(bool visible) { _nit_fader_slave.visible(visible); }
+ void visible(bool visible)
+ {
+ _nit_fader_slave.visible(visible);
+ _visible = visible;
+ }
+
+ bool visible() const { return _visible; }
void position(Position position) { _menu_view_slave.position(position); }
};
diff --git a/repos/gems/src/app/launcher/main.cc b/repos/gems/src/app/launcher/main.cc
index f0dc78f8b0..920c02db5f 100644
--- a/repos/gems/src/app/launcher/main.cc
+++ b/repos/gems/src/app/launcher/main.cc
@@ -21,115 +21,154 @@
#include
/* local includes */
-#include
+#include
namespace Launcher { struct Main; }
struct Launcher::Main
{
- Server::Entrypoint ep;
+ Server::Entrypoint _ep;
- Genode::Cap_connection cap;
+ Genode::Cap_connection _cap;
- char const *report_rom_config =
+ char const *_report_rom_config =
" "
" "
" "
+ " "
+ " "
" "
" "
" ";
- Report_rom_slave report_rom_slave = { cap, *env()->ram_session(), 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.
+ * subsystem's nitpicker sessions and to receive global keyboard
+ * shortcuts.
*/
- Nitpicker::Connection nitpicker;
+ Nitpicker::Connection _nitpicker;
+
+ Genode::Attached_dataspace _input_ds { _nitpicker.input()->dataspace() };
+
+ Input::Event const *_ev_buf() { return _input_ds.local_addr(); }
+
+ Genode::Signal_rpc_member _input_dispatcher =
+ { _ep, *this, &Main::_handle_input };
+
+ void _handle_input(unsigned);
+
+ unsigned _key_cnt = 0;
Genode::Signal_rpc_member _exited_child_dispatcher =
- { ep, *this, &Main::_handle_exited_child };
+ { _ep, *this, &Main::_handle_exited_child };
- Subsystem_manager subsystem_manager { ep, cap, _exited_child_dispatcher };
+ Subsystem_manager _subsystem_manager { _ep, _cap, _exited_child_dispatcher };
- Menu_dialog menu_dialog { ep, cap, *env()->ram_session(), report_rom_slave,
- subsystem_manager, nitpicker };
+ Panel_dialog _panel_dialog { _ep, _cap, *env()->ram_session(), *env()->heap(),
+ _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);
+ void _handle_config(unsigned);
void _handle_exited_child(unsigned)
{
- auto kill_child_fn = [&] (Child_base::Label label) { menu_dialog.kill(label); };
+ auto kill_child_fn = [&] (Child_base::Label label) { _panel_dialog.kill(label); };
- subsystem_manager.for_each_exited_child(kill_child_fn);
+ _subsystem_manager.for_each_exited_child(kill_child_fn);
}
+ Label _focus_prefix;
+
+ Genode::Attached_rom_dataspace _focus_rom { "focus" };
+
+ void _handle_focus_update(unsigned);
+
+ Genode::Signal_rpc_member _focus_update_dispatcher =
+ { _ep, *this, &Main::_handle_focus_update };
+
/**
* Constructor
*/
- Main(Server::Entrypoint &ep) : ep(ep)
+ Main(Server::Entrypoint &ep) : _ep(ep)
{
- handle_config(0);
+ _nitpicker.input()->sigh(_input_dispatcher);
+ _focus_rom.sigh(_focus_update_dispatcher);
- if (visibility == VISIBILITY_ALWAYS)
- menu_dialog.visible(true);
+ _handle_config(0);
+
+ _panel_dialog.visible(true);
}
};
-void Launcher::Main::handle_config(unsigned)
+void Launcher::Main::_handle_config(unsigned)
{
config()->reload();
- /* set default visibility */
- visibility = VISIBILITY_ALWAYS;
+ _focus_prefix = config()->xml_node().attribute_value("focus_prefix", Label());
- /* 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();
+ _panel_dialog.update(config()->xml_node());
}
-void Launcher::Main::handle_xray_update(unsigned)
+void Launcher::Main::_handle_input(unsigned)
{
- xray_rom_ds->update();
- if (!xray_rom_ds->is_valid()) {
- PWRN("could not access xray info");
- menu_dialog.visible(false);
- return;
+ unsigned const num_ev = _nitpicker.input()->flush();
+
+ for (unsigned i = 0; i < num_ev; i++) {
+
+ Input::Event const &e = _ev_buf()[i];
+
+ if (e.type() == Input::Event::PRESS) _key_cnt++;
+ if (e.type() == Input::Event::RELEASE) _key_cnt--;
+
+ /*
+ * The _key_cnt can become 2 only when the global key (as configured
+ * in the nitpicker config) is pressed together with another key.
+ * Hence, the following condition triggers on key combinations with
+ * the global modifier key, whatever the global modifier key is.
+ */
+ if (e.type() == Input::Event::PRESS && _key_cnt == 2) {
+
+ if (e.keycode() == Input::KEY_TAB)
+ _panel_dialog.focus_next();
+ }
}
+}
- Xml_node xray(xray_rom_ds->local_addr());
- bool const visible = xray.has_attribute("enabled")
- && xray.attribute("enabled").has_value("yes");
+void Launcher::Main::_handle_focus_update(unsigned)
+{
+ try {
+ _focus_rom.update();
- menu_dialog.visible(visible);
+ Xml_node focus_node(_focus_rom.local_addr());
+
+ /*
+ * Propagate focus information to panel such that the focused
+ * subsystem gets highlighted.
+ */
+ Label label = focus_node.attribute_value("label", Label());
+
+ size_t const prefix_len = Genode::strlen(_focus_prefix.string());
+ if (!Genode::strcmp(_focus_prefix.string(), label.string(), prefix_len)) {
+ label = Label(label.string() + prefix_len);
+
+ } else {
+
+ /*
+ * A foreign nitpicker client not started by ourself has the focus.
+ */
+ label = Label();
+ }
+
+ _panel_dialog.focus_changed(label);
+
+ } catch (...) {
+ PWRN("no focus model available");
+ }
}
diff --git a/repos/gems/src/app/launcher/menu_dialog.h b/repos/gems/src/app/launcher/menu_dialog.h
index c8e8ef74f8..7ee1dfac08 100644
--- a/repos/gems/src/app/launcher/menu_dialog.h
+++ b/repos/gems/src/app/launcher/menu_dialog.h
@@ -26,11 +26,21 @@ namespace Launcher { struct Menu_dialog; }
class Launcher::Menu_dialog : Input_event_handler, Dialog_generator,
- Hover_handler, Dialog_model,
- Context_dialog::Response_handler
+ Hover_handler, Dialog_model
{
+ public:
+
+ struct Response_handler
+ {
+ virtual void handle_selection(Label const &) = 0;
+ virtual void handle_menu_leave() = 0;
+ virtual void handle_menu_motion() = 0;
+ };
+
private:
+ Response_handler &_response_handler;
+
typedef String<128> Title;
struct Element : List::Element
@@ -58,7 +68,7 @@ class Launcher::Menu_dialog : Input_event_handler, Dialog_generator,
xml.node("button", [&] () {
xml.attribute("name", e->label.string());
- if ((e->hovered && !_click_in_progress)
+ if ((e->hovered)
|| (e->hovered && e->touched))
xml.attribute("hovered", "yes");
@@ -72,43 +82,15 @@ class Launcher::Menu_dialog : Input_event_handler, Dialog_generator,
}
}
- class Lookup_failed { };
+ Fading_dialog::Position _position { 0 - 4, 28 - 4 };
- 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;
+ Fading_dialog _dialog;
Rect _hovered_rect;
+ bool _open = false;
+
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
{
@@ -119,102 +101,17 @@ class Launcher::Menu_dialog : Input_event_handler, Dialog_generator,
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)
+ Response_handler &response_handler)
:
- _subsystem_manager(subsystem_manager),
- _nitpicker(nitpicker),
+ _response_handler(response_handler),
_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)
+ _position)
{
- _timer.sigh(_timer_dispatcher);
}
/**
@@ -287,8 +184,25 @@ class Launcher::Menu_dialog : Input_event_handler, Dialog_generator,
*/
bool handle_input_event(Input::Event const &ev) override
{
- if (ev.type() == Input::Event::MOTION)
+ if (ev.type() == Input::Event::LEAVE) {
+ _response_handler.handle_menu_leave();
+ return false;
+ }
+
+ if (ev.type() == Input::Event::MOTION) {
+
+ _response_handler.handle_menu_motion();
+
+ /*
+ * Re-enable the visibility of the menu if we detect motion
+ * events over the menu. This way, it reappears in situations
+ * where the pointer temporarily leaves the view and returns.
+ */
+ if (_open)
+ visible(true);
+
return true;
+ }
if (ev.type() == Input::Event::PRESS) _key_cnt++;
if (ev.type() == Input::Event::RELEASE) _key_cnt--;
@@ -297,71 +211,39 @@ class Launcher::Menu_dialog : Input_event_handler, Dialog_generator,
&& 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);
- }
-
- _touch("");
- _clicked = Label("");
- _click_in_progress = false;
+ _response_handler.handle_selection(_hovered());
}
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)
{
+ if (visible == _dialog.visible())
+ return;
+
_dialog.visible(visible);
- if (!visible)
- _context_dialog.visible(false);
+ if (visible)
+ _open = true;
}
- void kill(Child_base::Label const &label) { _kill(label); }
+ void close()
+ {
+ _open = false;
+ visible(false);
+ }
- void update()
+ void running(Label const &label, bool running)
+ {
+ for (Element *e = _elements.first(); e; e = e->next())
+ if (e->label == label)
+ e->running = running;
+
+ _dialog.update();
+ }
+
+ void update(Xml_node subsystems)
{
if (_elements.first()) {
PERR("subsequent updates are not supported");
@@ -370,8 +252,6 @@ class Launcher::Menu_dialog : Input_event_handler, Dialog_generator,
Element *last = nullptr;
- Xml_node subsystems = config()->xml_node();
-
subsystems.for_each_sub_node("subsystem",
[&] (Xml_node subsystem)
{
diff --git a/repos/gems/src/app/launcher/menu_view_slave.h b/repos/gems/src/app/launcher/menu_view_slave.h
index 06b92db91d..cae8ba9b67 100644
--- a/repos/gems/src/app/launcher/menu_view_slave.h
+++ b/repos/gems/src/app/launcher/menu_view_slave.h
@@ -131,7 +131,7 @@ class Launcher::Menu_view_slave
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::size_t const _quota = 6*1024*1024;
Genode::Slave _slave;
public:
diff --git a/repos/gems/src/app/launcher/panel_dialog.h b/repos/gems/src/app/launcher/panel_dialog.h
new file mode 100644
index 0000000000..777fa73fbd
--- /dev/null
+++ b/repos/gems/src/app/launcher/panel_dialog.h
@@ -0,0 +1,554 @@
+/*
+ * \brief Panel dialog
+ * \author Norman Feske
+ * \date 2015-10-07
+ */
+
+/*
+ * Copyright (C) 2015 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 _PANEL_DIALOG_H_
+#define _PANEL_DIALOG_H_
+
+/* Genode includes */
+#include
+
+/* local includes */
+#include
+#include
+#include
+#include
+
+namespace Launcher { struct Panel_dialog; }
+
+
+class Launcher::Panel_dialog : Input_event_handler, Dialog_generator,
+ Hover_handler, Dialog_model,
+ Context_dialog::Response_handler,
+ Menu_dialog::Response_handler
+{
+ private:
+
+ typedef String<128> Title;
+
+ struct Element : List::Element
+ {
+ Label const label;
+ Title const title;
+
+ bool hovered = false;
+ bool touched = false;
+ bool selected = false;
+
+ Element(Label label, Title title) : label(label), title(title)
+ { }
+ };
+
+ Genode::Allocator &_alloc;
+
+ List _elements;
+
+ Label _focus;
+
+ static char const *_menu_button_label() { return "_menu"; }
+
+ Element _menu_button { _menu_button_label(), "Menu" };
+
+ bool _is_focused(Element const &e)
+ {
+ size_t const label_len = strlen(e.label.string());
+
+ if (strcmp(e.label.string(), _focus.string(), label_len))
+ return false;
+
+ /*
+ * Even when the strcmp suceeded, the element's label might
+ * not match the focus. E.g., if two subsystems "scout" and
+ * "scoutx" are present the focus of "scoutx" would match both
+ * subsystem labels because the strcmp is limited to the length
+ * of the subsystem label. Hence, we need to make sure that
+ * the focus matched at a separator boundary.
+ */
+ char const *char_after_label = _focus.string() + label_len;
+
+ if (*char_after_label == 0 || !strcmp(" -> ", char_after_label, 4))
+ return true;
+
+ return false;
+ }
+
+ void _generate_dialog_element(Xml_generator &xml, Element const &e)
+ {
+ xml.node("button", [&] () {
+ xml.attribute("name", e.label.string());
+
+ if (&e != &_menu_button)
+ xml.attribute("style", "subdued");
+
+ if ((e.hovered && !_click_in_progress)
+ || (e.hovered && e.touched))
+ xml.attribute("hovered", "yes");
+
+ if (e.selected || e.touched || _is_focused(e))
+ 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 { 0, 0 };
+
+ Timer::Connection _timer;
+ Subsystem_manager &_subsystem_manager;
+ Nitpicker::Session &_nitpicker;
+ Fading_dialog _dialog;
+
+ Rect _hovered_rect;
+
+ unsigned _key_cnt = 0;
+ Element *_clicked = nullptr;
+ bool _click_in_progress = false;
+
+ Signal_rpc_member _timer_dispatcher;
+
+ Label _context_subsystem;
+ Context_dialog _context_dialog;
+
+ Menu_dialog _menu_dialog;
+
+ Element *_hovered()
+ {
+ for (Element *e = _elements.first(); e; e = e->next())
+ if (e->hovered)
+ return e;
+
+ return nullptr;
+ }
+
+ /**
+ * 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 {
+ Xml_node subsystem = _subsystem(config()->xml_node(),
+ label.string());
+ _subsystem_manager.start(subsystem);
+
+ Title const title = subsystem.attribute_value("title", Title());
+
+ Element *e = new (_alloc) Element(label, title);
+
+ /* find last element of the list */
+ Element *at = _elements.first();
+ for (; at && at->next(); at = at->next());
+
+ _elements.insert(e, at);
+
+ 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)
+ {
+ Element &e = _lookup(label);
+
+ _subsystem_manager.kill(label.string());
+
+ _elements.remove(&e);
+
+ if (_clicked == &e)
+ _clicked = nullptr;
+
+ Genode::destroy(_alloc, &e);
+
+ dialog_changed();
+ _dialog.update();
+
+ _context_dialog.close();
+
+ _menu_dialog.running(label, false);
+ }
+
+ void _hide(Label const &label)
+ {
+ _nitpicker.session_control(selector(label.string()),
+ Nitpicker::Session::SESSION_CONTROL_HIDE);
+
+ _context_dialog.close();
+ }
+
+ void _open_context_dialog(Label const &label)
+ {
+ /* reset touch state in each element */
+ for (Element *e = _elements.first(); e; e = e->next())
+ e->touched = false;
+
+ Fading_dialog::Position position(_hovered_rect.p1().x(),
+ _hovered_rect.p2().y());
+
+ _context_subsystem = label;
+ _context_dialog.position(_position + position);
+ _context_dialog.visible(true);
+ }
+
+ void _handle_timer(unsigned)
+ {
+ if (_click_in_progress && _clicked && _hovered() == _clicked) {
+ _open_context_dialog(_clicked->label);
+ }
+ _click_in_progress = false;
+ }
+
+ void _to_front(Label const &label)
+ {
+ _nitpicker.session_control(selector(label.string()),
+ Nitpicker::Session::SESSION_CONTROL_TO_FRONT);
+ _nitpicker.session_control(selector(label.string()),
+ Nitpicker::Session::SESSION_CONTROL_SHOW);
+ }
+
+ public:
+
+ Panel_dialog(Server::Entrypoint &ep, Cap_session &cap, Ram_session &ram,
+ Genode::Allocator &alloc,
+ Report_rom_slave &report_rom_slave,
+ Subsystem_manager &subsystem_manager,
+ Nitpicker::Session &nitpicker)
+ :
+ _alloc(alloc),
+ _subsystem_manager(subsystem_manager),
+ _nitpicker(nitpicker),
+ _dialog(ep, cap, ram, report_rom_slave, "panel_dialog", "panel_hover",
+ *this, *this, *this, *this,
+ _position),
+ _timer_dispatcher(ep, *this, &Panel_dialog::_handle_timer),
+ _context_dialog(ep, cap, ram, report_rom_slave, *this),
+ _menu_dialog(ep, cap, ram, report_rom_slave, *this)
+ {
+ _elements.insert(&_menu_button);
+ _timer.sigh(_timer_dispatcher);
+ }
+
+ /**
+ * Dialog_generator interface
+ */
+ void generate_dialog(Xml_generator &xml) override
+ {
+ xml.node("hbox", [&] () {
+ for (Element const *e = _elements.first(); e; e = e->next())
+ _generate_dialog_element(xml, *e);
+ });
+ }
+
+ 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
+ {
+ Element *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("hbox")
+ .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) { }
+
+ Element *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::LEAVE) {
+
+ /*
+ * Let menu dialog disappear when the panel is unhovered. One
+ * would expect that the user had no chance to select an item
+ * from the menu because when entering the menu, we will no
+ * longer hover the panel. However, the menu disappears slowly.
+ * If the pointer moves over to the menu in a reasonable time,
+ * the visiblity of the menu is re-enabled.
+ */
+ _menu_dialog.visible(false);
+ _context_dialog.visible(false);
+ _menu_button.selected = false;
+ _dialog.update();
+ return true;
+ }
+
+ 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);
+
+ Element *hovered = _hovered();
+
+ _click_in_progress = true;
+ _clicked = hovered;
+
+ if (!hovered)
+ return false;
+
+ hovered->touched = true;
+
+ if (hovered == &_menu_button) {
+
+ /* menu button presses */
+ if (_menu_button.selected)
+ _menu_dialog.close();
+ else
+ _menu_dialog.visible(true);
+
+ _menu_button.selected = !_menu_button.selected;
+ _dialog.update();
+ return false;
+ }
+
+ _menu_dialog.close();
+
+ _to_front(hovered->label);
+
+ /*
+ * Open the context dialog after the user keeps pressing the
+ * button for a while.
+ */
+ enum { CONTEXT_DELAY = 500 };
+ _timer.trigger_once(CONTEXT_DELAY*1000);
+ }
+
+ /*
+ * Open context dialog on right click
+ */
+ if (ev.type() == Input::Event::PRESS
+ && ev.keycode() == Input::BTN_RIGHT
+ && _key_cnt == 1) {
+
+ Element *hovered = _hovered();
+
+ if (hovered && hovered != &_menu_button)
+ _open_context_dialog(hovered->label);
+ }
+
+ if (ev.type() == Input::Event::RELEASE
+ && _click_in_progress && _key_cnt == 0) {
+
+ Element *hovered = _hovered();
+
+ if (hovered)
+ hovered->touched = false;
+
+ _clicked = nullptr;
+ _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);
+ }
+
+ /**
+ * Menu_dialog::Response_handler interface
+ */
+ void handle_menu_motion()
+ {
+ _menu_button.selected = true;
+ _dialog.update();
+ }
+
+ /**
+ * Menu_dialog::Response_handler interface
+ */
+ void handle_menu_leave()
+ {
+ /* XXX eventually revert the state of the menu button */
+ _menu_button.selected = false;
+
+ _dialog.update();
+
+ _menu_dialog.visible(false);
+ }
+
+ /**
+ * Menu_dialog::Response_handler interface
+ */
+ void handle_selection(Label const &label) override
+ {
+ /*
+ * If subsystem of the specified label is already running, ignore
+ * the click.
+ */
+ bool already_running = false;
+ for (Element *e = _elements.first(); e; e = e->next())
+ if (e->label == label)
+ already_running = true;
+
+ if (already_running) {
+ _to_front(label);
+
+ } else {
+
+ _start(label);
+
+ _dialog.update();
+
+ /* propagate running state of subsystem to menu dialog */
+ _menu_dialog.running(label, true);
+ }
+
+ /* let menu disappear */
+ _menu_dialog.close();
+ }
+
+ void visible(bool visible)
+ {
+ _dialog.visible(visible);
+
+ if (!visible)
+ _context_dialog.visible(false);
+ }
+
+ void kill(Child_base::Label const &label)
+ {
+ _kill(label);
+ }
+
+ void update(Xml_node config)
+ {
+ /* populate menu dialog with one item per subsystem */
+ _menu_dialog.update(config);
+
+ /* evaluate configuration */
+
+ _dialog.update();
+ }
+
+ void focus_changed(Label const &label)
+ {
+ _focus = label;
+
+ _dialog.update();
+ }
+
+ void focus_next()
+ {
+ /* find focused element */
+ Element *e = _elements.first();
+
+ for (; e && !_is_focused(*e); e = e->next());
+
+ /* none of our subsystems is focused, start with the first one */
+ if (!e)
+ e = _elements.first();
+
+ /*
+ * Determine next session in the list, if we reach the end, start
+ * at the beginning (the element right after the menu button.
+ */
+ Element *new_focused = e->next() ? e->next() : _menu_button.next();
+
+ if (new_focused)
+ _to_front(new_focused->label);
+ }
+};
+
+#endif /* _PANEL_DIALOG_H_ */