/* * Copyright (c) 2021 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "subtext.h" #include "ansi.h" #include "console.h" #include "focusable.h" #include "session.h" #include "user.h" #include "util.h" /* for monaco 9 */ #define TEXT_FONT monaco #define TEXT_SIZE 9 #define FONT_WIDTH 6 #define FONT_HEIGHT 11 #define CONSOLE_FORCE_REDRAW 1 #define CONSOLE_PADDING 6 #define CONSOLE_SMOOTH_SCROLLING 0 void console_idle(struct focusable *focusable, EventRecord *event); void console_close(struct focusable *focusable, EventRecord *event); void console_suspend(struct focusable *focusable, EventRecord *event); void console_resume(struct focusable *focusable, EventRecord *event); void console_update(struct focusable *focusable, EventRecord *event); void console_mouse_down(struct focusable *focusable, EventRecord *event); void console_key_down(struct focusable *focusable, EventRecord *event); void console_setup(struct session *session); void console_redraw(struct console *console, short force); short console_bound(struct console *console); void console_erase_chars(struct console *console, short start, short count); void console_shift_chars(struct console *console, short start, short offset); void console_parse_csi(struct console *console); struct node_funcs console_node_funcs = { console_setup, console_input, console_output, console_close_from_session }; struct console * console_init(void) { char title[64]; struct console *console; struct focusable *focusable; struct username_cache *ucache = NULL; unsigned long sysop_id; Rect bounds; short width, height; /* dismiss any dialog */ progress(NULL); console = xmalloczero(sizeof(struct console)); if (console == NULL) return NULL; console->session = session_create("console", "console", &console_node_funcs); if (console->session == NULL) { xfree(&console); warn("No free nodes for a console"); return NULL; } strlcpy(console->session->location, "Console", sizeof(console->session->location)); strlcpy(console->session->log.location, console->session->location, sizeof(console->session->log.location)); memset(console->chars, ' ', sizeof(console->chars)); console->ncolumns = DEFAULT_TERMINAL_COLUMNS; console->nlines = DEFAULT_TERMINAL_LINES; width = CONSOLE_PADDING + (FONT_WIDTH * console->ncolumns) + CONSOLE_PADDING; height = CONSOLE_PADDING + (FONT_HEIGHT * console->nlines) + CONSOLE_PADDING; bounds.left = (screenBits.bounds.right - width) / 2; bounds.right = bounds.left + width; bounds.top = GetMBarHeight() + ((screenBits.bounds.bottom - height) / 2); bounds.bottom = bounds.top + height; snprintf(title, sizeof(title), "%s: %s: console", PROGRAM_NAME, db->config.hostname); CtoPstr(title); console->win = NewWindow(0L, &bounds, title, false, noGrowDocProc, (WindowPtr)-1L, true, 0); if (!console->win) { warn("Can't create window"); xfree(&console); return NULL; } focusable = xmalloczero(sizeof(struct focusable)); if (focusable == NULL) { xfree(&console); return NULL; } focusable->win = console->win; focusable->cookie = console; focusable->update = console_update; focusable->key_down = console_key_down; focusable->close = console_close; console->focusable = focusable; if (!add_focusable(focusable)) { xfree(&console); return NULL; } console->session->cookie = (void *)console; console->session->vt100 = 1; console->session->tspeed = 19200; sysop_id = user_first_sysop_id(); if (sysop_id && (ucache = user_username(sysop_id)) != NULL) strlcpy(console->session->autologin_username, ucache->username, sizeof(console->session->autologin_username)); return console; } void console_idle(struct focusable *focusable, EventRecord *event) { struct console *console = (struct console *)focusable->cookie; struct session *session = console->session; GrafPtr old_port; short n, cursor; if (session->obuflen == 0) return; GetPort(&old_port); SetPort(console->win); /* uncursor */ cursor = (console->cursor_line * console->ncolumns) + console->cursor_column; if (cursor > sizeof(console->attrs) - 1) panic("cursor (%dx%d) out of bounds", console->cursor_line, console->cursor_column); console->attrs[cursor] &= ~ATTR_CURSOR; console->attrs[cursor] |= ATTR_DIRTY; for (n = 0; n < session->obuflen; n++) { if (console->in_csi) { if (session->obuf[n] == '\33' || console->csilen >= nitems(console->csi) - 1) { console_parse_csi(console); console->in_csi = 0; console->csi[0] = '\0'; console->csilen = 0; } else { console->csi[console->csilen] = session->obuf[n]; console->csilen++; console_parse_csi(console); } continue; } switch (session->obuf[n]) { case '\r': console->cursor_column = 0; break; case '\n': console->cursor_line++; console->cursor_column = 0; /* \r should do this, but JIC */ console_bound(console); break; case '\33': /* \e */ if (session->obuflen <= n + 1) { /* lone \e at end of buffer, keep it until we see next */ session->obuflen = 1; session->obuf[0] = '\33'; goto output_done; } if (session->obuf[n + 1] == '[') { console->in_csi = 1; n++; continue; } /* escape but not CSI, fall through */ default: console_bound(console); cursor = (console->cursor_line * console->ncolumns) + console->cursor_column; console->chars[cursor] = session->obuf[n]; console->attrs[cursor] = console->cur_attr | ATTR_DIRTY; console->cursor_column++; break; } } session->obuflen = 0; output_done: console_bound(console); console_redraw(console, 0); SetPort(old_port); } void console_suspend(struct focusable *focusable, EventRecord *event) { } void console_resume(struct focusable *focusable, EventRecord *event) { } void console_update(struct focusable *focusable, EventRecord *event) { struct console *console = (struct console *)focusable->cookie; short what = -1; if (event != NULL) what = event->what; switch (what) { case -1: case updateEvt: console_redraw(console, CONSOLE_FORCE_REDRAW); break; case activateEvt: break; } } void console_mouse_down(struct focusable *focusable, EventRecord *event) { } void console_key_down(struct focusable *focusable, EventRecord *event) { struct console *console = (struct console *)focusable->cookie; unsigned char k; if (console->session->ibuflen >= nitems(console->session->ibuf)) return; console->session->last_input_at = Time; k = (event->message & charCodeMask); if (event->modifiers & optionKey) { /* these are only correct on a US keyboard */ switch (k) { case 0x8d: /* opt+C */ /* ^C */ console->session->ibuf[console->session->ibuflen++] = CONTROL_C; break; case 0xb6: /* opt+D */ /* ^D */ console->session->ibuf[console->session->ibuflen++] = CONTROL_D; break; case 0xc2: /* opt+L */ console_redraw(console, CONSOLE_FORCE_REDRAW); break; } } else { switch (k) { case 0x1C: /* left */ console->session->ibuf[console->session->ibuflen++] = '\33'; console->session->ibuf[console->session->ibuflen++] = '['; console->session->ibuf[console->session->ibuflen++] = 'D'; break; case 0x1D: /* right */ console->session->ibuf[console->session->ibuflen++] = '\33'; console->session->ibuf[console->session->ibuflen++] = '['; console->session->ibuf[console->session->ibuflen++] = 'C'; break; case 0x1E: /* up */ console->session->ibuf[console->session->ibuflen++] = '\33'; console->session->ibuf[console->session->ibuflen++] = '['; console->session->ibuf[console->session->ibuflen++] = 'A'; break; case 0x1F: /* down */ console->session->ibuf[console->session->ibuflen++] = '\33'; console->session->ibuf[console->session->ibuflen++] = '['; console->session->ibuf[console->session->ibuflen++] = 'B'; break; default: console->session->ibuf[console->session->ibuflen++] = k; if (k == '\r') console->session->ibuf[console->session->ibuflen++] = '\n'; } } } void console_close(struct focusable *focusable, EventRecord *event) { struct console *console = (struct console *)focusable->cookie; console->session->ending = 1; } /* session API */ void console_setup(struct session *session) { struct console *console = (struct console *)session->cookie; session->terminal_columns = console->ncolumns; session->terminal_lines = console->nlines; snprintf(session->terminal_type, sizeof(session->terminal_type), "vt100"); } short console_output(struct session *session) { struct console *console = (struct console *)session->cookie; console_idle(console->focusable, NULL); return 0; } short console_input(struct session *session) { /* nothing to do here, input is fed from main loop */ return 0; } void console_close_from_session(struct session *session) { struct console *console = (struct console *)session->cookie; session_logf(session, "Closing console session"); destroy_focusable(console->focusable); xfree(&console); } short console_bound(struct console *console) { unsigned short shift_lines, shift_cols, chars_left, pxout, pxout_scroll; RgnHandle rgn; Rect r, r2; GrafPtr old_port; short ret = 0, n; while (console->cursor_column > console->ncolumns) { console->cursor_line++; console->cursor_column -= console->ncolumns; } if (console->cursor_line >= console->nlines) { shift_lines = console->cursor_line - console->nlines + 1; shift_cols = shift_lines * console->ncolumns; chars_left = (console->nlines * console->ncolumns) - shift_cols; pxout = shift_lines * FONT_HEIGHT; GetPort(&old_port); SetPort(console->win); rgn = NewRgn(); r.left = console->win->portRect.left + CONSOLE_PADDING; r.top = console->win->portRect.top + CONSOLE_PADDING; r.right = r.left + (console->ncolumns * FONT_WIDTH); r.bottom = r.top + ((console->nlines) * FONT_HEIGHT); n = CONSOLE_SMOOTH_SCROLLING ? CONSOLE_SMOOTH_SCROLLING : pxout; pxout_scroll = pxout; while (pxout_scroll > 0) { if (pxout_scroll < n) n = pxout_scroll; ScrollRect(&r, 0, -n, rgn); r2 = r; r2.top = r.bottom - 1; r2.bottom = r.top + n; FillRect(&r2, white); pxout_scroll -= n; } DisposeRgn(rgn); BlockMove(console->chars + shift_cols, console->chars, chars_left); BlockMove(console->attrs + shift_cols, console->attrs, chars_left); memset(console->chars + chars_left, ' ', shift_cols); memset(console->attrs + chars_left, console->cur_attr, shift_cols); SetPort(old_port); console->cursor_line = console->nlines - 1; ret = 1; } return ret; } #define CONSOLE_ATTRS_DRAWABLE (ATTR_REVERSE | ATTR_BOLD | ATTR_DIRTY) void console_redraw(struct console *console, short force) { Rect chunk; GrafPtr old_port; short curbold = -1, line, off, c, firstdirty; unsigned char curattr = 0, a; GetPort(&old_port); SetPort(console->win); TextFont(TEXT_FONT); TextSize(TEXT_SIZE); for (line = 0; line < console->nlines; line++) { off = line * console->ncolumns; chunk.top = console->win->portRect.top + CONSOLE_PADDING + (line * FONT_HEIGHT); for (firstdirty = -1, c = 0; c < console->ncolumns; c++) { a = console->attrs[off + c] & CONSOLE_ATTRS_DRAWABLE; if (!force && !(a & ATTR_DIRTY) && firstdirty == -1) continue; console->attrs[off + c] &= ~ATTR_DIRTY; if (c == console->ncolumns - 1) { if (firstdirty == -1) firstdirty = c; } else if (firstdirty == -1) { firstdirty = MAX(c - 1, 0); curattr = a; continue; } else if (a == curattr) { continue; } /* draw current chunk of dirty chars */ chunk.left = console->win->portRect.left + CONSOLE_PADDING + (firstdirty * FONT_WIDTH); chunk.right = chunk.left + ((c - firstdirty) * FONT_WIDTH); chunk.bottom = chunk.top + FONT_HEIGHT; FillRect(&chunk, white); if (curattr & ATTR_BOLD) { if (curbold != 1) { TextFace(thePort->txFace | bold | condense); curbold = 1; } } else if (curbold != 0) { TextFace(thePort->txFace & ~(bold | condense)); curbold = 0; } MoveTo(chunk.left, chunk.top + FONT_HEIGHT - 2); DrawText(console->chars, off + firstdirty, c - firstdirty); if (curattr & ATTR_REVERSE) InvertRect(&chunk); if (c != console->ncolumns - 1) { if (a & ATTR_DIRTY) firstdirty = c; else firstdirty = -1; curattr = a; } } } /* redraw cursor */ chunk.top = console->win->portRect.top + CONSOLE_PADDING + (console->cursor_line * FONT_HEIGHT); chunk.left = console->win->portRect.left + CONSOLE_PADDING + (console->cursor_column * FONT_WIDTH); chunk.bottom = chunk.top + FONT_HEIGHT; chunk.right = chunk.left + FONT_WIDTH; InvertRect(&chunk); ValidRect(&console->win->portRect); SetPort(old_port); } void console_erase_chars(struct console *console, short start, short count) { Rect eraser; short start_line, end_line, start_col, end_col, line; memset(console->chars + start, ' ', count); memset(console->attrs + start, console->cur_attr, count); start_line = start / console->ncolumns; start_col = start - (start_line * console->ncolumns); end_line = (start + count) / console->ncolumns; end_col = start + count - (end_line * console->ncolumns); for (line = start_line; line <= end_line; line++) { eraser.top = console->win->portRect.top + CONSOLE_PADDING + (line * FONT_HEIGHT); eraser.left = console->win->portRect.left + CONSOLE_PADDING; if (line == start_line) eraser.left += start_col * FONT_WIDTH; eraser.bottom = eraser.top + FONT_HEIGHT; if (line == end_line) eraser.right = eraser.left + (end_col * FONT_WIDTH); else eraser.right = eraser.left + (console->ncolumns * FONT_WIDTH); FillRect(&eraser, (console->cur_attr & ATTR_REVERSE) ? black : white); } } void console_shift_chars(struct console *console, short start, short offset) { Rect mover; RgnHandle rgn; short count; /* XXX: this currently only works on whole lines */ /* move all chars at start by offset, overwriting what's there */ count = (console->ncolumns * console->nlines) - start; if (start + offset + count > sizeof(console->chars)) /* inserting lines, losing trailing lines */ count = sizeof(console->chars) - start - offset; memmove(console->chars + start + offset, console->chars + start, count); memmove(console->attrs + start + offset, console->attrs + start, count); if (start % console->ncolumns != 0) warn("TODO partial console_shift_chars"); if (offset % console->ncolumns != 0) warn("TODO partial console_shift_chars"); rgn = NewRgn(); mover.top = console->win->portRect.top + CONSOLE_PADDING + ((start / console->ncolumns) * FONT_HEIGHT); if (offset < 0) mover.top -= FONT_HEIGHT; mover.left = console->win->portRect.left + CONSOLE_PADDING; mover.bottom = mover.top + (FONT_HEIGHT * ((count / console->ncolumns) + 1)); mover.right = mover.left + (FONT_WIDTH * console->ncolumns); ScrollRect(&mover, 0, (offset / console->ncolumns) * FONT_HEIGHT, rgn); DisposeRgn(rgn); } void console_parse_csi(struct console *console) { short x, y; long cursor; short serviced = 0; short param1 = -1, param2 = -1; short parambuflen; short start, count, offset; struct session *session; char parambuf[5]; unsigned char c; if (console->csilen == 0) return; c = console->csi[console->csilen - 1]; if (c < 'A' || (c > 'Z' && c < 'a') || c > 'z') return; switch (c) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'I': case 'J': case 'K': case 'L': case 'M': case 'S': case 'T': case 'd': case 'g': case 'n': case 's': case 'u': case '7': case '8': /* optional multiplier */ if (c == 'J' || c == 'K') param1 = 0; else param1 = 1; if (console->csilen > 1) { for (x = 0; x < console->csilen - 1 && x < nitems(parambuf) - 1; x++) { parambuf[x] = console->csi[x]; parambuf[x + 1] = '\0'; } param1 = atoi(parambuf); } break; case 'H': case 'f': /* two optional parameters separated by ; each defaulting to 1 */ param1 = 1; param2 = 1; y = -1; for (x = 0; x < console->csilen; x++) { if (console->csi[x] == ';') { y = x; break; } } if (y == -1) /* CSI 17H -> CSI 17; */ y = console->csilen - 1; if (y > 0) { for (x = 0; x < y && x < nitems(parambuf) - 1; x++) { parambuf[x] = console->csi[x]; parambuf[x + 1] = '\0'; } param1 = atoi(parambuf); if (y < console->csilen - 2) { parambuf[0] = '\0'; for (x = 0; x < (console->csilen - 1 - y) && x < nitems(parambuf) - 1; x++) { parambuf[x] = console->csi[y + 1 + x]; parambuf[x + 1] = '\0'; } param2 = atoi(parambuf); } } break; } serviced = 1; /* uncursor */ cursor = (console->cursor_line * console->ncolumns) + console->cursor_column; console->attrs[cursor] &= ~ATTR_CURSOR; console->attrs[cursor] |= ATTR_DIRTY; switch (c) { case 'A': /* CUU - cursor up, stop at top of screen */ console->cursor_line = MAX(0, console->cursor_line - param1); break; case 'B': /* CUD - cursor down, stop at bottom of screen */ console->cursor_line = MIN(console->nlines - 1, console->cursor_line + param1); break; case 'C': /* CUF - cursor forward, stop at screen edge */ console->cursor_column = MIN(console->ncolumns - 1, console->cursor_column + param1); break; case 'D': /* CUB - cursor back, stop at left edge of screen */ console->cursor_column = MAX(0, console->cursor_column - param1); break; case 'E': /* CNL - cursor next line */ console->cursor_column = 0; console->cursor_line = MAX(console->nlines - 1, console->cursor_line + param1); break; case 'F': /* CPL - cursor previous line */ console->cursor_column = 0; console->cursor_line = MAX(0, console->cursor_line - param1); break; case 'G': /* CHA - cursor horizontal absolute */ console->cursor_column = BOUND(param1, 0, console->ncolumns - 1); break; case 'H': /* CUP - cursor absolute position */ case 'f': /* HVP - horizontal vertical position */ if (param1 - 1 < 0) console->cursor_line = 0; else if (param1 > console->nlines) console->cursor_line = console->nlines - 1; else console->cursor_line = param1 - 1; if (param2 - 1 < 0) console->cursor_column = 0; else if (param2 > console->ncolumns) console->cursor_column = console->ncolumns - 1; else console->cursor_column = param2 - 1; break; case 'J': /* ED - erase in display */ switch (param1) { case 0: /* clear from cursor (inclusive) to end of screen */ start = (console->cursor_line * console->ncolumns) + console->cursor_column; count = (console->ncolumns * console->nlines) - start; console_erase_chars(console, start, count); break; case 1: /* clear from cursor to beginning of the screen */ start = 0; count = (console->cursor_line * console->ncolumns) + console->cursor_column; console_erase_chars(console, start, count); break; case 2: /* clear entire screen, mark everything clean */ start = 0; count = console->ncolumns * console->nlines; console_erase_chars(console, start, count); break; } break; case 'K': /* EL - erase in line */ switch (param1) { case 0: /* clear from cursor to end of line */ start = (console->cursor_line * console->ncolumns) + console->cursor_column; count = console->ncolumns - console->cursor_column; console_erase_chars(console, start, count); break; case 1: /* clear from cursor to beginning of line */ start = (console->cursor_line * console->ncolumns); count = console->ncolumns - console->cursor_column; console_erase_chars(console, start, count); break; case 2: /* clear entire line */ start = (console->cursor_line * console->ncolumns); count = console->ncolumns; console_erase_chars(console, start, count); break; } break; case 'L': /* IL - insert line */ param1 = BOUND(param1, 0, console->nlines - console->cursor_line); if (param1 == 0) break; if (console->cursor_line != console->nlines - 1) { /* shift lines down to insert new ones */ start = console->ncolumns * console->cursor_line; offset = console->ncolumns * param1; console_shift_chars(console, start, offset); } /* clear new lines at cursor line */ start = console->ncolumns * console->cursor_line; count = console->ncolumns * param1; console_erase_chars(console, start, count); break; case 'M': /* DL - delete line */ param1 = BOUND(param1, 0, console->nlines - console->cursor_line); if (param1 == 0) break; if (console->cursor_line != console->nlines - 1) { start = console->ncolumns * (console->cursor_line + param1); offset = console->ncolumns * -param1; console_shift_chars(console, start, offset); } /* fill in new blank lines after lines that moved up */ start = console->ncolumns * (console->nlines - param1); count = console->ncolumns * param1; console_erase_chars(console, start, count); break; case 'S': /* SU - scroll up */ /* TODO */ break; case 'T': /* SD - scroll down */ /* TODO */ break; case 'd': /* absolute line number */ if (param1 < 1) console->cursor_line = 0; else if (param1 > console->nlines) console->cursor_line = console->nlines; else console->cursor_line = param1 - 1; break; case 'g': /* clear tabs, ignore */ break; case 'h': /* reset, ignore */ break; case 'm': /* graphic changes */ parambuf[0] = '\0'; parambuflen = 0; param2 = console->cur_attr; for (x = 0; x < console->csilen; x++) { /* all the way to console->csilen to catch 'm' */ if (console->csi[x] == ';' || console->csi[x] == 'm') { param1 = atoi(parambuf); switch (param1) { case 0: /* reset */ #ifdef COLOR cur_color = FG_WHITE | BG_BLACK; #endif param2 = 0; break; case 1: /* bold */ param2 |= ATTR_BOLD; break; case 4: /* underline */ param2 |= ATTR_UNDERLINE; break; case 7: /* reverse */ param2 |= ATTR_REVERSE; break; case 22: /* normal color */ param2 = 0; break; case 21: /* bold off */ param2 &= ~ATTR_BOLD; break; case 24: /* underline off */ param2 &= ~ATTR_UNDERLINE; break; case 27: /* inverse off */ param2 &= ~ATTR_REVERSE; break; case 30: /* fg black */ case 31: /* fg red */ case 32: /* fg green */ case 33: /* fg yellow */ case 34: /* fg blue */ case 35: /* fg magenta */ case 36: /* fg cyan */ case 37: /* fg white */ #ifdef COLOR cur_color &= ~0xff; cur_color |= (1 << (param1 - 30)); #endif break; case 40: /* bg black */ case 41: /* bg red */ case 42: /* bg green */ case 43: /* bg yellow */ case 44: /* bg blue */ case 45: /* bg magenta */ case 46: /* bg cyan */ case 47: /* bg white */ #ifdef COLOR cur_color = (1 << (8 + param1 - 40)) | (cur_color & 0xff); #endif break; } parambuf[0] = '\0'; parambuflen = 0; } else if (parambuflen < nitems(parambuf) - 1) { parambuf[parambuflen] = console->csi[x]; parambuflen++; parambuf[parambuflen] = '\0'; } } console->cur_attr = param2; break; case 'n': /* DSR - device status report */ switch (param1) { case 5: /* terminal is ready */ session = console->session; if (session->ibuflen >= sizeof(session->ibuf) - 4) break; session->ibuf[session->ibuflen++] = ESC; session->ibuf[session->ibuflen++] = '['; session->ibuf[session->ibuflen++] = '0'; session->ibuf[session->ibuflen++] = 'n'; break; case 6: /* CPR - report cursor position */ session = console->session; if (session->ibuflen >= sizeof(session->ibuf) - 10) break; session->ibuf[session->ibuflen++] = ESC; session->ibuf[session->ibuflen++] = '['; if (console->cursor_line >= 99) session->ibuf[session->ibuflen++] = '0' + ((console->cursor_line + 1) / 100); if (console->cursor_line >= 9) session->ibuf[session->ibuflen++] = '0' + (((console->cursor_line + 1) % 100) / 10); session->ibuf[session->ibuflen++] = '0' + ((console->cursor_line + 1) % 10); session->ibuf[session->ibuflen++] = ';'; if (console->cursor_column >= 99) session->ibuf[session->ibuflen++] = '0' + ((console->cursor_column + 1) / 100); if (console->cursor_column >= 9) session->ibuf[session->ibuflen++] = '0' + (((console->cursor_column + 1) % 100) / 10); session->ibuf[session->ibuflen++] = '0' + ((console->cursor_column + 1) % 10); session->ibuf[session->ibuflen++] = 'R'; break; } break; case 's': /* SCO - save cursor position */ console->saved_cursor_column = console->cursor_column; console->saved_cursor_line = console->cursor_line; break; case 'u': /* SCO - restore saved cursor position */ console->cursor_column = console->saved_cursor_column; console->cursor_line = console->saved_cursor_line; break; default: /* * if the last character is a letter and we haven't serviced * it, assume it's a sequence we don't support and should just * suppress */ if (c < 65 || (c > 90 && c < 97) || c > 122) serviced = 0; } if (serviced) { console->csilen = 0; console->csi[0] = '\0'; console->in_csi = 0; console_bound(console); } }