Compare commits

...

12 commits

Author SHA1 Message Date
bfe2d7ca88 switch to the new vt emulator (untested) 2024-08-31 12:08:40 -05:00
7734d1c4d9 add buffer-oriented routines & constify 2024-08-31 12:08:26 -05:00
c6a43d0b41 selected charset is the inverse of what you'd expect 2024-08-31 11:44:49 -05:00
348da63f70 Make unicode mode optional, default off 2024-08-31 11:28:35 -05:00
636e79cd37 Show VT100 line drawing characters 2024-08-31 11:28:35 -05:00
2e5fb48327 Handle EIO when subprogram exits
In this case EIO is expected and
just indicates there will never be
further data to read.
2024-08-31 11:28:35 -05:00
9abcfdcafa hl-vt100: basic utf-8 support 2024-08-27 10:15:17 -05:00
bf3494ec6f fix clear color 2024-08-27 08:53:24 -05:00
117f3ccd53 minimal terminal attribute implementation 2024-08-27 08:48:33 -05:00
0ce5d49ac8 hl-vt100: Add character attribute support.
.. not really tested though.
2024-08-27 06:24:24 -05:00
c4867953cd Add hl-vt100@e598aeeccac6e28afefa8b5213d35c8e8cfb7d97 2024-07-26 09:17:24 -05:00
c4dba51f7f WIP more terminal 2024-07-26 09:13:36 -05:00
23 changed files with 2986 additions and 168 deletions

View file

@ -13,7 +13,13 @@ pico_sdk_init()
project(pico-cr100)
add_executable(cr100 chargen.c vt.c)
add_executable(cr100
chargen.c
hl-vt100/src/lw_terminal_parser.c
hl-vt100/src/lw_terminal_vt100.c
)
target_include_directories(cr100 PRIVATE hl-vt100/src)
pico_enable_stdio_usb(cr100 1)

186
chargen.c
View file

