#include #include #include "terminal.h" //#define BUF_COLUMNS (100) //#define BUF_LINES (75) #define BUF_COLUMNS (80) #define BUF_LINES (24) typedef unsigned char flag; struct modes { flag keyboard_action : 1; // KAM - Keyboard Action Mode flag insert_replace : 1; // IRM - Insertion-Replacement Mode flag linefeed_newline : 1; // LNM - Linefeed/Newline Mode flag local_echo : 1; // SRM - Send/Receive Mode flag cursor_key : 1; // DECCKM - Cursor Key Mode flag column : 1; // DECCOLM - Column Mode (80/132) flag scroll : 1; // DECSCLM - Smooth Scroll Mode flag screen : 1; // DECSCNM - Screen Mode (normal/reverse) flag origin : 1; // DECOM - Origin Mode flag auto_wrap : 1; // DECAWM - Auto Wrap Mode flag auto_repeat : 1; // DECARM - Auto Repeat Mode flag cursor_enable : 1; // DECTCEM - Text Cursor Enable Mode }; struct char_attribs { // SGR flag underline : 1; flag reverse : 1; flag blink : 1; flag bold : 1; }; struct line_attribs { // DECSWL when neither is set flag double_width : 1; // DECDWL - Can be set without DHL flag double_height_t : 1; // DECDHL Top Half - DHL is always also DWL flag double_height_b : 1; // DECDHL Bottom Half }; struct position { int col; int line; }; struct bounds { int min; int max; }; struct cell { struct char_attribs att; unsigned char glyph; }; struct line { struct line_attribs att; struct cell cells[BUF_COLUMNS]; }; struct page { int col_count; int line_count; struct bounds scroll; struct line lines[BUF_LINES]; }; struct terminal { struct page pagebuf; struct modes modes; unsigned char tabstops[BUF_COLUMNS]; struct position cursor; }; static const struct char_attribs blank_char_att = { .underline = 0, .reverse = 0, .blink = 0, .bold = 0 }; static const struct line_attribs blank_line_att = { .double_width = 0, .double_height_t = 0, .double_height_b = 0 }; static Terminal singleton_terminal = { .pagebuf = { BUF_COLUMNS, BUF_LINES, {0, BUF_LINES-1}, { {}, {} } }, .modes = {}, .tabstops = {}, {0, 0} }; int bounded_move(int *pos, int amt, struct bounds b) { int orig_pos = *pos; int actual_move = amt; *pos += amt; if (amt < 0) { if (*pos < b.min) *pos = b.min; } else { if (*pos > b.max) *pos = b.max; } actual_move = *pos - orig_pos; return actual_move; } void bounded_set(int *pos, int new_pos, struct bounds b) { if (new_pos < b.min) *pos = b.min; else if (new_pos > b.max) *pos = b.max; else *pos = new_pos; } struct bounds Terminal_cursorHBounds(Terminal *term) { struct bounds b = { 0, 0 }; b.min = 0; b.max = term->pagebuf.col_count - 1; return b; } struct bounds Terminal_cursorVBounds(Terminal *term, int scroll_region) { struct bounds b = { 0, 0 }; if (scroll_region) { b.min = term->pagebuf.scroll.min; b.max = term->pagebuf.scroll.max; } else { b.min = 0; b.max = term->pagebuf.line_count - 1; } return b; } Terminal *Terminal_new(void) { Terminal_init(&singleton_terminal); return &singleton_terminal; } void Terminal_defaultModes(Terminal *t) { t->modes.auto_wrap = 0; t->modes.insert_replace = 0; t->modes.cursor_enable = 1; } void Terminal_clearScreen(Terminal *t) { int i, j; struct cell *c; struct line *l; for (i = 0; i < t->pagebuf.line_count; i++) { l = &t->pagebuf.lines[i]; l->att = blank_line_att; for (j = 0; j < t->pagebuf.col_count; j++) { c = &l->cells[j]; c->att = blank_char_att; c->glyph = 0; } } } void Terminal_clearTabs(Terminal *t) { int i; for (i = 0; i < t->pagebuf.col_count; i++) { t->tabstops[i] = 0; } } void Terminal_defaultTabs(Terminal *t) { int i; for (i = 8; i < t->pagebuf.col_count; i += 8) { t->tabstops[i] = 1; } } void Terminal_setTab(Terminal *t) { t->tabstops[t->cursor.col] = 1; } void Terminal_clearTab(Terminal *t) { t->tabstops[t->cursor.col] = 0; } void Terminal_defaultScrollRegion(Terminal *t) { t->pagebuf.scroll.min = 0; t->pagebuf.scroll.max = t->pagebuf.line_count - 1; } void Terminal_init(Terminal *t) { t->pagebuf.col_count = BUF_COLUMNS; t->pagebuf.line_count = BUF_LINES; Terminal_defaultModes(t); Terminal_clearScreen(t); Terminal_clearTabs(t); Terminal_defaultTabs(t); Terminal_defaultScrollRegion(t); t->cursor.col = 0; t->cursor.line = 0; } void Terminal_bufferDump(Terminal *t) { int i, j; struct cell *c; for (i = 0; i < t->pagebuf.line_count; i++) { for (j = 0; j < t->pagebuf.col_count; j++) { c = &t->pagebuf.lines[i].cells[j]; if (c->glyph) { putchar(c->glyph); } else { putchar(' '); } } putchar('\n'); } } void Terminal_setAutoWrap(Terminal *t, int mode) { t->modes.auto_wrap = mode; } void Terminal_scroll(Terminal *t, int lines) { struct line *scroll_top; struct line *scroll_keep; struct line *scroll_clear; int direction, total_lines, keep_lines, clear_lines; struct bounds b; size_t scroll_size, clear_size; if (lines < 0) { lines = -lines; direction = -1; } else { direction = 1; } b = Terminal_cursorVBounds(t, 1); total_lines = b.max + 1 - b.min; keep_lines = total_lines - lines; if (keep_lines < 0) { keep_lines = 0; } clear_lines = total_lines - keep_lines; scroll_size = keep_lines * (sizeof (struct line)); clear_size = clear_lines * (sizeof (struct line)); if (direction > 0) { scroll_top = &t->pagebuf.lines[b.min]; scroll_clear = &t->pagebuf.lines[b.min + keep_lines]; if (keep_lines) { scroll_keep = &t->pagebuf.lines[b.min + lines]; memmove(scroll_top, scroll_keep, scroll_size); } } else { scroll_top = &t->pagebuf.lines[b.min + clear_lines]; scroll_clear = &t->pagebuf.lines[b.min]; if (keep_lines) { scroll_keep = &t->pagebuf.lines[b.min]; memmove(scroll_top, scroll_keep, scroll_size); } } memset(scroll_clear, 0, clear_size); } void Terminal_cursorRel(Terminal *t, int direction, int count, int scroll) { int *axis; struct bounds b; int move, result; int obey_margins = 1; switch (direction) { case DIR_UP: axis = &t->cursor.line; move = -count; b = Terminal_cursorVBounds(t, obey_margins); break; case DIR_DOWN: axis = &t->cursor.line; move = count; b = Terminal_cursorVBounds(t, obey_margins); break; case DIR_FORWARD: axis = &t->cursor.col; move = count; b = Terminal_cursorHBounds(t); break; case DIR_BACK: axis = &t->cursor.col; move = -count; b = Terminal_cursorHBounds(t); break; default: return; } result = bounded_move(axis, move, b); if (result != move && scroll) { if (direction == DIR_UP || direction == DIR_DOWN) { Terminal_scroll(t, move - result); } } } void Terminal_cursorAbsH(Terminal *t, int column) { bounded_set(&t->cursor.col, column, Terminal_cursorHBounds(t)); } void Terminal_cursorAbsV(Terminal *t, int line) { int obey_margins = 0; bounded_set(&t->cursor.line, line, Terminal_cursorVBounds(t, obey_margins)); } void Terminal_cursorAbs(Terminal *t, int column, int line) { Terminal_cursorAbsH(t, column); Terminal_cursorAbsV(t, line); } void Terminal_carriageReturn(Terminal *t) { Terminal_cursorAbsH(t, 0); } void Terminal_lineFeed(Terminal *t) { Terminal_cursorRel(t, DIR_DOWN, 1, 1); } void Terminal_newLine(Terminal *t) { Terminal_carriageReturn(t); Terminal_lineFeed(t); } void Terminal_backspace(Terminal *t) { Terminal_cursorRel(t, DIR_BACK, 1, 0); } void Terminal_horizontalTab(Terminal *t, int count) { int i, target_col; struct bounds b = Terminal_cursorHBounds(t); target_col = -1; for (i = t->cursor.col+1; i < b.max; i++) { if (t->tabstops[i] == 1) { if (count == 1) { target_col = i; break; } else { count--; } } } if (target_col == -1) { t->cursor.col = b.max + 1; } else { Terminal_cursorAbsH(t, target_col); } } void Terminal_backTab(Terminal *t, int count) { int i; for (i = t->cursor.col-1; i >= 0; i--) { if (t->tabstops[i] == 1) { if (count == 1) { t->cursor.col = i; return; } else { count--; } } } t->cursor.col = 0; } void Terminal_insertGraphic(Terminal *t, unsigned char code, int count) { struct line *l; int line, remain, from, to; from = t->cursor.col; line = t->pagebuf.col_count - from; if (count > line) count = line; remain = line - count; if (remain) { to = from + count; l = &t->pagebuf.lines[t->cursor.line]; memmove(&l->cells[to], &l->cells[from], sizeof (struct cell) * remain); } while (count--) { l->cells[from].att = blank_char_att; l->cells[from++].glyph = code; } } void Terminal_putGraphic(Terminal *t, unsigned char code) { int *col = &t->cursor.col; int *line = &t->cursor.line; /* Cursor pointing just past the end of line means we either need to wrap or move back to the last position of the line */ if (*col == t->pagebuf.col_count) { if (t->modes.auto_wrap == 1) { Terminal_newLine(t); } else { *col -= 1; } } t->pagebuf.lines[*line].cells[*col].glyph = code; *col += 1; }