You are not logged in or registered. Please login or register to use the full functionality of this board...
SIGN IN Join Our Community For FREE


VT100 terminal emulator
09-06-2014, 09:56 AM (This post was last modified: 09-06-2014 12:44 PM by flukiluke.)
Post: #1
 (Print Post)
VT100 terminal emulator
(Linux only - might work on Mac OSX)

It's a terminal emulator: in particular, a VT100 emulator. Works just like any other, except that not all features have been implemented yet (tab stops and reverse scrolling are things of note still missing). However, it is complete enough to run a full-screen editor like nano or vi.

There's a good chance that QB64 will refuse to compile this for you, on account of the DECLARE STATIC LIBRARY line. QB64's library search routine is broken, so we need to give it the path to the util library. To find it on your system, run the command "find /usr/lib -name libutil.a", and you will get the path to libutil.a - on my system that would be /usr/lib/i386-linux-gnu/libutil.a. Then chop off the ".a" and the "lib" from the file name, to get "/usr/lib/i386-linux-gnu/util" for me, and insert that.

A small C++ header is required for establishing the I/O interface at a low level. Save this as proc_mgmt.h
Code Snippet: [Select]
#include <unistd.h>
#include <fcntl.h>
#include <pty.h>
#include <errno.h>

int pty_init(char *shell) {
 int mfd;
 pid_t child;
 struct winsize win;
 win.ws_col = 80;
 win.ws_row = 24;
 win.ws_xpixel = 480;
 win.ws_ypixel = 192;
 child = forkpty(&mfd, NULL, NULL, &win);
 if (child == -1) return -1;
 fcntl(mfd, F_SETFL, O_NONBLOCK);
 if (!child) { //We are the child
   execl(shell, shell, NULL);
   return -2; //We shouldn't get here!
 }
 return mfd;
}

int pty_getchar(int fd) {
 char c;
 int result = read(fd, &c, 1);
 if (result == -1) { //error
   if (errno == EAGAIN || errno == EWOULDBLOCK) {
     return -2;  //No data available
   }
   else {
     return -1; //Serious error
   }
 }
 return c;
}

void pty_putchar(int fd, char c) {
 write(fd, &c, 1);
}

And here is the main program
Code Snippet: [Select]
INIT$ = "/bin/bash"

AUTOTYPE$ = "export TERM=vt100" + CHR$(13) + "export DISPLAY=" + CHR$(13) + CHR$(12)
DIM SHARED SETUPKEY$
SETUPKEY$ = CHR$(0) + CHR$(147)

DEFLNG A-Z
CONST FALSE = 0, TRUE = NOT FALSE
DECLARE STATIC LIBRARY "proc_mgmt", "/usr/lib/i386-linux-gnu/util"
    FUNCTION pty_init (shell$)
    FUNCTION pty_getchar& (BYVAL fd)
    SUB pty_putchar (BYVAL fd, BYVAL char~%%)
END DECLARE
TYPE term_settings_t
'vt100 settings
    linefeed AS INTEGER 'LNM
    cursor AS INTEGER 'DECCKM
    escmode AS INTEGER 'DECANM
    column AS INTEGER 'DECCOLM
    scrolling AS INTEGER 'DECSCLM
    scrn AS INTEGER 'DECSCNM
    origin AS INTEGER 'DECOM
    autowrap AS INTEGER 'DECAWM
    autorepeat AS INTEGER 'DECARM
    interlace AS INTEGER 'DECINLM
    answerback AS STRING * 20
    curshape AS INTEGER

    'formatting settings
    bold AS INTEGER
    uscore AS INTEGER
    blink AS INTEGER
    reverse AS INTEGER 'Not DECSCNM!
    tmargin AS INTEGER
    bmargin AS INTEGER

    'debugging settings
    escapes AS INTEGER

    'internal state
    frozen AS INTEGER 'XON/XOFF: 0=flowing, 1=user ordered XOFF, 2=automatic XOFF
