mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-21 10:01:57 +00:00
parent
6399fc12ac
commit
fc7b983a40
@ -5,5 +5,6 @@ input_session
|
||||
nitpicker_gfx
|
||||
terminal_session
|
||||
timer_session
|
||||
report_session
|
||||
vfs
|
||||
gems
|
||||
|
@ -1,14 +1,14 @@
|
||||
This is a graphical terminal implementation. It provides the Terminal
|
||||
service and uses a nitpicker session for screen representation.
|
||||
|
||||
Configuration
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Color configuration
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The default color palette can be configured via the <palette> XML
|
||||
configuration node like follows. There are 16 colors configurable -
|
||||
index 0-7 normal color and index 8-15 bright (bold) colors.
|
||||
|
||||
|
||||
! <config>
|
||||
! <palette>
|
||||
! <color index="0" value="#000000"/> <!-- black is real black -->
|
||||
@ -17,3 +17,17 @@ index 0-7 normal color and index 8-15 bright (bold) colors.
|
||||
! ...
|
||||
! </config>
|
||||
|
||||
|
||||
Clipboard support
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
With the '<config>' attribute 'copy="yes"' specified, the terminal allows
|
||||
the user to select text to be reported to a "clipboard" report. The selection
|
||||
mode is activated by holding the left shift key. While the selection mode
|
||||
is active, the text position under mouse pointer is highlighted and the
|
||||
user can select text via the left mouse button. Upon release of the mouse
|
||||
button, the selection is reported.
|
||||
|
||||
Vice versa, with the '<config>' attribute 'paste="yes"' specified, the
|
||||
terminal allows the user to paste the content of a "clipboard" ROM session
|
||||
to the terminal client by pressing the middle mouse button.
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
#include <base/attached_ram_dataspace.h>
|
||||
#include <input/event.h>
|
||||
#include <os/reporter.h>
|
||||
#include <gems/vfs.h>
|
||||
#include <gems/vfs_font.h>
|
||||
#include <gems/cached_font.h>
|
||||
@ -72,6 +73,9 @@ struct Terminal::Main : Character_consumer
|
||||
|
||||
Color_palette _color_palette { };
|
||||
|
||||
Constructible<Attached_rom_dataspace> _clipboard_rom { };
|
||||
Constructible<Expanding_reporter> _clipboard_reporter { };
|
||||
|
||||
void _handle_config();
|
||||
|
||||
Signal_handler<Main> _config_handler {
|
||||
@ -82,6 +86,14 @@ struct Terminal::Main : Character_consumer
|
||||
|
||||
Framebuffer _framebuffer { _env, _config_handler };
|
||||
|
||||
Point _pointer { }; /* pointer positon in pixels */
|
||||
|
||||
bool _shift_pressed = false;
|
||||
|
||||
bool _selecting = false;
|
||||
|
||||
struct Paste_buffer { char buffer[READ_BUFFER_SIZE]; } _paste_buffer { };
|
||||
|
||||
typedef Pixel_rgb565 PT;
|
||||
|
||||
Constructible<Text_screen_surface<PT>> _text_screen_surface { };
|
||||
@ -139,6 +151,9 @@ struct Terminal::Main : Character_consumer
|
||||
Signal_handler<Main> _input_handler {
|
||||
_env.ep(), *this, &Main::_handle_input };
|
||||
|
||||
void _report_clipboard_selection();
|
||||
void _paste_clipboard_content();
|
||||
|
||||
Main(Env &env) : _env(env)
|
||||
{
|
||||
_timer .sigh(_flush_handler);
|
||||
@ -170,6 +185,12 @@ void Terminal::Main::_handle_config()
|
||||
|
||||
_font.construct(_heap, _root_dir, cache_limit);
|
||||
|
||||
_clipboard_reporter.conditional(config.attribute_value("copy", false),
|
||||
_env, "clipboard", "clipboard");
|
||||
|
||||
_clipboard_rom.conditional(config.attribute_value("paste", false),
|
||||
_env, "clipboard");
|
||||
|
||||
/*
|
||||
* Adapt terminal to font or framebuffer mode changes
|
||||
*/
|
||||
@ -252,6 +273,56 @@ void Terminal::Main::_handle_input()
|
||||
{
|
||||
_input.for_each_event([&] (Input::Event const &event) {
|
||||
|
||||
event.handle_absolute_motion([&] (int x, int y) {
|
||||
|
||||
_pointer = Point(x, y);
|
||||
|
||||
if (_shift_pressed) {
|
||||
_text_screen_surface->pointer(_pointer);
|
||||
_schedule_flush();
|
||||
}
|
||||
|
||||
if (_selecting) {
|
||||
_text_screen_surface->define_selection(_pointer);
|
||||
_schedule_flush();
|
||||
}
|
||||
});
|
||||
|
||||
if (event.key_press(Input::KEY_LEFTSHIFT)) {
|
||||
if (_clipboard_reporter.constructed()) {
|
||||
_shift_pressed = true;
|
||||
_text_screen_surface->clear_selection();
|
||||
_text_screen_surface->pointer(_pointer);
|
||||
_schedule_flush();
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key_release(Input::KEY_LEFTSHIFT)) {
|
||||
_shift_pressed = false;
|
||||
_text_screen_surface->pointer(Point(-1, -1));
|
||||
_schedule_flush();
|
||||
}
|
||||
|
||||
if (event.key_press(Input::BTN_LEFT)) {
|
||||
if (_shift_pressed) {
|
||||
_selecting = true;
|
||||
_text_screen_surface->start_selection(_pointer);
|
||||
} else {
|
||||
_text_screen_surface->clear_selection();
|
||||
}
|
||||
_schedule_flush();
|
||||
}
|
||||
|
||||
if (event.key_release(Input::BTN_LEFT)) {
|
||||
if (_selecting) {
|
||||
_selecting = false;
|
||||
_report_clipboard_selection();
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key_press(Input::BTN_MIDDLE))
|
||||
_paste_clipboard_content();
|
||||
|
||||
event.handle_press([&] (Input::Keycode, Codepoint codepoint) {
|
||||
|
||||
/* function-key unicodes */
|
||||
@ -304,4 +375,56 @@ void Terminal::Main::_handle_input()
|
||||
}
|
||||
|
||||
|
||||
void Terminal::Main::_report_clipboard_selection()
|
||||
{
|
||||
if (!_clipboard_reporter.constructed())
|
||||
return;
|
||||
|
||||
_clipboard_reporter->generate([&] (Xml_generator &xml) {
|
||||
_text_screen_surface->for_each_selected_character([&] (Codepoint c) {
|
||||
String<10> const utf8(c);
|
||||
if (utf8.valid())
|
||||
xml.append_sanitized(utf8.string(), utf8.length() - 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Terminal::Main::_paste_clipboard_content()
|
||||
{
|
||||
if (!_clipboard_rom.constructed())
|
||||
return;
|
||||
|
||||
_clipboard_rom->update();
|
||||
|
||||
_paste_buffer = { };
|
||||
|
||||
/* leave last byte as zero-termination in tact */
|
||||
size_t const max_len = sizeof(_paste_buffer.buffer) - 1;
|
||||
size_t const len =
|
||||
_clipboard_rom->xml().decoded_content(_paste_buffer.buffer, max_len);
|
||||
|
||||
if (len == max_len) {
|
||||
warning("clipboard content exceeds paste buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
if (len >= (size_t)_read_buffer.avail_capacity()) {
|
||||
warning("clipboard content exceeds read-buffer capacity");
|
||||
return;
|
||||
}
|
||||
|
||||
for (Utf8_ptr utf8(_paste_buffer.buffer); utf8.complete(); utf8 = utf8.next()) {
|
||||
|
||||
Codepoint const c = utf8.codepoint();
|
||||
|
||||
/* filter out control characters */
|
||||
if (c.value < 32 && c.value != 10)
|
||||
continue;
|
||||
|
||||
_read_buffer.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Component::construct(Genode::Env &env) { static Terminal::Main main(env); }
|
||||
|
@ -91,6 +91,17 @@ class Terminal::Text_screen_surface
|
||||
Point start() const { return Point(1, 1); }
|
||||
|
||||
bool valid() const { return columns*lines > 0; }
|
||||
|
||||
/**
|
||||
* Return character position at given pixel coordinates
|
||||
*/
|
||||
Position position(Point p) const
|
||||
{
|
||||
if (char_width.value == 0 || char_height == 0)
|
||||
return Position { };
|
||||
|
||||
return Position((p.x() << 8) / char_width.value, p.y() / char_height);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -133,6 +144,29 @@ class Terminal::Text_screen_surface
|
||||
|
||||
Decoder _decoder { _character_screen };
|
||||
|
||||
struct Selection
|
||||
{
|
||||
Position start { };
|
||||
Position end { };
|
||||
|
||||
bool defined = false;
|
||||
|
||||
bool selected(Position pos) const
|
||||
{
|
||||
return defined && pos.in_range(start, end);
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void for_each_line(FN const &fn) const
|
||||
{
|
||||
for (int i = min(start.y, end.y); i <= max(start.y, end.y); i++)
|
||||
fn(i);
|
||||
}
|
||||
|
||||
} _selection { };
|
||||
|
||||
Position _pointer { -1, -1 };
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
@ -199,7 +233,20 @@ class Terminal::Text_screen_surface
|
||||
|
||||
Char_cell const cell = _cell_array.get_cell(column, line);
|
||||
|
||||
_font.apply_glyph(cell.codepoint(), [&] (Glyph_painter::Glyph const &glyph) {
|
||||
Codepoint codepoint = cell.codepoint();
|
||||
|
||||
/* display absent codepoints as whitespace */
|
||||
bool const codepoint_valid = (codepoint.value != 0);
|
||||
|
||||
bool const selected = _selection.selected(Position(column, line))
|
||||
&& codepoint_valid;
|
||||
|
||||
bool const pointer = (_pointer == Position(column, line));
|
||||
|
||||
if (!codepoint_valid)
|
||||
codepoint = Codepoint{' '};
|
||||
|
||||
_font.apply_glyph(codepoint, [&] (Glyph_painter::Glyph const &glyph) {
|
||||
|
||||
Color_palette::Highlighted const highlighted { cell.highlight() };
|
||||
|
||||
@ -216,6 +263,16 @@ class Terminal::Text_screen_surface
|
||||
Color fg_color = _palette.foreground(fg_idx, highlighted);
|
||||
Color bg_color = _palette.background(bg_idx, highlighted);
|
||||
|
||||
if (selected) {
|
||||
bg_color = Color(180, 180, 180);
|
||||
fg_color = Color( 50, 50, 50);
|
||||
}
|
||||
|
||||
if (pointer) {
|
||||
bg_color = Color(220, 220, 220);
|
||||
fg_color = Color( 50, 50, 50);
|
||||
}
|
||||
|
||||
if (cell.has_cursor()) {
|
||||
fg_color = Color( 63, 63, 63);
|
||||
bg_color = Color(255, 255, 255);
|
||||
@ -271,6 +328,8 @@ class Terminal::Text_screen_surface
|
||||
|
||||
void apply_character(Character c)
|
||||
{
|
||||
clear_selection();
|
||||
|
||||
/* submit character to sequence decoder */
|
||||
_decoder.insert(c);
|
||||
}
|
||||
@ -284,6 +343,110 @@ class Terminal::Text_screen_surface
|
||||
* Return size in colums/rows
|
||||
*/
|
||||
Area size() const { return _geometry.size(); }
|
||||
|
||||
/**
|
||||
* Set pointer position in pixels (to show the cursor)
|
||||
*/
|
||||
void pointer(Point pointer)
|
||||
{
|
||||
auto position_valid = [&] (Position pos) {
|
||||
return pos.y >= 0 && pos.y < (int)_geometry.lines; };
|
||||
|
||||
/* update old position */
|
||||
if (position_valid(_pointer))
|
||||
_cell_array.mark_line_as_dirty(_pointer.y);
|
||||
|
||||
_pointer = _geometry.position(pointer);
|
||||
|
||||
/* update new position */
|
||||
if (position_valid(_pointer))
|
||||
_cell_array.mark_line_as_dirty(_pointer.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set anchor point of selection
|
||||
*
|
||||
* \param pointer pointer position in pixels
|
||||
*/
|
||||
void start_selection(Point pointer)
|
||||
{
|
||||
if (_selection.defined)
|
||||
clear_selection();
|
||||
|
||||
_selection.start = _geometry.position(pointer);
|
||||
|
||||
define_selection(pointer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set end position of current selection
|
||||
*
|
||||
* \param pointer pointer position in pixels
|
||||
*/
|
||||
void define_selection(Point pointer)
|
||||
{
|
||||
_selection.for_each_line([&] (int line) {
|
||||
_cell_array.mark_line_as_dirty(line); });
|
||||
|
||||
_selection.end = _geometry.position(pointer);
|
||||
_selection.defined = true;
|
||||
|
||||
_selection.for_each_line([&] (int line) {
|
||||
_cell_array.mark_line_as_dirty(line); });
|
||||
}
|
||||
|
||||
void clear_selection()
|
||||
{
|
||||
if (!_selection.defined)
|
||||
return;
|
||||
|
||||
_selection.for_each_line([&] (int line) {
|
||||
_cell_array.mark_line_as_dirty(line); });
|
||||
|
||||
_selection.defined = false;
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void for_each_selected_character(FN const &fn) const
|
||||
{
|
||||
for (unsigned row = 0; row < _geometry.lines; row++) {
|
||||
bool skip_remaining_chars_on_row = false;
|
||||
|
||||
for (unsigned column = 0; column < _geometry.columns; column++) {
|
||||
|
||||
if (skip_remaining_chars_on_row)
|
||||
continue;
|
||||
|
||||
if (!_selection.selected(Position(column, row)))
|
||||
continue;
|
||||
|
||||
Codepoint const c { _cell_array.get_cell(column, row).value };
|
||||
|
||||
if (c.value == 0) {
|
||||
|
||||
auto remaining_line_empty = [&] ()
|
||||
{
|
||||
for (unsigned i = column + 1; i < _geometry.columns; i++)
|
||||
if (_cell_array.get_cell(i, row).value)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/* generate one line break at the end of a selected line */
|
||||
if (remaining_line_empty()) {
|
||||
fn(Codepoint{'\n'});
|
||||
skip_remaining_chars_on_row = true;
|
||||
|
||||
} else {
|
||||
fn(Codepoint{' '});
|
||||
}
|
||||
} else {
|
||||
fn(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _TEXT_SCREEN_SURFACE_H_ */
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
struct Char_cell
|
||||
{
|
||||
Genode::uint16_t value { ' ' };
|
||||
Genode::uint16_t value { 0 };
|
||||
|
||||
unsigned char attr;
|
||||
unsigned char color;
|
||||
|
@ -76,6 +76,23 @@ struct Terminal::Position
|
||||
bool operator != (Position const &pos) const {
|
||||
return (pos.x != x) || (pos.y != y); }
|
||||
|
||||
bool operator >= (Position const &other) const
|
||||
{
|
||||
if (y > other.y)
|
||||
return true;
|
||||
|
||||
if (y == other.y && x >= other.x)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool in_range(Position start, Position end) const
|
||||
{
|
||||
return (end >= start) ? *this >= start && end >= *this
|
||||
: *this >= end && start >= *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if position lies within the specified boundaries
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user