text_area: a simple text viewer / editor

The new text_area component is able to view and edit files.
Internally, it employs a menu_view for the graphics output.

Only basic notepad-like text-editing functions are supported.

At the current time, it is solely meant as a companion of the Sculpt
manager.

Issue #3650
This commit is contained in:
Norman Feske 2020-01-12 22:27:41 +01:00 committed by Christian Helmuth
parent b2bc718c1f
commit 5eaaee0dbe
15 changed files with 2407 additions and 0 deletions

View File

@ -0,0 +1,3 @@
SRC_DIR := src/app/text_area
include $(GENODE_DIR)/repos/base/recipes/src/content.inc

View File

@ -0,0 +1 @@
2020-02-12 ca4f34ab1f8c172a367b4c955920a466dbb3ab18

View File

@ -0,0 +1,9 @@
base
os
vfs
report_session
file_system_session
timer_session
nitpicker_session
input_session
framebuffer_session

View File

@ -0,0 +1,152 @@
create_boot_directory
import_from_depot [depot_user]/src/[base_src] \
[depot_user]/pkg/[drivers_interactive_pkg] \
[depot_user]/pkg/fonts_fs \
[depot_user]/src/init \
[depot_user]/src/report_rom \
[depot_user]/src/nitpicker \
[depot_user]/src/libc \
[depot_user]/src/libpng \
[depot_user]/src/zlib \
[depot_user]/src/vfs_import
install_config {
<config>
<parent-provides>
<service name="PD"/>
<service name="CPU"/>
<service name="ROM"/>
<service name="RM"/>
<service name="LOG"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
</parent-provides>
<default caps="100"/>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="drivers" caps="1000">
<resource name="RAM" quantum="32M" constrain_phys="yes"/>
<binary name="init"/>
<route>
<service name="ROM" label="config"> <parent label="drivers.config"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<any-service> <parent/> </any-service>
</route>
<provides>
<service name="Input"/> <service name="Framebuffer"/>
</provides>
</start>
<start name="report_rom">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config verbose="yes">
<policy label="text_area.1 -> hover" report="nitpicker -> hover"/>
<policy label="text_area.2 -> clipboard" report="text_area.2 -> clipboard"/>
</config>
</start>
<start name="nitpicker">
<resource name="RAM" quantum="4M"/>
<provides><service name="Nitpicker"/></provides>
<config focus="rom">
<report hover="yes"/>
<background color="#123456"/>
<domain name="pointer" layer="1" content="client" label="no" origin="pointer" />
<domain name="default" layer="3" content="client" label="no" hover="always" />
<domain name="second" layer="2" xpos="200" ypos="300" content="client" label="no" hover="always" />
<policy label_prefix="pointer" domain="pointer"/>
<policy label_prefix="text_area.2" domain="second"/>
<default-policy domain="default"/>
</config>
</start>
<start name="pointer">
<resource name="RAM" quantum="1M"/>
<route>
<service name="Nitpicker"> <child name="nitpicker" /> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="fonts_fs" caps="300">
<resource name="RAM" quantum="8M"/>
<binary name="vfs"/>
<route>
<service name="ROM" label="config"> <parent label="fonts_fs.config"/> </service>
<any-service> <parent/> </any-service>
</route>
<provides> <service name="File_system"/> </provides>
</start>
<start name="data_fs" caps="300">
<resource name="RAM" quantum="8M"/>
<binary name="vfs"/>
<config>
<vfs>
<ram/>
<import>
<rom name="drivers.config" binary="no"/>
</import>
</vfs>
<default-policy root="/" writeable="yes"/>
</config>
<route> <any-service> <parent/> </any-service> </route>
<provides> <service name="File_system"/> </provides>
</start>
<start name="text_area.1" caps="250">
<binary name="text_area"/>
<resource name="RAM" quantum="8M"/>
<config path="/data/drivers.config" max_lines="20" min_width="500" min_height="300" watch="yes">
<vfs> <dir name="data"> <fs label="data"/> </dir> </vfs>
</config>
<route>
<service name="ROM" label="hover"> <child name="report_rom"/> </service>
<service name="File_system" label="fonts"> <child name="fonts_fs"/> </service>
<service name="File_system" label="data"> <child name="data_fs"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="text_area.2" caps="250">
<binary name="text_area"/>
<resource name="RAM" quantum="8M"/>
<config path="/data/drivers.config" copy="yes" paste="yes"
max_lines="10" min_width="600">
<report saved="yes"/>
<save version="1"/>
<vfs> <dir name="data"> <fs label="data"/> </dir> </vfs>
</config>
<route>
<service name="Report"> <child name="report_rom"/> </service>
<service name="ROM" label="clipboard"> <child name="report_rom"/> </service>
<service name="File_system" label="fonts"> <child name="fonts_fs"/> </service>
<service name="File_system" label="data"> <child name="data_fs"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
</config>}
set fd [open [run_dir]/genode/focus w]
puts $fd "<focus label=\"text_area.2 -> \"/>"
close $fd
build { app/text_area app/menu_view }
build_boot_image { text_area sandbox.lib.so vfs.lib.so menu_view menu_view_styles.tar }
run_genode_until forever

View File

@ -0,0 +1,124 @@
/*
* \brief Runtime state of a child hosted in the runtime subsystem
* \author Norman Feske
* \date 2018-09-03
*/
/*
* 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 _CHILD_STATE_H_
#define _CHILD_STATE_H_
/* Genode includes */
#include <util/xml_node.h>
#include <util/noncopyable.h>
#include <util/string.h>
#include <base/registry.h>
#include <base/quota_guard.h>
/* local includes */
#include "types.h"
namespace Text_area { struct Child_state; }
struct Text_area::Child_state : Noncopyable
{
private:
using Start_name = String<128>;
Registry<Child_state>::Element _element;
Start_name const _name;
Ram_quota const _initial_ram_quota;
Cap_quota const _initial_cap_quota;
Ram_quota _ram_quota = _initial_ram_quota;
Cap_quota _cap_quota = _initial_cap_quota;
struct Version { unsigned value; } _version { 0 };
public:
/**
* Constructor
*
* \param ram_quota initial RAM quota
* \param cap_quota initial capability quota
*/
Child_state(Registry<Child_state> &registry, Start_name const &name,
Ram_quota ram_quota, Cap_quota cap_quota)
:
_element(registry, *this),
_name(name),
_initial_ram_quota(ram_quota), _initial_cap_quota(cap_quota)
{ }
void trigger_restart()
{
_version.value++;
_ram_quota = _initial_ram_quota;
_cap_quota = _initial_cap_quota;
}
void gen_start_node_version(Xml_generator &xml) const
{
if (_version.value)
xml.attribute("version", _version.value);
}
void gen_start_node_content(Xml_generator &xml) const
{
xml.attribute("name", _name);
gen_start_node_version(xml);
xml.attribute("caps", _cap_quota.value);
xml.node("resource", [&] () {
xml.attribute("name", "RAM");
Number_of_bytes const bytes(_ram_quota.value);
xml.attribute("quantum", String<64>(bytes)); });
}
/**
* Adapt runtime state information to the child
*
* This method responds to RAM and cap-resource requests by increasing
* the resource quotas as needed.
*
* \param child child node of the runtime'r state report
* \return true if runtime must be reconfigured so that the changes
* can take effect
*/
bool apply_child_state_report(Xml_node child)
{
bool result = false;
if (child.attribute_value("name", Start_name()) != _name)
return false;
if (child.has_sub_node("ram") && child.sub_node("ram").has_attribute("requested")) {
_ram_quota.value *= 2;
result = true;
}
if (child.has_sub_node("caps") && child.sub_node("caps").has_attribute("requested")) {
_cap_quota.value += 100;
result = true;
}
return result;
}
Ram_quota ram_quota() const { return _ram_quota; }
Start_name name() const { return _name; }
};
#endif /* _CHILD_STATE_H_ */

