HelenOS sources
This source file includes following definitions.
- _real_row
- termui_set_style
- _termui_evict_row
- termui_get_active_row
- _update_active_cells
- _update_current_cell
- _cursor_off
- _cursor_on
- _advance_line
- termui_put_lf
- termui_put_cr
- termui_put_crlf
- termui_put_tab
- termui_put_backspace
- termui_put_glyph
- termui_color_from_rgb
- termui_color_to_rgb
- termui_get_cols
- termui_get_rows
- termui_get_pos
- termui_set_pos
- termui_clear_screen
- termui_wipe_screen
- termui_set_scroll_cb
- termui_set_update_cb
- termui_set_refresh_cb
- termui_force_viewport_update
- termui_scrollback_is_active
- termui_create
- termui_destroy
- termui_history_scroll
- termui_set_cursor_visibility
- termui_get_cursor_visibility
- _termui_put_cells
- termui_resize
#include <termui.h>
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include "history.h"
struct termui {
int cols;
int rows;
int col;
int row;
bool cursor_visible;
int used_rows;
int first_row;
termui_cell_t *screen;
uint8_t *overflow_flags;
bool overflow;
struct history history;
termui_cell_t style;
termui_cell_t default_cell;
termui_scroll_cb_t scroll_cb;
termui_update_cb_t update_cb;
termui_refresh_cb_t refresh_cb;
void *scroll_udata;
void *update_udata;
void *refresh_udata;
};
static int _real_row(const termui_t *termui, int row)
{
row += termui->first_row;
if (row >= termui->rows)
row -= termui->rows;
return row;
}
#define _screen_cell(termui, col, row) \
((termui)->screen[(termui)->cols * _real_row((termui), (row)) + (col)])
#define _current_cell(termui) \
_screen_cell((termui), (termui)->col, (termui)->row)
#define _overflow_flag(termui, row) \
((termui)->overflow_flags[_real_row((termui), (row))])
void termui_set_style(termui_t *termui, termui_cell_t style)
{
termui->style = style;
}
static void _termui_evict_row(termui_t *termui)
{
if (termui->used_rows <= 0)
return;
bool last = !_overflow_flag(termui, 0);
for (int col = 0; col < termui->cols; col++)
_screen_cell(termui, col, 0).cursor = 0;
_history_append_row(&termui->history, &_screen_cell(termui, 0, 0), last);
_overflow_flag(termui, 0) = false;
for (int col = 0; col < termui->cols; col++)
_screen_cell(termui, col, 0) = termui->default_cell;
termui->used_rows--;
termui->row--;
if (termui->row < 0) {
termui->row = 0;
termui->col = 0;
}
termui->first_row++;
if (termui->first_row >= termui->rows)
termui->first_row -= termui->rows;
assert(termui->first_row < termui->rows);
}
termui_cell_t *termui_get_active_row(termui_t *termui, int row)
{
assert(row >= 0);
assert(row < termui->rows);
return &_screen_cell(termui, 0, row);
}
static void _update_active_cells(termui_t *termui, int col, int row, int cells)
{
int viewport_rows = _history_viewport_rows(&termui->history, termui->rows);
int active_rows_shown = termui->rows - viewport_rows;
if (termui->update_cb && active_rows_shown > row)
termui->update_cb(termui->update_udata, col, row + viewport_rows, &_screen_cell(termui, col, row), cells);
}
static void _update_current_cell(termui_t *termui)
{
_update_active_cells(termui, termui->col, termui->row, 1);
}
static void _cursor_off(termui_t *termui)
{
if (termui->cursor_visible) {
_current_cell(termui).cursor = 0;
_update_current_cell(termui);
}
}
static void _cursor_on(termui_t *termui)
{
if (termui->cursor_visible) {
_current_cell(termui).cursor = 1;
_update_current_cell(termui);
}
}
static void _advance_line(termui_t *termui)
{
if (termui->row + 1 >= termui->rows) {
size_t old_top = termui->history.viewport_top;
_termui_evict_row(termui);
if (old_top != termui->history.viewport_top && termui->refresh_cb)
termui->refresh_cb(termui->refresh_udata);
if (termui->scroll_cb && !_scrollback_active(&termui->history))
termui->scroll_cb(termui->scroll_udata, 1);
}
if (termui->rows > 1)
termui->row++;
if (termui->row >= termui->used_rows)
termui->used_rows = termui->row + 1;
assert(termui->row < termui->rows);
}
void termui_put_lf(termui_t *termui)
{
_cursor_off(termui);
termui->overflow = false;
_advance_line(termui);
_cursor_on(termui);
}
void termui_put_cr(termui_t *termui)
{
_cursor_off(termui);
if (termui->overflow && termui->row > 0) {
termui->row--;
_overflow_flag(termui, termui->row) = 0;
}
termui->overflow = false;
termui->col = 0;
_cursor_on(termui);
}
void termui_put_crlf(termui_t *termui)
{
_cursor_off(termui);
if (termui->overflow && termui->row > 0) {
termui->row--;
_overflow_flag(termui, termui->row) = 0;
}
termui->overflow = false;
_advance_line(termui);
termui->col = 0;
_cursor_on(termui);
}
void termui_put_tab(termui_t *termui)
{
_cursor_off(termui);
termui->overflow = false;
int new_col = (termui->col / 8 + 1) * 8;
if (new_col >= termui->cols)
new_col = termui->cols - 1;
termui->col = new_col;
_cursor_on(termui);
}
void termui_put_backspace(termui_t *termui)
{
_cursor_off(termui);
termui->overflow = false;
if (termui->col == 0) {
if (termui->row > 0 && _overflow_flag(termui, termui->row - 1)) {
termui->row--;
termui->col = termui->cols - 1;
_overflow_flag(termui, termui->row) = false;
}
} else {
termui->col--;
}
_cursor_on(termui);
}
void termui_put_glyph(termui_t *termui, uint32_t glyph_idx, int width)
{
if (termui->row >= termui->used_rows)
termui->used_rows = termui->row + 1;
termui_cell_t padding_cell = termui->style;
padding_cell.padding = 1;
termui_cell_t cell = termui->style;
cell.glyph_idx = glyph_idx;
if (termui->col + width > termui->cols) {
int blanks = termui->cols - termui->col;
for (int i = 0; i < blanks; i++)
_screen_cell(termui, termui->col + i, termui->row) = padding_cell;
_update_active_cells(termui, termui->col, termui->row, blanks);
_overflow_flag(termui, termui->row) = 1;
_advance_line(termui);
termui->col = 0;
}
_current_cell(termui) = cell;
termui->col++;
for (int i = 1; i < width; i++) {
_current_cell(termui) = padding_cell;
termui->col++;
}
if (termui->col < termui->cols) {
if (termui->cursor_visible)
_current_cell(termui).cursor = 1;
_update_active_cells(termui, termui->col - width, termui->row, width + 1);
termui->overflow = false;
} else {
_update_active_cells(termui, termui->col - width, termui->row, width);
_overflow_flag(termui, termui->row) = 1;
_advance_line(termui);
termui->col = 0;
termui->overflow = true;
_cursor_on(termui);
}
}
termui_color_t termui_color_from_rgb(uint8_t r, uint8_t g, uint8_t b)
{
r = r >> 3;
g = g >> 3;
b = b >> 3;
return 0x8000 | r << 10 | g << 5 | b;
}
void termui_color_to_rgb(const termui_color_t c, uint8_t *r, uint8_t *g, uint8_t *b)
{
assert((c & 0x8000) != 0);
int bb = c & 0x1f;
int gg = (c >> 5) & 0x1f;
int rr = (c >> 10) & 0x1f;
*r = (rr << 3) | (rr >> 2);
*g = (gg << 3) | (gg >> 2);
*b = (bb << 3) | (bb >> 2);
assert(termui_color_from_rgb(*r, *g, *b) == c);
}
int termui_get_cols(const termui_t *termui)
{
return termui->cols;
}
int termui_get_rows(const termui_t *termui)
{
return termui->rows;
}
void termui_get_pos(const termui_t *termui, int *col, int *row)
{
*col = termui->col;
*row = termui->row;
}
void termui_set_pos(termui_t *termui, int col, int row)
{
if (col < 0)
col = 0;
if (col >= termui->cols)
col = termui->cols - 1;
if (row < 0)
row = 0;
if (row >= termui->rows)
row = termui->rows - 1;
_cursor_off(termui);
termui->col = col;
termui->row = row;
_cursor_on(termui);
}
void termui_clear_screen(termui_t *termui)
{
_cursor_off(termui);
termui_put_crlf(termui);
int unused_rows = termui->rows - termui->used_rows;
while (termui->used_rows > 0)
_termui_evict_row(termui);
for (int row = 0; row < unused_rows; row++) {
for (int col = 0; col < termui->cols; col++) {
_screen_cell(termui, col, row) = termui->default_cell;
}
}
termui->row = 0;
termui->col = 0;
_cursor_on(termui);
if (termui->refresh_cb)
termui->refresh_cb(termui->refresh_udata);
}
void termui_wipe_screen(termui_t *termui, int first_row)
{
if (first_row >= termui->rows)
return;
if (first_row < 0)
first_row = 0;
for (int row = first_row; row < termui->rows; row++) {
for (int col = 0; col < termui->cols; col++)
_screen_cell(termui, col, row) = termui->default_cell;
_overflow_flag(termui, row) = false;
_update_active_cells(termui, 0, row, termui->cols);
}
if (termui->used_rows > first_row)
termui->used_rows = first_row;
if (termui->row >= first_row) {
termui->row = first_row;
termui->col = 0;
_cursor_on(termui);
}
}
void termui_set_scroll_cb(termui_t *termui, termui_scroll_cb_t cb, void *userdata)
{
termui->scroll_cb = cb;
termui->scroll_udata = userdata;
}
void termui_set_update_cb(termui_t *termui, termui_update_cb_t cb, void *userdata)
{
termui->update_cb = cb;
termui->update_udata = userdata;
}
void termui_set_refresh_cb(termui_t *termui, termui_refresh_cb_t cb, void *userdata)
{
termui->refresh_cb = cb;
termui->refresh_udata = userdata;
}
void termui_force_viewport_update(const termui_t *termui, int first_row, int rows)
{
assert(first_row >= 0);
assert(rows >= 0);
assert(first_row + rows <= termui->rows);
if (!termui->update_cb)
return;
int sb_rows = _history_viewport_rows(&termui->history, termui->rows);
int updated = _history_iter_rows(&termui->history, first_row, rows, termui->update_cb, termui->update_udata);
first_row += updated;
rows -= updated;
assert(sb_rows <= first_row);
for (int row = first_row; row < first_row + rows; row++) {
termui->update_cb(termui->update_udata, 0, row, &_screen_cell(termui, 0, row - sb_rows), termui->cols);
}
}
bool termui_scrollback_is_active(const termui_t *termui)
{
return _scrollback_active(&termui->history);
}
termui_t *termui_create(int cols, int rows, size_t history_lines)
{
if (cols < 2 || rows < 1 || INT_MAX / cols < rows)
return NULL;
int cells = cols * rows;
termui_t *termui = calloc(1, sizeof(termui_t));
if (!termui)
return NULL;
termui->cols = cols;
termui->rows = rows;
termui->history.lines.max_len = history_lines;
if (history_lines > SIZE_MAX / cols)
termui->history.cells.max_len = SIZE_MAX;
else
termui->history.cells.max_len = history_lines * cols;
termui->history.cols = cols;
termui->screen = calloc(cells, sizeof(termui->screen[0]));
if (!termui->screen) {
free(termui);
return NULL;
}
termui->overflow_flags = calloc(rows, sizeof(termui->overflow_flags[0]));
if (!termui->overflow_flags) {
free(termui->screen);
free(termui);
return NULL;
}
return termui;
}
void termui_destroy(termui_t *termui)
{
free(termui->screen);
free(termui);
}
void termui_history_scroll(termui_t *termui, int delta)
{
int scrolled = _history_scroll(&termui->history, delta);
if (scrolled != 0 && termui->scroll_cb)
termui->scroll_cb(termui->scroll_udata, scrolled);
}
void termui_set_cursor_visibility(termui_t *termui, bool visible)
{
if (termui->cursor_visible == visible)
return;
termui->cursor_visible = visible;
_current_cell(termui).cursor = visible;
_update_current_cell(termui);
}
bool termui_get_cursor_visibility(const termui_t *termui)
{
return termui->cursor_visible;
}
static void _termui_put_cells(termui_t *termui, const termui_cell_t *cells, int n)
{
while (n > 0) {
_current_cell(termui) = cells[0];
cells++;
n--;
termui->col++;
if (termui->col == termui->cols) {
_overflow_flag(termui, termui->row) = 1;
_advance_line(termui);
termui->col = 0;
termui->overflow = true;
} else {
termui->overflow = false;
}
}
if (termui->row >= termui->used_rows)
termui->used_rows = termui->row + 1;
}
errno_t termui_resize(termui_t *termui, int cols, int rows, size_t history_lines)
{
if (cols < 2 || rows < 1 || INT_MAX / cols < rows)
return ERANGE;
int cells = cols * rows;
termui_cell_t *new_screen = calloc(cells, sizeof(new_screen[0]));
if (!new_screen)
return ENOMEM;
uint8_t *new_flags = calloc(rows, sizeof(new_flags[0]));
if (!new_flags) {
free(new_screen);
return ENOMEM;
}
termui_t old_termui = *termui;
termui->rows = rows;
termui->cols = cols;
termui->row = 0;
termui->col = 0;
termui->used_rows = 0;
termui->first_row = 0;
termui->screen = new_screen;
termui->overflow_flags = new_flags;
termui->overflow = false;
bool cursor_visible = termui->cursor_visible;
termui->cursor_visible = false;
termui->history.lines.max_len = history_lines;
if (history_lines > SIZE_MAX / cols)
termui->history.cells.max_len = SIZE_MAX;
else
termui->history.cells.max_len = history_lines * cols;
termui->scroll_cb = NULL;
termui->update_cb = NULL;
termui->refresh_cb = NULL;
size_t recouped;
const termui_cell_t *c = _history_reflow(&termui->history, cols, &recouped);
if (recouped > 0)
_termui_put_cells(termui, c, recouped);
_current_cell(&old_termui).cursor = 1;
for (int row = 0; row < old_termui.used_rows; row++) {
int real_row_offset = _real_row(&old_termui, row) * old_termui.cols;
if (_overflow_flag(&old_termui, row)) {
_termui_put_cells(termui, &old_termui.screen[real_row_offset], old_termui.cols);
} else {
int len = old_termui.cols;
while (len > 0 && _cell_is_empty(old_termui.screen[real_row_offset + len - 1]))
len--;
_termui_put_cells(termui, &old_termui.screen[real_row_offset], len);
if (len < old_termui.cols)
_current_cell(termui).cursor = old_termui.screen[real_row_offset + len].cursor;
if (row < old_termui.used_rows - 1)
termui_put_crlf(termui);
}
}
int new_col = 0;
int new_row = 0;
for (int col = 0; col < termui->cols; col++) {
for (int row = 0; row < termui->rows; row++) {
if (_screen_cell(termui, col, row).cursor) {
_screen_cell(termui, col, row).cursor = 0;
new_col = col;
new_row = row;
}
}
}
free(old_termui.screen);
free(old_termui.overflow_flags);
termui->col = new_col;
termui->row = new_row;
termui->cursor_visible = cursor_visible;
_cursor_on(termui);
termui->scroll_cb = old_termui.scroll_cb;
termui->update_cb = old_termui.update_cb;
termui->refresh_cb = old_termui.refresh_cb;
if (termui->refresh_cb)
termui->refresh_cb(termui->refresh_udata);
return EOK;
}
HelenOS homepage, sources at GitHub