END TYPE
DIM SHARED settings AS term_settings_t
reset_term

fd = pty_init(INIT$)
IF fd = -1 THEN PRINT "PTY ERROR": END
IF fd = -2 THEN PRINT "EXEC ERROR": END

send_string fd, AUTOTYPE$
DO
    cnum = pty_getchar(fd)
    SELECT CASE cnum
        CASE -1 'Error
            read_error
        CASE 0, -2 '0=NUL, -2=no data read
            REM Do nothing
        CASE 7 'bell
            BEEP
        CASE 8 'backspace
            IF POS(0) = 1 THEN 'At start of line
                'if csrlin=1, we're already in the top left, so do nothing
                IF CSRLIN > 1 THEN LOCATE CSRLIN - 1, _WIDTH
            ELSE
                LOCATE CSRLIN, POS(0) - 1
            END IF
            'case 9 horizonal tab handled by PRINT
        CASE 10, 11, 12 'Line feed
            p = POS(0)
            PRINT
            IF NOT settings.linefeed THEN LOCATE CSRLIN, p
        CASE 13 'Carrige return
            LOCATE CSRLIN, 1
        CASE 14 'Switch to G1
        CASE 15 'Switch to G0
        CASE 27
            e$ = ""
            IF settings.escapes THEN
                DO
                    c$ = CHR$(pty_getchar(fd))
                    e$ = e$ + c$
                    IF INSTR("0123456789;[?", c$) = 0 THEN EXIT DO
                LOOP
                _TITLE e$
                SLEEP
            ELSE
                process_escape fd
            END IF

        CASE 1 TO 31, 127 'other controls and DEL
            REM Do nothing
        CASE ELSE
            PRINT CHR$(cnum);
    END SELECT

    a$ = INKEY$
    SELECT CASE a$
        CASE ""
            REM Do nothing
        CASE SETUPKEY$
            setup
        CASE CHR$(13)
            pty_putchar fd, 13
            IF settings.linefeed THEN pty_putchar fd, 10
        CASE CHR$(17) 'XON
            IF settings.frozen = 1 THEN
                pty_putchar fd, 17
                settings.frozen = 0
            END IF
        CASE CHR$(19) 'XOFF
            IF settings.frozen = 0 THEN
                pty_putchar fd, 19
                settings.frozen = 1
            END IF
            'arrow keys
        CASE CHR$(0) + "H"
            IF settings.cursor THEN send_string fd, CHR$(27) + "OA" ELSE send_string fd, CHR$(27) + "[A"
        CASE CHR$(0) + "P"
            IF settings.cursor THEN send_string fd, CHR$(27) + "OB" ELSE send_string fd, CHR$(27) + "[B"
        CASE CHR$(0) + "M"
            IF settings.cursor THEN send_string fd, CHR$(27) + "OC" ELSE send_string fd, CHR$(27) + "[C"
        CASE CHR$(0) + "K"
            IF settings.cursor THEN send_string fd, CHR$(27) + "OD" ELSE send_string fd, CHR$(27) + "[D"
        CASE CHR$(0) + "S"
            pty_putchar fd, 127
            'These keys aren't on a real VT100, but we send the equivalent escape sequences for them
        CASE CHR$(0) + "G" 'Home
            send_string fd, CHR$(27) + "[H"
        CASE CHR$(0) + "O" 'End
            send_string fd, CHR$(27) + "[F"
        CASE ELSE
            pty_putchar fd, ASC(a$)
    END SELECT
LOOP