View File

@ -0,0 +1,734 @@
/*
* \brief Text dialog
* \author Norman Feske
* \date 2020-01-14
*/
/*
* Copyright (C) 2020 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.
*/
/* local includes */
#include <dialog.h>
using namespace Text_area;
enum {
CODEPOINT_BACKSPACE = 8, CODEPOINT_NEWLINE = 10,
CODEPOINT_UP = 0xf700, CODEPOINT_DOWN = 0xf701,
CODEPOINT_LEFT = 0xf702, CODEPOINT_RIGHT = 0xf703,
CODEPOINT_HOME = 0xf729, CODEPOINT_INSERT = 0xf727,
CODEPOINT_DELETE = 0xf728, CODEPOINT_END = 0xf72b,
CODEPOINT_PAGEUP = 0xf72c, CODEPOINT_PAGEDOWN = 0xf72d,
};
static bool movement_codepoint(Codepoint code)
{
auto v = code.value;
return (v == CODEPOINT_UP) || (v == CODEPOINT_DOWN) ||
(v == CODEPOINT_LEFT) || (v == CODEPOINT_RIGHT) ||
(v == CODEPOINT_HOME) || (v == CODEPOINT_END) ||
(v == CODEPOINT_PAGEUP) || (v == CODEPOINT_PAGEDOWN);
}
static bool shift_key(Input::Keycode key)
{
return (key == Input::KEY_LEFTSHIFT) || (key == Input::KEY_RIGHTSHIFT);
}
static bool control_key(Input::Keycode key)
{
return (key == Input::KEY_LEFTCTRL) || (key == Input::KEY_RIGHTCTRL);
}
template <typename T>
static void swap(T &v1, T &v2) { auto tmp = v1; v1 = v2; v2 = tmp; };
template <typename FN>
void Dialog::Selection::for_each_selected_line(FN const &fn) const
{
if (!defined())
return;
unsigned start_y = start->y.value, end_y = end->y.value;
if (end_y < start_y)
swap(start_y, end_y);
if (end_y < start_y)
return;
for (unsigned i = start_y; i <= end_y; i++)
fn(Text::Index { i }, (i == end_y));
}
template <typename FN>
void Dialog::Selection::with_selection_at_line(Text::Index y, Line const &line,
FN const &fn) const
{
if (!defined())
return;
Line::Index start_x = start->x, end_x = end->x;
Text::Index start_y = start->y, end_y = end->y;
if (end_y.value < start_y.value) {
swap(start_x, end_x);
swap(start_y, end_y);
}
if (y.value < start_y.value || y.value > end_y.value)
return;
if (y.value > start_y.value)
start_x = Line::Index { 0 };
if (y.value < end_y.value)
end_x = line.upper_bound();
if (start_x.value > end_x.value)
swap(start_x, end_x);
fn(start_x, end_x.value - start_x.value);
}
void Dialog::Selection::gen_selected_line(Xml_generator &xml,
Text::Index y, Line const &line) const
{
with_selection_at_line(y, line, [&] (Line::Index const start_x, const unsigned n) {
xml.node("selection", [&] () {
xml.attribute("at", start_x.value);
xml.attribute("length", n);
});
});
}
void Dialog::produce_xml(Xml_generator &xml)
{
auto gen_line = [&] (Text::Index at, Line const &line)
{
xml.node("hbox", [&] () {
xml.attribute("name", at.value - _scroll.y.value);
xml.node("float", [&] () {
xml.attribute("north", "yes");
xml.attribute("south", "yes");
xml.attribute("west", "yes");
xml.node("label", [&] () {
xml.attribute("font", "monospace/regular");
xml.attribute("text", String<512>(line));
if (_cursor.y.value == at.value)
xml.node("cursor", [&] () {
xml.attribute("name", "cursor");
xml.attribute("at", _cursor.x.value); });
if (_hovered_position.constructed())
if (_hovered_position->y.value == at.value)
xml.node("cursor", [&] () {
xml.attribute("name", "hover");
xml.attribute("style", "hover");
xml.attribute("at", _hovered_position->x.value); });
_selection.gen_selected_line(xml, at, line);
});
});
});
};
xml.node("frame", [&] () {
xml.node("button", [&] () {
xml.attribute("name", "text");
if (_text_hovered)
xml.attribute("hovered", "yes");
xml.node("float", [&] () {
xml.attribute("north", "yes");
xml.attribute("east", "yes");
xml.attribute("west", "yes");
xml.node("vbox", [&] () {
Dynamic_array<Line>::Range const range { .at = _scroll.y,
.length = _max_lines };
_text.for_each(range, gen_line);
});
});
});
});
}
void Dialog::_delete_selection()
{
if (!_editable)
return;
if (!_selection.defined())
return;
_modification_count++;
/*
* Clear all characters within the selection
*/
unsigned num_lines = 0;
Text::Index first_y { 0 };
_selection.for_each_selected_line([&] (Text::Index const y, bool) {
_text.apply(y, [&] (Line &line) {
_selection.with_selection_at_line(y, line,
[&] (Line::Index x, unsigned n) {
for (unsigned i = 0; i < n; i++) {
line.destruct(Line::Index { x.value });
bool const cursor_right_of_deleted_character =
(_cursor.y.value == y.value) && (_cursor.x.value > x.value);
if (cursor_right_of_deleted_character)
_cursor.x.value--;
}
});
});
if (num_lines == 0)
first_y = y;
num_lines++;
});
/*
* Remove all selected lines, joining the remaining characters at the
* bounds of the selection.
*/
if (num_lines > 1) {
Text::Index const next_y { first_y.value + 1 };
while (--num_lines) {
bool const cursor_at_deleted_line = (_cursor.y.value == next_y.value);
bool const cursor_below_deleted_line = (_cursor.y.value > next_y.value);
_text.apply(first_y, [&] (Line &first) {
if (cursor_at_deleted_line)
_cursor = { .x = first.upper_bound(),
.y = first_y };
_text.apply(next_y, [&] (Line &next) {
_move_characters(next, first); });
});
_text.destruct(next_y);
if (cursor_below_deleted_line)
_cursor.y.value--;
}
}
_selection.clear();
}
void Dialog::_insert_printable(Codepoint code)
{
_tie_cursor_to_end_of_line();
_text.apply(_cursor.y, [&] (Line &line) {
line.insert(_cursor.x, Character(code)); });
_cursor.x.value++;
}
void Dialog::_handle_printable(Codepoint code)
{
if (!_editable)
return;
_modification_count++;
_delete_selection();
_insert_printable(code);
}
void Dialog::_move_characters(Line &from, Line &to)
{
/* move all characters of line 'from' to the end of line 'to' */
Line::Index const first { 0 };
while (from.exists(first)) {
from.apply(first, [&] (Codepoint &code) {
to.append(code); });
from.destruct(first);
}
}
void Dialog::_handle_backspace()
{
if (!_editable)
return;
_modification_count++;
/* eat backspace when deleting a selection */
if (_selection.defined()) {
_delete_selection();
return;
}
if (_cursor.x.value > 0) {
_cursor.x.value--;
_text.apply(_cursor.y, [&] (Line &line) {
line.destruct(_cursor.x); });
return;
}
if (_cursor.y.value == 0)
return;
/* join line with previous line */
Text::Index const prev_y { _cursor.y.value - 1 };
_text.apply(prev_y, [&] (Line &prev_line) {
_cursor.x = prev_line.upper_bound();
_text.apply(_cursor.y, [&] (Line &line) {
_move_characters(line, prev_line); });
});
_text.destruct(_cursor.y);
_cursor.y = prev_y;
}
void Dialog::_handle_delete()
{
if (!_editable)
return;
_modification_count++;
/* eat delete when deleting a selection */
if (_selection.defined()) {
_delete_selection();
return;
}
if (_end_of_text())
return;
_handle_right();
_handle_backspace();
}
void Dialog::_handle_newline()
{
if (!_editable)
return;
_modification_count++;
_delete_selection();
/* create new line at cursor position */
Text::Index const new_y { _cursor.y.value + 1 };
_text.insert(new_y, _alloc);
/* take the characters after the cursor to the new line */
_text.apply(_cursor.y, [&] (Line &line) {
_text.apply(new_y, [&] (Line &new_line) {
while (line.exists(_cursor.x)) {
line.apply(_cursor.x, [&] (Codepoint code) {
new_line.append(code); });
line.destruct(_cursor.x);
}
});
});
_cursor.y = new_y;
_cursor.x.value = 0;
}
void Dialog::_handle_left()
{
_tie_cursor_to_end_of_line();
if (_cursor.x.value == 0) {
if (_cursor.y.value > 0) {
_cursor.y.value--;
_text.apply(_cursor.y, [&] (Line &line) {
_cursor.x = line.upper_bound(); });
}
} else {
_cursor.x.value--;
}
}
void Dialog::_handle_right()
{
if (!_cursor_at_end_of_line()) {
_cursor.x.value++;
return;
}
if (!_cursor_at_last_line()) {
_cursor.x.value = 0;
_cursor.y.value++;
}
}
void Dialog::_handle_up()
{
if (_cursor.y.value > 0)
_cursor.y.value--;
}
void Dialog::_handle_down()
{
if (_cursor.y.value + 1 < _text.upper_bound().value)
_cursor.y.value++;
}
void Dialog::_handle_pageup()
{
if (_max_lines != ~0U) {
for (unsigned i = 0; i < _max_lines; i++)
_handle_up();
} else {
_cursor.y.value = 0;
}
}
void Dialog::_handle_pagedown()
{
if (_max_lines != ~0U) {
for (unsigned i = 0; i < _max_lines; i++)
_handle_down();
} else {
_cursor.y.value = _text.upper_bound().value;
}
}
void Dialog::_handle_home()
{
_cursor.x.value = 0;
}
void Dialog::_handle_end()
{
_text.apply(_cursor.y, [&] (Line &line) {
_cursor.x = line.upper_bound(); });
}
void Dialog::handle_input_event(Input::Event const &event)
{
bool update_dialog = false;
Position const orig_cursor = _cursor;
auto cursor_to_hovered_position = [&] ()
{
if (_hovered_position.constructed()) {
_cursor.x = _hovered_position->x;
_cursor.y = _hovered_position->y;
update_dialog = true;
}
};
event.handle_press([&] (Input::Keycode key, Codepoint code) {
bool key_has_visible_effect = true;
if (shift_key(key)) {
_shift = true;
if (!_selection.defined())
_selection.start.construct(_cursor);
}
if (control_key(key))
_control = true;
if (!_control) {
if (!_shift && movement_codepoint(code))
_selection.clear();
if (_printable(code)) {
_handle_printable(code);
}
else if (code.value == CODEPOINT_BACKSPACE) { _handle_backspace(); }
else if (code.value == CODEPOINT_DELETE) { _handle_delete(); }
else if (code.value == CODEPOINT_NEWLINE) { _handle_newline(); }
else if (code.value == CODEPOINT_LEFT) { _handle_left(); }
else if (code.value == CODEPOINT_UP) { _handle_up(); }
else if (code.value == CODEPOINT_DOWN) { _handle_down(); }
else if (code.value == CODEPOINT_RIGHT) { _handle_right(); }
else if (code.value == CODEPOINT_PAGEDOWN) { _handle_pagedown(); }
else if (code.value == CODEPOINT_PAGEUP) { _handle_pageup(); }
else if (code.value == CODEPOINT_HOME) { _handle_home(); }
else if (code.value == CODEPOINT_END) { _handle_end(); }
else if (code.value == CODEPOINT_INSERT) { _trigger_paste.trigger_paste(); }
else {
key_has_visible_effect = false;
}
if (_shift && movement_codepoint(code))
_selection.end.construct(_cursor);
}
if (_control) {
if (code.value == 'c')
_trigger_copy.trigger_copy();
if (code.value == 'x') {
_trigger_copy.trigger_copy();
_delete_selection();
}
if (code.value == 'v')
_trigger_paste.trigger_paste();
if (code.value == 's')
_trigger_save.trigger_save();
}
if (key_has_visible_effect)
update_dialog = true;
bool const click = (key == Input::BTN_LEFT);
if (click && _hovered_position.constructed()) {
if (_shift)
_selection.end.construct(*_hovered_position);
else
_selection.start.construct(*_hovered_position);
_drag = true;
}
bool const middle_click = (key == Input::BTN_MIDDLE);
if (middle_click) {
cursor_to_hovered_position();
_trigger_paste.trigger_paste();
}
});
if (_drag && _hovered_position.constructed()) {
_selection.end.construct(*_hovered_position);
update_dialog = true;
}
bool const clack = event.key_release(Input::BTN_LEFT);
if (clack) {
cursor_to_hovered_position();
_drag = false;
if (_selection.defined())
_trigger_copy.trigger_copy();
}
event.handle_release([&] (Input::Keycode key) {
if (shift_key(key)) _shift = false;
if (control_key(key)) _control = false;
});
bool const all_lines_visible =
(_max_lines == ~0U) || (_text.upper_bound().value <= _max_lines);
if (!all_lines_visible) {
event.handle_wheel([&] (int, int y) {
/* scroll at granulatory of 1/5th of vertical view size */
y *= max(1U, _max_lines / 5);
if (y < 0)
_scroll.y.value += -y;
if (y > 0)
_scroll.y.value -= min((int)_scroll.y.value, y);
update_dialog = true;
});
}
/* adjust scroll position */
if (all_lines_visible) {
_scroll.y.value = 0;
} else if (orig_cursor != _cursor) {
/* ensure that the cursor remains visible */
if (_cursor.y.value > 0)
if (_scroll.y.value > _cursor.y.value - 1)
_scroll.y.value = _cursor.y.value - 1;
if (_cursor.y.value == 0)
_scroll.y.value = 0;
if (_scroll.y.value + _max_lines < _cursor.y.value + 2)
_scroll.y.value = _cursor.y.value - _max_lines + 2;
}
_clamp_scroll_position_to_upper_bound();
if (update_dialog)
rom_session.trigger_update();
}
void Dialog::handle_hover(Xml_node const &hover)
{
Constructible<Position> orig_pos { };
if (_hovered_position.constructed())
orig_pos.construct(*_hovered_position);
_hovered_position.destruct();
auto with_hovered_line = [&] (Xml_node node)
{
Text::Index const y {
node.attribute_value("name", _text.upper_bound().value)
+ _scroll.y.value };
_text.apply(y, [&] (Line const &line) {
Line::Index const max_x = line.upper_bound();
_hovered_position.construct(max_x, y);
node.with_sub_node("float", [&] (Xml_node node) {
node.with_sub_node("label", [&] (Xml_node node) {
Line::Index const x {
node.attribute_value("at", max_x.value) };
_hovered_position.construct(x, y);
});
});
});
};
bool const hover_changed =
(orig_pos.constructed() != _hovered_position.constructed());
bool const position_changed = orig_pos.constructed()
&& _hovered_position.constructed()
&& (*orig_pos != *_hovered_position);
bool const orig_text_hovered = _text_hovered;
_text_hovered = false;
hover.with_sub_node("frame", [&] (Xml_node node) {
node.with_sub_node("button", [&] (Xml_node node) {
_text_hovered = true;
node.with_sub_node("float", [&] (Xml_node node) {
node.with_sub_node("vbox", [&] (Xml_node node) {
node.with_sub_node("hbox", [&] (Xml_node node) {
with_hovered_line(node); }); }); }); }); });
if (hover_changed || position_changed || (_text_hovered != orig_text_hovered))
rom_session.trigger_update();
}
Dialog::Dialog(Entrypoint &ep, Ram_allocator &ram, Region_map &rm,
Allocator &alloc, Trigger_copy &trigger_copy,
Trigger_paste &trigger_paste, Trigger_save &trigger_save)
:
Xml_producer("dialog"),
rom_session(ep, ram, rm, *this),
_alloc(alloc),
_trigger_copy(trigger_copy),
_trigger_paste(trigger_paste),
_trigger_save(trigger_save)
{
clear();
}
void Dialog::clear()
{
Text::Index const first { 0 };
while (_text.exists(first))
_text.destruct(first);
_cursor.x.value = 0;
_cursor.y.value = 0;
}
void Dialog::insert_at_cursor_position(Codepoint c)
{
if (_printable(c)) {
_insert_printable(c);
_modification_count++;
return;
}
if (c.value == CODEPOINT_NEWLINE)
_handle_newline();
}
void Dialog::gen_clipboard_content(Xml_generator &xml) const
{
if (!_selection.defined())
return;
auto for_each_selected_character = [&] (auto fn)
{
_selection.for_each_selected_line([&] (Text::Index const y, bool const last) {
_text.apply(y, [&] (Line const &line) {
_selection.with_selection_at_line(y, line, [&] (Line::Index x, unsigned n) {
for (unsigned i = 0; i < n; i++)
line.apply(Line::Index { x.value + i }, [&] (Codepoint c) {
fn(c); }); }); });
if (!last)
fn(Codepoint{'\n'});
});
};
for_each_selected_character([&] (Codepoint c) {
String<10> const utf8(c);
if (utf8.valid())
xml.append_sanitized(utf8.string(), utf8.length() - 1);
});
}

