From 952a236b326de910a52e4ee46f87c2cda49eb433 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 26 Jul 2024 09:15:59 -0500 Subject: [PATCH] Add hl-vt100@e598aeeccac6e28afefa8b5213d35c8e8cfb7d97 --- hl-vt100/.gitignore | 6 + hl-vt100/LICENSE | 24 + hl-vt100/MANIFEST.in | 1 + hl-vt100/Makefile | 45 ++ hl-vt100/README.md | 189 ++++++ hl-vt100/doc/vt100_emulator.3 | 76 +++ hl-vt100/example/Makefile | 33 + hl-vt100/example/parser.c | 33 + hl-vt100/example/top.c | 30 + hl-vt100/example/top.py | 19 + hl-vt100/run_tests.sh | 51 ++ hl-vt100/setup.py | 39 ++ hl-vt100/src/hl_vt100.c | 167 +++++ hl-vt100/src/hl_vt100.h | 54 ++ hl-vt100/src/lw_terminal_parser.c | 228 +++++++ hl-vt100/src/lw_terminal_parser.h | 227 +++++++ hl-vt100/src/lw_terminal_vt100.c | 1008 +++++++++++++++++++++++++++++ hl-vt100/src/lw_terminal_vt100.h | 104 +++ hl-vt100/src/test.c | 63 ++ hl-vt100/src/vt100_module.c | 281 ++++++++ 20 files changed, 2678 insertions(+) create mode 100644 hl-vt100/.gitignore create mode 100644 hl-vt100/LICENSE create mode 100644 hl-vt100/MANIFEST.in create mode 100644 hl-vt100/Makefile create mode 100644 hl-vt100/README.md create mode 100644 hl-vt100/doc/vt100_emulator.3 create mode 100644 hl-vt100/example/Makefile create mode 100644 hl-vt100/example/parser.c create mode 100644 hl-vt100/example/top.c create mode 100644 hl-vt100/example/top.py create mode 100755 hl-vt100/run_tests.sh create mode 100644 hl-vt100/setup.py create mode 100644 hl-vt100/src/hl_vt100.c create mode 100644 hl-vt100/src/hl_vt100.h create mode 100644 hl-vt100/src/lw_terminal_parser.c create mode 100644 hl-vt100/src/lw_terminal_parser.h create mode 100644 hl-vt100/src/lw_terminal_vt100.c create mode 100644 hl-vt100/src/lw_terminal_vt100.h create mode 100644 hl-vt100/src/test.c create mode 100644 hl-vt100/src/vt100_module.c diff --git a/hl-vt100/.gitignore b/hl-vt100/.gitignore new file mode 100644 index 0000000..4a3c5e8 --- /dev/null +++ b/hl-vt100/.gitignore @@ -0,0 +1,6 @@ +*.o +*.so +parser +*.egg-info/ +.venv/ +.envrc diff --git a/hl-vt100/LICENSE b/hl-vt100/LICENSE new file mode 100644 index 0000000..4231bdc --- /dev/null +++ b/hl-vt100/LICENSE @@ -0,0 +1,24 @@ +vt100-emulator is distributed under the following terms: + +Copyright (c) 2016 Palard Julien. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/hl-vt100/MANIFEST.in b/hl-vt100/MANIFEST.in new file mode 100644 index 0000000..3a04f5a --- /dev/null +++ b/hl-vt100/MANIFEST.in @@ -0,0 +1 @@ +include src/*.h diff --git a/hl-vt100/Makefile b/hl-vt100/Makefile new file mode 100644 index 0000000..0ff8eec --- /dev/null +++ b/hl-vt100/Makefile @@ -0,0 +1,45 @@ +## +## Makefile for vt100 +## +## Made by julien palard +## + +NAME = vt100 +VERSION = 0 +MINOR = 0 +RELEASE = 0 + +LINKERNAME = lib$(NAME).so +SONAME = $(LINKERNAME).$(VERSION) +REALNAME = $(SONAME).$(MINOR).$(RELEASE) + +SRC = src/lw_terminal_parser.c src/lw_terminal_vt100.c src/hl_vt100.c +SRC_TEST = src/test.c +OBJ = $(SRC:.c=.o) +OBJ_TEST = $(SRC_TEST:.c=.o) +CC = gcc +INCLUDE = src +DEFINE = _GNU_SOURCE +CFLAGS = -DNDEBUG -g3 -Wextra -Wstrict-prototypes -Wall -ansi -pedantic -fPIC -I$(INCLUDE) +LIB = -lutil +RM = rm -f + +$(NAME): $(OBJ) + $(CC) --shared $(OBJ) $(LIB) -o $(LINKERNAME) + +test: $(OBJ_TEST) + $(CC) $(OBJ_TEST) -L . -l$(NAME) -o test + +all: + @make $(NAME) + +.c.o: + $(CC) -D $(DEFINE) -c $(CFLAGS) $< -o $(<:.c=.o) + +clean: + $(RM) $(LINKERNAME) test src/*~ *~ src/\#*\# src/*.o \#*\# *.o *core + +re: clean all + +check-syntax: + gcc -Isrc -Wall -Wextra -ansi -pedantic -o /dev/null -S ${CHK_SOURCES} diff --git a/hl-vt100/README.md b/hl-vt100/README.md new file mode 100644 index 0000000..867c2c6 --- /dev/null +++ b/hl-vt100/README.md @@ -0,0 +1,189 @@ +# vt100 emulator + +`vt100-emulator` is a headless +[vt100](https://fr.wikipedia.org/wiki/VT100) emulator, a bit like any +terminal you may use daily (like urxvt, xterm, ...) but those you're +using are NOT headless, they have a graphical interface to interact +with you, human). Here, `vt100-emulator` is only the underlying a `C` +and `Python` API to an actual emulator, so you can do everything you +want with it, like interfacing over TCP, HTTP, automatically testing +your implementation `malloc` against `top` while running `top` in the +headless terminal, whatever pleases you. + +For copyright information, please see the file LICENSE in this +directory or in the files of the source tree. + + +# INSTALL + +## Python module + + pip install hl-vt100 + + +## Python module from source + +The simpliest way is just to run `pip install .` from within the repo, +but if you want build artifacts, you can build one in an isolated +environment using: + + pip install build + python -m build + +Or just create an `sdist` the quick way: +n + python setup.py sdist + +In both case it will provide a build artifact in the `dist/` directory +that you can also `pip install`. + + +# Usage using the Python wrapper (same methods in C) + +```python +import hl_vt100 + + +def dump(vt100): + print("╭" + "─" * vt100.width + "╮") + for line in vt100.getlines(): + print(f"│{line:{vt100.width}}│") + print("╰" + "─" * vt100.width + "╯") + + +def main(): + vt100 = hl_vt100.vt100_headless() + vt100.changed_callback = lambda: dump(vt100) + vt100.fork('top', ['top']) + vt100.main_loop() + + +if __name__ == '__main__': + main() +``` + +# Usage using the C library + +```c +#include +#include +#include "hl_vt100.h" + + +void changed(struct vt100_headless *vt100) +{ + const char **lines; + + lines = vt100_headless_getlines(vt100); + for (unsigned int y = 0; y < vt100->term->height; ++y) + { + write(1, "|", 1); + write(1, lines[y], vt100->term->width); + write(1, "|\n", 2); + } + write(1, "\n", 1); +} + +int main(int ac, char **av) +{ + struct vt100_headless *vt100; + char *argv[] = {"top", NULL}; + + vt100 = new_vt100_headless(); + vt100_headless_fork(vt100, argv[0], argv); + vt100->changed = changed; + vt100_headless_main_loop(vt100); + return EXIT_SUCCESS; +} +``` + +# Code overview + +lw_terminal_parser, lw_terminal_vt100, and hl_vt100 are three modules used to emulate a vt100 terminal: + +``` + ------------- + | | + | Your Code | + | | + ------------- + | ^ + vt100 = vt100_headless_init() | | + vt100->changed = changed; | | hl_vt100 raises 'changed' + vt100_headless_fork(vt100, ... | | when the screen has changed. + | | You get the content of the screen + | | calling vt100_headless_getlines. + V | + ------------- ------------- + Read from PTY master and write | | | PTY | | + to lw_terminal_vt100_read_str | | hl_vt100 |<------------>| Program | + V | |Master Slave| | + ------------- ------------- + | |^ hl_vt100 gets lw_terminal_vt100's + | || lines by calling + | || lw_terminal_vt100_getlines + | || + | || + V V| + ---------------------- + Got data from | | | Recieve data from callbacks + lw_terminal_vt100_read_str | | lw_terminal_vt100 | And store an in-memory + give it to | | | state of the vt100 terminal + lw_terminal_parser_read_strV ---------------------- + | ^ + | | + | | + | | + | | + | | Callbacks + | | + | | + | | + | | + | | + V | + ---------------------- + Got data from | | + lw_terminal_pasrser_read_str | lw_terminal_parser | + parses, and call callbacks | | + ---------------------- +``` + +## lw_terminal_parser + +`lw_terminal_parser` parses terminal escape sequences, calling callbacks +when a sequence is sucessfully parsed, read `example/parse.c`. + +Provides: + + * `struct lw_terminal *lw_terminal_parser_init(void);` + * `void lw_terminal_parser_destroy(struct lw_terminal* this);` + * `void lw_terminal_parser_default_unimplemented(struct lw_terminal* this, char *seq, char chr);` + * `void lw_terminal_parser_read(struct lw_terminal *this, char c);` + * `void lw_terminal_parser_read_str(struct lw_terminal *this, char *c);` + + +## lw_terminal_vt100 + +Hooks into a `lw_terminal_parser` and keep an in-memory state of the +screen of a vt100. + +Provides: + + * `struct lw_terminal_vt100 *lw_terminal_vt100_init(void *user_data, void (*unimplemented)(struct lw_terminal* term_emul, char *seq, char chr));` + * `char lw_terminal_vt100_get(struct lw_terminal_vt100 *vt100, unsigned int x, unsigned int y);` + * `const char **lw_terminal_vt100_getlines(struct lw_terminal_vt100 *vt100);` + * `void lw_terminal_vt100_destroy(struct lw_terminal_vt100 *this);` + * `void lw_terminal_vt100_read_str(struct lw_terminal_vt100 *this, char *buffer);` + + +## hl_vt100 + +Forks a program, plug its io to a pseudo terminal and emulate a vt100 +using `lw_terminal_vt100`. + +Provides: + + * `void vt100_headless_fork(struct vt100_headless *this, const char *progname, char *const argv[]);` + * `struct vt100_headless *vt100_headless_init(void);` + * `const char **vt100_headless_getlines(struct vt100_headless *this);` diff --git a/hl-vt100/doc/vt100_emulator.3 b/hl-vt100/doc/vt100_emulator.3 new file mode 100644 index 0000000..bdc4414 --- /dev/null +++ b/hl-vt100/doc/vt100_emulator.3 @@ -0,0 +1,76 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH lw_terminal_parser 3 2011-09-27 +.SH NAME +lw_terminal_parser_init, lw_terminal_read, lw_terminal_parser_read_str, lw_terminal_destroy \- LW Terminal Parser +.SH SYNOPSIS +.B #include +.sp +.BI "struct lw_terminal *lw_terminal_parser_init(void);" +.br +.BI "void lw_terminal_read(struct lw_terminal *" this ", char " c ");" +.br +.BI "void lw_terminal_parser_read_str(struct lw_terminal *" this " , char *" c ");" +.br +.BI "void lw_terminal_destroy(struct lw_terminal* " this ");" +.SH DESCRIPTION +lw_terminal_parser is a library to parse escape sequences commonly sent to terminals. The functions in lw_terminal_parser allows you to create, send data, and destroy a terminal parser. The function +.BR lw_terminal_parser_init () +allocates and prepare a new struct lw_terminal for you. Once a lw_terminal initialized you should hook your callbacks for escape sequences and write in lw_terminal->callbacks and lw_terminal->write. The you should call +.BR lw_terminal_parser_read_str() +or +.BR lw_terminal_read() +to make the terminal parse them. +Finally to free the struct terminal, call +.BR lw_terminal_destroy(). +.PP +lw_terminal->callback is a structure for you to hook into escape sequences. +This struct is broke into substructures for each type of sequencec : esc, csi, hash, and scs. +Each substructure is a struct ascii_callback that have one member for each ascii character, in order, starting from 0x30, '0'. Members from '9' to '9' are named "n0" to "n9", letters just have their name, and others characters are of the form hXX where XX is their hexadecimal notation. +.PP +Here is simple an example on how to hook a callback into a terminal emulator : +.nf + +#include +#include +#include "../src/lw_terminal_parser.h" + +static void vt100_write(struct lw_terminal *term_emul __attribute__((unused)), + char c) +{ + printf("Got a char : %c\\n", c); +} + +static void csi_f(struct lw_terminal *term_emul) +{ + printf("\\\\033[...f with %d parameters\\n", term_emul->argc); +} + +static void csi_K(struct lw_terminal *term_emul) +{ + printf("\\\\033[...K with %d parameters\\n", term_emul->argc); +} + +int main(void) +{ + struct lw_terminal *lw_terminal; + + lw_terminal = lw_terminal_parser_init(); + if (lw_terminal == NULL) + return EXIT_FAILURE; + lw_terminal->write = vt100_write; + lw_terminal->callbacks.csi.f = csi_f; + lw_terminal->callbacks.csi.K = csi_K; + lw_terminal_parser_read_str(lw_terminal, "\\033[2KHello world !\\033[f"); + return EXIT_SUCCESS; +} +.fi + +.br +.SH "AUTHOR" +lw_terminal_parser was written by Julien Palard. +.PP +This manual page was written by Julien Palard , +for the Debian project (and may be used by others). diff --git a/hl-vt100/example/Makefile b/hl-vt100/example/Makefile new file mode 100644 index 0000000..e82ca83 --- /dev/null +++ b/hl-vt100/example/Makefile @@ -0,0 +1,33 @@ +## +## Makefile for lw_vt100 examples +## +## Made by julien palard +## + +NAME = parser + +SRC = parser.c ../src/lw_terminal_parser.c +OBJ = $(SRC:.c=.o) +CC = gcc +INCLUDE = ../src +DEFINE = _GNU_SOURCE +CFLAGS = -g3 -Wextra -Wstrict-prototypes -Wall -ansi -pedantic -I$(INCLUDE) +LIB = -lutil +RM = rm -f + +$(NAME): $(OBJ) + $(CC) $(OBJ) $(LIB) -o $(NAME) + +all: + @make $(NAME) + +.c.o: + $(CC) -D $(DEFINE) -c $(CFLAGS) $< -o $(<:.c=.o) + +clean: + $(RM) $(NAME) *~ \#*\# *.o *core + +re: clean all + +check-syntax: + gcc -Isrc -Wall -Wextra -ansi -pedantic -o /dev/null -S ${CHK_SOURCES} diff --git a/hl-vt100/example/parser.c b/hl-vt100/example/parser.c new file mode 100644 index 0000000..84393d9 --- /dev/null +++ b/hl-vt100/example/parser.c @@ -0,0 +1,33 @@ +#include +#include +#include "../src/lw_terminal_parser.h" + +static void vt100_write(struct lw_terminal *term_emul __attribute__((unused)), + char c) +{ + printf("Got a char : %c\n", c); +} + +static void csi_f(struct lw_terminal *term_emul) +{ + printf("\\033[...f with %d parameters\n", term_emul->argc); +} + +static void csi_K(struct lw_terminal *term_emul) +{ + printf("\\033[...K with %d parameters\n", term_emul->argc); +} + +int main(void) +{ + struct lw_terminal *lw_terminal; + + lw_terminal = lw_terminal_parser_init(); + if (lw_terminal == NULL) + return EXIT_FAILURE; + lw_terminal->write = vt100_write; + lw_terminal->callbacks.csi.f = csi_f; + lw_terminal->callbacks.csi.K = csi_K; + lw_terminal_parser_read_str(lw_terminal, "\033[2KHello world !\033[f"); + return EXIT_SUCCESS; +} diff --git a/hl-vt100/example/top.c b/hl-vt100/example/top.c new file mode 100644 index 0000000..6cf9f72 --- /dev/null +++ b/hl-vt100/example/top.c @@ -0,0 +1,30 @@ +#include +#include +#include "hl_vt100.h" + + +void changed(struct vt100_headless *vt100) +{ + const char **lines; + + lines = vt100_headless_getlines(vt100); + for (unsigned int y = 0; y < vt100->term->height; ++y) + { + write(1, "|", 1); + write(1, lines[y], vt100->term->width); + write(1, "|\n", 2); + } + write(1, "\n", 1); +} + +int main(int ac, char **av) +{ + struct vt100_headless *vt100; + char *argv[] = {"top", NULL}; + + vt100 = new_vt100_headless(); + vt100_headless_fork(vt100, argv[0], argv); + vt100->changed = changed; + vt100_headless_main_loop(vt100); + return EXIT_SUCCESS; +} diff --git a/hl-vt100/example/top.py b/hl-vt100/example/top.py new file mode 100644 index 0000000..b72ee30 --- /dev/null +++ b/hl-vt100/example/top.py @@ -0,0 +1,19 @@ +import hl_vt100 + + +def dump(vt100): + print("╭" + "─" * vt100.width + "╮") + for line in vt100.getlines(): + print(f"│{line:{vt100.width}}│") + print("╰" + "─" * vt100.width + "╯") + + +def main(): + vt100 = hl_vt100.vt100_headless() + vt100.changed_callback = lambda: dump(vt100) + vt100.fork('top', ['top']) + vt100.main_loop() + + +if __name__ == '__main__': + main() diff --git a/hl-vt100/run_tests.sh b/hl-vt100/run_tests.sh new file mode 100755 index 0000000..2fe30ac --- /dev/null +++ b/hl-vt100/run_tests.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +# Copyright (c) 2016 Julien Palard. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if [ "$1" = python ] +then + python -m pip install . + cat < +#include +#include +#include +#include +#include "hl_vt100.h" + +struct vt100_headless *new_vt100_headless(void) +{ + return calloc(1, sizeof(struct vt100_headless)); +} + +void delete_vt100_headless(struct vt100_headless *this) +{ + free(this); +} + +static void set_non_canonical(struct vt100_headless *this, int fd) +{ + struct termios termios; + + ioctl(fd, TCGETS, &this->backup); + ioctl(fd, TCGETS, &termios); + termios.c_iflag |= ICANON; + termios.c_cc[VMIN] = 1; + termios.c_cc[VTIME] = 0; + ioctl(fd, TCSETS, &termios); +} + +static void restore_termios(struct vt100_headless *this, int fd) +{ + ioctl(fd, TCSETS, &this->backup); +} + +#ifndef NDEBUG +static void strdump(char *str) +{ + while (*str != '\0') + { + if (*str >= ' ' && *str <= '~') + fprintf(stderr, "%c", *str); + else + fprintf(stderr, "\\0%o", *str); + str += 1; + } + fprintf(stderr, "\n"); +} +#endif + +void vt100_headless_stop(struct vt100_headless *this) +{ + this->should_quit = 1; +} + +int vt100_headless_main_loop(struct vt100_headless *this) +{ + char buffer[4096]; + fd_set rfds; + int retval; + ssize_t read_size; + + while (!this->should_quit) + { + FD_ZERO(&rfds); + FD_SET(this->master, &rfds); + FD_SET(0, &rfds); + retval = select(this->master + 1, &rfds, NULL, NULL, NULL); + if (retval == -1) + { + perror("select()"); + } + if (FD_ISSET(0, &rfds)) + { + read_size = read(0, &buffer, 4096); + if (read_size == -1) + { + perror("read"); + return EXIT_FAILURE; + } + buffer[read_size] = '\0'; + write(this->master, buffer, read_size); + } + if (FD_ISSET(this->master, &rfds)) + { + read_size = read(this->master, &buffer, 4096); + if (read_size == -1) + { + perror("read"); + return EXIT_FAILURE; + } + buffer[read_size] = '\0'; +#ifndef NDEBUG + strdump(buffer); +#endif + lw_terminal_vt100_read_str(this->term, buffer); + if (this->changed != NULL) + this->changed(this); + } + } + return EXIT_SUCCESS; +} + +void master_write(void *user_data, void *buffer, size_t len) +{ + struct vt100_headless *this; + + this = (struct vt100_headless*)user_data; + write(this->master, buffer, len); +} + +const char **vt100_headless_getlines(struct vt100_headless *this) +{ + return lw_terminal_vt100_getlines(this->term); +} + +void vt100_headless_fork(struct vt100_headless *this, + const char *progname, + char **argv) +{ + int child; + struct winsize winsize; + + set_non_canonical(this, 0); + winsize.ws_row = 24; + winsize.ws_col = 80; + child = forkpty(&this->master, NULL, NULL, NULL); + if (child == CHILD) + { + setsid(); + putenv("TERM=vt100"); + execvp(progname, argv); + return ; + } + else + { + this->term = lw_terminal_vt100_init(this, lw_terminal_parser_default_unimplemented); + this->term->master_write = master_write; + ioctl(this->master, TIOCSWINSZ, &winsize); + } + restore_termios(this, 0); +} diff --git a/hl-vt100/src/hl_vt100.h b/hl-vt100/src/hl_vt100.h new file mode 100644 index 0000000..7226360 --- /dev/null +++ b/hl-vt100/src/hl_vt100.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016 Julien Palard. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __VT100_HEADLESS_H__ +#define __VT100_HEADLESS_H__ + +#define CHILD 0 + +#include +#include +#include +#include +#include "lw_terminal_vt100.h" + +struct vt100_headless +{ + int master; + struct termios backup; + struct lw_terminal_vt100 *term; + int should_quit; + void (*changed)(struct vt100_headless *this); +}; + + +void vt100_headless_fork(struct vt100_headless *this, const char *progname, char **argv); +int vt100_headless_main_loop(struct vt100_headless *this); +void delete_vt100_headless(struct vt100_headless *this); +struct vt100_headless *new_vt100_headless(void); +const char **vt100_headless_getlines(struct vt100_headless *this); +void vt100_headless_stop(struct vt100_headless *this); + +#endif diff --git a/hl-vt100/src/lw_terminal_parser.c b/hl-vt100/src/lw_terminal_parser.c new file mode 100644 index 0000000..7d980a9 --- /dev/null +++ b/hl-vt100/src/lw_terminal_parser.c @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2016 Julien Palard. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef NDEBUG +# include +#endif + +#include "lw_terminal_parser.h" + +static void lw_terminal_parser_push(struct lw_terminal *this, char c) +{ + if (this->stack_ptr >= TERM_STACK_SIZE) + return ; + this->stack[this->stack_ptr++] = c; +} + +static void lw_terminal_parser_parse_params(struct lw_terminal *this) +{ + unsigned int i; + int got_something; + + got_something = 0; + this->argc = 0; + this->argv[0] = 0; + for (i = 0; i < this->stack_ptr; ++i) + { + if (this->stack[i] >= '0' && this->stack[i] <= '9') + { + got_something = 1; + this->argv[this->argc] = this->argv[this->argc] * 10 + + this->stack[i] - '0'; + } + else if (this->stack[i] == ';') + { + got_something = 0; + this->argc += 1; + this->argv[this->argc] = 0; + } + } + this->argc += got_something; +} + +static void lw_terminal_parser_call_CSI(struct lw_terminal *this, char c) +{ + lw_terminal_parser_parse_params(this); + if (((term_action *)&this->callbacks.csi)[c - '0'] == NULL) + { + if (this->unimplemented != NULL) + this->unimplemented(this, "CSI", c); + goto leave; + } + ((term_action *)&this->callbacks.csi)[c - '0'](this); +leave: + this->state = INIT; + this->flag = '\0'; + this->stack_ptr = 0; + this->argc = 0; +} + +static void lw_terminal_parser_call_ESC(struct lw_terminal *this, char c) +{ + if (((term_action *)&this->callbacks.esc)[c - '0'] == NULL) + { + if (this->unimplemented != NULL) + this->unimplemented(this, "ESC", c); + goto leave; + } + ((term_action *)&this->callbacks.esc)[c - '0'](this); +leave: + this->state = INIT; + this->stack_ptr = 0; + this->argc = 0; +} + +static void lw_terminal_parser_call_HASH(struct lw_terminal *this, char c) +{ + if (((term_action *)&this->callbacks.hash)[c - '0'] == NULL) + { + if (this->unimplemented != NULL) + this->unimplemented(this, "HASH", c); + goto leave; + } + ((term_action *)&this->callbacks.hash)[c - '0'](this); +leave: + this->state = INIT; + this->stack_ptr = 0; + this->argc = 0; +} + +static void lw_terminal_parser_call_GSET(struct lw_terminal *this, char c) +{ + if (c < '0' || c > 'B' + || ((term_action *)&this->callbacks.scs)[c - '0'] == NULL) + { + if (this->unimplemented != NULL) + this->unimplemented(this, "GSET", c); + goto leave; + } + ((term_action *)&this->callbacks.scs)[c - '0'](this); +leave: + this->state = INIT; + this->stack_ptr = 0; + this->argc = 0; +} + +/* +** INIT +** \_ ESC "\033" +** | \_ CSI "\033[" +** | | \_ c == '?' : term->flag = '?' +** | | \_ c == ';' || (c >= '0' && c <= '9') : term_push +** | | \_ else : term_call_CSI() +** | \_ HASH "\033#" +** | | \_ term_call_hash() +** | \_ G0SET "\033(" +** | | \_ term_call_GSET() +** | \_ G1SET "\033)" +** | | \_ term_call_GSET() +** \_ term->write() +*/ +void lw_terminal_parser_read(struct lw_terminal *this, char c) +{ + if (this->state == INIT) + { + if (c == '\033') + this->state = ESC; + else + this->write(this, c); + } + else if (this->state == ESC) + { + if (c == '[') + this->state = CSI; + else if (c == '#') + this->state = HASH; + else if (c == '(') + this->state = G0SET; + else if (c == ')') + this->state = G1SET; + else if (c >= '0' && c <= 'z') + lw_terminal_parser_call_ESC(this, c); + else this->write(this, c); + } + else if (this->state == HASH) + { + if (c >= '0' && c <= '9') + lw_terminal_parser_call_HASH(this, c); + else + this->write(this, c); + } + else if (this->state == G0SET || this->state == G1SET) + { + lw_terminal_parser_call_GSET(this, c); + } + else if (this->state == CSI) + { + if (c == '?') + this->flag = '?'; + else if (c == ';' || (c >= '0' && c <= '9')) + lw_terminal_parser_push(this, c); + else if (c >= '?' && c <= 'z') + lw_terminal_parser_call_CSI(this, c); + else + this->write(this, c); + } +} + +void lw_terminal_parser_read_str(struct lw_terminal *this, char *c) +{ + while (*c) + lw_terminal_parser_read(this, *c++); +} + +#ifndef NDEBUG +void lw_terminal_parser_default_unimplemented(struct lw_terminal* this, char *seq, char chr) +{ + unsigned int argc; + + fprintf(stderr, "WARNING: UNIMPLEMENTED %s (", seq); + for (argc = 0; argc < this->argc; ++argc) + { + fprintf(stderr, "%d", this->argv[argc]); + if (argc != this->argc - 1) + fprintf(stderr, ", "); + } + fprintf(stderr, ")%o\n", chr); +} +#else +void lw_terminal_parser_default_unimplemented(struct lw_terminal* this, char *seq, char chr) +{ + this = this; + seq = seq; + chr = chr; +} +#endif + +struct lw_terminal *lw_terminal_parser_init(void) +{ + return calloc(1, sizeof(struct lw_terminal)); +} + +void lw_terminal_parser_destroy(struct lw_terminal* this) +{ + free(this); +} diff --git a/hl-vt100/src/lw_terminal_parser.h b/hl-vt100/src/lw_terminal_parser.h new file mode 100644 index 0000000..6defbd8 --- /dev/null +++ b/hl-vt100/src/lw_terminal_parser.h @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2016 Julien Palard. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __TERMINAL_H__ +#define __TERMINAL_H__ + +/* +** +** Introduction +** ============ +** +** terminal.c is a basic level that parses escape sequences to call +** appropriated callbacks permiting you to implement a specific terminal. +** +** Callbacks +** ========= +** +** terminal.c maps sequences to callbacks in this way : +** \033... maps to terminal->callbacks->esc +** \033[... maps to terminal->callbacks->csi +** \033#... maps to terminal->callbacks->hash +** and \033( and \033) maps to terminal->callbacks->scs +** +** In 'callbacks', esc, csi, hash and scs are structs ascii_callbacks +** where you can bind your callbacks. +** +** Typically when terminal parses \033[42;43m +** it calls terminal->callbacks->csi->m(terminal); +** +** Parameters (here 42;43) are stored in terminal->argc and terminal->argv +** argv is an array of integers of length argc. +** +** Public members +** ============== +** +** void *user_data : +** A (void *) where your implementation can store whatever you want +** to get it back on your callabks. +** +** void (*write)(struct terminal *, char c) : +** Hook for your implementation to recieve chars that are not +** escape sequences +** +** struct term_callbacks callbacks : +** Hooks for your callbacks to recieve escape sequences +** +** enum term_state state : +** During a callback, typically a scs, you can read here if it's a +** G1SET or a G0SET +** +** unsigned int argc : +** For your callbacks, to know how many parameters are available +** in argv. +** +** unsigned int argv[TERM_STACK_SIZE] : +** For your callbacks, parameters of escape sequences are accessible +** here. +** \033[42;43m will have 2 in argc and argv[0] = 42, argv[1] = 43 +** +** char flag; +** Optinal constructor flag present before parameters, like in : +** \033[?1049h -> The flag will be '?' +** Otherwise the flag is set to '\0' +** +** void (*unimplemented)(struct terminal*, char *seq, char chr) : +** Can be NULL, you can hook here to know where the terminal parses an +** escape sequence on which you have not registered a callback. +** +** Exemple +** ======= +** +** See terminal_vt100.c for a working exemple. +** +*/ + +#define TERM_STACK_SIZE 1024 + +enum term_state +{ + INIT, + ESC, + HASH, + G0SET, + G1SET, + CSI +}; + +struct lw_terminal; + +typedef void (*term_action)(struct lw_terminal *emul); + +struct ascii_callbacks +{ + term_action n0; + term_action n1; + term_action n2; + term_action n3; + term_action n4; + term_action n5; + term_action n6; + term_action n7; + term_action n8; + term_action n9; + + term_action h3A; + term_action h3B; + term_action h3C; + term_action h3D; + term_action h3E; + term_action h3F; + term_action h40; + + term_action A; + term_action B; + term_action C; + term_action D; + term_action E; + term_action F; + term_action G; + term_action H; + term_action I; + term_action J; + term_action K; + term_action L; + term_action M; + term_action N; + term_action O; + term_action P; + term_action Q; + term_action R; + term_action S; + term_action T; + term_action U; + term_action V; + term_action W; + term_action X; + term_action Y; + term_action Z; + + term_action h5B; + term_action h5C; + term_action h5D; + term_action h5E; + term_action h5F; + term_action h60; + + term_action a; + term_action b; + term_action c; + term_action d; + term_action e; + term_action f; + term_action g; + term_action h; + term_action i; + term_action j; + term_action k; + term_action l; + term_action m; + term_action n; + term_action o; + term_action p; + term_action q; + term_action r; + term_action s; + term_action t; + term_action u; + term_action v; + term_action w; + term_action x; + term_action y; + term_action z; +}; + +struct term_callbacks +{ + struct ascii_callbacks esc; + struct ascii_callbacks csi; + struct ascii_callbacks hash; + struct ascii_callbacks scs; +}; + +struct lw_terminal +{ + unsigned int cursor_pos_x; + unsigned int cursor_pos_y; + enum term_state state; + unsigned int argc; + unsigned int argv[TERM_STACK_SIZE]; + void (*write)(struct lw_terminal *, char c); + char stack[TERM_STACK_SIZE]; + unsigned int stack_ptr; + struct term_callbacks callbacks; + char flag; + void *user_data; + void (*unimplemented)(struct lw_terminal*, + char *seq, char chr); +}; + +struct lw_terminal *lw_terminal_parser_init(void); +void lw_terminal_parser_default_unimplemented(struct lw_terminal* this, char *seq, char chr); +void lw_terminal_parser_read(struct lw_terminal *this, char c); +void lw_terminal_parser_read_str(struct lw_terminal *this, char *c); +void lw_terminal_parser_destroy(struct lw_terminal* this); +#endif diff --git a/hl-vt100/src/lw_terminal_vt100.c b/hl-vt100/src/lw_terminal_vt100.c new file mode 100644 index 0000000..298a5e9 --- /dev/null +++ b/hl-vt100/src/lw_terminal_vt100.c @@ -0,0 +1,1008 @@ +/* + * Copyright (c) 2016 Julien Palard. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include "lw_terminal_vt100.h" + +static unsigned int get_mode_mask(unsigned int mode) +{ + switch (mode) + { + case LNM : return MASK_LNM; + case DECCKM : return MASK_DECCKM; + case DECANM : return MASK_DECANM; + case DECCOLM : return MASK_DECCOLM; + case DECSCLM : return MASK_DECSCLM; + case DECSCNM : return MASK_DECSCNM; + case DECOM : return MASK_DECOM; + case DECAWM : return MASK_DECAWM; + case DECARM : return MASK_DECARM; + case DECINLM : return MASK_DECINLM; + default: return 0; + } +} + +/* + Modes + ===== + + The following is a list of VT100 modes which may be changed with set + mode (SM) and reset mode (RM) controls. + + ANSI Specified Modes + -------------------- + + Parameter Mode Mnemonic Mode Function + 0 Error (ignored) + 20 LNM Line feed new line mode + + + DEC Private Modes + ================= + If the first character in the parameter string is ? (077), the + parameters are interpreted as DEC private parameters according to the + following: + + Parameter Mode Mnemonic Mode Function + 0 Error (ignored) + 1 DECCKM Cursor key + 2 DECANM ANSI/VT52 + 3 DECCOLM Column + 4 DECSCLM Scrolling + 5 DECSCNM Screen + 6 DECOM Origin + 7 DECAWM Auto wrap + 8 DECARM Auto repeating + 9 DECINLM Interlace + + LNM – Line Feed/New Line Mode + ----------------------------- + This is a parameter applicable to set mode (SM) and reset mode (RM) + control sequences. The reset state causes the interpretation of the + line feed (LF), defined in ANSI Standard X3.4-1977, to imply only + vertical movement of the active position and causes the RETURN key + (CR) to send the single code CR. The set state causes the LF to imply + movement to the first position of the following line and causes the + RETURN key to send the two codes (CR, LF). This is the New Line (NL) + option. + + This mode does not affect the index (IND), or next line (NEL) format + effectors. + + DECCKM – Cursor Keys Mode (DEC Private) + --------------------------------------- + This is a private parameter applicable to set mode (SM) and reset mode + (RM) control sequences. This mode is only effective when the terminal + is in keypad application mode (see DECKPAM) and the ANSI/VT52 mode + (DECANM) is set (see DECANM). Under these conditions, if the cursor + key mode is reset, the four cursor function keys will send ANSI cursor + control commands. If cursor key mode is set, the four cursor function + keys will send application functions. + + DECANM – ANSI/VT52 Mode (DEC Private) + ------------------------------------- + This is a private parameter applicable to set mode (SM) and reset mode + (RM) control sequences. The reset state causes only VT52 compatible + escape sequences to be interpreted and executed. The set state causes + only ANSI "compatible" escape and control sequences to be interpreted + and executed. + + DECCOLM – Column Mode (DEC Private) + ----------------------------------- + This is a private parameter applicable to set mode (SM) and reset mode + (RM) control sequences. The reset state causes a maximum of 80 columns + on the screen. The set state causes a maximum of 132 columns on the + screen. + + DECSCLM – Scrolling Mode (DEC Private) + -------------------------------------- + This is a private parameter applicable to set mode (SM) and reset mode + (RM) control sequences. The reset state causes scrolls to "jump" + instantaneously. The set state causes scrolls to be "smooth" at a + maximum rate of six lines per second. + + DECSCNM – Screen Mode (DEC Private) + ----------------------------------- + This is a private parameter applicable to set mode (SM) and reset mode + (RM) control sequences. The reset state causes the screen to be black + with white characters. The set state causes the screen to be white + with black characters. + + DECOM – Origin Mode (DEC Private) + --------------------------------- + This is a private parameter applicable to set mode (SM) and reset mode + (RM) control sequences. The reset state causes the origin to be at the + upper-left character position on the screen. Line and column numbers + are, therefore, independent of current margin settings. The cursor may + be positioned outside the margins with a cursor position (CUP) or + horizontal and vertical position (HVP) control. + + The set state causes the origin to be at the upper-left character + position within the margins. Line and column numbers are therefore + relative to the current margin settings. The cursor is not allowed to + be positioned outside the margins. + + The cursor is moved to the new home position when this mode is set or + reset. + + Lines and columns are numbered consecutively, with the origin being + line 1, column 1. + + DECAWM – Autowrap Mode (DEC Private) + ------------------------------------ + This is a private parameter applicable to set mode (SM) and reset mode + (RM) control sequences. The reset state causes any displayable + characters received when the cursor is at the right margin to replace + any previous characters there. The set state causes these characters + to advance to the start of the next line, doing a scroll up if + required and permitted. + + DECARM – Auto Repeat Mode (DEC Private) + --------------------------------------- + This is a private parameter applicable to set mode (SM) and reset mode + (RM) control sequences. The reset state causes no keyboard keys to + auto-repeat. The set state causes certain keyboard keys to auto-repeat. + + DECINLM – Interlace Mode (DEC Private) + -------------------------------------- + This is a private parameter applicable to set mode (SM) and reset mode + (RM) control sequences. The reset state (non-interlace) causes the video + processor to display 240 scan lines per frame. The set state (interlace) + causes the video processor to display 480 scan lines per frame. There is + no increase in character resolution. +*/ + +#define SCREEN_PTR(vt100, x, y) \ + ((vt100->top_line * vt100->width + x + vt100->width * y) \ + % (vt100->width * SCROLLBACK * vt100->height)) + +#define FROZEN_SCREEN_PTR(vt100, x, y) \ + ((x + vt100->width * y) \ + % (vt100->width * SCROLLBACK * vt100->height)) + +static void set(struct lw_terminal_vt100 *headless_term, + unsigned int x, unsigned int y, + char c) +{ + if (y < headless_term->margin_top || y > headless_term->margin_bottom) + headless_term->frozen_screen[FROZEN_SCREEN_PTR(headless_term, x, y)] = c; + else + headless_term->screen[SCREEN_PTR(headless_term, x, y)] = c; +} + + +char lw_terminal_vt100_get(struct lw_terminal_vt100 *vt100, unsigned int x, unsigned int y) +{ + if (y < vt100->margin_top || y > vt100->margin_bottom) + return vt100->frozen_screen[FROZEN_SCREEN_PTR(vt100, x, y)]; + else + return vt100->screen[SCREEN_PTR(vt100, x, y)]; +} + +static void froze_line(struct lw_terminal_vt100 *vt100, unsigned int y) +{ + memcpy(vt100->frozen_screen + vt100->width * y, + vt100->screen + SCREEN_PTR(vt100, 0, y), + vt100->width); +} + +static void unfroze_line(struct lw_terminal_vt100 *vt100, unsigned int y) +{ + memcpy(vt100->screen + SCREEN_PTR(vt100, 0, y), + vt100->frozen_screen + vt100->width * y, + vt100->width); +} + +static void blank_screen(struct lw_terminal_vt100 *lw_terminal_vt100) +{ + unsigned int x; + unsigned int y; + + for (x = 0; x < lw_terminal_vt100->width; ++x) + for (y = 0; y < lw_terminal_vt100->height; ++y) + set(lw_terminal_vt100, x, y, ' '); +} + +/* + DECSC – Save Cursor (DEC Private) + + ESC 7 + + This sequence causes the cursor position, graphic rendition, and + character set to be saved. (See DECRC). +*/ +static void DECSC(struct lw_terminal *term_emul) +{ + /*TODO: Save graphic rendition and charset.*/ + struct lw_terminal_vt100 *vt100; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + vt100->saved_x = vt100->x; + vt100->saved_y = vt100->y; +} + +/* + RM – Reset Mode + + ESC [ Ps ; Ps ; . . . ; Ps l + + Resets one or more VT100 modes as specified by each selective + parameter in the parameter string. Each mode to be reset is specified + by a separate parameter. [See Set Mode (SM) control sequence]. (See + Modes following this section). + +*/ +static void RM(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int mode; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + if (term_emul->argc > 0) + { + mode = term_emul->argv[0]; + if (mode == DECCOLM) + { + vt100->width = 80; + vt100->x = vt100->y = 0; + blank_screen(vt100); + } + UNSET_MODE(vt100, mode); + } +} + +/* + CUP – Cursor Position + + ESC [ Pn ; Pn H default value: 1 + + The CUP sequence moves the active position to the position specified + by the parameters. This sequence has two parameter values, the first + specifying the line position and the second specifying the column + position. A parameter value of zero or one for the first or second + parameter moves the active position to the first line or column in the + display, respectively. The default condition with no parameters + present is equivalent to a cursor to home action. In the VT100, this + control behaves identically with its format effector counterpart, + HVP. Editor Function + + The numbering of lines depends on the state of the Origin Mode + (DECOM). +*/ +static void CUP(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + int arg0; + int arg1; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + arg0 = 0; + arg1 = 0; + if (term_emul->argc > 0) + arg0 = term_emul->argv[0] - 1; + if (term_emul->argc > 1) + arg1 = term_emul->argv[1] - 1; + if (arg0 < 0) + arg0 = 0; + if (arg1 < 0) + arg1 = 0; + + if (MODE_IS_SET(vt100, DECOM)) + { + arg0 += vt100->margin_top; + if ((unsigned int)arg0 > vt100->margin_bottom) + arg0 = vt100->margin_bottom; + } + vt100->y = arg0; + vt100->x = arg1; +} + +/* + SM – Set Mode + + ESC [ Ps ; . . . ; Ps h + + Causes one or more modes to be set within the VT100 as specified by + each selective parameter in the parameter string. Each mode to be set + is specified by a separate parameter. A mode is considered set until + it is reset by a reset mode (RM) control sequence. + +*/ +static void SM(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int mode; + unsigned int saved_argc; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + if (term_emul->argc > 0) + { + mode = term_emul->argv[0]; + SET_MODE(vt100, mode); + if (mode == DECANM) + { + /* TODO: Support vt52 mode */ + return ; + } + if (mode == DECCOLM) + { + vt100->width = 132; + vt100->x = vt100->y = 0; + blank_screen(vt100); + } + if (mode == DECOM) + { + saved_argc = term_emul->argc; + term_emul->argc = 0; + CUP(term_emul); + term_emul->argc = saved_argc; + } + } +} + +/* + DECSTBM – Set Top and Bottom Margins (DEC Private) + + ESC [ Pn; Pn r + + This sequence sets the top and bottom margins to define the scrolling + region. The first parameter is the line number of the first line in + the scrolling region; the second parameter is the line number of the + bottom line in the scrolling region. Default is the entire screen (no + margins). The minimum size of the scrolling region allowed is two + lines, i.e., the top margin must be less than the bottom margin. The + cursor is placed in the home position (see Origin Mode DECOM). + +*/ +static void DECSTBM(struct lw_terminal *term_emul) +{ + unsigned int margin_top; + unsigned int margin_bottom; + struct lw_terminal_vt100 *vt100; + unsigned int line; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + + if (term_emul->argc == 2) + { + margin_top = term_emul->argv[0] - 1; + margin_bottom = term_emul->argv[1] - 1; + if (margin_bottom >= vt100->height) + return ; + if (margin_bottom - margin_top <= 0) + return ; + } + else + { + margin_top = 0; + margin_bottom = vt100->height - 1; + } + for (line = vt100->margin_top; line < margin_top; ++line) + froze_line(vt100, line); + for (line = vt100->margin_bottom; line < margin_bottom; ++line) + unfroze_line(vt100, line); + for (line = margin_top; line < vt100->margin_top; ++line) + unfroze_line(vt100, line); + for (line = margin_bottom; line < vt100->margin_bottom; ++line) + froze_line(vt100, line); + vt100->margin_bottom = margin_bottom; + vt100->margin_top = margin_top; + term_emul->argc = 0; + CUP(term_emul); +} + +/* + SGR – Select Graphic Rendition + + ESC [ Ps ; . . . ; Ps m + + Invoke the graphic rendition specified by the parameter(s). All + following characters transmitted to the VT100 are rendered according + to the parameter(s) until the next occurrence of SGR. Format Effector + + Parameter Parameter Meaning + 0 Attributes off + 1 Bold or increased intensity + 4 Underscore + 5 Blink + 7 Negative (reverse) image + + All other parameter values are ignored. + + With the Advanced Video Option, only one type of character attribute + is possible as determined by the cursor selection; in that case + specifying either the underscore or the reverse attribute will + activate the currently selected attribute. (See cursor selection in + Chapter 1). +*/ +static void SGR(struct lw_terminal *term_emul) +{ + term_emul = term_emul; + /* Just ignore them for now, we are rendering pure text only */ +} + +/* + DA – Device Attributes + + ESC [ Pn c + + + The host requests the VT100 to send a device attributes (DA) control + sequence to identify itself by sending the DA control sequence with + either no parameter or a parameter of 0. Response to the request + described above (VT100 to host) is generated by the VT100 as a DA + control sequence with the numeric parameters as follows: + + Option Present Sequence Sent + No options ESC [?1;0c + Processor option (STP) ESC [?1;1c + Advanced video option (AVO) ESC [?1;2c + AVO and STP ESC [?1;3c + Graphics option (GPO) ESC [?1;4c + GPO and STP ESC [?1;5c + GPO and AVO ESC [?1;6c + GPO, STP and AVO ESC [?1;7c + +*/ +static void DA(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + vt100->master_write(vt100->user_data, "\033[?1;0c", 7); +} + +/* + DECRC – Restore Cursor (DEC Private) + + ESC 8 + + This sequence causes the previously saved cursor position, graphic + rendition, and character set to be restored. +*/ +static void DECRC(struct lw_terminal *term_emul) +{ + /*TODO Save graphic rendition and charset */ + struct lw_terminal_vt100 *vt100; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + vt100->x = vt100->saved_x; + vt100->y = vt100->saved_y; +} + +/* + DECALN – Screen Alignment Display (DEC Private) + + ESC # 8 + + This command fills the entire screen area with uppercase Es for screen + focus and alignment. This command is used by DEC manufacturing and + Field Service personnel. +*/ +static void DECALN(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int x; + unsigned int y; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + for (x = 0; x < vt100->width; ++x) + for (y = 0; y < vt100->height; ++y) + set(vt100, x, y, 'E'); +} + +/* + IND – Index + + ESC D + + This sequence causes the active position to move downward one line + without changing the column position. If the active position is at the + bottom margin, a scroll up is performed. Format Effector +*/ +static void IND(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int x; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + if (vt100->y >= vt100->margin_bottom) + { + /* SCROLL */ + vt100->top_line = (vt100->top_line + 1) % (vt100->height * SCROLLBACK); + for (x = 0; x < vt100->width; ++x) + set(vt100, x, vt100->margin_bottom, ' '); + + } + else + { + /* Do not scroll, just move downward on the current display space */ + vt100->y += 1; + } +} +/* + RI – Reverse Index + + ESC M + + Move the active position to the same horizontal position on the + preceding line. If the active position is at the top margin, a scroll + down is performed. Format Effector +*/ +static void RI(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + if (vt100->y == 0) + { + /* SCROLL */ + vt100->top_line = (vt100->top_line - 1) % (vt100->height * SCROLLBACK); + } + else + { + /* Do not scroll, just move upward on the current display space */ + vt100->y -= 1; + } +} + +/* + NEL – Next Line + + ESC E + + This sequence causes the active position to move to the first position + on the next line downward. If the active position is at the bottom + margin, a scroll up is performed. Format Effector +*/ +static void NEL(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int x; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + if (vt100->y >= vt100->margin_bottom) + { + /* SCROLL */ + vt100->top_line = (vt100->top_line + 1) % (vt100->height * SCROLLBACK); + for (x = 0; x < vt100->width; ++x) + set(vt100, x, vt100->margin_bottom, ' '); + } + else + { + /* Do not scroll, just move downward on the current display space */ + vt100->y += 1; + } + vt100->x = 0; +} + +/* + CUU – Cursor Up – Host to VT100 and VT100 to Host + + ESC [ Pn A default value: 1 + + Moves the active position upward without altering the column + position. The number of lines moved is determined by the parameter. A + parameter value of zero or one moves the active position one line + upward. A parameter value of n moves the active position n lines + upward. If an attempt is made to move the cursor above the top margin, + the cursor stops at the top margin. Editor Function +*/ +static void CUU(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int arg0; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + arg0 = 1; + if (term_emul->argc > 0) + arg0 = term_emul->argv[0]; + if (arg0 == 0) + arg0 = 1; + if (arg0 <= vt100->y) + vt100->y -= arg0; + else + vt100->y = 0; +} + +/* + CUD – Cursor Down – Host to VT100 and VT100 to Host + + ESC [ Pn B default value: 1 + + The CUD sequence moves the active position downward without altering + the column position. The number of lines moved is determined by the + parameter. If the parameter value is zero or one, the active position + is moved one line downward. If the parameter value is n, the active + position is moved n lines downward. In an attempt is made to move the + cursor below the bottom margin, the cursor stops at the bottom + margin. Editor Function +*/ +static void CUD(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int arg0; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + arg0 = 1; + if (term_emul->argc > 0) + arg0 = term_emul->argv[0]; + if (arg0 == 0) + arg0 = 1; + vt100->y += arg0; + if (vt100->y >= vt100->height) + vt100->y = vt100->height - 1; +} + +/* + CUF – Cursor Forward – Host to VT100 and VT100 to Host + + ESC [ Pn C default value: 1 + + The CUF sequence moves the active position to the right. The distance + moved is determined by the parameter. A parameter value of zero or one + moves the active position one position to the right. A parameter value + of n moves the active position n positions to the right. If an attempt + is made to move the cursor to the right of the right margin, the + cursor stops at the right margin. Editor Function +*/ +static void CUF(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int arg0; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + arg0 = 1; + if (term_emul->argc > 0) + arg0 = term_emul->argv[0]; + if (arg0 == 0) + arg0 = 1; + vt100->x += arg0; + if (vt100->x >= vt100->width) + vt100->x = vt100->width - 1; +} + +/* + CUB – Cursor Backward – Host to VT100 and VT100 to Host + + ESC [ Pn D default value: 1 + + The CUB sequence moves the active position to the left. The distance + moved is determined by the parameter. If the parameter value is zero + or one, the active position is moved one position to the left. If the + parameter value is n, the active position is moved n positions to the + left. If an attempt is made to move the cursor to the left of the left + margin, the cursor stops at the left margin. Editor Function +*/ +static void CUB(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int arg0; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + arg0 = 1; + if (term_emul->argc > 0) + arg0 = term_emul->argv[0]; + if (arg0 == 0) + arg0 = 1; + if (arg0 < vt100->x) + vt100->x -= arg0; + else + vt100->x = 0; +} + +/* + ED – Erase In Display + + ESC [ Ps J default value: 0 + + This sequence erases some or all of the characters in the display + according to the parameter. Any complete line erased by this sequence + will return that line to single width mode. Editor Function + + Parameter Parameter Meaning + 0 Erase from the active position to the end of the screen, + inclusive (default) + 1 Erase from start of the screen to the active position, inclusive + 2 Erase all of the display – all lines are erased, changed to + single-width, and the cursor does not move. +*/ +static void ED(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int arg0; + unsigned int x; + unsigned int y; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + arg0 = 0; + if (term_emul->argc > 0) + arg0 = term_emul->argv[0]; + if (arg0 == 0) + { + for (x = vt100->x; x < vt100->width; ++x) + set(vt100, x, vt100->y, ' '); + for (x = 0 ; x < vt100->width; ++x) + for (y = vt100->y + 1; y < vt100->height; ++y) + set(vt100, x, y, ' '); + } + else if (arg0 == 1) + { + for (x = 0 ; x < vt100->width; ++x) + for (y = 0; y < vt100->y; ++y) + set(vt100, x, y, ' '); + for (x = 0; x <= vt100->x; ++x) + set(vt100, x, vt100->y, ' '); + } + else if (arg0 == 2) + { + for (x = 0 ; x < vt100->width; ++x) + for (y = 0; y < vt100->height; ++y) + set(vt100, x, y, ' '); + } +} + +/* + EL – Erase In Line + + ESC [ Ps K default value: 0 + + Erases some or all characters in the active line according to the + parameter. Editor Function + + Parameter Parameter Meaning + 0 Erase from the active position to the end of the line, inclusive + (default) + 1 Erase from the start of the screen to the active position, inclusive + 2 Erase all of the line, inclusive +*/ +static void EL(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int arg0; + unsigned int x; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + arg0 = 0; + if (term_emul->argc > 0) + arg0 = term_emul->argv[0]; + if (arg0 == 0) + { + for (x = vt100->x; x < vt100->width; ++x) + set(vt100, x, vt100->y, ' '); + } + else if (arg0 == 1) + { + for (x = 0; x <= vt100->x; ++x) + set(vt100, x, vt100->y, ' '); + } + else if (arg0 == 2) + { + for (x = 0; x < vt100->width; ++x) + set(vt100, x, vt100->y, ' '); + } +} + +/* + HVP – Horizontal and Vertical Position + + ESC [ Pn ; Pn f default value: 1 + + Moves the active position to the position specified by the + parameters. This sequence has two parameter values, the first + specifying the line position and the second specifying the column. A + parameter value of either zero or one causes the active position to + move to the first line or column in the display, respectively. The + default condition with no parameters present moves the active position + to the home position. In the VT100, this control behaves identically + with its editor function counterpart, CUP. The numbering of lines and + columns depends on the reset or set state of the origin mode + (DECOM). Format Effector +*/ +static void HVP(struct lw_terminal *term_emul) +{ + CUP(term_emul); +} + +static void TBC(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + unsigned int i; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + if (term_emul->argc == 0 || term_emul->argv[0] == 0) + { + vt100->tabulations[vt100->x] = '-'; + } + else if (term_emul->argc == 1 && term_emul->argv[0] == 3) + { + for (i = 0; i < 132; ++i) + vt100->tabulations[i] = '-'; + } +} + +static void HTS(struct lw_terminal *term_emul) +{ + struct lw_terminal_vt100 *vt100; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + vt100->tabulations[vt100->x] = '|'; +} + +static void vt100_write(struct lw_terminal *term_emul, char c) +{ + struct lw_terminal_vt100 *vt100; + + vt100 = (struct lw_terminal_vt100 *)term_emul->user_data; + if (c == '\r') + { + vt100->x = 0; + return ; + } + if (c == '\n' || c == '\013' || c == '\014') + { + if (MODE_IS_SET(vt100, LNM)) + NEL(term_emul); + else + IND(term_emul); + return ; + } + if (c == '\010' && vt100->x > 0) + { + if (vt100->x == vt100->width) + vt100->x -= 1; + vt100->x -= 1; + return ; + } + if (c == '\t') + { + do + { + set(vt100, vt100->x, vt100->y, ' '); + vt100->x += 1; + } while (vt100->x < vt100->width && vt100->tabulations[vt100->x] == '-'); + return ; + } + if (c == '\016') + { + vt100->selected_charset = 0; + return ; + } + if (c == '\017') + { + vt100->selected_charset = 1; + return ; + } + if (vt100->x == vt100->width) + { + if (MODE_IS_SET(vt100, DECAWM)) + NEL(term_emul); + else + vt100->x -= 1; + } + set(vt100, vt100->x, vt100->y, c); + vt100->x += 1; +} + +const char **lw_terminal_vt100_getlines(struct lw_terminal_vt100 *vt100) +{ + unsigned int y; + + pthread_mutex_lock(&vt100->mutex); + for (y = 0; y < vt100->height; ++y) + if (y < vt100->margin_top || y > vt100->margin_bottom) + vt100->lines[y] = vt100->frozen_screen + FROZEN_SCREEN_PTR(vt100, 0, y); + else + vt100->lines[y] = vt100->screen + SCREEN_PTR(vt100, 0, y); + pthread_mutex_unlock(&vt100->mutex); + return (const char **)vt100->lines; +} + +struct lw_terminal_vt100 *lw_terminal_vt100_init(void *user_data, + void (*unimplemented)(struct lw_terminal* term_emul, char *seq, char chr)) +{ + struct lw_terminal_vt100 *this; + + this = calloc(1, sizeof(*this)); + if (this == NULL) + return NULL; + this->user_data = user_data; + this->height = 24; + this->width = 80; + this->screen = malloc(132 * SCROLLBACK * this->height); + if (this->screen == NULL) + goto free_this; + memset(this->screen, ' ', 132 * SCROLLBACK * this->height); + this->frozen_screen = malloc(132 * this->height); + if (this->frozen_screen == NULL) + goto free_screen; + memset(this->frozen_screen, ' ', 132 * this->height); + this->tabulations = malloc(132); + if (this->tabulations == NULL) + goto free_frozen_screen; + if (this->tabulations == NULL) + return NULL; + this->margin_top = 0; + this->margin_bottom = this->height - 1; + this->selected_charset = 0; + this->x = 0; + this->y = 0; + this->modes = MASK_DECANM; + this->top_line = 0; + this->lw_terminal = lw_terminal_parser_init(); + if (this->lw_terminal == NULL) + goto free_tabulations; + this->lw_terminal->user_data = this; + this->lw_terminal->write = vt100_write; + this->lw_terminal->callbacks.csi.f = HVP; + this->lw_terminal->callbacks.csi.K = EL; + this->lw_terminal->callbacks.csi.c = DA; + this->lw_terminal->callbacks.csi.h = SM; + this->lw_terminal->callbacks.csi.l = RM; + this->lw_terminal->callbacks.csi.J = ED; + this->lw_terminal->callbacks.csi.H = CUP; + this->lw_terminal->callbacks.csi.C = CUF; + this->lw_terminal->callbacks.csi.B = CUD; + this->lw_terminal->callbacks.csi.r = DECSTBM; + this->lw_terminal->callbacks.csi.m = SGR; + this->lw_terminal->callbacks.csi.A = CUU; + this->lw_terminal->callbacks.csi.g = TBC; + this->lw_terminal->callbacks.esc.H = HTS; + this->lw_terminal->callbacks.csi.D = CUB; + this->lw_terminal->callbacks.esc.E = NEL; + this->lw_terminal->callbacks.esc.D = IND; + this->lw_terminal->callbacks.esc.M = RI; + this->lw_terminal->callbacks.esc.n8 = DECRC; + this->lw_terminal->callbacks.esc.n7 = DECSC; + this->lw_terminal->callbacks.hash.n8 = DECALN; + this->lw_terminal->unimplemented = unimplemented; + return this; +free_tabulations: + free(this->tabulations); +free_frozen_screen: + free(this->frozen_screen); +free_screen: + free(this->screen); +free_this: + free(this); + return NULL; +} + +void lw_terminal_vt100_read_str(struct lw_terminal_vt100 *this, char *buffer) +{ + pthread_mutex_lock(&this->mutex); + lw_terminal_parser_read_str(this->lw_terminal, buffer); + pthread_mutex_unlock(&this->mutex); +} + +void lw_terminal_vt100_destroy(struct lw_terminal_vt100 *this) +{ + lw_terminal_parser_destroy(this->lw_terminal); + free(this->screen); + free(this->frozen_screen); + free(this); +} diff --git a/hl-vt100/src/lw_terminal_vt100.h b/hl-vt100/src/lw_terminal_vt100.h new file mode 100644 index 0000000..9c2129a --- /dev/null +++ b/hl-vt100/src/lw_terminal_vt100.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016 Julien Palard. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LW_TERMINAL_VT100_H__ +#define __LW_TERMINAL_VT100_H__ + +#include +#include "lw_terminal_parser.h" + +/* + * Source : http://vt100.net/docs/vt100-ug/chapter3.html + http://vt100.net/docs/tp83/appendixb.html + * It's a vt100 implementation, that implements ANSI control function. + */ + +#define SCROLLBACK 3 + +#define MASK_LNM 1 +#define MASK_DECCKM 2 +#define MASK_DECANM 4 +#define MASK_DECCOLM 8 +#define MASK_DECSCLM 16 +#define MASK_DECSCNM 32 +#define MASK_DECOM 64 +#define MASK_DECAWM 128 +#define MASK_DECARM 256 +#define MASK_DECINLM 512 + +#define LNM 20 +#define DECCKM 1 +#define DECANM 2 +#define DECCOLM 3 +#define DECSCLM 4 +#define DECSCNM 5 +#define DECOM 6 +#define DECAWM 7 +#define DECARM 8 +#define DECINLM 9 + + +#define SET_MODE(vt100, mode) ((vt100)->modes |= get_mode_mask(mode)) +#define UNSET_MODE(vt100, mode) ((vt100)->modes &= ~get_mode_mask(mode)) +#define MODE_IS_SET(vt100, mode) ((vt100)->modes & get_mode_mask(mode)) + +/* +** frozen_screen is the frozen part of the screen +** when margins are set. +** The top of the frozen_screen holds the top margin +** while the bottom holds the bottom margin. +*/ +struct lw_terminal_vt100 +{ + struct lw_terminal *lw_terminal; + unsigned int width; + unsigned int height; + unsigned int x; + unsigned int y; + unsigned int saved_x; + unsigned int saved_y; + unsigned int margin_top; + unsigned int margin_bottom; + unsigned int top_line; /* Line at the top of the display */ + char *screen; + char *frozen_screen; + char *tabulations; + unsigned int selected_charset; + unsigned int modes; + char *lines[80]; + void (*master_write)(void *user_data, void *buffer, size_t len); + void *user_data; + pthread_mutex_t mutex; +}; + +struct lw_terminal_vt100 *lw_terminal_vt100_init(void *user_data, + void (*unimplemented)(struct lw_terminal* term_emul, + char *seq, char chr)); +char lw_terminal_vt100_get(struct lw_terminal_vt100 *vt100, unsigned int x, unsigned int y); +const char **lw_terminal_vt100_getlines(struct lw_terminal_vt100 *vt100); +void lw_terminal_vt100_destroy(struct lw_terminal_vt100 *this); +void lw_terminal_vt100_read_str(struct lw_terminal_vt100 *this, char *buffer); + +#endif diff --git a/hl-vt100/src/test.c b/hl-vt100/src/test.c new file mode 100644 index 0000000..c496ccd --- /dev/null +++ b/hl-vt100/src/test.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016 Julien Palard. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include "hl_vt100.h" + +void disp(struct vt100_headless *vt100) +{ + unsigned int y; + const char **lines; + + lines = vt100_headless_getlines(vt100); + write(1, "\n", 1); + for (y = 0; y < vt100->term->height; ++y) + { + write(1, "|", 1); + write(1, lines[y], vt100->term->width); + write(1, "|\n", 2); + } +} + +int main(int ac, char **av) +{ + struct vt100_headless *vt100_headless; + + if (ac == 1) + { + puts("Usage: test PROGNAME"); + return EXIT_FAILURE; + } + vt100_headless = new_vt100_headless(); + vt100_headless->changed = disp; + vt100_headless_fork(vt100_headless, av[1], (av + 1)); + vt100_headless_main_loop(vt100_headless); + return EXIT_SUCCESS; +} diff --git a/hl-vt100/src/vt100_module.c b/hl-vt100/src/vt100_module.c new file mode 100644 index 0000000..9349d17 --- /dev/null +++ b/hl-vt100/src/vt100_module.c @@ -0,0 +1,281 @@ +/* This is a Python wrapper of the vt100 headless library */ + +/* + + occurrences of 'xx' should be changed to something reasonable for your + module. If your module is named foo your sourcefile should be named + foomodule.c. + + You will probably want to delete all references to 'x_attr' and add + your own types of attributes instead. Maybe you want to name your + local variables other than 'self'. If your object type is needed in + other files, you'll have to create a file "foobarobject.h"; see + floatobject.h for an example. */ + + +#include + +#include "Python.h" +#include "structmember.h" + +#include "lw_terminal_vt100.h" +#include "hl_vt100.h" + +typedef struct { + PyObject_HEAD + struct vt100_headless *obj; + PyObject *changed_callback; +} VT100Object; + +static PyTypeObject VT100_Type; + +#define VT100Object_Check(v) Py_IS_TYPE(v, &VT100_Type) + + +VT100Object **allocated; +size_t allocated_size; + +/* VT100 methods */ + +PyDoc_STRVAR(vt100_headless_fork_doc, +"fork(progname, argv)\n\ +\n\ +Fork a process in a new PTY handled by an headless VT100 emulator."); + +static PyObject * +VT100_fork(VT100Object *self, PyObject *args) +{ + char *progname; + PyObject *pyargv; + const char **argv; + int argc; + int i; + + if (!PyArg_ParseTuple(args, "sO:fork", &progname, &pyargv)) + return NULL; + if (!PyList_Check(pyargv)) + { + PyErr_SetString(PyExc_TypeError, "not a list"); + return NULL; + } + argc = PyList_Size(pyargv); + for (i = 0; i < argc; i++) + { + PyObject *o = PyList_GetItem(pyargv, i); + if (!PyUnicode_Check(o)) + { + PyErr_SetString(PyExc_TypeError, "argv list must contain strings"); + return NULL; + } + } + argv = PyMem_Calloc(argc + 1, sizeof(char *)); + for (i = 0; i < argc; i++) + argv[i] = PyUnicode_AsUTF8(PyList_GetItem(pyargv, i)); + argv[i] = NULL; + vt100_headless_fork(self->obj, progname, (char **)argv); + PyMem_Free(argv); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(vt100_headless_getlines_doc, +"getlines()\n\ +\n\ +Get a list of lines as currently seen by the emulator."); + +static PyObject * +VT100_getlines(VT100Object *self, PyObject *Py_UNUSED(ignored)) +{ + const char **lines; + PyObject *result; + + lines = vt100_headless_getlines(self->obj); + result = PyList_New(0); + if (result == NULL) + return NULL; + for (unsigned int i = 0; i < self->obj->term->height; i++) + PyList_Append(result, PyUnicode_FromStringAndSize(lines[i], self->obj->term->width)); + return result; +} + +PyDoc_STRVAR(vt100_headless_main_loop_doc, +"main_loop()\n\ +\n\ +Enter the emulator main loop."); + +static PyObject * +VT100_main_loop(VT100Object *self, PyObject *Py_UNUSED(ignored)) +{ + vt100_headless_main_loop(self->obj); + if (PyErr_Occurred()) + return NULL; + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(vt100_headless_stop_doc, +"stop()\n\ +\n\ +Stop emulator main loop."); + +static PyObject * +VT100_stop(VT100Object *self, PyObject *Py_UNUSED(ignored)) +{ + vt100_headless_stop(self->obj); + Py_RETURN_NONE; +} + + +static int +vt100_add_to_allocated(VT100Object *obj) +{ + for (size_t i = 0; i < allocated_size; i++) + { + if (allocated[i] == NULL) + { + allocated[i] = obj; + return 0; + } + } + /* Out of allocated memory, realloc. */ + allocated_size *= 2; + allocated = PyMem_Realloc(allocated, allocated_size * sizeof(VT100Object*)); + if (allocated == NULL) + { + PyErr_SetString(PyExc_MemoryError, "cannot allocate vt100 emulator"); + return -1; + } + return vt100_add_to_allocated(obj); +} + +static int +vt100_del_from_allocated(VT100Object *obj) +{ + for (size_t i = 0; i < allocated_size; i++) + { + if (allocated[i] == obj) + { + allocated[i] = NULL; + return 0; + } + } + return -1; +} + +VT100Object * +vt100_find_in_allocated(struct vt100_headless *obj) +{ + for (size_t i = 0; i < allocated_size; i++) + if (allocated[i]->obj == obj) + return allocated[i]; + return NULL; +} + +void +hl_vt100_changed_cb(struct vt100_headless *this) +{ + VT100Object *obj; + PyObject *result; + + obj = vt100_find_in_allocated(this); + if (obj->changed_callback != NULL && obj->changed_callback != Py_None) + { + result = PyObject_CallNoArgs(obj->changed_callback); + if (result == NULL) + this->should_quit = 1; + } +} + +static int +VT100_init(VT100Object *self, PyObject *args, PyObject *kwds) +{ + self->obj = new_vt100_headless(); + vt100_add_to_allocated(self); + self->obj->changed = hl_vt100_changed_cb; + if (self->obj == NULL) + { + PyErr_SetString(PyExc_MemoryError, "cannot allocate vt100 emulator"); + return -1; + } + return 0; +} + +static PyObject * +VT100_getwidth(VT100Object *self, void *closure) +{ + return PyLong_FromUnsignedLong(self->obj->term->width); +} + +static PyObject * +VT100_getheight(VT100Object *self, void *closure) +{ + return PyLong_FromUnsignedLong(self->obj->term->height); +} + +static void +VT100_dealloc(VT100Object *self) +{ + vt100_del_from_allocated(self); + Py_XDECREF(self->changed_callback); + delete_vt100_headless(self->obj); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyMethodDef VT100_methods[] = { + {"fork", (PyCFunction)VT100_fork, METH_VARARGS, vt100_headless_fork_doc}, + {"getlines", (PyCFunction)VT100_getlines, METH_NOARGS, vt100_headless_getlines_doc}, + {"main_loop", (PyCFunction)VT100_main_loop, METH_NOARGS, vt100_headless_main_loop_doc}, + {"stop", (PyCFunction)VT100_stop, METH_NOARGS, vt100_headless_stop_doc}, + {NULL, NULL} /* sentinel */ +}; + + +static PyMemberDef VT100_members[] = { + {"changed_callback", T_OBJECT_EX, offsetof(VT100Object, changed_callback), 0, + "Changed Callback"}, + {NULL} /* Sentinel */ +}; + +static PyGetSetDef VT100_getsetters[] = { + {"width", (getter) VT100_getwidth, NULL, "Terminal width", NULL}, + {"height", (getter) VT100_getheight, NULL, "Terminal height", NULL}, + {NULL} /* Sentinel */ +}; + +static PyTypeObject VT100_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "hl_vt100.vt100_headless", + .tp_basicsize = sizeof(VT100Object), + .tp_dealloc = (destructor)VT100_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_methods = VT100_methods, + .tp_init = (initproc)VT100_init, + .tp_new = PyType_GenericNew, + .tp_members = VT100_members, + .tp_getset = VT100_getsetters, +}; + +PyDoc_STRVAR(module_doc, +"Headless VT100 Terminal Emulator."); + +static struct PyModuleDef hl_vt100_module = { + PyModuleDef_HEAD_INIT, + .m_name = "hl_vt100", + .m_doc = module_doc, +}; + +PyMODINIT_FUNC +PyInit_hl_vt100(void) +{ + PyObject *m; + + m = PyModule_Create(&hl_vt100_module); + allocated = PyMem_Calloc(4096, sizeof(VT100Object*)); + allocated_size = 4096; + if (m == NULL) + return NULL; + if (PyModule_AddType(m, &VT100_Type) < 0) { + Py_DECREF(m); + return NULL; + } + return m; +}