terminal: improve internal structure

This patch reorganizes the terminal's source code to become easier to
extend. It also enables the strict warning level.
This commit is contained in:
Norman Feske 2018-02-06 21:32:02 +01:00
parent 12c8e51071
commit 96a068f90a
16 changed files with 834 additions and 682 deletions

View File

@ -0,0 +1,87 @@
/*
* \brief Terminal color palette
* \author Norman Feske
* \date 2018-02-06
*/
/*
* Copyright (C) 2018 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.
*/
#ifndef _COLOR_PALETTE_H_
#define _COLOR_PALETTE_H_
/* Genode includes */
#include <util/color.h>
/* local includes */
#include "types.h"
namespace Terminal {
class Color_palette;
}
class Terminal::Color_palette
{
public:
struct Index { unsigned value; };
struct Highlighted { bool value; };
struct Inverse { bool value; };
private:
enum { NUM_COLORS = 16U };
Color _colors[NUM_COLORS];
public:
Color_palette()
{
_colors[0] = Color( 0, 0, 0); /* black */
_colors[1] = Color(255, 128, 128); /* red */
_colors[2] = Color(128, 255, 128); /* green */
_colors[3] = Color(255, 255, 0); /* yellow */
_colors[4] = Color(128, 128, 255); /* blue */
_colors[5] = Color(255, 0, 255); /* magenta */
_colors[6] = Color( 0, 255, 255); /* cyan */
_colors[7] = Color(255, 255, 255); /* white */
/* the upper portion of the palette contains highlight colors */
for (unsigned i = 0; i < NUM_COLORS/2; i++) {
Color const col = _colors[i];
_colors[i + NUM_COLORS/2] = Color((col.r*2)/3, (col.g*2)/3, (col.b*2)/3);
}
}
Color foreground(Index index, Highlighted highlighted, Inverse inverse) const
{
if (index.value >= NUM_COLORS/2)
return Color(0, 0, 0);
Color const col =
_colors[index.value + (highlighted.value ? NUM_COLORS/2 : 0)];
return (inverse.value) ? Color(col.r/2, col.g/2, col.b/2) : col;
}
Color background(Index index, Highlighted highlighted, Inverse inverse) const
{
Color const color =
_colors[index.value + (highlighted.value ? NUM_COLORS/2 : 0)];
return (inverse.value) ? Color((color.r + 255)/2,
(color.g + 255)/2,
(color.b + 255)/2)
: color;
}
};
#endif /* _COLOR_PALETTE_H_ */

View File

@ -0,0 +1,69 @@
/*
* \brief Function for drawing the glyphs of terminal characters
* \author Norman Feske
* \date 2018-02-06
*/
/*
* Copyright (C) 2018 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.
*/
#ifndef _DRAW_GLYPH_H_
#define _DRAW_GLYPH_H_
/* Genode includes */
#include <os/pixel_rgb565.h>
template <typename PT>
inline void draw_glyph(Genode::Color fg_color,
Genode::Color bg_color,
const unsigned char *glyph_base,
unsigned glyph_width,
unsigned glyph_img_width,
unsigned glyph_img_height,
unsigned cell_width,
PT *fb_base,
unsigned fb_width)
{
PT fg_pixel(fg_color.r, fg_color.g, fg_color.b);
PT bg_pixel(bg_color.r, bg_color.g, bg_color.b);
unsigned const horizontal_gap = cell_width
- Genode::min(glyph_width, cell_width);
unsigned const left_gap = horizontal_gap / 2;
unsigned const right_gap = horizontal_gap - left_gap;
/*
* Clear gaps to the left and right of the character if the character's
* with is smaller than the cell width.
*/
if (horizontal_gap) {
PT *line = fb_base;
for (unsigned y = 0 ; y < glyph_img_height; y++, line += fb_width) {
for (unsigned x = 0; x < left_gap; x++)
line[x] = bg_pixel;
for (unsigned x = cell_width - right_gap; x < cell_width; x++)
line[x] = bg_pixel;
}
}
/* center glyph horizontally within its cell */
fb_base += left_gap;
for (unsigned y = 0 ; y < glyph_img_height; y++) {
for (unsigned x = 0; x < glyph_width; x++)
fb_base[x] = PT::mix(bg_pixel, fg_pixel, glyph_base[x]);
fb_base += fb_width;
glyph_base += glyph_img_width;
}
}
#endif /* _DRAW_GLYPH_H_ */

View File

@ -0,0 +1,47 @@
/*
* \brief Terminal font handling
* \author Norman Feske
* \date 2018-02-06
*/
/*
* Copyright (C) 2018 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.
*/
#ifndef _FONT_FAMILY_H_
#define _FONT_FAMILY_H_
/* local includes */
#include "types.h"
namespace Terminal {
typedef Text_painter::Font Font;
class Font_family;
}
class Terminal::Font_family
{
private:
Font const &_regular;
public:
Font_family(Font const &regular) : _regular(regular) { }
/**
* Return font for specified face
*
* For now, we do not support font faces other than regular.
*/
Font const &font(Font_face) const { return _regular; }
unsigned cell_width() const { return _regular.str_w("m"); }
unsigned cell_height() const { return _regular.str_h("m"); }
};
#endif /* _FONT_FAMILY_H_ */

View File