View File

@ -0,0 +1,254 @@
/*
* \brief Text dialog
* \author Norman Feske
* \date 2020-01-14
*/
/*
* Copyright (C) 2020 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 _DIALOG_H_
#define _DIALOG_H_
/* Genode includes */
#include <base/component.h>
#include <base/session_object.h>
#include <os/buffered_xml.h>
#include <os/sandbox.h>
#include <os/dynamic_rom_session.h>
#include <input/event.h>
#include <util/utf8.h>
/* local includes */
#include <dynamic_array.h>
#include <report.h>
#include <types.h>
namespace Text_area { struct Dialog; }
struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer
{
public:
Dynamic_rom_session rom_session;
struct Trigger_copy : Interface, Noncopyable
{
virtual void trigger_copy() = 0;
};
struct Trigger_paste : Interface, Noncopyable
{
virtual void trigger_paste() = 0;
};
struct Trigger_save : Interface, Noncopyable
{
virtual void trigger_save() = 0;
};
private:
Allocator &_alloc;
Trigger_copy &_trigger_copy;
Trigger_paste &_trigger_paste;
Trigger_save &_trigger_save;
struct Character : Codepoint
{
Character(Codepoint &codepoint) : Codepoint(codepoint) { }
void print(Output &out) const
{
if (value == '"')
Genode::print(out, "&quot;");
else if (value == 9)
Genode::print(out, " ");
else
Codepoint::print(out);
}
};
using Line = Dynamic_array<Character>;
using Text = Dynamic_array<Line>;
Text _text { _alloc };
struct Position
{
Line::Index x;
Text::Index y;
Position(Line::Index x, Text::Index y) : x(x), y(y) { }
Position(Position const &other) : x(other.x), y(other.y) { }
bool operator != (Position const &other) const
{
return (x.value != other.x.value) || (y.value != other.y.value);
}
};
Position _cursor { { 0 }, { 0 } };
Position _scroll { { 0 }, { 0 } };
Constructible<Position> _hovered_position { };
unsigned _max_lines = ~0U;
bool _editable = false;
unsigned _modification_count = 0;
struct Selection
{
Constructible<Position> start;
Constructible<Position> end;
void clear()
{
start.destruct();
end .destruct();
}
bool defined() const
{
return start.constructed() && end.constructed() && (*start != *end);
}
template <typename FN>
void for_each_selected_line(FN const &) const;
template <typename FN>
void with_selection_at_line(Text::Index y, Line const &, FN const &) const;
/* generate dialog model */
void gen_selected_line(Xml_generator &, Text::Index, Line const &) const;
};
bool _drag = false;
bool _shift = false;
bool _control = false;
bool _text_hovered = false;
Selection _selection { };
void produce_xml(Xml_generator &xml) override;
static bool _printable(Codepoint code)
{
if (!code.valid())
return false;
if (code.value == '\t')
return true;
return (code.value >= 0x20 && code.value < 0xf000);
}
bool _cursor_at_last_line() const
{
return (_cursor.y.value + 1 >= _text.upper_bound().value);
}
bool _cursor_at_end_of_line() const
{
bool result = false;
_text.apply(_cursor.y, [&] (Line &line) {
result = (_cursor.x.value >= line.upper_bound().value); });
return result;
}
void _tie_cursor_to_end_of_line()
{
_text.apply(_cursor.y, [&] (Line &line) {
if (_cursor.x.value > line.upper_bound().value)
_cursor.x = line.upper_bound(); });
}
bool _end_of_text() const
{
return _cursor_at_last_line() && _cursor_at_end_of_line();
}
void _clamp_scroll_position_to_upper_bound()
{
if (_max_lines != ~0U)
if (_scroll.y.value + _max_lines > _text.upper_bound().value)
_scroll.y.value = max(_text.upper_bound().value, _max_lines)
- _max_lines;
}
void _move_characters(Line &, Line &);
void _delete_selection();
void _insert_printable(Codepoint);
void _handle_printable(Codepoint);
void _handle_backspace();
void _handle_delete();
void _handle_newline();
void _handle_left();
void _handle_right();
void _handle_up();
void _handle_down();
void _handle_pageup();
void _handle_pagedown();
void _handle_home();
void _handle_end();
public:
Dialog(Entrypoint &, Ram_allocator &, Region_map &, Allocator &,
Trigger_copy &, Trigger_paste &, Trigger_save &);
void editable(bool editable) { _editable = editable; }
unsigned modification_count() const { return _modification_count; }
void max_lines(unsigned max_lines) { _max_lines = max_lines; }
void handle_input_event(Input::Event const &);
void handle_hover(Xml_node const &hover);
void clear();
void append_newline()
{
_text.append(_alloc);
}
void append_character(Codepoint c)
{
if (_printable(c)) {
Text::Index const y { _text.upper_bound().value - 1 };
_text.apply(y, [&] (Line &line) {
line.append(c); });
}
}
/* insert character and advance cursor */
void insert_at_cursor_position(Codepoint);
void gen_clipboard_content(Xml_generator &xml) const;
template <typename FN>
void for_each_character(FN const &fn) const
{
_text.for_each([&] (Text::Index at, Line const &line) {
line.for_each([&] (Line::Index, Character c) {
fn(c); });
if (at.value + 1 < _text.upper_bound().value)
fn(Codepoint{'\n'});
});
}
};
#endif /* _DIALOG_H_ */