@ -6,7 +6,7 @@
#include "pico/multicore.h"
#include "vga_660x480_60.pio.h"
#include "vt.h"
#include "lw_terminal_vt100.h"
int pixels_sm;
@ -21,6 +21,8 @@ int pixels_sm;
#define CHAR_Y (9)
#define FB_HEIGHT_PIXEL (FB_HEIGHT_CHAR * CHAR_Y)
struct lw_terminal_vt100 *vt100;
void __not_in_flash_func(scan_convert)(const uint32_t * restrict cptr32, const uint16_t * restrict cgptr, const uint16_t * restrict shade) {
#define READ_CHARDATA \
(ch = *cptr32++)
@ -80,65 +82,8 @@ int cx, cy, attr = 0x300;
_Static_assert(FB_WIDTH_CHAR % 6 == 0);
uint32_t chardata32[FB_WIDTH_CHAR * FB_HEIGHT_CHAR / 2];
int readchar(int cx, int cy) {
uint16_t *chardata = (void*)chardata32;
int i = cx + cy * FB_WIDTH_CHAR;
return chardata[i] & 0xff;
}
int readattr(int cx, int cy) {
uint16_t *chardata = (void*)chardata32;
int i = cx + cy * FB_WIDTH_CHAR;
return chardata[i] & 0xff00;
}
void setchar(int cx, int cy, int ch) {
uint16_t *chardata = (void*)chardata32;
int i = cx + cy * FB_WIDTH_CHAR;
chardata[i] = (chardata[i] & 0xff00) | ch;
}
void setattr(int cx, int cy, int attr) {
uint16_t *chardata = (void*)chardata32;
int i = cx + cy * FB_WIDTH_CHAR;
chardata[i] = (chardata[i] & 0xff) | attr;
}
void scroll_terminal() {
memmove(chardata32, chardata32 + FB_WIDTH_CHAR / 2, FB_WIDTH_CHAR * (FB_HEIGHT_CHAR - 1) * 2);
uint32_t mask = attr | (attr << 16);
for(size_t i=0; i<FB_WIDTH_CHAR / 2; i++) {
chardata32[(FB_HEIGHT_CHAR-1) * FB_WIDTH_CHAR / 2 + i] = mask;
}
}
void increase_y() {
cy = (cy + 1);
if (cy == FB_HEIGHT_CHAR) {
scroll_terminal();
cy = FB_HEIGHT_CHAR - 1;
}
}
int writefn(void *cookie, const char *data, int n) {
uint16_t *chardata = cookie;
for(; n; data++, n--) {
switch(*data) {
case '\r':
cx = 0;
break;
case '\n':
increase_y();
break;
default:
if(*data >= 32) {
if(cx == FB_WIDTH_CHAR) {
cx = 0;
increase_y();
}
chardata[cx + cy * FB_WIDTH_CHAR] = *data | attr;
cx ++;
}
}
}
lw_terminal_vt100_read_buf(vt100, data, n);
return n;
}
@ -241,141 +186,48 @@ void __not_in_flash_func(core1_entry)() {
#define BG_ATTR(x) ((x) << 11)
#define FG_ATTR(x) ((x) << 8)
int saved_attr;
void show_cursor() {
int xx = cx == FB_WIDTH_CHAR ? FB_WIDTH_CHAR - 1 : cx;
saved_attr = readattr(xx, cy);
setattr(xx, cy, saved_attr ^ BG_ATTR(7));
}
void hide_cursor() {
int xx = cx == FB_WIDTH_CHAR ? FB_WIDTH_CHAR - 1 : cx;
setattr(xx, cy, saved_attr);
}
#define MAKE_ATTR(fg, bg) ((fg) ^ (((bg) * 9) & 073))
esc_state vt_st;
#define MAKE_ATTR(fg, bg) (((fg) ^ (((bg) * 9) & 073)) << 8)
void invert_screen() {
for(size_t i=0; i<count_of(chardata32); i++) { chardata32[i] ^= 0x18001800; }
}
void clear_eol() {
uint16_t *chardata = (void*)chardata32;
for(int x = cx; x < FB_WIDTH_CHAR; x++) {
chardata[cx + cy * FB_WIDTH_CHAR] = 32 | attr;
}
}
void clear_screen() {
uint32_t x = (32 | attr) | ((32 | attr) << 16);
for(size_t i=0; i<count_of(chardata32); i++) { chardata32[i] = x; }
}
void cursor_left() {
if(cx > 0) cx -= 1;
}
void cursor_position(esc_state *st) {
// param 1 is row (cy), 1-bsaed
if(st->esc_param[1] > 0 && st->esc_param[1] < FB_WIDTH_CHAR) {
cx = st->esc_param[1] - 1;
}
// param 1 is column (cx), 1-bsaed
if(st->esc_param[0] > 0 && st->esc_param[0] < FB_HEIGHT_CHAR) {
cy = st->esc_param[0] - 1;
}
}
int map_one(int i) {
return (i > 0) + (i > 6);
}
void char_attr(esc_state *st) {
int new_fg = 2;
int new_bg = 0;
for(int i= 0; i<count_of(st->esc_param); i++) {
int p = st->esc_param[i];
if (30 <= p && p <= 37) new_fg = map_one(p - 30);
if (90 <= p && p <= 97) new_fg = map_one(p - 90);
if (40 <= p && p <= 47) new_fg = map_one(p - 40);
if (100 <= p && p <= 107) new_fg = map_one(p - 100);
lw_cell_t char_attr(void *user_data, const struct lw_parsed_attr *attr) {
int fg = map_one(attr->fg);
int bg = map_one(attr->bg);
if(attr->bold) fg = 3;
if(attr->blink) fg ^= 4;
if(attr->inverse) {
return MAKE_ATTR(bg, fg);
}
attr = MAKE_ATTR(new_fg, new_bg) << 8;
return MAKE_ATTR(fg, bg);
}
int main() {
#if !STANDALONE
set_sys_clock_khz(vga_660x480_60_sys_clock_khz, false);
stdio_init_all();
#endif
vt100 = lw_terminal_vt100_init(NULL, NULL, NULL, FB_WIDTH_CHAR, FB_HEIGHT_CHAR);
multicore_launch_core1(core1_entry);
scrnprintf(
"(line 0)\r\n"
"CR100 terminal demo...\r\n"
);
for(int bg = 0; bg < 8; bg++) {
for(int fg = 0; fg < 8; fg++) {
attr = MAKE_ATTR(fg, bg) << 8;
scrnprintf(" %o%o ", bg, fg);
attr = 0x300;
scrnprintf(" ");
}
scrnprintf("\r\n");
}
multicore_launch_core1(core1_entry);
attr = 0x300;
show_cursor();
while (true) {
int c = getchar();
if (c == EOF) { continue; }
vt_action action = vt_process_code(&vt_st, c);
if (action == NO_OUTPUT) { continue; }
hide_cursor();
switch(action) {
case NO_OUTPUT:
__builtin_unreachable();
case PRINTABLE:
scrnprintf("%c", c);
if(0 && c == '\r')
scrnprintf("\n");
break;
case BELL:
invert_screen();
sleep_ms(100);
invert_screen();
break;
case CLEAR_EOL:
clear_eol();
break;
case CLEAR_SCREEN:
clear_screen();
break;
case CURSOR_LEFT:
cursor_left();
break;
case CURSOR_POSITION:
cursor_position(&vt_st);
break;
case CHAR_ATTR:
char_attr(&vt_st);
break;
}
show_cursor();
char buf[2] = { c, 0 };
lw_terminal_vt100_read_str(vt100, buf);
}
return 0;

7
hl-vt100/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
*.o
*.so
parser
*.egg-info/
.venv/
.envrc
test

24
hl-vt100/LICENSE Normal file
View file

@ -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.

1
hl-vt100/MANIFEST.in Normal file
View file

@ -0,0 +1 @@
include src/*.h

45
hl-vt100/Makefile Normal file
View file

@ -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 -std=gnu17 -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 -Wfallthrough -std=gnu17 -o /dev/null -S ${CHK_SOURCES}

189
hl-vt100/README.md Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#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);`

View file

@ -0,0 +1,79 @@
.\" 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 <lw_terminal_parser.h>
.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_parser_read_buf(struct lw_terminal *" this " , char *" c ", size_t " n);"
.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_buf()
.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 <stdlib.h>
#include <stdio.h>
#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 <julien@palard.fr>,
for the Debian project (and may be used by others).

33
hl-vt100/example/Makefile Normal file
View file

@ -0,0 +1,33 @@
##
## Makefile for lw_vt100 examples
##
## Made by julien palard <vt100@mandark.fr>
##
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}

33
hl-vt100/example/parser.c Normal file
View file

@ -0,0 +1,33 @@
#include <stdlib.h>
#include <stdio.h>
#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;
}

30
hl-vt100/example/top.c Normal file
View file

@ -0,0 +1,30 @@
#include <stdio.h>
#include <stdlib.h>
#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;
}

19
hl-vt100/example/top.py Normal file
View file

@ -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()

44
hl-vt100/run_tests.sh Executable file
View file

@ -0,0 +1,44 @@
#!/bin/bash
# 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.
set -eo pipefail
if [ "$1" = python ]
then
python -m pip install .
python vt_test.py
exit
fi
make clean
if [ "$1" = c ]
then
make && make test
LD_LIBRARY_PATH=. ./test /usr/bin/top -n 1
exit
fi
bash $0 python
bash $0 c

39
hl-vt100/setup.py Normal file
View file

@ -0,0 +1,39 @@
#!/usr/bin/env python
"""
setup.py file for hl_vt100
"""
from pathlib import Path
from setuptools import setup, Extension
hl_vt100_module = Extension('hl_vt100',
include_dirs=['src'],
define_macros=[('NDEBUG', '1')],
sources=['src/vt100_module.c',
'src/hl_vt100.c',
'src/lw_terminal_parser.c',
'src/lw_terminal_vt100.c'])
setup(name='hl_vt100',
version='0.2',
url='https://github.com/JulienPalard/vt100-emulator',
author="Julien Palard",
author_email='julien@palard.fr',
description="""Headless vt100 emulator""",
long_description=(Path(__file__).parent / "README.md").read_text(encoding="UTF-8"),
long_description_content_type="text/markdown",
ext_modules=[hl_vt100_module],
include_package_data=True,
py_modules=["hl_vt100"],
classifiers=[
"Programming Language :: C",
"Programming Language :: Python",
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Topic :: System :: Emulators",
"Topic :: Terminals :: Terminal Emulators/X Terminals"
])

171
hl-vt100/src/hl_vt100.c Normal file
View file

@ -0,0 +1,171 @@
/*
* 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.
*/
#define _XOPEN_SOURCE
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pty.h>
#include <stdlib.h>
#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)
{
if (errno == EIO) {
return EXIT_SUCCESS;
}
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 lw_cell_t **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, NULL, winsize.ws_col, winsize.ws_row);
this->term->master_write = master_write;
ioctl(this->master, TIOCSWINSZ, &winsize);
}
restore_termios(this, 0);
}

54
hl-vt100/src/hl_vt100.h Normal file
View file

@ -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 <utmp.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <termios.h>
#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 lw_cell_t **vt100_headless_getlines(struct vt100_headless *this);
void vt100_headless_stop(struct vt100_headless *this);
#endif

View file

@ -0,0 +1,234 @@
/*
* 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 <stdlib.h>
#ifndef NDEBUG
# include <stdio.h>
#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_buf(struct lw_terminal *this, const char *c, size_t n)
{
while (n--)
lw_terminal_parser_read(this, *c++);
}
void lw_terminal_parser_read_str(struct lw_terminal *this, const 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);
}

View file

@ -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.
*/
#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, const char *c);
void lw_terminal_parser_read_buf(struct lw_terminal *this, const char *c, size_t n);
void lw_terminal_parser_destroy(struct lw_terminal* this);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,124 @@
/*
* 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 <stdint.h>
#include <stdbool.h>
#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))
typedef uint16_t lw_cell_t;
struct lw_parsed_attr {
uint8_t fg, bg;
bool blink, bold, inverse;
};
#define LW_DEFAULT_ATTR ((struct lw_parsed_attr) { 7, 0, false, false, false })
/*
** 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;
int ustate, ubits;
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 */
lw_cell_t *ascreen;
lw_cell_t *afrozen_screen;
char *tabulations;
bool unicode;
unsigned int selected_charset;
unsigned int modes;
struct lw_parsed_attr parsed_attr;
lw_cell_t attr, saved_cell;
const lw_cell_t *alines[80];
void (*master_write)(void *user_data, void *buffer, size_t len);
lw_cell_t (*encode_attr)(void *user_data, const struct lw_parsed_attr *attr);
void *user_data;
};
struct lw_terminal_vt100 *lw_terminal_vt100_init(void *user_data,
void (*unimplemented)(struct lw_terminal* term_emul,
char *seq, char chr),
lw_cell_t (*encode_attr)(void *user_data,
const struct lw_parsed_attr *attr),
unsigned int width, unsigned int height);
char lw_terminal_vt100_get(struct lw_terminal_vt100 *vt100, unsigned int x, unsigned int y);
const lw_cell_t *lw_terminal_vt100_getline(struct lw_terminal_vt100 *vt100, unsigned y);
const lw_cell_t **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, const char *buffer);
void lw_terminal_vt100_read_buf(struct lw_terminal_vt100 *this, const char *buffer, size_t n);
#endif

65
hl-vt100/src/test.c Normal file
View file

@ -0,0 +1,65 @@
/*
* 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 <stdlib.h>
#include <unistd.h>
#include <utmp.h>
#include <string.h>
#include <pty.h>
#include <stdio.h>
#include "hl_vt100.h"
void disp(struct vt100_headless *vt100)
{
unsigned int x, y;
const lw_cell_t **lines;
lines = vt100_headless_getlines(vt100);
write(1, "\n", 1);
for (y = 0; y < vt100->term->height; ++y)
{
write(1, "|", 1);
for (x = 0; x < vt100->term->width; x++) {
write(1, &lines[y][x], 1);
}
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;
}

325
hl-vt100/src/vt100_module.c Normal file
View file

@ -0,0 +1,325 @@
/* 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 <stddef.h>
#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_getattrlines_doc,
"getattrlines()\n\
\n\
Get a list of lines (with attributes) as currently seen by the emulator.");
static PyObject *
VT100_getattrlines(VT100Object *self, PyObject *Py_UNUSED(ignored))
{
const lw_cell_t **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++) {
PyObject *row = PyList_New(0);
if (row == NULL) {
Py_XDECREF(result);
return NULL;
}
PyList_Append(result, row);
for (unsigned int j = 0; j < self->obj->term->width; j++) {
PyObject *value = PyLong_FromLong(lines[i][j]);
if (!value) {
Py_XDECREF(result);
return NULL;
}
PyList_Append(row, value);
}
}
return result;
}
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 lw_cell_t **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++) {
PyObject *row = PyUnicode_New(self->obj->term->width, 255);
if (row == NULL) {
Py_XDECREF(result);
return NULL;
}
for (unsigned int j = 0; j < self->obj->term->width; j++) {
PyUnicode_WriteChar(row, j, lines[i][j] & 0xff);
}
PyList_Append(result, row);
}
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},
{"getattrlines", (PyCFunction)VT100_getattrlines, METH_NOARGS, vt100_headless_getattrlines_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;
}

13
hl-vt100/vt_test.py Normal file
View file

@ -0,0 +1,13 @@
import hl_vt100
print("Starting python test...")
vt100 = hl_vt100.vt100_headless()
vt100.fork("/usr/bin/top", ["/usr/bin/top", "-n", "1"])
vt100.main_loop()
print(*vt100.getlines(), sep="\n")
print()
for a in vt100.getattrlines():
s = " ".join(f"{c >> 8:02x}" for c in a[:20])
print(s)