539 lines
11 KiB
C
539 lines
11 KiB
C
#ident "$Id: ansicon_write.c,v 1.9 2005/01/05 07:45:23 hpa Exp $"
|
|
/* ----------------------------------------------------------------------- *
|
|
*
|
|
* Copyright 2004 H. Peter Anvin - All Rights Reserved
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom
|
|
* the Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall
|
|
* be included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* ----------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* ansicon_write.c
|
|
*
|
|
* Write to the screen using ANSI control codes (about as capable as
|
|
* DOS' ANSI.SYS.)
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <com32.h>
|
|
#include <minmax.h>
|
|
#include <klibc/compiler.h>
|
|
#include "file.h"
|
|
|
|
struct curxy {
|
|
uint8_t x, y;
|
|
} __attribute__((packed));
|
|
#define BIOS_CURXY ((struct curxy *)0x450) /* Array for each page */
|
|
#define BIOS_ROWS (*(uint8_t *)0x484) /* Minus one; if zero use 24 (= 25 lines) */
|
|
#define BIOS_COLS (*(uint16_t *)0x44A)
|
|
#define BIOS_PAGE (*(uint8_t *)0x462)
|
|
|
|
enum ansi_state {
|
|
st_init, /* Normal (no ESC seen) */
|
|
st_esc, /* ESC seen */
|
|
st_csi, /* CSI seen */
|
|
};
|
|
|
|
#define MAX_PARMS 16
|
|
|
|
struct term_state {
|
|
int disabled;
|
|
int attr; /* Current display attribute */
|
|
int vtgraphics; /* VT graphics on/off */
|
|
int intensity;
|
|
int underline;
|
|
int blink;
|
|
int reverse;
|
|
int fg;
|
|
int bg;
|
|
int autocr;
|
|
struct curxy saved_xy;
|
|
uint16_t cursor_type;
|
|
enum ansi_state state;
|
|
int pvt; /* Private code? */
|
|
int nparms; /* Number of parameters seen */
|
|
int parms[MAX_PARMS];
|
|
};
|
|
|
|
static const struct term_state default_state =
|
|
{
|
|
.disabled = 0,
|
|
.attr = 0x07, /* Grey on black */
|
|
.vtgraphics = 0,
|
|
.intensity = 1,
|
|
.underline = 0,
|
|
.blink = 0,
|
|
.reverse = 0,
|
|
.fg = 7,
|
|
.bg = 0,
|
|
.autocr = 0,
|
|
.saved_xy = { 0, 0 },
|
|
.cursor_type = 0x0607,
|
|
.state = st_init,
|
|
.pvt = 0,
|
|
.nparms = 0,
|
|
};
|
|
|
|
static struct term_state st;
|
|
|
|
/* DEC VT graphics to codepage 437 table (characters 0x60-0x7F only) */
|
|
static const char decvt_to_cp437[] =
|
|
{ 0004, 0261, 0007, 0007, 0007, 0007, 0370, 0361, 0007, 0007, 0331, 0277, 0332, 0300, 0305, 0304,
|
|
0304, 0304, 0137, 0137, 0303, 0264, 0301, 0302, 0263, 0363, 0362, 0343, 0330, 0234, 0007, 00 };
|
|
|
|
/* Common setup */
|
|
static void __constructor ansicon_init(void)
|
|
{
|
|
static com32sys_t ireg; /* Auto-initalized to all zero */
|
|
com32sys_t oreg;
|
|
|
|
/* Initial state */
|
|
memcpy(&st, &default_state, sizeof st);
|
|
|
|
/* Are we disabled? */
|
|
ireg.eax.w[0] = 0x000b;
|
|
__intcall(0x22, &ireg, &oreg);
|
|
|
|
if ( (signed char)oreg.ebx.b[1] < 0 ) {
|
|
st.disabled = 1;
|
|
return;
|
|
}
|
|
|
|
/* Force text mode */
|
|
ireg.eax.w[0] = 0x0005;
|
|
__intcall(0x22, &ireg, NULL);
|
|
|
|
/* Get cursor shape */
|
|
ireg.eax.b[1] = 0x03;
|
|
ireg.ebx.b[1] = BIOS_PAGE;
|
|
__intcall(0x10, &ireg, &oreg);
|
|
st.cursor_type = oreg.ecx.w[0];
|
|
}
|
|
|
|
/* Erase a region of the screen */
|
|
static void ansicon_erase(int x0, int y0, int x1, int y1)
|
|
{
|
|
static com32sys_t ireg;
|
|
|
|
ireg.eax.w[0] = 0x0600; /* Clear window */
|
|
ireg.ebx.b[1] = st.attr; /* Fill with current attribute */
|
|
ireg.ecx.b[0] = x0;
|
|
ireg.ecx.b[1] = y0;
|
|
ireg.edx.b[0] = x1;
|
|
ireg.edx.b[1] = y1;
|
|
__intcall(0x10, &ireg, NULL);
|
|
}
|
|
|
|
/* Show or hide the cursor */
|
|
static void showcursor(int yes)
|
|
{
|
|
static com32sys_t ireg;
|
|
|
|
ireg.eax.b[1] = 0x01;
|
|
ireg.ecx.w[0] = yes ? st.cursor_type : 0x2020;
|
|
__intcall(0x10, &ireg, NULL);
|
|
}
|
|
|
|
static void ansicon_putchar(int ch)
|
|
{
|
|
static com32sys_t ireg;
|
|
const int rows = BIOS_ROWS ? BIOS_ROWS+1 : 25;
|
|
const int cols = BIOS_COLS;
|
|
const int page = BIOS_PAGE;
|
|
struct curxy xy = BIOS_CURXY[page];
|
|
|
|
switch ( st.state ) {
|
|
case st_init:
|
|
switch ( ch ) {
|
|
case '\b':
|
|
if ( xy.x > 0 ) xy.x--;
|
|
break;
|
|
case '\t':
|
|
{
|
|
int nsp = 8 - (xy.x & 7);
|
|
while ( nsp-- )
|
|
ansicon_putchar(' ');
|
|
}
|
|
return; /* Cursor already updated */
|
|
case '\n':
|
|
case '\v':
|
|
case '\f':
|
|
xy.y++;
|
|
if ( st.autocr )
|
|
xy.x = 0;
|
|
break;
|
|
case '\r':
|
|
xy.x = 0;
|
|
break;
|
|
case 127:
|
|
/* Ignore delete */
|
|
break;
|
|
case 14:
|
|
st.vtgraphics = 1;
|
|
break;
|
|
case 15:
|
|
st.vtgraphics = 0;
|
|
break;
|
|
case 27:
|
|
st.state = st_esc;
|
|
break;
|
|
default:
|
|
/* Print character */
|
|
if ( ch >= 32 ) {
|
|
if ( st.vtgraphics && (ch & 0xe0) == 0x60 )
|
|
ch = decvt_to_cp437[ch - 0x60];
|
|
|
|
ireg.eax.b[1] = 0x09;
|
|
ireg.eax.b[0] = ch;
|
|
ireg.ebx.b[1] = page;
|
|
ireg.ebx.b[0] = st.attr;
|
|
ireg.ecx.w[0] = 1;
|
|
__intcall(0x10, &ireg, NULL);
|
|
xy.x++;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case st_esc:
|
|
switch ( ch ) {
|
|
case '%':
|
|
case '(':
|
|
case ')':
|
|
case '#':
|
|
/* Ignore this plus the subsequent character, allows
|
|
compatibility with Linux sequence to set charset */
|
|
break;
|
|
case '[':
|
|
st.state = st_csi;
|
|
st.nparms = st.pvt = 0;
|
|
memset(st.parms, 0, sizeof st.parms);
|
|
break;
|
|
case 'c':
|
|
/* Reset terminal */
|
|
memcpy(&st, &default_state, sizeof st);
|
|
ansicon_erase(0, 0, cols-1, rows-1);
|
|
xy.x = xy.y = 1;
|
|
break;
|
|
default:
|
|
/* Ignore sequence */
|
|
st.state = st_init;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case st_csi:
|
|
{
|
|
int p0 = st.parms[0] ? st.parms[0] : 1;
|
|
|
|
if ( ch >= '0' && ch <= '9' ) {
|
|
st.parms[st.nparms] = st.parms[st.nparms]*10 + (ch-'0');
|
|
} else if ( ch == ';' ) {
|
|
st.nparms++;
|
|
if ( st.nparms >= MAX_PARMS )
|
|
st.nparms = MAX_PARMS-1;
|
|
break;
|
|
} else if ( ch == '?' ) {
|
|
st.pvt = 1;
|
|
} else {
|
|
switch ( ch ) {
|
|
case 'A':
|
|
{
|
|
int y = xy.y - p0;
|
|
xy.y = (y < 0) ? 0 : y;
|
|
}
|
|
break;
|
|
case 'B':
|
|
{
|
|
int y = xy.y + p0;
|
|
xy.y = (y >= rows) ? rows-1 : y;
|
|
}
|
|
break;
|
|
case 'C':
|
|
{
|
|
int x = xy.x + p0;
|
|
xy.x = (x >= cols) ? cols-1 : x;
|
|
}
|
|
break;
|
|
case 'D':
|
|
{
|
|
int x = xy.x - p0;
|
|
xy.x = (x < 0) ? 0 : x;
|
|
}
|
|
break;
|
|
case 'E':
|
|
{
|
|
int y = xy.y + p0;
|
|
xy.y = (y >= rows) ? rows-1 : y;
|
|
xy.x = 0;
|
|
}
|
|
break;
|
|
case 'F':
|
|
{
|
|
int y = xy.y - p0;
|
|
xy.y = (y < 0) ? 0 : y;
|
|
xy.x = 0;
|
|
}
|
|
break;
|
|
case 'G':
|
|
case '\'':
|
|
{
|
|
int x = st.parms[0] - 1;
|
|
xy.x = (x >= cols) ? cols-1 : (x < 0) ? 0 : x;
|
|
}
|
|
break;
|
|
case 'H':
|
|
case 'f':
|
|
{
|
|
int y = st.parms[0] - 1;
|
|
int x = st.parms[1] - 1;
|
|
|
|
xy.x = (x >= cols) ? cols-1 : (x < 0) ? 0 : x;
|
|
xy.y = (y >= rows) ? rows-1 : (y < 0) ? 0 : y;
|
|
}
|
|
break;
|
|
case 'J':
|
|
{
|
|
switch ( st.parms[0] ) {
|
|
case 0:
|
|
ansicon_erase(xy.x, xy.y, cols-1, xy.y);
|
|
if ( xy.y < rows-1 )
|
|
ansicon_erase(0, xy.y+1, cols-1, rows-1);
|
|
break;
|
|
|
|
case 1:
|
|
if ( xy.y > 0 )
|
|
ansicon_erase(0, 0, cols-1, xy.y-1);
|
|
if ( xy.y > 0 )
|
|
ansicon_erase(0, xy.y, xy.x-1, xy.y);
|
|
break;
|
|
|
|
case 2:
|
|
ansicon_erase(0, 0, cols-1, rows-1);
|
|
break;
|
|
|
|
default:
|
|
/* Ignore */
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'K':
|
|
{
|
|
switch ( st.parms[0] ) {
|
|
case 0:
|
|
ansicon_erase(xy.x, xy.y, cols-1, xy.y);
|
|
break;
|
|
|
|
case 1:
|
|
if ( xy.x > 0 )
|
|
ansicon_erase(0, xy.y, xy.x-1, xy.y);
|
|
break;
|
|
|
|
case 2:
|
|
ansicon_erase(0, xy.y, cols-1, xy.y);
|
|
break;
|
|
|
|
default:
|
|
/* Ignore */
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'h':
|
|
case 'l':
|
|
{
|
|
int set = (ch == 'h');
|
|
switch ( st.parms[0] ) {
|
|
case 20:
|
|
st.autocr = set;
|
|
break;
|
|
case 25:
|
|
showcursor(set);
|
|
break;
|
|
default:
|
|
/* Ignore */
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'm':
|
|
{
|
|
static const int ansi2pc[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
|
|
|
|
int i;
|
|
for ( i = 0 ; i <= st.nparms ; i++ ) {
|
|
int a = st.parms[i];
|
|
switch ( a ) {
|
|
case 0:
|
|
st.fg = 7;
|
|
st.bg = 0;
|
|
st.intensity = 1;
|
|
st.underline = 0;
|
|
st.blink = 0;
|
|
st.reverse = 0;
|
|
break;
|
|
case 1:
|
|
st.intensity = 2;
|
|
break;
|
|
case 2:
|
|
st.intensity = 0;
|
|
break;
|
|
case 4:
|
|
st.underline = 1;
|
|
break;
|
|
case 5:
|
|
st.blink = 1;
|
|
break;
|
|
case 7:
|
|
st.reverse = 1;
|
|
break;
|
|
case 21:
|
|
case 22:
|
|
st.intensity = 1;
|
|
break;
|
|
case 24:
|
|
st.underline = 0;
|
|
break;
|
|
case 25:
|
|
st.blink = 0;
|
|
break;
|
|
case 27:
|
|
st.reverse = 0;
|
|
break;
|
|
case 30 ... 37:
|
|
st.fg = ansi2pc[a-30];
|
|
break;
|
|
case 38:
|
|
st.fg = 7;
|
|
st.underline = 1;
|
|
break;
|
|
case 39:
|
|
st.fg = 7;
|
|
st.underline = 0;
|
|
break;
|
|
case 40 ... 47:
|
|
st.bg = ansi2pc[a-40];
|
|
break;
|
|
case 49:
|
|
st.bg = 7;
|
|
break;
|
|
default:
|
|
/* Do nothing */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Turn into an attribute code */
|
|
{
|
|
int bg = st.bg;
|
|
int fg;
|
|
|
|
if ( st.underline )
|
|
fg = 0x01;
|
|
else if ( st.intensity == 0 )
|
|
fg = 0x08;
|
|
else
|
|
fg = st.fg;
|
|
|
|
if ( st.reverse ) {
|
|
bg = fg & 0x07;
|
|
fg &= 0x08;
|
|
fg |= st.bg;
|
|
}
|
|
|
|
if ( st.blink )
|
|
bg ^= 0x08;
|
|
|
|
if ( st.intensity == 2 )
|
|
fg ^= 0x08;
|
|
|
|
st.attr = (bg << 4) | fg;
|
|
}
|
|
}
|
|
break;
|
|
case 's':
|
|
st.saved_xy = xy;
|
|
break;
|
|
case 'u':
|
|
xy = st.saved_xy;
|
|
break;
|
|
default: /* Includes CAN and SUB */
|
|
break; /* Drop unknown sequence */
|
|
}
|
|
st.state = st_init;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* If we fell off the end of the screen, adjust */
|
|
if ( xy.x >= cols ) {
|
|
xy.x = 0;
|
|
xy.y++;
|
|
}
|
|
while ( xy.y >= rows ) {
|
|
xy.y--;
|
|
ireg.eax.w[0] = 0x0601;
|
|
ireg.ebx.b[1] = st.attr;
|
|
ireg.ecx.w[0] = 0;
|
|
ireg.edx.b[1] = rows-1;
|
|
ireg.edx.b[0] = cols-1;
|
|
__intcall(0x10, &ireg, NULL); /* Scroll */
|
|
}
|
|
|
|
/* Update cursor position */
|
|
ireg.eax.b[1] = 0x02;
|
|
ireg.ebx.b[1] = page;
|
|
ireg.edx.b[1] = xy.y;
|
|
ireg.edx.b[0] = xy.x;
|
|
__intcall(0x10, &ireg, NULL);
|
|
}
|
|
|
|
|
|
ssize_t __ansicon_write(struct file_info *fp, const void *buf, size_t count)
|
|
{
|
|
const unsigned char *bufp = buf;
|
|
size_t n = 0;
|
|
|
|
(void)fp;
|
|
|
|
if ( st.disabled )
|
|
return n; /* Nothing to do */
|
|
|
|
while ( count-- ) {
|
|
ansicon_putchar(*bufp++);
|
|
n++;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
const struct output_dev dev_ansicon_w = {
|
|
.dev_magic = __DEV_MAGIC,
|
|
.flags = __DEV_TTY | __DEV_OUTPUT,
|
|
.fileflags = O_WRONLY | O_CREAT | O_TRUNC | O_APPEND,
|
|
.write = __ansicon_write,
|
|
.close = NULL,
|
|
};
|