View File

@ -0,0 +1,194 @@
/*
* \brief Dynamically growing array
* \author Norman Feske
* \date 2020-01-12
*/
/*
* Copyright (C) 2020 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 _DYNAMIC_ARRAY_H_
#define _DYNAMIC_ARRAY_H_
/* Genode includes */
#include <base/allocator.h>
namespace Text_area {
using namespace Genode;
template <typename>
struct Dynamic_array;
}
template <typename ET>
struct Text_area::Dynamic_array
{
public:
struct Index { unsigned value; };
private:
Allocator &_alloc;
using Element = Constructible<ET>;
Element *_array = nullptr;
unsigned _capacity = 0;
unsigned _upper_bound = 0; /* index after last used element */
bool _index_valid(Index at) const
{
return (at.value < _upper_bound) && _array[at.value].constructed();
}
/*
* Noncopyable
*/
Dynamic_array(Dynamic_array const &other);
void operator = (Dynamic_array const &);
public:
/**
* Moving constructor
*/
Dynamic_array(Dynamic_array &other)
:
_alloc(other._alloc), _array(other._array),
_capacity(other._capacity), _upper_bound(other._upper_bound)
{
other._array = nullptr;
other._capacity = 0;
other._upper_bound = 0;
}
Dynamic_array(Allocator &alloc) : _alloc(alloc) { }
~Dynamic_array()
{
if (!_array)
return;
clear();
_alloc.free(_array, _capacity*sizeof(Element));
}
void clear()
{
if (_upper_bound > 0)
for (unsigned i = _upper_bound; i > 0; i--)
destruct(Index{i - 1});
}
template <typename... ARGS>
void insert(Index at, ARGS &&... args)
{
/* grow array if index exceeds current capacity or if it's full */
if (at.value >= _capacity || _upper_bound == _capacity) {
size_t const new_capacity =
2 * max(_capacity, max(8U, at.value));
Element *new_array = nullptr;
try {
(void)_alloc.alloc(sizeof(Element)*new_capacity, &new_array);
for (unsigned i = 0; i < new_capacity; i++)
construct_at<Element>(&new_array[i]);
}
catch (... /* Out_of_ram, Out_of_caps */ ) { throw; }
if (_array) {
for (unsigned i = 0; i < _upper_bound; i++)
new_array[i].construct(*_array[i]);
_alloc.free(_array, sizeof(Element)*_capacity);
}
_array = new_array;
_capacity = new_capacity;
}
/* make room for new element */
if (_upper_bound > 0)
for (unsigned i = _upper_bound; i > at.value; i--)
_array[i].construct(*_array[i - 1]);
_array[at.value].construct(args...);
_upper_bound = max(at.value + 1, _upper_bound + 1);
}
template <typename... ARGS>
void append(ARGS &&... args) { insert(Index{_upper_bound}, args...); }
bool exists(Index at) const { return _index_valid(at); }
Index upper_bound() const { return Index { _upper_bound }; }
void destruct(Index at)
{
if (!_index_valid(at))
return;
_array[at.value].destruct();
if (_upper_bound > 0)
for (unsigned i = at.value; i < _upper_bound - 1; i++)
_array[i].construct(*_array[i + 1]);
_upper_bound--;
_array[_upper_bound].destruct();
}
template <typename FN>
void apply(Index at, FN const &fn)
{
if (_index_valid(at))
fn(*_array[at.value]);
}
template <typename FN>
void apply(Index at, FN const &fn) const
{
if (_index_valid(at))
fn(*_array[at.value]);
}
struct Range { Index at; unsigned length; };
template <typename FN>
void for_each(Range range, FN const &fn) const
{
unsigned const first = range.at.value;
unsigned const limit = min(_upper_bound, first + range.length);
for (unsigned i = first; i < limit; i++)
if (_array[i].constructed())
fn(Index{i}, *_array[i]);
}
template <typename FN>
void for_each(FN const &fn) const
{
for_each(Range { .at = { 0U }, .length = ~0U }, fn);
}
void print(Output &out) const
{
for (unsigned i = 0; i < _upper_bound; i++)
if (_array[i].constructed())
Genode::print(out, *_array[i]);
}
};
#endif /* _DYNAMIC_ARRAY_H_ */

