jcs
/subtext
/amendments
/38
chat: Start on multi-user chat
jcs made amendment 38 over 2 years ago
--- chat.c Thu Dec 23 21:00:16 2021
+++ chat.c Thu Dec 23 21:00:16 2021
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * 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 <time.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ansi.h"
+#include "chat.h"
+#include "db.h"
+#include "session.h"
+#include "util.h"
+
+#define MAX_CHAT_INPUT 100
+
+static char chat_tbuf[256];
+
+void
+chat_broadcast(struct session *s, char *str)
+{
+ short n;
+
+ for (n = 0; n < nsessions; n++) {
+ if (!sessions[n]->chatting)
+ continue;
+ if (strcmp((char *)s->chatting_with_node,
+ (char *)sessions[n]->chatting_with_node) != 0)
+ continue;
+
+ chat_printf_line(sessions[n], 1, "%s", str);
+ }
+}
+
+size_t
+chat_printf_line(struct session *s, short around_bar, char *format, ...)
+{
+ static char chat_pbuf[256];
+ char *pbuf_line;
+ va_list ap;
+ size_t len, llen, max_llen;
+ struct tm *tm;
+ short i;
+
+ tm = localtime((time_t *)&Time);
+ len = strftime(chat_pbuf, sizeof(chat_pbuf), "[%H:%M] ", tm);
+
+ va_start(ap, format);
+ len += vsnprintf(chat_pbuf + len, sizeof(chat_pbuf) - len, format, ap);
+ va_end(ap);
+
+ pbuf_line = chat_pbuf;
+ max_llen = s->terminal_cols;
+
+ if (around_bar)
+ session_output_string(s, ansi(s, ANSI_SAVE_CURSOR, NULL));
+
+ while (len > 0) {
+ if (len > max_llen) {
+ /* chop at last word boundary */
+ llen = max_llen;
+ while (llen >= 0 && pbuf_line[llen] != ' ')
+ llen--;
+ if (llen == -1)
+ /* no words to break on, hard break */
+ llen = max_llen;
+ } else {
+ llen = len;
+ }
+
+ 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));
+
+ if (pbuf_line != chat_pbuf) {
+ if (!around_bar)
+ session_output(s, "\r\n", 2);
+ session_output(s, " ", 3);
+ }
+ session_output(s, pbuf_line, llen);
+
+ pbuf_line += llen;
+ len -= llen;
+ }
+
+ if (around_bar)
+ session_output_string(s, ansi(s, ANSI_RESTORE_SAVED_CURSOR, NULL));
+ else
+ session_output(s, "\r\n", 2);
+
+ return 1; /* XXX */
+}
+
+void
+chat_start(struct session *s, char *with_node)
+{
+ char *input;
+ short len, alen;
+
+ s->chatting = 1;
+ if (with_node[0])
+ strlcpy((char *)s->chatting_with_node, with_node,
+ sizeof(s->chatting_with_node));
+ else
+ s->chatting_with_node[0] = '\0';
+
+ session_output_string(s,
+ ansi(s, ANSI_CURSOR_ROW_COL, s->terminal_rows, 1, NULL));
+
+ chat_printf_line(s, 0, "%sWelcome to Multi-User Chat%s",
+ ansi(s, ANSI_BOLD, NULL), ansi(s, ANSI_RESET, NULL));
+ chat_printf_line(s, 0, "Type %s/help%s for available commands",
+ ansi(s, ANSI_BOLD, NULL), ansi(s, ANSI_RESET, NULL));
+
+ snprintf(chat_tbuf, sizeof(chat_tbuf),
+ " [ %s ] [ Chatting with %s ]",
+ s->user ? s->user->username : "guest",
+ s->chatting_with_node[0] ? "..." : "everyone");
+ session_printf(s, "%s\r\n", session_bar(s, chat_tbuf, NULL));
+
+ /* TODO: print other users in chat */
+
+ snprintf(chat_tbuf, sizeof(chat_tbuf),
+ "*** %s%s has joined chat",
+ s->user ? s->user->username : "guest",
+ s->user && s->user->is_sysop ? " (sysop)" : "");
+ chat_broadcast(s, chat_tbuf);
+
+ for (;;) {
+ session_output(s, "> ", 2);
+ input = session_field_input(s, s->terminal_cols - 3, 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);
+ free(input);
+ chat_broadcast(s, chat_tbuf);
+ }
+}
--- chat.h Thu Dec 23 14:12:24 2021
+++ chat.h Thu Dec 23 14:12:24 2021
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * 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.
+ */
+
+#ifndef __CHAT_H__
+#define __CHAT_H__
+
+#include <stddef.h>
+#include "session.h"
+
+void chat_broadcast(struct session *s, char *str);
+size_t chat_printf_line(struct session *s, short around_bar,
+ char *format, ...);
+void chat_start(struct session *s, char *with_node);
+
+#endif
--- session.c Wed Dec 15 15:41:30 2021
+++ session.c Thu Dec 23 15:55:44 2021
@@ -20,6 +20,7 @@
#include <string.h>
#include "ansi.h"
+#include "chat.h"
#include "subtext.h"
#include "session.h"
#include "user.h"
@@ -30,7 +31,6 @@
struct session **sessions = NULL;
short nsessions = 0;
-char session_tbuf[512];
unsigned short sessions_tally = 0;
char sessions_tally_day[9] = { 0 };
@@ -50,7 +50,6 @@ session_create(char *node, char *via, struct node_func
return NULL;
}
- session->state = SESSION_STATE_INIT;
session->node_funcs = node_funcs;
strlcpy(session->node, node, sizeof(session->node));
strlcpy(session->log.node, node, sizeof(session->log.node));
@@ -78,7 +77,7 @@ session_run(struct uthread *uthread, void *arg)
/* until we negotiate otherwise */
s->terminal_cols = 80;
s->terminal_rows = 24;
- sprintf(s->terminal_type, "xterm-color");
+ snprintf(s->terminal_type, sizeof(s->terminal_type), "xterm-color");
if (s->node_funcs->setup)
s->node_funcs->setup(s);
@@ -93,7 +92,7 @@ session_run(struct uthread *uthread, void *arg)
"\r\n", db->config.name, s->node);
}
- if (s->autologin_username) {
+ 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");
@@ -130,11 +129,19 @@ session_run(struct uthread *uthread, void *arg)
while (!done) {
session_output_string(s, "Main Menu> ");
+get_another_char:
c = session_input_char(s);
+ if (c == '\r')
+ goto get_another_char;
+
session_printf(s, "%c\r\n", c);
/* TODO: make letter->command dynamic from a resource */
switch (c) {
+ case 'c':
+ /* chat */
+ chat_start(s, NULL);
+ break;
case 'g':
case 'G':
/* goodbye */
@@ -145,8 +152,6 @@ session_run(struct uthread *uthread, void *arg)
/* who's online */
session_who(s);
break;
- case '\r':
- break;
default:
session_output_string(s, "Invalid option\r\n");
break;
@@ -194,14 +199,13 @@ session_close(struct session **session)
size_t
session_printf(struct session *session, const char *format, ...)
{
+ static char session_tbuf[512];
va_list ap;
size_t len;
-
+
va_start(ap, format);
- len = vsprintf(session_tbuf, format, ap);
+ len = vsnprintf(session_tbuf, sizeof(session_tbuf), format, ap);
va_end(ap);
- if (len >= sizeof(session_tbuf))
- panic("sprintf overflow in session_output!");
return session_output(session, session_tbuf, len);
}
@@ -250,6 +254,10 @@ wait_for_char:
while (session->ibuflen == 0) {
session->node_funcs->input(session);
uthread_yield();
+ if (session->ibuflen != 0)
+ break;
+ if (session->obuflen != 0)
+ return 0;
}
ret = session->ibuf[0];
@@ -260,7 +268,7 @@ wait_for_char:
session->ibuflen--;
}
- if (session->last_input == '\r' && ret == '\n') {
+ if (session->last_input == '\r' && (ret == '\n' || ret == 0)) {
/* we already responded to the \r, just ignore this */
session->last_input = ret;
goto wait_for_char;
@@ -283,6 +291,12 @@ session_field_input(struct session *session, unsigned
for (;;) {
c = session_input_char(session);
switch (c) {
+ case 0: /* session_input_char bailed */
+ if (session->obuflen > 0) {
+ session->node_funcs->output(session);
+ uthread_yield();
+ }
+ break;
case 8: /* backspace / ^H */
case 127: /* delete, backspace through telnet */
if (ipos == 0)
@@ -302,8 +316,8 @@ session_field_input(struct session *session, unsigned
if (redraw) {
/* TODO */
} else
- /* back one, space, back one (\33 oct = \e) */
- session_output(session, "\33[D \33[D", 7);
+ session_output_string(session,
+ ansi(session, ANSI_BACKSPACE));
break;
case '\r':
@@ -439,7 +453,7 @@ session_bar(struct session *s, char *left_str, char *r
}
if (sidelen >= sizeof(sides[0]))
- panic("session_bar: side %d overflow!", nside);
+ break;
}
side[sidelen] = '\0';
--- session.h Wed Dec 15 15:14:32 2021
+++ session.h Thu Dec 23 14:26:18 2021
@@ -21,12 +21,6 @@
#include "uthread.h"
-enum session_state {
- SESSION_STATE_INIT,
- SESSION_STATE_LOGIN,
- SESSION_STATE_WELCOME
-};
-
enum session_input_state {
SESSION_INPUT_NONE,
SESSION_INPUT_LINE,
@@ -57,7 +51,6 @@ struct session {
unsigned char ibuf[64];
short obuflen;
short ibuflen;
- enum session_state state;
enum session_input_state input_state;
unsigned char last_input;
unsigned long last_input_at;
@@ -65,9 +58,13 @@ struct session {
unsigned short terminal_rows;
char terminal_type[20];
unsigned short tspeed;
- unsigned char ansi;
+ unsigned char vt100;
+ unsigned char vt52;
unsigned char color;
unsigned char cp437;
+ unsigned char chatting;
+ unsigned char chatting_with_node[10];
+ unsigned char chat_ring_idx;
struct session_log log;
char autologin_username[32];
struct user *user;
--- telnet.c Wed Dec 15 15:22:52 2021
+++ telnet.c Thu Dec 23 14:52:10 2021
@@ -366,6 +366,9 @@ telnet_input(struct session *session)
strlcpy(session->terminal_type,
(char *)&node->iac_sb + 2,
sizeof(session->terminal_type));
+
+ /* TODO: compare terminal type to list */
+ session->vt100 = 1;
break;
}
node->iac_state = TELNET_IAC_STATE_IDLE;
--- util.h Tue Dec 14 14:55:01 2021
+++ util.h Thu Dec 23 15:39:11 2021
@@ -25,11 +25,15 @@
#define SIZE_MAX ULONG_MAX
#endif
+#define nitems(what) (sizeof((what)) / sizeof((what)[0]))
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define BOUND(a, min, max) ((a) > (max) ? (max) : ((a) < (min) ? (min) : (a)))
+
#define SCROLLBAR_WIDTH 16
#define MAX_TEXTEDIT_SIZE 32767L
-
-#define nitems(what) (sizeof((what)) / sizeof((what)[0]))
#ifndef bool
typedef Boolean bool;