SUB process_escape (fd)
DIM attribs(0) AS STRING
c$ = getchar_block$(fd)
SELECT CASE c$ 'getchar_block$(fd)
    CASE "["
        DO
            u = UBOUND(attribs)
            c$ = getchar_block$(fd)
            SELECT CASE c$
                CASE CHR$(24), CHR$(26)
                    GOTO abort
                CASE "0" TO "9", "?"
                    attribs(u) = attribs(u) + c$
                CASE ";"
                    REDIM _PRESERVE attribs(0 TO u + 1) AS STRING
                CASE "A"
                    IF VAL(attribs(0)) = 0 THEN num = 1 ELSE num = VAL(attribs(0))
                    IF num >= CSRLIN THEN LOCATE 1 ELSE LOCATE CSRLIN - num
                    EXIT SUB
                CASE "B"
                    IF VAL(attribs(0)) = 0 THEN num = 1 ELSE num = VAL(attribs(0))
                    IF num > 24 - CSRLIN THEN LOCATE 24 ELSE LOCATE CSRLIN + num
                    EXIT SUB
                CASE "C"
                    IF VAL(attribs(0)) = 0 THEN num = 1 ELSE num = VAL(attribs(0))
                    IF num > _WIDTH - POS(0) THEN LOCATE , _WIDTH ELSE LOCATE , POS(0) + num
                    EXIT SUB
                CASE "D"
                    IF VAL(attribs(0)) = 0 THEN num = 1 ELSE num = VAL(attribs(0))
                    IF num >= POS(0) THEN LOCATE , 1 ELSE LOCATE , POS(0) - num
                    EXIT SUB
                CASE "H", "f"
                    row = VAL(attribs(0))
                    IF row = 0 THEN row = 1
                    col = 1
                    IF u > 0 THEN
                        col = VAL(attribs(1))
                        IF col = 0 THEN col = 1
                    END IF
                    IF col > _WIDTH THEN col = _WIDTH
                    IF row > 24 THEN row = 24
                    LOCATE row, col
                    EXIT SUB
                CASE "n"
                    IF VAL(attribs(0)) = 5 THEN
                        send_string fd, CHR$(27) + "[0n"
                    ELSEIF VAL(attribs(0)) = 6 THEN
                        send_string fd, CHR$(27) + "[" + RTRIM$(LTRIM$(STR$(CSRLIN))) + ";" + RTRIM$(LTRIM$(STR$(POS(0)))) + "R"
                    END IF
                CASE "m"
                    FOR i = 0 TO UBOUND(attribs)
                        SELECT CASE VAL(attribs(i))
                            CASE 0
                                settings.bold = FALSE
                                settings.uscore = FALSE
                                settings.blink = FALSE
                                settings.reverse = FALSE
                                COLOR 7, 0
                            CASE 1
                                settings.bold = TRUE
                                IF NOT settings.reverse THEN COLOR 15, 0 ELSE COLOR 0, 15
                            CASE 4
                                settings.uscore = TRUE
                            CASE 5
                                settings.blink = TRUE
                            CASE 7
                                settings.reverse = TRUE
                                IF NOT settings.bold THEN COLOR 0, 7 ELSE COLOR 0, 15
                        END SELECT
                    NEXT i
                    EXIT SUB
                CASE "K"
                    oldrow = CSRLIN
                    oldcol = POS(0)
                    SELECT CASE VAL(attribs(0))
                        CASE 0
                            FOR col = POS(0) TO _WIDTH
                                PRINT " ";
                            NEXT col
                        CASE 1
                            FOR col = 1 TO POS(0)
                                PRINT " ";
                            NEXT col
                        CASE 2
                            FOR col = 1 TO _WIDTH
                                PRINT " ";
                            NEXT col
                    END SELECT
                    LOCATE oldrow, oldcol
                    EXIT SUB
                CASE "J"
                    oldrow = CSRLIN
                    oldcol = POS(0)

                    SELECT CASE VAL(attribs(0))
                        CASE 0
                            FOR col = POS(0) TO _WIDTH
                                PRINT " ";
                            NEXT col
                            FOR row = CSRLIN TO 24
                                FOR col = 1 TO _WIDTH
                                    PRINT " ";
                                NEXT col
                            NEXT row
                        CASE 1
                            FOR row = 1 TO CSRLIN
                                FOR col = 1 TO POS(0)
                                    PRINT " ";
                                NEXT col
                            NEXT row
                        CASE 2
                            CLS
                    END SELECT
                    LOCATE oldrow, oldcol
                    EXIT SUB
                CASE "q"
                    REM LEDs not supported
                    EXIT SUB
                CASE "r"
                    EXIT SUB
                CASE "g"
                    EXIT SUB
                CASE "c"
                    send_string fd, CHR$(27) + "[?1;2c"
                CASE "h", "l"
                    IF c$ = "h" THEN newsetting = TRUE ELSE newsetting = FALSE
                    FOR i = 0 TO UBOUND(attribs)
                        IF VAL(attribs(i)) = 20 THEN
                            settings.linefeed = newsetting
                        ELSEIF LEFT$(attribs(i), 1) = "?" THEN
                            v$ = MID$(attribs(i), 2)
                            SELECT CASE VAL(v$)
                                CASE 0
                                    REM Ignore
                                CASE 1
                                    settings.cursor = newsetting
                                CASE 2
                                    settings.escmode = newsetting
                                CASE 3
                                    settings.column = newsetting
                                    IF settings.column THEN WIDTH 132, 25 ELSE WIDTH 80, 25
                                CASE 4
                                    settings.scrolling = newsetting
                                CASE 5
                                    settings.scrn = newsetting
                                    IF settings.scrn THEN COLOR 0, 7 ELSE COLOR 7, 0
                                CASE 6
                                    settings.origin = newsetting
                                CASE 7
                                    settings.autowrap = newsetting
                                CASE 8
                                    settings.autorepeat = newsetting
                                CASE 9
                                    settings.interlace = newsetting
                            END SELECT
                        END IF
                    NEXT i
                    EXIT SUB
            END SELECT
        LOOP
    CASE "#"
        SELECT CASE getchar_block$(fd)
            CASE "3" 'double-height top half
            CASE "4" 'double-height bottom half
            CASE "5" 'single-width single-height
            CASE "6" 'double-width single-height
            CASE "8"
                PRINT STRING$(24 * _WIDTH, "E");
            CASE ELSE
                GOTO abort
        END SELECT
    CASE "=", ">"
        REM Auxiliary keypad related  - ignored
    CASE "8"
    CASE "7"
    CASE "H"
    CASE "D"
        p = POS(0)
        IF CSRLIN = 24 THEN
            LOCATE 24, 1
            PRINT
            LOCATE 24, p
        ELSE
            LOCATE CSRLIN + 1
        END IF
    CASE "E"
    CASE "M"
    CASE "c"
        reset_term
    CASE "("
    CASE ")"
    CASE ELSE
        GOTO abort