View File

@ -0,0 +1,28 @@
/*
* \brief Interface for handling input events
* \author Norman Feske
* \date 2018-05-02
*/
/*
* 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 _INPUT_EVENT_HANDLER_H_
#define _INPUT_EVENT_HANDLER_H_
/* Genode includes */
#include <util/interface.h>
#include <input/event.h>
namespace Nitpicker { struct Input_event_handler; }
struct Nitpicker::Input_event_handler : Genode::Interface
{
virtual void handle_input_event(Input::Event const &) = 0;
};
#endif /* _INPUT_EVENT_HANDLER_H_ */

View File

@ -0,0 +1,525 @@
/*
* \brief Simple text viewer and editor
* \author Norman Feske
* \date 2020-01-12
*/
/*
* Copyright (C) 2020 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.
*/
/* Genode includes */
#include <base/component.h>
#include <base/session_object.h>
#include <base/attached_rom_dataspace.h>
#include <base/buffered_output.h>
#include <os/buffered_xml.h>
#include <os/sandbox.h>
#include <os/dynamic_rom_session.h>
#include <os/vfs.h>
#include <os/reporter.h>
/* local includes */
#include <nitpicker.h>
#include <report.h>
#include <dialog.h>
#include <new_file.h>
#include <child_state.h>
namespace Text_area { struct Main; }
struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
Sandbox::State_handler,
Nitpicker::Input_event_handler,
Dialog::Trigger_copy, Dialog::Trigger_paste,
Dialog::Trigger_save
{
Env &_env;
Heap _heap { _env.ram(), _env.rm() };
Attached_rom_dataspace _config { _env, "config" };
Root_directory _vfs { _env, _heap, _config.xml().sub_node("vfs") };
unsigned _min_width = 0;
unsigned _min_height = 0;
Registry<Child_state> _children { };
Child_state _menu_view_child_state { _children, "menu_view",
Ram_quota { 4*1024*1024 },
Cap_quota { 200 } };
/**
* Sandbox::State_handler
*/
void handle_sandbox_state() override
{
/* obtain current sandbox state */
Buffered_xml state(_heap, "state", [&] (Xml_generator &xml) {
_sandbox.generate_state_report(xml);
});
bool reconfiguration_needed = false;
state.with_xml_node([&] (Xml_node state) {
state.for_each_sub_node("child", [&] (Xml_node const &child) {
if (_menu_view_child_state.apply_child_state_report(child))
reconfiguration_needed = true; }); });
if (reconfiguration_needed)
_update_sandbox_config();
}
Sandbox _sandbox { _env, *this };
typedef Sandbox::Local_service<Nitpicker::Session_component> Nitpicker_service;
Nitpicker_service _nitpicker_service { _sandbox, *this };
typedef Sandbox::Local_service<Dynamic_rom_session> Rom_service;
Rom_service _rom_service { _sandbox, *this };
typedef Sandbox::Local_service<Report::Session_component> Report_service;
Report_service _report_service { _sandbox, *this };
void _handle_hover(Xml_node const &node)
{
if (!node.has_sub_node("dialog"))
_dialog.handle_hover(Xml_node("<empty/>"));
node.with_sub_node("dialog", [&] (Xml_node const &dialog) {
_dialog.handle_hover(dialog); });
}
Report::Session_component::Xml_handler<Main>
_hover_handler { *this, &Main::_handle_hover };
Dialog _dialog { _env.ep(), _env.ram(), _env.rm(), _heap, *this, *this, *this };
Constructible<Expanding_reporter> _saved_reporter { };
struct Saved_version { unsigned value; } _saved_version { 0 };
/*
* The dialog's modification count at the time of last saving
*/
struct Modification_count { unsigned value; } _saved_modification_count { 0 };
bool _modified() const
{
return _dialog.modification_count() != _saved_modification_count.value;
}
void _generate_saved_report()
{
if (!_saved_reporter.constructed())
return;
_saved_reporter->generate([&] (Xml_generator &xml) {
xml.attribute("version", _saved_version.value);
if (_modified())
xml.attribute("modified", "yes");
});
}
void _generate_sandbox_config(Xml_generator &xml) const
{
xml.node("report", [&] () {
xml.attribute("child_ram", "yes");
xml.attribute("child_caps", "yes");
xml.attribute("delay_ms", 20*1000);
});
xml.node("parent-provides", [&] () {
auto service_node = [&] (char const *name) {
xml.node("service", [&] () {
xml.attribute("name", name); }); };
service_node("ROM");
service_node("CPU");
service_node("PD");
service_node("LOG");
service_node("File_system");
service_node("Nitpicker");
service_node("Timer");
service_node("Report");
});
xml.node("start", [&] () {
_menu_view_child_state.gen_start_node_content(xml);
xml.node("config", [&] () {
xml.attribute("xpos", "100");
xml.attribute("ypos", "50");
if (_min_width) xml.attribute("width", _min_width);
if (_min_height) xml.attribute("height", _min_height);
xml.node("report", [&] () {
xml.attribute("hover", "yes"); });
xml.node("libc", [&] () {
xml.attribute("stderr", "/dev/log"); });
xml.node("vfs", [&] () {
xml.node("tar", [&] () {
xml.attribute("name", "menu_view_styles.tar"); });
xml.node("dir", [&] () {
xml.attribute("name", "dev");
xml.node("log", [&] () { });
});
xml.node("dir", [&] () {
xml.attribute("name", "fonts");
xml.node("fs", [&] () {
xml.attribute("label", "fonts");
});
});
});
});
xml.node("route", [&] () {
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label", "dialog");
xml.node("local", [&] () { });
});
xml.node("service", [&] () {
xml.attribute("name", "Report");
xml.attribute("label", "hover");
xml.node("local", [&] () { });
});
xml.node("service", [&] () {
xml.attribute("name", "Nitpicker");
xml.node("local", [&] () { });
});
xml.node("service", [&] () {
xml.attribute("name", "File_system");
xml.attribute("label", "fonts");
xml.node("parent", [&] () {
xml.attribute("label", "fonts"); });
});
xml.node("any-service", [&] () {
xml.node("parent", [&] () { }); });
});
});
}
/**
* Sandbox::Local_service_base::Wakeup interface
*/
void wakeup_local_service() override
{
_rom_service.for_each_requested_session([&] (Rom_service::Request &request) {
if (request.label == "menu_view -> dialog")
request.deliver_session(_dialog.rom_session);
else
request.deny();
});
_report_service.for_each_requested_session([&] (Report_service::Request &request) {
if (request.label == "menu_view -> hover") {
Report::Session_component &session = *new (_heap)
Report::Session_component(_env, _hover_handler,
_env.ep(),
request.resources, "", request.diag);
request.deliver_session(session);
}
});
_report_service.for_each_session_to_close([&] (Report::Session_component &session) {
destroy(_heap, &session);
return Report_service::Close_response::CLOSED;
});
_nitpicker_service.for_each_requested_session([&] (Nitpicker_service::Request &request) {
Nitpicker::Session_component &session = *new (_heap)
Nitpicker::Session_component(_env, *this, _env.ep(),
request.resources, "", request.diag);
request.deliver_session(session);
});
_nitpicker_service.for_each_upgraded_session([&] (Nitpicker::Session_component &session,
Session::Resources const &amount) {
session.upgrade(amount);
return Nitpicker_service::Upgrade_response::CONFIRMED;
});
_nitpicker_service.for_each_session_to_close([&] (Nitpicker::Session_component &session) {
destroy(_heap, &session);
return Nitpicker_service::Close_response::CLOSED;
});
}
/**
* Nitpicker::Input_event_handler interface
*/
void handle_input_event(Input::Event const &event) override
{
bool const orig_modified = _modified();
_dialog.handle_input_event(event);
if (_modified() != orig_modified)
_generate_saved_report();
}
Directory::Path _path() const
{
return _config.xml().attribute_value("path", Directory::Path());
}
void _watch(bool enabled)
{
_watch_handler.conditional(enabled, _vfs, _path(), *this, &Main::_handle_watch);
}
bool _editable() const { return !_watch_handler.constructed(); }
void _load()
{
struct Max_line_len_exceeded : Exception { };
try {
File_content content(_heap, _vfs, _path(), File_content::Limit{1024*1024});
enum { MAX_LINE_LEN = 1000 };
typedef String<MAX_LINE_LEN + 1> Content_line;
_dialog.clear();
content.for_each_line<Content_line>([&] (Content_line const &line) {
if (line.length() == Content_line::capacity()) {
warning("maximum line length ", (size_t)MAX_LINE_LEN, " exceeded");
throw Max_line_len_exceeded();
}
_dialog.append_newline();
for (Utf8_ptr utf8(line.string()); utf8.complete(); utf8 = utf8.next())
_dialog.append_character(utf8.codepoint());
});
}
catch (...) {
warning("failed to load file ", _path());
_dialog.clear();
}
_dialog.rom_session.trigger_update();
}
Constructible<Watch_handler<Main>> _watch_handler { };
void _handle_watch() { _load(); }
/*
* Copy
*/
Constructible<Expanding_reporter> _clipboard_reporter { };
/**
* Dialog::Trigger_copy interface
*/
void trigger_copy() override
{
if (!_clipboard_reporter.constructed())
return;
_clipboard_reporter->generate([&] (Xml_generator &xml) {
_dialog.gen_clipboard_content(xml); });
}
/*
* Paste
*/
Constructible<Attached_rom_dataspace> _clipboard_rom { };
enum { PASTE_BUFFER_SIZE = 64*1024 };
struct Paste_buffer { char buffer[PASTE_BUFFER_SIZE]; } _paste_buffer { };
/**
* Dialog::Trigger_paste interface
*/
void trigger_paste() override
{
if (!_editable())
return;
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;
}
for (Utf8_ptr utf8(_paste_buffer.buffer); utf8.complete(); utf8 = utf8.next())
_dialog.insert_at_cursor_position(utf8.codepoint());
_dialog.rom_session.trigger_update();
}
/*
* Save
*/
void _save_to_file(Directory::Path const &path)
{
bool write_error = false;
try {
New_file new_file(_vfs, path);
auto write = [&] (char const *cstring)
{
switch (new_file.append(cstring, strlen(cstring))) {
case New_file::Append_result::OK: break;
case New_file::Append_result::WRITE_ERROR:
write_error = true;
break;
}
};
Buffered_output<1024, decltype(write)> output(write);
_dialog.for_each_character([&] (Codepoint c) { print(output, c); });
}
catch (New_file::Create_failed) {
error("file creation failed while saving file"); }
if (write_error) {
error("write error while saving ", _path());
return;
}
_saved_modification_count.value = _dialog.modification_count();
_generate_saved_report();
}
/**
* Dialog::Trigger_save interface
*/
void trigger_save() override
{
if (!_editable())
return;
_saved_version.value++;
_save_to_file(_path());
}
bool _initial_config = true;
void _handle_config()
{
_config.update();
Xml_node const config = _config.xml();
_min_width = config.attribute_value("min_width", 0U);
_min_height = config.attribute_value("min_height", 0U);
bool const copy_enabled = config.attribute_value("copy", false);
bool const paste_enabled = config.attribute_value("paste", false);
_clipboard_reporter.conditional(copy_enabled, _env, "clipboard", "clipboard");
_clipboard_rom .conditional(paste_enabled, _env, "clipboard");
_dialog.max_lines(config.attribute_value("max_lines", ~0U));
_watch(config.attribute_value("watch", false));
_dialog.editable(_editable());
if (_editable()) {
bool const orig_saved_reporter_enabled = _saved_reporter.constructed();
config.with_sub_node("report", [&] (Xml_node const &node) {
_saved_reporter.conditional(node.attribute_value("saved", false),
_env, "saved", "saved"); });
bool const saved_report_out_of_date =
!orig_saved_reporter_enabled && _saved_reporter.constructed();
Saved_version const orig_saved_version = _saved_version;
config.with_sub_node("save", [&] (Xml_node const &node) {
_saved_version.value =
node.attribute_value("version", _saved_version.value); });
bool const saved_version_changed =
(_saved_version.value != orig_saved_version.value);
if (saved_version_changed || saved_report_out_of_date) {
if (!_initial_config)
_save_to_file(_path());
else
_generate_saved_report();
}
}
_initial_config = false;
}
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
void _update_sandbox_config()
{
Buffered_xml const config { _heap, "config", [&] (Xml_generator &xml) {
_generate_sandbox_config(xml); } };
config.with_xml_node([&] (Xml_node const &config) {
_sandbox.apply_config(config); });
}
Main(Env &env)
:
_env(env)
{
/*
* The '_load' must be performed before '_handle_config' because
* '_handle_config' may call '_save_to_file' if the <config> contains a
* <saved> node.
*/
_load();
_config.sigh(_config_handler);
_handle_config();
_update_sandbox_config();
}
};
void Component::construct(Genode::Env &env) { static Text_area::Main main(env); }

