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