@ -0,0 +1,80 @@
/*
* \brief Terminal framebuffer output backend
* \author Norman Feske
* \date 2018-02-06
*/
/*
* Copyright (C) 2018 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.
*/
#ifndef _FRAMEBUFFER_H_
#define _FRAMEBUFFER_H_
/* Genode includes */
#include <framebuffer_session/connection.h>
/* local includes */
#include "types.h"
namespace Terminal { class Framebuffer; }
class Terminal::Framebuffer
{
private:
Env &_env;
::Framebuffer::Connection _fb { _env, ::Framebuffer::Mode() };
Constructible<Attached_dataspace> _ds { };
::Framebuffer::Mode _mode { };
public:
/**
* Constructor
*
* \param mode_sigh signal handler to be triggered on mode changes
*/
Framebuffer(Env &env, Signal_context_capability mode_sigh) : _env(env)
{
switch_to_new_mode();
_fb.mode_sigh(mode_sigh);
}
unsigned w() const { return _mode.width(); }
unsigned h() const { return _mode.height(); }
template <typename PT>
PT *pixel() { return _ds->local_addr<PT>(); }
void refresh(Rect rect)
{
_fb.refresh(rect.x1(), rect.y1(), rect.w(), rect.h());
}
/**
* Return true if the framebuffer mode differs from the current
* terminal size.
*/
bool mode_changed() const
{
::Framebuffer::Mode _new_mode = _fb.mode();
return _new_mode.width() != _mode.width()
|| _new_mode.height() != _mode.height();
}
void switch_to_new_mode()
{
_ds.construct(_env.rm(), _fb.dataspace());
_mode = _fb.mode();
}
};
#endif /* _FRAMEBUFFER_H_ */

View File

