all repos — xyw @ 280D

A minimal virtual machine and assembler for terminals.

devices/terminal.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
#include <stdio.h>
#include <string.h>
#include "terminal.h"

#ifndef _WIN32
#include <termios.h>
#include <unistd.h>

static struct termios terminal_orig_termios;
static int terminal_raw_mode_active = 0;
#endif

/* terminal device */
#define TERMINAL_INPUT 0x0
#define TERMINAL_OUTPUT 0x1
#define TERMINAL_ON_KEYPRESS 0x2
#define TERMINAL_ON_ARGUMENT 0x4

#define POLL_INTERVAL_MS 100

//// Platform-specific non-blocking key input

#ifdef _WIN32
#include <conio.h>
#include <windows.h>
static int getch_timeout(int timeout_ms)
{
    HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
    if (WaitForSingleObject(h, timeout_ms) == WAIT_OBJECT_0 && _kbhit())
        return _getch();
    return 0;
}
#else
static int getch_timeout(int timeout_ms)
{
    (void)timeout_ms; // bounded by VMIN=0/VTIME in terminal_init()
    int c = getchar();
    return (c == EOF) ? 0 : c;
}
#endif

//// Argument feeding state

static int arg_index = 1;
static int arg_char_pos = 0;
static int exec_args_pos = 0;

//// Generic save/restore

void terminal_save(xyw_byte *data, xyw_byte *state)
{
    (void)data;
    memcpy(&state[0], &arg_index, sizeof(arg_index));
    memcpy(&state[4], &arg_char_pos, sizeof(arg_char_pos));
    memcpy(&state[8], &exec_args_pos, sizeof(exec_args_pos));
}

void terminal_restore(xyw_byte *data, const xyw_byte *state)
{
    (void)data;
    memcpy(&arg_index, &state[0], sizeof(arg_index));
    memcpy(&arg_char_pos, &state[4], sizeof(arg_char_pos));
    memcpy(&exec_args_pos, &state[8], sizeof(exec_args_pos));
}

//// Poll: argument feeding and keypress handling

int terminal_poll(xyw_byte *data)
{
    // Feed command line arguments
    xyw_word on_arg_handler = XYW_PEEKW(&data[TERMINAL_ON_ARGUMENT]);
    if (!xyw_argv_processed && on_arg_handler)
    {
        if (xyw_exec_args_active)
        {
            char c = xyw_exec_args[exec_args_pos];
            if (c == ' ')
            {
                data[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR;
                exec_args_pos++;
            }
            else if (c != '\0')
            {
                data[TERMINAL_INPUT] = (xyw_byte)c;
                exec_args_pos++;
            }
            else
            {
                data[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS;
                xyw_argv_processed = 1;
                xyw_exec_args_active = 0;
            }
        }
        else if (arg_index < xyw_argc)
        {
            char c = xyw_argv[arg_index][arg_char_pos];
            if (c != '\0')
            {
                data[TERMINAL_INPUT] = (xyw_byte)c;
                arg_char_pos++;
            }
            else if (arg_index < xyw_argc - 1)
            {
                data[TERMINAL_INPUT] = XYW_ARGUMENT_SEPARATOR;
                arg_index++;
                arg_char_pos = 0;
            }
            else
            {
                data[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS;
                xyw_argv_processed = 1;
            }
        }
        else
        {
            data[TERMINAL_INPUT] = XYW_END_OF_ARGUMENTS;
            xyw_argv_processed = 1;
        }
        xyw_vm_set_running(1);
        xyw_vm_push_frame(XYW_DEVICE_AREA_START);
        *pc = on_arg_handler;
        return 1;
    }

    // Keypress handling
    xyw_word on_keypress_handler = XYW_PEEKW(&data[TERMINAL_ON_KEYPRESS]);
    if (on_keypress_handler)
    {
        int c = getch_timeout(POLL_INTERVAL_MS);
        if (c > 0)
        {
            if (c == 3 || c == 4)
            {
                xyw_vm_set_waiting(0);
                return 0;
            }
            data[TERMINAL_INPUT] = (xyw_byte)c;
            xyw_vm_set_running(1);
            xyw_vm_push_frame(XYW_DEVICE_AREA_START);
            *pc = on_keypress_handler;
            return 1;
        }
        return -1; // active handler, no event yet (keep polling)
    }

    return 0;
}

//// Device init/teardown/IO

void terminal_init(xyw_byte *data)
{
    (void)data;
    // Reset arg feeding state for fresh image
    arg_index = 1;
    arg_char_pos = 0;
    exec_args_pos = 0;
#ifndef _WIN32
    struct termios raw;

    if (!isatty(STDIN_FILENO))
    {
        return;
    }

    if (tcgetattr(STDIN_FILENO, &terminal_orig_termios) != 0)
    {
        XYW_DBG("terminal: tcgetattr failed, raw mode not enabled\n");
        return;
    }

    raw = terminal_orig_termios;
    raw.c_lflag &= ~(ICANON | ECHO | ISIG);
    raw.c_cc[VMIN] = 0;
    raw.c_cc[VTIME] = 1;

    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != 0)
    {
        XYW_DBG("terminal: tcsetattr failed, raw mode not enabled\n");
        return;
    }

    terminal_raw_mode_active = 1;
#endif
}

void terminal_teardown(xyw_byte *data)
{
    (void)data;
#ifndef _WIN32
    if (terminal_raw_mode_active)
    {
        tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminal_orig_termios);
        terminal_raw_mode_active = 0;
    }
#endif
}

void terminal_output(xyw_byte *data, xyw_byte addr, xyw_byte *error)
{
    (void)error;
    if (addr == TERMINAL_OUTPUT) {
        fputc(data[addr], stdout);
        fflush(stdout);
    }
}

xyw_byte terminal_input(xyw_byte *data, xyw_byte addr, xyw_byte *error)
{
    (void)error;
    return data[addr];
}