View File

@ -0,0 +1,147 @@
/*
* \brief Utility for writing data to a file via the Genode VFS library
* \author Norman Feske
* \date 2020-01-25
*/
/*
* Copyright (C) 2020 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 _NEW_FILE_H_
#define _NEW_FILE_H_
#include <os/vfs.h>
namespace Genode { class New_file; }
class Genode::New_file : Noncopyable
{
public:
struct Create_failed : Exception { };
private:
Entrypoint &_ep;
Allocator &_alloc;
Vfs::File_system &_fs;
Vfs::Vfs_handle &_handle;
Vfs::Vfs_handle &_init_handle(Directory::Path const &path)
{
unsigned mode = Vfs::Directory_service::OPEN_MODE_WRONLY;
Vfs::Directory_service::Stat stat { };
if (_fs.stat(path.string(), stat) != Vfs::Directory_service::STAT_OK)
mode |= Vfs::Directory_service::OPEN_MODE_CREATE;
Vfs::Vfs_handle *handle_ptr = nullptr;
Vfs::Directory_service::Open_result const res =
_fs.open(path.string(), mode, &handle_ptr, _alloc);
if (res != Vfs::Directory_service::OPEN_OK || (handle_ptr == nullptr)) {
error("failed to create file '", path, "'");
throw Create_failed();
}
handle_ptr->fs().ftruncate(handle_ptr, 0);
return *handle_ptr;
}
public:
/**
* Constructor
*
* \throw Open_failed
*/
New_file(Vfs::Env &env, Directory::Path const &path)
:
_ep(env.env().ep()), _alloc(env.alloc()), _fs(env.root_dir()),
_handle(_init_handle(path))
{ }
~New_file()
{
while (_handle.fs().queue_sync(&_handle) == false)
_ep.wait_and_dispatch_one_io_signal();
for (bool sync_done = false; !sync_done; ) {
switch (_handle.fs().complete_sync(&_handle)) {
case Vfs::File_io_service::SYNC_QUEUED:
break;
case Vfs::File_io_service::SYNC_ERR_INVALID:
warning("could not complete file sync operation");
sync_done = true;
break;
case Vfs::File_io_service::SYNC_OK:
sync_done = true;
break;
}
if (!sync_done)
_ep.wait_and_dispatch_one_io_signal();
}
_handle.ds().close(&_handle);
}
enum class Append_result { OK, WRITE_ERROR };
Append_result append(char const *src, size_t size)
{
bool write_error = false;
size_t remaining_bytes = size;
while (remaining_bytes > 0 && !write_error) {
bool stalled = false;
try {
Vfs::file_size out_count = 0;
using Write_result = Vfs::File_io_service::Write_result;
switch (_handle.fs().write(&_handle, src, remaining_bytes,
out_count)) {
case Write_result::WRITE_ERR_AGAIN:
case Write_result::WRITE_ERR_WOULD_BLOCK:
stalled = true;
break;
case Write_result::WRITE_ERR_INVALID:
case Write_result::WRITE_ERR_IO:
case Write_result::WRITE_ERR_INTERRUPT:
write_error = true;
break;
case Write_result::WRITE_OK:
out_count = min(remaining_bytes, out_count);
remaining_bytes -= out_count;
src += out_count;
_handle.advance_seek(out_count);
break;
};
}
catch (Vfs::File_io_service::Insufficient_buffer) {
stalled = true; }
if (stalled)
_ep.wait_and_dispatch_one_io_signal();
}
return write_error ? Append_result::WRITE_ERROR
: Append_result::OK;
}
};
#endif /* _NEW_FILE_H_ */

