diff --git a/repos/gems/run/menu_view.run b/repos/gems/run/menu_view.run
new file mode 100644
index 0000000000..8e9dca05ba
--- /dev/null
+++ b/repos/gems/run/menu_view.run
@@ -0,0 +1,162 @@
+#
+# Build
+#
+if {![have_spec linux]} {
+ puts "Runs on Linux only"
+ exit 0
+}
+
+set build_components {
+ core init drivers/timer drivers/framebuffer/sdl
+ server/dynamic_rom server/nitpicker
+ app/pointer app/menu_view
+ app/scout
+}
+
+build $build_components
+
+create_boot_directory
+
+#
+# Generate config
+#
+
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+install_config $config
+
+#
+# Boot modules
+#
+
+# generic modules
+set boot_modules {
+ core init timer dynamic_rom fb_sdl nitpicker pointer menu_view
+ ld.lib.so libpng.lib.so libc.lib.so libm.lib.so zlib.lib.so
+ menu_view_styles.tar
+ scout
+}
+
+build_boot_image $boot_modules
+
+run_genode_until forever
diff --git a/repos/gems/src/app/menu_view/dither_painter.h b/repos/gems/src/app/menu_view/dither_painter.h
new file mode 100644
index 0000000000..655e7da2cf
--- /dev/null
+++ b/repos/gems/src/app/menu_view/dither_painter.h
@@ -0,0 +1,75 @@
+/*
+ * \brief Functor for converting pixel formats by applying dithering
+ * \author Norman Feske
+ * \date 2014-09-10
+ */
+
+/*
+ * 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 _DITHER_PAINTER_H_
+#define _DITHER_PAINTER_H_
+
+#include
+#include
+
+
+struct Dither_painter
+{
+ /*
+ * Surface and texture must have the same size
+ */
+ template
+ static inline void paint(Genode::Surface &surface,
+ Genode::Texture const &texture)
+ {
+ if (surface.size() != texture.size()) return;
+
+ Genode::Surface_base::Rect const clipped = surface.clip();
+
+ if (!clipped.valid()) return;
+
+ unsigned const offset = surface.size().w()*clipped.y1() + clipped.x1();
+
+ DST_PT *dst, *dst_line = surface.addr() + offset;
+ SRC_PT const *src_pixel, *src_pixel_line = texture.pixel() + offset;
+ unsigned char const *src_alpha, *src_alpha_line = texture.alpha() + offset;
+
+ unsigned const line_len = surface.size().w();
+
+ for (int y = clipped.y1(), h = clipped.h() ; h--; y++) {
+
+ src_pixel = src_pixel_line;
+ src_alpha = src_alpha_line;
+ dst = dst_line;
+
+ for (int x = clipped.x1(), w = clipped.w(); w--; x++) {
+
+ int const v = Genode::Dither_matrix::value(x, y) >> 4;
+
+ SRC_PT const pixel = *src_pixel++;
+ unsigned char const alpha = *src_alpha++;
+
+ int const r = pixel.r() - v;
+ int const g = pixel.g() - v;
+ int const b = pixel.b() - v;
+ int const a = alpha ? (int)alpha - v : 0;
+
+ using Genode::min;
+ using Genode::max;
+
+ *dst++ = DST_PT(max(0, r), max(0, g), max(0, b), max(0, a));
+ }
+
+ src_pixel_line += line_len;
+ src_alpha_line += line_len;
+ dst_line += line_len;
+ }
+ }
+};
+
+#endif /* _DITHER_PAINTER_H_ */
diff --git a/repos/gems/src/app/menu_view/main.cc b/repos/gems/src/app/menu_view/main.cc
new file mode 100644
index 0000000000..1823a2fa8e
--- /dev/null
+++ b/repos/gems/src/app/menu_view/main.cc
@@ -0,0 +1,442 @@
+/*
+ * \brief Menu view
+ * \author Norman Feske
+ * \date 2009-09-11
+ */
+
+/*
+ * 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.
+ */
+
+/* local includes */
+#include "widgets.h"
+
+/* Genode includes */
+#include
+#include
+
+
+struct Menu_view::Main
+{
+ Nitpicker::Connection nitpicker;
+
+ /*
+ * The back buffer (surface) is RGB888 with interleaved alpha values.
+ */
+ struct Buffer
+ {
+ Nitpicker::Connection &nitpicker;
+
+ Framebuffer::Mode const mode;
+
+ /**
+ * Return dataspace capability for virtual framebuffer
+ */
+ Dataspace_capability _ds_cap(Nitpicker::Connection &nitpicker)
+ {
+ /* setup virtual framebuffer mode */
+ nitpicker.buffer(mode, true);
+
+ if (mode.format() != Framebuffer::Mode::RGB565) {
+ PWRN("Color mode %d not supported\n", (int)mode.format());
+ return Dataspace_capability();
+ }
+
+ return nitpicker.framebuffer()->dataspace();
+ }
+
+ Attached_dataspace fb_ds { _ds_cap(nitpicker) };
+
+ size_t pixel_surface_num_bytes() const
+ {
+ return size().count()*sizeof(Pixel_rgb888);
+ }
+
+ size_t alpha_surface_num_bytes() const
+ {
+ return size().count();
+ }
+
+ Attached_ram_dataspace pixel_surface_ds { env()->ram_session(), pixel_surface_num_bytes() };
+ Attached_ram_dataspace alpha_surface_ds { env()->ram_session(), alpha_surface_num_bytes() };
+
+ /**
+ * Constructor
+ */
+ Buffer(Nitpicker::Connection &nitpicker, Area size)
+ :
+ nitpicker(nitpicker),
+ mode(size.w(), size.h(), nitpicker.mode().format())
+ { }
+
+ /**
+ * Return size of virtual framebuffer
+ */
+ Surface_base::Area size() const
+ {
+ return Surface_base::Area(mode.width(), mode.height());
+ }
+
+ /**
+ * Return back buffer as RGB888 painting surface
+ */
+ Surface pixel_surface()
+ {
+ return Surface(pixel_surface_ds.local_addr(), size());
+ }
+
+ Surface alpha_surface()
+ {
+ return Surface(alpha_surface_ds.local_addr(), size());
+ }
+
+ void reset_surface()
+ {
+ size_t const num_pixels = pixel_surface().size().count();
+ Genode::memset(alpha_surface().addr(), 0, num_pixels);
+ Genode::memset(pixel_surface().addr(), 0, num_pixels*sizeof(Pixel_rgb888));
+ }
+
+ template
+ void _convert_back_to_front(DST_PT *front_base,
+ Texture const &texture,
+ Rect const clip_rect)
+ {
+ Surface surface(front_base, size());
+
+ surface.clip(clip_rect);
+
+ Dither_painter::paint(surface, texture);
+ }
+
+ void _update_input_mask()
+ {
+ unsigned const num_pixels = size().count();
+
+ unsigned char * const alpha_base = fb_ds.local_addr()
+ + mode.bytes_per_pixel()*num_pixels;
+
+ unsigned char * const input_base = alpha_base + num_pixels;
+
+ unsigned char const *src = alpha_base;
+ unsigned char *dst = input_base;
+
+ /*
+ * Set input mask for all pixels where the alpha value is above a
+ * given threshold. The threshold is defines such that typical
+ * drop shadows are below the value.
+ */
+ unsigned char const threshold = 100;
+
+ for (unsigned i = 0; i < num_pixels; i++)
+ *dst++ = (*src++) > threshold;
+ }
+
+ void flush_surface()
+ {
+ /* represent back buffer as texture */
+ Texture
+ texture(pixel_surface_ds.local_addr(),
+ alpha_surface_ds.local_addr(),
+ size());
+
+ // XXX track dirty rectangles
+ Rect const clip_rect(Point(0, 0), size());
+
+ Pixel_rgb565 *pixel_base = fb_ds.local_addr();
+ Pixel_alpha8 *alpha_base = fb_ds.local_addr()
+ + mode.bytes_per_pixel()*size().count();
+
+ _convert_back_to_front(pixel_base, texture, clip_rect);
+ _convert_back_to_front(alpha_base, texture, clip_rect);
+
+ _update_input_mask();
+ }
+ };
+
+ Lazy_volatile_object buffer;
+
+ Nitpicker::Session::View_handle view_handle = nitpicker.create_view();
+
+ Point position;
+
+ Rect _view_geometry;
+
+ void _update_view()
+ {
+ if (_view_geometry.p1() == position
+ && _view_geometry.area() == buffer->size())
+ return;
+
+ /* display view behind all others */
+ typedef Nitpicker::Session::Command Command;
+
+ _view_geometry = Rect(position, buffer->size());
+ nitpicker.enqueue(view_handle, _view_geometry);
+ nitpicker.enqueue(view_handle);
+ nitpicker.execute();
+ }
+
+ Signal_receiver &sig_rec;
+
+ /**
+ * Function called on config change or mode change
+ */
+ void handle_dialog_update(unsigned);
+
+ Signal_dispatcher dialog_update_dispatcher = {
+ sig_rec, *this, &Main::handle_dialog_update};
+
+ Style_database styles;
+
+ Animator animator;
+
+ Widget_factory widget_factory { *env()->heap(), styles, animator };
+
+ Root_widget root_widget { widget_factory, Xml_node(""), Widget::Unique_id() };
+
+ Attached_rom_dataspace dialog_rom { "dialog" };
+
+ Attached_dataspace input_ds { nitpicker.input()->dataspace() };
+
+ Widget::Unique_id hovered;
+
+ void handle_config(unsigned);
+
+ Signal_dispatcher config_dispatcher = {
+ sig_rec, *this, &Main::handle_config};
+
+ void handle_input(unsigned);
+
+ Signal_dispatcher input_dispatcher = {
+ sig_rec, *this, &Main::handle_input};
+
+ /*
+ * Timer used for animating widgets
+ */
+ struct Frame_timer : Timer::Connection
+ {
+ enum { PERIOD = 10 };
+
+ unsigned curr_frame() const { return elapsed_ms() / PERIOD; }
+
+ void schedule() { trigger_once(Frame_timer::PERIOD*1000); }
+
+ } timer;
+
+ void handle_frame_timer(unsigned);
+
+ Signal_dispatcher frame_timer_dispatcher = {
+ sig_rec, *this, &Main::handle_frame_timer};
+
+ Genode::Reporter hover_reporter = { "hover" };
+
+ bool schedule_redraw = false;
+
+ /**
+ * Frame of last call of 'handle_frame_timer'
+ */
+ unsigned last_frame = 0;
+
+ /**
+ * Number of frames between two redraws
+ */
+ enum { REDRAW_PERIOD = 4 };
+
+ /**
+ * Counter used for triggering redraws. Incremented in each frame-timer
+ * period, wraps at 'REDRAW_PERIOD'. The redraw is performed when the
+ * counter wraps.
+ */
+ unsigned frame_cnt = 0;
+
+ Main(Signal_receiver &sig_rec) : sig_rec(sig_rec)
+ {
+ dialog_rom.sigh(dialog_update_dispatcher);
+ config()->sigh(config_dispatcher);
+
+ nitpicker.input()->sigh(input_dispatcher);
+
+ timer.sigh(frame_timer_dispatcher);
+
+ /* apply initial configuration */
+ handle_config(0);
+ }
+};
+
+
+void Menu_view::Main::handle_dialog_update(unsigned)
+{
+ try {
+ position = Decorator::point_attribute(config()->xml_node());
+ } catch (...) { }
+
+ dialog_rom.update();
+
+ try {
+ Xml_node dialog_xml(dialog_rom.local_addr());
+
+ root_widget.update(dialog_xml);
+ } catch (...) {
+ PERR("failed to construct widget tree");
+ }
+
+ schedule_redraw = true;
+
+ /*
+ * If we have not processed a period for at least one frame, perform the
+ * processing immediately. This way, we avoid latencies when the dialog
+ * model is updated sporadically.
+ */
+ if (timer.curr_frame() != last_frame)
+ handle_frame_timer(0);
+ else
+ timer.schedule();
+}
+
+
+void Menu_view::Main::handle_config(unsigned)
+{
+ config()->reload();
+
+ try {
+ hover_reporter.enabled(config()->xml_node().sub_node("report")
+ .attribute("hover")
+ .has_value("yes"));
+ } catch (...) {
+ hover_reporter.enabled(false);
+ }
+
+ handle_dialog_update(0);
+}
+
+
+void Menu_view::Main::handle_input(unsigned)
+{
+ Input::Event const *ev_buf = input_ds.local_addr();
+
+ unsigned const num_events = nitpicker.input()->flush();
+ for (unsigned i = 0; i < num_events; i++) {
+
+ Input::Event ev = ev_buf[i];
+
+ if (ev.is_absolute_motion()) {
+
+ Point const at = Point(ev.ax(), ev.ay()) - position;
+ Widget::Unique_id const new_hovered = root_widget.hovered(at);
+
+ if (hovered != new_hovered) {
+
+ if (hover_reporter.is_enabled()) {
+ Genode::Reporter::Xml_generator xml(hover_reporter, [&] () {
+ root_widget.gen_hover_model(xml, at);
+ });
+ }
+
+ hovered = new_hovered;
+ }
+ }
+
+ /*
+ * Reset hover model when losing the focus
+ */
+ if ((ev.type() == Input::Event::FOCUS && ev.code() == 0)
+ || (ev.type() == Input::Event::LEAVE)) {
+
+ hovered = Widget::Unique_id();
+
+ if (hover_reporter.is_enabled()) {
+ Genode::Reporter::Xml_generator xml(hover_reporter, [&] () { });
+ }
+ }
+ }
+}
+
+
+void Menu_view::Main::handle_frame_timer(unsigned)
+{
+ frame_cnt++;
+
+ unsigned const curr_frame = timer.curr_frame();
+
+ if (animator.active()) {
+
+ unsigned const passed_frames = curr_frame - last_frame;
+
+ if (passed_frames > 0) {
+
+ for (unsigned i = 0; i < passed_frames; i++)
+ animator.animate();
+
+ schedule_redraw = true;
+ }
+ }
+
+ last_frame = curr_frame;
+
+ if (schedule_redraw && frame_cnt >= REDRAW_PERIOD) {
+
+ frame_cnt = 0;
+
+ Area const old_size = buffer.is_constructed() ? buffer->size() : Area();
+ Area const size = root_widget.min_size();
+
+ if (!buffer.is_constructed() || size != old_size)
+ buffer.construct(nitpicker, size);
+ else
+ buffer->reset_surface();
+
+ root_widget.size(size);
+ root_widget.position(Point(0, 0));
+
+ Surface pixel_surface = buffer->pixel_surface();
+ Surface alpha_surface = buffer->alpha_surface();
+
+ // XXX restrict redraw to dirty regions
+ // don't perform a full dialog update
+ root_widget.draw(pixel_surface, alpha_surface, Point(0, 0));
+
+ buffer->flush_surface();
+ nitpicker.framebuffer()->refresh(0, 0, buffer->size().w(), buffer->size().h());
+ _update_view();
+
+ schedule_redraw = false;
+ }
+
+ /*
+ * Deactivate timer periods when idle, activate timer when an animation is
+ * in progress or a redraw is pending.
+ */
+ bool const redraw_pending = schedule_redraw && frame_cnt != 0;
+
+ if (animator.active() || redraw_pending)
+ timer.schedule();
+}
+
+
+/*
+ * Silence debug messages
+ */
+extern "C" void _sigprocmask() { }
+
+int main(int argc, char **argv)
+{
+ static Genode::Signal_receiver sig_rec;
+
+ static Menu_view::Main application(sig_rec);
+
+ /* process incoming signals */
+ for (;;) {
+ using namespace Genode;
+
+ Signal sig = sig_rec.wait_for_signal();
+ Signal_dispatcher_base *dispatcher =
+ dynamic_cast(sig.context());
+
+ if (dispatcher)
+ dispatcher->dispatch(sig.num());
+ }
+}
diff --git a/repos/gems/src/app/menu_view/style_database.h b/repos/gems/src/app/menu_view/style_database.h
new file mode 100644
index 0000000000..018f54e0a2
--- /dev/null
+++ b/repos/gems/src/app/menu_view/style_database.h
@@ -0,0 +1,177 @@
+/*
+ * \brief Menu view
+ * \author Norman Feske
+ * \date 2009-09-11
+ */
+
+/*
+ * 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 _STYLE_DATABASE_H_
+#define _STYLE_DATABASE_H_
+
+/* gems includes */
+#include
+#include
+
+/* local includes */
+#include "types.h"
+
+namespace Menu_view { struct Style_database; }
+
+
+class Menu_view::Style_database
+{
+ private:
+
+ enum { PATH_MAX_LEN = 200 };
+
+ struct Texture_entry : List::Element
+ {
+ String path;
+ File png_file;
+ Png_image png_image;
+ Texture &texture;
+
+ /**
+ * Constructor
+ *
+ * \throw Reading_failed
+ */
+ Texture_entry(char const *path, Allocator &alloc)
+ :
+ path(path),
+ png_file(path, alloc),
+ png_image(png_file.data()),
+ texture(*png_image.texture())
+ { }
+ };
+
+ struct Font_entry : List::Element
+ {
+ String path;
+ File tff_file;
+ Text_painter::Font font;
+
+ /**
+ * Constructor
+ *
+ * \throw Reading_failed
+ */
+ Font_entry(char const *path, Allocator &alloc)
+ :
+ path(path),
+ tff_file(path, alloc),
+ font(tff_file.data())
+ { }
+ };
+
+ /*
+ * The list is mutable because it is populated as a side effect of
+ * calling the const lookup function.
+ */
+ List mutable _textures;
+ List mutable _fonts;
+
+ template
+ T const *_lookup(List &list, char const *path) const
+ {
+ for (T const *e = list.first(); e; e = e->next())
+ if (Genode::strcmp(e->path.string(), path) == 0)
+ return e;
+
+ return 0;
+ }
+
+ typedef String<256> Path;
+
+ /*
+ * Assemble path name 'styles//