jcs
/wallops
/amendments
/2
chatter+irc: Lots of progress on protocol parsing and display
jcs made amendment 2 over 2 years ago
--- chatter.c Sun Jan 30 13:44:20 2022
+++ chatter.c Tue Feb 1 13:02:04 2022
@@ -21,12 +21,12 @@
#include "irc.h"
#include "util.h"
-#define NICK_LIST_WIDTH 100
+#define NICK_LIST_WIDTH 75
-void chatter_update_titlebar(struct chatter *chatter);
void chatter_layout(struct chatter *chatter, bool init);
void chatter_key_down(struct focusable *focusable, EventRecord *event);
void chatter_mouse_down(struct focusable *focusable, EventRecord *event);
+void chatter_menu(struct focusable *focusable, short menu, short item);
void chatter_idle(struct focusable *focusable, EventRecord *event);
void chatter_update(struct focusable *focusable, EventRecord *event);
void chatter_close(struct focusable *focusable, EventRecord *event);
@@ -34,7 +34,7 @@ void chatter_atexit(struct focusable *focusable);
void
chatter_init(const char *server, const unsigned short port,
- const char *nick)
+ const char *nick, const char *realname)
{
struct focusable *focusable;
struct chatter *chatter;
@@ -42,7 +42,6 @@ chatter_init(const char *server, const unsigned short
Rect bounds = { 0 }, control_bounds = { 0 };
Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
Point cell_size = { 0 };
- Cell cell = { 0 };
if (_TCPInit() != 0)
panic("TCPInit failed");
@@ -52,6 +51,7 @@ chatter_init(const char *server, const unsigned short
chatter->irc_hostname = xstrdup(server);
chatter->irc_port = port;
chatter->irc_nick = xstrdup(nick);
+ chatter->irc_realname = xstrdup(realname);
bounds.left = 10;
bounds.top = screenBits.bounds.top + 10 + (GetMBarHeight() * 2) - 1;
@@ -79,16 +79,15 @@ chatter_init(const char *server, const unsigned short
chatter->input_te = TENew(&bounds, &bounds);
/* nick list */
+ bounds.right = chatter->win->portRect.right + 1;
+ bounds.left = bounds.right - NICK_LIST_WIDTH;
chatter->nick_list = LNew(&bounds, &data_bounds, cell_size, 0,
chatter->win, true, true, false, true);
if (!chatter->nick_list)
panic("Can't create mailboxes list");
+ LAddColumn(1, 0, chatter->nick_list);
(*(chatter->nick_list))->selFlags = lOnlyOne | lNoNilHilite;
- /* nick scroller */
- chatter->nick_scroller = NewControl(chatter->win, &bounds, "\p", true,
- 1, 1, 1, scrollBarProc, 0L);
-
/* messages */
chatter->messages_te = TENew(&bounds, &bounds);
@@ -105,13 +104,14 @@ chatter_init(const char *server, const unsigned short
focusable->update = chatter_update;
focusable->close = chatter_close;
focusable->atexit = chatter_atexit;
+ focusable->menu = chatter_menu;
show_focusable(focusable);
chatter_layout(chatter, false);
UpdateScrollbarForTE(chatter->messages_scroller, chatter->messages_te,
false);
- chatter_output(chatter, "*** Welcome to %s\r", PROGRAM_NAME);
+ chatter_printf(chatter, "*** Welcome to %s", PROGRAM_NAME);
}
void
@@ -134,19 +134,14 @@ chatter_layout(struct chatter *chatter, bool init)
(*(chatter->input_te))->destRect = bounds;
TECalText(chatter->input_te);
- /* nick scrollbar */
- bounds.top = -1;
- bounds.right = win_bounds.right + 1;
- bounds.left = bounds.right - SCROLLBAR_WIDTH;
- bounds.bottom = (*(chatter->input_te))->viewRect.top - 1;
- (*(chatter->nick_scroller))->contrlRect = bounds;
-
/* nick list */
bounds.top = 0;
- bounds.right = (*(chatter->nick_scroller))->contrlRect.left;
+ bounds.right = win_bounds.right + 1;
bounds.left = bounds.right - NICK_LIST_WIDTH;
bounds.bottom = (*(chatter->input_te))->viewRect.top - 2;
(*(chatter->nick_list))->rView = bounds;
+ LSize(bounds.right - bounds.left, bounds.bottom - bounds.top - 1,
+ chatter->nick_list);
/* messages scrollbar */
bounds.top = -1;
@@ -188,11 +183,27 @@ chatter_atexit(struct focusable *focusable)
void
chatter_update_titlebar(struct chatter *chatter)
{
+ Str255 curtitle;
char title[64];
-
- snprintf(title, sizeof(title), "%s: %s@%s", PROGRAM_NAME,
- chatter->irc_nick, chatter->irc_hostname);
- SetWTitle(chatter->win, CtoPstr(title));
+
+ if (chatter->irc_state <= IRC_STATE_DISCONNECTED)
+ snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME);
+ else if (chatter->irc_state == IRC_STATE_CONNECTING)
+ snprintf(title, sizeof(title), "%s: Connecting to %s",
+ PROGRAM_NAME, chatter->irc_hostname);
+ else if (chatter->irc_channel)
+ snprintf(title, sizeof(title), "%s: %s@%s: %s", PROGRAM_NAME,
+ chatter->irc_nick, chatter->irc_hostname,
+ chatter->irc_channel->name);
+ else
+ snprintf(title, sizeof(title), "%s: %s@%s", PROGRAM_NAME,
+ chatter->irc_nick, chatter->irc_hostname);
+
+ GetWTitle(chatter->win, &curtitle);
+ PtoCstr(curtitle);
+
+ if (strcmp((char *)&curtitle, title) != 0)
+ SetWTitle(chatter->win, CtoPstr(title));
}
void
@@ -325,6 +336,27 @@ chatter_mouse_down(struct focusable *focusable, EventR
}
}
+void
+chatter_menu(struct focusable *focusable, short menu, short item)
+{
+ struct chatter *chatter = (struct chatter *)(focusable->cookie);
+
+ switch (menu) {
+ case EDIT_MENU_ID:
+ switch (item) {
+ case EDIT_MENU_CUT_ID:
+ TECut(chatter->messages_te);
+ break;
+ case EDIT_MENU_COPY_ID:
+ TECopy(chatter->messages_te);
+ break;
+ case EDIT_MENU_PASTE_ID:
+ TEPaste(chatter->messages_te);
+ break;
+ }
+ }
+}
+
#if 0
void
chatter_grow(Point p)
@@ -352,55 +384,75 @@ void
chatter_key_down(struct focusable *focusable, EventRecord *event)
{
struct chatter *chatter = (struct chatter *)(focusable->cookie);
+ TERec *te;
char k;
k = (event->message & charCodeMask);
- TEKey(k, chatter->input_te);
+ if (k == '\r') {
+ HLock(chatter->input_te);
+ te = *(chatter->input_te);
+ HLock(te->hText);
+ /* null terminate just in case */
+ (*(te->hText))[te->teLength] = '\0';
+ irc_process_input(chatter, *(te->hText));
+ TESetText(&k, 0, chatter->input_te);
+ InvalRect(&te->viewRect);
+ HUnlock(te->hText);
+ HUnlock(chatter->input_te);
+ } else
+ TEKey(k, chatter->input_te);
}
size_t
-chatter_output(struct chatter *chatter, const char *format, ...)
+chatter_printf(struct chatter *chatter, const char *format, ...)
{
+ static char buf[1024];
+ static char time[10];
va_list argptr;
- size_t len = 0, last_pos, last_line, i;
- short vlines, tries;
- char didup = 0;
+ size_t len = 0;
+ time_t now = Time;
- for (tries = 1; tries <= 3; tries++) {
- if (chatter->message_line_size == 0) {
- chatter->message_line_size = 1024 * tries;
- chatter->message_line = xNewHandle(chatter->message_line_size);
- }
-
- va_start(argptr, format);
- HLock(chatter->message_line);
- /* TODO: vsnprintf */
- len = vsprintf(*(chatter->message_line), format, argptr);
- va_end(argptr);
- HUnlock(chatter->message_line);
-
- if (len < chatter->message_line_size)
- break;
-
- DisposHandle(chatter->message_line);
- chatter->message_line = NULL;
- chatter->message_line_size = 0;
- }
+ len = strftime(buf, sizeof(buf), "\r[%H:%M] ", localtime(&now));
+
+ va_start(argptr, format);
+ len += vsnprintf(buf + len, sizeof(buf) - len, format, argptr);
+ va_end(argptr);
- if (chatter->message_line_size == 0)
- panic("Can't build message line of size %lu", len);
-
- HLock(chatter->message_line);
TESetSelect(1024 * 32, 1024 * 32, chatter->messages_te);
- TEInsert(*(chatter->message_line), len, chatter->messages_te);
- HUnlock(chatter->message_line);
+ TEInsert(buf, len, chatter->messages_te);
- if (tries > 1) {
- /* shrink */
- DisposHandle(chatter->message_line);
- chatter->message_line = NULL;
- chatter->message_line_size = 0;
+ TEPinScroll(0, -INT_MAX, chatter->messages_te);
+ SetCtlValue(chatter->messages_scroller,
+ GetCtlMax(chatter->messages_scroller));
+ UpdateScrollbarForTE(chatter->messages_scroller,
+ chatter->messages_te, false);
+
+ return len;
+}
+
+void
+chatter_sync_nick_list(struct chatter *chatter)
+{
+ Cell cell = { 0 };
+ size_t n;
+ char nick[32];
+
+ LDoDraw(false, chatter->nick_list);
+ LDelRow(0, 0, chatter->nick_list);
+
+ if (!chatter->irc_channel)
+ return;
+
+ /* fill in files */
+ for (n = 0; n < chatter->irc_channel->nusers; n++) {
+ LAddRow(1, cell.v, chatter->nick_list);
+ LSetCell(chatter->irc_channel->users[n].nick,
+ strlen(chatter->irc_channel->users[n].nick), cell,
+ chatter->nick_list);
+ cell.v++;
}
- return len;
+ LDoDraw(true, chatter->nick_list);
+
+ InvalRect(&(*(chatter->nick_list))->rView);
}
--- chatter.h Sun Jan 30 14:35:31 2022
+++ chatter.h Tue Feb 1 08:55:26 2022
@@ -1,10 +1,50 @@
+/*
+ * Copyright (c) 2021-2022 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 __CHATTER_H__
+#define __CHATTER_H__
+
#include <stdio.h>
+#include "irc.h"
#include "tcp.h"
#include "util.h"
-#define PROGRAM_NAME "wallops"
+#define PROGRAM_NAME "wallops"
+#define MBAR_ID 128
+
+#define APPLE_MENU_ID 128
+#define APPLE_MENU_ABOUT_ID 1
+
+#define FILE_MENU_ID 129
+#define FILE_MENU_CONNECT_ID 1
+#define FILE_MENU_QUIT_ID 3
+
+#define EDIT_MENU_ID 130
+#define EDIT_MENU_CUT_ID 1
+#define EDIT_MENU_COPY_ID 2
+#define EDIT_MENU_PASTE_ID 3
+
+#define CONNECT_DLOG_ID 129
+#define CONNECT_SERVER_ID 2
+#define CONNECT_PORT_ID 3
+#define CONNECT_NICK_ID 4
+#define CONNECT_REALNAME_ID 5
+
struct focusable {
WindowPtr win;
void *cookie;
@@ -12,6 +52,7 @@ struct focusable {
void (*update)(struct focusable *focusable, EventRecord *event);
void (*key_down)(struct focusable *focusable, EventRecord *event);
void (*mouse_down)(struct focusable *focusable, EventRecord *event);
+ void (*menu)(struct focusable *focusable, short menu, short item);
void (*close)(struct focusable *focusable, EventRecord *event);
void (*suspend)(struct focusable *focusable, EventRecord *event);
void (*resume)(struct focusable *focusable, EventRecord *event);
@@ -27,22 +68,23 @@ struct chatter {
ControlHandle messages_scroller;
TEHandle input_te;
ListHandle nick_list;
- ControlHandle nick_scroller;
- Handle message_line;
- size_t message_line_size;
short irc_state;
char *irc_hostname;
short irc_port;
char *irc_nick;
+ char *irc_realname;
+ struct irc_channel *irc_channel;
TCPiopb irc_rcv_pb, irc_send_pb, irc_close_pb;
TCPStatusPB irc_status_pb;
StreamPtr irc_stream;
wdsEntry irc_wds[2];
- unsigned char irc_obuf[512];
- unsigned char irc_ibuf[512]; /* RFC2812 says max line will be 512 */
+ char irc_obuf[512];
+ char irc_ibuf[512]; /* RFC2812 says max line will be 512 */
+ char irc_line[512];
short irc_obuflen;
short irc_ibuflen;
+ short irc_linelen;
/* docs say 4*MTU+1024, but MTU will probably be <1500 */
unsigned char irc_tcp_buf[(4 * 1500) + 1024];
};
@@ -51,5 +93,9 @@ void show_focusable(struct focusable *focusable);
void close_focusable(struct focusable *focusable);
void chatter_init(const char *server, const unsigned short port,
- const char *nick);
-size_t chatter_output(struct chatter *chatter, const char *format, ...);
+ const char *nick, const char *realname);
+void chatter_update_titlebar(struct chatter *chatter);
+size_t chatter_printf(struct chatter *chatter, const char *format, ...);
+void chatter_sync_nick_list(struct chatter *chatter);
+
+#endif
--- irc.c Sun Jan 30 14:33:57 2022
+++ irc.c Tue Feb 1 12:54:34 2022
@@ -14,7 +14,9 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <ctype.h>
#include <string.h>
+#include <stdarg.h>
#include "chatter.h"
#include "irc.h"
@@ -22,29 +24,47 @@
void irc_init(struct chatter *chatter);
short irc_verify_state(struct chatter *chatter, int state);
short irc_recv(struct chatter *chatter);
-short irc_send(struct chatter *chatter);
-void irc_process_ibuf(struct chatter *chatter);
+size_t irc_send(struct chatter *chatter, char *line, size_t size);
+size_t irc_printf(struct chatter *chatter, const char *format, ...);
+char * irc_get_line(struct chatter *chatter, size_t *retsize);
+struct irc_user * irc_parse_user(char *str);
+bool irc_can_send(struct chatter *chatter);
+void irc_login(struct chatter *chatter);
+void irc_process_server(struct chatter *chatter);
+void irc_join_channel(struct chatter *chatter, char *channame);
+void irc_parse_names(struct chatter *chatter, char *line);
void
irc_process(struct chatter *chatter)
{
+ size_t oldlen;
+ short oldstate;
+
+start:
if (chatter->irc_state >= IRC_STATE_CONNECTED)
irc_recv(chatter);
-
+
+ oldlen = chatter->irc_ibuflen;
+ oldstate = chatter->irc_state;
+
switch (chatter->irc_state) {
case IRC_STATE_UNINITED:
chatter->irc_state = IRC_STATE_CONNECTING;
irc_init(chatter);
break;
- case IRC_STATE_LOGIN:
- /* TODO */
- chatter->irc_state = IRC_STATE_CONNECTED;
- break;
case IRC_STATE_CONNECTED:
- if (chatter->irc_ibuflen)
- irc_process_ibuf(chatter);
+ irc_login(chatter);
break;
+ case IRC_STATE_IDLE:
+ irc_process_server(chatter);
+ break;
}
+
+ if (chatter->irc_state != oldstate)
+ chatter_update_titlebar(chatter);
+
+ if (chatter->irc_ibuflen && chatter->irc_ibuflen != oldlen)
+ goto start;
}
short
@@ -72,7 +92,7 @@ irc_init(struct chatter *chatter)
if (!irc_verify_state(chatter, IRC_STATE_CONNECTING))
return;
- chatter_output(chatter, "*** Connecting to %s:%d...\r",
+ chatter_printf(chatter, "*** Connecting to %s:%d...",
chatter->irc_hostname, chatter->irc_port);
if ((err = _TCPCreate(&chatter->irc_send_pb, &chatter->irc_stream,
@@ -100,16 +120,16 @@ irc_init(struct chatter *chatter)
return;
}
- chatter_output(chatter, "*** Connected to %s (%s) port %d\r",
+ chatter_printf(chatter, "*** Connected to %s (%s) port %d",
chatter->irc_hostname, ip_str, chatter->irc_port);
- chatter->irc_state = IRC_STATE_LOGIN;
+ chatter->irc_state = IRC_STATE_CONNECTED;
}
void
irc_abort(struct chatter *chatter)
{
- _TCPClose(&chatter->irc_close_pb, chatter->irc_stream, nil, nil,
+ _TCPAbort(&chatter->irc_close_pb, chatter->irc_stream, nil, nil,
false);
_TCPRelease(&chatter->irc_close_pb, chatter->irc_stream, nil, nil,
false);
@@ -121,42 +141,11 @@ irc_close(struct chatter *chatter)
if (chatter->irc_state == IRC_STATE_DISCONNECTED)
return;
- chatter_output(chatter, "*** Disconnecting\r");
+ chatter_printf(chatter, "*** Disconnecting");
irc_abort(chatter);
chatter->irc_state = IRC_STATE_DISCONNECTED;
}
-void
-irc_process_ibuf(struct chatter *chatter)
-{
- size_t n;
-
- if (chatter->irc_ibuflen == 0)
- return;
-
- /* get one line */
-look_for_line:
- for (n = 0; n < chatter->irc_ibuflen - 1; n++) {
- if (chatter->irc_ibuf[n] != '\r')
- continue;
- if (chatter->irc_ibuf[n + 1] != '\n')
- continue;
-
- chatter->irc_ibuf[n] = '\0';
- chatter->irc_ibuf[n + 1] = '\0';
- chatter_output(chatter, "*** %s\r", chatter->irc_ibuf);
-
- if (n == chatter->irc_ibuflen - 2) {
- chatter->irc_ibuflen = 0;
- break;
- } else {
- memmove(chatter->irc_ibuf, chatter->irc_ibuf + n + 2,
- chatter->irc_ibuflen - n - 2);
- goto look_for_line;
- }
- }
-}
-
short
irc_recv(struct chatter *chatter)
{
@@ -166,10 +155,6 @@ irc_recv(struct chatter *chatter)
if (chatter->irc_ibuflen >= sizeof(chatter->irc_ibuf))
return 0;
- if (chatter->irc_rcv_pb.ioResult > 0)
- /* previous _TCPSend/_TCPRecv has not completed yet */
- return 0;
-
error = _TCPStatus(&chatter->irc_rcv_pb, chatter->irc_stream,
&chatter->irc_status_pb, nil, nil, false);
if (error) {
@@ -181,14 +166,17 @@ irc_recv(struct chatter *chatter)
return 0;
rlen = chatter->irc_status_pb.amtUnreadData;
- if (chatter->irc_ibuflen + rlen >= sizeof(chatter->irc_ibuf))
+ if (chatter->irc_ibuflen + rlen > sizeof(chatter->irc_ibuf))
rlen = sizeof(chatter->irc_ibuf) - chatter->irc_ibuflen;
+ if (rlen > 128)
+ rlen = 128;
+
error = _TCPRcv(&chatter->irc_rcv_pb, chatter->irc_stream,
(Ptr)(chatter->irc_ibuf + chatter->irc_ibuflen), &rlen, nil, nil,
false);
if (error) {
- chatter_output(chatter, "*** TCPRecv failed: %d\r", error);
+ chatter_printf(chatter, "*** TCPRecv failed: %d", error);
irc_close(chatter);
return 0;
}
@@ -197,36 +185,34 @@ irc_recv(struct chatter *chatter)
return rlen;
}
-short
-irc_send(struct chatter *chatter)
+bool
+irc_can_send(struct chatter *chatter)
{
+ return (chatter->irc_send_pb.ioResult <= 0);
+}
+
+size_t
+irc_send(struct chatter *chatter, char *line, size_t size)
+{
short error;
- if (chatter->irc_obuflen == 0 ||
- chatter->irc_state == IRC_STATE_DISCONNECTED)
- return 0;
+ if (size > sizeof(chatter->irc_obuf))
+ panic("irc_send: too much data %lu", size);
- if (chatter->irc_send_pb.ioResult > 0)
- /* previous _TCPSend has not completed yet */
+ if (chatter->irc_state == IRC_STATE_DISCONNECTED)
return 0;
-process_result:
- if (chatter->irc_wds[0].length) {
- /* previous _TCPSend completed, shift out those bytes */
- chatter->irc_obuflen -= chatter->irc_wds[0].length;
- if (chatter->irc_obuflen < 0) {
- warn("bogus obuflen %d", chatter->irc_obuflen);
- chatter->irc_obuflen = 0;
- } else if (chatter->irc_obuflen > 0)
- memmove(chatter->irc_obuf,
- chatter->irc_obuf + chatter->irc_wds[0].length,
- chatter->irc_obuflen);
-
- chatter->irc_wds[0].length = 0;
-
- if (chatter->irc_obuflen == 0)
- return 0;
+ while (!irc_can_send(chatter))
+ SystemTask();
+
+ if (chatter->irc_send_pb.ioResult < 0) {
+ warn("TCPSend failed: %d", chatter->irc_send_pb.ioResult);
+ irc_close(chatter);
+ return 0;
}
+
+ memcpy(&chatter->irc_obuf, line, size);
+ chatter->irc_obuflen = size;
/*
* _TCPSend only knows how many wds pointers were passed in when it
@@ -236,7 +222,7 @@ process_result:
*/
memset(&chatter->irc_wds, 0, sizeof(chatter->irc_wds));
chatter->irc_wds[0].ptr = (Ptr)&chatter->irc_obuf;
- chatter->irc_wds[0].length = chatter->irc_obuflen;
+ chatter->irc_wds[0].length = size;
error = _TCPSend(&chatter->irc_send_pb, chatter->irc_stream,
chatter->irc_wds, nil, nil, true);
@@ -246,10 +232,382 @@ process_result:
return 0;
}
- /* if we sent quickly enough, we won't have to cycle again */
- if (chatter->irc_send_pb.ioResult <= 0)
- goto process_result;
+ return size;
+}
+
+size_t
+irc_printf(struct chatter *chatter, const char *format, ...)
+{
+ static char buf[512];
+ va_list argptr;
+ size_t len = 0;
+
+ va_start(argptr, format);
+ len = vsnprintf(buf, sizeof(buf), format, argptr);
+ va_end(argptr);
+
+ if (len > sizeof(buf)) {
+ warn("irc_printf overflow!");
+ len = sizeof(buf);
+ buf[len - 1] = '\0';
+ }
+
+ irc_send(chatter, buf, len);
+
+ return len;
+}
+
+void
+irc_login(struct chatter *chatter)
+{
+ size_t len;
+
+ if (!irc_verify_state(chatter, IRC_STATE_CONNECTED))
+ return;
- return 0;
+ if (!irc_can_send(chatter))
+ return;
+
+ len = snprintf(chatter->irc_line, sizeof(chatter->irc_line),
+ "NICK %s\r\n", chatter->irc_nick);
+ irc_send(chatter, chatter->irc_line, len);
+
+ len = snprintf(chatter->irc_line, sizeof(chatter->irc_line),
+ "USER %s 0 * :%s\r\n", chatter->irc_nick, chatter->irc_realname);
+ irc_send(chatter, chatter->irc_line, len);
+
+ chatter->irc_state = IRC_STATE_IDLE;
}
+struct irc_user *
+irc_parse_user(char *str)
+{
+ static struct irc_user parsed_user;
+ short ret;
+
+ memset(&parsed_user, 0, sizeof(parsed_user));
+ ret = sscanf(str, "%[^!]!%[^@]@%s", &parsed_user.nick,
+ &parsed_user.username, &parsed_user.hostname);
+ if (ret != 3) {
+ warn("irc_parse_user: failed parsing \"%s\"", str);
+ strcpy(parsed_user.nick, "?");
+ strcpy(parsed_user.username, "?");
+ strlcpy(parsed_user.hostname, str, sizeof(parsed_user.hostname));
+ }
+ return &parsed_user;
+}
+
+char *
+irc_get_line(struct chatter *chatter, size_t *retsize)
+{
+ size_t n;
+
+ if (chatter->irc_ibuflen == 0) {
+ if (retsize != NULL)
+ *retsize = 0;
+ return NULL;
+ }
+
+ for (n = 0; n < chatter->irc_ibuflen - 1; n++) {
+ if (chatter->irc_ibuf[n] != '\r')
+ continue;
+ if (chatter->irc_ibuf[n + 1] != '\n')
+ continue;
+
+ memcpy(chatter->irc_line, chatter->irc_ibuf, n + 1);
+ chatter->irc_line[n] = '\0';
+ if (retsize != NULL)
+ *retsize = n + 1;
+
+ if (n == chatter->irc_ibuflen - 2) {
+ chatter->irc_ibuflen = 0;
+ } else {
+ chatter->irc_ibuflen -= n + 2;
+ if (chatter->irc_ibuflen < 0)
+ panic("bogus irc_ibuflen %d", chatter->irc_ibuflen);
+ memmove(chatter->irc_ibuf, chatter->irc_ibuf + n + 2,
+ chatter->irc_ibuflen);
+ }
+ return chatter->irc_line;
+ }
+
+ return NULL;
+}
+
+void
+irc_process_server(struct chatter *chatter)
+{
+ struct irc_msg msg;
+ struct irc_user *user;
+ char *line;
+ size_t size, n, lastbreak;
+ short state;
+
+ line = irc_get_line(chatter, &size);
+ if (size == 0 || line == NULL)
+ return;
+
+ memset(&msg, 0, sizeof(msg));
+
+ for (n = 0, lastbreak = 0, state = 0; n < size; n++) {
+ if (!(line[n] == ' ' || n == size - 1))
+ continue;
+
+ line[n] = '\0';
+
+ switch (state) {
+ case 0:
+ if (line[0] == ':')
+ /* :server.name 001 jcs :Hi -> msg.source=server.name */
+ strlcpy(msg.source, line + 1, sizeof(msg.source));
+ else
+ /* PING :server.name -> msg.cmd=PING */
+ strlcpy(msg.cmd, line, sizeof(msg.cmd));
+ state++;
+ break;
+ case 1:
+ if (isdigit(line[lastbreak]) && isdigit(line[lastbreak + 1]) &&
+ isdigit(line[lastbreak + 2]))
+ /* :server.name 001 jcs :Hi -> msg.code=1 */
+ msg.code = atoi(line + lastbreak);
+ else if (msg.cmd[0] != '\0') {
+ /* PING :server.name -> msg.msg=server.name */
+ if (line[lastbreak + 1] == ':')
+ lastbreak++;
+ strlcpy(msg.msg, line + lastbreak + 1, sizeof(msg.msg));
+ goto done_parsing;
+ } else
+ /* :jcs!~jcs@jcs JOIN #wallops -> msg.cmd=JOIN */
+ strlcpy(msg.cmd, line + lastbreak, sizeof(msg.cmd));
+ state++;
+ break;
+ case 2:
+ if (line[lastbreak] == ':') {
+ /* :jcs!... QUIT :Ping timeout -> msg.msg=Ping timeout */
+ strlcpy(msg.msg, line + lastbreak + 1,
+ sizeof(msg.msg));
+ goto done_parsing;
+ }
+
+ /* :server.name 251 jcs :Blah -> msg.dest=jcs */
+ strlcpy(msg.dest, line + lastbreak, sizeof(msg.dest));
+
+ if (line[n + 1] == ':') {
+ strlcpy(msg.msg, line + n + 2, sizeof(msg.msg));
+ goto done_parsing;
+ }
+ state++;
+ break;
+ case 3:
+ if (line[lastbreak] != ':') {
+ /* :server 480 jcs #wallops :Cannot... -> msg.channel=#wallops */
+ strlcpy(msg.channel, line + lastbreak, sizeof(msg.channel));
+ lastbreak = n + 1;
+ }
+
+ strlcpy(msg.msg, line + lastbreak, sizeof(msg.msg));
+ goto done_parsing;
+ }
+
+ lastbreak = n + 1;
+ }
+
+done_parsing:
+ if (strcmp(msg.cmd, "PING") == 0) {
+ irc_printf(chatter, "PONG :%s\r\n", msg.source);
+ return;
+ }
+ if (strcmp(msg.cmd, "NOTICE") == 0) {
+ chatter_printf(chatter, "%s", msg.msg);
+ return;
+ }
+ if (strcmp(msg.cmd, "MODE") == 0) {
+ chatter_printf(chatter, "*** Mode change (%s) for %s", msg.msg,
+ msg.dest);
+ return;
+ }
+ if (strcmp(msg.cmd, "PRIVMSG") == 0 &&
+ strcmp(msg.dest, chatter->irc_nick) == 0) {
+ user = irc_parse_user(msg.source);
+ chatter_printf(chatter, "[%s(%s@%s)] %s", user->nick,
+ user->username, user->hostname, msg.msg);
+ return;
+ }
+ if (strcmp(msg.cmd, "JOIN") == 0) {
+ user = irc_parse_user(msg.source);
+ if (strcmp(user->nick, chatter->irc_nick) == 0)
+ irc_join_channel(chatter, msg.dest);
+ return;
+ }
+ switch (msg.code) {
+ case 0:
+ goto unknown;
+ case 1:
+ case 2:
+ /* welcome banners */
+ goto print_msg;
+ case 3:
+ case 4:
+ case 5:
+ case 250:
+ case 251:
+ case 252:
+ case 253:
+ case 254:
+ case 255:
+ case 265:
+ case 266:
+ /* server stats, unhelpful */
+ return;
+ case 372:
+ case 375:
+ case 376:
+ /* MOTD */
+ goto print_msg;
+ case 311:
+ case 312:
+ case 317:
+ case 318:
+ case 338:
+ /* WHOIS stuff */
+ goto print_msg;
+ case 332:
+ /* TOPIC */
+ chatter_printf(chatter, "*** Topic for %s: %s", msg.channel,
+ msg.msg);
+ if (chatter->irc_channel &&
+ strcmp(chatter->irc_channel->name, msg.channel) == 0) {
+ strlcpy(chatter->irc_channel->topic, msg.msg,
+ sizeof(chatter->irc_channel->topic));
+ }
+ return;
+ case 333:
+ /* TOPIC creator */
+ return;
+ case 352:
+ /* WHO output */
+ goto unknown;
+ case 315:
+ /* end of WHO */
+ goto unknown;
+ case 353:
+ /* NAMES output */
+ if (chatter->irc_channel) /* TODO: match arg to channel name */
+ irc_parse_names(chatter, msg.msg);
+ return;
+ case 366:
+ /* end of NAMES output */
+ if (chatter->irc_channel)
+ chatter_sync_nick_list(chatter);
+ return;
+ default:
+ goto unknown;
+ }
+
+print_msg:
+ chatter_printf(chatter, "%s", msg.msg);
+ return;
+
+unknown:
+ chatter_printf(chatter, "[?] code:%d cmd:%s source:%s dest:%s "
+ "msg:%s", msg.code, msg.cmd, msg.source, msg.dest, msg.msg);
+}
+
+void
+irc_process_input(struct chatter *chatter, char *str)
+{
+ char command[32];
+ size_t n;
+
+ if (str[0] != '/') {
+ if (chatter->irc_channel == NULL) {
+ chatter_printf(chatter, "*** Cannot send (not in channel)");
+ return;
+ }
+ irc_printf(chatter, "PRIVMSG %s :%s\r\n",
+ chatter->irc_channel->name, str);
+ chatter_printf(chatter, "<%s> %s", chatter->irc_nick, str);
+ return;
+ }
+
+ /* ignore / */
+ str++;
+
+ for (n = 0; ; n++) {
+ if (str[n] == ' ' || str[n] == '\0') {
+ str[n] = '\0';
+ strlcpy(command, str, sizeof(command));
+ str += n + 1;
+ break;
+ }
+ }
+
+ if (strcmp(command, "quote") == 0) {
+ irc_printf(chatter, "%s\r\n", str);
+ } else if (strcmp(command, "join") == 0) {
+ irc_printf(chatter, "JOIN %s\r\n", str);
+ } else if (strcmp(command, "quit") == 0) {
+ irc_printf(chatter, "QUIT :%s\r\n", str[0] == '\0' ? "&" : str);
+ } else
+ chatter_printf(chatter, "unrecognized command \"%s\"", command);
+}
+
+void
+irc_join_channel(struct chatter *chatter, char *channame)
+{
+ struct irc_channel *channel;
+
+ channel = xmalloczero(sizeof(struct irc_channel));
+ strlcpy(channel->name, channame, sizeof(channel->name));
+
+ if (chatter->irc_channel)
+ ; /* TODO: part channel and free */
+
+ chatter->irc_channel = channel;
+ chatter_update_titlebar(chatter);
+}
+
+void
+irc_parse_names(struct chatter *chatter, char *line)
+{
+ size_t n;
+ char *space;
+ bool didchannel = false, last = false;
+ struct irc_channel_nick *user;
+
+ if (!chatter->irc_channel)
+ return;
+
+ /* #wallops :jcs[mac] @jcs */
+
+ for (;;) {
+ if ((space = strchr(line, ' ')) == NULL) {
+ space = line;
+ last = true;
+ } else
+ space[0] = '\0';
+
+ if (!didchannel) {
+ if (strcmp(chatter->irc_channel->name, line) != 0)
+ /* wrong channel */
+ return;
+ didchannel = true;
+
+ /* remove leading : on first nick */
+ line = space + 2;
+ continue;
+ }
+
+ chatter->irc_channel->nusers++;
+ chatter->irc_channel->users = xreallocarray(
+ chatter->irc_channel->users, sizeof(struct irc_channel_nick),
+ chatter->irc_channel->nusers);
+ user = &chatter->irc_channel->users[chatter->irc_channel->nusers - 1];
+ strlcpy(user->nick, line, sizeof(user->nick));
+ user->flag = '\0';
+
+ if (last)
+ break;
+ line = space + 1;
+ }
+}
--- irc.h Sun Jan 30 14:20:47 2022
+++ irc.h Tue Feb 1 09:09:59 2022
@@ -22,11 +22,39 @@ enum {
IRC_STATE_DISCONNECTED,
IRC_STATE_CONNECTING,
IRC_STATE_CONNECTED,
- IRC_STATE_LOGIN
+ IRC_STATE_IDLE
};
+struct irc_msg {
+ short code;
+ char cmd[16];
+ char source[256];
+ char dest[32];
+ char channel[64];
+ char msg[512];
+};
+
+struct irc_user {
+ char nick[32];
+ char username[32];
+ char hostname[128];
+};
+
+struct irc_channel_nick {
+ char nick[32];
+ char flag;
+};
+
+struct irc_channel {
+ char name[64];
+ size_t nusers;
+ struct irc_channel_nick *users;
+ char topic[400];
+};
+
void irc_process(struct chatter *chatter);
void irc_abort(struct chatter *chatter);
void irc_close(struct chatter *chatter);
+void irc_process_input(struct chatter *chatter, char *input);
#endif
--- main.c Sun Jan 30 14:13:53 2022
+++ main.c Mon Jan 31 17:31:18 2022
@@ -20,22 +20,6 @@
#include "tcp.h"
#include "util.h"
-#define MBAR_ID 128
-#define APPLE_MENU_ID 128
-#define APPLE_MENU_ABOUT_ID 1
-#define FILE_MENU_ID 129
-#define FILE_MENU_CONNECT_ID 1
-#define FILE_MENU_QUIT_ID 3
-#define EDIT_MENU_ID 130
-
-#define SOCKS_PROXY_IP_ID 1005
-#define SOCKS_PROXY_PORT_ID 1006
-
-#define CONNECT_DLOG_ID 129
-#define CONNECT_SERVER_ID 2
-#define CONNECT_PORT_ID 3
-#define CONNECT_NICK_ID 4
-
MenuHandle apple_menu, file_menu;
struct focusable *cur_focusable = NULL;
struct focusable *last_focusable = NULL;
@@ -58,6 +42,8 @@ main(void)
short event_in, n;
char key, *socks_proxy_host;
+ SetApplLimit(GetApplLimit() - (1024 * 8));
+
InitGraf(&thePort);
InitFonts();
FlushEvents(everyEvent, 0);
@@ -81,14 +67,9 @@ main(void)
update_menu();
DrawMenuBar();
- socks_proxy_host = xGetStringAsChar(SOCKS_PROXY_IP_ID);
- if (socks_proxy_host[0] != '\0')
- socks_proxy_ip = ip2long(socks_proxy_host);
- free(socks_proxy_host);
- socks_proxy_port = (ip_port)xGetStringAsLong(SOCKS_PROXY_PORT_ID);
+ //show_connect_dialog();
+ chatter_init("irc.libera.chat", 6667, "jcs[mac]", "joshua stein");
-chatter_init("irc.libera.chat", 6667, "jcs[mac]");
-
for (;;) {
WaitNextEvent(everyEvent, &event, 5L, 0L);
@@ -186,10 +167,10 @@ chatter_init("irc.libera.chat", 6667, "jcs[mac]");
short
show_connect_dialog(void)
{
- Str255 server, nick, ports;
+ Str255 server, nick, realname, ports;
DialogPtr dlg;
bool done = false, connect = false;
- short hit, itype;
+ short hit, itype, ret;
long port;
Handle ihandle;
Rect irect;
@@ -222,6 +203,16 @@ get_input:
continue;
}
+ GetDItem(dlg, CONNECT_PORT_ID, &itype, &ihandle, &irect);
+ GetIText(ihandle, ports);
+ PtoCstr(ports);
+ ret = sscanf((char *)&ports, "%ld", &port);
+ if (ret != 1 || port < 1 || port > 65535) {
+ warn("Port must be between 1-65535");
+ connect = false;
+ continue;
+ }
+
GetDItem(dlg, CONNECT_NICK_ID, &itype, &ihandle, &irect);
GetIText(ihandle, nick);
PtoCstr(nick);
@@ -231,15 +222,11 @@ get_input:
continue;
}
- GetDItem(dlg, CONNECT_PORT_ID, &itype, &ihandle, &irect);
- GetIText(ihandle, ports);
- PtoCstr(ports);
- if (sscanf((char *)ports, "%d", &port) != 1 ||
- port < 1 || port > 65535) {
- warn("Port must be between 1-65535");
- connect = false;
- continue;
- }
+ GetDItem(dlg, CONNECT_REALNAME_ID, &itype, &ihandle, &irect);
+ GetIText(ihandle, realname);
+ PtoCstr(realname);
+ if (realname[0] == '\0')
+ memcpy(realname, nick, sizeof(realname));
break;
}
@@ -248,7 +235,8 @@ get_input:
DisposeDialog(dlg);
if (connect)
- chatter_init((char *)&server, port, (char *)&nick);
+ chatter_init((char *)&server, port, (char *)&nick,
+ (char *)&realname);
}
short
@@ -295,9 +283,10 @@ handle_menu(long menu_id)
exit(0);
}
break;
- case EDIT_MENU_ID:
- /* let each focusable handle this */
- break;
+ default:
+ if (cur_focusable && cur_focusable->menu)
+ cur_focusable->menu(cur_focusable, HiWord(menu_id),
+ LoWord(menu_id));
}
HiliteMenu(0);
--- util.c Sun Jan 30 11:18:22 2022
+++ util.c Tue Feb 1 12:57:23 2022
@@ -961,8 +961,8 @@ UpdateScrollbarForTE(ControlHandle control, TEHandle t
vlines = (ter->viewRect.bottom - ter->viewRect.top) / fheight;
telines = ter->nLines;
/* telines is inaccurate if the last line doesn't have any chars */
- if (telines >= vlines)
- telines++;
+ //if (telines >= vlines)
+ // telines++;
max = telines - vlines + 1;
if (max < 1)
max = 1;