AmendHub

Download:

jcs

/

subtext

/

amendments

/

39

*: Lots of unrelated changes lost to repo corruption :(

session: Better handle autologin, look like it's actually logging in
main: Handle GoAway, stop using WaitNextEvent
ansi: Terminate calls to ansi() with ANSI_END instead of NULL
chat: Implement /help and /quit
console: Lots of speed improvements
*: Standarize on "column" and "line"
session: Various bugfixes, mark ending sessions and check everywhere
console: Fix attr resetting in redraw, fixes one-char bold items
session: Support specific-width varible expansion in session_load_view

jcs made amendment 39 over 2 years ago
--- ansi.c Thu Dec 23 20:57:33 2021 +++ ansi.c Tue Dec 28 17:41:19 2021 @@ -47,7 +47,7 @@ ansi(struct session *s, ...) *ansi_out = 0; va_start(ap, s); - while ((attr = va_arg(ap, short)) != 0) { + while ((attr = va_arg(ap, short)) != ANSI_END) { len = 0; switch (attr) { case ANSI_RESET: @@ -159,14 +159,14 @@ ansi(struct session *s, ...) } } break; - case ANSI_CURSOR_ROW_COL: + case ANSI_CURSOR_LINE_COL: val = va_arg(ap, short); val2 = va_arg(ap, short); if (s->vt100) - /* row, column */ + /* line, column */ sprintf(ansi_tmp, "\33[%d;%dH", val, val2); else if (s->vt52) - /* column, row as chr(col)+31,chr(row)+31 */ + /* column, line as chr(col)+31,chr(line)+31 */ sprintf(ansi_tmp, "\33Y%c%c", val2 + 31, val + 31); len = strlcat(ansi_out, ansi_tmp, sizeof(ansi_buf[0])); break; --- ansi.h Thu Dec 23 20:57:41 2021 +++ ansi.h Tue Dec 28 17:41:38 2021 @@ -35,11 +35,11 @@ enum { ANSI_DOWN_N, ANSI_BACK_N, ANSI_COL_N, - ANSI_CURSOR_ROW_COL, + ANSI_CURSOR_LINE_COL, ANSI_INSERT_LINES_N, ANSI_DELETE_LINES_N, - ANSI_NULL = -1 + ANSI_END = -1 }; char *ansi(struct session *s, ...); --- chat.c Thu Dec 23 21:00:16 2021 +++ chat.c Wed Dec 29 08:57:17 2021 @@ -29,6 +29,8 @@ static char chat_tbuf[256]; +void chat_help(struct session *s); + void chat_broadcast(struct session *s, char *str) { @@ -63,10 +65,10 @@ chat_printf_line(struct session *s, short around_bar, va_end(ap); pbuf_line = chat_pbuf; - max_llen = s->terminal_cols; + max_llen = s->terminal_columns; if (around_bar) - session_output_string(s, ansi(s, ANSI_SAVE_CURSOR, NULL)); + session_output_string(s, ansi(s, ANSI_SAVE_CURSOR, ANSI_END)); while (len > 0) { if (len > max_llen) { @@ -83,9 +85,9 @@ chat_printf_line(struct session *s, short around_bar, if (around_bar) session_output_string(s, - ansi(s, ANSI_CURSOR_ROW_COL, 1, 1, ANSI_DELETE_LINES_N, 1, - ANSI_CURSOR_ROW_COL, s->terminal_rows - 2, 1, - ANSI_INSERT_LINES_N, 1, NULL)); + ansi(s, ANSI_CURSOR_LINE_COL, 1, 1, ANSI_DELETE_LINES_N, 1, + ANSI_CURSOR_LINE_COL, s->terminal_lines - 2, 1, + ANSI_INSERT_LINES_N, 1, ANSI_END)); if (pbuf_line != chat_pbuf) { if (!around_bar) @@ -99,7 +101,8 @@ chat_printf_line(struct session *s, short around_bar, } if (around_bar) - session_output_string(s, ansi(s, ANSI_RESTORE_SAVED_CURSOR, NULL)); + session_output_string(s, + ansi(s, ANSI_RESTORE_SAVED_CURSOR, ANSI_END)); else session_output(s, "\r\n", 2); @@ -120,12 +123,12 @@ chat_start(struct session *s, char *with_node) s->chatting_with_node[0] = '\0'; session_output_string(s, - ansi(s, ANSI_CURSOR_ROW_COL, s->terminal_rows, 1, NULL)); + ansi(s, ANSI_CURSOR_LINE_COL, s->terminal_lines, 1, ANSI_END)); chat_printf_line(s, 0, "%sWelcome to Multi-User Chat%s", - ansi(s, ANSI_BOLD, NULL), ansi(s, ANSI_RESET, NULL)); + ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); chat_printf_line(s, 0, "Type %s/help%s for available commands", - ansi(s, ANSI_BOLD, NULL), ansi(s, ANSI_RESET, NULL)); + ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); snprintf(chat_tbuf, sizeof(chat_tbuf), " [ %s ] [ Chatting with %s ]", @@ -142,14 +145,52 @@ chat_start(struct session *s, char *with_node) chat_broadcast(s, chat_tbuf); for (;;) { - session_output(s, "> ", 2); - input = session_field_input(s, s->terminal_cols - 3, 0); + input = session_field_input(s, MAX_CHAT_INPUT - 1, + s->terminal_columns - 1, 0); if (!input) break; - session_printf(s, "\r%s", ansi(s, ANSI_ERASE_LINE, NULL)); - snprintf(chat_tbuf, sizeof(chat_tbuf), "<%s> %s", - s->user ? s->user->username : "guest", input); + session_printf(s, "\r%s", ansi(s, ANSI_ERASE_LINE, ANSI_END)); + + if (input[0] == '/') { + if (strcmp(input, "/quit") == 0 || + strcmp(input, "/exit") == 0 || + strcmp(input, "/leave") == 0) { + free(input); + break; + } else if (strcmp(input, "/help") == 0) { + chat_help(s); + } else { + chat_printf_line(s, 1, "*** Unknown command: %s", + input + 1); + } + } else { + snprintf(chat_tbuf, sizeof(chat_tbuf), "<%s> %s", + s->user ? s->user->username : "guest", input); + chat_broadcast(s, chat_tbuf); + } + free(input); - chat_broadcast(s, chat_tbuf); } -} + + snprintf(chat_tbuf, sizeof(chat_tbuf), + "*** %s%s has left chat", + s->user ? s->user->username : "guest", + s->user && s->user->is_sysop ? " (sysop)" : ""); + chat_broadcast(s, chat_tbuf); + + s->chatting = 0; + memset(s->chatting_with_node, 0, sizeof(s->chatting_with_node)); + + session_output(s, "\r\n", 2); +} + +void +chat_help(struct session *s) +{ + chat_printf_line(s, 1, "*** Available chat commands:"); + chat_printf_line(s, 1, "*** %s/quit% : Leave chat", + ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); + chat_printf_line(s, 1, "*** %s/msg user message% : " + "Send \"message\" only to user \"user\"", + ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); +} --- console.c Thu Dec 23 20:47:51 2021 +++ console.c Wed Dec 29 20:45:19 2021 @@ -27,13 +27,15 @@ #define FONT_HEIGHT 11 #define CONSOLE_FORCE_REDRAW 1 -#define CONSOLE_CLEAR_SCREEN 2 - #define CONSOLE_PADDING 6 +#define CONSOLE_SMOOTH_SCROLLING 0 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 = { @@ -54,8 +56,8 @@ console_init(struct console **cur_console) console = xmalloczero(sizeof(struct console)); console->cur_console = cur_console; memset(console->chars, ' ', sizeof(console->chars)); - console->ncolumns = 80; - console->nlines = 24; + console->ncolumns = DEFAULT_TERMINAL_COLUMNS; + console->nlines = DEFAULT_TERMINAL_LINES; width = CONSOLE_PADDING + (FONT_WIDTH * console->ncolumns) + CONSOLE_PADDING; @@ -64,7 +66,7 @@ console_init(struct console **cur_console) bounds.left = (screenBits.bounds.right - width) / 2; bounds.right = bounds.left + width; - bounds.top = (GetMBarHeight()) + + bounds.top = MENUBAR_HEIGHT + ((screenBits.bounds.bottom - height) / 2); bounds.bottom = bounds.top + height; @@ -101,8 +103,8 @@ void console_setup(struct session *session) { struct console *console = (struct console *)session->cookie; - session->terminal_cols = console->ncolumns; - session->terminal_rows = console->nlines; + session->terminal_columns = console->ncolumns; + session->terminal_lines = console->nlines; } void @@ -227,16 +229,24 @@ console_mouse_down(struct console *console, EventRecor void console_key_down(struct console *console, EventRecord *event) { - char k; + unsigned char k; + short km, option_mask, control_mask; if (console->session->ibuflen >= nitems(console->session->ibuf)) return; console->session->last_input_at = Time; + k = (event->message & charCodeMask); - console->session->ibuf[console->session->ibuflen++] = k; - if (k == '\r') - console->session->ibuf[console->session->ibuflen++] = '\n'; + if (event->modifiers & optionKey) { + /* this is only correct on a US keyboard */ + if (k == 0xc2) /* opt+l */ + console_redraw(console, CONSOLE_FORCE_REDRAW); + } else { + console->session->ibuf[console->session->ibuflen++] = k; + if (k == '\r') + console->session->ibuf[console->session->ibuflen++] = '\n'; + } } short @@ -258,14 +268,11 @@ console_input(struct session *session) short console_bound(struct console *console) { - unsigned short shift_lines, shift_cols, chars_left, pxout; + unsigned short shift_lines, shift_cols, chars_left, pxout, + pxout_scroll; RgnHandle rgn; - Rect r; - short ret = 0; -#ifdef SMOOTH_SCROLLING - Rect r2; - short n; -#endif + Rect r, r2; + short ret = 0, n; while (console->cursor_column > console->ncolumns) { console->cursor_line++; @@ -283,20 +290,19 @@ console_bound(struct console *console) r.top = console->win->portRect.top + CONSOLE_PADDING; r.right = r.left + (console->ncolumns * FONT_WIDTH); r.bottom = r.top + ((console->nlines) * FONT_HEIGHT); -#ifdef SMOOTH_SCROLLING - for (n = 0; n < pxout; n++) { - ScrollRect(&r, 0, -1, rgn); + + 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 + 1; + r2.bottom = r.top + n; FillRect(&r2, white); + pxout_scroll -= n; } -#else - ScrollRect(&r, 0, -pxout, rgn); - r.top = r.bottom - 1; - r.bottom = r.top + pxout; - FillRect(&r, white); -#endif DisposeRgn(rgn); BlockMove(console->chars + shift_cols, console->chars, chars_left); @@ -311,77 +317,147 @@ console_bound(struct console *console) return ret; } +#define CONSOLE_ATTRS_DRAWABLE (ATTR_REVERSE | ATTR_BOLD | ATTR_DIRTY) + void console_redraw(struct console *console, short force) { - Rect cursor; - short n, nsize, cell, cbold = -1; + Rect chunk; + short n, nsize, cell, curbold = -1, line, off, c, firstdirty; + unsigned char curattr, a; - /* recursor */ - console->attrs[(console->cursor_line * console->ncolumns) + - console->cursor_column] |= (ATTR_CURSOR | ATTR_DIRTY); - - if (force == CONSOLE_CLEAR_SCREEN) { - cursor.left = console->win->portRect.left + CONSOLE_PADDING; - cursor.top = console->win->portRect.top + CONSOLE_PADDING; - cursor.right = cursor.left + (FONT_WIDTH * console->ncolumns); - cursor.bottom = cursor.top + (FONT_HEIGHT * console->nlines); - - FillRect(&cursor, console->cur_attr & ATTR_REVERSE ? black : white); - force = 0; - } - nsize = console->ncolumns * console->nlines; - SetCursor(*(GetCursor(watchCursor))); - - cursor.left = console->win->portRect.left + CONSOLE_PADDING; - cursor.top = console->win->portRect.top + CONSOLE_PADDING; - for (cell = 0; cell < nsize; cell++) { - if (cell) { - if (cell % console->ncolumns == 0) { - cursor.left = console->win->portRect.left + CONSOLE_PADDING; - cursor.top += FONT_HEIGHT; - } else { - cursor.left += FONT_WIDTH; + + 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; } - } - - if (!force && !(console->attrs[cell] & ATTR_DIRTY)) - continue; - cursor.right = cursor.left + FONT_WIDTH; - cursor.bottom = cursor.top + FONT_HEIGHT; - FillRect(&cursor, white); - - if (console->chars[cell] != ' ') { - MoveTo(cursor.left, cursor.top + FONT_HEIGHT - 2); + /* 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 (cbold != (console->attrs[cell] & ATTR_BOLD)) { - if (console->attrs[cell] & ATTR_BOLD) - TextFace(thePort->txFace | bold); - else - TextFace(thePort->txFace & ~bold); - - cbold = console->attrs[cell] & ATTR_BOLD; + 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); - DrawChar(console->chars[cell]); + if (curattr & ATTR_REVERSE) + InvertRect(&chunk); + + if (c != console->ncolumns - 1) { + if (a & ATTR_DIRTY) + firstdirty = c; + else + firstdirty = -1; + curattr = a; + } } - - if (console->attrs[cell] & ATTR_REVERSE) - InvertRect(&cursor); - if (console->attrs[cell] & ATTR_CURSOR) - InvertRect(&cursor); - - console->attrs[cell] &= ~ATTR_DIRTY; } + /* 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); +} + +void +console_erase_chars(struct console *console, short start, short count) +{ + Rect eraser; + short start_line, end_line, start_col, end_col, line; - SetCursor(&arrow); + 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; + + /* 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) + panic("TODO partial console_shift_chars"); + if (offset % console->ncolumns != 0) + panic("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)); + 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; @@ -389,7 +465,7 @@ console_parse_csi(struct console *console) short serviced = 0; short param1 = -1, param2 = -1; short parambuflen; - short start, count; + short start, count, offset; char parambuf[4]; unsigned char c; @@ -530,26 +606,20 @@ console_parse_csi(struct console *console) start = (console->cursor_line * console->ncolumns) + console->cursor_column; count = (console->ncolumns * console->nlines) - start; - memset(console->chars + start, ' ', count); - memset(console->attrs + start, console->cur_attr | ATTR_DIRTY, - count); + 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; - memset(console->chars, ' ', count); - memset(console->attrs, console->cur_attr | ATTR_DIRTY, count); + console_erase_chars(console, start, count); break; case 2: /* clear entire screen, mark everything clean */ + start = 0; count = console->ncolumns * console->nlines; - memset(console->chars, ' ', count); - memset(console->attrs, console->cur_attr | ATTR_DIRTY, count); - - /* and then quickly clear the screen */ - //console_bound(console); - //console_redraw(console, CONSOLE_CLEAR_SCREEN); + console_erase_chars(console, start, count); break; } break; @@ -560,25 +630,19 @@ console_parse_csi(struct console *console) start = (console->cursor_line * console->ncolumns) + console->cursor_column; count = console->ncolumns - console->cursor_column; - memset(console->chars + start, ' ', count); - memset(console->attrs + start, console->cur_attr | ATTR_DIRTY, - count); + 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; - memset(console->chars + start, ' ', count); - memset(console->attrs + start, console->cur_attr | ATTR_DIRTY, - count); + console_erase_chars(console, start, count); break; case 2: /* clear entire line */ start = (console->cursor_line * console->ncolumns); count = console->ncolumns; - memset(console->chars + start, ' ', count); - memset(console->attrs + start, console->cur_attr | ATTR_DIRTY, - count); + console_erase_chars(console, start, count); break; } break; @@ -587,53 +651,29 @@ console_parse_csi(struct console *console) if (param1 == 0) break; if (console->cursor_line != console->nlines - 1) { - /* move remaining lines down */ - count = console->ncolumns * - (console->nlines - console->cursor_line - param1); - memmove(console->chars + - ((console->cursor_line + param1) * console->ncolumns), - console->chars + - (console->cursor_line * console->ncolumns), - count); - memmove(console->attrs + - ((console->cursor_line + param1) * console->ncolumns), - console->attrs + - (console->cursor_line * console->ncolumns), - count); + /* 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; - memset(console->chars + start, ' ', count); - memset(console->attrs + start, console->cur_attr, count); - /* mark all of that dirty */ - count = start + (console->ncolumns * - (console->nlines - console->cursor_line)); - for (x = start; x < count; x++) - console->attrs[x] |= ATTR_DIRTY; + 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; - start = console->cursor_line * console->ncolumns; - count = console->ncolumns * - (console->nlines - console->cursor_line - param1); - memmove(console->chars + start, console->chars + start + - console->ncolumns, count); - memmove(console->attrs + start, console->attrs + start + - console->ncolumns, count); - /* fill in new blank lines at the end */ - start += count; - count = param1 * console->ncolumns; - memset(console->chars + start, ' ', count); - memset(console->attrs + start, console->cur_attr, count); - /* mark all of that dirty */ - start = console->cursor_line * console->ncolumns; - count = start + (console->ncolumns * - (console->nlines - console->cursor_line)); - for (x = start; x < count; x++) - console->attrs[x] |= ATTR_DIRTY; + 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 */ --- console.h Wed Dec 15 09:24:45 2021 +++ console.h Tue Dec 28 17:45:29 2021 @@ -26,9 +26,9 @@ struct console { ControlHandle scroller; short nlines; short ncolumns; - char chars[80 * 24]; - char attrs[80 * 24]; - char cur_attr; + char chars[DEFAULT_TERMINAL_COLUMNS * DEFAULT_TERMINAL_LINES]; + unsigned char attrs[DEFAULT_TERMINAL_COLUMNS * DEFAULT_TERMINAL_LINES]; + unsigned char cur_attr; #define ATTR_BOLD (1 << 0) #define ATTR_REVERSE (1 << 1) #define ATTR_UNDERLINE (1 << 2) --- db.h Mon Dec 6 10:35:10 2021 +++ db.h Wed Dec 29 16:09:47 2021 @@ -49,9 +49,11 @@ struct config { short telnet_port; }; +#define DB_USERNAME_SIZE 16 + struct user { short id; - char username[32]; + char username[DB_USERNAME_SIZE]; char password_hash[SHA256_DIGEST_STRING_LENGTH + 1]; /* 66 */ char password_salt[SHA256_DIGEST_STRING_LENGTH + 1]; unsigned long created_at; --- main.c Sun Dec 12 13:07:43 2021 +++ main.c Tue Dec 28 22:03:18 2021 @@ -89,6 +89,8 @@ main(void) if (db->config.telnet_port) telnet_init(); + console_init(&cur_console); + iters = 0; while (!quitting) { if (db->config.telnet_port) @@ -96,17 +98,10 @@ main(void) uthread_coordinate(); - if (++iters % 10 == 0) { - WaitNextEvent(everyEvent, &event, 1, 0); - iters = 0; - } else if (!GetNextEvent(everyEvent, &event)) + if (!GetNextEvent(everyEvent, &event)) continue; switch (event.what) { - case nullEvent: - if (cur_console == NULL) - console_init(&cur_console); - break; case keyDown: case autoKey: key = event.message & charCodeMask; @@ -129,9 +124,8 @@ main(void) DragWindow(event_win, event.where, &screenBits.bounds); break; case inGoAway: - if (TrackGoAway(event_win, event.where)) { - /* TODO: close */ - } + if (TrackGoAway(event_win, event.where) && cur_console) + cur_console->session->ending = 1; break; case inContent: if (event_win != FrontWindow()) --- session.c Thu Dec 23 15:55:44 2021 +++ session.c Wed Dec 29 20:50:48 2021 @@ -70,13 +70,13 @@ session_run(struct uthread *uthread, void *arg) Handle h; char date[9]; struct tm *date_tm; - char *view, done = 0; - unsigned char c; + char *view; + unsigned char c, done = 0; size_t len; /* until we negotiate otherwise */ - s->terminal_cols = 80; - s->terminal_rows = 24; + s->terminal_columns = DEFAULT_TERMINAL_COLUMNS; + s->terminal_lines = DEFAULT_TERMINAL_LINES; snprintf(s->terminal_type, sizeof(s->terminal_type), "xterm-color"); if (s->node_funcs->setup) @@ -92,14 +92,7 @@ session_run(struct uthread *uthread, void *arg) "\r\n", db->config.name, s->node); } - if (s->autologin_username[0]) { - s->user = user_find(db, s->autologin_username); - if (!s->user) { - session_output_string(s, "Failed to find autologin user\r\n"); - session_close(&s); - return; - } - } else if (session_login(s) != AUTH_USER_OK) { + if (session_login(s) != AUTH_USER_OK) { session_close(&s); return; } @@ -118,20 +111,28 @@ session_run(struct uthread *uthread, void *arg) } sessions_tally++; - session_printf(s, "Welcome, %s%s%s, you are the %d%s caller today.\r\n", - ansi(s, ANSI_BOLD, NULL), + session_printf(s, "\r\n" + "Welcome, %s%s%s, you are the %d%s caller today.\r\n", + ansi(s, ANSI_BOLD, ANSI_END), s->user ? s->user->username : GUEST_USERNAME, - ansi(s, ANSI_RESET, NULL), + ansi(s, ANSI_RESET, ANSI_END), sessions_tally, ordinal(sessions_tally)); - session_output_string(s, "\r\n[ menu goes here ]\r\n\r\n"); + len = session_load_view(s, DB_TEXT_MENU_ID, &view); + if (len) { + session_output(s, view, len); + free(view); + } else + session_output_string(s, "\r\n[ Menu missing! ]\r\n\r\n"); - while (!done) { + while (!done && !s->ending) { session_output_string(s, "Main Menu> "); get_another_char: c = session_input_char(s); - if (c == '\r') + if (s->ending) + break; + if (c == '\r' || c == 0) goto get_another_char; session_printf(s, "%c\r\n", c); @@ -164,30 +165,37 @@ get_another_char: void session_close(struct session **session) { - struct session **newsessions = NULL; + struct session **newsessions; unsigned long now; - short onsessions, n; + short newnsessions, n; - /* give 1 second to flush output */ - now = Ticks; - while ((*session)->obuflen && (Ticks - now) < 60) { - (*session)->node_funcs->output(*session); - uthread_yield(); + if (!(*session)->ending) { + /* give 1 second to flush output */ + now = Ticks; + while ((*session)->obuflen && (Ticks - now) < 60) { + (*session)->node_funcs->output(*session); + uthread_yield(); + } + + (*session)->ending = 1; } /* close the node */ (*session)->node_funcs->close(*session); /* remove session from sessions */ - onsessions = nsessions - 1; - if (onsessions) { - newsessions = xmallocarray(onsessions, sizeof(struct session)); + newnsessions = nsessions - 1; + if (newnsessions) { + newsessions = xmallocarray(newnsessions, sizeof(struct session)); nsessions = 0; - for (n = 0; n < onsessions; n++) { + for (n = 0; n < newnsessions; n++) { if (sessions[n] == *session) continue; newsessions[nsessions++] = sessions[n]; } + } else { + nsessions = 0; + newsessions = NULL; } free(sessions); sessions = newsessions; @@ -215,11 +223,13 @@ session_output(struct session *session, const char *st { size_t chunk, olen = len, stroff = 0; - while (len) { + while (len && !session->ending) { chunk = (sizeof(session->obuf) - session->obuflen); if (chunk == 0) { /* wait until output buffer clears */ uthread_yield(); + if (session->ending) + return 0; continue; } if (chunk > len) @@ -254,6 +264,8 @@ wait_for_char: while (session->ibuflen == 0) { session->node_funcs->input(session); uthread_yield(); + if (session->ending) + return 0; if (session->ibuflen != 0) break; if (session->obuflen != 0) @@ -279,26 +291,35 @@ wait_for_char: return ret; } +/* + * Collect up to size-1 bytes of input with trailing null, in a field + * width columns wide, optionally masking echoed output with mask char. + */ char * -session_field_input(struct session *session, unsigned short len, char mask) +session_field_input(struct session *session, unsigned short size, + unsigned short width, char mask) { short ilen = 0, ipos = 0, lastlen = 0; char *field; unsigned char c, redraw = 0; - field = xmalloczero(len); + field = xmalloczero(size); for (;;) { c = session_input_char(session); + if (session->ending) + goto field_input_bail; switch (c) { case 0: /* session_input_char bailed */ if (session->obuflen > 0) { session->node_funcs->output(session); uthread_yield(); + if (session->ending) + goto field_input_bail; } break; case 8: /* backspace / ^H */ - case 127: /* delete, backspace through telnet */ + case 127: /* delete (through telnet) */ if (ipos == 0) continue; @@ -317,7 +338,7 @@ session_field_input(struct session *session, unsigned /* TODO */ } else session_output_string(session, - ansi(session, ANSI_BACKSPACE)); + ansi(session, ANSI_BACKSPACE, ANSI_END)); break; case '\r': @@ -326,7 +347,7 @@ session_field_input(struct session *session, unsigned default: if (c < 32 || c > 127) break; - if (ilen >= len) + if (ilen >= size - 1) break; if (ipos <= ilen) /* TODO: move */ @@ -339,7 +360,8 @@ session_field_input(struct session *session, unsigned session_output(session, mask ? &mask : (char *)&c, 1); } } - + +field_input_bail: free(field); return NULL; } @@ -349,22 +371,35 @@ session_login(struct session *s) { struct user *user; char junk[SHA256_DIGEST_STRING_LENGTH]; - char *username, *password; + char *username = NULL, *password = NULL; size_t len; short n; for (n = 1; n <= 3; n++) { session_output_string(s, "login: "); - username = session_field_input(s, 32, 0); - session_output(s, "\r\n", 2); - - if (username[0] == '\0') { - n--; - continue; + + if (s->autologin_username[0]) { + session_printf(s, "%s\r\n", s->autologin_username); + username = xstrdup(s->autologin_username); + } else { + username = session_field_input(s, DB_USERNAME_SIZE, + DB_USERNAME_SIZE - 1, 0); + session_output(s, "\r\n", 2); + + if (username == NULL || s->ending) + goto login_bail; + + if (username[0] == '\0') { + n--; + free(username); + continue; + } } - if (strcmp(username, GUEST_USERNAME) == 0) + if (strcmp(username, GUEST_USERNAME) == 0) { + free(username); return AUTH_USER_OK; + } if (strcmp(username, "signup") == 0 || strcmp(username, "new") == 0) { @@ -373,10 +408,19 @@ session_login(struct session *s) user = user_find(db, username); } + if (s->autologin_username[0] && user) { + free(username); + s->user = user; + return AUTH_USER_OK; + } + session_output_string(s, "Password: "); - password = session_field_input(s, 64, '*'); + password = session_field_input(s, 64, 64, '*'); session_output(s, "\r\n", 2); - + + if (password == NULL || s->ending) + goto login_bail; + if (user) { if (user_authenticate(db, user, password) == AUTH_USER_OK) s->user = user; @@ -387,10 +431,12 @@ session_login(struct session *s) len = strlen(username); memset(username, 0, len); free(username); + username = NULL; len = strlen(password); memset(password, 0, len); free(password); + password = NULL; if (s->user) return AUTH_USER_OK; @@ -399,6 +445,11 @@ session_login(struct session *s) session_output_string(s, "Login incorrect\r\n"); } +login_bail: + if (username) + free(username); + if (password) + free(password); session_output_string(s, "Thanks for playing\r\n"); return AUTH_USER_FAILED; } @@ -419,7 +470,7 @@ session_bar(struct session *s, char *left_str, char *r short sstrlen, sidelen, bold, n, nside, barlen, pad; memset(bar, 0, sizeof(bar)); - barlen = sprintf(bar, "\r%s", ansi(s, ANSI_REVERSE, NULL)); + barlen = sprintf(bar, "\r%s", ansi(s, ANSI_REVERSE, ANSI_END)); for (nside = 0; nside < 2; nside++) { if (nside == 0) @@ -441,10 +492,10 @@ session_bar(struct session *s, char *left_str, char *r if (str[n] == '\b') { if (bold) sidelen = strlcat(side, - ansi(s, ANSI_RESET, ANSI_REVERSE, NULL), + ansi(s, ANSI_RESET, ANSI_REVERSE, ANSI_END), sizeof(sides[0])); else - sidelen = strlcat(side, ansi(s, ANSI_BOLD, NULL), + sidelen = strlcat(side, ansi(s, ANSI_BOLD, ANSI_END), sizeof(sides[0])); bold = !bold; @@ -459,7 +510,7 @@ session_bar(struct session *s, char *left_str, char *r side[sidelen] = '\0'; } - pad = s->terminal_cols; + pad = s->terminal_columns; if (left_str) pad -= ansi_strip(sides[0], NULL); if (right_str) @@ -471,7 +522,7 @@ session_bar(struct session *s, char *left_str, char *r bar[barlen++] = ' '; barlen = strlcat(bar, sides[1], sizeof(bar)); - barlen = strlcat(bar, ansi(s, ANSI_RESET, NULL), sizeof(bar)); + barlen = strlcat(bar, ansi(s, ANSI_RESET, ANSI_END), sizeof(bar)); if (barlen > sizeof(bar)) panic("session_bar: bar overflow!"); @@ -483,10 +534,11 @@ size_t session_load_view(struct session *session, short id, char **ret) { char curvar[64]; + struct tm *now; size_t retsize = 1024, retpos = 0; - size_t hsize, len; + size_t hsize, vallen; Handle h; - short n, invar = 0, varlen = 0; + short n, j, invar = 0, varlen = 0, valsize, count, pad; char c; h = Get1Resource(DB_TEXT_TYPE, id); @@ -503,19 +555,57 @@ session_load_view(struct session *session, short id, c if (c == '%' && (*h)[n + 1] == '%') { n++; - if (invar) { - invar = 0; - curvar[varlen] = '\0'; - if (strcmp(curvar, "NODE") == 0) { - len = strlcpy(curvar, session->node, sizeof(curvar)); - EXPAND_TO_FIT(*ret, retsize, retpos, len); - memcpy(*ret + retpos, curvar, len); - retpos += len; - } - } else { + if (!invar) { invar = 1; varlen = 0; + continue; } + + invar = 0; + count = 0; + pad = 0; + vallen = 0; + curvar[varlen] = '\0'; + + if (sscanf(curvar, "%d-%n", &valsize, &count) == 1 && + count > 0) { + /* field of fixed length, either truncated or padded */ + if (valsize > sizeof(curvar)) + valsize = sizeof(curvar); + /* 32-USERNAME -> USERNAME */ + memmove(curvar, curvar + count, valsize - count); + pad = 1; + } else { + valsize = sizeof(curvar); + } + + if (strcmp(curvar, "NODE") == 0) { + vallen = strlcpy(curvar, session->node, valsize); + } else if (strcmp(curvar, "USERNAME") == 0) { + vallen = strlcpy(curvar, session->user ? + session->user->username : GUEST_USERNAME, + valsize); + } else if (strcmp(curvar, "TIME") == 0) { + now = localtime((time_t *)&Time); + vallen = strftime(curvar, valsize, "%H:%M", now); + } else if (strcmp(curvar, "ANSI_BOLD") == 0) { + vallen = strlcpy(curvar, + ansi(session, ANSI_BOLD, ANSI_END), valsize); + } else if (strcmp(curvar, "ANSI_RESET") == 0) { + vallen = strlcpy(curvar, + ansi(session, ANSI_RESET, ANSI_END), valsize); + } + + if (vallen) { + if (pad && vallen < valsize) { + while (vallen < valsize) + curvar[vallen++] = ' '; + curvar[vallen] = '\0'; + } + EXPAND_TO_FIT(*ret, retsize, retpos, vallen); + memcpy(*ret + retpos, curvar, vallen); + retpos += vallen; + } } else if (invar) { if (varlen < sizeof(curvar) - 2) curvar[varlen++] = c; @@ -538,8 +628,8 @@ session_pause_return(struct session *s, short enforce, unsigned char c; session_printf(s, "%sPress %s<Enter>%s ", - ansi(s, ANSI_RESET, NULL), ansi(s, ANSI_BOLD, NULL), - ansi(s, ANSI_RESET, NULL)); + ansi(s, ANSI_RESET, ANSI_END), ansi(s, ANSI_BOLD, ANSI_END), + ansi(s, ANSI_RESET, ANSI_END)); if (msg) session_output_string(s, msg); else @@ -547,6 +637,8 @@ session_pause_return(struct session *s, short enforce, for (;;) { c = session_input_char(s); + if (c == 0) + continue; if (!enforce || c == '\r') break; } @@ -562,11 +654,11 @@ session_who(struct session *s) session_printf(s, "%sWho's Online%s\r\n", - ansi(s, ANSI_BOLD, NULL), ansi(s, ANSI_RESET, NULL)); + ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); session_printf(s, "%sNode User Via Speed Idle%s\r\n", - ansi(s, ANSI_BOLD, NULL), ansi(s, ANSI_RESET, NULL)); + ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); for (n = 0; n < nsessions; n++) { idle = Time - sessions[n]->last_input_at; @@ -581,7 +673,7 @@ session_who(struct session *s) session_printf(s, "%-7s %-20s %-7s %-6d %-6s\r\n", sessions[n]->node, - sessions[n]->user ? sessions[n]->user->username : "guest", + sessions[n]->user ? sessions[n]->user->username : GUEST_USERNAME, sessions[n]->via, sessions[n]->tspeed, idle_s); --- session.h Thu Dec 23 14:26:18 2021 +++ session.h Wed Dec 29 08:56:08 2021 @@ -19,6 +19,7 @@ #include <stdlib.h> +#include "db.h" #include "uthread.h" enum session_input_state { @@ -27,8 +28,11 @@ enum session_input_state { SESSION_INPUT_CHAR }; -#define GUEST_USERNAME "guest" +#define DEFAULT_TERMINAL_COLUMNS 80 +#define DEFAULT_TERMINAL_LINES 24 +#define GUEST_USERNAME "guest" + struct node_funcs { void (*setup)(struct session *session); short (*input)(struct session *session); @@ -37,7 +41,7 @@ struct node_funcs { }; struct session_log { - char username[32]; + char username[DB_USERNAME_SIZE]; char node[10]; unsigned long logged_on_at; unsigned long logged_off_at; @@ -45,6 +49,7 @@ struct session_log { }; struct session { + short ending; char node[10]; char via[10]; unsigned char obuf[256]; /* telnet.obuf must be double this */ @@ -54,8 +59,8 @@ struct session { enum session_input_state input_state; unsigned char last_input; unsigned long last_input_at; - unsigned short terminal_cols; - unsigned short terminal_rows; + unsigned short terminal_columns; + unsigned short terminal_lines; char terminal_type[20]; unsigned short tspeed; unsigned char vt100; @@ -84,8 +89,8 @@ char session_input_char(struct session *session); size_t session_printf(struct session *session, const char *format, ...); size_t session_output(struct session *session, const char *str, size_t len); size_t session_output_string(struct session *session, const char *str); -char *session_field_input(struct session *session, - unsigned short len, char mask); +char *session_field_input(struct session *session, unsigned short size, + unsigned short width, char mask); char *session_bar(struct session *s, char *left_str, char *right_str); size_t session_load_view(struct session *session, short id, char **ret); void session_pause_return(struct session *s, short enforce, char *msg); --- subtext.π.r Sun Dec 5 16:19:28 2021 +++ subtext.π.r Wed Dec 29 16:14:01 2021 @@ -59,11 +59,16 @@ data 'TMPL' (128, "STCF") { data 'TMPL' (129, "STUS") { $"0269 6444 5752 4408 7573 6572 6E61 6D65" /* .idDWRD.username */ - $"4330 3230 0D70 6173 7377 6F72 645F 6861" /* C020¬password_ha */ + $"4330 3130 0D70 6173 7377 6F72 645F 6861" /* C010¬password_ha */ $"7368 4330 3432 0D70 6173 7377 6F72 645F" /* shC042¬password_ */ $"7361 6C74 4330 3432 0A63 7265 6174 6564" /* saltC042.created */ $"5F61 7444 4C4E 470C 6C61 7374 5F73 6565" /* _atDLNG.last_see */ $"6E5F 6174 444C 4E47 0869 735F 7379 736F" /* n_atDLNG.is_syso */ $"7042 4F4F 4C" /* pBOOL */ +}; + +data 'STR ' (128, "STR_LAST_DB") { + $"1B4D 6163 696E 746F 7368 3A6B 6C75 6467" /* .Macintosh:kludg */ + $"653A 6B6C 7564 6765 2E62 6273" /* e:kludge.bbs */ }; --- telnet.c Thu Dec 23 14:52:10 2021 +++ telnet.c Tue Dec 28 22:01:27 2021 @@ -225,6 +225,8 @@ telnet_idle(void) case TELNET_PB_STATE_CONNECTED: if (node->session) { telnet_output(node->session); + if (node->session->ending) + break; telnet_input(node->session); } break; @@ -257,6 +259,9 @@ telnet_input(struct session *session) unsigned char c; char iac_out[8] = { IAC, 0 }; + if (session->ending) + return 0; + if (session->ibuflen >= sizeof(session->ibuf)) return 0; @@ -268,7 +273,7 @@ telnet_input(struct session *session) nil, false); if (error || telnet_status_pb.connectionState != ConnectionStateEstablished) { - telnet_close(session); + session->ending = 1; return 0; } @@ -281,9 +286,11 @@ telnet_input(struct session *session) error = _TCPRcv(&node->pb, node->stream, (Ptr)(node->ibuf), &rlen, nil, nil, false); - if (error) - /* TODO: make this not fatal */ - panic("TCPRecv[%d] failed: %d", node->id, error); + if (error) { + warn("TCPRecv[%d] failed: %d", node->id, error); + session->ending = 1; + return 0; + } session->last_input_at = Time; @@ -348,17 +355,17 @@ telnet_input(struct session *session) if (node->iac_sb[sboff] == 255) sboff++; - session->terminal_cols = (node->iac_sb[sboff++] << 8); + session->terminal_columns = (node->iac_sb[sboff++] << 8); if (node->iac_sb[sboff] == 255) sboff++; - session->terminal_cols |= node->iac_sb[sboff++]; + session->terminal_columns |= node->iac_sb[sboff++]; if (node->iac_sb[sboff] == 255) sboff++; - session->terminal_rows = (node->iac_sb[sboff++] << 8); + session->terminal_lines = (node->iac_sb[sboff++] << 8); if (node->iac_sb[sboff] == 255) sboff++; - session->terminal_rows |= node->iac_sb[sboff++]; + session->terminal_lines |= node->iac_sb[sboff++]; break; case IAC_TTYPE: /* IAC SB TTYPE IS XTERM IAC SE */ @@ -465,7 +472,7 @@ telnet_output(struct session *session) short n, error; unsigned char c; - if (session->obuflen == 0) + if (session->obuflen == 0 || session->ending) return 0; if (node->pb.ioResult > 0) @@ -512,9 +519,10 @@ telnet_output(struct session *session) error = _TCPSend(&node->pb, node->stream, node->tcp_wds, nil, nil, true); - if (error) - /* TODO: make this not fatal */ - panic("TCPSend[%d] failed: %d", node->id, error); + if (error) { + warn("TCPSend[%d] failed: %d", node->id, error); + session->ending = 1; + } return 0; } @@ -525,14 +533,17 @@ telnet_output_iac(struct session *session, char *iacs, struct telnet_node *node = (struct telnet_node *)session->cookie; /* flush output buffer */ - while (session->obuflen) + while (session->obuflen && !session->ending) telnet_output(session); + if (session->ending) + return; + node->sending_iac = 1; session_output(session, iacs, len); - while (session->obuflen) + while (session->obuflen && !session->ending) telnet_output(session); node->sending_iac = 0; --- util.h Thu Dec 23 15:39:11 2021 +++ util.h Sat Dec 25 21:21:12 2021 @@ -33,6 +33,9 @@ #define SCROLLBAR_WIDTH 16 +/* GetMBarHeight() is not very useful */ +#define MENUBAR_HEIGHT 20 + #define MAX_TEXTEDIT_SIZE 32767L #ifndef bool