AmendHub

Download:

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;