gems: turn launcher into a panel-like application

This commit is contained in:
Norman Feske 2015-09-29 17:17:08 +02:00 committed by Christian Helmuth
parent 519eb334e9
commit 755d2cce05
7 changed files with 896 additions and 263 deletions

View File

@ -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 {
<service name="Input"/>
<service name="Framebuffer"/>
</provides>
<config width="640" height="480"/>
</start>}
append_platform_drv_config
@ -73,20 +76,23 @@ append config {
<resource name="RAM" quantum="1M"/>
<provides><service name="Nitpicker"/></provides>
<config>
<report xray="yes" />
<domain name="pointer" layer="1" xray="no" origin="pointer" />
<domain name="panel" layer="2" xray="no" />
<domain name="" layer="3" />
<report hover="yes" focus="yes" />
<domain name="pointer" layer="1" content="client" label="no" origin="pointer" />
<domain name="panel" layer="2" content="client" label="no" hover="always" focus="transient" />
<domain name="" layer="3" color="#ff0000" hover="always" focus="click" />
<domain name="decorator" layer="3" content="client" label="no" hover="always" focus="transient" />
<policy label="pointer" domain="pointer"/>
<policy label="launcher -> menu" domain="panel"/>
<policy label="" domain=""/>
<policy label="pointer" domain="pointer"/>
<policy label="wm -> launcher -> menu" domain="panel"/>
<policy label="" domain=""/>
<policy label="wm -> decorator" domain="decorator"/>
<policy label="status_bar" domain="panel"/>
<global-key name="KEY_SCROLLLOCK" operation="xray" />
<global-key name="KEY_SYSRQ" operation="kill" />
<global-key name="KEY_PRINT" operation="kill" />
<global-key name="KEY_F11" operation="kill" />
<global-key name="KEY_F12" operation="xray" />
<global-key name="KEY_SCROLLLOCK" label="xray_trigger" />
<global-key name="KEY_F1" label="xray_trigger" />
<global-key name="KEY_F2" label="xray_trigger" />
<global-key name="KEY_LEFTMETA" label="wm -> launcher" />
<global-key name="KEY_F3" label="wm -> launcher" />
</config>
<route>
<service name="Report"> <child name="report_rom"/> </service>
@ -100,18 +106,129 @@ append config {
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="xray_trigger">
<resource name="RAM" quantum="1M"/>
<config>
<press name="KEY_F1" xray="on"/>
<release name="KEY_F1" xray="off"/>
<press name="KEY_F2" xray="toggle"/>
<hover domain="panel"/>
<hover domain="decorator"/>
</config>
<route>
<service name="ROM"> <child name="report_rom"/> </service>
<service name="Nitpicker"> <child name="nitpicker"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="status_bar">
<resource name="RAM" quantum="1M"/>
<route>
<service name="ROM"> <child name="report_rom"/> </service>
<service name="Nitpicker"> <child name="nitpicker"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="report_rom">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config>
<config verbose="no">
<rom>
<policy label="launcher -> xray" report="nitpicker -> xray"/>
<policy label="decorator_config -> xray" report="xray_trigger -> xray"/>
<policy label="layouter -> window_list" report="wm -> window_list"/>
<policy label="layouter -> focus_request" report="wm -> focus_request" />
<policy label="decorator -> window_layout" report="layouter -> window_layout"/>
<policy label="wm -> resize_request" report="layouter -> resize_request"/>
<policy label="decorator -> pointer" report="wm -> pointer"/>
<policy label="layouter -> hover" report="decorator -> hover"/>
<policy label="wm -> focus" report="layouter -> focus"/>
<policy label="status_bar -> focus" report="nitpicker -> focus"/>
<policy label="launcher -> focus" report="nitpicker -> focus"/>
<policy label="xray_trigger -> hover" report="nitpicker -> hover"/>
</rom>
</config>
</start>
<start name="wm">
<resource name="RAM" quantum="6M"/>
<provides>
<service name="Nitpicker"/>
</provides>
<config>
<policy label="decorator" role="decorator"/>
<policy label="layouter" role="layouter"/>
<policy label="launcher -> menu" role="direct"/>
<policy label="launcher -> testnit" role="direct"/>
</config>
<route>
<any-service>
<child name="nitpicker"/> <child name="report_rom"/> <parent/> <any-child/>
</any-service>
</route>
</start>
<start name="layouter">
<binary name="floating_window_layouter"/>
<resource name="RAM" quantum="4M"/>
<route>
<any-service>
<child name="wm"/> <child name="report_rom"/> <parent/> <any-child/>
</any-service>
</route>
</start>
<start name="decorator">
<binary name="decorator"/>
<resource name="RAM" quantum="8M"/>
<configfile name="decorator.config"/>
<route>
<service name="ROM" label="decorator.config">
<child name="decorator_config"/> </service>
<any-service>
<child name="wm"/> <child name="report_rom"/> <parent/> <any-child/>
</any-service>
</route>
</start>
<start name="decorator_config">
<binary name="rom_filter"/>
<resource name="RAM" quantum="4M"/>
<provides><service name="ROM"/></provides>
<config verbose="yes">
<input name="xray_enabled" rom="xray" node="xray">
<attribute name="enabled" />
</input>
<output node="config">
<inline>
<controls>
<title/> <minimizer/> <maximizer/> <closer/>
</controls>
</inline>
<if>
<has_value input="xray_enabled" value="yes" />
<then>
<inline>
<policy label="" color="#ff0000" gradient="75" />
</inline>
</then>
</if>
</output>
</config>
<route>
<service name="ROM"> <child name="report_rom"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="launcher">
<resource name="RAM" quantum="60M" />
<config visibility="xray">
<config focus_prefix="wm -> launcher -> ">
<subsystem name="scout" title="Scout">
<resource name="RAM" quantum="20M" />
<binary name="scout" />
@ -136,20 +253,28 @@ append config {
<resource name="RAM" quantum="2M" />
<binary name="testnit" />
</subsystem>
<subsystem name="testnit5">
<subsystem name="testnit5" title="Nitpicker Test5">
<resource name="RAM" quantum="2M" />
<binary name="testnit" />
</subsystem>
<subsystem name="testnit6">
<resource name="RAM" quantum="2M" />
<binary name="testnit" />
<subsystem name="backdrop" title="Backdrop">
<resource name="RAM" quantum="4M"/>
<binary name="backdrop" />
<config>
<libc>
<vfs>
</vfs>
</libc>
<fill color="#224433" />
</config>
</subsystem>
</config>
<route>
<service name="ROM"> <if-arg key="label" value="xray"/>
<service name="ROM"> <if-arg key="label" value="focus"/>
<child name="report_rom" />
</service>
<any-service> <parent/> <any-child/> </any-service>
<any-service> <child name="wm"/> <parent/> <any-child/> </any-service>
</route>
</start>
</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

View File

@ -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);

View File

@ -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); }
};

