#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 #include #include #include #include #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, };