END SELECT
EXIT SUB

abort:
PRINT CHR$(176)
END SUB

SUB setup
DIM screen_data(1 TO 24, 1 TO _WIDTH) AS _UNSIGNED _BYTE
FOR row = 1 TO 24
    FOR col = 1 TO _WIDTH
        screen_data(row, col) = SCREEN(row, col)
    NEXT col
NEXT row
oldcsrlin = CSRLIN
oldpos = POS(0)
menu_show:
CLS
PRINT SPACE$(_WIDTH / 2 - 34 / 2) + "QB64 VT100 Emulator - version 0.02"
PRINT " 1) ANSI/VT52: ";
IF settings.escmode THEN PRINT "ANSI" ELSE PRINT "VT52"
PRINT " 2) Answerback: ";
PRINT RTRIM$(settings.answerback)
PRINT " 3) Auto repeat: ";
IF settings.autorepeat THEN PRINT "Yes" ELSE PRINT "No"
PRINT " 4) Characters/line: ";
IF settings.column THEN PRINT "132" ELSE PRINT "80"
PRINT " 5) Cursor shape: ";
IF settings.curshape THEN PRINT CHR$(219) ELSE PRINT "_"
PRINT " 6) Linefeed: ";
IF settings.linefeed THEN PRINT "Next line and start of line" ELSE PRINT "Next line only"
PRINT " 7) Screen background: ";
IF settings.scrn THEN PRINT "Black on white" ELSE PRINT "White on black"
PRINT " 8) Scroll: ";
IF settings.scrolling THEN PRINT "Smooth" ELSE PRINT "Jump"
PRINT " 9) Set tab stops"
PRINT "10) Wraparound: ";
IF settings.autowrap THEN PRINT "Automatic" ELSE PRINT "Manual"
PRINT "11) Escape sleep: ";
IF settings.escapes THEN PRINT "Yes" ELSE PRINT "No"
INPUT "Menu choice (empty or 0 to exit): ", choice
SELECT CASE choice
    CASE 1
        settings.escmode = NOT settings.escmode
    CASE 2
        DO
            INPUT "Answerback message (max 20 chars): ", ansmsg$
            IF LEN(ansmsg$) > 20 THEN
                LOCATE CSRLIN - 1, 1
                PRINT SPACE$(35 + LEN(ansmsg$))
                LOCATE CSRLIN - 1, 1
            ELSE
                EXIT DO
            END IF
        LOOP
        settings.answerback = ansmsg$
    CASE 3
        settings.autorepeat = NOT settings.autorepeat
    CASE 4
        settings.column = NOT settings.column
        IF settings.column THEN WIDTH 132, 25 ELSE WIDTH 80, 25
        dontrestore = TRUE
    CASE 5
        settings.curshape = NOT settings.curshape
        IF settings.curshape THEN LOCATE , , , 0, 8 ELSE LOCATE , , , 8, 8
    CASE 6
        settings.linefeed = NOT settings.linefeed
    CASE 7
        settings.scrn = NOT settings.scrn
        IF settings.scrn THEN COLOR 0, 7 ELSE COLOR 7, 0
    CASE 8
        settings.scrolling = NOT settings.scrolling
    CASE 9
        'Tab stops not implemented
    CASE 10
        settings.autowrap = NOT settings.autowrap
    CASE 11
        settings.escapes = NOT settings.escapes
    CASE 0
        IF dontrestore THEN CLS: EXIT SUB
        FOR row = 1 TO 24
            FOR col = 1 TO 80
                PRINT CHR$(screen_data(row, col));
            NEXT col
        NEXT row
        LOCATE oldcsrlin, oldpos
        EXIT SUB
