diff options
author | Gerd Hoffmann <kraxel@redhat.com> | 2019-02-28 12:36:45 +0100 |
---|---|---|
committer | Gerd Hoffmann <kraxel@redhat.com> | 2019-02-28 12:36:45 +0100 |
commit | 853c1b3a67a6b9c7bd6a67c67b5ce6f43f9fe90d (patch) | |
tree | 53ee96da1ecd41ba75601721a6165aec40c135e5 | |
parent | 6d62ddcf9d95c5ddcbe1da14630cc69b44076f3c (diff) | |
download | fbida-853c1b3a67a6b9c7bd6a67c67b5ce6f43f9fe90d.tar.gz |
fbcon: switch to libtsm
-rw-r--r-- | README.tmt | 637 | ||||
-rw-r--r-- | fbcon.c | 244 | ||||
-rw-r--r-- | meson.build | 19 | ||||
-rw-r--r-- | tmt.c | 500 | ||||
-rw-r--r-- | tmt.h | 140 |
5 files changed, 156 insertions, 1384 deletions
diff --git a/README.tmt b/README.tmt deleted file mode 100644 index f3819df..0000000 --- a/README.tmt +++ /dev/null @@ -1,637 +0,0 @@ - -============================================ -libtmt - a simple terminal emulation library -============================================ - -libtmt is the Tiny Mock Terminal Library. It provides emulation of a classic -smart text terminal, by maintaining an in-memory screen image. Sending text -and command sequences to libtmt causes it to update this in-memory image, -which can then be examined and rendered however the user sees fit. - -The imagined primary goal for libtmt is to for terminal emulators and -multiplexers; it provides the terminal emulation layer for the `mtm`_ -terminal multiplexer, for example. Other uses include screen-scraping and -automated test harnesses. - -libtmt is similar in purpose to `libtsm`_, but considerably smaller (500 -lines versus 6500 lines). libtmt is also, in this author's humble opinion, -considerably easier to use. - -.. _`mtm`: https://github.com/deadpixi/mtm -.. _`libtsm`: https://www.freedesktop.org/wiki/Software/kmscon/libtsm/ - -Major Features and Advantages -============================= - -Works Out-of-the-Box - libtmt emulates a well-known terminal type (`ansi`), the definition of - which has been in the terminfo database since at least 1995. There's no - need to install a custom terminfo entry. There's no claiming to be an - xterm but only emulating a small subset of its features. Any program - using terminfo works automatically: this includes vim, emacs, mc, - cmus, nano, nethack, ... - -Portable - Written in pure C99. - Optionally, the POSIX-mandated `wcwidth` function can be used, which - provides minimal support for combining characters. - -Small - Less than 500 lines of C, including comments and whitespace. - -Free - Released under a BSD-style license, free for commercial and - non-commerical use, with no restrictions on source code release or - redistribution. - -Simple - Only 8 functions to learn, and really you can get by with 6! - -International - libtmt internally uses wide characters exclusively, and uses your C - library's multibyte encoding functions. - This means that the library automatically supports any encoding that - your operating system does. - -How to Use libtmt -================= - -libtmt is a single C file and a single header. Just include these files -in your project and you should be good to go. - -By default, libtmt uses only ISO standard C99 features, -but see `Compile-Time Options`_ below. - -Example Code ------------- - -Below is a simple program fragment giving the flavor of libtmt. -Note that another good example is the `mtm`_ terminal multiplexer: - -.. _`mtm`: https://github.com/deadpixi/mtm - -.. code:: c - - #include <stdio.h> - #include <stdlib.h> - #include "tmt.h" - - /* Forward declaration of a callback. - * libtmt will call this function when the terminal's state changes. - */ - void callback(tmt_msg_t m, TMT *vt, const void *a, void *p); - - int - main(void) - { - /* Open a virtual terminal with 2 lines and 10 columns. - * The first NULL is just a pointer that will be provided to the - * callback; it can be anything. The second NULL specifies that - * we want to use the default Alternate Character Set; this - * could be a pointer to a wide string that has the desired - * characters to be displayed when in ACS mode. - */ - TMT *vt = tmt_open(2, 10, callback, NULL, NULL); - if (!vt) - return perror("could not allocate terminal"), EXIT_FAILURE; - - /* Write some text to the terminal, using escape sequences to - * use a bold rendition. - * - * The final argument is the length of the input; 0 means that - * libtmt will determine the length dynamically using strlen. - */ - tmt_write(vt, "\033[1mhello, world (in bold!)\033[0m", 0); - - /* Writing input to the virtual terminal can (and in this case, did) - * call the callback letting us know the screen was updated. See the - * callback below to see how that works. - */ - tmt_close(vt); - return EXIT_SUCCESS; - } - - void - callback(tmt_msg_t m, TMT *vt, const void *a, void *p) - { - /* grab a pointer to the virtual screen */ - const TMTSCREEN *s = tmt_screen(vt); - const TMTPOINT *c = tmt_cursor(vt); - - switch (m){ - case TMT_MSG_BELL: - /* the terminal is requesting that we ring the bell/flash the - * screen/do whatever ^G is supposed to do; a is NULL - */ - printf("bing!\n"); - break; - - case TMT_MSG_UPDATE: - /* the screen image changed; a is a pointer to the TMTSCREEN */ - for (size_t r = 0; r < s->nline; r++){ - if (s->lines[r]->dirty){ - for (size_t c = 0; c < s->ncol; c++){ - printf("contents of %zd,%zd: %lc (%s bold)\n", r, c, - s->lines[r]->chars[c].c, - s->lines[r]->chars[c].a.bold? "is" : "is not"); - } - } - } - - /* let tmt know we've redrawn the screen */ - tmt_clean(vt); - break; - - case TMT_MSG_ANSWER: - /* the terminal has a response to give to the program; a is a - * pointer to a string */ - printf("terminal answered %s\n", (const char *)a); - break; - - case TMT_MSG_MOVED: - /* the cursor moved; a is a pointer to the cursor's TMTPOINT */ - printf("cursor is now at %zd,%zd\n", c->r, c->c); - break; - } - } - -Data Types and Enumerations ---------------------------- - -.. code:: c - - /* an opaque structure */ - typedef struct TMT TMT; - - /* possible messages sent to the callback */ - typedef enum{ - TMT_MSG_MOVED, /* the cursor changed position */ - TMT_MSG_UPDATE, /* the screen image changed */ - TMT_MSG_ANSWER, /* the terminal responded to a query */ - TMT_MSG_BELL /* the terminal bell was rung */ - } tmt_msg_T; - - /* a callback for the library - * m is one of the message constants above - * vt is a pointer to the vt structure - * r is NULL for TMT_MSG_BELL - * is a pointer to the cursor's TMTPOINT for TMT_MSG_MOVED - * is a pointer to the terminal's TMTSCREEN for TMT_MSG_UPDATE - * is a pointer to a string for TMT_MSG_ANSWER - * p is whatever was passed to tmt_open (see below). - */ - typedef void (*TMTCALLBACK)(tmt_msg_t m, struct TMT *vt, - const void *r, void *p); - - /* color definitions */ - typedef enum{ - TMT_COLOR_BLACK, - TMT_COLOR_RED, - TMT_COLOR_GREEN, - TMT_COLOR_YELLOW, - TMT_COLOR_BLUE, - TMT_COLOR_MAGENTA, - TMT_COLOR_CYAN, - TMT_COLOR_WHITE, - TMT_COLOR_DEFAULT /* whatever the host terminal wants it to mean */ - } tmt_color_t; - - /* graphical rendition */ - typedef struct TMTATTRS TMTATTRS; - struct TMTATTRS{ - bool bold; /* character is bold */ - bool dim; /* character is half-bright */ - bool underline; /* character is underlined */ - bool blink; /* character is blinking */ - bool reverse; /* character is in reverse video */ - bool invisible; /* character is invisible */ - tmt_color_t fg; /* character foreground color */ - tmt_color_t bg; /* character background color */ - }; - - /* characters */ - typedef struct TMTCHAR TMTCHAR; - struct TMTCHAR{ - wchar_t c; /* the character */ - TMTATTRS a; /* its rendition */ - }; - - /* a position on the screen; upper left corner is 0,0 */ - typedef struct TMTPOINT TMTPOINT; - struct TMTPOINT{ - size_t r; /* row */ - size_t c; /* column */ - }; - - /* a line of characters on the screen; - * every line is always as wide as the screen - */ - typedef struct TMTLINE TMTLINE; - struct TMTLINE{ - bool dirty; /* line has changed since it was last drawn */ - TMTCHAR chars; /* the contents of the line */ - }; - - /* a virtual terminal screen image */ - typedef struct TMTSCREEN TMTSCREEN; - struct TMTSCREEN{ - size_t nline; /* number of rows */ - size_t ncol; /* number of columns */ - TMTLINE **lines; /* the lines on the screen */ - }; - -Functions ---------- - -`TMT *tmt_open(size_t nrows, size_t ncols, TMTCALLBACK cb, VOID *p, const wchar *acs);` - Creates a new virtual terminal, with `nrows` rows and `ncols` columns. - The callback `cb` will be called on updates, and passed `p` as a final - argument. See the definition of `tmt_msg_t` above for possible values - of each argument to the callback. - - Terminals must have a size of at least two rows and two columns. - - `acs` specifies the characters to use when in Alternate Character Set - (ACS) mode. The default string (used if `NULL` is specified) is:: - - L"><^v#+:o##+++++~---_++++|<>*!fo" - - See `Alternate Character Set`_ for more information. - - Note that the callback must be ready to be called immediately, as - it will be called after initialization of the terminal is done, but - before the call to `tmt_open` returns. - -`void tmt_close(TMT *vt)` - Close and free all resources associated with `vt`. - -`bool tmt_resize(TMT *vt, size_t nrows, size_t ncols)` - Resize the virtual terminal to have `nrows` rows and `ncols` columns. - The contents of the area in common between the two sizes will be preserved. - - Terminals must have a size of at least two rows and two columns. - - If this function returns false, the resize failed (only possible in - out-of-memory conditions or invalid sizes). If this happens, the terminal - is trashed and the only valid operation is the close the terminal. - -`void tmt_write(TMT *vt, const char *s, size_t n);` - Write the provided string to the terminal, interpreting any escape - sequences contained threin, and update the screen image. The last - argument is the length of the input. If set to 0, the length is - determined using `strlen`. - - The terminal's callback function may be invoked one or more times before - a call to this function returns. - - The string is converted internally to a wide-character string using the - system's current multibyte encoding. Each terminal maintains a private - multibyte decoding state, and correctly handles mulitbyte characters that - span multiple calls to this function (that is, the final byte(s) of `s` - may be a partial mulitbyte character to be completed on the next call). - -`const TMTSCREEN *tmt_screen(const TMT *vt);` - Returns a pointer to the terminal's screen image. - -`const TMTPOINT *tmt_cursor(cosnt TMT *vt);` - Returns a pointer to the terminal's cursor position. - -`void tmt_clean(TMT *vt);` - Call this after receiving a `TMT_MSG_UPDATE` or `TMT_MSG_MOVED` callback - to let the library know that the program has handled all reported changes - to the screen image. - -`void tmt_reset(TMT *vt);` - Resets the virtual terminal to its default state (colors, multibyte - decoding state, rendition, etc). - -Special Keys ------------- - -To send special keys to a program that is using libtmt for its display, -write one of the `TMT_KEY_*` strings to that program's standard input -(*not* to libtmt; it makes no sense to send any of these constants to -libtmt itself). - -The following macros are defined, and are all constant strings: - -- TMT_KEY_UP -- TMT_KEY_DOWN -- TMT_KEY_RIGHT -- TMT_KEY_LEFT -- TMT_KEY_HOME -- TMT_KEY_END -- TMT_KEY_INSERT -- TMT_KEY_BACKSPACE -- TMT_KEY_ESCAPE -- TMT_KEY_BACK_TAB -- TMT_KEY_PAGE_UP -- TMT_KEY_PAGE_DOWN -- TMT_KEY_F1 through TMT_KEY_F10 - -Note also that the classic PC console sent the enter key as -a carriage return, not a linefeed. Many programs don't care, -but some do. - -Compile-Time Options --------------------- - -There are two preprocessor macros that affect libtmt: - -`TMT_INVALID_CHAR` - Define this to a wide-character. This character will be added to - the virtual display when an invalid multibyte character sequence - is encountered. - - By default (if you don't define it as something else before compiling), - this is `((wchar_t)0xfffd)`, which is the codepoint for the Unicode - 'REPLACEMENT CHARACTER'. Note that your system might not use Unicode, - and its wide-character type might not be able to store a constant as - large as `0xfffd`, in which case you'll want to use an alternative. - -`TMT_HAS_WCWIDTH` - By default, libtmt uses only standard C99 features. If you define - TMT_HAS_WCWIDTH before compiling, libtmt will use the POSIX `wcwidth` - function to detect combining characters. - - Note that combining characters are still not handled particularly - well, regardless of whether this was defined. Also note that what - your C library's `wcwidth` considers a combining character and what - the written language in question considers one could be different. - -Alternate Character Set ------------------------ - -The terminal can be switched to and from its "Alternate Character Set" (ACS) -using escape sequences. The ACS traditionally contained box-drawing and other -semigraphic characters. - -The characters in the ACS are configurable at runtime, by passing a wide string -to `tmt_open`. The default if none is provided (i.e. the argument is `NULL`) -uses ASCII characters to approximate the traditional characters. - -The string passed to `tmt_open` must be 31 characters long. The characters, -and their default ASCII-safe values, are in order: - -- RIGHT ARROW ">" -- LEFT ARROW "<" -- UP ARROW "^" -- DOWN ARROW "v" -- BLOCK "#" -- DIAMOND "+" -- CHECKERBOARD "#" -- DEGREE "o" -- PLUS/MINUS "+" -- BOARD ":" -- LOWER RIGHT CORNER "+" -- UPPER RIGHT CORNER "+" -- UPPER LEFT CORNER "+" -- LOWER LEFT CORNER "+" -- CROSS "+" -- SCAN LINE 1 "~" -- SCAN LINE 3 "-" -- HORIZONTAL LINE "-" -- SCAN LINE 7 "-" -- SCAN LINE 9 "_" -- LEFT TEE "+" -- RIGHT TEE "+" -- BOTTOM TEE "+" -- TOP TEE "+" -- VERTICAL LINE "|" -- LESS THAN OR EQUAL "<" -- GREATER THAN OR EQUAL ">" -- PI "*" -- NOT EQUAL "!" -- POUND STERLING "f" -- BULLET "o" - -If your system's wide character type's character set corresponds to the -Universal Character Set (UCS/Unicode), the following wide string is a -good option to use:: - - L"→←↑↓■◆▒°±▒┘┐┌└┼⎺───⎽├┤┴┬│≤≥π≠£•" - -**Note that multibyte decoding is disabled in ACS mode.** The traditional -implementations of the "ansi" terminal type (i.e. IBM PCs and compatibles) -had no concept of multibyte encodings and used the character codes -outside the ASCII range for various special semigraphic characters. -(Technically they had an entire alternate character set as well via the -code page mechanism, but that's beyond the scope of this explanation.) - -The end result is that the terminfo definition of "ansi" sends characters -with the high bit set when in ACS mode. This breaks several multibyte -encoding schemes (including, most importantly, UTF-8). - -As a result, libtmt does not attempt to decode multibyte characters in -ACS mode, since that would break the multibyte encoding, the semigraphic -characters, or both. - -In general this isn't a problem, since programs explicitly switch to and -from ACS mode using escape sequences. - -When in ACS mode, bytes that are not special members of the alternate -character set (that is, bytes not mapped to the string provided to -`tmt_open`) are passed unchanged to the terminal. - -Supported Input and Escape Sequences -==================================== - -Internally libtmt uses your C library's/compiler's idea of a wide character -for all characters, so you should be able to use whatever characters you want -when writing to the virtual terminal (but see `Alternate Character Set`_). - -The following escape sequences are recognized and will be processed -specially. - -In the descriptions below, "ESC" means a literal escape character and "Ps" -means zero or more decimal numeric arguments separated by semicolons. -In descriptions "P1", "P2", etc, refer to the first parameter, second -parameter, and so on. If a required parameter is omitted, it defaults -to the smallest meaningful value (zero if the command accepts zero as -an argument, one otherwise). Any number of parameters may be passed, -but any after the first eight are ignored. - -Unless explicitly stated below, cursor motions past the edges of the screen -are ignored and do not result in scrolling. When characters are moved, -the spaces left behind are filled with blanks and any characters moved -off the edges of the screen are lost. - -====================== ====================================================================== -Sequence Action -====================== ====================================================================== -0x07 (Bell) Callback with TMT_MSG_BELL -0x08 (Backspace) Cursor left one cell -0x09 (Tab) Cursor to next tab stop or end of line -0x0a (Carriage Return) Cursor to first cell on this line -0x0d (Linefeed) Cursor to same column one line down, scroll if needed -ESC H Set a tabstop in this column -ESC 7 Save cursor position and current graphical state -ESC 8 Restore saved cursor position and current graphical state -ESC c Reset terminal to default state -ESC [ Ps A Cursor up P1 rows -ESC [ Ps B Cursor down P1 rows -ESC [ Ps C Cursor right P1 columns -ESC [ Ps D Cursor left P1 columns -ESC [ Ps E Cursor to first column of line P1 rows down from current -ESC [ Ps F Cursor to first column of line P1 rows up from current -ESC [ Ps G Cursor to column P1 -ESC [ Ps d Cursor to row P1 -ESC [ Ps H Cursor to row P1, column P2 -ESC [ Ps f Alias for ESC [ Ps H -ESC [ Ps I Cursor to next tab stop -ESC [ Ps J Clear screen - P1 == 0: from cursor to end of screen - P1 == 1: from beginning of screen to cursor - P1 == 2: entire screen -ESC [ Ps K Clear line - P1 == 0: from cursor to end of line - P1 == 1: from beginning of line to cursor - P1 == 2: entire line -ESC [ Ps L Insert P1 lines at cursor, scrolling lines below down -ESC [ Ps M Delete P1 lines at cursor, scrolling lines below up -ESC [ Ps P Delete P1 characters at cursor, moving characters to the right over -ESC [ Ps S Scroll screen up P1 lines -ESC [ Ps T Scroll screen down P1 lines -ESC [ Ps X Erase P1 characters at cursor (overwrite with spaces) -ESC [ Ps Z Go to previous tab stop -ESC [ Ps b Repeat previous character P1 times -ESC [ Ps c Callback with TMT_MSG_ANSWER "\033[?6c" -ESC [ Ps g If P1 == 3, clear all tabstops -ESC [ Ps h If P1 == 25, show the cursor (if it was hidden) -ESC [ Ps m Change graphical rendition state; see below -ESC [ Ps l If P1 == 25, hide the cursor -ESC [ Ps n If P1 == 6, callback with TMT_MSG_ANSWER "\033[%d;%dR" - with cursor row, column -ESC [ Ps s Alias for ESC 7 -ESC [ Ps u Alias for ESC 8 -ESC [ Ps @ Insert P1 blank spaces at cursor, moving characters to the right over -====================== ====================================================================== - -For the `ESC [ Ps m` escape sequence above ("Set Graphic Rendition"), -up to eight parameters may be passed; the results are cumulative: - -============== ================================================= -Rendition Code Meaning -============== ================================================= -0 Reset all graphic rendition attributes to default -1 Bold -2 Dim (half bright) -4 Underline -5 Blink -7 Reverse video -8 Invisible -10 Leave ACS mode -11 Enter ACS mode -22 Bold off -23 Dim (half bright) off -24 Underline off -25 Blink off -27 Reverse video off -28 Invisible off -30 Foreground black -31 Foreground red -32 Foreground green -33 Foreground yellow -34 Foreground blue -35 Foreground magenta -36 Foreground cyan -37 Foreground white -39 Foreground default color -40 Background black -41 Background red -42 Background green -43 Background yellow -44 Background blue -45 Background magenta -46 Background cyan -47 Background white -49 Background default color -============== ================================================= - -Other escape sequences are recognized but ignored. This includes escape -sequences for switching out codesets (officially, all code sets are defined -as equivalent in libtmt), and the various "Media Copy" escape sequences -used to print output on paper (officially, there is no printer attached -to libtmt). - -Additionally, "?" characters are stripped out of escape sequence parameter -lists for compatibility purposes. - -Known Issues -============ - -- Combining characters are "handled" by ignoring them - (when compiled with `TMT_HAS_WCWIDTH`) or by printing them separately. -- Double-width characters are rendered as single-width invalid - characters. -- The documentation and error messages are available only in English. - -Frequently Asked Questions -========================== - -What programs work with libtmt? -------------------------------- - -Pretty much all of them. Any program that doesn't assume what terminal -it's running under should work without problem; this includes any program -that uses the terminfo, termcap, or (pd|n)?curses libraries. Any program -that assumes it's running under some specific terminal might fail if its -assumption is wrong, and not just under libtmt. - -I've tested quite a few applications in libtmt and they've worked flawlessly: -vim, GNU emacs, nano, cmus, mc (Midnight Commander), and others just work -with no changes. - -What programs don't work with libtmt? -------------------------------------- - -Breakage with libtmt is of two kinds: breakage due to assuming a terminal -type, and reduced functionality. - -In all my testing, I only found one program that didn't work correctly by -default with libtmt: recent versions of Debian's `apt`_ assume a terminal -with definable scrolling regions to draw a fancy progress bar during -package installation. Using apt in its default configuration in libtmt will -result in a corrupted display (that can be fixed by clearing the screen). - -.. _`apt`: https://wiki.debian.org/Apt - -In my honest opinion, this is a bug in apt: it shouldn't assume the type -of terminal it's running in. - -The second kind of breakage is when not all of a program's features are -available. The biggest missing feature here is mouse support: libtmt -doesn't, and probably never will, support mouse tracking. I know of many -programs that *can* use mouse tracking in a terminal, but I don't know -of any that *require* it. Most (if not all?) programs of this kind would -still be completely usable in libtmt. - -License -------- - -Copyright (c) 2017 Rob King -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -- 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. -- Neither the name of the copyright holder nor the - names of contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 AUTHORS, -COPYRIGHT HOLDERS, 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. @@ -26,12 +26,12 @@ #include <libudev.h> #include <libinput.h> #include <xkbcommon/xkbcommon.h> +#include <libtsm.h> #include "fbtools.h" #include "drmtools.h" #include "vt.h" #include "kbd.h" -#include "tmt.h" /* ---------------------------------------------------------------------- */ @@ -40,14 +40,15 @@ static char *font_name = "monospace"; static int font_size = 16; static gfxstate *gfx; -static cairo_surface_t *surface1; -static cairo_surface_t *surface2; -static cairo_t *context1; -static cairo_t *context2; -cairo_font_extents_t extents; - -static TMT *vt; -static int clear, dirty, pty; +static cairo_font_extents_t extents; +static struct cairo_state { + cairo_surface_t *surface; + cairo_t *context; + tsm_age_t age; + int clear; +} state1, state2; + +static int dirty, pty; static struct udev *udev; static struct libinput *kbd; @@ -62,6 +63,9 @@ static struct xkb_rule_names layout = { .options = NULL, }; +static struct tsm_screen *vts; +static struct tsm_vte *vte; + int debug = 0; /* ---------------------------------------------------------------------- */ @@ -118,7 +122,8 @@ static void console_switch_resume(void) { gfx->restore_display(); libinput_resume(kbd); - clear++; + state1.clear++; + state2.clear++; dirty++; } @@ -182,12 +187,7 @@ static void xkb_configure(void) /* ---------------------------------------------------------------------- */ -struct color { - float r; - float g; - float b; -}; - +#if 0 static struct color tmt_colors_normal[] = { [ TMT_COLOR_BLACK ] = { .r = 0.0, .g = 0.0, .b = 0.0 }, [ TMT_COLOR_RED ] = { .r = 0.7, .g = 0.0, .b = 0.0 }, @@ -230,80 +230,123 @@ struct color *tmt_background(struct TMTATTRS *a) bg = TMT_COLOR_BLACK; return tmt_colors_normal + bg; } +#endif /* ---------------------------------------------------------------------- */ -static void render(void) +struct color { + float r; + float g; + float b; +}; + +static const struct color black = { 0, 0, 0 }; +static const struct color white = { 1, 1, 1 }; + +void tsm_log_cb(void *data, const char *file, int line, + const char *func, const char *subs, unsigned int sev, + const char *format, va_list args) { - static bool second; - const TMTSCREEN *s = tmt_screen(vt); - const TMTPOINT *cursor = tmt_cursor(vt); - cairo_t *context; - int line, col, tx, ty; - wchar_t ws[2]; - char utf8[10]; + if (sev == 7 /* debug */ && !debug) + return; - if (surface2) - second = !second; - context = second ? context2 : context1; + fprintf(stderr, "<%d> ", sev); + if (file) + fprintf(stderr, "[%s:%d] ", file, line); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); +} - tx = (gfx->hdisplay - (extents.max_x_advance * s->ncol)) / 2; - ty = (gfx->vdisplay - (extents.height * s->nline)) / 2; +void tsm_write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data) +{ +} - if (clear) { - cairo_set_source_rgb(context, 0, 0, 0); - cairo_paint(context); +int tsm_draw_cb(struct tsm_screen *con, uint32_t id, + const uint32_t *ch, size_t len, + unsigned int width, unsigned int posx, unsigned int posy, + const struct tsm_screen_attr *attr, + tsm_age_t age, void *data) +{ + struct cairo_state *s = data; + struct color bg = { + .r = attr->br / 255.0, + .g = attr->bg / 255.0, + .b = attr->bb / 255.0, + }; + struct color fg = { + .r = attr->fr / 255.0, + .g = attr->fg / 255.0, + .b = attr->fb / 255.0, + }; + int sw = tsm_screen_get_width(con) * extents.max_x_advance; + int sh = tsm_screen_get_height(con) * extents.height; + int tx = (gfx->hdisplay - sw) / 2; + int ty = (gfx->vdisplay - sh) / 2; + wchar_t ws[8]; + char utf8[32]; + int i; + + if (posx == tsm_screen_get_cursor_x(con) && + posy == tsm_screen_get_cursor_y(con) && + !(tsm_screen_get_flags(con) & TSM_SCREEN_HIDE_CURSOR)) { + bg = white; + fg = black; } - for (line = 0; line < s->nline; line++) { - for (col = 0; col < s->ncol; col++) { - TMTCHAR *c = &s->lines[line]->chars[col]; - struct color *bg = tmt_background(&c->a); - struct color *fg = tmt_foreground(&c->a); + /* background */ + cairo_rectangle(s->context, + tx + posx * extents.max_x_advance, + ty + posy * extents.height, + extents.max_x_advance * width, + extents.height); + cairo_set_source_rgb(s->context, bg.r, bg.g, bg.b); + cairo_fill(s->context); + + /* char */ + cairo_move_to(s->context, + tx + posx * extents.max_x_advance, + ty + posy * extents.height + extents.ascent); + cairo_set_source_rgb(s->context, fg.r, fg.g, fg.b); + for (i = 0; i < len && i < ARRAY_SIZE(ws)-1; i++) + ws[i] = ch[i]; + ws[i] = 0; + wcstombs(utf8, ws, sizeof(utf8)); + cairo_show_text(s->context, utf8); - if (cursor->r == line && cursor->c == col) { - bg = tmt_colors_normal + TMT_COLOR_WHITE; - fg = tmt_colors_normal + TMT_COLOR_BLACK; - } + return 0; +} - /* background */ - cairo_rectangle(context, - tx + col * extents.max_x_advance, - ty + line * extents.height, - extents.max_x_advance, - extents.height); - cairo_set_source_rgb(context, bg->r, bg->g, bg->b); - cairo_fill(context); - - /* char */ - cairo_move_to(context, - tx + col * extents.max_x_advance, - ty + line * extents.height + extents.ascent); - cairo_set_source_rgb(context, fg->r, fg->g, fg->b); - ws[0] = c->c; - ws[1] = 0; - wcstombs(utf8, ws, sizeof(utf8)); - cairo_show_text(context, utf8); - } +static void cairo_render(void) +{ + static bool second; + struct cairo_state *s; + + if (state2.surface) + second = !second; + s = second ? &state2 : &state1; + + if (s->clear) { + s->clear = 0; + cairo_set_source_rgb(s->context, 0, 0, 0); + cairo_paint(s->context); + s->age = 0; } - cairo_show_page(context); + s->age = tsm_screen_draw(vts, tsm_draw_cb, s); + cairo_show_page(s->context); if (gfx->flush_display) gfx->flush_display(second); } -static void tmt_callback(tmt_msg_t m, TMT *vt, const void *a, void *p) +static void cairo_state_init(struct cairo_state *s, + const char *font_name, int font_size) { - switch (m) { - case TMT_MSG_UPDATE: - dirty++; - break; - case TMT_MSG_ANSWER: - write(pty, a, strlen(a)); - default: - break; - } + s->context = cairo_create(s->surface); + cairo_select_font_face(s->context, font_name, + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(s->context, font_size); } static void child_exec_shell(struct winsize *win) @@ -335,7 +378,7 @@ static void child_exec_shell(struct winsize *win) /* prepare environment, run shell */ snprintf(lines, sizeof(lines), "%d", win->ws_row); snprintf(columns, sizeof(columns), "%d", win->ws_col); - setenv("TERM", "ansi", true); + setenv("TERM", "xterm-256color", true); setenv("LINES", lines, true); setenv("COLUMNS", columns, true); @@ -402,28 +445,20 @@ int main(int argc, char *argv[]) } /* init cairo */ - surface1 = cairo_image_surface_create_for_data(gfx->mem, - gfx->fmt->cairo, - gfx->hdisplay, - gfx->vdisplay, - gfx->stride); - context1 = cairo_create(surface1); - cairo_select_font_face(context1, font_name, - CAIRO_FONT_SLANT_NORMAL, - CAIRO_FONT_WEIGHT_NORMAL); - cairo_set_font_size(context1, font_size); - cairo_font_extents(context1, &extents); + state1.surface = cairo_image_surface_create_for_data(gfx->mem, + gfx->fmt->cairo, + gfx->hdisplay, + gfx->vdisplay, + gfx->stride); + cairo_state_init(&state1, font_name, font_size); + cairo_font_extents(state1.context, &extents); if (gfx->mem2) { - surface2 = cairo_image_surface_create_for_data(gfx->mem2, - gfx->fmt->cairo, - gfx->hdisplay, - gfx->vdisplay, - gfx->stride); - context2 = cairo_create(surface2); - cairo_select_font_face(context2, font_name, - CAIRO_FONT_SLANT_NORMAL, - CAIRO_FONT_WEIGHT_NORMAL); - cairo_set_font_size(context2, font_size); + state2.surface = cairo_image_surface_create_for_data(gfx->mem2, + gfx->fmt->cairo, + gfx->hdisplay, + gfx->vdisplay, + gfx->stride); + cairo_state_init(&state2, font_name, font_size); } /* init libinput */ @@ -450,7 +485,12 @@ int main(int argc, char *argv[]) win.ws_row = gfx->vdisplay / extents.height; win.ws_xpixel = extents.max_x_advance; win.ws_ypixel = extents.height; - vt = tmt_open(win.ws_row, win.ws_col, tmt_callback, NULL, NULL); + + tsm_screen_new(&vts, tsm_log_cb, NULL); + tsm_screen_resize(vts, win.ws_col, win.ws_row); + tsm_vte_new(&vte, vts, tsm_write_cb, NULL, tsm_log_cb, NULL); + + /* run shell */ child = forkpty(&pty, NULL, NULL, &win); if (0 == child) { child_exec_shell(&win); @@ -460,14 +500,17 @@ int main(int argc, char *argv[]) } /* parent */ - clear++; + state1.clear++; + state2.clear++; dirty++; for (;;) { fd_set set; int rc, max; - if (dirty) - render(); + if (dirty) { + cairo_render(); + dirty = 0; + } max = 0; FD_ZERO(&set); @@ -489,8 +532,10 @@ int main(int argc, char *argv[]) break; /* read error */ if (rc == 0) break; /* no data -> EOF */ - if (rc > 0) - tmt_write(vt, buf, rc); + if (rc > 0) { + tsm_vte_input(vte, buf, rc); + dirty++; + } } if (FD_ISSET(input, &set)) { @@ -520,6 +565,7 @@ int main(int argc, char *argv[]) if (rc > 0) write(pty, buf, rc); } + dirty++; } break; default: diff --git a/meson.build b/meson.build index 5746eb9..af71b23 100644 --- a/meson.build +++ b/meson.build @@ -27,6 +27,7 @@ webp_dep = dependency('libwebp', required : false) udev_dep = dependency('libudev') input_dep = dependency('libinput') xkb_dep = dependency('xkbcommon') +tsm_dep = dependency('libtsm', required : false) # other library deps cc = meson.get_compiler('c') @@ -127,15 +128,17 @@ executable('fbpdf', install : true) # build fbcon -fbcon_srcs = [ 'fbcon.c', 'drmtools.c', 'fbtools.c', 'gfx.c', 'vt.c', 'kbd.c', 'tmt.c' ] -fbcon_deps = [ drm_dep, cairo_dep, util_dep, udev_dep, input_dep, xkb_dep ] +fbcon_srcs = [ 'fbcon.c', 'drmtools.c', 'fbtools.c', 'gfx.c', 'vt.c', 'kbd.c' ] +fbcon_deps = [ drm_dep, cairo_dep, util_dep, udev_dep, input_dep, xkb_dep, tsm_dep ] -executable('fbcon', - sources : fbcon_srcs, - dependencies : fbcon_deps, - install : true) -install_data('fbcon.desktop', - install_dir : 'share/wayland-sessions') +if tsm_dep.found() + executable('fbcon', + sources : fbcon_srcs, + dependencies : fbcon_deps, + install : true) + install_data('fbcon.desktop', + install_dir : 'share/wayland-sessions') +endif # build kbdtest executable('kbdtest', @@ -1,500 +0,0 @@ -/* Copyright (c) 2017 Rob King - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * 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. - * * Neither the name of the copyright holder nor the - * names of contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER 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 AUTHORS, - * COPYRIGHT HOLDERS, 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. - */ -#include <limits.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include "tmt.h" - -#define BUF_MAX 100 -#define PAR_MAX 8 -#define TAB 8 -#define MAX(x, y) (((size_t)(x) > (size_t)(y)) ? (size_t)(x) : (size_t)(y)) -#define MIN(x, y) (((size_t)(x) < (size_t)(y)) ? (size_t)(x) : (size_t)(y)) -#define CLINE(vt) (vt)->screen.lines[MIN((vt)->curs.r, (vt)->screen.nline - 1)] - -#define P0(x) (vt->pars[x]) -#define P1(x) (vt->pars[x]? vt->pars[x] : 1) -#define CB(vt, m, a) ((vt)->cb? (vt)->cb(m, vt, a, (vt)->p) : (void)0) -#define INESC ((vt)->state) - -#define COMMON_VARS \ - TMTSCREEN *s = &vt->screen; \ - TMTPOINT *c = &vt->curs; \ - TMTLINE *l = CLINE(vt); \ - TMTCHAR *t = vt->tabs->chars - -#define HANDLER(name) static void name (TMT *vt) { COMMON_VARS; - -struct TMT{ - TMTPOINT curs, oldcurs; - TMTATTRS attrs, oldattrs; - - bool dirty, acs, ignored; - TMTSCREEN screen; - TMTLINE *tabs; - - TMTCALLBACK cb; - void *p; - const wchar_t *acschars; - - mbstate_t ms; - size_t nmb; - char mb[BUF_MAX + 1]; - - size_t pars[PAR_MAX]; - size_t npar; - size_t arg; - enum {S_NUL, S_ESC, S_ARG} state; -}; - -static TMTATTRS defattrs = {.fg = TMT_COLOR_DEFAULT, .bg = TMT_COLOR_DEFAULT}; -static void writecharatcurs(TMT *vt, wchar_t w); - -static wchar_t -tacs(const TMT *vt, unsigned char c) -{ - /* The terminfo alternate character set for ANSI. */ - static unsigned char map[] = {0020U, 0021U, 0030U, 0031U, 0333U, 0004U, - 0261U, 0370U, 0361U, 0260U, 0331U, 0277U, - 0332U, 0300U, 0305U, 0176U, 0304U, 0304U, - 0304U, 0137U, 0303U, 0264U, 0301U, 0302U, - 0263U, 0363U, 0362U, 0343U, 0330U, 0234U, - 0376U}; - for (size_t i = 0; i < sizeof(map); i++) if (map[i] == c) - return vt->acschars[i]; - return (wchar_t)c; -} - -static void -dirtylines(TMT *vt, size_t s, size_t e) -{ - vt->dirty = true; - for (size_t i = s; i < e; i++) - vt->screen.lines[i]->dirty = true; -} - -static void -clearline(TMT *vt, TMTLINE *l, size_t s, size_t e) -{ - vt->dirty = l->dirty = true; - for (size_t i = s; i < e && i < vt->screen.ncol; i++){ - l->chars[i].a = defattrs; - l->chars[i].c = L' '; - } -} - -static void -clearlines(TMT *vt, size_t r, size_t n) -{ - for (size_t i = r; i < r + n && i < vt->screen.nline; i++) - clearline(vt, vt->screen.lines[i], 0, vt->screen.ncol); -} - -static void -scrup(TMT *vt, size_t r, size_t n) -{ - n = MIN(n, vt->screen.nline - 1 - r); - - if (n){ - TMTLINE *buf[n]; - - memcpy(buf, vt->screen.lines + r, n * sizeof(TMTLINE *)); - memmove(vt->screen.lines + r, vt->screen.lines + r + n, - (vt->screen.nline - n - r) * sizeof(TMTLINE *)); - memcpy(vt->screen.lines + (vt->screen.nline - n), - buf, n * sizeof(TMTLINE *)); - - clearlines(vt, vt->screen.nline - n, n); - dirtylines(vt, r, vt->screen.nline); - } -} - -static void -scrdn(TMT *vt, size_t r, size_t n) -{ - n = MIN(n, vt->screen.nline - 1 - r); - - if (n){ - TMTLINE *buf[n]; - - memcpy(buf, vt->screen.lines + (vt->screen.nline - n), - n * sizeof(TMTLINE *)); - memmove(vt->screen.lines + r + n, vt->screen.lines + r, - (vt->screen.nline - n - r) * sizeof(TMTLINE *)); - memcpy(vt->screen.lines + r, buf, n * sizeof(TMTLINE *)); - - clearlines(vt, r, n); - dirtylines(vt, r, vt->screen.nline); - } -} - -HANDLER(ed) - size_t b = 0; - size_t e = s->nline; - - switch (P0(0)){ - case 0: b = c->r + 1; clearline(vt, l, c->c, vt->screen.ncol); break; - case 1: e = c->r - 1; clearline(vt, l, 0, c->c); break; - case 2: /* use defaults */ break; - default: /* do nothing */ return; - } - - clearlines(vt, b, e - b); -} - -HANDLER(ich) - size_t n = P1(0); /* XXX use MAX */ - if (n > s->ncol - c->c - 1) n = s->ncol - c->c - 1; - - memmove(l->chars + c->c + n, l->chars + c->c, - MIN(s->ncol - 1 - c->c, - (s->ncol - c->c - n - 1)) * sizeof(TMTCHAR)); - clearline(vt, l, c->c, n); -} - -HANDLER(dch) - size_t n = P1(0); /* XXX use MAX */ - if (n > s->ncol - c->c) n = s->ncol - c->c; - - memmove(l->chars + c->c, l->chars + c->c + n, - (s->ncol - c->c - n) * sizeof(TMTCHAR)); - - clearline(vt, l, s->ncol - c->c - n, s->ncol); -} - -HANDLER(el) - switch (P0(0)){ - case 0: clearline(vt, l, c->c, vt->screen.ncol); break; - case 1: clearline(vt, l, 0, MIN(c->c + 1, s->ncol - 1)); break; - case 2: clearline(vt, l, 0, vt->screen.ncol); break; - } -} - -HANDLER(sgr) - #define FGBG(c) *(P0(i) < 40? &vt->attrs.fg : &vt->attrs.bg) = c - for (size_t i = 0; i < vt->npar; i++) switch (P0(i)){ - case 0: vt->attrs = defattrs; break; - case 1: case 22: vt->attrs.bold = P0(0) < 20; break; - case 2: case 23: vt->attrs.dim = P0(0) < 20; break; - case 4: case 24: vt->attrs.underline = P0(0) < 20; break; - case 5: case 25: vt->attrs.blink = P0(0) < 20; break; - case 7: case 27: vt->attrs.reverse = P0(0) < 20; break; - case 8: case 28: vt->attrs.invisible = P0(0) < 20; break; - case 10: case 11: vt->acs = P0(0) > 10; break; - case 30: case 40: FGBG(TMT_COLOR_BLACK); break; - case 31: case 41: FGBG(TMT_COLOR_RED); break; - case 32: case 42: FGBG(TMT_COLOR_GREEN); break; - case 33: case 43: FGBG(TMT_COLOR_YELLOW); break; - case 34: case 44: FGBG(TMT_COLOR_BLUE); break; - case 35: case 45: FGBG(TMT_COLOR_MAGENTA); break; - case 36: case 46: FGBG(TMT_COLOR_CYAN); break; - case 37: case 47: FGBG(TMT_COLOR_WHITE); break; - case 39: case 49: FGBG(TMT_COLOR_DEFAULT); break; - } -} - -HANDLER(rep) - if (!c->c) return; - wchar_t r = l->chars[c->c - 1].c; - for (size_t i = 0; i < P1(0); i++) - writecharatcurs(vt, r); -} - -HANDLER(dsr) - char r[BUF_MAX + 1] = {0}; - snprintf(r, BUF_MAX, "\033[%zd;%zdR", c->r, c->c); - CB(vt, TMT_MSG_ANSWER, (const char *)r); -} - -HANDLER(resetparser) - memset(vt->pars, 0, sizeof(vt->pars)); - vt->state = vt->npar = vt->arg = vt->ignored = (bool)0; -} - -HANDLER(consumearg) - if (vt->npar < PAR_MAX) - vt->pars[vt->npar++] = vt->arg; - vt->arg = 0; -} - -HANDLER(fixcursor) - c->r = MIN(c->r, s->nline - 1); - c->c = MIN(c->c, s->ncol - 1); -} - -static bool -handlechar(TMT *vt, char i) -{ - COMMON_VARS; - - char cs[] = {i, 0}; - #define ON(S, C, A) if (vt->state == (S) && strchr(C, i)){ A; return true;} - #define DO(S, C, A) ON(S, C, consumearg(vt); if (!vt->ignored) {A;} \ - fixcursor(vt); resetparser(vt);); - - DO(S_NUL, "\x07", CB(vt, TMT_MSG_BELL, NULL)) - DO(S_NUL, "\x08", if (c->c) c->c--) - DO(S_NUL, "\x09", while (++c->c < s->ncol - 1 && t[c->c].c != L'*')) - DO(S_NUL, "\x0a", c->r < s->nline - 1? (void)c->r++ : scrup(vt, 0, 1)) - DO(S_NUL, "\x0d", c->c = 0) - ON(S_NUL, "\x1b", vt->state = S_ESC) - ON(S_ESC, "\x1b", vt->state = S_ESC) - DO(S_ESC, "H", t[c->c].c = L'*') - DO(S_ESC, "7", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs) - DO(S_ESC, "8", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs) - ON(S_ESC, "+*()", vt->ignored = true; vt->state = S_ARG) - DO(S_ESC, "c", tmt_reset(vt)) - ON(S_ESC, "[", vt->state = S_ARG) - ON(S_ARG, "\x1b", vt->state = S_ESC) - ON(S_ARG, ";", consumearg(vt)) - ON(S_ARG, "?", (void)0) - ON(S_ARG, "0123456789", vt->arg = vt->arg * 10 + atoi(cs)) - DO(S_ARG, "A", c->r = MAX(c->r - P1(0), 0)) - DO(S_ARG, "B", c->r = MIN(c->r + P1(0), s->nline - 1)) - DO(S_ARG, "C", c->c = MIN(c->c + P1(0), s->ncol - 1)) - DO(S_ARG, "D", c->c = MIN(c->c - P1(0), c->c)) - DO(S_ARG, "E", c->c = 0; c->r = MIN(c->r + P1(0), s->nline - 1)) - DO(S_ARG, "F", c->c = 0; c->r = MAX(c->r - P1(0), 0)) - DO(S_ARG, "G", c->c = MIN(P1(0) - 1, s->ncol - 1)) - DO(S_ARG, "d", c->r = MIN(P1(0) - 1, s->nline - 1)) - DO(S_ARG, "Hf", c->r = P1(0) - 1; c->c = P1(1) - 1) - DO(S_ARG, "I", while (++c->c < s->ncol - 1 && t[c->c].c != L'*')) - DO(S_ARG, "J", ed(vt)) - DO(S_ARG, "K", el(vt)) - DO(S_ARG, "L", scrdn(vt, c->r, P1(0))) - DO(S_ARG, "M", scrup(vt, c->r, P1(0))) - DO(S_ARG, "P", dch(vt)) - DO(S_ARG, "S", scrup(vt, 0, P1(0))) - DO(S_ARG, "T", scrdn(vt, 0, P1(0))) - DO(S_ARG, "X", clearline(vt, l, c->c, P1(0))) - DO(S_ARG, "Z", while (c->c && t[--c->c].c != L'*')) - DO(S_ARG, "b", rep(vt)); - DO(S_ARG, "c", CB(vt, TMT_MSG_ANSWER, "\033[?6c")) - DO(S_ARG, "g", if (P0(0) == 3) clearline(vt, vt->tabs, 0, s->ncol)) - DO(S_ARG, "m", sgr(vt)) - DO(S_ARG, "n", if (P0(0) == 6) dsr(vt)) - DO(S_ARG, "h", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "t")) - DO(S_ARG, "i", (void)0) - DO(S_ARG, "l", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "f")) - DO(S_ARG, "s", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs) - DO(S_ARG, "u", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs) - DO(S_ARG, "@", ich(vt)) - - return resetparser(vt), false; -} - -static void -notify(TMT *vt, bool update, bool moved) -{ - if (update) CB(vt, TMT_MSG_UPDATE, &vt->screen); - if (moved) CB(vt, TMT_MSG_MOVED, &vt->curs); -} - -static TMTLINE * -allocline(TMT *vt, TMTLINE *o, size_t n, size_t pc) -{ - TMTLINE *l = realloc(o, sizeof(TMTLINE) + n * sizeof(TMTCHAR)); - if (!l) return NULL; - - clearline(vt, l, pc, n); - return l; -} - -static void -freelines(TMT *vt, size_t s, size_t n, bool screen) -{ - for (size_t i = s; vt->screen.lines && i < s + n; i++){ - free(vt->screen.lines[i]); - vt->screen.lines[i] = NULL; - } - if (screen) free(vt->screen.lines); -} - -TMT * -tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p, - const wchar_t *acs) -{ - TMT *vt = calloc(1, sizeof(TMT)); - if (!nline || !ncol || !vt) return free(vt), NULL; - - /* ASCII-safe defaults for box-drawing characters. */ - vt->acschars = acs? acs : L"><^v#+:o##+++++~---_++++|<>*!fo"; - vt->cb = cb; - vt->p = p; - - if (!tmt_resize(vt, nline, ncol)) return tmt_close(vt), NULL; - return vt; -} - -void -tmt_close(TMT *vt) -{ - free(vt->tabs); - freelines(vt, 0, vt->screen.nline, true); - free(vt); -} - -bool -tmt_resize(TMT *vt, size_t nline, size_t ncol) -{ - if (nline < 2 || ncol < 2) return false; - if (nline < vt->screen.nline) - freelines(vt, nline, vt->screen.nline - nline, false); - - TMTLINE **l = realloc(vt->screen.lines, nline * sizeof(TMTLINE *)); - if (!l) return false; - - size_t pc = vt->screen.ncol; - vt->screen.lines = l; - vt->screen.ncol = ncol; - for (size_t i = 0; i < nline; i++){ - TMTLINE *nl = NULL; - if (i >= vt->screen.nline) - nl = vt->screen.lines[i] = allocline(vt, NULL, ncol, 0); - else - nl = allocline(vt, vt->screen.lines[i], ncol, pc); - - if (!nl) return false; - vt->screen.lines[i] = nl; - } - vt->screen.nline = nline; - - vt->tabs = allocline(vt, vt->tabs, ncol, 0); - if (!vt->tabs) return free(l), false; - vt->tabs->chars[0].c = vt->tabs->chars[ncol - 1].c = L'*'; - for (size_t i = 0; i < ncol; i++) if (i % TAB == 0) - vt->tabs->chars[i].c = L'*'; - - fixcursor(vt); - dirtylines(vt, 0, nline); - notify(vt, true, true); - return true; -} - -static void -writecharatcurs(TMT *vt, wchar_t w) -{ - COMMON_VARS; - - #ifdef TMT_HAS_WCWIDTH - extern int wcwidth(wchar_t c); - if (wcwidth(w) > 1) w = TMT_INVALID_CHAR; - if (wcwidth(w) < 0) return; - #endif - - CLINE(vt)->chars[vt->curs.c].c = w; - CLINE(vt)->chars[vt->curs.c].a = vt->attrs; - CLINE(vt)->dirty = vt->dirty = true; - - if (c->c < s->ncol - 1) - c->c++; - else{ - c->c = 0; - c->r++; - } - - if (c->r >= s->nline){ - c->r = s->nline - 1; - scrup(vt, 0, 1); - } -} - -static inline size_t -testmbchar(TMT *vt) -{ - mbstate_t ts = vt->ms; - return vt->nmb? mbrtowc(NULL, vt->mb, vt->nmb, &ts) : (size_t)-2; -} - -static inline wchar_t -getmbchar(TMT *vt) -{ - wchar_t c = 0; - size_t n = mbrtowc(&c, vt->mb, vt->nmb, &vt->ms); - vt->nmb = 0; - return (n == (size_t)-1 || n == (size_t)-2)? TMT_INVALID_CHAR : c; -} - -void -tmt_write(TMT *vt, const char *s, size_t n) -{ - TMTPOINT oc = vt->curs; - n = n? n : strlen(s); - - for (size_t p = 0; p < n; p++){ - if (handlechar(vt, s[p])) - continue; - else if (vt->acs) - writecharatcurs(vt, tacs(vt, (unsigned char)s[p])); - else if (vt->nmb >= BUF_MAX) - writecharatcurs(vt, getmbchar(vt)); - else{ - switch (testmbchar(vt)){ - case (size_t)-1: writecharatcurs(vt, getmbchar(vt)); break; - case (size_t)-2: vt->mb[vt->nmb++] = s[p]; break; - } - - if (testmbchar(vt) <= MB_LEN_MAX) - writecharatcurs(vt, getmbchar(vt)); - } - } - - notify(vt, vt->dirty, memcmp(&oc, &vt->curs, sizeof(oc)) != 0); -} - -const TMTSCREEN * -tmt_screen(const TMT *vt) -{ - return &vt->screen; -} - -const TMTPOINT * -tmt_cursor(const TMT *vt) -{ - return &vt->curs; -} - -void -tmt_clean(TMT *vt) -{ - for (size_t i = 0; i < vt->screen.nline; i++) - vt->dirty = vt->screen.lines[i]->dirty = false; -} - -void -tmt_reset(TMT *vt) -{ - vt->curs.r = vt->curs.c = vt->oldcurs.r = vt->oldcurs.c = vt->acs = (bool)0; - resetparser(vt); - vt->attrs = vt->oldattrs = defattrs; - memset(&vt->ms, 0, sizeof(vt->ms)); - clearlines(vt, 0, vt->screen.nline); - CB(vt, TMT_MSG_CURSOR, "t"); - notify(vt, true, true); -} @@ -1,140 +0,0 @@ -/* Copyright (c) 2017 Rob King - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * 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. - * * Neither the name of the copyright holder nor the - * names of contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER 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 AUTHORS, - * COPYRIGHT HOLDERS, 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. - */ - -#ifndef TMT_H -#define TMT_H - -#include <stdbool.h> -#include <stddef.h> -#include <wchar.h> - -/**** INVALID WIDE CHARACTER */ -#ifndef TMT_INVALID_CHAR -#define TMT_INVALID_CHAR ((wchar_t)0xfffd) -#endif - -/**** INPUT SEQUENCES */ -#define TMT_KEY_UP "\033[A" -#define TMT_KEY_DOWN "\033[B" -#define TMT_KEY_RIGHT "\033[C" -#define TMT_KEY_LEFT "\033[D" -#define TMT_KEY_HOME "\033[H" -#define TMT_KEY_END "\033[Y" -#define TMT_KEY_INSERT "\033[L" -#define TMT_KEY_BACKSPACE "\x08" -#define TMT_KEY_ESCAPE "\x1b" -#define TMT_KEY_BACK_TAB "\033[Z" -#define TMT_KEY_PAGE_UP "\033[V" -#define TMT_KEY_PAGE_DOWN "\033[U" -#define TMT_KEY_F1 "\033OP" -#define TMT_KEY_F2 "\033OQ" -#define TMT_KEY_F3 "\033OR" -#define TMT_KEY_F4 "\033OS" -#define TMT_KEY_F5 "\033OT" -#define TMT_KEY_F6 "\033OU" -#define TMT_KEY_F7 "\033OV" -#define TMT_KEY_F8 "\033OW" -#define TMT_KEY_F9 "\033OX" -#define TMT_KEY_F10 "\033OY" - -/**** BASIC DATA STRUCTURES */ -typedef struct TMT TMT; - -typedef enum{ - TMT_COLOR_DEFAULT = -1, - TMT_COLOR_BLACK = 1, - TMT_COLOR_RED, - TMT_COLOR_GREEN, - TMT_COLOR_YELLOW, - TMT_COLOR_BLUE, - TMT_COLOR_MAGENTA, - TMT_COLOR_CYAN, - TMT_COLOR_WHITE, - TMT_COLOR_MAX -} tmt_color_t; - -typedef struct TMTATTRS TMTATTRS; -struct TMTATTRS{ - bool bold; - bool dim; - bool underline; - bool blink; - bool reverse; - bool invisible; - tmt_color_t fg; - tmt_color_t bg; -}; - -typedef struct TMTCHAR TMTCHAR; -struct TMTCHAR{ - wchar_t c; - TMTATTRS a; -}; - -typedef struct TMTPOINT TMTPOINT; -struct TMTPOINT{ - size_t r; - size_t c; -}; - -typedef struct TMTLINE TMTLINE; -struct TMTLINE{ - bool dirty; - TMTCHAR chars[]; -}; - -typedef struct TMTSCREEN TMTSCREEN; -struct TMTSCREEN{ - size_t nline; - size_t ncol; - - TMTLINE **lines; -}; - -/**** CALLBACK SUPPORT */ -typedef enum{ - TMT_MSG_MOVED, - TMT_MSG_UPDATE, - TMT_MSG_ANSWER, - TMT_MSG_BELL, - TMT_MSG_CURSOR -} tmt_msg_t; - -typedef void (*TMTCALLBACK)(tmt_msg_t m, struct TMT *v, const void *r, void *p); - -/**** PUBLIC FUNCTIONS */ -TMT *tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p, - const wchar_t *acs); -void tmt_close(TMT *vt); -bool tmt_resize(TMT *vt, size_t nline, size_t ncol); -void tmt_write(TMT *vt, const char *s, size_t n); -const TMTSCREEN *tmt_screen(const TMT *vt); -const TMTPOINT *tmt_cursor(const TMT *vt); -void tmt_clean(TMT *vt); -void tmt_reset(TMT *vt); - -#endif |