514 lines
12 KiB
C++
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);
|
|
}
|