END SELECT
GOTO menu_show
END SUB

SUB reset_term
settings.linefeed = FALSE
settings.cursor = FALSE
settings.escmode = TRUE
settings.column = FALSE
settings.scrolling = FALSE
settings.scrn = FALSE
settings.origin = FALSE
settings.autowrap = TRUE
settings.autorepeat = TRUE
settings.interlace = TRUE
settings.answerback = "QB64 VT100"
settings.curshape = FALSE

'formatting settings
settings.bold = FALSE
settings.uscore = FALSE
settings.blink = FALSE
settings.reverse = FALSE
settings.tmargin = 1
settings.bmargin = 24

'debugging settings
settings.escapes = FALSE

'internal state
settings.frozen = 0

COLOR 7, 0
LOCATE 1, 1, 1, 8, 8
CLS
END SUB

FUNCTION getchar_block$ (fd)
DO
    c = pty_getchar(fd)
LOOP WHILE c = -2
IF c = -1 THEN read_error
getchar_block$ = CHR$(c)
END FUNCTION

SUB send_string (fd, s$)
FOR i = 1 TO LEN(s$)
    pty_putchar fd, ASC(MID$(s$, i, 1))
NEXT i
END SUB

SUB read_error
PRINT "READ ERROR"
END
END SUB

To access various options, press Control+Delete. An explanation of the options:
ANSI/VT52 - Usually, the VT100 would follow the ANSI standard for escape codes. However, it could also pretend to be a VT52, which was an older terminal. This option is current unimplemented.

Answerback: This string is used to identify the terminal to the host. It is settable, but pretty useless really.

