genode/gems/src/server/terminal/main.cc

1420 lines
35 KiB
C++

/*
* \brief Terminal service
* \author Norman Feske
* \date 2011-08-11
*/
/*
* Copyright (C) 2011-2012 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.
*/
/* Genode includes */
#include <base/env.h>
#include <base/printf.h>
#include <base/heap.h>
#include <framebuffer_session/connection.h>
#include <input_session/connection.h>
#include <timer_session/connection.h>
#include <cap_session/connection.h>
#include <root/component.h>
#include <os/ring_buffer.h>
#include <os/attached_ram_dataspace.h>
#include <input/keycodes.h>
#include <input/event.h>
#include <os/config.h>
#include <terminal/decoder.h>
#include <terminal/types.h>
#include <nitpicker_gfx/font.h>
#include <nitpicker_gfx/color.h>
#include <nitpicker_gfx/pixel_rgb565.h>
#include <terminal_session/terminal_session.h>
static bool const verbose = false;
enum { READ_BUFFER_SIZE = 4096 };
class Read_buffer : public 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
*/
void add(unsigned char c)
{
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++);
}
};
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
* 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;
return res;
}
/*
* Font initialization
*/
extern char _binary_mono_tff_start;
static Font mono_font (&_binary_mono_tff_start);
static Font *fonts[4] = { &mono_font, &mono_font,
&mono_font, &mono_font };
enum Font_face { FONT_REGULAR, FONT_ITALIC, FONT_BOLD, FONT_BOLD_ITALIC };
static Color color_palette[2*8];
struct Char_cell
{
unsigned char attr;
unsigned char ascii;
enum { ATTR_COLIDX_MASK = 0x07,
ATTR_CURSOR = 0x10,
ATTR_INVERSE = 0x20,
ATTR_HIGHLIGHT = 0x40 };
Char_cell() : attr(0), ascii(0) { }
Char_cell(unsigned char c, Font_face f, int colidx, bool inv, bool highlight)
:
attr(f | (colidx & ATTR_COLIDX_MASK)
| (inv ? ATTR_INVERSE : 0)
| (highlight ? ATTR_HIGHLIGHT : 0)),
ascii(c)
{ }
Font_face font_face() const { return (Font_face)(attr & 0x3); }
int colidx() const { return attr & ATTR_COLIDX_MASK; }
bool inverse() const { return attr & ATTR_INVERSE; }
bool highlight() const { return attr & ATTR_HIGHLIGHT; }
Color fg_color() const
{
Color col = color_palette[colidx() + (highlight() ? 8 : 0)];
if (inverse())
col = Color(col.r/2, col.g/2, col.b/2);
return col;
}
Color bg_color() const
{
Color col = color_palette[colidx() + (highlight() ? 8 : 0)];
if (inverse())
return Color((col.r + 255)/2, (col.g + 255)/2, (col.b + 255)/2);
else
return Color(0, 0, 0);
}
void set_cursor() { attr |= ATTR_CURSOR; }
void clear_cursor() { attr &= ~ATTR_CURSOR; }
bool has_cursor() const { return attr & ATTR_CURSOR; }
};
class Char_cell_array
{
private:
unsigned _num_cols;
unsigned _num_lines;
Genode::Allocator *_alloc;
Char_cell **_array;
bool *_line_dirty;
typedef Char_cell *Char_cell_line;
void _clear_line(Char_cell_line line)
{
for (unsigned col = 0; col < _num_cols; col++)
*line++ = Char_cell();
}
void _mark_lines_as_dirty(int start, int end)
{
for (int line = start; line <= end; line++)
_line_dirty[line] = true;
}
void _scroll_vertically(int start, int end, bool up)
{
/* rotate lines of the scroll region */
Char_cell_line yanked_line = _array[up ? start : end];
if (up) {
for (int line = start; line <= end - 1; line++)
_array[line] = _array[line + 1];
} else {
for (int line = end; line >= start + 1; line--)
_array[line] = _array[line - 1];
}
_clear_line(yanked_line);
_array[up ? end: start] = yanked_line;
_mark_lines_as_dirty(start, end);
}
public:
Char_cell_array(unsigned num_cols, unsigned num_lines,
Genode::Allocator *alloc)
:
_num_cols(num_cols),
_num_lines(num_lines),
_alloc(alloc)
{
_array = new (alloc) Char_cell_line[num_lines];
_line_dirty = new (alloc) bool[num_lines];
for (unsigned i = 0; i < num_lines; i++)
_line_dirty[i] = false;
for (unsigned i = 0; i < num_lines; i++)
_array[i] = new (alloc) Char_cell[num_cols];
}
void set_cell(int column, int line, Char_cell cell)
{
_array[line][column] = cell;
_line_dirty[line] = true;
}
Char_cell get_cell(int column, int line)
{
return _array[line][column];
}
bool line_dirty(int line) { return _line_dirty[line]; }
void mark_line_as_clean(int line)
{
_line_dirty[line] = false;
}
void scroll_up(int region_start, int region_end)
{
_scroll_vertically(region_start, region_end, true);
}
void scroll_down(int region_start, int region_end)
{
_scroll_vertically(region_start, region_end, false);
}
void clear(int region_start, int region_end)
{
for (int line = region_start; line <= region_end; line++)
_clear_line(_array[line]);
_mark_lines_as_dirty(region_start, region_end);
}
void cursor(Terminal::Position pos, bool enable, bool mark_dirty = false)
{
Char_cell &cell = _array[pos.y][pos.x];
if (enable)
cell.set_cursor();
else
cell.clear_cursor();
if (mark_dirty)
_line_dirty[pos.y] = true;
}
unsigned num_cols() { return _num_cols; }
unsigned num_lines() { return _num_lines; }
};
class Char_cell_array_character_screen : public Terminal::Character_screen
{
private:
enum Cursor_visibility { CURSOR_INVISIBLE,
CURSOR_VISIBLE,
CURSOR_VERY_VISIBLE };
Char_cell_array &_char_cell_array;
Terminal::Boundary _boundary;
Terminal::Position _cursor_pos;
int _color_index;
bool _inverse;
bool _highlight;
Cursor_visibility _cursor_visibility;
int _region_start;
int _region_end;
enum { DEFAULT_COLOR_INDEX = 4 };
struct Cursor_guard
{
Char_cell_array_character_screen &cs;
Terminal::Position old_cursor_pos;
Cursor_guard(Char_cell_array_character_screen &cs)
:
cs(cs), old_cursor_pos(cs._cursor_pos)
{
/* temporarily remove cursor */
cs._char_cell_array.cursor(old_cursor_pos, false);
}
~Cursor_guard()
{
/* restore original cursor */
cs._char_cell_array.cursor(old_cursor_pos, true);
/* if cursor position changed, move cursor */
Terminal::Position &new_cursor_pos = cs._cursor_pos;
if (old_cursor_pos != new_cursor_pos) {
cs._char_cell_array.cursor(old_cursor_pos, false, true);
cs._char_cell_array.cursor(new_cursor_pos, true, true);
}
}
};
public:
Char_cell_array_character_screen(Char_cell_array &char_cell_array)
:
_char_cell_array(char_cell_array),
_boundary(_char_cell_array.num_cols(), _char_cell_array.num_lines()),
_color_index(DEFAULT_COLOR_INDEX),
_inverse(false),
_highlight(false),
_cursor_visibility(CURSOR_VISIBLE),
_region_start(0),
_region_end(_boundary.height - 1)
{ }
void _line_feed()
{
Cursor_guard guard(*this);
_cursor_pos.y++;
if (_cursor_pos.y > _region_end) {
_char_cell_array.scroll_up(_region_start, _region_end);
_cursor_pos.y = _region_end;
}
}
void _carriage_return()
{
Cursor_guard guard(*this);
_cursor_pos.x = 0;
}
/********************************
** Character_screen interface **
********************************/
Terminal::Position cursor_pos() const { return _cursor_pos; }
void output(Terminal::Character c)
{
if (c.ascii() > 0x10) {
Cursor_guard guard(*this);
_char_cell_array.set_cell(_cursor_pos.x, _cursor_pos.y,
Char_cell(c.ascii(), FONT_REGULAR,
_color_index, _inverse, _highlight));
_cursor_pos.x++;
}
switch (c.ascii()) {
case '\r': /* 13 */
_carriage_return();
break;
case '\n': /* 10 */
_line_feed();
_carriage_return();
break;
case 8: /* backspace */
{
Cursor_guard guard(*this);
if (_cursor_pos.x > 0)
_cursor_pos.x--;
break;
}
default:
break;
}
if (_cursor_pos.x >= _boundary.width) {
_carriage_return();
_line_feed();
}
}
void civis()
{
_cursor_visibility = CURSOR_INVISIBLE;
}
void cnorm()
{
_cursor_visibility = CURSOR_VISIBLE;
}
void cvvis()
{
_cursor_visibility = CURSOR_VERY_VISIBLE;
}
void cpr()
{
PDBG("not implemented");
}
void csr(int start, int end)
{
/* the arguments are specified use coordinate origin (1, 1) */
start--;
end--;
_region_start = Genode::max(start, 0);
_region_end = Genode::min(end, _boundary.height - 1);
/* preserve invariant of region size >= 0 */
_region_end = Genode::max(_region_end, _region_start);
}
void cuf(int dx)
{
Cursor_guard guard(*this);
_cursor_pos.x += dx;
_cursor_pos.x = Genode::min(_boundary.width - 1, _cursor_pos.x);
}
void cup(int y, int x)
{
Cursor_guard guard(*this);
/* top-left cursor position is reported as (1, 1) */
x--;
y--;
using namespace Genode;
x = max(0, min(x, _boundary.width - 1));
y = max(0, min(y, _boundary.height - 1));
_cursor_pos = Terminal::Position(x, y);
}
void cuu1()
{
PWRN("not implemented");
}
void dch(int)
{
PDBG("not implemented");
}
void dl(int num_lines)
{
/* delete number of lines */
for (int i = 0; i < num_lines; i++)
_char_cell_array.scroll_up(_cursor_pos.y, _region_end);
}
void ech(int v)
{
Cursor_guard guard(*this);
for (int i = 0; i < v; i++, _cursor_pos.x++)
_char_cell_array.set_cell(_cursor_pos.x, _cursor_pos.y,
Char_cell(' ', FONT_REGULAR,
_color_index, _inverse, _highlight));
}
void ed()
{
/* clear to end of screen */
_char_cell_array.clear(_cursor_pos.y, _boundary.height - 1);
}
void el()
{
/* clear to end of line */
for (int x = _cursor_pos.x; x < _boundary.width; x++)
_char_cell_array.set_cell(x, _cursor_pos.y, Char_cell());
}
void el1()
{
PDBG("not implemented");
}
void home()
{
Cursor_guard guard(*this);
_cursor_pos = Terminal::Position(0, 0);
}
void hpa(int x)
{
Cursor_guard guard(*this);
_cursor_pos.x = x;
_cursor_pos.x = Genode::min(_boundary.width - 1, _cursor_pos.x);
}
void hts()
{
PDBG("not implemented");
}
void ich(int)
{
PDBG("not implemented");
}
void il(int value)
{
Cursor_guard guard(*this);
if (_cursor_pos.y > _region_end)
return;
_char_cell_array.cursor(_cursor_pos, false);
for (int i = 0; i < value; i++)
_char_cell_array.scroll_down(_cursor_pos.y, _region_end);
_char_cell_array.cursor(_cursor_pos, true);
}
void oc()
{
PDBG("not implemented");
}
void op()
{
_color_index = DEFAULT_COLOR_INDEX;
}
void rc()
{
PDBG("not implemented");
}
void ri()
{
PDBG("not implemented");
}
void ris()
{
PDBG("not implemented");
}
void rmam()
{
PDBG("not implemented");
}
void rmir()
{
PDBG("not implemented");
}
void setab(int value)
{
_inverse = (value != 0);
}
void setaf(int value)
{
_color_index = value;
}
void sgr(int value)
{
_highlight = (value & 0x1) != 0;
_inverse = (value & 0x2) != 0;
/* sgr 0 is the command to reset all attributes, including color */
if (value == 0)
_color_index = DEFAULT_COLOR_INDEX;
}
void sc()
{
PDBG("not implemented");
}
void smam()
{
PDBG("not implemented");
}
void smir()
{
PDBG("not implemented");
}
void tbc()
{
PDBG("not implemented");
}
void u6(int, int)
{
PDBG("not implemented");
}
void u7()
{
PDBG("not implemented");
}
void u8()
{
PDBG("not implemented");
}
void u9()
{
PDBG("not implemented");
}
void vpa(int y)
{
Cursor_guard guard(*this);
_cursor_pos.y = y;
_cursor_pos.y = Genode::min(_boundary.height - 1, _cursor_pos.y);
}
};
template <typename PT>
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
- 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] = 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(Char_cell_array *cell_array,
PT *fb_base,
unsigned fb_width,
unsigned fb_height)
{
unsigned glyph_height = mono_font.img_h,
glyph_step_x = mono_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::printf("convert line %d\n", line);
unsigned x = 0;
for (unsigned column = 0; column < cell_array->num_cols(); column++) {
Char_cell cell = cell_array->get_cell(column, line);
Font *font = fonts[cell.font_face()];
unsigned char ascii = cell.ascii;
if (ascii == 0)
ascii = ' ';
unsigned char *glyph_base = font->img + font->otab[ascii];
unsigned glyph_width = mono_font.wtab[ascii];
if (x + glyph_width >= fb_width) break;
Color fg_color = cell.fg_color();
Color bg_color = cell.bg_color();
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;
};
class Session_component : public Genode::Rpc_object<Session, Session_component>,
public Flush_callback
{
private:
Read_buffer *_read_buffer;
Framebuffer::Session *_framebuffer;
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;
Char_cell_array _char_cell_array;
Char_cell_array_character_screen _char_cell_array_character_screen;
Terminal::Decoder _decoder;
Terminal::Position _last_cursor_pos;
/**
* Initialize framebuffer-related attributes
*/
Genode::Dataspace_capability _init_fb()
{
if (_fb_mode.format() != Framebuffer::Mode::RGB565) {
PERR("Color mode %d not supported", _fb_mode.format());
return Genode::Dataspace_capability();
}
return _framebuffer->dataspace();
}
public:
/**
* Constructor
*/
Session_component(Read_buffer *read_buffer,
Framebuffer::Session *framebuffer,
Genode::size_t io_buffer_size)
: _read_buffer(read_buffer), _framebuffer(framebuffer),
_io_buffer(Genode::env()->ram_session(), io_buffer_size),
_fb_mode(_framebuffer->mode()),
_fb_ds_cap(_init_fb()),
/* take size of space character as character cell size */
_char_width(mono_font.str_w("m")),
_char_height(mono_font.str_h("m")),
/* compute number of characters fitting on the framebuffer */
_columns(_fb_mode.width()/_char_width),
_lines(_fb_mode.height()/_char_height),
_fb_addr(Genode::env()->rm_session()->attach(_fb_ds_cap)),
_char_cell_array(_columns, _lines, Genode::env()->heap()),
_char_cell_array_character_screen(_char_cell_array),
_decoder(_char_cell_array_character_screen)
{
using namespace Genode;
printf("new terminal session:\n");
printf(" framebuffer has %dx%d pixels\n", _fb_mode.width(), _fb_mode.height());
printf(" character size is %dx%d pixels\n", _char_width, _char_height);
printf(" terminal size is %dx%d characters\n", _columns, _lines);
framebuffer->refresh(0, 0, _fb_mode.width(), _fb_mode.height());
}
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());
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;
_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);
do {
dst[num_bytes++] = _read_buffer->get();;
} while (!_read_buffer->empty() && num_bytes < dst_size);
return num_bytes;
}
void _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++) {
if (verbose)
Genode::printf("%c (%d)\n", src[i], (int)src[i]);
/* submit character to sequence decoder */
_decoder.insert(src[i]);
}
}
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; }
};
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 flush()
{
Genode::Lock::Guard guard(_lock);
Flush_callback *curr = _list.first();
for (; curr; curr = curr->next())
curr->flush();
}
};
class Root_component : public Genode::Root_component<Session_component>
{
private:
Read_buffer *_read_buffer;
Framebuffer::Session *_framebuffer;
Flush_callback_registry &_flush_callback_registry;
protected:
Session_component *_create_session(const char *args)
{
Genode::printf("create terminal session\n");
/*
* XXX read I/O buffer size from args
*/
Genode::size_t io_buffer_size = 4096;
Session_component *session =
new (md_alloc()) Session_component(_read_buffer,
_framebuffer,
io_buffer_size);
_flush_callback_registry.add(session);
return session;
}
public:
/**
* Constructor
*/
Root_component(Genode::Rpc_entrypoint *ep,
Genode::Allocator *md_alloc,
Read_buffer *read_buffer,
Framebuffer::Session *framebuffer,
Flush_callback_registry &flush_callback_registry)
:
Genode::Root_component<Session_component>(ep, md_alloc),
_read_buffer(read_buffer), _framebuffer(framebuffer),
_flush_callback_registry(flush_callback_registry)
{ }
};
}
enum {
BS = 8,
ESC = 27,
TAB = 9,
LF = 10,
UE = 252, /* 'ü' */
AE = 228, /* 'ä' */
OE = 246, /* 'ö' */
PAR = 167, /* '§' */
DEG = 176, /* '°' */
SS = 223, /* 'ß' */
};
static unsigned char usenglish_keymap[128] = {
0 ,ESC,'1','2','3','4','5','6','7','8','9','0','-','=', BS,TAB,
'q','w','e','r','t','y','u','i','o','p','[','}', LF, 0 ,'a','s',
'd','f','g','h','j','k','l',';','\'','`', 0 , 0 ,'z','x','c','v',
'b','n','m',',','.','/', 0 , 0 , 0 ,' ', 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 ,'7','8','9','-','4','5','6','+','1',
'2','3','0',',', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
LF, 0 ,'/', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
};
/**
* Mapping from ASCII value to another ASCII value when shift is pressed
*
* The table does not contain mappings for control characters. The table
* entry 0 corresponds to ASCII value 32.
*/
static unsigned char usenglish_shift[256 - 32] = {
/* 32 */ ' ', 0 , 0, 0 , 0 , 0 , 0 ,'"', 0 , 0 , 0 , 0 ,'<','_','>','?',
/* 48 */ ')','!','@','#','$','%','^','&','*','(', 0 ,':', 0 ,'+', 0 , 0 ,
/* 64 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 80 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,'{', 0 ,'}', 0 , 0 ,
/* 96 */ '~','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
/* 112 */ 'P','Q','R','S','T','U','V','W','X','Y','Z', 0 ,'\\', 0 , 0 , 0 ,
};
static unsigned char german_keymap[128] = {
0 ,ESC,'1','2','3','4','5','6','7','8','9','0', SS, 39, BS,TAB,
'q','w','e','r','t','z','u','i','o','p', UE,'+', LF, 0 ,'a','s',
'd','f','g','h','j','k','l', OE, AE,'^', 0 ,'#','y','x','c','v',
'b','n','m',',','.','-', 0 ,'*', 0 ,' ', 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 ,'7','8','9','-','4','5','6','+','1',
'2','3','0',',', 0 , 0 ,'<', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
LF, 0 ,'/', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
};
/**
* Mapping from ASCII value to another ASCII value when shift is pressed
*
* The table does not contain mappings for control characters. The table
* entry 0 corresponds to ASCII value 32.
*/
static unsigned char german_shift[256 - 32] = {
/* 32 */ ' ', 0 , 0, 39 , 0 , 0 , 0 ,'`', 0 , 0 , 0 ,'*',';','_',':', 0 ,
/* 48 */ '=','!','"',PAR,'$','%','&','/','(',')', 0 , 0 ,'>', 0 , 0 , 0 ,
/* 64 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 80 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,DEG, 0 ,
/* 96 */ 0 ,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
/* 112 */ 'P','Q','R','S','T','U','V','W','X','Y','Z', 0 , 0 , 0 , 0 , 0 ,
/* 128 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 144 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 160 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 176 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 192 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 208 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,'?',
};
/**
* Mapping from ASCII value to another ASCII value when altgr is pressed
*/
static unsigned char german_altgr[256 - 32] = {
/* 32 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,'~', 0 , 0 , 0 , 0 ,
/* 48 */'}', 0 ,178,179, 0 , 0 , 0 ,'{','[',']', 0 , 0 ,'|', 0 , 0 , 0 ,
/* 64 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 80 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 96 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 112 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 128 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 144 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 160 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 176 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 192 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
/* 208 */ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,'\\',
};
/**
* Mapping from ASCII value to value reported when control is pressed
*
* The table starts with ASCII value 32.
*/
static unsigned char control[256 - 32] = {
/* 32 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 48 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 64 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 96 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
/* 112 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0,
};
/**
* State machine that translates keycode sequences to terminal characters
*/
class Scancode_tracker
{
private:
/**
* Tables containing the scancode-to-character mapping
*/
unsigned char const *_keymap;
unsigned char const *_shift;
unsigned char const *_altgr;
/**
* Current state of modifer keys
*/
bool _mod_shift;
bool _mod_control;
bool _mod_altgr;
/**
* Currently pressed key, or 0 if no normal key (one that can be
* encoded in a single 'char') is pressed
*/
unsigned char _last_character;
/**
* Currently pressed special key (a key that corresponds to an escape
* sequence), or no if no special key is pressed
*/
char const *_last_sequence;
/**
* Convert keycode to terminal character
*/
unsigned char _keycode_to_latin1(int keycode)
{
if (keycode >= 112) return 0;
unsigned ch = _keymap[keycode];
if (ch < 32)
return ch;
/* all ASCII-to-ASCII table start at index 32 */
if (_mod_shift || _mod_control || _mod_altgr) {
ch -= 32;
/*
* 'ch' is guaranteed to be in the range 0..223. So it is safe to
* use it as index into the ASCII-to-ASCII tables.
*/
if (_mod_shift)
return _shift[ch];
if (_mod_control)
return control[ch];
if (_altgr && _mod_altgr)
return _altgr[ch];
}
return ch;
}
public:
/**
* Constructor
*
* \param keymap table for keycode-to-character mapping
* \param shift table for character-to-character mapping used when
* Shift is pressed
* \param altgr table for character-to-character mapping with AltGr
* is pressed
*/
Scancode_tracker(unsigned char const *keymap,
unsigned char const *shift,
unsigned char const *altgr)
:
_keymap(keymap),
_shift(shift),
_altgr(altgr),
_mod_shift(false),
_mod_control(false),
_mod_altgr(false),
_last_character(0),
_last_sequence(0)
{ };
/**
* Submit key event to state machine
*
* \param press true on press event, false on release event
*/
void submit(int keycode, bool press)
{
/* track modifier keys */
switch (keycode) {
case Input::KEY_LEFTSHIFT:
case Input::KEY_RIGHTSHIFT:
_mod_shift = press;
break;
case Input::KEY_LEFTCTRL:
case Input::KEY_RIGHTCTRL:
_mod_control = press;
break;
case Input::KEY_RIGHTALT:
_mod_altgr = press;
default:
break;
}
/* reset information about the currently pressed key */
_last_character = 0;
_last_sequence = 0;
if (!press) return;
/* convert key codes to ASCII */
_last_character = _keycode_to_latin1(keycode);
/* handle special key to be represented by an escape sequence */
if (!_last_character) {
switch (keycode) {
case Input::KEY_DOWN: _last_sequence = "\E[B"; break;
case Input::KEY_UP: _last_sequence = "\E[A"; break;
case Input::KEY_RIGHT: _last_sequence = "\E[C"; break;
case Input::KEY_LEFT: _last_sequence = "\E[D"; break;
case Input::KEY_HOME: _last_sequence = "\E[1~"; break;
case Input::KEY_INSERT: _last_sequence = "\E[2~"; break;
case Input::KEY_DELETE: _last_sequence = "\E[3~"; break;
case Input::KEY_END: _last_sequence = "\E[4~"; break;
case Input::KEY_PAGEUP: _last_sequence = "\E[5~"; break;
case Input::KEY_PAGEDOWN: _last_sequence = "\E[6~"; break;
case Input::KEY_F1: _last_sequence = "\E[[A"; break;
case Input::KEY_F2: _last_sequence = "\E[[B"; break;
case Input::KEY_F3: _last_sequence = "\E[[C"; break;
case Input::KEY_F4: _last_sequence = "\E[[D"; break;
case Input::KEY_F5: _last_sequence = "\E[[E"; break;
case Input::KEY_F6: _last_sequence = "\E[17~"; break;
case Input::KEY_F7: _last_sequence = "\E[18~"; break;
case Input::KEY_F8: _last_sequence = "\E[19~"; break;
case Input::KEY_F9: _last_sequence = "\E[20~"; break;
case Input::KEY_F10: _last_sequence = "\E[21~"; break;
case Input::KEY_F11: _last_sequence = "\E[23~"; break;
case Input::KEY_F12: _last_sequence = "\E[24~"; break;
}
}
}
/**
* Output currently pressed key to read buffer
*/
void emit_current_character(Read_buffer &read_buffer)
{
if (_last_character)
read_buffer.add(_last_character);
if (_last_sequence)
read_buffer.add(_last_sequence);
}
/**
* Return true if there is a currently pressed key
*/
bool valid() const
{
return (_last_sequence || _last_character);
}
};
int main(int, char **)
{
using namespace Genode;
PDBG("--- terminal service started ---");
static Framebuffer::Connection framebuffer;
static Input::Connection input;
static Timer::Connection timer;
static Cap_connection cap;
Dataspace_capability ev_ds_cap = input.dataspace();
Input::Event *ev_buf = static_cast<Input::Event *>
(env()->rm_session()->attach(ev_ds_cap));
/* initialize entry point that serves the root interface */
enum { STACK_SIZE = 4096 };
static Rpc_entrypoint ep(&cap, STACK_SIZE, "terminal_ep");
static Sliced_heap sliced_heap(env()->ram_session(), env()->rm_session());
/* input read buffer */
static Read_buffer read_buffer;
/* initialize color palette */
color_palette[0] = Color(100, 100, 100);
color_palette[1] = Color(0, 0, 224);
color_palette[2] = Color(0,255, 0);
color_palette[3] = Color(0,255,255);
color_palette[4] = Color(224, 224, 224);
color_palette[5] = Color(255, 0, 255);
color_palette[6] = Color(255, 255, 0);
color_palette[7] = Color(255, 0, 0);
/* 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;
}
static Terminal::Flush_callback_registry flush_callback_registry;
/* create root interface for service */
static Terminal::Root_component root(&ep, &sliced_heap,
&read_buffer, &framebuffer,
flush_callback_registry);
/* announce service at our parent */
env()->parent()->announce(ep.manage(&root));
/* state needed for key-repeat handling */
static int const repeat_delay = 170;
static int const repeat_rate = 25;
static int repeat_cnt = 0;
unsigned char *keymap = usenglish_keymap;
unsigned char *shift = usenglish_shift;
unsigned char *altgr = 0;
/*
* Read keyboard layout from config file
*/
try {
using namespace Genode;
if (config()->xml_node().sub_node("keyboard")
.attribute("layout").has_value("de")) {
keymap = german_keymap;
shift = german_shift;
altgr = german_altgr;
}
} catch (...) { }
static Scancode_tracker scancode_tracker(keymap, shift, altgr);
while (1) {
flush_callback_registry.flush();
while (!input.is_pending()) {
enum { PASSED_MSECS = 10 };
timer.msleep(PASSED_MSECS);
flush_callback_registry.flush();
if (scancode_tracker.valid()) {
repeat_cnt -= PASSED_MSECS;
if (repeat_cnt < 0) {
/* repeat current character or sequence */
scancode_tracker.emit_current_character(read_buffer);
/* reset repeat counter according to repeat rate */
repeat_cnt = repeat_rate;
}
}
}
unsigned num_events = input.flush();
for (Input::Event *event = ev_buf; num_events--; event++) {
bool press = (event->type() == Input::Event::PRESS ? true : false);
bool release = (event->type() == Input::Event::RELEASE ? true : false);
int keycode = event->keycode();
if (press || release)
scancode_tracker.submit(keycode, press);
if (press)
scancode_tracker.emit_current_character(read_buffer);
/* setup first key repeat */
repeat_cnt = repeat_delay;
}
}
return 0;
}