View File

@ -21,115 +21,154 @@
#include <nitpicker_session/connection.h>
/* local includes */
#include <menu_dialog.h>
#include <panel_dialog.h>
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 =
"<config> <rom>"
" <policy label=\"menu_dialog\" report=\"menu_dialog\"/>"
" <policy label=\"menu_hover\" report=\"menu_hover\"/>"
" <policy label=\"panel_dialog\" report=\"panel_dialog\"/>"
" <policy label=\"panel_hover\" report=\"panel_hover\"/>"
" <policy label=\"context_dialog\" report=\"context_dialog\"/>"
" <policy label=\"context_hover\" report=\"context_hover\"/>"
"</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<Input::Event>(); }
Genode::Signal_rpc_member<Main> _input_dispatcher =
{ _ep, *this, &Main::_handle_input };
void _handle_input(unsigned);
unsigned _key_cnt = 0;
Genode::Signal_rpc_member<Main> _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<Attached_rom_dataspace> xray_rom_ds;
enum Visibility { VISIBILITY_ALWAYS, VISIBILITY_XRAY };
Visibility visibility = VISIBILITY_ALWAYS;
void handle_config(unsigned);
Genode::Signal_rpc_member<Main> 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<Main> _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<char>());
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<char>());
/*
* 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");
}
}

View File

@ -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>::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<Menu_dialog> _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)
{

View File

@ -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:

View File

@ -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 <timer_session/connection.h>
/* local includes */
#include <fading_dialog.h>
#include <subsystem_manager.h>
#include <context_dialog.h>
#include <menu_dialog.h>
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>::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<Element> _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<Panel_dialog> _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_ */