Auto repeat: Whether or not a key should repeat when held down. Due to limitations in the current input system (I'm using INKEY$ and too lazy to change it to _KEYDOWN), this is effectively always set to Yes.

Characters/Line: The VT100 could switch between 80 characters per line an 132 characters per line. The original did this by squashing all the characters together; I do it by making the window wider. If you're running a shell, you'll probably want to do "export COLUMNS=132" to let programs know the window is bigger, but even then it might not have any effect.

Cursor shape: Not much to say here. Do you want a underbar cursor or a block cursor? Purely aesthetic.

Linefeed: "Next line only" - the Line Feed character will move down one row only, and the Enter key will only send a Carriage Return character. "Next line and start of line" will cause the Line Feed character to move down and to the left, and the Enter key to send Carriage return and Line Feed.

Screen background: Again, just an aesthetic thing.

Scroll: "Jump" is just normal scroll, "smooth" will limit the scroll rate to ≈6 lines/second. Unimplemented.

Set tab stops: Unimplemented

Wraparound: Set it to manual and character printed after end of line is reached will just overwrite the last character on the line. Set to automatic for the usual move to next line behaviour.

Escape sleep: Debugging option. Leave set to "No".
Find all posts by this user
Like Post
09-20-2014, 09:18 PM
Post: #2
 (Print Post)
RE: VT100 terminal emulator
Luke,

As you already know, I don't use Linux, so I do not not know what VT100 Terminal is, nor can I run your example.

But, I would however like to thank you for showing us a way of creating external functions in a C++ *.h file that can be used in QB64, that we do not have to incorporate into the source code of the language just to use. This is a wonderful addition to extending QB64.

thank you so much for sharing this information, and this project.


Walter Whitman
The Joyful Programmer

My goal is to bring joy, excitement, fun and education to all computer programming hobbyists, tinkerers, and amateurs. I also enjoy helping and working with those who aspire at becoming masters of their craft.
Find all posts by this user
Like Post



Forum Jump:


User(s) browsing this thread: 1 Guest(s)




QB64 Member Project - Red Scrolling LED Sign
QB64 Member Project - Overboard
QB64 Member Project - Pivot version two
QB64 Member Project - STxAxTIC 3D World
QB64 Member Project - Spiro Roses
QB64 Member Project - Amazon
QB64 Member Project - Kings Vallery version two
QB64 Member Project - Basic Dithering
QB64 Member Project - RGB Color Wheel
QB64 Member Project - Martin Fractals version two
QB64 Member Project - Quarto
QB64 Member Project - Kobolts Monopoly
QB64 Member Project - Isolation
QB64 Member Project - Splatter
QB64 Member Project - Spinning Color Wheel
QB64 Member Project - Touche
QB64 Member Project - Bowditch curve
QB64 Member Project - Score 4
QB64 Member Project - Connect Four
QB64 Member Project - Martin Fractals version one
QB64 Member Project - Line Thickness
QB64 Member Project - Swirl
QB64 Member Project - Blokus
QB64 Member Project - Othello
QB64 Member Project - Martin Fractals version three
QB64 Member Project - Full Color LED Sign
QB64 Member Project - 9 Board
QB64 Member Project - Qubic
QB64 Member Project - Inside Moves
QB64 Member Project - ARB Checkers
QB64 Member Project - Rubix's Magic
QB64 Member Project - Sabotage
QB64 Member Project - Rotating Background
QB64 Member Project - Pivet version one
QB64 Member Project - OpenGL Triangles
QB64 Member Project - Point Blank
QB64 Member Project - Input
QB64 Member Project - Color Rotating Text
QB64 Member Project - Dreamy Clock
QB64 Member Project - MAPTRIANGLE
QB64 Member Project - Exit
QB64 Member Project - Kings Court
QB64 Member Project - Foursight
QB64 Member Project - Algeria Weather
QB64 Member Project - Kings Valley verion one
QB64 Member Project - Domain
QB64 Member Project - Color Triangles
QB64 Member Project - Martin Fractals version four
QB64 Member Project - Dakapo