View File

@ -0,0 +1,122 @@
/*
* \brief Nitpicker wrapper for monitoring the user input of GUI components
* \author Norman Feske
* \date 2020-01-12
*/
/*
* Copyright (C) 2020 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 _NITPICKER_H_
#define _NITPICKER_H_
/* Genode includes */
#include <input/component.h>
#include <base/session_object.h>
#include <nitpicker_session/connection.h>
/* local includes */
#include <input_event_handler.h>
namespace Nitpicker {
using namespace Genode;
struct Session_component;
}
struct Nitpicker::Session_component : Session_object<Nitpicker::Session>
{
Env &_env;
Input_event_handler &_event_handler;
Nitpicker::Connection _connection;
Input::Session_component _input_component { _env, _env.ram() };
Signal_handler<Session_component> _input_handler {
_env.ep(), *this, &Session_component::_handle_input };
void _handle_input()
{
_connection.input()->for_each_event([&] (Input::Event ev) {
/* handle event locally within the sculpt manager */
_event_handler.handle_input_event(ev);
_input_component.submit(ev);
});
}
template <typename... ARGS>
Session_component(Env &env, Input_event_handler &event_handler, ARGS &&... args)
:
Session_object(args...),
_env(env), _event_handler(event_handler),
_connection(env, _label.string())
{
_connection.input()->sigh(_input_handler);
_env.ep().manage(_input_component);
_input_component.event_queue().enabled(true);
}
~Session_component() { _env.ep().dissolve(_input_component); }
void upgrade(Session::Resources const &resources)
{
_connection.upgrade(resources);
}
Framebuffer::Session_capability framebuffer_session() override {
return _connection.framebuffer_session(); }
Input::Session_capability input_session() override {
return _input_component.cap(); }
View_handle create_view(View_handle parent) override {
return _connection.create_view(parent); }
void destroy_view(View_handle view) override {
_connection.destroy_view(view); }
View_handle view_handle(View_capability view_cap, View_handle handle) override {
return _connection.view_handle(view_cap, handle); }
View_capability view_capability(View_handle view) override {
return _connection.view_capability(view); }
void release_view_handle(View_handle view) override {
_connection.release_view_handle(view); }
Dataspace_capability command_dataspace() override {
return _connection.command_dataspace(); }
void execute() override {
_connection.execute(); }
Framebuffer::Mode mode() override {
return _connection.mode(); }
void mode_sigh(Signal_context_capability sigh) override {
_connection.mode_sigh(sigh); }
void buffer(Framebuffer::Mode mode, bool use_alpha) override
{
/*
* Do not call 'Connection::buffer' to avoid paying session quota
* from our own budget.
*/
_connection.Client::buffer(mode, use_alpha);
}
void focus(Capability<Nitpicker::Session> session) override {
_connection.focus(session); }
};
#endif /* _NITPICKER_H_ */

