AmendHub

Download:

jcs

/

wallops

/

amendments

/

42

*: Large reorganization in preparation for multiple chat windows

Each channel belongs to a connection, and each channel belongs to
a chatter. Right now they're all the same chatter, but eventually
they'll be different windows or different TE buffers on the same
window.
 
The connection and channel are passed to chatter_printf so it can
eventually determine where to print the text.

jcs made amendment 42 about 1 year ago
--- chatter.c Thu Dec 1 21:49:35 2022 +++ chatter.c Sun Dec 11 20:55:24 2022 @@ -28,7 +28,8 @@ static Handle scrp_rec_h = NULL; void chatter_layout(struct chatter *chatter, bool init, Rect *init_bounds); void chatter_draw_grow_icon(struct chatter *chatter); -void chatter_autoscroll(struct chatter *chatter); +void chatter_autoscroll(struct chatter *chatter, TEHandle te, + ControlHandle scroller); short chatter_wait_type(struct focusable *focusable); void chatter_key_down(struct focusable *focusable, EventRecord *event); void chatter_mouse_down(struct focusable *focusable, EventRecord *event); @@ -41,10 +42,8 @@ void chatter_close(struct focusable *focusable, EventR bool chatter_quit(struct focusable *focusable); void chatter_atexit(struct focusable *focusable); -void -chatter_init(const char *server, const unsigned short port, - const char *password, const char *nick, const char *ident, - const char *realname, const char *channel) +struct chatter * +chatter_init(void) { struct focusable *focusable; struct chatter *chatter; @@ -56,16 +55,6 @@ chatter_init(const char *server, const unsigned short panic("TCPInit failed"); chatter = xmalloczero(sizeof(struct chatter), "chatter"); - chatter->irc_state = IRC_STATE_UNINITED; - chatter->irc_hostname = xstrdup(server, "server"); - chatter->irc_port = port; - if (password && password[0]) - chatter->irc_password = xstrdup(password, "password"); - chatter->irc_nick = xstrdup(nick, "nick"); - chatter->irc_ident = xstrdup(ident, "ident"); - chatter->irc_realname = xstrdup(realname, "realname"); - if (channel && channel[0]) - chatter->irc_channel_autojoin = xstrdup(channel, "chan"); bounds.left = padding; bounds.top = screenBits.bounds.top + padding + @@ -104,16 +93,104 @@ chatter_init(const char *server, const unsigned short focusable->resume = chatter_resume; add_focusable(focusable); chatter->focusable = focusable; + + chatter_printf(chatter, NULL, NULL, + "$B***$0 Welcome to %s", + PROGRAM_NAME); + + return chatter; } +void +chatter_connect(struct chatter *chatter, const char *server, + const unsigned short port, const char *password, const char *nick, + const char *ident, const char *realname, const char *channel) +{ + struct irc_connection *conn; + + chatter->nconns++; + chatter->conns = xreallocarray(chatter->conns, chatter->nconns, + sizeof(struct irc_connection)); + conn = &chatter->conns[chatter->nconns - 1]; + chatter->cur_conn = conn; + memset(conn, 0, sizeof(struct irc_connection)); + + conn->chatter = chatter; + conn->state = IRC_STATE_UNINITED; + conn->hostname = xstrdup(server, "server"); + conn->port = port; + if (password && password[0]) + conn->password = xstrdup(password, "password"); + conn->nick = xstrdup(nick, "nick"); + conn->ident = xstrdup(ident, "ident"); + conn->realname = xstrdup(realname, "realname"); + if (channel && channel[0]) + conn->channel_autojoin = xstrdup(channel, "chan"); + + /* chatter_idle() will call irc_process() to kick off connection */ +} + +void +chatter_dealloc_connection(struct chatter *chatter, + struct irc_connection *conn) +{ + short n; + + if (conn->hostname != NULL) + xfree(&conn->hostname); + if (conn->password != NULL) + xfree(&conn->password); + if (conn->nick != NULL) + xfree(&conn->nick); + if (conn->ident != NULL) + xfree(&conn->ident); + if (conn->realname != NULL) + xfree(&conn->realname); + if (conn->channel_autojoin != NULL) + xfree(&conn->channel_autojoin); + + chatter->cur_conn = NULL; + + if (chatter->nconns == 1) { + chatter->nconns = 0; + xfree(&chatter->conns); + } else { + for (n = 0; n < chatter->nconns; n++) { + if (conn != &chatter->conns[n]) + continue; + + if (n == chatter->nconns - 1) { + /* just lop it off */ + } else { + /* move the conn at the last place to here */ + memcpy(&chatter->conns[n], + &chatter->conns[chatter->nconns - 1], + sizeof(struct irc_connection)); + } + break; + } + chatter->nconns--; + + xreallocarray(chatter->conns, chatter->nconns, + sizeof(struct irc_connection)); + chatter->cur_conn = &chatter->conns[0]; + } + + chatter_update_titlebar(chatter); +} + short chatter_wait_type(struct focusable *focusable) { struct chatter *chatter = (struct chatter *)(focusable->cookie); + short n; - if (chatter->irc_ibuflen) - return WAIT_TYPE_URGENT; - else if (!focusable->visible) + for (n = 0; n < chatter->nconns; n++) { + if (chatter->conns[n].ibuflen) + return WAIT_TYPE_URGENT; + } + + if (!focusable->visible) return WAIT_TYPE_BACKGROUND; return WAIT_TYPE_FOREGROUND; @@ -199,7 +276,8 @@ chatter_layout(struct chatter *chatter, bool init, Rec (*(chatter->messages_te))->destRect = bounds; TECalText(chatter->messages_te); - chatter_autoscroll(chatter); + chatter_autoscroll(chatter, chatter->messages_te, + chatter->messages_scroller); } InvalRect(&win_bounds); @@ -221,10 +299,12 @@ chatter_close(struct focusable *focusable, EventRecord { struct chatter *chatter = (struct chatter *)(focusable->cookie); - if (chatter->irc_state < IRC_STATE_CONNECTED) { +#if 0 + if (chatter->conn->state < IRC_STATE_CONNECTED) { irc_abort(chatter); destroy_focusable(focusable); } else +#endif hide_focusable(focusable); } @@ -232,12 +312,12 @@ bool chatter_quit(struct focusable *focusable) { struct chatter *chatter = (struct chatter *)(focusable->cookie); - - if (chatter->irc_state < IRC_STATE_CONNECTED) - irc_abort(chatter); - else - irc_close(chatter); + short n; + /* chatter->nconns will change as we dealloc, so we can't walk it */ + while (chatter->nconns > 0) + irc_dealloc_connection(&chatter->conns[0]); + destroy_focusable(focusable); return true; } @@ -246,8 +326,10 @@ void chatter_atexit(struct focusable *focusable) { struct chatter *chatter = (struct chatter *)(focusable->cookie); - - irc_abort(chatter); + short n; + + for (n = 0; n < chatter->nconns; n++) + irc_dealloc_connection(&chatter->conns[n]); } void @@ -256,18 +338,19 @@ chatter_update_titlebar(struct chatter *chatter) Str255 curtitle; char title[64]; - if (chatter->irc_state <= IRC_STATE_DISCONNECTED) + if (!chatter->cur_conn || + chatter->cur_conn->state <= IRC_STATE_DISCONNECTED) snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME); - else if (chatter->irc_state == IRC_STATE_CONNECTING) + else if (chatter->cur_conn->state == IRC_STATE_CONNECTING) snprintf(title, sizeof(title), "%s: Connecting to %s", - PROGRAM_NAME, chatter->irc_hostname); - else if (chatter->irc_channel) + PROGRAM_NAME, chatter->cur_conn->hostname); + else if (chatter->cur_channel) snprintf(title, sizeof(title), "%s: %s@%s: %s", PROGRAM_NAME, - chatter->irc_nick, chatter->irc_hostname, - chatter->irc_channel->name); + chatter->cur_conn->nick, chatter->cur_conn->hostname, + chatter->cur_channel->name); else snprintf(title, sizeof(title), "%s: %s@%s", PROGRAM_NAME, - chatter->irc_nick, chatter->irc_hostname); + chatter->cur_conn->nick, chatter->cur_conn->hostname); GetWTitle(chatter->win, &curtitle); PtoCstr(curtitle); @@ -280,9 +363,14 @@ void chatter_idle(struct focusable *focusable, EventRecord *event) { struct chatter *chatter = (struct chatter *)(focusable->cookie); + short n; TEIdle(chatter->input_te); - irc_process(chatter); + for (n = 0; n < chatter->nconns; n++) { + if (!irc_process(&chatter->conns[n])) + /* conn list might have changed */ + return; + } } void @@ -398,7 +486,7 @@ chatter_mouse_down(struct focusable *focusable, EventR if (selected.v != now.v) { LSetSelect(false, selected, chatter->nick_list); - /* TODO */ + /* TODO: detect double-click, open query window? */ } return; @@ -527,7 +615,12 @@ chatter_key_down(struct focusable *focusable, EventRec TEIdle(chatter->input_te); HUnlock(te->hText); HUnlock(chatter->input_te); - irc_process_input(chatter, input); + if (chatter->cur_conn) + irc_process_input(chatter->cur_conn, chatter->cur_channel, + input); + else + chatter_printf(chatter, NULL, NULL, + "$B*!*$0 Not connected"); xfree(&input); } else { TEKey(k, chatter->input_te); @@ -536,7 +629,8 @@ chatter_key_down(struct focusable *focusable, EventRec } size_t -chatter_printf(struct chatter *chatter, const char *format, ...) +chatter_printf(struct chatter *chatter, struct irc_connection *conn, + struct irc_channel *channel, const char *format, ...) { static char buf[600], buf_out[600]; StScrpRec *scrp_rec; @@ -551,6 +645,8 @@ chatter_printf(struct chatter *chatter, const char *fo len = 0; + /* TODO: find correct TE based on conn+channel or just conn */ + if ((*(chatter->messages_te))->teLength > 0) { buf[0] = '\r'; len++; @@ -678,38 +774,72 @@ no_overflow: HUnlock(scrp_rec_h); HUnlock(chatter->messages_te); - chatter_autoscroll(chatter); + chatter_autoscroll(chatter, chatter->messages_te, + chatter->messages_scroller); return buf_out_len; } void -chatter_autoscroll(struct chatter *chatter) +chatter_autoscroll(struct chatter *chatter, TEHandle te, + ControlHandle scroller) { - TEPinScroll(0, -INT_MAX, chatter->messages_te); - SetCtlValue(chatter->messages_scroller, - GetCtlMax(chatter->messages_scroller)); - UpdateScrollbarForTE(chatter->win, chatter->messages_scroller, - chatter->messages_te, false); + TEPinScroll(0, -INT_MAX, te); + SetCtlValue(scroller, GetCtlMax(scroller)); + UpdateScrollbarForTE(chatter->win, scroller, te, false); } +struct chatter * +chatter_add_channel(struct irc_channel *channel) +{ + struct chatter *chatter; + + /* XXX: for now put everything in the same TE as the connection */ + chatter = channel->connection->chatter; + chatter->cur_conn = channel->connection; + chatter->cur_channel = channel; + + chatter_sync_nick_list(chatter, channel, false); + chatter_update_titlebar(chatter); + + return chatter; +} + void -chatter_sync_nick_list(struct chatter *chatter, bool just_summary) +chatter_remove_channel(struct chatter *chatter, + struct irc_channel *channel) { + short n; + + /* TODO: change active channel, update nick list, etc. */ + + /* + * Don't bother updating titlebar because we should be inside + * irc_dealloc_channel which will then call chatter_update_titlebar + */ + LDelRow(0, 0, chatter->nick_list); + InvalRect(&(*(chatter->nick_list))->rView); +} + +void +chatter_sync_nick_list(struct chatter *chatter, struct irc_channel *channel, + bool just_summary) +{ size_t n, i, j, tj, ops, voices; short ret, cellv; struct irc_channel_nick *nick = NULL; - if (!just_summary) { + /* TODO: find correct nick list for channel */ + + if (!just_summary && channel == chatter->cur_channel) { LDoDraw(false, chatter->nick_list); LDelRow(0, 0, chatter->nick_list); } - if (chatter->irc_channel) { + if (channel) { cellv = 0; ops = voices = 0; - nick = &chatter->irc_channel->nicks[ - chatter->irc_channel->first_nick]; + nick = &channel->nicks[channel->first_nick]; while (nick) { if (nick->flags & IRC_NICK_FLAG_OP) ops++; @@ -717,20 +847,21 @@ chatter_sync_nick_list(struct chatter *chatter, bool j voices++; if (!just_summary) - chatter_insert_to_nick_list(chatter, nick, cellv); + chatter_insert_to_nick_list(chatter, channel, nick, cellv); cellv++; if (nick->next_nick == -1) break; - nick = &chatter->irc_channel->nicks[nick->next_nick]; + nick = &channel->nicks[nick->next_nick]; } if (just_summary) - chatter_printf(chatter, "$B%s$0: Total of $B%ld$0 nick%s $B(" - "%ld$0 op%s, $B%ld$0 voice%s$B)$0", - chatter->irc_channel->name, chatter->irc_channel->nnicks, - chatter->irc_channel->nnicks == 1 ? "" : "s", + chatter_printf(chatter, NULL, channel, + "$B%s$0: Total of $B%ld$0 nick%s $B(%ld$0 op%s, $B%ld$0 " + "voice%s$B)$0", + channel->name, channel->nnicks, + channel->nnicks == 1 ? "" : "s", ops, ops == 1 ? "" : "s", voices, voices == 1 ? "" : "s"); } @@ -743,12 +874,15 @@ chatter_sync_nick_list(struct chatter *chatter, bool j void chatter_insert_to_nick_list(struct chatter *chatter, - struct irc_channel_nick *nick, short pos) + struct irc_channel *channel, struct irc_channel_nick *nick, short pos) { Cell cell = { 0, 0 }; struct irc_channel_nick tnick; - short j = 0; + ListHandle nick_list; + short j = 0; + nick_list = chatter->nick_list; + if (nick->flags & IRC_NICK_FLAG_OP) { tnick.nick[0] = '@'; j++; @@ -759,6 +893,6 @@ chatter_insert_to_nick_list(struct chatter *chatter, cell.v = pos; j += strlcpy(tnick.nick + j, nick->nick, sizeof(tnick.nick) - j); - LAddRow(1, cell.v, chatter->nick_list); - LSetCell(&tnick.nick, j, cell, chatter->nick_list); + LAddRow(1, cell.v, nick_list); + LSetCell(&tnick.nick, j, cell, nick_list); } --- chatter.h Wed Nov 30 23:19:19 2022 +++ chatter.h Sun Dec 11 15:01:04 2022 @@ -21,7 +21,6 @@ #include "focusable.h" #include "irc.h" -#include "tcp.h" #include "util.h" #define PROGRAM_NAME "Wallops" @@ -81,38 +80,30 @@ struct chatter { TEHandle input_te; ListHandle nick_list; - short irc_state; - char *irc_hostname; - short irc_port; - char *irc_password; - char *irc_nick; - char *irc_ident; - char *irc_realname; - char *irc_channel_autojoin; - 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]; - char irc_obuf[512]; - char irc_ibuf[512]; /* RFC2812 says max line will be 512 */ - char irc_line[512]; - 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]; + short nconns; + struct irc_connection *conns; + struct irc_connection *cur_conn; + struct irc_channel *cur_channel; }; void notify(void); void cancel_notification(void); -void chatter_init(const char *server, const unsigned short port, - const char *nick, const char *password, const char *ident, - const char *realname, const char *channel); +struct chatter * chatter_init(void); +void chatter_connect(struct chatter *chatter, const char *server, + const unsigned short port, const char *password, const char *nick, + const char *ident, const char *realname, const char *channel); +void chatter_dealloc_connection(struct chatter *chatter, + struct irc_connection *conn); void chatter_update_titlebar(struct chatter *chatter); -size_t chatter_printf(struct chatter *chatter, const char *format, ...); +size_t chatter_printf(struct chatter *chatter, struct irc_connection *conn, + struct irc_channel *channel, const char *format, ...); void chatter_insert_to_nick_list(struct chatter *chatter, - struct irc_channel_nick *nick, short pos); -void chatter_sync_nick_list(struct chatter *chatter, bool just_summary); + struct irc_channel *channel, struct irc_channel_nick *nick, short pos); +void chatter_sync_nick_list(struct chatter *chatter, + struct irc_channel *channel, bool just_summary); +struct chatter * chatter_add_channel(struct irc_channel *channel); +void chatter_remove_channel(struct chatter *chatter, + struct irc_channel *channel); #endif --- irc.c Thu Dec 1 21:49:44 2022 +++ irc.c Sun Dec 11 21:35:36 2022 @@ -22,61 +22,72 @@ #include "irc.h" #include "strnatcmp.h" -void irc_init(struct chatter *chatter); -short irc_verify_state(struct chatter *chatter, int state); -short irc_recv(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); +void irc_connect(struct irc_connection *conn); +short irc_verify_state(struct irc_connection *conn, short state); +short irc_recv(struct irc_connection *conn); +size_t irc_send(struct irc_connection *conn, char *line, size_t size); +size_t irc_printf(struct irc_connection *conn, const char *format, ...); +char * irc_get_line(struct irc_connection *conn, size_t *retsize); struct irc_user * irc_parse_user(char *str); -bool irc_can_send(struct chatter *chatter); -void irc_login(struct chatter *chatter); -bool irc_process_server(struct chatter *chatter); -void irc_set_active_channel(struct chatter *chatter, char *channame); -void irc_parse_names(struct chatter *chatter, char *line); -void irc_add_nick_to_channel(struct chatter *chatter, char *nick, +bool irc_can_send(struct irc_connection *conn); +void irc_login(struct irc_connection *conn); +struct irc_channel * irc_find_channel(struct irc_connection *conn, + char *channame); +bool irc_process_server(struct irc_connection *conn); +struct irc_channel * irc_create_channel(struct irc_connection *conn, + char *channame); +void irc_dealloc_channel(struct irc_channel *channel); +void irc_set_active_channel(struct irc_connection *conn, char *channame); +void irc_parse_names(struct irc_channel *channel, char *line); +void irc_add_nick_to_channel(struct irc_channel *channel, char *nick, short flags); -void irc_remove_nick_from_channel(struct chatter *chatter, char *nick); -void irc_change_user_nick(struct chatter *chatter, struct irc_user *user, - char *nick); -void irc_parse_channel_mode_change(struct chatter *chatter, char *mode, +void irc_remove_nick_from_channel(struct irc_channel *channel, char *nick); +bool irc_nick_is_in_channel(struct irc_channel *channel, char *nick); +void irc_change_user_nick(struct irc_channel *channel, + struct irc_user *user, char *nick); +void irc_parse_channel_mode_change(struct irc_channel *channel, char *mode, char *args); -void -irc_process(struct chatter *chatter) +#define IRC_CAN_SEND(conn) ((conn)->send_pb.ioResult <= 0) + +bool +irc_process(struct irc_connection *conn) { short oldstate; - if (chatter->irc_state >= IRC_STATE_CONNECTED) - irc_recv(chatter); - - oldstate = chatter->irc_state; + if (conn->state >= IRC_STATE_CONNECTED) { + if (irc_recv(conn) < 0) + return false; + } - switch (chatter->irc_state) { + oldstate = conn->state; + + switch (conn->state) { case IRC_STATE_UNINITED: - chatter_printf(chatter, "$B***$0 Welcome to %s", PROGRAM_NAME); - chatter->irc_state = IRC_STATE_CONNECTING; - irc_init(chatter); + conn->state = IRC_STATE_CONNECTING; + irc_connect(conn); break; case IRC_STATE_CONNECTED: - irc_login(chatter); + irc_login(conn); break; case IRC_STATE_IDLE: - irc_process_server(chatter); + if (!irc_process_server(conn)) + return false; break; } - if (chatter->irc_state != oldstate) - chatter_update_titlebar(chatter); + if (conn->state != oldstate) + chatter_update_titlebar(conn->chatter); + + return true; } short -irc_verify_state(struct chatter *chatter, int state) +irc_verify_state(struct irc_connection *conn, short state) { - if (chatter->irc_state != state) { - warn("Bad IRC state (in %d, expected %d)", chatter->irc_state, - state); - chatter->irc_state = IRC_STATE_DISCONNECTED; + if (conn->state != state) { + warn("Bad IRC state (in %d, expected %d)", conn->state, state); + conn->state = IRC_STATE_DISCONNECTED; return 0; } @@ -84,7 +95,7 @@ irc_verify_state(struct chatter *chatter, int state) } void -irc_init(struct chatter *chatter) +irc_connect(struct irc_connection *conn) { char ip_str[] = "255.255.255.255"; char *retname; @@ -92,140 +103,138 @@ irc_init(struct chatter *chatter) ip_port port, local_port = 0; short err; - if (!irc_verify_state(chatter, IRC_STATE_CONNECTING)) + if (!irc_verify_state(conn, IRC_STATE_CONNECTING)) return; - chatter_printf(chatter, "$B***$0 Connecting to $B%s:%d$0...", - chatter->irc_hostname, chatter->irc_port); + chatter_printf(conn->chatter, conn, NULL, + "$B***$0 Connecting to $B%s:%d$0...", conn->hostname, conn->port); - if ((err = _TCPCreate(&chatter->irc_send_pb, &chatter->irc_stream, - (Ptr)chatter->irc_tcp_buf, sizeof(chatter->irc_tcp_buf), - nil, nil, nil, false)) != noErr) { - chatter_printf(chatter, "%B*!* TCPCreate failed: %d$0", err); - chatter->irc_state = IRC_STATE_DISCONNECTED; + if ((err = _TCPCreate(&conn->send_pb, &conn->stream, + (Ptr)conn->tcp_buf, sizeof(conn->tcp_buf), nil, nil, nil, + false)) != noErr) { + chatter_printf(conn->chatter, conn, NULL, + "%B*!* TCPCreate failed: %d$0", err); + conn->state = IRC_STATE_DISCONNECTED; return; } - if ((err = TCPResolveName(&chatter->irc_hostname, &ip)) != 0) { - chatter_printf(chatter, "$B*!* Couldn't resolve host %s (%d)$0", - chatter->irc_hostname, err); - chatter->irc_state = IRC_STATE_DISCONNECTED; + if ((err = TCPResolveName(conn->hostname, &ip)) != 0) { + chatter_printf(conn->chatter, conn, NULL, + "$B*!* Couldn't resolve host %s (%d)$0", conn->hostname, err); + conn->state = IRC_STATE_DISCONNECTED; return; } long2ip(ip, ip_str); - if ((err = _TCPActiveOpen(&chatter->irc_send_pb, chatter->irc_stream, - ip, chatter->irc_port, &local_ip, &local_port, nil, nil, - false)) != noErr) { - chatter_printf(chatter, "$B*!* Failed connecting to %s (%s) " - "port %d: %d$0", chatter->irc_hostname, ip_str, - chatter->irc_port, err); - chatter->irc_state = IRC_STATE_DISCONNECTED; + if ((err = _TCPActiveOpen(&conn->send_pb, conn->stream, ip, + conn->port, &local_ip, &local_port, nil, nil, false)) != noErr) { + chatter_printf(conn->chatter, conn, NULL, + "$B*!* Failed connecting to %s (%s) port %d: %d$0", + conn->hostname, ip_str, conn->port, err); + conn->state = IRC_STATE_DISCONNECTED; return; } - chatter_printf(chatter, "$B***$0 Connected to $B%s$0 (%s) port %d", - chatter->irc_hostname, ip_str, chatter->irc_port); + chatter_printf(conn->chatter, conn, NULL, + "$B***$0 Connected to $B%s$0 (%s) port %d", conn->hostname, ip_str, + conn->port); - chatter->irc_state = IRC_STATE_CONNECTED; + conn->state = IRC_STATE_CONNECTED; } void -irc_abort(struct chatter *chatter) +irc_dealloc_connection(struct irc_connection *conn) { - chatter->irc_state = IRC_STATE_DISCONNECTED; - irc_set_active_channel(chatter, NULL); + short n; - _TCPClose(&chatter->irc_close_pb, chatter->irc_stream, nil, nil, - false); - _TCPAbort(&chatter->irc_close_pb, chatter->irc_stream, nil, nil, - false); - _TCPRelease(&chatter->irc_close_pb, chatter->irc_stream, nil, nil, - false); + while (conn->nchannels > 0) + irc_dealloc_channel(&conn->channels[0]); - chatter->irc_ibuflen = 0; + _TCPClose(&conn->close_pb, conn->stream, nil, nil, false); + _TCPAbort(&conn->close_pb, conn->stream, nil, nil, false); + _TCPRelease(&conn->close_pb, conn->stream, nil, nil, false); + + chatter_dealloc_connection(conn->chatter, conn); } void -irc_close(struct chatter *chatter) +irc_close(struct irc_connection *conn) { - if (chatter->irc_state == IRC_STATE_DISCONNECTED) + if (conn->state == IRC_STATE_DISCONNECTED) return; - chatter_printf(chatter, "$B***$0 Disconnecting"); - if (chatter->irc_state == IRC_STATE_IDLE) - irc_printf(chatter, "QUIT :Cmd+Q\r\n"); - irc_abort(chatter); + chatter_printf(conn->chatter, conn, NULL, + "$B***$0 Disconnecting"); + if (conn->state == IRC_STATE_IDLE) + irc_printf(conn, "QUIT :Cmd+Q\r\n"); + irc_dealloc_connection(conn); } short -irc_recv(struct chatter *chatter) +irc_recv(struct irc_connection *conn) { unsigned short rlen; short error, rerror, n; - error = _TCPStatus(&chatter->irc_rcv_pb, chatter->irc_stream, - &chatter->irc_status_pb, nil, nil, false); + error = _TCPStatus(&conn->rcv_pb, conn->stream, &conn->status_pb, nil, + nil, false); - if (chatter->irc_status_pb.amtUnreadData > 0 && - chatter->irc_ibuflen < sizeof(chatter->irc_ibuf)) { - rlen = chatter->irc_status_pb.amtUnreadData; - if (chatter->irc_ibuflen + rlen > sizeof(chatter->irc_ibuf)) - rlen = sizeof(chatter->irc_ibuf) - chatter->irc_ibuflen; + if (conn->status_pb.amtUnreadData > 0 && + conn->ibuflen < sizeof(conn->ibuf)) { + rlen = conn->status_pb.amtUnreadData; + if (conn->ibuflen + rlen > sizeof(conn->ibuf)) + rlen = sizeof(conn->ibuf) - conn->ibuflen; - rerror = _TCPRcv(&chatter->irc_rcv_pb, chatter->irc_stream, - (Ptr)(chatter->irc_ibuf + chatter->irc_ibuflen), &rlen, nil, nil, - false); + rerror = _TCPRcv(&conn->rcv_pb, conn->stream, + (Ptr)(conn->ibuf + conn->ibuflen), &rlen, nil, nil, false); if (rerror) { - chatter_printf(chatter, "$B*!* TCPRecv failed: %d$0", error); - irc_abort(chatter); - return 0; + chatter_printf(conn->chatter, conn, NULL, + "$B*!* TCPRecv failed (%d), disconnecting$0", error); + irc_dealloc_connection(conn); + return -1; } - chatter->irc_ibuflen += rlen; + conn->ibuflen += rlen; } if (error) { /* let already-consumed buffer finish processing */ - while (irc_process_server(chatter)) - ; - chatter_printf(chatter, "$B*!* TCPStatus failed: %d$0", error); - irc_abort(chatter); - return 0; + while (irc_process_server(conn)) + SystemTask(); + chatter_printf(conn->chatter, conn, NULL, + "$B*!* TCPStatus failed: %d$0", + error); + irc_dealloc_connection(conn); + return -1; } return rlen; } -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) +irc_send(struct irc_connection *conn, char *line, size_t size) { short error; - if (size > sizeof(chatter->irc_obuf)) + if (size > sizeof(conn->obuf)) panic("irc_send: too much data %lu", size); - if (chatter->irc_state == IRC_STATE_DISCONNECTED) + if (conn->state == IRC_STATE_DISCONNECTED) return 0; - while (!irc_can_send(chatter)) + while (!IRC_CAN_SEND(conn)) SystemTask(); - if (chatter->irc_send_pb.ioResult < 0) { - chatter_printf(chatter, "$B*!* TCPSend failed: %d$0", - chatter->irc_send_pb.ioResult); - irc_abort(chatter); + if (conn->send_pb.ioResult < 0) { + chatter_printf(conn->chatter, conn, NULL, + "$B*!* TCPSend failed: %d$0", + conn->send_pb.ioResult); + irc_dealloc_connection(conn); return 0; } - memcpy(&chatter->irc_obuf, line, size); + memcpy(&conn->obuf, line, size); /* * _TCPSend only knows how many wds pointers were passed in when it @@ -233,15 +242,16 @@ irc_send(struct chatter *chatter, char *line, size_t s * even though we're only sending one wds, memory after wds[0] * has to be zeroed out. */ - memset(&chatter->irc_wds, 0, sizeof(chatter->irc_wds)); - chatter->irc_wds[0].ptr = (Ptr)&chatter->irc_obuf; - chatter->irc_wds[0].length = size; + memset(&conn->wds, 0, sizeof(conn->wds)); + conn->wds[0].ptr = (Ptr)&conn->obuf; + conn->wds[0].length = size; - error = _TCPSend(&chatter->irc_send_pb, chatter->irc_stream, - chatter->irc_wds, nil, nil, true); + error = _TCPSend(&conn->send_pb, conn->stream, conn->wds, nil, nil, + true); if (error) { - chatter_printf(chatter, "$B*!* TCPSend failed: %d$0", error); - irc_abort(chatter); + chatter_printf(conn->chatter, conn, NULL, + "$B*!* TCPSend failed: %d$0", error); + irc_dealloc_connection(conn); return 0; } @@ -249,7 +259,7 @@ irc_send(struct chatter *chatter, char *line, size_t s } size_t -irc_printf(struct chatter *chatter, const char *format, ...) +irc_printf(struct irc_connection *conn, const char *format, ...) { static char buf[512]; va_list argptr; @@ -265,43 +275,37 @@ irc_printf(struct chatter *chatter, const char *format buf[len - 1] = '\0'; } - irc_send(chatter, buf, len); + irc_send(conn, buf, len); return len; } void -irc_login(struct chatter *chatter) +irc_login(struct irc_connection *conn) { size_t len; - if (!irc_verify_state(chatter, IRC_STATE_CONNECTED)) + if (!irc_verify_state(conn, IRC_STATE_CONNECTED)) return; - if (!irc_can_send(chatter)) + if (!IRC_CAN_SEND(conn)) return; - if (chatter->irc_password && chatter->irc_password[0]) { - len = snprintf(chatter->irc_line, sizeof(chatter->irc_line), - "PASS %s\r\n", chatter->irc_password); - irc_send(chatter, chatter->irc_line, len); + if (conn->password && conn->password[0]) { + len = snprintf(conn->line, sizeof(conn->line), + "PASS %s\r\n", conn->password); + irc_send(conn, conn->line, len); } - 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(conn->line, sizeof(conn->line), + "NICK %s\r\n", conn->nick); + irc_send(conn, conn->line, len); - len = snprintf(chatter->irc_line, sizeof(chatter->irc_line), - "USER %s 8 * :%s\r\n", chatter->irc_ident, chatter->irc_realname); - irc_send(chatter, chatter->irc_line, len); + len = snprintf(conn->line, sizeof(conn->line), + "USER %s 0 * :%s\r\n", conn->ident, conn->realname); + irc_send(conn, conn->line, len); - if (chatter->irc_channel_autojoin && chatter->irc_channel_autojoin[0]) { - len = snprintf(chatter->irc_line, sizeof(chatter->irc_line), - "JOIN %s\r\n", chatter->irc_channel_autojoin); - irc_send(chatter, chatter->irc_line, len); - } - - chatter->irc_state = IRC_STATE_IDLE; + conn->state = IRC_STATE_IDLE; } struct irc_user * @@ -323,59 +327,77 @@ irc_parse_user(char *str) } char * -irc_get_line(struct chatter *chatter, size_t *retsize) +irc_get_line(struct irc_connection *conn, size_t *retsize) { size_t n; - if (chatter->irc_ibuflen == 0) { + if (conn->ibuflen == 0) { if (retsize != NULL) *retsize = 0; return NULL; } - for (n = 0; n < chatter->irc_ibuflen - 1; n++) { - if (chatter->irc_ibuf[n] != '\r') + for (n = 0; n < conn->ibuflen - 1; n++) { + if (conn->ibuf[n] != '\r') continue; - if (chatter->irc_ibuf[n + 1] != '\n') + if (conn->ibuf[n + 1] != '\n') continue; - memcpy(chatter->irc_line, chatter->irc_ibuf, n + 1); - chatter->irc_line[n] = '\0'; + memcpy(conn->line, conn->ibuf, n + 1); + conn->line[n] = '\0'; if (retsize != NULL) *retsize = n + 1; - if (n == chatter->irc_ibuflen - 2) { - chatter->irc_ibuflen = 0; + if (n == conn->ibuflen - 2) { + conn->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); + conn->ibuflen -= n + 2; + if (conn->ibuflen < 0) + panic("bogus ibuflen %d", conn->ibuflen); + memmove(conn->ibuf, conn->ibuf + n + 2, conn->ibuflen); } - return chatter->irc_line; + return conn->line; } return NULL; } +struct irc_channel * +irc_find_channel(struct irc_connection *conn, char *channame) +{ + short n; + + if (channame == NULL || channame[0] == '\0') + return NULL; + + for (n = 0; n < conn->nchannels; n++) { + if (strcasecmp(conn->channels[n].name, channame) == 0) + return &conn->channels[n]; + } + + return NULL; +} + bool -irc_process_server(struct chatter *chatter) +irc_process_server(struct irc_connection *conn) { struct irc_msg msg; struct irc_user *user; + struct irc_channel *channel; char *line, *word; size_t size, n, lastbreak; short state, curarg; - line = irc_get_line(chatter, &size); + line = irc_get_line(conn, &size); if (size == 0 || line == NULL) return false; - + memset(&msg, 0, sizeof(msg)); - if (strstr(line, "services")) +#if 0 /* XXX: what is this for? */ + if (strstr(line, "services") != 0) msg.code = 1; +#endif word = strsep(&line, " "); @@ -428,22 +450,26 @@ irc_process_server(struct chatter *chatter) if (strcmp(msg.cmd, "PRIVMSG") == 0) { user = irc_parse_user(msg.source); size = strlen(msg.msg); + channel = irc_find_channel(conn, msg.arg[0]); if (msg.msg[0] == '\1' && msg.msg[size - 1] == '\1') { /* CTCP or a /me action */ msg.msg[size - 1] = '\0'; if (strncmp(msg.msg + 1, "ACTION", 6) == 0) { - chatter_printf(chatter, "* %s$/ %s", user->nick, - msg.msg + 8); + chatter_printf(channel ? channel->chatter : + conn->chatter, conn, channel, + "* %s$/ %s", + user->nick, msg.msg + 8); } else if (strncmp(msg.msg + 1, "VERSION", 7) == 0) { - chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 " - "requested CTCP $BVERSION$0 from $B%s$0", + chatter_printf(conn->chatter, conn, NULL, + "$B*** %s ($0%s@%s$B)$0 requested CTCP $BVERSION$0 " + "from $B%s$0", user->nick, user->username, user->hostname, msg.arg[0]); /* only respond if it was sent directly to us */ - if (strcmp(msg.arg[0], chatter->irc_nick) == 0) { - irc_printf(chatter, + if (strcmp(msg.arg[0], conn->nick) == 0) { + irc_printf(conn, "NOTICE %s :\1VERSION %s %s on a %s\1\r\n", user->nick, PROGRAM_NAME, get_version(false), gestalt_machine_type()); @@ -451,74 +477,96 @@ irc_process_server(struct chatter *chatter) } else { goto unknown; } - } else if (strcmp(msg.arg[0], chatter->irc_nick) == 0) { - chatter_printf(chatter, "$B[$0%s$B($0%s@%s$B)]$0$/ %s", + } else if (strcmp(msg.arg[0], conn->nick) == 0) { + chatter_printf(channel ? channel->chatter : conn->chatter, + conn, channel, + "$B[%s($0%s@%s$B)]$0$/ %s", user->nick, user->username, user->hostname, msg.msg); - } else if (chatter->irc_channel && - strcmp(msg.arg[0], chatter->irc_channel->name) == 0) { - size = strlen(chatter->irc_nick); - if (strncmp(msg.msg, chatter->irc_nick, size) == 0 && + } else if (channel != NULL) { + size = strlen(conn->nick); + if (strncmp(msg.msg, conn->nick, size) == 0 && (msg.msg[size] == ' ' || msg.msg[size] == ':' || msg.msg[size] == ',' || msg.msg[size] == '\0')) { /* highlight message */ - chatter_printf(chatter, "<$U%s$0>$/ %s", user->nick, + chatter_printf(channel->chatter, conn, channel, + "<$U%s$0>$/ %s", user->nick, msg.msg); - if (!chatter->focusable->visible) + if (!conn->chatter->focusable->visible) notify(); } else - chatter_printf(chatter, "$/<%s> %s", user->nick, - msg.msg); + chatter_printf(channel->chatter, conn, channel, + "$/<%s> %s", + user->nick, msg.msg); } else - chatter_printf(chatter, "$B[%s($0%s@%s$B):%s]$0$/ %s", + chatter_printf(conn->chatter, conn, NULL, + "$B[%s($0%s@%s$B):%s]$0$/ %s", user->nick, user->username, user->hostname, msg.arg[0], msg.msg); return true; } if (strcmp(msg.cmd, "ERROR") == 0) { - chatter_printf(chatter, "$B*!* Disconnected$0 from $B%s$0:$/ %s", - chatter->irc_hostname, msg.msg); - irc_abort(chatter); + chatter_printf(conn->chatter, conn, NULL, + "$B*!* Disconnected$0 from $B%s$0:$/ %s", + conn->hostname, msg.msg); + irc_dealloc_connection(conn); return true; } if (strcmp(msg.cmd, "JOIN") == 0) { user = irc_parse_user(msg.source); - chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has joined " - "$B%s$0", user->nick, user->username, user->hostname, - msg.arg[0]); - if (strcmp(user->nick, chatter->irc_nick) == 0) - irc_set_active_channel(chatter, msg.arg[0]); - else - irc_add_nick_to_channel(chatter, user->nick, 0); + if (strcmp(user->nick, conn->nick) == 0) + channel = irc_create_channel(conn, msg.arg[0]); + else { + channel = irc_find_channel(conn, msg.arg[0]); + irc_add_nick_to_channel(channel, user->nick, 0); + } + chatter_printf(channel->chatter, conn, channel, + "$B*** %s ($0%s@%s$B)$0 has joined $B%s$0", + user->nick, user->username, user->hostname, msg.arg[0]); return true; } if (strcmp(msg.cmd, "KICK") == 0) { user = irc_parse_user(msg.source); - chatter_printf(chatter, "$B*** %s$0 was kicked " - "from $B%s$0 by $B%s%0:$/ %s", msg.arg[1], msg.arg[0], - user->nick, msg.msg); - if (strcmp(msg.arg[1], chatter->irc_nick) == 0) - irc_set_active_channel(chatter, NULL); - else - irc_remove_nick_from_channel(chatter, msg.arg[1]); + channel = irc_find_channel(conn, msg.arg[0]); + if (strcmp(msg.arg[1], conn->nick) == 0) { + irc_dealloc_channel(channel); + channel = NULL; + } else + irc_remove_nick_from_channel(channel, msg.arg[1]); + chatter_printf(channel ? channel->chatter : conn->chatter, + conn, channel, + "$B*** %s$0 was kicked from $B%s$0 by $B%s%0:$/ %s", + msg.arg[1], msg.arg[0], user->nick, msg.msg); return true; } if (strcmp(msg.cmd, "KILL") == 0) { user = irc_parse_user(msg.source); - chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has been " - "killed:$/ %s", user->nick, user->username, user->hostname, - msg.msg); - if (strcmp(user->nick, chatter->irc_nick) == 0) - irc_set_active_channel(chatter, NULL); - else - irc_remove_nick_from_channel(chatter, user->nick); + if (strcmp(user->nick, conn->nick) == 0) { + /* we died :( */ + warn("%s (%s@%s) has been killed: %s", + user->nick, user->username, user->hostname, msg.msg); + irc_dealloc_connection(conn); + return false; + } + for (n = 0; n < conn->nchannels; n++) { + if (!irc_nick_is_in_channel(&conn->channels[n], + user->nick)) + continue; + + chatter_printf(conn->chatter, conn, &conn->channels[n], + "$B*** %s ($0%s@%s$B)$0 has been killed:$/ %s", + user->nick, user->username, user->hostname, msg.msg); + irc_remove_nick_from_channel(channel, user->nick); + } return true; } if (strcmp(msg.cmd, "MODE") == 0) { - if (strcmp(msg.arg[0], chatter->irc_nick) == 0) - chatter_printf(chatter, "$B***$0 Mode change ($B%s$0) " - "for user $B%s$0", msg.msg, msg.arg[0]); + if (strcmp(msg.arg[0], conn->nick) == 0) + chatter_printf(conn->chatter, conn, NULL, + "$B***$0 Mode change ($B%s$0) for user $B%s$0", + msg.msg, msg.arg[0]); else { user = irc_parse_user(msg.source); + channel = irc_find_channel(conn, msg.arg[0]); /* concatenate nicks */ msg.msg[0] = '\0'; if (msg.arg[2][0] != '\0') @@ -535,51 +583,75 @@ irc_process_server(struct chatter *chatter) strlcat(msg.msg, " ", sizeof(msg.msg)); strlcat(msg.msg, msg.arg[5], sizeof(msg.msg)); } - chatter_printf(chatter, "$B***$0 Mode change ($B%s %s$0) " - "on $B%s$0 by $B%s%0", msg.arg[1], msg.msg, msg.arg[0], - user->nick); - irc_parse_channel_mode_change(chatter, msg.arg[1], - msg.msg); + chatter_printf(channel ? channel->chatter : conn->chatter, + conn, channel, + "$B***$0 Mode change ($B%s %s$0) on $B%s$0 by $B%s%0", + msg.arg[1], msg.msg, msg.arg[0], user->nick); + if (channel) + irc_parse_channel_mode_change(channel, msg.arg[1], + msg.msg); } return true; } if (strcmp(msg.cmd, "NICK") == 0) { user = irc_parse_user(msg.source); - chatter_printf(chatter, "$B*** %s$0 is now known as $B%s$0", - user->nick, msg.msg); - irc_change_user_nick(chatter, user, msg.msg); + for (n = 0; n < conn->nchannels; n++) { + channel = &conn->channels[n]; + if (!irc_nick_is_in_channel(channel, user->nick)) + continue; + + chatter_printf(channel->chatter, conn, channel, + "$B*** %s$0 is now known as $B%s$0", + user->nick, msg.msg); + irc_change_user_nick(channel, user, msg.msg); + } return true; } if (strcmp(msg.cmd, "NOTICE") == 0) { if (strncmp(msg.msg, "*** ", 4) == 0) - chatter_printf(chatter, "$B***$0 $/%s", msg.msg + 4); + chatter_printf(conn->chatter, conn, NULL, + "$B***$0 $/%s", + msg.msg + 4); else - chatter_printf(chatter, "$/%s", msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$/%s", + msg.msg); return true; } if (strcmp(msg.cmd, "PART") == 0) { user = irc_parse_user(msg.source); - chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has left " - "$B%s$0", user->nick, user->username, user->hostname, - msg.arg[0]); - if (strcmp(user->nick, chatter->irc_nick) == 0) - irc_set_active_channel(chatter, NULL); - else - irc_remove_nick_from_channel(chatter, user->nick); + channel = irc_find_channel(conn, msg.arg[0]); + if (strcmp(user->nick, conn->nick) == 0) { + irc_dealloc_channel(channel); + channel = NULL; + } else + irc_remove_nick_from_channel(channel, user->nick); + chatter_printf(channel ? channel->chatter : conn->chatter, + conn, channel, + "$B*** %s ($0%s@%s$B)$0 has left $B%s$0", + user->nick, user->username, user->hostname, msg.arg[0]); return true; } if (strcmp(msg.cmd, "PING") == 0) { - irc_printf(chatter, "PONG :%s\r\n", msg.msg); + irc_printf(conn, "PONG :%s\r\n", msg.msg); return true; } if (strcmp(msg.cmd, "QUIT") == 0) { user = irc_parse_user(msg.source); - chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0 has quit:$/ %s", - user->nick, user->username, user->hostname, msg.msg); - if (strcmp(user->nick, chatter->irc_nick) == 0) - irc_set_active_channel(chatter, NULL); - else - irc_remove_nick_from_channel(chatter, user->nick); + for (n = 0; n < conn->nchannels; n++) { + channel = &conn->channels[n]; + if (!irc_nick_is_in_channel(channel, user->nick)) + continue; + + irc_remove_nick_from_channel(channel, user->nick); + chatter_printf(conn->chatter, conn, channel, + "$B*** %s ($0%s@%s$B)$0 has quit:$/ %s", + user->nick, user->username, user->hostname, msg.msg); + } + if (strcmp(user->nick, conn->nick) == 0) { + irc_dealloc_connection(conn); + return false; + } return true; } goto unknown; @@ -607,59 +679,73 @@ irc_process_server(struct chatter *chatter) return true; case 307: /* WHOIS regnick */ - chatter_printf(chatter, "$B*** |$0 Authenticated:$/ %s", msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B*** |$0 Authenticated:$/ %s", + msg.msg); return true; case 311: /* WHOIS nick: "nick :ident hostname * :name" */ - chatter_printf(chatter, "$B*** %s ($0%s@%s$B)$0", msg.arg[1], - msg.arg[2], msg.arg[3]); - chatter_printf(chatter, "$B*** |$0 Name:$/ %s", msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B*** %s ($0%s@%s$B)$0", + msg.arg[1], msg.arg[2], msg.arg[3]); + chatter_printf(conn->chatter, conn, NULL, + "$B*** |$0 Name:$/ %s", + msg.msg); return true; case 312: /* WHOIS server */ - chatter_printf(chatter, "$B*** |$0 Server:$/ %s (%s)", msg.arg[2], - msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B*** |$0 Server:$/ %s (%s)", + msg.arg[2], msg.msg); return true; case 671: /* WHOIS server */ - chatter_printf(chatter, "$B*** |$0 Connection:$/ %s", msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B*** |$0 Connection:$/ %s", + msg.msg); return true; case 317: /* WHOIS idle */ - chatter_printf(chatter, "$B*** |$0 Idle:$/ %s %s", msg.arg[2], - msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B*** |$0 Idle:$/ %s %s", + msg.arg[2], msg.msg); return true; case 318: /* WHOIS end */ - chatter_printf(chatter, "$B*** `-------$0"); + chatter_printf(conn->chatter, conn, NULL, + "$B*** `-------$0"); return true; case 319: /* WHOIS channels */ - chatter_printf(chatter, "$B*** |$0 Channels:$/ %s", msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B*** |$0 Channels:$/ %s", + msg.msg); return true; case 330: /* WHOIS account */ - chatter_printf(chatter, "$B*** |$0 Account:$/ %s %s", msg.msg, - msg.arg[2]); + chatter_printf(conn->chatter, conn, NULL, + "$B*** |$0 Account:$/ %s %s", + msg.msg, msg.arg[2]); return true; case 338: case 378: /* WHOIS host */ - chatter_printf(chatter, "$B*** |$0 Host:$/ %s %s", msg.msg, - msg.arg[2]); + chatter_printf(conn->chatter, conn, NULL, + "$B*** |$0 Host:$/ %s %s", + msg.msg, msg.arg[2]); return true; case 328: /* channel URL, we probably can't do anything with it anyway */ return true; case 332: /* TOPIC */ - chatter_printf(chatter, "$B***$0 Topic for $B%s$0:$/ %s", + channel = irc_find_channel(conn, msg.arg[1]); + chatter_printf(channel ? channel->chatter : conn->chatter, conn, + channel, + "$B***$0 Topic for $B%s$0:$/ %s", msg.arg[1], msg.msg); - if (chatter->irc_channel && - strcmp(chatter->irc_channel->name, msg.arg[1]) == 0) { - strlcpy(chatter->irc_channel->topic, msg.msg, - sizeof(chatter->irc_channel->topic)); - } + if (channel) + strlcpy(channel->topic, msg.msg, sizeof(channel->topic)); return true; case 333: /* TOPIC creator */ @@ -672,40 +758,51 @@ irc_process_server(struct chatter *chatter) goto unknown; case 353: /* NAMES output */ - if (chatter->irc_channel && - strcmp(msg.arg[2], chatter->irc_channel->name) == 0) - irc_parse_names(chatter, msg.msg); + channel = irc_find_channel(conn, msg.arg[2]); + if (channel) + irc_parse_names(channel, msg.msg); return true; case 366: /* end of NAMES output */ - if (chatter->irc_channel && - strcmp(msg.arg[1], chatter->irc_channel->name) == 0) - chatter_sync_nick_list(chatter, true); + channel = irc_find_channel(conn, msg.arg[1]); + if (channel) + chatter_sync_nick_list(channel->chatter, channel, true); return true; case 372: case 375: - case 376: /* MOTD */ goto print_msg; + case 376: + /* end of MOTD */ + if (conn->channel_autojoin && conn->channel_autojoin[0] && + !conn->did_autojoin) { + irc_printf(conn, "JOIN %s\r\n", conn->channel_autojoin); + conn->did_autojoin = true; + } + goto print_msg; case 396: /* Cloak */ - chatter_printf(chatter, "$B***$0$/ %s %s", msg.arg[1], msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B***$0$/ %s %s", + msg.arg[1], msg.msg); return true; case 433: { /* Nick in use, try appending a _ */ - char *new_nick = xmalloc(strlen(chatter->irc_nick) + 2, "new nick"); + char *new_nick = xmalloc(strlen(conn->nick) + 2, "new nick"); size_t len; - chatter_printf(chatter, "$B***$0$/ %s: %s", msg.arg[1], msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B***$0$/ %s: %s", + msg.arg[1], msg.msg); - sprintf(new_nick, "%s_", chatter->irc_nick); - xfree(&chatter->irc_nick); - chatter->irc_nick = new_nick; - chatter_update_titlebar(chatter); + sprintf(new_nick, "%s_", conn->nick); + xfree(&conn->nick); + conn->nick = new_nick; + chatter_update_titlebar(conn->chatter); - 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(conn->line, sizeof(conn->line), + "NICK %s\r\n", conn->nick); + irc_send(conn, conn->line, len); return true; } default: @@ -713,29 +810,34 @@ irc_process_server(struct chatter *chatter) } print_msg: - chatter_printf(chatter, "$B***$0$/ %s", msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B***$0$/ %s", + msg.msg); return true; unknown: - chatter_printf(chatter, "$B[?]$0$/ code:%d cmd:%s source:%s " - "arg0:%s arg1:%s arg2:%s arg3:%s arg4:%s msg:%s", msg.code, msg.cmd, - msg.source, msg.arg[0], msg.arg[1], msg.arg[2], msg.arg[3], - msg.arg[4], msg.msg); + chatter_printf(conn->chatter, conn, NULL, + "$B[?]$0$/ code:%d cmd:%s source:%s arg0:%s arg1:%s arg2:%s arg3:%s " + "arg4:%s msg:%s", + msg.code, msg.cmd, msg.source, msg.arg[0], msg.arg[1], msg.arg[2], + msg.arg[3], msg.arg[4], msg.msg); return true; } void -irc_process_input(struct chatter *chatter, char *str) +irc_process_input(struct irc_connection *conn, struct irc_channel *channel, + char *str) { char *arg0, *arg1; size_t n; if (str[0] != '/') { - if (chatter->irc_channel == NULL) + if (channel == NULL) goto not_in_channel; - irc_printf(chatter, "PRIVMSG %s :%s\r\n", - chatter->irc_channel->name, str); - chatter_printf(chatter, "<$B%s$0>$/ %s", chatter->irc_nick, str); + irc_printf(conn, "PRIVMSG %s :%s\r\n", channel->name, str); + chatter_printf(conn->chatter, conn, channel, + "<$B%s$0>$/ %s", + conn->nick, str); return; } @@ -747,101 +849,168 @@ irc_process_input(struct chatter *chatter, char *str) if (strcmp(arg0, "join") == 0) { if (str == NULL) goto not_enough_params; - irc_printf(chatter, "JOIN %s\r\n", str); + /* + * If we're already in this channel, we won't get any response + * from the server to switch channels. + */ + channel = irc_find_channel(conn, str); + if (channel) + /* this won't actually create, it'll just switch for us */ + irc_create_channel(conn, str); + else + irc_printf(conn, "JOIN %s\r\n", str); return; } if (strcmp(arg0, "me") == 0) { if (str == NULL) goto not_enough_params; - if (chatter->irc_channel == NULL) + if (channel == NULL) goto not_in_channel; - chatter_printf(chatter, "* %s$/ %s", chatter->irc_nick, str); - irc_printf(chatter, "PRIVMSG %s :\1ACTION %s\1\r\n", - chatter->irc_channel->name, str); + chatter_printf(conn->chatter, conn, channel, + "* %s$/ %s", + conn->nick, str); + irc_printf(conn, "PRIVMSG %s :\1ACTION %s\1\r\n", channel->name, + str); return; } if (strcmp(arg0, "msg") == 0) { arg1 = strsep(&str, " "); if (arg1 == NULL || str == NULL) goto not_enough_params; - chatter_printf(chatter, "$B[$0msg$B($0%s$B)]$0$/ %s", arg1, str); - irc_printf(chatter, "PRIVMSG %s :%s\r\n", arg1, str); + chatter_printf(conn->chatter, conn, NULL, + "$B[$0msg$B($0%s$B)]$0$/ %s", + arg1, str); + irc_printf(conn, "PRIVMSG %s :%s\r\n", arg1, str); return; } if (strcmp(arg0, "nick") == 0) { if (str == NULL) goto not_enough_params; - irc_printf(chatter, "NICK %s\r\n", str); + irc_printf(conn, "NICK %s\r\n", str); return; } if (strcmp(arg0, "part") == 0) { - if (str == NULL && chatter->irc_channel) - irc_printf(chatter, "PART %s\r\n", chatter->irc_channel->name); + if (str == NULL && channel) + irc_printf(conn, "PART %s\r\n", channel->name); + else if (str) + irc_printf(conn, "PART %s\r\n", str); else - irc_printf(chatter, "PART %s\r\n", str); + goto not_in_channel; return; } if (strcmp(arg0, "quit") == 0) { - irc_printf(chatter, "QUIT :%s\r\n", str == NULL ? "&" : str); + irc_printf(conn, "QUIT :%s\r\n", str == NULL ? "Quitting" : str); return; } if (strcmp(arg0, "quote") == 0) { if (str == NULL) goto not_enough_params; - irc_printf(chatter, "%s\r\n", str); + irc_printf(conn, "%s\r\n", str); return; } if (strcmp(arg0, "whois") == 0) { if (str == NULL) goto not_enough_params; - irc_printf(chatter, "WHOIS %s\r\n", str); + irc_printf(conn, "WHOIS %s\r\n", str); return; } - chatter_printf(chatter, "unrecognized command \"$B%s$0\"", arg0); + chatter_printf(conn->chatter, conn, NULL, + "unrecognized command \"$B%s$0\"", + arg0); return; not_enough_params: - chatter_printf(chatter, "$B***$0 Not enough parameters given"); + chatter_printf(conn->chatter, conn, NULL, + "$B***$0 Not enough parameters given"); return; not_in_channel: - chatter_printf(chatter, "$B*** Cannot send (not in channel)$0"); + chatter_printf(conn->chatter, conn, NULL, + "$B***$0 Cannot send (not in a channel)"); return; } -void -irc_set_active_channel(struct chatter *chatter, char *channame) +struct irc_channel * +irc_create_channel(struct irc_connection *conn, char *channame) { - if (chatter->irc_channel) { - if (chatter->irc_channel->nicks) - xfree(&chatter->irc_channel->nicks); - xfree(&chatter->irc_channel); - chatter->irc_channel = NULL; + struct irc_channel *channel = NULL; + short n; + + for (n = 0; n < conn->nchannels; n++) { + channel = &conn->channels[n]; + + if (strcasecmp(channel->name, channame) == 0) { + channel->chatter->cur_conn = channel->connection; + channel->chatter->cur_channel = channel; + chatter_sync_nick_list(channel->chatter, channel, false); + chatter_update_titlebar(channel->chatter); + return channel; + } } - if (channame == NULL) - chatter_sync_nick_list(chatter, false); - else { - chatter->irc_channel = xmalloczero(sizeof(struct irc_channel), - "channel"); - strlcpy(chatter->irc_channel->name, channame, - sizeof(chatter->irc_channel->name)); + conn->nchannels++; + conn->channels = xreallocarray(conn->channels, conn->nchannels, + sizeof(struct irc_channel)); + channel = &conn->channels[conn->nchannels - 1]; + memset(channel, 0, sizeof(struct irc_channel)); + + channel->connection = conn; + strlcpy(channel->name, channame, sizeof(channel->name)); + channel->chatter = chatter_add_channel(channel); + + return channel; +} + +void +irc_dealloc_channel(struct irc_channel *channel) +{ + struct irc_connection *conn = channel->connection; + struct chatter *chatter = channel->chatter; + short n; + + chatter_remove_channel(chatter, channel); + + if (channel->nicks) + xfree(&channel->nicks); + + chatter->cur_channel = NULL; + + if (conn->nchannels == 1) { + conn->nchannels = 0; + xfree(&conn->channels); + } else { + for (n = 0; n < conn->nchannels; n++) { + if (channel != &conn->channels[n]) + continue; + + if (n == conn->nchannels - 1) { + /* just lop it off */ + } else { + /* move the channel at the last place to here */ + memcpy(&conn->channels[n], + &conn->channels[conn->nchannels - 1], + sizeof(struct irc_channel)); + } + break; + } + conn->nchannels--; + + xreallocarray(conn->channels, conn->nchannels, + sizeof(struct irc_channel)); + chatter->cur_channel = &conn->channels[0]; } chatter_update_titlebar(chatter); } void -irc_parse_names(struct chatter *chatter, char *line) +irc_parse_names(struct irc_channel *channel, char *line) { size_t n; char *nick; bool last = false; short flags; - if (!chatter->irc_channel) - return; - - LDoDraw(false, chatter->nick_list); + LDoDraw(false, channel->chatter->nick_list); for (;;) { nick = strsep(&line, " "); @@ -858,58 +1027,56 @@ irc_parse_names(struct chatter *chatter, char *line) } else flags = 0; - irc_add_nick_to_channel(chatter, nick, flags); + irc_add_nick_to_channel(channel, nick, flags); if (line == NULL) break; } - LDoDraw(true, chatter->nick_list); - InvalRect(&(*(chatter->nick_list))->rView); + LDoDraw(true, channel->chatter->nick_list); + InvalRect(&(*(channel->chatter->nick_list))->rView); } void -irc_add_nick_to_channel(struct chatter *chatter, char *nick, short flags) +irc_add_nick_to_channel(struct irc_channel *channel, char *nick, + short flags) { struct irc_channel_nick *anick, *cnick, *pnick; short aidx, cidx, ret; - if (chatter->irc_channel->nnicks >= chatter->irc_channel->nicks_size) { + if (channel->nnicks >= channel->nicks_size) { /* allocate a chunk at a time so we don't do this every iteration */ - chatter->irc_channel->nicks_size += 5; - chatter->irc_channel->nicks = - xreallocarray(chatter->irc_channel->nicks, + channel->nicks_size += 5; + channel->nicks = xreallocarray(channel->nicks, sizeof(struct irc_channel_nick), - chatter->irc_channel->nicks_size); - memset(&chatter->irc_channel->nicks[chatter->irc_channel->nnicks], - 0, + channel->nicks_size); + memset(&channel->nicks[channel->nnicks], 0, sizeof(struct irc_channel_nick) * - (chatter->irc_channel->nicks_size - chatter->irc_channel->nnicks)); - aidx = chatter->irc_channel->nnicks; + (channel->nicks_size - channel->nnicks)); + aidx = channel->nnicks; } else { /* find an open slot */ - for (aidx = 0; aidx < chatter->irc_channel->nicks_size; aidx++) { - if (chatter->irc_channel->nicks[aidx].nick[0] == '\0') + for (aidx = 0; aidx < channel->nicks_size; aidx++) { + if (channel->nicks[aidx].nick[0] == '\0') break; } - if (aidx >= chatter->irc_channel->nicks_size) + if (aidx >= channel->nicks_size) panic("irc_add_nick_to_channel overflow"); } - chatter->irc_channel->nnicks++; - anick = &chatter->irc_channel->nicks[aidx]; + channel->nnicks++; + anick = &channel->nicks[aidx]; strlcpy(anick->nick, nick, sizeof(anick->nick)); anick->flags = flags; - if (chatter->irc_channel->nnicks == 1) { + if (channel->nnicks == 1) { anick->next_nick = -1; - chatter->irc_channel->first_nick = 0; + channel->first_nick = 0; cidx = 0; } else { /* sort it in the right place by flags descending, then by nick */ - cnick = &chatter->irc_channel->nicks[ - chatter->irc_channel->first_nick]; + cnick = &channel->nicks[channel->first_nick]; pnick = NULL; cidx = 0; while (cnick) { @@ -928,8 +1095,8 @@ irc_add_nick_to_channel(struct chatter *chatter, char anick->next_nick = pnick->next_nick; pnick->next_nick = aidx; } else { - anick->next_nick = chatter->irc_channel->first_nick; - chatter->irc_channel->first_nick = aidx; + anick->next_nick = channel->first_nick; + channel->first_nick = aidx; } break; } @@ -944,24 +1111,24 @@ irc_add_nick_to_channel(struct chatter *chatter, char } pnick = cnick; - cnick = &chatter->irc_channel->nicks[cnick->next_nick]; + cnick = &channel->nicks[cnick->next_nick]; } } - chatter_insert_to_nick_list(chatter, anick, cidx); + chatter_insert_to_nick_list(channel->chatter, channel, anick, cidx); } void -irc_remove_nick_from_channel(struct chatter *chatter, char *nick) +irc_remove_nick_from_channel(struct irc_channel *channel, char *nick) { struct irc_channel_nick *cnick, *pnick; short cidx; - if (chatter->irc_channel->first_nick == -1) + if (channel->first_nick == -1) return; pnick = NULL; - cnick = &chatter->irc_channel->nicks[chatter->irc_channel->first_nick]; + cnick = &channel->nicks[channel->first_nick]; cidx = 0; while (cnick) { if (strcmp(cnick->nick, nick) != 0) { @@ -969,7 +1136,7 @@ irc_remove_nick_from_channel(struct chatter *chatter, cidx++; if (cnick->next_nick == -1) return; - cnick = &chatter->irc_channel->nicks[cnick->next_nick]; + cnick = &channel->nicks[cnick->next_nick]; continue; } @@ -977,26 +1144,48 @@ irc_remove_nick_from_channel(struct chatter *chatter, if (pnick) pnick->next_nick = cnick->next_nick; else - chatter->irc_channel->first_nick = cnick->next_nick; + channel->first_nick = cnick->next_nick; - chatter->irc_channel->nnicks--; + channel->nnicks--; cnick->nick[0] = '\0'; - LDelRow(1, cidx, chatter->nick_list); + LDelRow(1, cidx, channel->chatter->nick_list); return; } } +bool +irc_nick_is_in_channel(struct irc_channel *channel, char *nick) +{ + struct irc_channel_nick *cnick, *pnick; + + if (channel->first_nick == -1) + return; + + pnick = NULL; + cnick = &channel->nicks[channel->first_nick]; + while (cnick) { + if (strcmp(cnick->nick, nick) == 0) + return true; + + pnick = cnick; + if (cnick->next_nick == -1) + break; + cnick = &channel->nicks[cnick->next_nick]; + } + + return false; +} + void -irc_change_user_nick(struct chatter *chatter, struct irc_user *user, +irc_change_user_nick(struct irc_channel *channel, struct irc_user *user, char *nick) { struct irc_channel_nick *cnick; short n, flags; - if (chatter->irc_channel && chatter->irc_channel->first_nick > -1) { + if (channel && channel->first_nick > -1) { /* preserve flags */ - cnick = &chatter->irc_channel->nicks[ - chatter->irc_channel->first_nick]; + cnick = &channel->nicks[channel->first_nick]; while (cnick) { if (strcmp(cnick->nick, user->nick) == 0) { flags = cnick->flags; @@ -1004,22 +1193,22 @@ irc_change_user_nick(struct chatter *chatter, struct i } if (cnick->next_nick == -1) break; - cnick = &chatter->irc_channel->nicks[cnick->next_nick]; + cnick = &channel->nicks[cnick->next_nick]; } - irc_remove_nick_from_channel(chatter, user->nick); - irc_add_nick_to_channel(chatter, nick, flags); + irc_remove_nick_from_channel(channel, user->nick); + irc_add_nick_to_channel(channel, nick, flags); } - if (strcmp(chatter->irc_nick, user->nick) == 0) { - xfree(&chatter->irc_nick); - chatter->irc_nick = xstrdup(nick, "nick"); - chatter_update_titlebar(chatter); + if (strcmp(channel->connection->nick, user->nick) == 0) { + xfree(&channel->connection->nick); + channel->connection->nick = xstrdup(nick, "nick"); + chatter_update_titlebar(channel->chatter); } } void -irc_parse_channel_mode_change(struct chatter *chatter, char *mode, +irc_parse_channel_mode_change(struct irc_channel *channel, char *mode, char *args) { size_t len; @@ -1045,13 +1234,12 @@ irc_parse_channel_mode_change(struct chatter *chatter, if (user == NULL) user = args; - cnick = &chatter->irc_channel->nicks[ - chatter->irc_channel->first_nick]; + cnick = &channel->nicks[channel->first_nick]; while (cnick) { if (strcmp(cnick->nick, user) != 0) { if (cnick->next_nick == -1) break; - cnick = &chatter->irc_channel->nicks[cnick->next_nick]; + cnick = &channel->nicks[cnick->next_nick]; continue; } @@ -1068,9 +1256,9 @@ irc_parse_channel_mode_change(struct chatter *chatter, flags &= ~IRC_NICK_FLAG_VOICE; } - irc_remove_nick_from_channel(chatter, cnick->nick); + irc_remove_nick_from_channel(channel, cnick->nick); /* cnick is probably invalid now */ - irc_add_nick_to_channel(chatter, user, flags); + irc_add_nick_to_channel(channel, user, flags); break; } --- irc.h Tue Sep 6 12:23:57 2022 +++ irc.h Sun Dec 11 16:33:59 2022 @@ -17,6 +17,9 @@ #ifndef __IRC_H__ #define __IRC_H__ +#include "chatter.h" +#include "tcp.h" + enum { IRC_STATE_UNINITED = 0, IRC_STATE_DISCONNECTED, @@ -51,6 +54,8 @@ struct irc_channel_nick { struct irc_channel { char name[64]; + struct chatter *chatter; + struct irc_connection *connection; struct irc_channel_nick *nicks; size_t nnicks; size_t nicks_size; @@ -58,9 +63,40 @@ struct irc_channel { 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); +struct irc_connection { + short state; + struct chatter *chatter; + char *hostname; + short port; + char *password; + char *nick; + char *ident; + char *realname; + char *channel_autojoin; + bool did_autojoin; + struct irc_channel *channels; + short nchannels; + TCPiopb rcv_pb, send_pb, close_pb; + TCPStatusPB status_pb; + StreamPtr stream; + wdsEntry wds[2]; + char obuf[512]; + char ibuf[512]; /* RFC2812 says max line will be 512 */ + char line[512]; + short ibuflen; + short linelen; + /* docs say 4*MTU+1024, but MTU will probably be <1500 */ + unsigned char tcp_buf[(4 * 1500) + 1024]; +}; + +bool irc_process(struct irc_connection *conn); +void irc_abort(struct irc_connection *conn); +void irc_close(struct irc_connection *conn); +void irc_process_input(struct irc_connection *conn, + struct irc_channel *channel, char *input); +void irc_dealloc_connection(struct irc_connection *conn); + +short strcasecmp(const char *s1, const char *s2); +short strncasecmp(const char *s1, const char *s2, size_t n); #endif --- main.c Thu Dec 1 13:53:21 2022 +++ main.c Fri Dec 2 15:44:51 2022 @@ -233,6 +233,7 @@ short show_connect_dialog(void) { Str255 txt, server, ports, nick, ident, realname, channel, password; + struct chatter *chatter; StringHandle h; DialogTHndl dlgh; DialogPtr dlg; @@ -383,8 +384,9 @@ verify: DisposeDialog(dlg); ReleaseResource(dlgh); - chatter_init((char *)&server, port, (char *)&password, (char *)&nick, - (char *)&ident, (char *)&realname, (char *)&channel); + chatter = chatter_init(); + chatter_connect(chatter, (char *)&server, port, (char *)&password, + (char *)&nick, (char *)&ident, (char *)&realname, (char *)&channel); } bool --- tcp.c Tue Sep 6 12:24:08 2022 +++ tcp.c Fri Dec 2 16:05:38 2022 @@ -392,7 +392,7 @@ StrToAddrMarkDone(struct hostInfo *hi, char *data) } OSErr -TCPResolveName(char **name, unsigned long *ipAddress) +TCPResolveName(char *name, unsigned long *ipAddress) { OSErr osErr; struct hostInfo aHostInfo; @@ -402,7 +402,7 @@ TCPResolveName(char **name, unsigned long *ipAddress) if (osErr) return osErr; - osErr = StrToAddr(*name, &aHostInfo, (ResultProcPtr)StrToAddrMarkDone, + osErr = StrToAddr(name, &aHostInfo, (ResultProcPtr)StrToAddrMarkDone, (char *)&done); if (osErr == cacheFault) { --- tcp.h Tue Sep 6 12:24:11 2022 +++ tcp.h Fri Dec 2 16:06:56 2022 @@ -49,7 +49,7 @@ OSErr _TCPRelease(TCPiopb *pb, StreamPtr stream, Ptr u OSErr _UDPMaxMTUSize(UDPiopb *pb, short *mtu); -OSErr TCPResolveName(char **name, unsigned long *ipAddress); +OSErr TCPResolveName(char *name, unsigned long *ipAddress); long ip2long(char *ip); void long2ip(unsigned long num, char *ip);