diff --git a/Emulators/GNOConsole.mm.ragel b/Emulators/GNOConsole.mm.ragel index 6a49353..26ce769 100644 --- a/Emulators/GNOConsole.mm.ragel +++ b/Emulators/GNOConsole.mm.ragel @@ -129,7 +129,6 @@ screen->scrollUp(window); } else cursor.y++; } - } arg1 = any ${ _scratch[0] = ((fc & 0x7f) - 32); }; diff --git a/Emulators/VT100.h b/Emulators/VT100.h index 5eb1ebf..762076c 100644 --- a/Emulators/VT100.h +++ b/Emulators/VT100.h @@ -9,35 +9,54 @@ #import #import "Emulator.h" - #include "iGeometry.h" +#include "Screen.h" #ifdef __cplusplus #include +#include + + struct vt100_context : public context { + + enum { + G0, G1 + }; + enum { + CharSet_A, + CharSet_B, + CharSet_0, + CharSet_1, + CharSet_2, + }; + + unsigned G0_charset = CharSet_B; + unsigned G1_charset = CharSet_B; + unsigned charset = G0; + }; + #endif @interface VT100 : NSObject { unsigned _state; - BOOL _altKeyPad; + unsigned cs; + vt100_context _context; + Screen::CursorType _cursorType; + BOOL _private; + + vt100_context _saved_context; + - BOOL _keyMode; - BOOL _vt52Mode; - BOOL _graphics; - - iPoint _dca; - - struct __vt100flags { - unsigned int DECANM:1; // vt52 mode + unsigned int DECANM:1; // ANSI/vt52 mode unsigned int DECARM:1; // auto repeat mode. unsigned int DECAWM:1; // autowrap mode unsigned int DECCKM:1; // cursor key mode. unsigned int DECKPAM:1; // alternate keypad. - unsigned int DECKPNM:1; // not alternate keypad. + //unsigned int DECKPNM:1; // not alternate keypad. unsigned int DECCOLM:1; // 80/132 mode. unsigned int DECSCLM:1; // scrolling unsigned int DECSCNM:1; // screen @@ -46,17 +65,15 @@ unsigned int LNM:1; // line feed new line mode. - + unsigned int VT52GM:1; // vt52 graphics mode } _flags; #ifdef __cplusplus - std::vector _parms; + std::vector _args; + std::bitset<80> _tabs; #endif } --(void)tab: (Screen *)screen; - - @end diff --git a/Emulators/VT100.mm b/Emulators/VT100.mm index dff31cc..99e92a8 100644 --- a/Emulators/VT100.mm +++ b/Emulators/VT100.mm @@ -89,8 +89,6 @@ enum { _flags.DECOM = 0; _flags.DECINLM = 0; _flags.LNM = 0; - - } diff --git a/Emulators/VT100.mm.ragel b/Emulators/VT100.mm.ragel new file mode 100644 index 0000000..652bb79 --- /dev/null +++ b/Emulators/VT100.mm.ragel @@ -0,0 +1,771 @@ +// +// VT100.mm.ragel +// TwoTerm +// +// Created by Kelvin Sherlock on 4/6/2018. +// + +#include +#include +#include +#include +#include +#include + +#import "VT100.h" +#include "OutputChannel.h" +#include "Screen.h" +#include "algorithm.h" + +#define ESC "\x1b" + + +%%{ + machine vt100; + alphtype unsigned int; + + + esc = 0x1b; + cancel = 0x30 | 0x32; + + action clear_args { + _args.clear(); + _args.push_back(0); + } + + action answerback { + output->write(ESC "[?1;0c"); + } + + + # DECOM - locked to scrolling region + # DECAWM - autowrap + action forward { + if (cursor.x > window_x.second) { + cursor.x = window_x.first; + if (_flags.DECAWM) { + if (cursor_in_region()) { + if (cursor.y == region_y.second) + screen->scrollUp(window); + else { + if (cursor.y < window_y.second) cursor.y++; + } + } + } + } + } + + # + # ROM test - scrolling only happens if at bottom of scrolling region. + # + action linefeed { + + /* + * ROM test - scrolling ONLY happens if at bottom line of scrolling region. + * + */ + if (cursor.y == region_y.second) { + screen->scrollUp(window); + } else { + cursor.y = std::min(cursor.y + 1u, window_y.second); + } + } + + action reverse_linefeed { + if (cursor.y == region_y.first) { + screen->scrollDown(window); + } else { + cursor.y = std::min(cursor.y - 1u, window_y.first); + } + } + + # + # ROM TEST - up/down can't escape the scrolling region. + # + action cursor_up { + /* cursor up */ + unsigned count = _flags.DECANM ? std::min(1u, _args.front()) : 1; + + if (cursor_in_region()) { + cursor.y = clamp(cursor.y - count, region_y.first, region_y.second); + } else { + cursor.y = clamp(cursor.y - count, window_y.first, window_y.second); + } + } + action cursor_down { + /* cursor down */ + unsigned count = _flags.DECANM ? std::min(1u, _args.front()) : 1; + + if (cursor_in_region()) { + cursor.y = clamp(cursor.y + count, region_y.first, region_y.second); + } else { + cursor.y = clamp(cursor.y + count, window_y.first, window_y.second); + } + } + + action cursor_left { + /* cursor left */ + unsigned count = _flags.DECANM ? std::min(1u, _args.front()) : 1; + cursor.x = clamp(cursor.x - count, window_x.first, window_x.second); + } + + action cursor_right { + /* cursor right */ + unsigned count = _flags.DECANM ? std::min(1u, _args.front()) : 1; + cursor.x = clamp(cursor.x + count, window_x.first, window_x.second); + } + + action cup { + /* Cursor Position aka DCA */ + /* todo - numbering of lines depends on DECOM */ + unsigned y = _args.size() > 0 ? _args[0] : 0; + unsigned x = _args.size() > 1 ? _args[1] : 0; + + if (x == 0) x = 1; + if (y == 0) y = 1; + + if (_flags.DECOM) { + cursor.y = clamp(y - 1 + region_y.first, region_y.first, region_y.second); + cursor.x = clamp(x - 1, window_x.first, window_x.second); + } else { + cursor.y = clamp(y - 1, window_y.first, window_y.second); + cursor.x = clamp(x - 1, window_x.first, window_x.second); + } + + } + action vt52_dca { + /* todo - how are invalid values handled? */ + /* todo - DECOM supported? */ + if (_flags.DECOM) { + cursor.y = clamp(_args[0] + region_y.first, region_y.first, region_y.second); + cursor.x = clamp(_args[1], window_x.first, window_x.second); + } else { + cursor.y = clamp(_args[0], window_y.first, window_y.second); + cursor.x = clamp(_args[1], window_x.first, window_x.second); + } + } + + action stbm { + /* Set Top and Bottom Margins */ + /* aka scrolling region */ + unsigned top = _args.size() > 0 ? _args[0] : 0; + unsigned bottom = _args.size() > 1 ? _args[1] : 0; + + + if (top == 0) top = 1; + if (bottom == 0) bottom = 24; + + if (top < bottom && bottom <= 24) { + + --top; + + window = iRect(0, top, 80, bottom - top); + // also home the cursor ... depeds on DECOM. + if (_flags.DECOM) cursor = window.origin; + else cursor = iPoint(0, 0); + + region_y.first = top; + region_y.second = bottom - 1; + } + } + + + + action reset_mode { + for (auto m : _args) { + switch (m) { + case 1: if (_private) _flags.DECCKM = 0; break; + case 2: if (_private) _flags.DECANM = 0; break; + case 3: if (_private) _flags.DECCOLM = 0; break; + case 4: if (_private) _flags.DECSCLM = 0; break; + case 5: if (_private) _flags.DECSCNM = 0; break; + case 6: if (_private) { + _flags.DECOM = 0; + /* also move to new origin */ + cursor = iPoint(0, 0); + } + break; + case 7: if (_private) _flags.DECAWM = 0; break; + case 8: if (_private) _flags.DECARM = 0; break; + case 9: if (_private) _flags.DECINLM = 0; break; + case 20: if (!_private) _flags.LNM = 0; break; + + } + } + } + + action set_mode { + for (auto m : _args) { + switch (m) { + case 1: if (_private) _flags.DECCKM = 1; break; + case 2: if (_private) _flags.DECANM = 1; break; + case 3: if (_private) _flags.DECCOLM = 1; break; + case 4: if (_private) _flags.DECSCLM = 1; break; + case 5: if (_private) _flags.DECSCNM = 1; break; + case 6: if (_private) { + _flags.DECOM = 1; + /* also move to new origin */ + cursor = window.origin; + } + case 7: if (_private) _flags.DECAWM = 1; break; + case 8: if (_private) _flags.DECARM = 1; break; + case 9: if (_private) _flags.DECINLM = 1; break; + case 20: if (!_private) _flags.LNM = 1; break; + } + } + } + + + action tbc { + /* TBC – Tabulation Clear */ + for (auto arg : _args) { + switch (arg) { + case 0: if (cursor.x <= 79) _tabs[cursor.x] = 0; break; + case 3: _tabs.reset(); break; + } + } + } + + action dsr { + /* Device Status Report */ + for (auto arg : _args) { + switch(arg) { + default: break; + case 5: /* report status */ + output->write(ESC "[0n"); + break; + case 6: { /* cursor position report */ + char buffer[16]; + iPoint pt = cursor; + pt.x++; + pt.y++; + if (_flags.DECOM) pt.y -= region_y.first; + snprintf(buffer, sizeof(buffer)-1, ESC "[%u;%uR", pt.y, pt.x); + output->write(buffer); + break; + } + } + } + } + + + action erase_line { + // mode 0 and 1 erase cursor.x + iRect r(0, cursor.y, 80, 1); + + for (auto arg : _args) { + + switch (arg) { + case 0: { + // x ... eos + r.origin.x = cursor.x; + r.size.width = 80 - cursor.x; + screen->eraseRect(r); + break; + } + case 1: { + // 0 ... x + r.origin.x = 0; + r.size.width = cursor.x + 1; + screen->eraseRect(r); + break; + } + case 2: { + // 0 .. eos + r.origin.x = 0; + r.size.width = 80; + screen->eraseRect(r); + break; + } + } + } + } + + + action erase_screen { + + iRect r(0, cursor.y, 80, 1); + + for (auto arg : _args) { + + switch (arg) { + case 0: { + // x .. eos + r.origin.x = cursor.x; + r.size.width = 80 - cursor.x; + screen->eraseRect(r); + + iRect tmp(0, cursor.y + 1, 80, 24 - cursor.y - 1); + screen->eraseRect(tmp); + } + case 1: { + // 0 ... x + // 0 ... x + r.origin.x = 0; + r.size.width = cursor.x + 1; + screen->eraseRect(r); + + iRect tmp(0, 0, 80, cursor.y - 1); + screen->eraseRect(tmp); + break; + } + case 2: { + iRect tmp(0, 0, 80, 24); + screen->eraseRect(tmp); + break; + } + } + } + } + + action vt52_erase_line { + iRect r(cursor.x, cursor.y, 80 - cursor.x, 1); + screen->eraseRect(r); + } + + action vt52_erase_screen { + iRect r(cursor.x, cursor.y, 80 - cursor.x, 1); + screen->eraseRect(r); + r = iRect(0, cursor.y + 1, 80, 24 - cursor.y - 1); + screen->eraseRect(r); + } + + + action sgr { + /* Select Graphical Rendition */ + + for (auto arg : _args) { + switch(arg) { + case 0: _context.flags = Screen::FlagNormal; break; + case 1: _context.flags |= Screen::FlagBold; break; + case 4: _context.flags |= Screen::FlagUnderscore; break; + case 5: _context.flags |= Screen::FlagBlink; break; + case 7: _context.flags |= Screen::FlagInverse; break; + } + } + } + + action sc { + /* Save Cursor */ + _saved_context = _context; + } + # todo -- what if DECOM changes? + action rc { + _context.cursor = _saved_context.cursor; + _context.charset = _saved_context.charset; + _context.G0_charset = _saved_context.G0_charset; + _context.G1_charset = _saved_context.G1_charset; + + } + + control_codes = ( + 0x05 $answerback + | 0x07 ${ NSBeep(); } + | 0x08 ${ if (cursor.x) cursor.x--; } + | 0x09 ${ cursor.x = tab(cursor.x); } + | (0x0a | 0x0b | 0x0c) ${ if (_flags.LNM) cursor.x = 0; } $linefeed + | 0x0d ${ cursor.x = 0; } + | 0x0e ${ _context.charset = vt100_context::G1; } + | 0x0f ${ _context.charset = vt100_context::G0; } + | 0x11 ${ /* xon */ } + | 0x13 ${ /* xoff */ } + | cntrl - esc + ); + + args = ( + ';' ${ _args.push_back(0); } + | [0-9] ${ _args.back() *= 10; _args.back() += fc - '0'; } + )** + # >to(clear_args) + ; + + lbrace = ( + 'A' $cursor_up + | 'B' $cursor_down + | 'C' $cursor_right + | 'D' $cursor_left + | ('H' | 'f') $cup + | 'K' $erase_line + | 'J' $erase_screen + | 'm' $sgr + | 'r' $stbm + | 'g' $tbc + | 'h' $set_mode + | 'l' $reset_mode + | 'n' $dsr + | 'c' ${ if (_args.front() == 0) output->write(ESC "[?1;0c"); } + | 'y' ${ /* tests */ } + | 'q' ${ /* led lights */ } + | 'x' # DECREQTPARM + ) + ; + + lparen = [AB012] ${ + switch (fc) { + case 'A': _context.G0_charset = vt100_context::CharSet_A; break; + case 'B': _context.G0_charset = vt100_context::CharSet_B; break; + case '0': _context.G0_charset = vt100_context::CharSet_0; break; + case '1': _context.G0_charset = vt100_context::CharSet_1; break; + case '2': _context.G0_charset = vt100_context::CharSet_2; break; + + } + }; + + rparen = [AB012] ${ + switch (fc) { + case 'A': _context.G1_charset = vt100_context::CharSet_A; break; + case 'B': _context.G1_charset = vt100_context::CharSet_B; break; + case '0': _context.G1_charset = vt100_context::CharSet_0; break; + case '1': _context.G1_charset = vt100_context::CharSet_1; break; + case '2': _context.G1_charset = vt100_context::CharSet_2; break; + } + }; + + # #3 = DECDHL - Double-Height Line (top half) + # #4 = DECDHL - Double-Height Line (bottom half) + # #5 = DECSWL – Single-width Line + # #6 = DECDWL – Double-Width Line + + pound = ( + '3' + | '4' + | '5' + | '6' + | '7' # DECHCP - Hard Copy + | '8' ${ + /* DECALN */ + screen->fillScreen(char_info('E', 0)); + } + ); + +dca_arg = any ${ _args.push_back((fc & 0x7f) - 32); } ; + +escape_vt52 := ( + 'A' $cursor_up + | 'B' $cursor_down + | 'C' $cursor_right + | 'D' $cursor_left + | 'F' ${ _flags.VT52GM = 1; } + | 'G' ${ _flags.VT52GM = 0; } + | 'H' ${ cursor = window.origin; } # todo - DECOM? + | 'I' $reverse_linefeed + | 'J' $vt52_erase_screen + | 'K' $vt52_erase_line + | 'Y' ${ _args.clear(); } dca_arg dca_arg $vt52_dca + | 'Z' ${ output->write(ESC "/Z"); } + | '=' ${ _flags.DECKPAM = 1;} + | '>' ${ _flags.DECKPAM = 0; } + | '<' ${ _flags.DECANM = 1; } + ) + @{ fgoto main; } + $err{ fgoto main; } + ; + +csi = '[' ${ _args.clear(); _args.push_back(0); _private = NO; }; +private_flag = ('?' ${ _private = YES; })?; + +escape := ( + ( control_codes | esc )** + | cancel ${ /* cancel */ fgoto main; } + | csi private_flag args lbrace + | '(' lparen + | ')' rparen + | '#' pound + | 'D' $linefeed + | 'E' ${cursor.x = 0; } $linefeed + | 'H' ${ if (cursor.x <= 79) _tabs[cursor.x] = 1; } + | 'M' $reverse_linefeed + | 'Z' ${ output->write(ESC "[?1;0c"); } + | '7' $sc + | '8' $rc + | '=' ${ _flags.DECKPAM = 1;} + | '>' ${ _flags.DECKPAM = 0; } +# | 'N' +# | 'O' + | 'c' ${ [self reset: YES]; /* should also clear the screen */ } + | '1' # DECG - graphic processor on (vt105?) + | '2' # DECG - graphic processor off (vt105?) + ) + @{ fnext main; } + $err{ fgoto main; } + ; + + main := ( + control_codes + | esc ${ + if (_flags.DECANM) fgoto escape; else fgoto escape_vt52; + + } + | 0x20 .. 0x7f $forward ${ + screen->putc(fc, _context); + cursor.x++; + } + )** + $err{ fgoto main; } + ; + + write data; +}%% + + +@implementation VT100 + + ++(void)load +{ + [EmulatorManager registerClass: self]; +} + +-(id)init +{ + self = [super init]; + [self reset: YES]; + + return self; +} + + ++(NSString *)name +{ + return @"VT100"; +} + +-(NSString *)name +{ + return @"VT100"; +} + +-(const char *)termName +{ + return "vt100"; +} + +-(BOOL)resizable +{ + return NO; +} + +-(struct winsize)defaultSize +{ + struct winsize ws = { 24, 80, 0, 0}; + + return ws; +} + +-(void)reset: (BOOL)hard +{ + %% write init; + + _args.clear(); + + _flags.DECANM = 1; // ansi/vt100 mode + _flags.DECARM = 0; + _flags.DECAWM = 1; + _flags.DECCKM = 0; + _flags.DECKPAM = 0; + //_flags.DECKPNM = 1; + _flags.DECCOLM = 0; + + _flags.DECSCLM = 0; + _flags.DECSCNM = 0; + _flags.DECOM = 0; + _flags.DECINLM = 0; + _flags.LNM = 0; + _flags.VT52GM = 0; + + + if (hard) { + _context.cursor = iPoint(0,0); + _context.window = iRect(0, 0, 80, 24); + + _tabs.reset(); + _tabs[8] = true; + _tabs[16] = true; + _tabs[24] = true; + _tabs[32] = true; + _tabs[40] = true; + _tabs[48] = true; + _tabs[56] = true; + _tabs[64] = true; + _tabs[72] = true; + } + _context.flags = 0; + _context.charset = vt100_context::G0; + _context.G0_charset = vt100_context::CharSet_B; + _context.G1_charset = vt100_context::CharSet_B; +} + +-(void)processData: (uint8_t *)data length: (size_t)length screen:(Screen *)screen output:(OutputChannel *)output +{ + std::transform(data, data + length, data, [](uint8_t c){ return c & 0x7f; }); + + const uint8_t *eof = nullptr; + const uint8_t *p = data; + const uint8_t *pe = std::copy_if(data, data + length, data, [](uint8_t c){ + if (c == 0 || c == 0x7f) return false; + return true; + }); + + if (p == pe) return; + + iPoint &cursor = _context.cursor; + iRect &window = _context.window; + + + std::pair window_x(0u, 80-1); + std::pair window_y(0u, 24-1); + std::pair region_y(window.minY(), window.maxY()-1); + + auto cursor_in_region = [&](){ + return cursor.y >= region_y.first && cursor.y <= region_y.second; + }; + + /* todo - vt100 rom sometimes tabs to 80, sometimes doesn't. */ + /* difference being: tab, x < x wraps to next line? */ + auto tab = [&](unsigned x) -> unsigned { + if (x >= 79) return x; + for (x = x + 1; x < 80; ++x) { + if (_tabs[x]) return x; + } + return 79; //? + }; + + %%write exec; + + + if (cursor.x == 80) screen->setCursor(iPoint(79, cursor.y)); + else screen->setCursor(cursor); +} + +static const char *RemapKey(unichar uc, NSEventModifierFlags flags, struct __vt100flags vt100flags) +{ + + auto DECKPAM = vt100flags.DECKPAM; +/* +DECKPAM – Keypad Application Mode (DEC Private) + +The auxiliary keypad keys will transmit control sequences as defined in Tables 3-7 and 3-8. +*/ + + auto DECCKM = vt100flags.DECCKM; +/* +DECCKM – Cursor Keys Mode (DEC Private) + +This is a private parameter applicable to set mode (SM) and reset mode (RM) control sequences. This mode is only effective when the terminal is in keypad application mode (see DECKPAM) and the ANSI/VT52 mode (DECANM) is set (see DECANM). Under these conditions, if the cursor key mode is reset, the four cursor function keys will send ANSI cursor control commands. If cursor key mode is set, the four cursor function keys will send application functions. +*/ + + + auto LNM = vt100flags.LNM; + auto DECANM = vt100flags.DECANM; +/* +DECANM – ANSI/VT52 Mode (DEC Private) + +This is a private parameter applicable to set mode (SM) and reset mode (RM) control sequences. The reset state causes only VT52 compatible escape sequences to be interpreted and executed. The set state causes only ANSI "compatible" escape and control sequences to be interpreted and executed. +*/ + +#if 0 + /* Device-independent bits found in event modifier flags */ +typedef NS_OPTIONS(NSUInteger, NSEventModifierFlags) { + NSEventModifierFlagCapsLock = 1 << 16, // Set if Caps Lock key is pressed. + NSEventModifierFlagShift = 1 << 17, // Set if Shift key is pressed. + NSEventModifierFlagControl = 1 << 18, // Set if Control key is pressed. + NSEventModifierFlagOption = 1 << 19, // Set if Option or Alternate key is pressed. + NSEventModifierFlagCommand = 1 << 20, // Set if Command key is pressed. + NSEventModifierFlagNumericPad = 1 << 21, // Set if any key in the numeric keypad is pressed. + NSEventModifierFlagHelp = 1 << 22, // Set if the Help key is pressed. + NSEventModifierFlagFunction = 1 << 23, // Set if any function key is pressed. + + // Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information. + NSEventModifierFlagDeviceIndependentFlagsMask = 0xffff0000UL +}; +#endif + +/* + NSEnterCharacter: keypad Enter Key + NSNewlineCharacter: \n + NSCarriageReturnCharacter: \r (main Enter/Return key) + */ + + //NSLog(@"%c %02x %08lx", isprint(uc) ? uc : '.', uc, (unsigned long)flags); + + if (DECKPAM && (flags & NSNumericPadKeyMask)) { + switch (uc) { + case '0': return DECANM ? ESC "Op" : ESC "?p"; + case '1': return DECANM ? ESC "Oq" : ESC "?q"; + case '2': return DECANM ? ESC "Or" : ESC "?r"; + case '3': return DECANM ? ESC "Os" : ESC "?s"; + case '4': return DECANM ? ESC "Ot" : ESC "?t"; + case '5': return DECANM ? ESC "Ou" : ESC "?u"; + case '6': return DECANM ? ESC "Ov" : ESC "?v"; + case '7': return DECANM ? ESC "Ow" : ESC "?w"; + case '8': return DECANM ? ESC "Ox" : ESC "?x"; + case '9': return DECANM ? ESC "Oy" : ESC "?y"; + case ',': return DECANM ? ESC "Ol" : ESC "?l"; + case '-': return DECANM ? ESC "Om" : ESC "?m"; + case '.': return DECANM ? ESC "On" : ESC "?n"; + + + case NSEnterCharacter: return DECANM ? ESC "OM" : ESC "?M"; + } + } + + + switch(uc) { + case NSUpArrowFunctionKey: + if (DECANM) return DECKPAM ? ESC "OA" : ESC "[A"; + return ESC "A"; + + case NSDownArrowFunctionKey: + if (DECANM) return DECKPAM ? ESC "OB" : ESC "[B"; + return ESC "B"; + + case NSRightArrowFunctionKey: + if (DECANM) return DECKPAM ? ESC "OC" : ESC "[C"; + return ESC "C"; + + case NSLeftArrowFunctionKey: + if (DECANM) return DECKPAM ? ESC "OD" : ESC "[D"; + return ESC "D"; + + case NSF1FunctionKey: return DECANM ? ESC "OP" : ESC "P"; + case NSF2FunctionKey: return DECANM ? ESC "OQ" : ESC "Q"; + case NSF3FunctionKey: return DECANM ? ESC "OR" : ESC "R"; + case NSF4FunctionKey: return DECANM ? ESC "OS" : ESC "S"; + + + /* return/enter key is CR */ + case NSEnterCharacter: case NSNewlineCharacter: case NSCarriageReturnCharacter: + return LNM ? "\n\r" : "\r"; + + // NSDeleteCharacter IS 0x7f. + //case NSDeleteCharacter: return "\x7f"; // + } + return nullptr; +} + + + +-(void)keyDown:(NSEvent *)event screen:(Screen *)screen output:(OutputChannel *)output +{ + NSEventModifierFlags flags = [event modifierFlags]; + NSString *chars = [event charactersIgnoringModifiers]; + NSUInteger length = [chars length]; + + for (unsigned i = 0; i < length; ++i) { + unichar uc = [chars characterAtIndex: i]; + + const char *str = RemapKey(uc, flags, _flags); + if (str) { + output->write(str); + } else if (uc <= 0x7f) { + uint8_t c = uc; + if (flags & NSControlKeyMask) c = CTRL(c); + output->write(c); + } + } + +} + + +@end + diff --git a/Emulators/VT50.mm.ragel b/Emulators/VT50.mm.ragel index b44d4f5..0974038 100644 --- a/Emulators/VT50.mm.ragel +++ b/Emulators/VT50.mm.ragel @@ -143,12 +143,12 @@ namespace { | 0x09 $tab | 0x0a $linefeed | 0x0d ${ cursor.x = 0; } - | 0x0e when vt50 arg1 arg2 $dca + | 0x0e when vt50h arg1 arg2 $dca + | cntrl - esc ); - escape_codes = ( - control_codes - | esc + escape_codes = control_codes* <: ( + esc | 'A' ${ if (cursor.y) cursor.y--; } | 'B' when vt50h ${ if (cursor.y < window.maxY() -1) cursor.y++; } | 'C' ${ if (cursor.x < window.maxX() -1) cursor.x++; }