diff --git a/Makefile b/Makefile index 7f22266a0..9d0a91e16 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,8 @@ include third_party/xed/xed.mk # │ include third_party/zlib/zlib.mk # │ include libc/elf/elf.mk # │ include ape/ape.mk # │ -include libc/fmt/fmt.mk #─┘ +include libc/fmt/fmt.mk # │ +include libc/vga/vga.mk #─┘ include libc/calls/calls.mk #─┐ include libc/runtime/runtime.mk # ├──SYSTEMS RUNTIME include libc/crt/crt.mk # │ You can issue system calls @@ -318,7 +319,8 @@ COSMOPOLITAN_OBJECTS = \ LIBC_SYSV \ LIBC_INTRIN \ LIBC_NT_KERNEL32 \ - LIBC_NEXGEN32E + LIBC_NEXGEN32E \ + LIBC_VGA COSMOPOLITAN_HEADERS = \ APE \ @@ -343,6 +345,7 @@ COSMOPOLITAN_HEADERS = \ LIBC_TINYMATH \ LIBC_X \ LIBC_ZIPOS \ + LIBC_VGA \ NET_HTTP \ THIRD_PARTY_DLMALLOC \ THIRD_PARTY_GDTOA \ diff --git a/examples/examples.mk b/examples/examples.mk index 1deecdb80..1f1134f09 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -66,6 +66,7 @@ EXAMPLES_DIRECTDEPS = \ LIBC_THREAD \ LIBC_TIME \ LIBC_TINYMATH \ + LIBC_VGA \ LIBC_X \ LIBC_ZIPOS \ NET_HTTP \ diff --git a/examples/hello4.c b/examples/hello4.c index 960c530a9..f3aecca99 100644 --- a/examples/hello4.c +++ b/examples/hello4.c @@ -10,6 +10,8 @@ #include "libc/math.h" #include "libc/stdio/stdio.h" +STATIC_YOINK("vga_console"); + int main(int argc, char *argv[]) { volatile long double x = -.5; volatile long double y = 1.5; diff --git a/libc/calls/writev-metal.c b/libc/calls/writev-metal.c index c94263782..7bc473d75 100644 --- a/libc/calls/writev-metal.c +++ b/libc/calls/writev-metal.c @@ -19,10 +19,16 @@ #include "libc/calls/struct/fd.internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" +#include "libc/intrin/weaken.h" #include "libc/sysv/errfuns.h" +#include "libc/vga/vga.internal.h" ssize_t sys_writev_metal(struct Fd *fd, const struct iovec *iov, int iovlen) { switch (fd->kind) { + case kFdConsole: + if (weaken(sys_writev_vga)) + weaken(sys_writev_vga)(fd, iov, iovlen); + /* fallthrough */ case kFdSerial: return sys_writev_serial(fd, iov, iovlen); default: diff --git a/libc/intrin/g_fds.c b/libc/intrin/g_fds.c index 118a33142..b90a1435b 100644 --- a/libc/intrin/g_fds.c +++ b/libc/intrin/g_fds.c @@ -21,6 +21,7 @@ #include "libc/intrin/pthread.h" #include "libc/intrin/pushpop.h" #include "libc/intrin/spinlock.h" +#include "libc/intrin/weaken.h" #include "libc/nt/runtime.h" #include "libc/sysv/consts/o.h" @@ -55,10 +56,17 @@ textstartup void InitializeFileDescriptors(void) { fds->f = 3; fds->p = fds->__init_p; if (IsMetal()) { + extern const char vga_console[]; pushmov(&fds->f, 3ull); - fds->__init_p[0].kind = pushpop(kFdSerial); - fds->__init_p[1].kind = pushpop(kFdSerial); - fds->__init_p[2].kind = pushpop(kFdSerial); + if (weaken(vga_console)) { + fds->__init_p[0].kind = pushpop(kFdConsole); + fds->__init_p[1].kind = pushpop(kFdConsole); + fds->__init_p[2].kind = pushpop(kFdConsole); + } else { + fds->__init_p[0].kind = pushpop(kFdSerial); + fds->__init_p[1].kind = pushpop(kFdSerial); + fds->__init_p[2].kind = pushpop(kFdSerial); + } fds->__init_p[0].handle = VEIL("r", 0x3F8ull); fds->__init_p[1].handle = VEIL("r", 0x3F8ull); fds->__init_p[2].handle = VEIL("r", 0x3F8ull); diff --git a/libc/libc.mk b/libc/libc.mk index 3dde7c5f4..de254707c 100644 --- a/libc/libc.mk +++ b/libc/libc.mk @@ -30,6 +30,7 @@ o/$(MODE)/libc: o/$(MODE)/libc/calls \ o/$(MODE)/libc/thread \ o/$(MODE)/libc/time \ o/$(MODE)/libc/tinymath \ + o/$(MODE)/libc/vga \ o/$(MODE)/libc/x \ o/$(MODE)/libc/zipos \ $(LIBC_CHECKS) diff --git a/libc/vga/tty.c b/libc/vga/tty.c new file mode 100644 index 000000000..68b3190e8 --- /dev/null +++ b/libc/vga/tty.c @@ -0,0 +1,1216 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/fmt/bing.internal.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/bits.h" +#include "libc/intrin/safemacros.internal.h" +#include "libc/runtime/pc.internal.h" +#include "libc/str/str.h" +#include "libc/str/thompike.h" +#include "libc/str/tpenc.h" +#include "libc/str/unicode.h" +#include "libc/sysv/errfuns.h" +#include "libc/vga/vga.internal.h" + +/** + * @fileoverview ECMA-48 / VT100 video terminal implementation for bare + * metal VGA. + * + * The code is mainly a pared-down version of the implementation of a + * pseudo-tty in tool/build/lib/pty.c. + * + * @see tool/build/lib/pty.c + */ + +#define DEFAULT_FG 7 +#define DEFAULT_BG 0 +#define CRTPORT 0x3d4 + +static void SetYn(struct Tty *tty, unsigned short yn) { +#ifndef VGA_TTY_HEIGHT + tty->yn = yn; +#endif +} + +static void SetXn(struct Tty *tty, unsigned short xn) { +#ifndef VGA_TTY_WIDTH + tty->xn = xn; +#endif +} + +static wchar_t * SetWcs(struct Tty *tty, wchar_t *wcs) +{ +#ifdef VGA_USE_WCS + tty->wcs = wcs; + return wcs; +#else + return NULL; +#endif +} + +static size_t Yn(struct Tty *tty) +{ +#ifdef VGA_TTY_HEIGHT + return VGA_TTY_HEIGHT; +#else + return tty->yn; +#endif +} + +static size_t Xn(struct Tty *tty) +{ +#ifdef VGA_TTY_WIDTH + return VGA_TTY_WIDTH; +#else + return tty->xn; +#endif +} + +static wchar_t *Wcs(struct Tty *tty) +{ +#ifdef VGA_USE_WCS + return tty->wcs; +#else + return NULL; +#endif +} + +void _StartTty(struct Tty *tty, unsigned short yn, unsigned short xn, + unsigned short starty, unsigned short startx, + void *vccs, wchar_t *wcs) { + struct VgaTextCharCell *ccs = vccs; + memset(tty, 0, sizeof(struct Tty)); + SetYn(tty, yn); + SetXn(tty, xn); + tty->ccs = ccs; + if (starty >= yn) + starty = yn - 1; + if (startx >= xn) + startx = xn - 1; + tty->y = starty; + tty->x = startx; + if (SetWcs(tty, wcs)) { + size_t n = (size_t)yn * xn, i; + for (i = 0; i < n; ++i) + wcs[i] = bing(ccs[i].ch, 0); + } + _TtyResetOutputMode(tty); +} + +static wchar_t *GetXlatAscii(void) { + unsigned i; + static bool once; + static wchar_t xlat[128]; + if (!once) { + for (i = 0; i < 128; ++i) { + xlat[i] = i; + } + once = true; + } + return xlat; +} + +static wchar_t *GetXlatLineDrawing(void) { + unsigned i; + static bool once; + static wchar_t xlat[128]; + if (!once) { + for (i = 0; i < 128; ++i) { + if (0x5F <= i && i <= 0x7E) { + xlat[i] = u" ◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·"[i - 0x5F]; + } else { + xlat[i] = i; + } + } + once = true; + } + return xlat; +} + +static void XlatAlphabet(wchar_t xlat[128], int a, int b) { + unsigned i; + for (i = 0; i < 128; ++i) { + if ('a' <= i && i <= 'z') { + xlat[i] = i - 'a' + a; + } else if ('A' <= i && i <= 'Z') { + xlat[i] = i - 'A' + b; + } else { + xlat[i] = i; + } + } +} + +static wchar_t *GetXlatItalic(void) { + static bool once; + static wchar_t xlat[128]; + if (!once) { + XlatAlphabet(xlat, L'𝑎', L'𝐴'); + once = true; + } + return xlat; +} + +static wchar_t *GetXlatBoldItalic(void) { + static bool once; + static wchar_t xlat[128]; + if (!once) { + XlatAlphabet(xlat, L'𝒂', L'𝑨'); + once = true; + } + return xlat; +} + +static wchar_t *GetXlatBoldFraktur(void) { + static bool once; + static wchar_t xlat[128]; + if (!once) { + XlatAlphabet(xlat, L'𝖆', L'𝕬'); + once = true; + } + return xlat; +} + +static wchar_t *GetXlatFraktur(void) { + unsigned i; + static bool once; + static wchar_t xlat[128]; + if (!once) { + for (i = 0; i < ARRAYLEN(xlat); ++i) { + if ('A' <= i && i <= 'Z') { + xlat[i] = L"𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ"[i - 'A']; + } else if ('a' <= i && i <= 'z') { + xlat[i] = i - 'a' + L'𝔞'; + } else { + xlat[i] = i; + } + } + once = true; + } + return xlat; +} + +static wchar_t *GetXlatDoubleWidth(void) { + unsigned i; + static bool once; + static wchar_t xlat[128]; + if (!once) { + for (i = 0; i < ARRAYLEN(xlat); ++i) { + if ('!' <= i && i <= '~') { + xlat[i] = -(i - '!' + L'!'); + } else { + xlat[i] = i; + } + } + once = true; + } + return xlat; +} + +static wchar_t *GetXlatSgr(struct Tty *tty) { + switch (!!(tty->pr & kTtyFraktur) << 2 | !!(tty->pr & kTtyItalic) << 1 | + !!(tty->pr & kTtyBold) << 0) { + case 0b100: + case 0b110: + return GetXlatFraktur(); + case 0b101: + case 0b111: + return GetXlatBoldFraktur(); + case 0b011: + return GetXlatBoldItalic(); + case 0b010: + return GetXlatItalic(); + default: + return GetXlatAscii(); + } +} + +static void TtySetXlat(struct Tty *tty, wchar_t *xlat) { + tty->xlat = xlat; + tty->pr &= ~(kTtyItalic | kTtyFraktur); +} + +static void TtySetCodepage(struct Tty *tty, char id) { + unsigned i; + switch (id) { + default: + case 'B': + TtySetXlat(tty, GetXlatAscii()); + break; + case '0': + TtySetXlat(tty, GetXlatLineDrawing()); + break; + } +} + +static uint8_t TtyGetVgaAttr(struct Tty *tty) +{ + uint8_t attr = tty->fg | tty->bg << 4; + if ((tty->pr & kTtyBold) != 0) + attr |= 0x08; + if ((tty->pr & kTtyBlink) != 0) + attr |= 0x80; + return attr; +} + +void _TtyErase(struct Tty *tty, size_t dst, size_t n) { + uint8_t attr = TtyGetVgaAttr(tty); + size_t i; + for (i = 0; i < n; ++i) + tty->ccs[dst + i] = (struct VgaTextCharCell){ ' ', attr }; + if (Wcs(tty)) + wmemset(Wcs(tty) + dst, L' ', n); +} + +void _TtyMemmove(struct Tty *tty, size_t dst, size_t src, size_t n) { + memmove(tty->ccs + dst, tty->ccs + src, n * sizeof(struct VgaTextCharCell)); + if (Wcs(tty)) + wmemmove(Wcs(tty) + dst, Wcs(tty) + src, n); +} + +void _TtyResetOutputMode(struct Tty *tty) { + tty->pr = 0; + tty->fg = DEFAULT_FG; + tty->bg = DEFAULT_BG; + tty->u8 = 0; + tty->n8 = 0; + tty->conf = 0; + tty->savey = 0; + tty->savex = 0; + tty->state = 0; + tty->esc.i = 0; + tty->input.i = 0; + tty->xlat = GetXlatAscii(); +} + +void _TtyFullReset(struct Tty *tty) { + _TtyResetOutputMode(tty); + tty->y = 0; + tty->x = 0; + _TtyErase(tty, 0, Yn(tty) * Xn(tty)); +} + +void _TtySetY(struct Tty *tty, unsigned short y) { + tty->conf &= ~kTtyRedzone; + tty->y = MAX(0, MIN(Yn(tty) - 1, y)); +} + +void _TtySetX(struct Tty *tty, unsigned short x) { + tty->conf &= ~kTtyRedzone; + tty->x = MAX(0, MIN(Xn(tty) - 1, x)); +} + +static void TtyScroll(struct Tty *tty) { + _TtyMemmove(tty, 0, Xn(tty), Xn(tty) * (Yn(tty) - 1)); + _TtyErase(tty, Xn(tty) * (Yn(tty) - 1), Xn(tty)); +} + +static void TtyReverse(struct Tty *tty) { + _TtyMemmove(tty, Xn(tty), 0, Xn(tty) * (Yn(tty) - 1)); + _TtyErase(tty, 0, Xn(tty)); +} + +static void TtyIndex(struct Tty *tty) { + if (tty->y < Yn(tty) - 1) { + ++tty->y; + } else { + TtyScroll(tty); + } +} + +static void TtyReverseIndex(struct Tty *tty) { + if (tty->y) { + --tty->y; + } else { + TtyReverse(tty); + } +} + +static void TtyCarriageReturn(struct Tty *tty) { + _TtySetX(tty, 0); +} + +static void TtyNewline(struct Tty *tty) { + TtyIndex(tty); + if (!(tty->conf & kTtyNoopost)) { + TtyCarriageReturn(tty); + } +} + +static void TtyAdvance(struct Tty *tty) { + tty->conf &= ~kTtyRedzone; + tty->x = 0; + if (tty->y < Yn(tty) - 1) { + ++tty->y; + } else { + TtyScroll(tty); + } +} + +static void TtyWriteGlyph(struct Tty *tty, wint_t wc, int w) { + uint8_t attr = TtyGetVgaAttr(tty); + size_t i; + int c; + if (w < 1) + wc = L' ', w = 1; + if ((tty->conf & kTtyRedzone) || tty->x + w > Xn(tty)) { + TtyAdvance(tty); + } + i = tty->y * Xn(tty) + tty->x; + c = unbing(wc); + if (c == -1) + c = 0xFE; + tty->ccs[i] = (struct VgaTextCharCell){ c, attr }; + if (Wcs(tty)) + Wcs(tty)[i] = wc; + if ((tty->x += w) >= Xn(tty)) { + tty->x = Xn(tty) - 1; + tty->conf |= kTtyRedzone; + } +} + +static void TtyWriteTab(struct Tty *tty) { + uint8_t attr = TtyGetVgaAttr(tty); + unsigned x, x2; + if (tty->conf & kTtyRedzone) { + TtyAdvance(tty); + } + x2 = MIN(Xn(tty), ROUNDUP(tty->x + 1, 8)); + for (x = tty->x; x < x2; ++x) { + tty->ccs[tty->y * Xn(tty) + x] = (struct VgaTextCharCell){ ' ', attr }; + } + if (Wcs(tty)) { + for (x = tty->x; x < x2; ++x) { + Wcs(tty)[tty->y * Xn(tty) + x] = L' '; + } + } + if (x2 < Xn(tty)) { + tty->x = x2; + } else { + tty->x = Xn(tty) - 1; + tty->conf |= kTtyRedzone; + } +} + +int TtyAtoi(const char *s, const char **e) { + int i; + for (i = 0; isdigit(*s); ++s) i *= 10, i += *s - '0'; + if (e) *e = s; + return i; +} + +static int TtyGetMoveParam(struct Tty *tty) { + int x = TtyAtoi(tty->esc.s, NULL); + if (x < 1) x = 1; + return x; +} + +static void TtySetCursorPosition(struct Tty *tty) { + int row, col; + const char *s = tty->esc.s; + row = max(1, TtyAtoi(s, &s)); + if (*s == ';') ++s; + col = max(1, TtyAtoi(s, &s)); + _TtySetY(tty, row - 1); + _TtySetX(tty, col - 1); +} + +static void TtySetCursorRow(struct Tty *tty) { + _TtySetY(tty, TtyGetMoveParam(tty) - 1); +} + +static void TtySetCursorColumn(struct Tty *tty) { + _TtySetX(tty, TtyGetMoveParam(tty) - 1); +} + +static void TtyMoveCursor(struct Tty *tty, int dy, int dx) { + int n = TtyGetMoveParam(tty); + _TtySetY(tty, tty->y + dy * n); + _TtySetX(tty, tty->x + dx * n); +} + +static void TtyScrollUp(struct Tty *tty) { + int n = TtyGetMoveParam(tty); + while (n--) TtyScroll(tty); +} + +static void TtyScrollDown(struct Tty *tty) { + int n = TtyGetMoveParam(tty); + while (n--) TtyReverse(tty); +} + +static void TtySetCursorStatus(struct Tty *tty, bool status) { + if (status) { + tty->conf &= ~kTtyNocursor; + } else { + tty->conf |= kTtyNocursor; + } +} + +static void TtySetMode(struct Tty *tty, bool status) { + const char *p = tty->esc.s; + switch (*p++) { + case '?': + while (isdigit(*p)) { + switch (TtyAtoi(p, &p)) { + case 25: + TtySetCursorStatus(tty, status); + break; + default: + break; + } + if (*p == ';') { + ++p; + } + } + break; + default: + break; + } +} + +static void TtySaveCursorPosition(struct Tty *tty) { + tty->savey = tty->y; + tty->savex = tty->x; +} + +static void TtyRestoreCursorPosition(struct Tty *tty) { + _TtySetY(tty, tty->savey); + _TtySetX(tty, tty->savex); +} + +static void TtyEraseDisplay(struct Tty *tty) { + switch (TtyAtoi(tty->esc.s, NULL)) { + case 0: + _TtyErase(tty, tty->y * Xn(tty) + tty->x, + Yn(tty) * Xn(tty) - (tty->y * Xn(tty) + tty->x)); + break; + case 1: + _TtyErase(tty, 0, tty->y * Xn(tty) + tty->x); + break; + case 2: + case 3: + _TtyErase(tty, 0, Yn(tty) * Xn(tty)); + break; + default: + break; + } +} + +static void TtyEraseLine(struct Tty *tty) { + switch (TtyAtoi(tty->esc.s, NULL)) { + case 0: + _TtyErase(tty, tty->y * Xn(tty) + tty->x, Xn(tty) - tty->x); + break; + case 1: + _TtyErase(tty, tty->y * Xn(tty), tty->x); + break; + case 2: + _TtyErase(tty, tty->y * Xn(tty), Xn(tty)); + break; + default: + break; + } +} + +static void TtyEraseCells(struct Tty *tty) { + int i, n, x; + i = tty->y * Xn(tty) + tty->x; + n = Yn(tty) * Xn(tty); + x = min(max(TtyAtoi(tty->esc.s, NULL), 1), n - i); + _TtyErase(tty, i, x); +} + +static int TtyArg1(struct Tty *tty) { + return max(1, TtyAtoi(tty->esc.s, NULL)); +} + +static void TtyInsertCells(struct Tty *tty) { + int n = min(Xn(tty) - tty->x, TtyArg1(tty)); + _TtyMemmove(tty, tty->y * Xn(tty) + tty->x + n, tty->y * Xn(tty) + tty->x, + Xn(tty) - (tty->x + n)); + _TtyErase(tty, tty->y * Xn(tty) + tty->x, n); +} + +static void TtyInsertLines(struct Tty *tty) { + int n = min(Yn(tty) - tty->y, TtyArg1(tty)); + _TtyMemmove(tty, (tty->y + n) * Xn(tty), tty->y * Xn(tty), + (Yn(tty) - tty->y - n) * Xn(tty)); + _TtyErase(tty, tty->y * Xn(tty), n * Xn(tty)); +} + +static void TtyDeleteCells(struct Tty *tty) { + int n = min(Xn(tty) - tty->x, TtyArg1(tty)); + _TtyMemmove(tty, tty->y * Xn(tty) + tty->x, tty->y * Xn(tty) + tty->x + n, + Xn(tty) - (tty->x + n)); + _TtyErase(tty, tty->y * Xn(tty) + tty->x, n); +} + +static void TtyDeleteLines(struct Tty *tty) { + int n = min(Yn(tty) - tty->y, TtyArg1(tty)); + _TtyMemmove(tty, tty->y * Xn(tty), (tty->y + n) * Xn(tty), + (Yn(tty) - tty->y - n) * Xn(tty)); + _TtyErase(tty, (tty->y + n) * Xn(tty), n * Xn(tty)); +} + +static void TtyReportDeviceStatus(struct Tty *tty) { + _TtyWriteInput(tty, "\e[0n", 4); +} + +static void TtyReportPreferredVtType(struct Tty *tty) { + _TtyWriteInput(tty, "\e[?1;0c", 4); +} + +static void TtyReportPreferredVtIdentity(struct Tty *tty) { + _TtyWriteInput(tty, "\e/Z", 4); +} + +static void TtyBell(struct Tty *tty) { + tty->conf |= kTtyBell; +} + +static void TtyLed(struct Tty *tty) { + switch (TtyAtoi(tty->esc.s, NULL)) { + case 0: + tty->conf &= ~kTtyLed1; + tty->conf &= ~kTtyLed2; + tty->conf &= ~kTtyLed3; + tty->conf &= ~kTtyLed4; + break; + case 1: + tty->conf |= kTtyLed1; + break; + case 2: + tty->conf |= kTtyLed2; + break; + case 3: + tty->conf |= kTtyLed3; + break; + case 4: + tty->conf |= kTtyLed4; + break; + default: + break; + } +} + +static void TtyReportCursorPosition(struct Tty *tty) { + char *p; + char buf[2 + 10 + 1 + 10 + 1]; + p = buf; + *p++ = '\e'; + *p++ = '['; + p = FormatInt32(p, (tty->y + 1) & 0x7fff); + *p++ = ';'; + p = FormatInt32(p, (tty->x + 1) & 0x7fff); + *p++ = 'R'; + _TtyWriteInput(tty, buf, p - buf); +} + +static void TtyCsiN(struct Tty *tty) { + switch (TtyAtoi(tty->esc.s, NULL)) { + case 5: + TtyReportDeviceStatus(tty); + break; + case 6: + TtyReportCursorPosition(tty); + break; + default: + break; + } +} + +/** + * Map the given (R, G, B) triplet to one of the 16 basic foreground colors + * or one of the 8 background colors. + * + * @see drivers/tty/vt/vt.c in Linux 5.9.14 source code + */ +static uint8_t TtyMapTrueColor(uint8_t r, uint8_t g, uint8_t b, bool as_fg) +{ + uint8_t hue = 0; + if (as_fg) { + uint8_t max = MAX(MAX(r, g), b); + if (r > max / 2) + hue |= 4; + if (g > max / 2) + hue |= 2; + if (b > max / 2) + hue |= 1; + if (hue == 7 && max <= 0x55) + hue = 8; + else if (max > 0xaa) + hue |= 8; + } else { + if (r >= 128) + hue |= 4; + if (g >= 128) + hue |= 2; + if (b >= 128) + hue |= 1; + } + return hue; +} + +/** + * Map the given 256-color code one of the 16 basic foreground colors or one + * of the 8 background colors. + * + * @see drivers/tty/vt/vt.c in Linux 5.9.14 source code + */ +static uint8_t TtyMapXtermColor(uint8_t color, bool as_fg) +{ + uint8_t r, g, b; + if (color < 8) { + r = (color & 1) ? 0xaa : 0; + g = (color & 2) ? 0xaa : 0; + b = (color & 4) ? 0xaa : 0; + } else if (color < 16) { + r = (color & 1) ? 0xff : 0x55; + g = (color & 2) ? 0xff : 0x55; + b = (color & 4) ? 0xff : 0x55; + } else if (color < 232) { + color -= 16; + b = color % 6 * 0x55 / 2; + color /= 6; + g = color % 6 * 0x55 / 2; + color /= 6; + r = color * 0x55 / 2; + } else + r = g = b = (unsigned)color * 10 - 2312; + return TtyMapTrueColor(r, g, b, as_fg); +} + +static void TtySelectGraphicsRendition(struct Tty *tty) { + char *p, c; + unsigned x; + uint8_t code[4]; + enum { + kSgr, + kSgrFg = 010, + kSgrFgTrue = 012, + kSgrFgXterm = 015, + kSgrBg = 020, + kSgrBgTrue = 022, + kSgrBgXterm = 025, + } t; + x = 0; + t = kSgr; + p = tty->esc.s; + bzero(code, sizeof(code)); + for (;;) { + c = *p++; + switch (c) { + case '\0': + return; + case '0' ... '9': + x *= 10; + x += c - '0'; + break; + case ';': + case 'm': + code[code[3]] = x; + x = 0; + switch (t) { + case kSgr: + switch (code[0]) { + case 38: + t = kSgrFg; + break; + case 48: + t = kSgrBg; + break; + case 0: + tty->pr = 0; + tty->fg = DEFAULT_FG; + tty->bg = DEFAULT_BG; + tty->xlat = GetXlatSgr(tty); + break; + case 1: + tty->pr |= kTtyBold; + tty->xlat = GetXlatSgr(tty); + break; + case 2: + tty->pr |= kTtyFaint; + break; + case 3: + tty->pr |= kTtyItalic; + tty->xlat = GetXlatSgr(tty); + break; + case 4: + tty->pr |= kTtyUnder; + break; + case 5: + tty->pr |= kTtyBlink; + break; + case 7: + tty->pr |= kTtyFlip; + break; + case 8: + tty->pr |= kTtyConceal; + break; + case 9: + tty->pr |= kTtyStrike; + break; + case 20: + tty->pr |= kTtyFraktur; + tty->xlat = GetXlatSgr(tty); + break; + case 21: + tty->pr |= kTtyUnder | kTtyDunder; + break; + case 22: + tty->pr &= ~(kTtyFaint | kTtyBold); + tty->xlat = GetXlatSgr(tty); + break; + case 23: + tty->pr &= ~kTtyItalic; + tty->xlat = GetXlatSgr(tty); + break; + case 24: + tty->pr &= ~(kTtyUnder | kTtyDunder); + break; + case 25: + tty->pr &= ~kTtyBlink; + break; + case 27: + tty->pr &= ~kTtyFlip; + break; + case 28: + tty->pr &= ~kTtyConceal; + break; + case 29: + tty->pr &= ~kTtyStrike; + break; + case 39: + tty->fg = DEFAULT_FG; + tty->pr &= ~kTtyFg; + break; + case 49: + tty->bg = DEFAULT_BG; + tty->pr &= ~kTtyBg; + break; + case 90 ... 97: + code[0] -= 90 - 30; + code[0] += 8; + /* fallthrough */ + case 30 ... 37: + tty->fg = code[0] - 30; + tty->pr |= kTtyFg; + tty->pr &= ~kTtyTrue; + break; + case 100 ... 107: + code[0] -= 100 - 40; + /* fallthrough */ + case 40 ... 47: + tty->bg = code[0] - 40; + tty->pr |= kTtyBg; + tty->pr &= ~kTtyTrue; + break; + default: + break; + } + break; + case kSgrFg: + case kSgrBg: + switch (code[0]) { + case 2: + case 5: + t += code[0]; + break; + default: + t = kSgr; + break; + } + break; + case kSgrFgTrue: + if (++code[3] == 3) { + code[3] = 0; + t = kSgr; + tty->fg = TtyMapTrueColor(code[0], code[1], code[2], true); + tty->pr |= kTtyFg; + tty->pr |= kTtyTrue; + } + break; + case kSgrBgTrue: + if (++code[3] == 3) { + code[3] = 0; + t = kSgr; + tty->bg = TtyMapTrueColor(code[0], code[1], code[2], false); + tty->pr |= kTtyBg; + tty->pr |= kTtyTrue; + } + break; + case kSgrFgXterm: + t = kSgr; + tty->fg = TtyMapXtermColor(code[0], true); + tty->pr |= kTtyFg; + tty->pr &= ~kTtyTrue; + break; + case kSgrBgXterm: + t = kSgr; + tty->bg = TtyMapXtermColor(code[0], false); + tty->pr |= kTtyBg; + tty->pr &= ~kTtyTrue; + break; + default: + abort(); + } + break; + default: + break; + } + } +} + +static void TtyCsi(struct Tty *tty) { + switch (tty->esc.s[tty->esc.i - 1]) { + case 'f': + case 'H': + TtySetCursorPosition(tty); + break; + case 'G': + TtySetCursorColumn(tty); + break; + case 'd': + TtySetCursorRow(tty); + break; + case 'F': + tty->x = 0; + /* fallthrough */ + case 'A': + TtyMoveCursor(tty, -1, +0); + break; + case 'E': + tty->x = 0; + /* fallthrough */ + case 'B': + TtyMoveCursor(tty, +1, +0); + break; + case 'C': + TtyMoveCursor(tty, +0, +1); + break; + case 'D': + TtyMoveCursor(tty, +0, -1); + break; + case 'S': + TtyScrollUp(tty); + break; + case 'T': + TtyScrollDown(tty); + break; + case '@': + TtyInsertCells(tty); + break; + case 'P': + TtyDeleteCells(tty); + break; + case 'L': + TtyInsertLines(tty); + break; + case 'M': + TtyDeleteLines(tty); + break; + case 'J': + TtyEraseDisplay(tty); + break; + case 'K': + TtyEraseLine(tty); + break; + case 'X': + TtyEraseCells(tty); + break; + case 's': + TtySaveCursorPosition(tty); + break; + case 'u': + TtyRestoreCursorPosition(tty); + break; + case 'n': + TtyCsiN(tty); + break; + case 'm': + TtySelectGraphicsRendition(tty); + break; + case 'h': + TtySetMode(tty, true); + break; + case 'l': + TtySetMode(tty, false); + break; + case 'c': + TtyReportPreferredVtType(tty); + break; + case 'q': + TtyLed(tty); + break; + default: + break; + } +} + +static void TtyScreenAlignmentDisplay(struct Tty *tty) { + uint8_t attr = TtyGetVgaAttr(tty); + size_t n = Yn(tty) * Xn(tty), i; + for (i = 0; i < n; ++i) + tty->ccs[i] = (struct VgaTextCharCell){ 'E', attr }; + if (Wcs(tty)) + wmemset(Wcs(tty), L'E', n); +} + +static void TtyEscHash(struct Tty *tty) { + switch (tty->esc.s[1]) { + case '5': + TtySetXlat(tty, GetXlatAscii()); + break; + case '6': + TtySetXlat(tty, GetXlatDoubleWidth()); + break; + case '8': + TtyScreenAlignmentDisplay(tty); + break; + default: + break; + } +} + +static void TtyEsc(struct Tty *tty) { + switch (tty->esc.s[0]) { + case 'c': + _TtyFullReset(tty); + break; + case '7': + TtySaveCursorPosition(tty); + break; + case '8': + TtyRestoreCursorPosition(tty); + break; + case 'E': + tty->x = 0; + case 'D': + TtyIndex(tty); + break; + case 'M': + TtyReverseIndex(tty); + break; + case 'Z': + TtyReportPreferredVtIdentity(tty); + break; + case '(': + TtySetCodepage(tty, tty->esc.s[1]); + break; + case '#': + TtyEscHash(tty); + break; + default: + break; + } +} + +static void TtyCntrl(struct Tty *tty, int c01) { + switch (c01) { + case '\a': + TtyBell(tty); + break; + case 0x85: + case '\f': + case '\v': + case '\n': + TtyNewline(tty); + break; + case '\r': + TtyCarriageReturn(tty); + break; + case '\e': + tty->state = kTtyEsc; + tty->esc.i = 0; + break; + case '\t': + TtyWriteTab(tty); + break; + case 0x7F: + case '\b': + tty->x = MAX(0, tty->x - 1); + break; + case 0x84: + TtyIndex(tty); + break; + case 0x8D: + TtyReverseIndex(tty); + break; + case 0x9B: + tty->state = kTtyCsi; + break; + default: + break; + } +} + +static void TtyEscAppend(struct Tty *tty, char c) { + tty->esc.i = MIN(tty->esc.i + 1, ARRAYLEN(tty->esc.s) - 1); + tty->esc.s[tty->esc.i - 1] = c; + tty->esc.s[tty->esc.i - 0] = 0; +} + +static void TtyUpdateHwCursor(struct Tty *tty) { + unsigned short pos = tty->y * Xn(tty) + tty->x; + outb(CRTPORT, 0x0e); + outb(CRTPORT + 1, (unsigned char)(pos >> 8)); + outb(CRTPORT, 0x0f); + outb(CRTPORT + 1, (unsigned char)pos); +} + +ssize_t _TtyWrite(struct Tty *tty, const void *data, size_t n) { + int i; + wchar_t wc; + const uint8_t *p; + for (p = data, i = 0; i < n; ++i) { + switch (tty->state) { + case kTtyAscii: + if (0x00 <= p[i] && p[i] <= 0x7F) { + if (0x20 <= p[i] && p[i] <= 0x7E) { + if ((wc = tty->xlat[p[i]]) >= 0) { + TtyWriteGlyph(tty, wc, 1); + } else { + TtyWriteGlyph(tty, -wc, 2); + } + } else { + TtyCntrl(tty, p[i]); + } + } else if (!ThomPikeCont(p[i])) { + tty->state = kTtyUtf8; + tty->u8 = ThomPikeByte(p[i]); + tty->n8 = ThomPikeLen(p[i]) - 1; + } + break; + case kTtyUtf8: + if (ThomPikeCont(p[i])) { + tty->u8 = ThomPikeMerge(tty->u8, p[i]); + if (--tty->n8) break; + } + wc = tty->u8; + if ((0x00 <= wc && wc <= 0x1F) || (0x7F <= wc && wc <= 0x9F)) { + TtyCntrl(tty, wc); + } else { + TtyWriteGlyph(tty, wc, wcwidth(wc)); + } + tty->state = kTtyAscii; + tty->u8 = 0; + --i; + break; + case kTtyEsc: + if (p[i] == '[') { + tty->state = kTtyCsi; + } else if (0x30 <= p[i] && p[i] <= 0x7E) { + TtyEscAppend(tty, p[i]); + TtyEsc(tty); + tty->state = kTtyAscii; + } else if (0x20 <= p[i] && p[i] <= 0x2F) { + TtyEscAppend(tty, p[i]); + } else { + tty->state = kTtyAscii; + } + break; + case kTtyCsi: + TtyEscAppend(tty, p[i]); + switch (p[i]) { + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '0' ... '9': + break; + case '`': + case '~': + case '^': + case '@': + case '[': + case ']': + case '{': + case '}': + case '_': + case '|': + case '\\': + case 'A' ... 'Z': + case 'a' ... 'z': + TtyCsi(tty); + tty->state = kTtyAscii; + break; + default: + tty->state = kTtyAscii; + continue; + } + break; + default: + unreachable; + } + } + TtyUpdateHwCursor(tty); + return n; +} + +ssize_t _TtyWriteInput(struct Tty *tty, const void *data, size_t n) { + int c; + bool cr; + char *p; + const char *q; + size_t i, j, m; + q = data; + p = tty->input.p; + i = tty->input.i; + cr = i && p[i - 1] == '\r'; + for (j = 0; j < n && i < sizeof tty->input.p; ++j) { + c = q[j] & 255; + if (c == '\r') { + cr = true; + } else if (cr) { + if (c != '\n') { + p[i++] = '\n'; + } + cr = false; + } + p[i++] = c; + } + if (cr && i < sizeof tty->input.p) { + p[i++] = '\n'; + } + if (!(tty->conf & kTtyNoecho)) { + _TtyWrite(tty, p + tty->input.i, i - tty->input.i); + } + tty->input.i = i; + return n; +} + +ssize_t _TtyRead(struct Tty *tty, void *buf, size_t size) { + char *p; + size_t n; + n = MIN(size, tty->input.i); + if (!(tty->conf & kTtyNocanon)) { + if ((p = memchr(tty->input.p, '\n', n))) { + n = MIN(n, tty->input.p - p + 1); + } else { + n = 0; + } + } + memcpy(buf, tty->input.p, n); + memcpy(tty->input.p, tty->input.p + n, tty->input.i - n); + tty->input.i -= n; + return n; +} diff --git a/libc/vga/vga-init.S b/libc/vga/vga-init.S new file mode 100644 index 000000000..db00bb7c7 --- /dev/null +++ b/libc/vga/vga-init.S @@ -0,0 +1,66 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ This is free and unencumbered software released into the public domain. │ +│ │ +│ Anyone is free to copy, modify, publish, use, compile, sell, or │ +│ distribute this software, either in source code form or as a compiled │ +│ binary, for any purpose, commercial or non-commercial, and by any │ +│ means. │ +│ │ +│ In jurisdictions that recognize copyright laws, the author or authors │ +│ of this software dedicate any and all copyright interest in the │ +│ software to the public domain. We make this dedication for the benefit │ +│ of the public at large and to the detriment of our heirs and │ +│ successors. We intend this dedication to be an overt act of │ +│ relinquishment in perpetuity of all present and future rights to this │ +│ software under copyright law. │ +│ │ +│ 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 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. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.internal.h" + +// Code snippet for initializing the VGA video mode for bare metal. +// +// If a program requests VGA support (by yoinking vga_console), +// and it is started in bare metal mode, then try to ensure that +// the VGA monitor is in a known mode. This is easier to do while +// the program is still running in real mode. +// +// This module also ropes in the sys_writev_vga routine, which +// implements the actual VGA console output under x86-64 long mode. +// +// @see rlinit & .sort.text.real.init.* (ape/ape.S) +// @see ape/ape.lds +// @see sys_writev_vga (libc/vga/writev-vga.c) + .section .sort.text.real.init.2,"ax",@progbits + .code16 + mov $0x4f03,%ax # get current video mode via VESA + int $0x10 + cmp $0x004f,%ax # is VESA a thing here? + jz 1f + mov $0x0f,%ah # if not, get the video mode via a + int $0x10 # classical BIOS call + cbtw + xchgw %ax,%bx +1: mov $0x0003,%ax # check if we are in a 80 × ? × 16 + cmp %ax,%bx # text mode + jnz 2f + cmpb $25-1,0x0484 # check if number of screen rows + jnz 2f # (BDA.ROWS + 1) is 25; if so, then + mov $0x0500,%ax # just make sure we are on display + # page 0 +2: int $0x10 # otherwise, change the video mode + .previous + .code64 + .section .rodata,"a",@progbits +vga_console: + .endobj vga_console,globl,hidden + .previous + .yoink sys_writev_vga diff --git a/libc/vga/vga.internal.h b/libc/vga/vga.internal.h new file mode 100644 index 000000000..39cf3ff47 --- /dev/null +++ b/libc/vga/vga.internal.h @@ -0,0 +1,118 @@ +#ifndef COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_ + +/* + * VGA_TTY_HEIGHT, VGA_TTY_WIDTH, & VGA_USE_WCS are configuration knobs + * which can potentially be used to tweak the features to be compiled into + * our VGA teletypewriter support. + */ + +/** + * Height of the video screen, in character units. Undefine if the height + * may vary at runtime. + */ +#define VGA_TTY_HEIGHT 25 +/** + * Width of the video screen, in character units. Undefine if the width may + * vary at runtime. + */ +#define VGA_TTY_WIDTH 80 +/** + * If VGA_USE_WCS is defined, the tty code can maintain an array of the + * Unicode characters "underlying" the 8-bit (or 9-bit) characters that are + * actually displayed on the text screen. This can be used to implement + * something similar to Linux's /dev/vcsu* facility. + * + * @see lkml.kernel.org/lkml/204888.1529277815@turing-police.cc.vt.edu/T/ + */ +#undef VGA_USE_WCS + +#define kTtyFg 0x0001 +#define kTtyBg 0x0002 +#define kTtyBold 0x0004 +#define kTtyFlip 0x0008 +#define kTtyFaint 0x0010 +#define kTtyUnder 0x0020 +#define kTtyDunder 0x0040 +#define kTtyTrue 0x0080 +#define kTtyBlink 0x0100 +#define kTtyItalic 0x0200 +#define kTtyFraktur 0x0400 +#define kTtyStrike 0x0800 +#define kTtyConceal 0x1000 + +#define kTtyBell 0x001 +#define kTtyRedzone 0x002 +#define kTtyNocursor 0x004 +#define kTtyBlinkcursor 0x008 +#define kTtyNocanon 0x010 +#define kTtyNoecho 0x020 +#define kTtyNoopost 0x040 +#define kTtyLed1 0x080 +#define kTtyLed2 0x100 +#define kTtyLed3 0x200 +#define kTtyLed4 0x400 + +#if !(__ASSEMBLER__ + __LINKER__ + 0) +#include "libc/calls/struct/fd.internal.h" +#include "libc/calls/struct/iovec.h" +#include "libc/calls/struct/iovec.internal.h" + +COSMOPOLITAN_C_START_ + +struct VgaTextCharCell { + uint8_t ch, attr; +}; + +struct Tty { + unsigned short y, x; +#ifndef VGA_TTY_HEIGHT + unsigned short yn; +#endif +#ifndef VGA_TTY_WIDTH + unsigned short xn; +#endif + uint32_t u8; + uint32_t n8; + uint32_t pr; + uint8_t fg, bg; + uint32_t conf; + unsigned short savey, savex; + struct VgaTextCharCell *ccs; +#ifdef VGA_USE_WCS + wchar_t *wcs; +#endif + wchar_t *xlat; + enum TtyState { + kTtyAscii, + kTtyUtf8, + kTtyEsc, + kTtyCsi, + } state; + struct TtyEsc { + unsigned i; + char s[64]; + } esc; + struct TtyInput { + size_t i; + char p[256]; + } input; +}; + +void _StartTty(struct Tty *, unsigned short, unsigned short, + unsigned short, unsigned short, void *, wchar_t *); +ssize_t _TtyRead(struct Tty *, void *, size_t); +ssize_t _TtyWrite(struct Tty *, const void *, size_t); +ssize_t _TtyWriteInput(struct Tty *, const void *, size_t); +void _TtyResetOutputMode(struct Tty *); +void _TtyFullReset(struct Tty *); +void _TtyMemmove(struct Tty *, size_t, size_t, size_t); +void _TtyErase(struct Tty *, size_t, size_t); +void _TtySetY(struct Tty *, unsigned short); +void _TtySetX(struct Tty *, unsigned short); + +ssize_t sys_writev_vga(struct Fd *, const struct iovec *, int); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_ */ diff --git a/libc/vga/vga.mk b/libc/vga/vga.mk new file mode 100644 index 000000000..0fabf0284 --- /dev/null +++ b/libc/vga/vga.mk @@ -0,0 +1,58 @@ +#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘ + +PKGS += LIBC_VGA + +LIBC_VGA_ARTIFACTS += LIBC_VGA_A +LIBC_VGA_A = o/$(MODE)/libc/vga/vga.a +LIBC_VGA_A_FILES := $(wildcard libc/vga/*) +LIBC_VGA_A_HDRS = $(filter %.h,$(LIBC_VGA_A_FILES)) +LIBC_VGA_A_SRCS_S = $(filter %.S,$(LIBC_VGA_A_FILES)) +LIBC_VGA_A_SRCS_C = $(filter %.c,$(LIBC_VGA_A_FILES)) + +LIBC_VGA = \ + $(LIBC_VGA_A_DEPS) \ + $(LIBC_VGA_A) + +LIBC_VGA_A_SRCS = \ + $(LIBC_VGA_A_SRCS_S) \ + $(LIBC_VGA_A_SRCS_C) + +LIBC_VGA_A_OBJS = \ + $(LIBC_VGA_A_SRCS_S:%.S=o/$(MODE)/%.o) \ + $(LIBC_VGA_A_SRCS_C:%.c=o/$(MODE)/%.o) + +LIBC_VGA_A_CHECKS = \ + $(LIBC_VGA_A).pkg \ + $(LIBC_VGA_A_HDRS:%=o/$(MODE)/%.ok) + +LIBC_VGA_A_DIRECTDEPS = \ + LIBC_NEXGEN32E \ + LIBC_SYSV \ + LIBC_STR \ + LIBC_INTRIN \ + LIBC_STUBS \ + LIBC_FMT + +LIBC_VGA_A_DEPS := \ + $(call uniq,$(foreach x,$(LIBC_VGA_A_DIRECTDEPS),$($(x)))) + +$(LIBC_VGA_A):libc/vga/ \ + $(LIBC_VGA_A).pkg \ + $(LIBC_VGA_A_OBJS) + +$(LIBC_VGA_A).pkg: \ + $(LIBC_VGA_A_OBJS) \ + $(foreach x,$(LIBC_VGA_A_DIRECTDEPS),$($(x)_A).pkg) + +LIBC_VGA_LIBS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x))) +LIBC_VGA_SRCS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_SRCS)) +LIBC_VGA_HDRS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_HDRS)) +LIBC_VGA_BINS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_BINS)) +LIBC_VGA_CHECKS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_CHECKS)) +LIBC_VGA_OBJS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_OBJS)) +LIBC_VGA_TESTS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_TESTS)) +$(LIBC_VGA_OBJS): $(BUILD_FILES) libc/vga/vga.mk + +.PHONY: o/$(MODE)/libc/vga +o/$(MODE)/libc/vga: $(LIBC_VGA_CHECKS) diff --git a/libc/vga/writev-vga.c b/libc/vga/writev-vga.c new file mode 100644 index 000000000..854596441 --- /dev/null +++ b/libc/vga/writev-vga.c @@ -0,0 +1,72 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ This is free and unencumbered software released into the public domain. │ +│ │ +│ Anyone is free to copy, modify, publish, use, compile, sell, or │ +│ distribute this software, either in source code form or as a compiled │ +│ binary, for any purpose, commercial or non-commercial, and by any │ +│ means. │ +│ │ +│ In jurisdictions that recognize copyright laws, the author or authors │ +│ of this software dedicate any and all copyright interest in the │ +│ software to the public domain. We make this dedication for the benefit │ +│ of the public at large and to the detriment of our heirs and │ +│ successors. We intend this dedication to be an overt act of │ +│ relinquishment in perpetuity of all present and future rights to this │ +│ software under copyright law. │ +│ │ +│ 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 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. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/fd.internal.h" +#include "libc/calls/struct/iovec.h" +#include "libc/calls/struct/iovec.internal.h" +#include "libc/vga/vga.internal.h" +#include "libc/runtime/pc.internal.h" +#include "libc/str/str.h" + +static struct Tty vga_tty; +#ifdef VGA_USE_WCS +static wchar_t vga_wcs[VGA_TTY_HEIGHT * VGA_TTY_WIDTH]; +#else +static wchar_t * const vga_wcs = NULL; +#endif + +ssize_t sys_writev_vga(struct Fd *fd, const struct iovec *iov, int iovlen) { + size_t i, wrote = 0; + ssize_t res = 0; + for (i = 0; i < iovlen; ++i) { + void *input = iov[i].iov_base; + size_t len = iov[i].iov_len; + res = _TtyWrite(&vga_tty, input, len); + if (res < 0) + break; + wrote += res; + if (res != len) + return wrote; + } + if (!wrote) + return res; + return wrote; +} + +__attribute__((__constructor__)) static textstartup void _vga_init(void) { + void * const vid_buf = (void *)(BANE + 0xb8000ull); + /* Get the initial cursor position from the BIOS data area. */ + typedef struct { + unsigned char col, row; + } bios_curs_pos_t; + bios_curs_pos_t pos = *(bios_curs_pos_t *)(BANE + 0x0450ull); + /* + * Initialize our tty structure from the current screen contents & current + * cursor position. + */ + _StartTty(&vga_tty, VGA_TTY_HEIGHT, VGA_TTY_WIDTH, pos.row, pos.col, + vid_buf, vga_wcs); +}