View File

@ -0,0 +1,89 @@
/*
* \brief Report session provided to the sandbox
* \author Norman Feske
* \date 2020-01-14
*/
/*
* Copyright (C) 2020 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 _REPORT_H_
#define _REPORT_H_
/* Genode includes */
#include <base/session_object.h>
#include <report_session/report_session.h>
namespace Report {
using namespace Genode;
struct Session_component;
}
class Report::Session_component : public Session_object<Report::Session>
{
public:
struct Handler_base : Interface, Genode::Noncopyable
{
virtual void handle_report(char const *, size_t) = 0;
};
template <typename T>
struct Xml_handler : Handler_base
{
T &_obj;
void (T::*_member) (Xml_node const &);
Xml_handler(T &obj, void (T::*member)(Xml_node const &))
: _obj(obj), _member(member) { }
void handle_report(char const *start, size_t length) override
{
(_obj.*_member)(Xml_node(start, length));
}
};
private:
Attached_ram_dataspace _ds;
Handler_base &_handler;
/*******************************
** Report::Session interface **
*******************************/
Dataspace_capability dataspace() override { return _ds.cap(); }
void submit(size_t length) override
{
_handler.handle_report(_ds.local_addr<char const>(),
min(_ds.size(), length));
}
void response_sigh(Signal_context_capability) override { }
size_t obtain_response() override { return 0; }
public:
template <typename... ARGS>
Session_component(Env &env, Handler_base &handler,
Entrypoint &ep, Resources const &resources,
ARGS &&... args)
:
Session_object(ep, resources, args...),
_ds(env.ram(), env.rm(), resources.ram_quota.value),
_handler(handler)
{ }
};
#endif /* _REPORT_H_ */

View File

@ -0,0 +1,4 @@
TARGET = text_area
SRC_CC = main.cc dialog.cc
LIBS += base sandbox vfs
INC_DIR += $(PRG_DIR)

View File

@ -0,0 +1,21 @@
/*
* \brief Common types
* \author Norman Feske
* \date 2020-01-14
*/
/*
* Copyright (C) 2012 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_
namespace Genode { }
namespace Text_area { using namespace Genode; }
#endif /* _TYPES_H_ */