/* sim_vt.c: VT2xx compatible terminal emulator | |
Copyright (c) 2002, Robert M Supnik | |
Written by Fischer Franz and used with his gracious permission | |
Permission is hereby granted, free of charge, to any person obtaining a | |
copy of this software and associated documentation files (the "Software"), | |
to deal in the Software without restriction, including without limitation | |
the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
and/or sell copies of the Software, and to permit persons to whom the | |
Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
Except as contained in this notice, the name of Robert M Supnik shall not | |
be used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from Robert M Supnik. | |
13-Apr-02 FF Added Hold-Screen on Pause-Key | |
Corrected scrolling | |
Added additional Esc-sequences Erase-Insert-Delete-Char | |
Corrected decoding | |
(ESC [ H, ESC [ x H, ESC [ ; y H , ESC [ x ; y H | |
are all valid sequences) | |
15-Mar-02 FF Original version Fischer Franz | |
*/ | |
#include <windows.h> | |
#include "sim_defs.h" | |
#define HOLD 0x1 | |
#define INSERT 0x2 | |
#define SCRMAX 64 | |
static HANDLE kbdHdl; | |
static HANDLE scrHdl; | |
static INPUT_RECORD inBuf; | |
static DWORD act, mode; | |
static CONSOLE_SCREEN_BUFFER_INFO screen; | |
static COORD margin; | |
static WORD attrib; | |
static char *kbd_ptr; | |
static char *scr_ptr; | |
static char scr_buf[SCRMAX]; | |
static uint8 scr_mode; | |
struct keyEntry { | |
uint8 asciiCode; | |
uint8 scanCode; | |
char *escSeq; | |
}; | |
typedef struct keyEntry KEY; | |
typedef void (*scr_func) (); | |
struct scrEntry { | |
char last; | |
scr_func interpret; | |
}; | |
typedef struct scrEntry SCR; | |
static KEY keyTab[] = { | |
{0 ,0x3B,"[31~"}, /* F1, mapped to F17 on VT320 */ | |
{0 ,0x3C,"[32~"}, /* F2, mapped to F18 on VT320 */ | |
{0 ,0x3D,"[33~"}, /* F3, mapped to F19 on VT320 */ | |
{0 ,0x3E,"[34~"}, /* F4, mapped to F20 on VT320 */ | |
{0 ,0x3F,"[17~"}, /* F5, mapped to F6 on VT320 */ | |
{0 ,0x40,"[18~"}, /* F6, mapped to F7 on VT320 */ | |
{0 ,0x41,"[19~"}, /* F7, mapped to F8 on VT320 */ | |
{0 ,0x42,"[20~"}, /* F8, mapped to F9 on VT320 */ | |
{0 ,0x43,"[23~"}, /* F9, mapped to F11 on VT320 */ | |
{0 ,0x44,"[24~"}, /* F10,mapped to F12 on VT320 */ | |
{0 ,0x57,"[25~"}, /* F11,mapped to F13 on VT320 */ | |
{0 ,0x58,"[26~"}, /* F12,mapped to F14 on VT320 */ | |
{0xE0,0x52,"[2~"}, /* INS,mapped to INSERT on VT320 */ | |
{0xE0,0x53,"[3~"}, /* DEL,mapped to REMOVE in VT320 */ | |
{0xE0,0x47,"[1~"}, /* HOME, mapped to FIND on VT320 */ | |
{0xE0,0x4F,"[4~"}, /* END,mapped to SELECT on VT320 */ | |
{0xE0,0x49,"[5~"}, /* PAGE UP, mapped to PREV on VT320 */ | |
{0xE0,0x51,"[6~"}, /* PAGE DOWN, mapped to NEXT on VT320 */ | |
{0xE0,0x48,"[A"},/* UP */ | |
{0xE0,0x50,"[B"},/* DOWN */ | |
{0xE0,0x4D,"[C"},/* RIGHT */ | |
{0xE0,0x4B,"[D"},/* LEFT */ | |
{0xE0,0x45,"OP"},/* NUM, mapped to PF1 on VT320 */ | |
{0xE0,0x35,"OQ"},/* / , mapped to PF2 on VT320 */ | |
{'*' ,0x37,"OR"},/* * , mapped to PF3 on VT320 */ | |
{'-', 0x4A,"OS"},/* / , mapped to PF4 on VT320 */ | |
/* The following Keys send Application Keypad-Mode sequences */ | |
{0 ,0x52,"Op"}, /* KP0 */ | |
{'0',0x52,"Op"}, /* KP0 */ | |
{0 ,0x4F,"Oq"}, /* KP1 */ | |
{'1',0x4F,"Oq"}, /* KP1 */ | |
{0 ,0x50,"Or"}, /* KP2 */ | |
{'2',0x50,"Or"}, /* KP2 */ | |
{0 ,0x51,"Os"}, /* KP3 */ | |
{'3',0x51,"Os"}, /* KP3 */ | |
{0 ,0x4B,"Ot"}, /* KP4 */ | |
{'4',0x4B,"Ot"}, /* KP4 */ | |
{0 ,0x4C,"Ou"}, /* KP5 */ | |
{'5',0x4C,"Ou"}, /* KP5 */ | |
{0 ,0x4D,"Ov"}, /* KP6 */ | |
{'6',0x4D,"Ov"}, /* KP6 */ | |
{0 ,0x47,"Ow"}, /* KP7 */ | |
{'7',0x47,"Ow"}, /* KP7 */ | |
{0 ,0x48,"Ox"}, /* KP8 */ | |
{'8',0x48,"Ox"}, /* KP8 */ | |
{0 ,0x49,"Oy"}, /* KP9 */ | |
{'9',0x49,"Oy"}, /* KP9 */ | |
{0 ,0x53,"On"}, /* PERIOD */ | |
{'.',0x53,"On"}, /* PERIOD */ | |
{0xE0,0x1C,"OM"}, /* ENTER */ | |
{'+',0x4E,"Ol"}, /* COMMA */ | |
{0} /* End of List */ | |
}; | |
int vt_read() { | |
DWORD cnt = 0; | |
uint8 sCode, aCode; | |
KEY *key; | |
if (kbd_ptr && *kbd_ptr) { | |
return (*kbd_ptr++ & 0177); | |
} | |
GetNumberOfConsoleInputEvents(kbdHdl, &cnt); | |
if (!cnt) return -1; | |
ReadConsoleInput(kbdHdl, &inBuf, 1, &act); | |
if (inBuf.EventType != KEY_EVENT) return -1; | |
if (!inBuf.Event.KeyEvent.bKeyDown) return -1; | |
if (inBuf.Event.KeyEvent.dwControlKeyState & ENHANCED_KEY) inBuf.Event.KeyEvent.uChar.AsciiChar = (uint8)0xE0; | |
sCode = (uint8)inBuf.Event.KeyEvent.wVirtualScanCode; | |
aCode = inBuf.Event.KeyEvent.uChar.AsciiChar; | |
if (scr_mode & HOLD) { | |
scr_mode &= ~HOLD; | |
return 0x11; | |
} | |
if (sCode < 0x37 && aCode != 0 && aCode != 0xE0) return aCode; | |
if (sCode == 0x45 && aCode == 0) { | |
scr_mode |= HOLD; | |
return 0x13; | |
} | |
for (key = keyTab; key->scanCode; key++) { | |
if (key->asciiCode != aCode || key->scanCode != sCode) continue; | |
kbd_ptr = key->escSeq; | |
return 0x1b; | |
} | |
if (aCode != 0 && aCode != 0xE0) return aCode; | |
return -1; | |
} | |
static void scr_nop () {}; | |
static void scr_scroll_up(int from, int to, int lines) { | |
SMALL_RECT src; | |
COORD dst; | |
CHAR_INFO fill; | |
dst.X=0; dst.Y=from; | |
src.Top=from+lines; src.Left=0; src.Bottom=to; src.Right=screen.dwSize.X; | |
fill.Char.AsciiChar = ' '; | |
fill.Attributes = attrib; | |
ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill); | |
}; | |
static void scr_scroll_down(int from, int to, int lines) { | |
SMALL_RECT src; | |
COORD dst; | |
CHAR_INFO fill; | |
dst.X=0; dst.Y=from+lines; | |
src.Top=from; src.Left=0; src.Bottom=to-lines; src.Right=screen.dwSize.X; | |
fill.Char.AsciiChar = ' '; | |
fill.Attributes = attrib; | |
ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill); | |
}; | |
static void scr_insert_char() { | |
int nr = atoi(&scr_buf[1]); | |
SMALL_RECT src; | |
COORD dst; | |
CHAR_INFO fill; | |
if (!nr) nr = 1; | |
src.Top=screen.dwCursorPosition.Y; src.Left=screen.dwCursorPosition.X-nr; | |
src.Bottom=src.Top; src.Right=screen.dwSize.X; | |
dst.X=src.Left+nr; dst.Y=src.Top; | |
fill.Char.AsciiChar = ' '; | |
fill.Attributes = attrib; | |
ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill); | |
}; | |
static void scr_pos_up() { | |
int nr = atoi(&scr_buf[1]); | |
if (!nr) nr = 1; | |
if (screen.dwCursorPosition.Y > nr) screen.dwCursorPosition.Y -= nr; | |
else screen.dwCursorPosition.Y = 0; | |
SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition); | |
}; | |
static void scr_pos_down() { | |
int nr = atoi(&scr_buf[1]); | |
if (!nr) nr = 1; | |
screen.dwCursorPosition.Y += nr; | |
if (screen.dwCursorPosition.Y >= margin.Y) screen.dwCursorPosition.Y = margin.Y-1; | |
SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition); | |
}; | |
static void scr_pos_right() { | |
int nr = atoi(&scr_buf[1]); | |
if (!nr) nr = 1; | |
screen.dwCursorPosition.X += nr; | |
if (screen.dwCursorPosition.X >= screen.dwSize.X) screen.dwCursorPosition.X = screen.dwSize.X-1; | |
SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition); | |
}; | |
static void scr_pos_left() { | |
int nr = atoi(&scr_buf[1]); | |
if (!nr) nr = 1; | |
if (screen.dwCursorPosition.X > nr) screen.dwCursorPosition.X -= nr; | |
else screen.dwCursorPosition.X = 0; | |
SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition); | |
}; | |
static void scr_pos_cursor() { | |
int x, y; | |
if (scr_buf[0] == 'H') return; | |
x = 1; y = 1; | |
if (!sscanf(scr_buf,"[%d;%d",&y,&x)) sscanf(scr_buf,"[;%d",&x); | |
screen.dwCursorPosition.X = x-1; | |
screen.dwCursorPosition.Y = y-1; | |
SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition); | |
}; | |
static void scr_prev_line() { | |
scr_scroll_down(margin.X,margin.Y,1); | |
}; | |
static void scr_next_line() { | |
scr_scroll_up(margin.X,margin.Y,1); | |
}; | |
static void scr_erase_display() { | |
int nr = atoi(&scr_buf[1]); | |
COORD pos; | |
int len; | |
if (nr == 0) { | |
pos = screen.dwCursorPosition; | |
len = (screen.dwSize.Y-screen.dwCursorPosition.Y-1)*screen.dwSize.X + | |
(screen.dwSize.X-screen.dwCursorPosition.X); | |
} else if (nr == 1) { | |
pos.X = 0; pos.Y = 0; | |
len = (screen.dwCursorPosition.Y-1)*screen.dwSize.X + screen.dwCursorPosition.X; | |
} else if (nr == 2) { | |
pos.X = 0; pos.Y = 0; | |
len = screen.dwSize.X*screen.dwSize.Y; | |
} else { | |
return; | |
} | |
FillConsoleOutputAttribute(scrHdl,screen.wAttributes,len,pos,&act); | |
FillConsoleOutputCharacter(scrHdl,' ',len,pos,&act); | |
}; | |
static void scr_erase_line() { | |
int nr = atoi(&scr_buf[1]); | |
COORD pos; | |
int len; | |
if (nr == 0) { | |
pos = screen.dwCursorPosition; | |
len = screen.dwSize.X-screen.dwCursorPosition.X; | |
} else if (nr == 1) { | |
pos.X = 0; pos.Y = screen.dwCursorPosition.Y; | |
len = screen.dwCursorPosition.X; | |
} else if (nr == 2) { | |
pos.X = 0; pos.Y = screen.dwCursorPosition.Y; | |
len = screen.dwSize.X; | |
} else { | |
return; | |
} | |
FillConsoleOutputAttribute(scrHdl,screen.wAttributes,len,pos,&act); | |
FillConsoleOutputCharacter(scrHdl,' ',len,pos,&act); | |
}; | |
static void scr_delete_line() { | |
if (scr_buf[0] == 'M') { | |
scr_prev_line(); | |
} else { | |
int nr = atoi(&scr_buf[1]); | |
if (!nr) nr = 1; | |
scr_scroll_up(screen.dwCursorPosition.Y,margin.Y,nr); | |
} | |
}; | |
static void scr_insert_line() { | |
int nr = atoi(&scr_buf[1]); | |
if (!nr) nr = 1; | |
scr_scroll_down(screen.dwCursorPosition.Y,margin.Y,nr); | |
}; | |
static void scr_delete_char() { | |
int nr = atoi(&scr_buf[1]); | |
SMALL_RECT src; | |
COORD dst; | |
CHAR_INFO fill; | |
if (!nr) nr = 1; | |
dst = screen.dwCursorPosition; | |
src.Top=dst.Y; src.Left=dst.X+nr; src.Bottom=dst.Y; src.Right=screen.dwSize.X; | |
fill.Char.AsciiChar = ' '; | |
fill.Attributes = attrib; | |
ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill); | |
}; | |
static void scr_erase_char() { | |
int nr = atoi(&scr_buf[1]); | |
COORD pos; | |
if (!nr) nr = 1; | |
pos = screen.dwCursorPosition; | |
FillConsoleOutputAttribute(scrHdl,screen.wAttributes,nr,pos,&act); | |
FillConsoleOutputCharacter(scrHdl,' ',nr,pos,&act); | |
}; | |
static void scr_request() { | |
kbd_ptr = "\033[?6c"; | |
}; | |
static void scr_set() { | |
int nr = atoi(&scr_buf[1]); | |
if (nr == 4) scr_mode |= INSERT; | |
}; | |
static void scr_reset() { | |
int nr = atoi(&scr_buf[1]); | |
if (nr == 4) scr_mode &= ~INSERT; | |
}; | |
static void scr_attrib() { | |
int nr = atoi(&scr_buf[1]); | |
switch (nr) { | |
case 0: | |
attrib = screen.wAttributes; | |
break; | |
case 1: | |
attrib = screen.wAttributes | 0x80; | |
break; | |
case 7: | |
attrib = ((screen.wAttributes & 0xf) << 4) | ((screen.wAttributes & 0x7) >> 4); | |
break; | |
} | |
}; | |
static void scr_report() {}; | |
static void scr_margin() { | |
int top, bot; | |
top = 1; bot = screen.dwSize.Y+1; | |
if (!sscanf(scr_buf,"[%d;%d",&top,&bot)) sscanf(scr_buf,"[;%d",&bot); | |
margin.X = top-1; | |
if (bot != 24) margin.Y = bot-1; | |
else margin.Y = screen.dwSize.Y; | |
screen.dwCursorPosition.X = 0; | |
screen.dwCursorPosition.Y = 0; | |
SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition); | |
}; | |
static SCR scrTab[] = { | |
{'@', scr_insert_char}, | |
{'A', scr_pos_up}, | |
{'B', scr_pos_down}, | |
{'C', scr_pos_right}, | |
{'D', scr_pos_left}, | |
{'E', scr_next_line}, | |
{'H', scr_pos_cursor}, | |
{'I', scr_prev_line}, | |
{'J', scr_erase_display}, | |
{'K', scr_erase_line}, | |
{'L', scr_insert_line}, | |
{'M', scr_delete_line}, | |
{'P', scr_delete_char}, | |
{'X', scr_erase_char}, | |
{'Z', scr_request}, | |
{'c', scr_request},/* Device attribute request */ | |
{'f', scr_pos_cursor}, | |
{'h', scr_set}, /* Set mode */ | |
{'l', scr_reset}, /* Reset mode */ | |
{'m', scr_attrib}, /* Display attributes */ | |
{'n', scr_report}, /* Device report */ | |
{'r', scr_margin}, | |
{'=', scr_nop}, /* Set applikation keypad mode */ | |
{'>', scr_nop}, /* Set numeric keypad mode */ | |
{0} | |
}; | |
static void scr_char(char c) { | |
if (c == 0x8) { /* BS */ | |
if (screen.dwCursorPosition.X > 0) screen.dwCursorPosition.X--; | |
} else if (c == 0xa) { /* LF */ | |
if (screen.dwCursorPosition.Y < margin.Y-1) screen.dwCursorPosition.Y++; | |
else scr_scroll_up(margin.X,margin.Y,1); | |
} else if (c == 0xd) { /* CR */ | |
screen.dwCursorPosition.X = 0; | |
} else if (c == 0x9) { /* Tab */ | |
screen.dwCursorPosition.X = (screen.dwCursorPosition.X + 8) & ~7; | |
if (screen.dwCursorPosition.X >= screen.dwSize.X) screen.dwCursorPosition.X = screen.dwSize.X-1; | |
} else if (c < ' ') { | |
return; | |
} else if (!(scr_mode & INSERT)) { | |
WriteConsoleOutputCharacter(scrHdl,&c,1,screen.dwCursorPosition,&act); | |
WriteConsoleOutputAttribute(scrHdl,&attrib,1,screen.dwCursorPosition,&act); | |
if (screen.dwCursorPosition.X < screen.dwSize.X) screen.dwCursorPosition.X++; | |
else if (screen.dwCursorPosition.Y < margin.Y-1) { | |
screen.dwCursorPosition.X = 0; | |
screen.dwCursorPosition.Y++; | |
} else { | |
scr_scroll_up(margin.X,margin.Y,1); | |
screen.dwCursorPosition.X = 0; | |
} | |
} else { | |
SMALL_RECT src; | |
COORD dst; | |
CHAR_INFO fill; | |
src.Top=screen.dwCursorPosition.Y; src.Left=screen.dwCursorPosition.X; | |
src.Bottom=src.Top; src.Right=screen.dwSize.X; | |
dst.X=src.Left+1; dst.Y=src.Top; | |
fill.Char.AsciiChar = ' '; | |
fill.Attributes = attrib; | |
ScrollConsoleScreenBuffer(scrHdl,&src,0,dst,&fill); | |
WriteConsoleOutputCharacter(scrHdl,&c,1,screen.dwCursorPosition,&act); | |
if (screen.dwCursorPosition.X < screen.dwSize.X) screen.dwCursorPosition.X++; | |
else if (screen.dwCursorPosition.Y < margin.Y-1) { | |
screen.dwCursorPosition.X = 0; | |
screen.dwCursorPosition.Y++; | |
} else { | |
scr_scroll_up(margin.X,margin.Y,1); | |
screen.dwCursorPosition.X = 0; | |
} | |
} | |
SetConsoleCursorPosition(scrHdl,screen.dwCursorPosition); | |
} | |
static void check_esc (char c) { | |
SCR *scr; | |
*scr_ptr = c; | |
if ((scr_ptr - scr_buf) < SCRMAX) scr_ptr++; | |
for (scr = scrTab; scr->last; scr++) { | |
if (scr->last != c) continue; | |
*scr_ptr = 0; | |
scr->interpret(); | |
scr_ptr = 0; | |
return; | |
} | |
} | |
void vt_write(char c) { | |
if (c != 0x1b && !scr_ptr) { | |
scr_char(c); | |
} else if (c == 0x1b) { | |
scr_ptr = scr_buf; | |
} else if (c < ' ') { | |
scr_ptr = 0; | |
scr_char(c); | |
} else if (scr_ptr == scr_buf) { | |
check_esc(c); | |
} else if (c >= '@') { | |
check_esc(c); | |
scr_ptr = 0; | |
} else { | |
*scr_ptr = c; | |
if ((scr_ptr - scr_buf) < SCRMAX) scr_ptr++; | |
} | |
} | |
void vt_init() { | |
kbdHdl = GetStdHandle(STD_INPUT_HANDLE); | |
scrHdl = GetStdHandle(STD_OUTPUT_HANDLE); | |
GetConsoleMode(kbdHdl, &mode); | |
GetConsoleScreenBufferInfo(scrHdl, &screen); | |
margin.X = 0; margin.Y = screen.dwSize.Y; | |
attrib = screen.wAttributes; | |
scr_mode = 0; | |
} | |
void vt_cmd() { | |
SetConsoleMode(kbdHdl, mode); | |
} | |
void vt_run() { | |
kbd_ptr = 0; | |
SetConsoleMode(kbdHdl, 0); | |
GetConsoleScreenBufferInfo(scrHdl, &screen); | |
margin.X = 0; margin.Y = screen.dwSize.Y; | |
attrib = screen.wAttributes; | |
scr_mode = 0; | |
} |