genode/repos/demo/src/server/nitlog/main.cc

514 lines
12 KiB
C++

/*
* \brief Nitpicker-based logging service
* \author Norman Feske
* \date 2006-09-18
*/
/*
* Copyright (C) 2006-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#include <util/arg_string.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/rpc_server.h>
#include <base/session_label.h>
#include <root/component.h>
#include <log_session/log_session.h>
#include <nitpicker_session/connection.h>
#include <timer_session/connection.h>
#include <input/event.h>
#include <os/pixel_rgb565.h>
/*
* Nitpicker's graphics backend
*/
#include <nitpicker_gfx/box_painter.h>
#include <nitpicker_gfx/tff_font.h>
enum { LOG_W = 80 }; /* number of visible characters per line */
enum { LOG_H = 25 }; /* number of lines of log window */
typedef Text_painter::Font Font;
typedef Genode::Surface_base::Point Point;
typedef Genode::Surface_base::Area Area;
typedef Genode::Surface_base::Rect Rect;
typedef Genode::Color Color;
/*
* Builtin font
*/
extern char _binary_mono_tff_start[];
namespace Nitlog {
class Session_component;
class Root;
struct Main;
using namespace Genode;
}
/**
* Pixel-type-independent interface to graphics backend
*/
struct Canvas_base : Genode::Interface
{
virtual void draw_string(Point, Font const &, Color, char const *) = 0;
virtual void draw_box(Rect, Color) = 0;
};
/**
* Pixel-type-specific graphics backend
*/
template <typename PT>
class Canvas : public Canvas_base
{
private:
Genode::Surface<PT> _surface;
public:
Canvas(PT *base, Area size) : _surface(base, size) { }
void clip(Rect rect) { _surface.clip(rect); }
void draw_string(Point p, Font const &font, Color color,
char const *sstr) override
{
Text_painter::paint(_surface, Text_painter::Position(p.x(), p.y()),
font, color, sstr);
}
void draw_box(Rect rect, Color color) override
{
Box_painter::paint(_surface, rect, color);
}
};
class Log_entry
{
private:
typedef Genode::Color Color;
char _label[64];
char _text[LOG_W];
char _attr[LOG_W];
Color _color { };
int _label_len = 0;
int _text_len = 0;
int _id = 0;
public:
/**
* Constructors
*
* The default constructor is used to build an array of log entries.
*/
Log_entry() { }
Log_entry(Genode::Color color, const char *label, const char *log_text, const char *log_attr, int id):
_color(color), _id(id)
{
Genode::copy_cstring(_label, label, sizeof(_label));
Genode::copy_cstring(_text, log_text, sizeof(_text));
_label_len = Genode::strlen(_label);
_text_len = Genode::strlen(_text);
/* replace line feed at the end of the text with a blank */
if (_text_len > 0 && _text[_text_len - 1] == '\n')
_text[_text_len - 1] = ' ';
Genode::memcpy(_attr, log_attr, _text_len);
}
/**
* Draw entry
*
* An entry consists of a label and text. The argument 'new_section'
* marks a transition of output from one session to another. This
* information is used to separate sessions visually.
*/
void draw(Canvas_base &canvas, Font const &font, int y, int new_section = false)
{
Color label_fgcol = Color(Genode::min(255, _color.r + 200),
Genode::min(255, _color.g + 200),
Genode::min(255, _color.b + 200));
Color label_bgcol = Color(_color.r, _color.g, _color.b);
Color text_fgcol = Color(180, 180, 180);
Color text_bgcol = Color(_color.r / 2, _color.g / 2, _color.b / 2);
/* calculate label dimensions */
int label_w = font.string_width(_label).decimal();
int label_h = font.bounding_box().h();
if (new_section) {
canvas.draw_box(Rect(Point(1, y), Area(label_w + 2, label_h - 1)), label_bgcol);
canvas.draw_string(Point(1, y - 1), font, label_fgcol, _label);
canvas.draw_box(Rect(Point(1, y + label_h - 1), Area(label_w + 2, 1)), Color(0, 0, 0));
canvas.draw_box(Rect(Point(label_w + 2, y), Area(1, label_h - 1)), _color);
canvas.draw_box(Rect(Point(label_w + 3, y), Area(1, label_h - 1)), Color(0, 0, 0));
canvas.draw_box(Rect(Point(label_w + 4, y), Area(1000, label_h)), text_bgcol);
canvas.draw_box(Rect(Point(label_w + 4, y), Area(1000, 1)), Color(0, 0, 0));
} else
canvas.draw_box(Rect(Point(1, y), Area(1000, label_h)), text_bgcol);
/* draw log text */
canvas.draw_string(Point(label_w + 6, y), font, text_fgcol, _text);
}
/**
* Accessors
*/
int label_len() { return _label_len; }
int id() { return _id; }
};
class Log_window
{
private:
Canvas_base &_canvas;
Font const &_font;
Log_entry _entries[LOG_H]; /* log entries */
int _dst_entry = 0; /* destination entry for next write */
int _view_pos = 0; /* current view port on the entry array */
bool _scroll = false; /* scroll mode (when text hits bottom) */
char _attr[LOG_W]; /* character attribute buffer */
bool _dirty = true; /* schedules the log window for a redraw */
Genode::Lock _dirty_lock { };
public:
/**
* Constructor
*/
Log_window(Canvas_base &canvas, Font const &font)
: _canvas(canvas), _font(font) { }
/**
* Write log entry
*
* \param color base color for highlighting the session.
* \param sid unique ID of the log session. This ID is used to
* determine section transitions in the log output.
*/
void write(Genode::Color color, const char *label,
const char *log_text, int sid)
{
_entries[_dst_entry] = Log_entry(color, label, log_text, _attr, sid);
if (_scroll)
_view_pos++;
/* cycle through log entries */
_dst_entry = (_dst_entry + 1) % LOG_H;
/* start scrolling when the dst entry wraps for the first time */
if (_dst_entry == 0)
_scroll = true;
/* schedule log window for redraw */
Genode::Lock::Guard lock_guard(_dirty_lock);
_dirty |= 1;
}
/**
* Draw log window
*
* \retval true drawing operations had been performed
*/
bool draw()
{
{
Genode::Lock::Guard lock_guard(_dirty_lock);
if (!_dirty) return false;
_dirty = false;
}
int line_h = _font.bounding_box().h();
int curr_session_id = -1;
for (int i = 0, y = 0; i < LOG_H; i++, y += line_h) {
Log_entry *le = &_entries[(i + _view_pos) % LOG_H];
le->draw(_canvas, _font, y, curr_session_id != le->id());
curr_session_id = le->id();
}
return true;
}
};
class Nitlog::Session_component : public Rpc_object<Log_session>
{
private:
Log_window &_log_window;
Session_label const _label;
int const _id;
static int _bit(int v, int bit_num) { return (v >> bit_num) & 1; }
/**
* Compute session color
*/
static Color _session_color(int id)
{
int const scale = 32;
int const offset = 64;
int r = (_bit(id, 3) + 2*_bit(id, 0))*scale + offset;
int g = (_bit(id, 4) + 2*_bit(id, 1))*scale + offset;
int b = (_bit(id, 5) + 2*_bit(id, 2))*scale + offset;
return Color(r, g, b);
}
Color const _color = _session_color(_id);
public:
/**
* Constructor
*/
Session_component(Session_label const &label,
Log_window &log_window, int &cnt)
:
_log_window(log_window), _label(label), _id(cnt++)
{ }
/***************************
** Log session interface **
***************************/
void write(String const &log_text) override
{
if (!log_text.valid_string()) {
error("corrupted string");
return;
}
_log_window.write(_color, _label.string(), log_text.string(), _id);
}
};
class Nitlog::Root : public Root_component<Session_component>
{
private:
Log_window &_log_window;
/* session counter, used as a key to generate session colors */
int _session_cnt = 0;
protected:
Session_component *_create_session(const char *args) override
{
log("create log session args: ", args);
return new (md_alloc())
Session_component(label_from_args(args),
_log_window, _session_cnt);
}
public:
/**
* Constructor
*/
Root(Entrypoint &ep, Allocator &md_alloc, Log_window &log_window)
:
Root_component<Session_component>(ep, md_alloc),
_log_window(log_window)
{ }
};
class Log_view
{
private:
Nitpicker::Session_client &_nitpicker;
Nitpicker::Point _pos;
Nitpicker::Area _size;
Nitpicker::Session::View_handle _handle;
typedef Nitpicker::Session::Command Command;
typedef Nitpicker::Session::View_handle View_handle;
public:
Log_view(Nitpicker::Session_client &nitpicker, Nitpicker::Rect geometry)
:
_nitpicker(nitpicker),
_pos(geometry.p1()),
_size(geometry.area()),
_handle(nitpicker.create_view())
{
move(_pos);
top();
}
void top()
{
_nitpicker.enqueue<Command::To_front>(_handle, View_handle());
_nitpicker.execute();
}
void move(Nitpicker::Point pos)
{
_pos = pos;
Nitpicker::Rect rect(_pos, _size);
_nitpicker.enqueue<Command::Geometry>(_handle, rect);
_nitpicker.execute();
}
Nitpicker::Point pos() const { return _pos; }
};
struct Nitlog::Main
{
Env &_env;
Tff_font::Static_glyph_buffer<4096> _glyph_buffer { };
Tff_font _font { _binary_mono_tff_start, _glyph_buffer };
/* calculate size of log view in pixels */
unsigned const _win_w = _font.bounding_box().w() * LOG_W + 2;
unsigned const _win_h = _font.bounding_box().h() * LOG_H + 2;
/* init sessions to the required external services */
Nitpicker::Connection _nitpicker { _env };
Timer::Connection _timer { _env };
void _init_nitpicker_buffer()
{
_nitpicker.buffer(Framebuffer::Mode(_win_w, _win_h,
Framebuffer::Mode::RGB565), false);
}
bool const _nitpicker_buffer_initialized = (_init_nitpicker_buffer(), true);
Sliced_heap _sliced_heap { _env.ram(), _env.rm() };
/* create log window */
Attached_dataspace _fb_ds { _env.rm(), _nitpicker.framebuffer()->dataspace() };
Canvas<Pixel_rgb565> _canvas { _fb_ds.local_addr<Pixel_rgb565>(),
::Area(_win_w, _win_h) };
Log_window _log_window { _canvas, _font };
void _init_canvas()
{
/*
* We clip a border of one pixel off the canvas. This way, the
* border remains unaffected by the drawing operations and
* acts as an outline for the log window.
*/
_canvas.clip(::Rect(::Point(1, 1), ::Area(_win_w - 2, _win_h - 2)));
}
bool const _canvas_initialized = (_init_canvas(), true);
/* create view for log window */
Nitpicker::Rect const _view_geometry { Nitpicker::Point(20, 20),
Nitpicker::Area(_win_w, _win_h) };
Log_view _view { _nitpicker, _view_geometry };
/* create root interface for service */
Root _root { _env.ep(), _sliced_heap, _log_window };
Attached_dataspace _ev_ds { _env.rm(), _nitpicker.input()->dataspace() };
Nitpicker::Point const _initial_mouse_pos { -1, -1 };
Nitpicker::Point _old_mouse_pos = _initial_mouse_pos;
unsigned _key_cnt = 0;
Signal_handler<Main> _input_handler {
_env.ep(), *this, &Main::_handle_input };
void _handle_input()
{
Input::Event const *ev_buf = _ev_ds.local_addr<Input::Event const>();
for (int i = 0, num_ev = _nitpicker.input()->flush(); i < num_ev; i++) {
Input::Event const &ev = ev_buf[i];
if (ev.press()) _key_cnt++;
if (ev.release()) _key_cnt--;
/* move view */
ev.handle_absolute_motion([&] (int x, int y) {
Nitpicker::Point const mouse_pos(x, y);
if (_key_cnt && _old_mouse_pos != _initial_mouse_pos)
_view.move(_view.pos() + mouse_pos - _old_mouse_pos);
_old_mouse_pos = mouse_pos;
});
/* find selected view and bring it to front */
if (ev.press() && _key_cnt == 1)
_view.top();
}
}
Signal_handler<Main> _timer_handler {
_env.ep(), *this, &Main::_handle_timer };
void _handle_timer()
{
if (_log_window.draw())
_nitpicker.framebuffer()->refresh(0, 0, _win_w, _win_h);
}
Main(Env &env) : _env(env)
{
/* announce service at our parent */
_env.parent().announce(_env.ep().manage(_root));
_timer.sigh(_timer_handler);
_timer.trigger_periodic(20*1000);
_nitpicker.input()->sigh(_input_handler);
}
};
void Component::construct(Genode::Env &env)
{
static Nitlog::Main main(env);
}