@ -18,528 +18,121 @@
#include <framebuffer_session/connection.h> #include <framebuffer_session/connection.h>
#include <input_session/connection.h> #include <input_session/connection.h>
#include <timer_session/connection.h> #include <timer_session/connection.h>
#include <root/component.h>
#include <base/attached_rom_dataspace.h> #include <base/attached_rom_dataspace.h>
#include <base/attached_ram_dataspace.h> #include <base/attached_ram_dataspace.h>
#include <input/event.h> #include <input/event.h>
#include <util/color.h>
#include <os/pixel_rgb565.h>
/* terminal includes */ /* terminal includes */
#include <terminal/decoder.h> #include <terminal/decoder.h>
#include <terminal/types.h> #include <terminal/types.h>
#include <terminal/scancode_tracker.h> #include <terminal/scancode_tracker.h>
#include <terminal/keymaps.h> #include <terminal/keymaps.h>
#include <terminal/char_cell_array_character_screen.h>
#include <terminal_session/terminal_session.h>
/* nitpicker graphic back end */ /* local includes */
#include <nitpicker_gfx/text_painter.h> #include "text_screen_surface.h"
#include "session.h"
namespace Terminal { namespace Terminal { struct Main; }
using namespace Genode;
struct Main;
}
using Genode::Pixel_rgb565; struct Terminal::Main : Character_consumer
typedef Text_painter::Font Font;
static bool const verbose = false;
inline Pixel_rgb565 blend(Pixel_rgb565 src, int alpha)
{ {
Pixel_rgb565 res;
res.pixel = ((((alpha >> 3) * (src.pixel & 0xf81f)) >> 5) & 0xf81f)
| ((( alpha * (src.pixel & 0x07c0)) >> 8) & 0x07c0);
return res;
}
inline Pixel_rgb565 mix(Pixel_rgb565 p1, Pixel_rgb565 p2, int alpha)
{
Pixel_rgb565 res;
/* /*
* We substract the alpha from 264 instead of 255 to * Noncopyable
* compensate the brightness loss caused by the rounding
* error of the blend function when having only 5 bits
* per channel.
*/ */
res.pixel = blend(p1, 264 - alpha).pixel + blend(p2, alpha).pixel; Main(Main const &);
return res; Main &operator = (Main const &);
}
Env &_env;
using Genode::Color; Attached_rom_dataspace _config { _env, "config" };
static Color color_palette[2*8];
static Color foreground_color(Char_cell const &cell)
{
Color col = color_palette[cell.colidx_fg() + (cell.highlight() ? 8 : 0)];
if (cell.inverse())
col = Color(col.r/2, col.g/2, col.b/2);
return col;
}
static Color background_color(Char_cell const &cell)
{
Color col = color_palette[cell.colidx_bg() + (cell.highlight() ? 8 : 0)];
if (cell.inverse())
return Color((col.r + 255)/2, (col.g + 255)/2, (col.b + 255)/2);
return col;
}
class Font_family
{
private:
Font const &_regular;
public:
Font_family(Font const &regular /* ...to be extended */ )
: _regular(regular) { }
/** /**
* Return font for specified face * Return font data according to config
*
* For now, we do not support font faces other than regular.
*/ */
Font const *font(Font_face) const { return &_regular; } static char const *_font_data(Xml_node config);
unsigned cell_width() const { return _regular.str_w("m"); } Reconstructible<Font> _font { _font_data(_config.xml()) };
unsigned cell_height() const { return _regular.str_h("m"); } Reconstructible<Font_family> _font_family { *_font };
};
unsigned char *_keymap = Terminal::usenglish_keymap;
unsigned char *_shift = Terminal::usenglish_shift;
unsigned char *_altgr = nullptr;
template <typename PT> Color_palette _color_palette { };
inline void draw_glyph(Color fg_color,
Color bg_color,
const unsigned char *glyph_base,
unsigned glyph_width,
unsigned glyph_img_width,
unsigned glyph_img_height,
unsigned cell_width,
PT *fb_base,
unsigned fb_width)
{
PT fg_pixel(fg_color.r, fg_color.g, fg_color.b);
PT bg_pixel(bg_color.r, bg_color.g, bg_color.b);
unsigned const horizontal_gap = cell_width void _handle_config();
- Genode::min(glyph_width, cell_width);
unsigned const left_gap = horizontal_gap / 2; Signal_handler<Main> _config_handler {
unsigned const right_gap = horizontal_gap - left_gap; _env.ep(), *this, &Main::_handle_config };
/*
* Clear gaps to the left and right of the character if the character's
* with is smaller than the cell width.
*/
if (horizontal_gap) {
PT *line = fb_base;
for (unsigned y = 0 ; y < glyph_img_height; y++, line += fb_width) {
for (unsigned x = 0; x < left_gap; x++)
line[x] = bg_pixel;
for (unsigned x = cell_width - right_gap; x < cell_width; x++)
line[x] = bg_pixel;
}
}
/* center glyph horizontally within its cell */
fb_base += left_gap;
for (unsigned y = 0 ; y < glyph_img_height; y++) {
for (unsigned x = 0; x < glyph_width; x++)
fb_base[x] = mix(bg_pixel, fg_pixel, glyph_base[x]);
fb_base += fb_width;
glyph_base += glyph_img_width;
}
}
template <typename PT>
static void convert_char_array_to_pixels(Cell_array<Char_cell> *cell_array,
PT *fb_base,
unsigned fb_width,
unsigned fb_height,
Font_family const &font_family)
{
Font const &regular_font = *font_family.font(Font_face::REGULAR);
unsigned glyph_height = regular_font.img_h,
glyph_step_x = regular_font.wtab['m'];
unsigned y = 0;
for (unsigned line = 0; line < cell_array->num_lines(); line++) {
if (cell_array->line_dirty(line)) {
if (verbose)
Genode::log("convert line ", line);
unsigned x = 0;
for (unsigned column = 0; column < cell_array->num_cols(); column++) {
Char_cell cell = cell_array->get_cell(column, line);
Font const *font = font_family.font(cell.font_face());
unsigned char ascii = cell.ascii;
if (ascii == 0)
ascii = ' ';
unsigned char const *glyph_base = font->img + font->otab[ascii];
unsigned glyph_width = regular_font.wtab[ascii];
if (x + glyph_width > fb_width) break;
Color fg_color = foreground_color(cell);
Color bg_color = background_color(cell);
if (cell.has_cursor()) {
fg_color = Color( 63, 63, 63);
bg_color = Color(255, 255, 255);
}
draw_glyph<PT>(fg_color, bg_color,
glyph_base, glyph_width,
(unsigned)font->img_w, (unsigned)font->img_h,
glyph_step_x, fb_base + x, fb_width);
x += glyph_step_x;
}
}
y += glyph_height;
fb_base += fb_width*glyph_height;
if (y + glyph_height > fb_height) break;
}
}
namespace Terminal {
struct Flush_callback : Genode::List<Flush_callback>::Element
{
virtual void flush() = 0;
};
struct Flush_callback_registry
{
Genode::List<Flush_callback> _list;
Genode::Lock _lock;
void add(Flush_callback *flush_callback)
{
Genode::Lock::Guard guard(_lock);
_list.insert(flush_callback);
}
void remove(Flush_callback *flush_callback)
{
Genode::Lock::Guard guard(_lock);
_list.remove(flush_callback);
}
void flush()
{
Genode::Lock::Guard guard(_lock);
Flush_callback *curr = _list.first();
for (; curr; curr = curr->next())
curr->flush();
}
};
struct Trigger_flush_callback
{
virtual void trigger_flush() = 0;
};
class Session_component : public Genode::Rpc_object<Session, Session_component>,
public Flush_callback
{
private:
Read_buffer &_read_buffer;
Framebuffer::Session &_framebuffer;
Flush_callback_registry &_flush_callback_registry;
Trigger_flush_callback &_trigger_flush_callback;
Genode::Attached_ram_dataspace _io_buffer;
Framebuffer::Mode _fb_mode;
Genode::Dataspace_capability _fb_ds_cap;
unsigned _char_width;
unsigned _char_height;
unsigned _columns;
unsigned _lines;
void *_fb_addr;
/**
* Protect '_char_cell_array'
*/
Genode::Lock _lock;
Cell_array<Char_cell> _char_cell_array;
Char_cell_array_character_screen _char_cell_array_character_screen;
Terminal::Decoder _decoder;
Terminal::Position _last_cursor_pos;
Font_family const &_font_family;
/**
* Initialize framebuffer-related attributes
*/
Genode::Dataspace_capability _init_fb()
{
if (_fb_mode.format() != Framebuffer::Mode::RGB565) {
Genode::error("color mode ", _fb_mode, " not supported");
return Genode::Dataspace_capability();
}
return _framebuffer.dataspace();
}
public:
/**
* Constructor
*/
Session_component(Genode::Env &env,
Genode::Allocator &alloc,
Read_buffer &read_buffer,
Framebuffer::Session &framebuffer,
Genode::size_t io_buffer_size,
Flush_callback_registry &flush_callback_registry,
Trigger_flush_callback &trigger_flush_callback,
Font_family const &font_family)
:
_read_buffer(read_buffer), _framebuffer(framebuffer),
_flush_callback_registry(flush_callback_registry),
_trigger_flush_callback(trigger_flush_callback),
_io_buffer(env.ram(), env.rm(), io_buffer_size),
_fb_mode(_framebuffer.mode()),
_fb_ds_cap(_init_fb()),
/* take size of space character as character cell size */
_char_width(font_family.cell_width()),
_char_height(font_family.cell_height()),
/* compute number of characters fitting on the framebuffer */
_columns(_fb_mode.width()/_char_width),
_lines(_fb_mode.height()/_char_height),
_fb_addr(env.rm().attach(_fb_ds_cap)),
_char_cell_array(_columns, _lines, &alloc),
_char_cell_array_character_screen(_char_cell_array),
_decoder(_char_cell_array_character_screen),
_font_family(font_family)
{
using namespace Genode;
log("new terminal session:");
log(" framebuffer has mode ", _fb_mode);
log(" character size is ", _char_width, "x", _char_height, " pixels");
log(" terminal size is ", _columns, "x", _lines, " characters");
framebuffer.refresh(0, 0, _fb_mode.width(), _fb_mode.height());
_flush_callback_registry.add(this);
}
~Session_component()
{
_flush_callback_registry.remove(this);
}
void flush()
{
Genode::Lock::Guard guard(_lock);
convert_char_array_to_pixels<Pixel_rgb565>(&_char_cell_array,
(Pixel_rgb565 *)_fb_addr,
_fb_mode.width(),
_fb_mode.height(),
_font_family);
int first_dirty_line = 10000,
last_dirty_line = -10000;
for (int line = 0; line < (int)_char_cell_array.num_lines(); line++) {
if (!_char_cell_array.line_dirty(line)) continue;
first_dirty_line = Genode::min(line, first_dirty_line);
last_dirty_line = Genode::max(line, last_dirty_line);
_char_cell_array.mark_line_as_clean(line);
}
int num_dirty_lines = last_dirty_line - first_dirty_line + 1;
if (num_dirty_lines > 0)
_framebuffer.refresh(0, first_dirty_line*_char_height,
_fb_mode.width(),
num_dirty_lines*_char_height);
}
/********************************
** Terminal session interface **
********************************/
Size size() { return Size(_columns, _lines); }
bool avail() { return !_read_buffer.empty(); }
Genode::size_t _read(Genode::size_t dst_len)
{
/* read data, block on first byte if needed */
unsigned num_bytes = 0;
unsigned char *dst = _io_buffer.local_addr<unsigned char>();
Genode::size_t dst_size = Genode::min(_io_buffer.size(), dst_len);
while (!_read_buffer.empty() && num_bytes < dst_size)
dst[num_bytes++] = _read_buffer.get();
return num_bytes;
}
Genode::size_t _write(Genode::size_t num_bytes)
{
Genode::Lock::Guard guard(_lock);
unsigned char *src = _io_buffer.local_addr<unsigned char>();
for (unsigned i = 0; i < num_bytes; i++) {
/* submit character to sequence decoder */
_decoder.insert(src[i]);
}
_trigger_flush_callback.trigger_flush();
return num_bytes;
}
Genode::Dataspace_capability _dataspace()
{
return _io_buffer.cap();
}
void connected_sigh(Genode::Signal_context_capability sigh)
{
/*
* Immediately reflect connection-established signal to the
* client because the session is ready to use immediately after
* creation.
*/
Genode::Signal_transmitter(sigh).submit();
}
void read_avail_sigh(Genode::Signal_context_capability cap)
{
_read_buffer.sigh(cap);
}
Genode::size_t read(void *buf, Genode::size_t) { return 0; }
Genode::size_t write(void const *buf, Genode::size_t) { return 0; }
};
class Root_component : public Genode::Root_component<Session_component>
{
private:
Genode::Env &_env;
Read_buffer &_read_buffer;
Framebuffer::Session &_framebuffer;
Flush_callback_registry &_flush_callback_registry;
Trigger_flush_callback &_trigger_flush_callback;
Font_family const &_font_family;
protected:
Session_component *_create_session(const char *args)
{
Genode::log("create terminal session");
/*
* XXX read I/O buffer size from args
*/
Genode::size_t io_buffer_size = 4096;
return new (md_alloc())
Session_component(_env, *md_alloc(),
_read_buffer,
_framebuffer,
io_buffer_size,
_flush_callback_registry,
_trigger_flush_callback,
_font_family);
}
public:
/**
* Constructor
*/
Root_component(Genode::Env &env,
Genode::Allocator &md_alloc,
Read_buffer &read_buffer,
Framebuffer::Session &framebuffer,
Flush_callback_registry &flush_callback_registry,
Trigger_flush_callback &trigger_flush_callback,
Font_family const &font_family)
:
Genode::Root_component<Session_component>(env.ep(), md_alloc),
_env(env),
_read_buffer(read_buffer), _framebuffer(framebuffer),
_flush_callback_registry(flush_callback_registry),
_trigger_flush_callback(trigger_flush_callback),
_font_family(font_family)
{ }
};
}
struct Terminal::Main
{
Genode::Env &_env;
Framebuffer::Connection _framebuffer { _env, Framebuffer::Mode() };
Input::Connection _input { _env }; Input::Connection _input { _env };
Timer::Connection _timer { _env }; Timer::Connection _timer { _env };
Heap _heap { _env.ram(), _env.rm() }; Heap _heap { _env.ram(), _env.rm() };
/* input read buffer */ Framebuffer _framebuffer { _env, _config_handler };
Read_buffer _read_buffer;
Terminal::Flush_callback_registry _flush_callback_registry; typedef Pixel_rgb565 PT;
Constructible<Text_screen_surface<PT>> _text_screen_surface { };
Session::Size _terminal_size { };
/*
* Time in milliseconds between a change of the terminal content and the
* update of the pixels. By delaying the update, multiple intermediate
* changes result in only one rendering step.
*/
unsigned const _flush_delay = 5;
bool _flush_scheduled = false;
void _handle_flush(Duration)
{
_flush_scheduled = false;
// XXX distinguish between normal and alternate display
if (_text_screen_surface.constructed())
_text_screen_surface->redraw();
}
Timer::One_shot_timeout<Main> _flush_timeout {
_timer, *this, &Main::_handle_flush };
void _schedule_flush()
{
if (!_flush_scheduled) {
_flush_timeout.schedule(Microseconds{1000*_flush_delay});
_flush_scheduled = true;
}
}
/**
* Character_consumer interface, called from 'Terminal::Session_component'
*/
void consume_character(Character c) override
{
// XXX distinguish between normal and alternative display mode (smcup)
if (_text_screen_surface.constructed())
_text_screen_surface->apply_character(c);
_schedule_flush();
}
/* input read buffer */
Read_buffer _read_buffer { };
/* create root interface for service */ /* create root interface for service */
Terminal::Root_component _root; Root_component _root { _env, _heap, _read_buffer, *this, _terminal_size };
Terminal::Scancode_tracker _scancode_tracker; /*
* builtin keyboard-layout handling
*
* \deprecated The keyboard layout should be handled by the input-filter
* component.
*/
Constructible<Scancode_tracker> _scancode_tracker { };
/* state needed for key-repeat handling */ /* state needed for key-repeat handling */
unsigned const _repeat_delay = 250; unsigned const _repeat_delay = 250;
@ -556,53 +149,10 @@ struct Terminal::Main
Timer::One_shot_timeout<Main> _key_repeat_timeout { Timer::One_shot_timeout<Main> _key_repeat_timeout {
_timer, *this, &Main::_handle_key_repeat }; _timer, *this, &Main::_handle_key_repeat };
void _handle_flush(Duration); Main(Env &env) : _env(env)
/*
* Time in milliseconds between a change of the terminal content and the
* update of the pixels. By delaying the update, multiple intermediate
* changes result in only one rendering step.
*/
unsigned const _flush_delay = 5;
bool _flush_scheduled = false;
void _trigger_flush()
{ {
if (!_flush_scheduled) { _handle_config();
_flush_timeout.schedule(Microseconds{1000*_flush_delay});
_flush_scheduled = true;
}
}
/*
* Callback invoked if new terminal content appears
*/
struct Trigger_flush : Trigger_flush_callback
{
Main &_main;
void trigger_flush() override { _main._trigger_flush(); }
Trigger_flush(Main &main) : _main(main) { }
} _trigger_flush_callback { *this };
Timer::One_shot_timeout<Main> _flush_timeout {
_timer, *this, &Main::_handle_flush };
Main(Genode::Env &env,
Font_family &font_family,
unsigned char const *keymap,
unsigned char const *shift,
unsigned char const *altgr,
unsigned char const *control)
:
_env(env),
_root(_env, _heap,
_read_buffer, _framebuffer,
_flush_callback_registry,
_trigger_flush_callback,
font_family),
_scancode_tracker(keymap, shift, altgr, Terminal::control)
{
_input.sigh(_input_handler); _input.sigh(_input_handler);
/* announce service at our parent */ /* announce service at our parent */
@ -611,6 +161,79 @@ struct Terminal::Main
}; };
/* built-in fonts */
extern char const _binary_notix_8_tff_start;
extern char const _binary_terminus_12_tff_start;
extern char const _binary_terminus_16_tff_start;
char const *Terminal::Main::_font_data(Xml_node config)
{
if (config.has_sub_node("font")) {
size_t const size = config.sub_node("font").attribute_value("size", 16U);
switch (size) {
case 8: return &_binary_notix_8_tff_start; break;
case 12: return &_binary_terminus_12_tff_start; break;
case 16: return &_binary_terminus_16_tff_start; break;
default: break;
}
}
return &_binary_terminus_16_tff_start;
}
void Terminal::Main::_handle_config()
{
_config.update();
_font_family.destruct();
_font.destruct();
Xml_node const config = _config.xml();
_font.construct(_font_data(config));
_font_family.construct(*_font);
/*
* Adapt terminal to framebuffer mode changes
*/
if (_framebuffer.mode_changed() || !_text_screen_surface.constructed()) {
_framebuffer.switch_to_new_mode();
_text_screen_surface.construct(_heap, *_font_family,
_color_palette, _framebuffer);
_terminal_size = _text_screen_surface->size();
_schedule_flush();
}
/*
* Read keyboard layout from config file
*/
_keymap = Terminal::usenglish_keymap;
_shift = Terminal::usenglish_shift;
_altgr = nullptr;
if (config.has_sub_node("keyboard")) {
if (config.sub_node("keyboard").attribute("layout").has_value("de")) {
_keymap = Terminal::german_keymap;
_shift = Terminal::german_shift;
_altgr = Terminal::german_altgr;
}
if (config.sub_node("keyboard").attribute("layout").has_value("none")) {
_keymap = nullptr;
_shift = nullptr;
_altgr = nullptr;
}
}
if (!_scancode_tracker.constructed())
_scancode_tracker.construct(_keymap, _shift, _altgr, Terminal::control);
}
void Terminal::Main::_handle_input() void Terminal::Main::_handle_input()
{ {
_input.for_each_event([&] (Input::Event const &event) { _input.for_each_event([&] (Input::Event const &event) {
@ -623,15 +246,19 @@ void Terminal::Main::_handle_input()
if (utf8.b3) _read_buffer.add(utf8.b3); if (utf8.b3) _read_buffer.add(utf8.b3);
} }
/* apply the terminal's built-in character map if configured */
if (!_scancode_tracker.constructed())
return;
bool press = (event.type() == Input::Event::PRESS ? true : false); bool press = (event.type() == Input::Event::PRESS ? true : false);
bool release = (event.type() == Input::Event::RELEASE ? true : false); bool release = (event.type() == Input::Event::RELEASE ? true : false);
int keycode = event.code(); int keycode = event.code();
if (press || release) if (press || release)
_scancode_tracker.submit(keycode, press); _scancode_tracker->submit(keycode, press);
if (press) { if (press) {
_scancode_tracker.emit_current_character(_read_buffer); _scancode_tracker->emit_current_character(_read_buffer);
/* setup first key repeat */ /* setup first key repeat */
_repeat_next = _repeat_delay; _repeat_next = _repeat_delay;
@ -651,7 +278,8 @@ void Terminal::Main::_handle_key_repeat(Duration)
if (_repeat_next) { if (_repeat_next) {
/* repeat current character or sequence */ /* repeat current character or sequence */
_scancode_tracker.emit_current_character(_read_buffer); if (_scancode_tracker.constructed())
_scancode_tracker->emit_current_character(_read_buffer);
_repeat_next = _repeat_rate; _repeat_next = _repeat_rate;
} }
@ -660,89 +288,4 @@ void Terminal::Main::_handle_key_repeat(Duration)
} }
void Terminal::Main::_handle_flush(Duration) void Component::construct(Genode::Env &env) { static Terminal::Main main(env); }
{
_flush_scheduled = false;
_flush_callback_registry.flush();
}
/* built-in fonts */
extern char _binary_notix_8_tff_start;
extern char _binary_terminus_12_tff_start;
extern char _binary_terminus_16_tff_start;
void Component::construct(Genode::Env &env)
{
/* XXX execute constructors of global statics */
env.exec_static_constructors();
using namespace Genode;
Attached_rom_dataspace config(env, "config");
/* initialize color palette */
color_palette[0] = Color( 0, 0, 0); /* black */
color_palette[1] = Color(255, 128, 128); /* red */
color_palette[2] = Color(128, 255, 128); /* green */
color_palette[3] = Color(255, 255, 0); /* yellow */
color_palette[4] = Color(128, 128, 255); /* blue */
color_palette[5] = Color(255, 0, 255); /* magenta */
color_palette[6] = Color( 0, 255, 255); /* cyan */
color_palette[7] = Color(255, 255, 255); /* white */
/* the upper portion of the palette contains highlight colors */
for (int i = 0; i < 8; i++) {
Color col = color_palette[i];
col = Color((col.r*2)/3, (col.g*2)/3, (col.b*2)/3);
color_palette[i + 8] = col;
}
/* pick font according to config file */
char const *font_data = &_binary_terminus_16_tff_start;
try {
size_t font_size = 16;
config.xml().sub_node("font").attribute("size").value(&font_size);
switch (font_size) {
case 8: font_data = &_binary_notix_8_tff_start; break;
case 12: font_data = &_binary_terminus_12_tff_start; break;
case 16: font_data = &_binary_terminus_16_tff_start; break;
default: break;
}
} catch (...) { }
static Font font(font_data);
static Font_family font_family(font);
log("cell size is ", (int)font_family.cell_width(),
"x", (int)font_family.cell_height());
unsigned char *keymap = Terminal::usenglish_keymap;
unsigned char *shift = Terminal::usenglish_shift;
unsigned char *altgr = 0;
/*
* Read keyboard layout from config file
*/
try {
if (config.xml().sub_node("keyboard")
.attribute("layout").has_value("de")) {
keymap = Terminal::german_keymap;
shift = Terminal::german_shift;
altgr = Terminal::german_altgr;
}
} catch (...) { }
try {
if (config.xml().sub_node("keyboard")
.attribute("layout").has_value("none")) {
keymap = nullptr;
shift = nullptr;
altgr = nullptr;
}
} catch (...) { }
static Terminal::Main main(env, font_family, keymap, shift, altgr, Terminal::control);
}

View File

@ -0,0 +1,157 @@
/*
* \brief Terminal session interface
* \author Norman Feske
* \date 2018-02-06
*/
/*
* Copyright (C) 2018 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.
*/
#ifndef _SESSION_H_
#define _SESSION_H_
/* Genode includes */
#include <root/component.h>
#include <terminal_session/terminal_session.h>
/* local includes */
#include "types.h"
namespace Terminal {
class Session_component;
class Root_component;
}
class Terminal::Session_component : public Rpc_object<Session, Session_component>
{
private:
Read_buffer &_read_buffer;
Character_consumer &_character_consumer;
Size const &_terminal_size;
Attached_ram_dataspace _io_buffer;
Terminal::Position _last_cursor_pos { };
public:
/**
* Constructor
*/
Session_component(Env &env,
Read_buffer &read_buffer,
Character_consumer &character_consumer,
Size const &terminal_size,
size_t io_buffer_size)
:
_read_buffer(read_buffer),
_character_consumer(character_consumer),
_terminal_size(terminal_size),
_io_buffer(env.ram(), env.rm(), io_buffer_size)
{ }
/********************************
** Terminal session interface **
********************************/
Size size() override { return _terminal_size; }
bool avail() override { return !_read_buffer.empty(); }
size_t _read(size_t dst_len)
{
/* read data, block on first byte if needed */
unsigned num_bytes = 0;
unsigned char *dst = _io_buffer.local_addr<unsigned char>();
size_t dst_size = min(_io_buffer.size(), dst_len);
while (!_read_buffer.empty() && num_bytes < dst_size)
dst[num_bytes++] = _read_buffer.get();
return num_bytes;
}
size_t _write(size_t num_bytes)
{
unsigned char *src = _io_buffer.local_addr<unsigned char>();
for (unsigned i = 0; i < num_bytes; i++)
_character_consumer.consume_character(src[i]);
return num_bytes;
}
Dataspace_capability _dataspace() { return _io_buffer.cap(); }
void connected_sigh(Signal_context_capability sigh) override
{
/*
* Immediately reflect connection-established signal to the
* client because the session is ready to use immediately after
* creation.
*/
Signal_transmitter(sigh).submit();
}
void read_avail_sigh(Signal_context_capability cap) override
{
_read_buffer.sigh(cap);
}
size_t read(void *, size_t) override { return 0; }
size_t write(void const *, size_t) override { return 0; }
};
class Terminal::Root_component : public Genode::Root_component<Session_component>
{
private:
Env &_env;
Read_buffer &_read_buffer;
Character_consumer &_character_consumer;
Session::Size &_terminal_size;
protected:
Session_component *_create_session(const char *)
{
/*
* XXX read I/O buffer size from args
*/
size_t io_buffer_size = 4096;
return new (md_alloc())
Session_component(_env,
_read_buffer,
_character_consumer,
_terminal_size,
io_buffer_size);
}
public:
/**
* Constructor
*/
Root_component(Env &env,
Allocator &md_alloc,
Read_buffer &read_buffer,
Character_consumer &character_consumer,
Session::Size &terminal_size)
:
Genode::Root_component<Session_component>(env.ep(), md_alloc),
_env(env),
_read_buffer(read_buffer),
_character_consumer(character_consumer),
_terminal_size(terminal_size)
{ }
};
#endif /* _SESSION_H_ */

View File

@ -2,5 +2,3 @@ TARGET = terminal
SRC_CC = main.cc SRC_CC = main.cc
LIBS = base LIBS = base
SRC_BIN = $(notdir $(wildcard $(PRG_DIR)/*.tff)) SRC_BIN = $(notdir $(wildcard $(PRG_DIR)/*.tff))
CC_CXX_WARN_STRICT =

View File

@ -0,0 +1,160 @@
/*
* \brief Terminal graphics backend for textual screen
* \author Norman Feske
* \date 2018-02-06
*/
/*
* Copyright (C) 2011-2018 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.
*/
#ifndef _TEXT_SCREEN_SURFACE_H_
#define _TEXT_SCREEN_SURFACE_H_
/* Genode includes */
#include <os/pixel_rgb565.h>
/* terminal includes */
#include <terminal/char_cell_array_character_screen.h>
/* nitpicker graphic back end */
#include <nitpicker_gfx/text_painter.h>
/* local includes */
#include "font_family.h"
#include "draw_glyph.h"
#include "color_palette.h"
#include "framebuffer.h"
namespace Terminal { template <typename> class Text_screen_surface; }
template <typename PT>
class Terminal::Text_screen_surface
{
private:
Font_family const &_font_family;
Color_palette const &_palette;
Framebuffer &_framebuffer;
/* take size of space character as character cell size */
unsigned const _char_width { _font_family.cell_width() };
unsigned const _char_height { _font_family.cell_height() };
/* number of characters fitting on the framebuffer */
unsigned const _columns { _framebuffer.w()/_char_width };
unsigned const _lines { _framebuffer.h()/_char_height };
Cell_array<Char_cell> _cell_array;
Char_cell_array_character_screen _character_screen { _cell_array };
Decoder _decoder { _character_screen };
public:
Text_screen_surface(Allocator &alloc, Font_family const &font_family,
Color_palette &palette, Framebuffer &framebuffer)
:
_font_family(font_family),
_palette(palette),
_framebuffer(framebuffer),
_cell_array(_columns, _lines, alloc)
{ }
void redraw()
{
Font const &regular_font = _font_family.font(Font_face::REGULAR);
unsigned const glyph_height = regular_font.img_h,
glyph_step_x = regular_font.wtab['m'];
unsigned const fb_width = _framebuffer.w(),
fb_height = _framebuffer.h();
PT *fb_base = _framebuffer.pixel<PT>();
unsigned y = 0;
for (unsigned line = 0; line < _cell_array.num_lines(); line++) {
if (_cell_array.line_dirty(line)) {
unsigned x = 0;
for (unsigned column = 0; column < _cell_array.num_cols(); column++) {
Char_cell cell = _cell_array.get_cell(column, line);
Font const &font = _font_family.font(cell.font_face());
unsigned char ascii = cell.ascii;
if (ascii == 0)
ascii = ' ';
unsigned char const *glyph_base = font.img + font.otab[ascii];
unsigned glyph_width = regular_font.wtab[ascii];
if (x + glyph_width > fb_width)
break;
Color_palette::Highlighted const highlighted { cell.highlight() };
Color_palette::Inverse const inverse { cell.inverse() };
Color fg_color =
_palette.foreground(Color_palette::Index{cell.colidx_fg()},
highlighted, inverse);
Color bg_color =
_palette.background(Color_palette::Index{cell.colidx_bg()},
highlighted, inverse);
if (cell.has_cursor()) {
fg_color = Color( 63, 63, 63);
bg_color = Color(255, 255, 255);
}
draw_glyph<PT>(fg_color, bg_color,
glyph_base, glyph_width,
(unsigned)font.img_w, (unsigned)font.img_h,
glyph_step_x, fb_base + x, fb_width);
x += glyph_step_x;
}
}
y += glyph_height;
fb_base += fb_width*glyph_height;
if (y + glyph_height > fb_height) break;
}
int first_dirty_line = 10000,
last_dirty_line = -10000;
for (int line = 0; line < (int)_cell_array.num_lines(); line++) {
if (!_cell_array.line_dirty(line)) continue;
first_dirty_line = min(line, first_dirty_line);
last_dirty_line = max(line, last_dirty_line);
_cell_array.mark_line_as_clean(line);
}
int const num_dirty_lines = last_dirty_line - first_dirty_line + 1;
if (num_dirty_lines > 0)
_framebuffer.refresh(Rect(Point(0, first_dirty_line*_char_height),
Area(fb_width,
num_dirty_lines*_char_height)));
}
void apply_character(Character c)
{
/* submit character to sequence decoder */
_decoder.insert(c.c);
}
Session::Size size() const { return Session::Size(_columns, _lines); }
};
#endif /* _TEXT_SCREEN_SURFACE_H_ */

View File

@ -0,0 +1,41 @@
/*
* \brief Common types used by the terminal
* \author Norman Feske
* \date 2018-02-06
*/
/*
* Copyright (C) 2018 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.
*/
#ifndef _TYPES_H_
#define _TYPES_H_
/* Genode includes */
#include <util/interface.h>
#include <util/list.h>
#include <base/registry.h>
#include <os/surface.h>
#include <terminal_session/terminal_session.h>
/* terminal includes */
#include <terminal/types.h>
namespace Terminal {
using namespace Genode;
typedef Surface_base::Rect Rect;
typedef Surface_base::Area Area;
typedef Surface_base::Point Point;
struct Character_consumer : Interface
{
virtual void consume_character(Character c) = 0;
};
}
#endif /* _TYPES_H_ */

View File

@ -220,7 +220,7 @@ class Terminal::Session_component : public Genode::Rpc_object<Session, Session_c
_label(label), _label(label),
_session_manager(session_manager), _session_manager(session_manager),
_io_buffer(_env.ram(), _env.rm(), io_buffer_size), _io_buffer(_env.ram(), _env.rm(), io_buffer_size),
_char_cell_array(ncurses.columns(), ncurses.lines() - 1, &heap), _char_cell_array(ncurses.columns(), ncurses.lines() - 1, heap),
_char_cell_array_character_screen(_char_cell_array), _char_cell_array_character_screen(_char_cell_array),
_decoder(_char_cell_array_character_screen) _decoder(_char_cell_array_character_screen)
{ {

View File

@ -30,11 +30,17 @@ class Cell_array
{ {
private: private:
/*
* Noncopyable
*/
Cell_array(Cell_array const &);
Cell_array &operator = (Cell_array const &);
unsigned _num_cols; unsigned _num_cols;
unsigned _num_lines; unsigned _num_lines;
Genode::Allocator *_alloc; Genode::Allocator &_alloc;
CELL **_array; CELL **_array = nullptr;
bool *_line_dirty; bool *_line_dirty = nullptr;
typedef CELL *Char_cell_line; typedef CELL *Char_cell_line;
@ -73,7 +79,7 @@ class Cell_array
public: public:
Cell_array(unsigned num_cols, unsigned num_lines, Cell_array(unsigned num_cols, unsigned num_lines,
Genode::Allocator *alloc) Genode::Allocator &alloc)
: :
_num_cols(num_cols), _num_cols(num_cols),
_num_lines(num_lines), _num_lines(num_lines),

View File

@ -24,10 +24,10 @@ struct Char_cell
unsigned char ascii; unsigned char ascii;
unsigned char color; unsigned char color;
enum { ATTR_COLIDX_MASK = 0x07, enum { ATTR_COLIDX_MASK = 0x07U,
ATTR_CURSOR = 0x10, ATTR_CURSOR = 0x10U,
ATTR_INVERSE = 0x20, ATTR_INVERSE = 0x20U,
ATTR_HIGHLIGHT = 0x40 }; ATTR_HIGHLIGHT = 0x40U };
enum { COLOR_MASK = 0x3f }; /* 111111 */ enum { COLOR_MASK = 0x3f }; /* 111111 */
@ -47,8 +47,8 @@ struct Char_cell
return Font_face((Font_face::Type)(attr & Font_face::attr_mask())); return Font_face((Font_face::Type)(attr & Font_face::attr_mask()));
} }
int colidx_fg() const { return color & ATTR_COLIDX_MASK; } unsigned colidx_fg() const { return color & ATTR_COLIDX_MASK; }
int colidx_bg() const { return (color >> 3) & ATTR_COLIDX_MASK; } unsigned colidx_bg() const { return (color >> 3) & ATTR_COLIDX_MASK; }
bool inverse() const { return attr & ATTR_INVERSE; } bool inverse() const { return attr & ATTR_INVERSE; }
bool highlight() const { return attr & ATTR_HIGHLIGHT; } bool highlight() const { return attr & ATTR_HIGHLIGHT; }
@ -69,7 +69,8 @@ class Char_cell_array_character_screen : public Terminal::Character_screen
Cell_array<Char_cell> &_char_cell_array; Cell_array<Char_cell> &_char_cell_array;
Terminal::Boundary _boundary; Terminal::Boundary _boundary;
Terminal::Position _cursor_pos; Terminal::Position _cursor_pos { };
/** /**
* Color index contains the fg color in the first 3 bits * Color index contains the fg color in the first 3 bits
* and the bg color in the second 3 bits (0bbbbfff). * and the bg color in the second 3 bits (0bbbbfff).

View File

@ -22,7 +22,7 @@ namespace Terminal { struct Character_screen; }
/** /**
* Character-screen interface called by input-stream decoder * Character-screen interface called by input-stream decoder
*/ */
struct Terminal::Character_screen struct Terminal::Character_screen : Genode::Interface
{ {
virtual void output(Character c) = 0; virtual void output(Character c) = 0;

View File

@ -140,7 +140,7 @@ class Terminal::Decoder
return (index <= _index) ? _entries[index] : Invalid_entry(); return (index <= _index) ? _entries[index] : Invalid_entry();
} }
} _escape_stack; } _escape_stack { };
enum State { enum State {
STATE_IDLE, STATE_IDLE,

View File

@ -30,54 +30,14 @@ class Terminal::Read_buffer : public Genode::Ring_buffer<unsigned char, READ_BUF
{ {
private: private:
Genode::Signal_context_capability _sigh_cap; Genode::Signal_context_capability _sigh_cap { };
public: public:
/** /**
* Register signal handler for read-avail signals * Register signal handler for read-avail signals
*/ */
void sigh(Genode::Signal_context_capability cap) void sigh(Genode::Signal_context_capability cap) { _sigh_cap = cap; }
{
_sigh_cap = cap;
}
/**
* Add element into read buffer and emit signal
*/
void add(unsigned char c)
{
Genode::Ring_buffer<unsigned char, READ_BUFFER_SIZE>::add(c);
if (_sigh_cap.valid())
Genode::Signal_transmitter(_sigh_cap).submit();
}
void add(char const *str)
{
while (*str)
add(*str++);
}
};
enum { READ_BUFFER_SIZE = 4096 };
class Read_buffer : public Genode::Ring_buffer<unsigned char, READ_BUFFER_SIZE>
{
private:
Genode::Signal_context_capability _sigh_cap;
public:
/**
* Register signal handler for read-avail signals
*/
void sigh(Genode::Signal_context_capability cap)
{
_sigh_cap = cap;
}
/** /**
* Add element into read buffer and emit signal * Add element into read buffer and emit signal

View File

@ -14,6 +14,9 @@
#ifndef _TERMINAL__TYPES_H_ #ifndef _TERMINAL__TYPES_H_
#define _TERMINAL__TYPES_H_ #define _TERMINAL__TYPES_H_
/* Genode includes */
#include <util/interface.h>
namespace Terminal { namespace Terminal {
struct Character; struct Character;
@ -84,7 +87,7 @@ struct Terminal::Position
}; };
struct Terminal::Character_array struct Terminal::Character_array : Genode::Interface
{ {
/** /**
* Assign character to specified position * Assign character to specified position