AmendHub

Download:

jcs

/

wallops

/

amendments

/

46

*: Add tabbed view for multiple channels


jcs made amendment 46 about 1 year ago
--- chatter.c Sun Dec 11 20:55:24 2022 +++ chatter.c Tue Jan 10 15:22:50 2023 @@ -23,10 +23,14 @@ #define NICK_LIST_WIDTH 75 #define CHATTER_SCRAP_ELEMENTS 20 +#define MAX_TAB_WIDTH 100 static Handle scrp_rec_h = NULL; +static Pattern tab_bar_pattern; void chatter_layout(struct chatter *chatter, bool init, Rect *init_bounds); +void chatter_draw_tab_bar(struct chatter *chatter); +void chatter_focus_tab(struct chatter *chatter, struct chatter_tab *tab); void chatter_draw_grow_icon(struct chatter *chatter); void chatter_autoscroll(struct chatter *chatter, TEHandle te, ControlHandle scroller); @@ -38,30 +42,35 @@ bool chatter_menu(struct focusable *focusable, short m void chatter_idle(struct focusable *focusable, EventRecord *event); void chatter_update(struct focusable *focusable, EventRecord *event); void chatter_resume(struct focusable *focusable, EventRecord *event); -void chatter_close(struct focusable *focusable, EventRecord *event); +bool chatter_close(struct focusable *focusable); bool chatter_quit(struct focusable *focusable); -void chatter_atexit(struct focusable *focusable); +struct chatter_tab * chatter_find_tab_for_conn_and_channel( + struct chatter *chatter, struct irc_connection *conn, + struct irc_channel *channel); struct chatter * -chatter_init(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 focusable *focusable; struct chatter *chatter; + struct chatter_tab *tab; char title[64]; Rect bounds = { 0 }; short padding = 20; - if (_TCPInit() != 0) - panic("TCPInit failed"); - + GetIndPattern(&tab_bar_pattern, sysPatListID, 23); + chatter = xmalloczero(sizeof(struct chatter), "chatter"); + SLIST_INIT(&chatter->tabs_list); bounds.left = padding; bounds.top = screenBits.bounds.top + padding + (GetMBarHeight() * 2) - 1; bounds.right = screenBits.bounds.right - padding - 1; bounds.bottom = screenBits.bounds.bottom - padding - 1; - + snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME); chatter->win = NewWindow(0L, &bounds, CtoPstr(title), false, documentProc, (WindowPtr)-1L, true, 0); @@ -86,107 +95,37 @@ chatter_init(void) focusable->mouse_down = chatter_mouse_down; focusable->update = chatter_update; focusable->close = chatter_close; - focusable->atexit = chatter_atexit; focusable->quit = chatter_quit; focusable->resize = chatter_resize; focusable->menu = chatter_menu; focusable->resume = chatter_resume; - add_focusable(focusable); + focusable_add(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]; - } - + tab = SLIST_FIRST(&chatter->tabs_list); + tab->conn = irc_connect(chatter, server, port, password, nick, ident, + realname, channel); + InvalRect(&bounds); + DrawControls(chatter->win); + chatter_draw_tab_bar(chatter); chatter_update_titlebar(chatter); + + return chatter; } short chatter_wait_type(struct focusable *focusable) { + struct irc_connection *conn; struct chatter *chatter = (struct chatter *)(focusable->cookie); short n; - for (n = 0; n < chatter->nconns; n++) { - if (chatter->conns[n].ibuflen) + SLIST_FOREACH(conn, &irc_connections_list, list) { + if (conn->ibuflen) return WAIT_TYPE_URGENT; } @@ -197,28 +136,24 @@ chatter_wait_type(struct focusable *focusable) } void -chatter_layout(struct chatter *chatter, bool init, Rect *init_bounds) +chatter_layout(struct chatter *chatter, bool init, Rect *win_bounds) { - Rect bounds, inset_bounds, win_bounds; + Rect bounds, inset_bounds; Rect control_bounds = { 0 }; - Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */ - Point cell_size = { 0 }; - Cell cell = { 0, 0 }; + struct chatter_tab *tab; - if (init) - win_bounds = *init_bounds; - else - win_bounds = chatter->win->portRect; + if (win_bounds == NULL) + win_bounds = &chatter->win->portRect; /* input */ - bounds.left = win_bounds.left; - bounds.right = win_bounds.right - SCROLLBAR_WIDTH - 3; - bounds.top = win_bounds.bottom - SCROLLBAR_WIDTH; - bounds.bottom = win_bounds.bottom; + bounds.left = win_bounds->left; + bounds.right = win_bounds->right - SCROLLBAR_WIDTH + 1; + bounds.top = win_bounds->bottom - SCROLLBAR_WIDTH + 1; + bounds.bottom = win_bounds->bottom; if (init) { inset_bounds = bounds; InsetRect(&inset_bounds, 3, 1); - inset_bounds.right = win_bounds.right * 2; + inset_bounds.right = win_bounds->right * 2; chatter->input_te = TENew(&inset_bounds, &bounds); (*(chatter->input_te))->crOnly = -1; TEAutoView(true, chatter->input_te); @@ -229,60 +164,103 @@ chatter_layout(struct chatter *chatter, bool init, Rec (*(chatter->input_te))->destRect = bounds; TECalText(chatter->input_te); } - - /* nick list */ - bounds.top = 0; - bounds.right = win_bounds.right - SCROLLBAR_WIDTH + 1; - bounds.left = bounds.right - NICK_LIST_WIDTH; - bounds.bottom = (*(chatter->input_te))->viewRect.top - 2; + if (init) { - 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; + chatter_add_tab(chatter, win_bounds, NULL, NULL); } else { - (*(chatter->nick_list))->rView = bounds; - LSize(bounds.right - bounds.left, bounds.bottom - bounds.top, - chatter->nick_list); + /* TODO: move tabs around */ } +} + +struct chatter_tab * +chatter_add_tab(struct chatter *chatter, Rect *win_bounds, + struct irc_connection *conn, struct irc_channel *channel) +{ + struct chatter_tab *tab = NULL; + Rect bounds, inset_bounds; + Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */ + Point cell_size = { 0 }; + Cell cell = { 0, 0 }; + if (win_bounds == NULL) + win_bounds = &chatter->win->portRect; + + tab = xmalloczero(sizeof(struct chatter_tab), "tab"); + SLIST_APPEND(&chatter->tabs_list, tab, chatter_tab, list); + tab->index = chatter->ntabs++; + tab->conn = conn; + tab->channel = channel; + + bounds.bottom = (*(chatter->input_te))->viewRect.top - 15; + + if (tab->channel) { + /* nick list */ + bounds.top = 0; + bounds.right = win_bounds->right - SCROLLBAR_WIDTH + 1; + bounds.left = bounds.right - NICK_LIST_WIDTH; + tab->nick_list = LNew(&bounds, &data_bounds, cell_size, 0, + chatter->win, true, true, false, true); + if (!tab->nick_list) + panic("Can't create nick list"); + LAddColumn(1, 0, tab->nick_list); + (*(tab->nick_list))->selFlags = lOnlyOne | lNoNilHilite; + } + /* messages scrollbar */ bounds.top = -1; - bounds.right = (*(chatter->nick_list))->rView.left; - bounds.left = bounds.right - SCROLLBAR_WIDTH; - bounds.bottom = (*(chatter->input_te))->viewRect.top - 1; - if (init) - chatter->messages_scroller = NewControl(chatter->win, &bounds, - "\p", true, 1, 1, 1, scrollBarProc, 0L); + if (tab->channel) + bounds.right = (*(tab->nick_list))->rView.left; else - (*(chatter->messages_scroller))->contrlRect = bounds; + bounds.right = win_bounds->right + 1; + bounds.left = bounds.right - SCROLLBAR_WIDTH; + bounds.bottom += 1; + tab->messages_scroller = NewControl(chatter->win, &bounds, "\p", true, + 1, 1, 1, scrollBarProc, 0L); /* messages */ - bounds.right = (*(chatter->messages_scroller))->contrlRect.left; + bounds.right = (*(tab->messages_scroller))->contrlRect.left; bounds.left = 0; bounds.top = 0; - bounds.bottom = (*(chatter->input_te))->viewRect.top - 2; - if (init) { - inset_bounds = bounds; - InsetRect(&inset_bounds, 4, 4); - chatter->messages_te = TEStylNew(&inset_bounds, &bounds); - (*(chatter->messages_te))->caretHook = NullCaretHook; - TEActivate(chatter->messages_te); - } else { - (*(chatter->messages_te))->viewRect = bounds; - InsetRect(&bounds, 4, 4); - (*(chatter->messages_te))->destRect = bounds; - TECalText(chatter->messages_te); + bounds.bottom -= 1; + inset_bounds = bounds; + InsetRect(&inset_bounds, 4, 4); + tab->messages_te = TEStylNew(&inset_bounds, &bounds); + (*(tab->messages_te))->caretHook = NullCaretHook; + TEActivate(tab->messages_te); + chatter_autoscroll(chatter, tab->messages_te, tab->messages_scroller); + + chatter_focus_tab(chatter, tab); + + return tab; +} + +void +chatter_focus_tab(struct chatter *chatter, struct chatter_tab *tab) +{ + if (chatter->current_tab) { + if (chatter->current_tab == tab) + return; - chatter_autoscroll(chatter, chatter->messages_te, - chatter->messages_scroller); + TEDeactivate(chatter->current_tab->messages_te); + if (chatter->current_tab->nick_list) + LDoDraw(false, chatter->current_tab->nick_list); + HideControl(chatter->current_tab->messages_scroller); } - InvalRect(&win_bounds); + chatter->current_tab = tab; + + FillRect(&(*(tab->messages_te))->viewRect, white); + TEActivate(tab->messages_te); + TEUpdate(&(*(tab->messages_te))->viewRect, tab->messages_te); + ShowControl(tab->messages_scroller); + if (tab->nick_list) { + FillRect(&(*(tab->nick_list))->rView, white); + LDoDraw(true, tab->nick_list); + LUpdate(chatter->win->visRgn, tab->nick_list); + } + DrawControls(chatter->win); - chatter_draw_grow_icon(chatter); + chatter_draw_tab_bar(chatter); } void @@ -290,67 +268,75 @@ chatter_resume(struct focusable *focusable, EventRecor { struct chatter *chatter = (struct chatter *)(focusable->cookie); - show_focusable(focusable); + focusable_show(focusable); InvalRect(chatter->win->visRgn); } -void -chatter_close(struct focusable *focusable, EventRecord *event) +bool +chatter_close(struct focusable *focusable) { struct chatter *chatter = (struct chatter *)(focusable->cookie); - -#if 0 - if (chatter->conn->state < IRC_STATE_CONNECTED) { - irc_abort(chatter); - destroy_focusable(focusable); - } else -#endif - hide_focusable(focusable); + struct chatter_tab *tab; + struct irc_connection *conn; + bool connected = false; + + if (chatter->quitting) { + DisposeWindow(focusable->win); + return true; + } + + SLIST_FOREACH(tab, &chatter->tabs_list, list) { + if (tab->conn->state == IRC_STATE_CONNECTED) { + connected = true; + break; + } + } + + if (connected) { + focusable_hide(focusable); + return false; + } + + /* TODO: maybe ask the user whether they want to hide or close? */ + chatter_quit(focusable); + return true; } bool chatter_quit(struct focusable *focusable) { struct chatter *chatter = (struct chatter *)(focusable->cookie); + struct chatter_tab *tab; 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]); + SLIST_FOREACH(tab, &chatter->tabs_list, list) { + /* TODO: quit with a proper quit message */ + irc_close_connection(tab->conn); + } - destroy_focusable(focusable); + chatter->quitting = true; + focusable_close(focusable); return true; } void -chatter_atexit(struct focusable *focusable) -{ - struct chatter *chatter = (struct chatter *)(focusable->cookie); - short n; - - for (n = 0; n < chatter->nconns; n++) - irc_dealloc_connection(&chatter->conns[n]); -} - -void chatter_update_titlebar(struct chatter *chatter) { Str255 curtitle; char title[64]; + struct chatter_tab *tab = chatter->current_tab; - if (!chatter->cur_conn || - chatter->cur_conn->state <= IRC_STATE_DISCONNECTED) + if (!tab->conn || tab->conn->state <= IRC_STATE_DISCONNECTED) snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME); - else if (chatter->cur_conn->state == IRC_STATE_CONNECTING) + else if (tab->conn->state == IRC_STATE_CONNECTING) snprintf(title, sizeof(title), "%s: Connecting to %s", - PROGRAM_NAME, chatter->cur_conn->hostname); - else if (chatter->cur_channel) + PROGRAM_NAME, tab->conn->hostname); + else if (tab->channel) snprintf(title, sizeof(title), "%s: %s@%s: %s", PROGRAM_NAME, - chatter->cur_conn->nick, chatter->cur_conn->hostname, - chatter->cur_channel->name); + tab->conn->nick, tab->conn->hostname, tab->channel->name); else snprintf(title, sizeof(title), "%s: %s@%s", PROGRAM_NAME, - chatter->cur_conn->nick, chatter->cur_conn->hostname); + tab->conn->nick, tab->conn->hostname); GetWTitle(chatter->win, &curtitle); PtoCstr(curtitle); @@ -363,13 +349,13 @@ void chatter_idle(struct focusable *focusable, EventRecord *event) { struct chatter *chatter = (struct chatter *)(focusable->cookie); + struct chatter_tab *tab; short n; TEIdle(chatter->input_te); - for (n = 0; n < chatter->nconns; n++) { - if (!irc_process(&chatter->conns[n])) - /* conn list might have changed */ - return; + + SLIST_FOREACH(tab, &chatter->tabs_list, list) { + irc_process(tab->conn); } } @@ -377,6 +363,7 @@ void chatter_update(struct focusable *focusable, EventRecord *event) { struct chatter *chatter = (struct chatter *)(focusable->cookie); + struct chatter_tab *tab = chatter->current_tab; GrafPtr old_port; Rect r; short what = -1; @@ -394,30 +381,36 @@ chatter_update(struct focusable *focusable, EventRecor EraseRect(&chatter->win->portRect); - r = (*(chatter->nick_list))->rView; - LUpdate(chatter->win->visRgn, chatter->nick_list); + if (tab->nick_list) { + r = (*(tab->nick_list))->rView; + LUpdate(chatter->win->visRgn, tab->nick_list); + InsetRect(&r, -1, -1); + FrameRect(&r); + } + + r = (*(tab->messages_te))->viewRect; InsetRect(&r, -1, -1); FrameRect(&r); - - r = (*(chatter->messages_te))->viewRect; + TEUpdate(&(*(tab->messages_te))->viewRect, tab->messages_te); + + r = (*(chatter->input_te))->viewRect; InsetRect(&r, -1, -1); FrameRect(&r); - - TEUpdate(&(*(chatter->messages_te))->viewRect, - chatter->messages_te); TEUpdate(&(*(chatter->input_te))->viewRect, chatter->input_te); DrawControls(chatter->win); - chatter_draw_grow_icon(chatter); + chatter_draw_tab_bar(chatter); break; case activateEvt: if (event->modifiers & activeFlag) { - LActivate(true, chatter->nick_list); - TEActivate(chatter->messages_te); + if (tab->nick_list) + LActivate(true, tab->nick_list); + TEActivate(tab->messages_te); TEActivate(chatter->input_te); } else { - LActivate(false, chatter->nick_list); - TEDeactivate(chatter->messages_te); + if (tab->nick_list) + LActivate(false, tab->nick_list); + TEDeactivate(tab->messages_te); TEDeactivate(chatter->input_te); } break; @@ -425,6 +418,95 @@ chatter_update(struct focusable *focusable, EventRecor } void +chatter_draw_tab_bar(struct chatter *chatter) +{ + Rect r, r2; + RgnHandle clip; + short tab_width, tab_offset, n, width; + size_t len; + static const char no_connection[] = "Disconnected"; + struct chatter_tab *tab; + char *label; + + TextFont(geneva); + TextSize(9); + + tab_offset = 5; + + tab_width = (chatter->win->portRect.right - + chatter->win->portRect.left - tab_offset - tab_offset) / + chatter->ntabs; + if (tab_width > MAX_TAB_WIDTH) + tab_width = MAX_TAB_WIDTH; + + r = (*(chatter->input_te))->viewRect; + r.bottom = r.top - 1; + r.right = chatter->win->portRect.right; + r.top -= 14; + FillRect(&r, tab_bar_pattern); + + r.left = chatter->win->portRect.left + tab_offset; + r.top -= 1; + r.bottom -= 1; + r.right = r.left + tab_width; + + r2.left = r.left + 1; + r2.right = r.right - 1; + r2.top = r.top; + r2.bottom = r2.top + 1; + + GetClip(clip = NewRgn()); + + SLIST_FOREACH(tab, &chatter->tabs_list, list) { + tab->label_rect = r; + + FillRect(&r, white); + FrameRect(&r); + if (tab == chatter->current_tab) { + FrameRect(&r2); + FillRect(&r2, white); + tab->have_activity = false; + } + + ClipRect(&r); + + if (tab->conn) { + if (tab->channel) + label = tab->channel->name; + else + label = tab->conn->hostname; + } else + label = (char *)&no_connection; + + len = strlen(label); + if (tab->have_activity) + TextFace(bold | condense); + else + TextFace(0); + width = TextWidth(label, 0, len); + if (width > tab_width - 4) + width = tab_width - 4; + MoveTo(r.left + ((tab_width - width) / 2), r.bottom - 3); + DrawText(label, 0, len); + + SetClip(clip); + + r.left += tab_width + 2; + r.right += tab_width + 2; + r2.left += tab_width + 2; + r2.right += tab_width + 2; + } + + DisposeRgn(clip); + + TextFont(applFont); + TextSize(10); + TextFace(0); + + chatter_draw_grow_icon(chatter); +} + +void chatter_draw_grow_icon(struct chatter *chatter) { Rect r, *te; @@ -439,7 +521,7 @@ chatter_draw_grow_icon(struct chatter *chatter) te = &(*(chatter->input_te))->viewRect; r = win->portRect; - r.top = r.bottom - (te->bottom - te->top + 1) - 1; + r.top = r.bottom - (te->bottom - te->top + 1); r.left = r.right - SCROLLBAR_WIDTH + 1; r.right += 1; r.bottom += 1; @@ -451,10 +533,10 @@ chatter_draw_grow_icon(struct chatter *chatter) r.left = r.right - 9; FrameRect(&r); - r.top -= 5; + r.top -= 3; r.left -= 2; - r.bottom -= 5; - r.right -= 3; + r.bottom -= 4; + r.right -= 4; FillRect(&r, white); FrameRect(&r); @@ -465,42 +547,43 @@ void chatter_mouse_down(struct focusable *focusable, EventRecord *event) { struct chatter *chatter = (struct chatter *)(focusable->cookie); + struct chatter_tab *tab = chatter->current_tab, *ttab; Point p; Cell selected = { 0 }, now = { 0 }, t = { 0 }; ControlHandle control; Rect r; - int val, adj, page, ret; - short part; + short val, adj, ret, part, n; p = event->where; GlobalToLocal(&p); - r = (*(chatter->nick_list))->rView; - r.right += SCROLLBAR_WIDTH; - if (PtInRect(p, &r)) { - /* store what is selected now */ - ret = LGetSelect(true, &selected, chatter->nick_list); - - /* possibly highlight a new cell */ - LClick(p, event->modifiers, chatter->nick_list); - - if (selected.v != now.v) { - LSetSelect(false, selected, chatter->nick_list); - /* TODO: detect double-click, open query window? */ + if (tab->nick_list) { + r = (*(tab->nick_list))->rView; + r.right += SCROLLBAR_WIDTH; + if (PtInRect(p, &r)) { + /* store what is selected now */ + ret = LGetSelect(true, &selected, tab->nick_list); + + /* possibly highlight a new cell */ + LClick(p, event->modifiers, tab->nick_list); + + if (selected.v != now.v) { + LSetSelect(false, selected, tab->nick_list); + /* TODO: detect double-click, open query window? */ + } + + return; } - - return; } - r = (*(chatter->messages_te))->viewRect; + r = (*(tab->messages_te))->viewRect; if (PtInRect(p, &r)) { - TEClick(p, ((event->modifiers & shiftKey) != 0), - chatter->messages_te); - HLock(chatter->messages_te); - if ((*(chatter->messages_te))->selStart != - (*(chatter->messages_te))->selEnd) + TEClick(p, ((event->modifiers & shiftKey) != 0), tab->messages_te); + HLock(tab->messages_te); + if ((*(tab->messages_te))->selStart != + (*(tab->messages_te))->selEnd) TESetSelect(0, 0, chatter->input_te); - HUnlock(chatter->messages_te); + HUnlock(tab->messages_te); return; } @@ -511,19 +594,26 @@ chatter_mouse_down(struct focusable *focusable, EventR HLock(chatter->input_te); if ((*(chatter->input_te))->selStart != (*(chatter->input_te))->selEnd) - TESetSelect(0, 0, chatter->messages_te); + TESetSelect(0, 0, tab->messages_te); HUnlock(chatter->input_te); return; } + + SLIST_FOREACH(ttab, &chatter->tabs_list, list) { + if (PtInRect(p, &ttab->label_rect)) { + chatter_focus_tab(chatter, ttab); + return; + } + } switch (part = FindControl(p, chatter->win, &control)) { case inUpButton: case inDownButton: case inPageUp: case inPageDown: - if (control != chatter->messages_scroller) + if (control != tab->messages_scroller) break; - SetTrackControlTE(chatter->messages_te); + SetTrackControlTE(tab->messages_te); TrackControl(control, p, TrackMouseDownInControl); break; case inThumb: @@ -533,9 +623,9 @@ chatter_mouse_down(struct focusable *focusable, EventR adj = val - GetCtlValue(control); if (adj != 0) { val -= adj; - if (control == chatter->messages_scroller) - TEScroll(0, adj * TEGetHeight(0, 0, chatter->messages_te), - chatter->messages_te); + if (control == tab->messages_scroller) + TEScroll(0, adj * TEGetHeight(0, 0, tab->messages_te), + tab->messages_te); SetCtlValue(control, val); } break; @@ -567,6 +657,7 @@ bool chatter_menu(struct focusable *focusable, short menu, short item) { struct chatter *chatter = (struct chatter *)(focusable->cookie); + struct chatter_tab *tab = chatter->current_tab; switch (menu) { case EDIT_MENU_ID: @@ -576,14 +667,14 @@ chatter_menu(struct focusable *focusable, short menu, return true; case EDIT_MENU_COPY_ID: HLock(chatter->input_te); - HLock(chatter->messages_te); + HLock(tab->messages_te); if ((*(chatter->input_te))->selStart != (*(chatter->input_te))->selEnd) TECopy(chatter->input_te); - else if ((*(chatter->messages_te))->selStart != - (*(chatter->messages_te))->selEnd) - TECopy(chatter->messages_te); - HUnlock(chatter->messages_te); + else if ((*(tab->messages_te))->selStart != + (*(tab->messages_te))->selEnd) + TECopy(tab->messages_te); + HUnlock(tab->messages_te); HUnlock(chatter->input_te); return true; case EDIT_MENU_PASTE_ID: @@ -599,6 +690,7 @@ void chatter_key_down(struct focusable *focusable, EventRecord *event) { struct chatter *chatter = (struct chatter *)(focusable->cookie); + struct chatter_tab *tab = chatter->current_tab; TERec *te; char *input, k; @@ -615,12 +707,7 @@ chatter_key_down(struct focusable *focusable, EventRec TEIdle(chatter->input_te); HUnlock(te->hText); HUnlock(chatter->input_te); - if (chatter->cur_conn) - irc_process_input(chatter->cur_conn, chatter->cur_channel, - input); - else - chatter_printf(chatter, NULL, NULL, - "$B*!*$0 Not connected"); + irc_process_input(tab->conn, tab->channel, input); xfree(&input); } else { TEKey(k, chatter->input_te); @@ -628,11 +715,30 @@ chatter_key_down(struct focusable *focusable, EventRec } } +struct chatter_tab * +chatter_find_tab_for_conn_and_channel(struct chatter *chatter, + struct irc_connection *conn, struct irc_channel *channel) +{ + short n; + struct chatter_tab *tab; + + if (conn == NULL) + return NULL; + + SLIST_FOREACH(tab, &chatter->tabs_list, list) { + if (tab->conn == conn && channel == tab->channel) + return tab; + } + + return NULL; +} + size_t chatter_printf(struct chatter *chatter, struct irc_connection *conn, struct irc_channel *channel, const char *format, ...) { static char buf[600], buf_out[600]; + struct chatter_tab *tab = chatter->current_tab, *ttab; StScrpRec *scrp_rec; ScrpSTElement *scrp_ele, *prev_scrp_ele; RgnHandle savergn; @@ -641,13 +747,25 @@ chatter_printf(struct chatter *chatter, struct irc_con size_t len, n, buf_out_len, in_this_style; time_t now = Time; short line_height = 0; - bool stop_formatting = false; + bool stop_formatting = false, had_activity; len = 0; - /* TODO: find correct TE based on conn+channel or just conn */ + if (conn != NULL) { + SLIST_FOREACH(ttab, &chatter->tabs_list, list) { + if (ttab->conn == conn && channel == ttab->channel) { + tab = ttab; + break; + } + } + } - if ((*(chatter->messages_te))->teLength > 0) { + if (tab == NULL) + panic("no tab"); + + had_activity = tab->have_activity; + + if ((*(tab->messages_te))->teLength > 0) { buf[0] = '\r'; len++; } @@ -728,21 +846,21 @@ chatter_printf(struct chatter *chatter, struct irc_con return 0; } - HLock(chatter->messages_te); + HLock(tab->messages_te); /* check for TE overflow */ /* too many lines */ - if ((*(chatter->messages_te))->nLines >= - (nitems((*(chatter->messages_te))->lineStarts) - 10)) + if ((*(tab->messages_te))->nLines >= + (nitems((*(tab->messages_te))->lineStarts) - 10)) goto te_overflow; /* too many characters */ - if ((*(chatter->messages_te))->teLength >= (SHRT_MAX - 500)) + if ((*(tab->messages_te))->teLength >= (SHRT_MAX - 500)) goto te_overflow; /* rect of all lines is too tall */ - if ((*(chatter->messages_te))->nLines * line_height >= (SHRT_MAX - 100)) + if ((*(tab->messages_te))->nLines * line_height >= (SHRT_MAX - 100)) goto te_overflow; goto no_overflow; @@ -754,29 +872,47 @@ te_overflow: ClipRect(&zerorect); /* select some lines at the start, delete them */ - TESetSelect(0, (*(chatter->messages_te))->lineStarts[5], - chatter->messages_te); - TEDelete(chatter->messages_te); + TESetSelect(0, (*(tab->messages_te))->lineStarts[5], tab->messages_te); + TEDelete(tab->messages_te); /* scroll up, causing a repaint */ - TEPinScroll(0, INT_MAX, chatter->messages_te); + TEPinScroll(0, INT_MAX, tab->messages_te); /* then scroll back down to what it looked like before we did anything */ - TEPinScroll(0, -INT_MAX, chatter->messages_te); + TEPinScroll(0, -INT_MAX, tab->messages_te); /* resume normal drawing */ SetClip(savergn); DisposeRgn(savergn); no_overflow: - TESetSelect(SHRT_MAX, SHRT_MAX, chatter->messages_te); - TEStylInsert(buf_out, buf_out_len, scrp_rec_h, chatter->messages_te); + if (chatter->current_tab != tab) { + savergn = NewRgn(); + GetClip(savergn); + /* create an empty clip region so all TE updates are hidden */ + ClipRect(&zerorect); + } + + TESetSelect(SHRT_MAX, SHRT_MAX, tab->messages_te); + TEStylInsert(buf_out, buf_out_len, scrp_rec_h, tab->messages_te); HUnlock(scrp_rec_h); - HUnlock(chatter->messages_te); + HUnlock(tab->messages_te); - chatter_autoscroll(chatter, chatter->messages_te, - chatter->messages_scroller); + chatter_autoscroll(chatter, tab->messages_te, tab->messages_scroller); + if (chatter->current_tab == tab) + tab->have_activity = false; + else { + /* resume normal drawing */ + SetClip(savergn); + DisposeRgn(savergn); + + if (!had_activity) { + tab->have_activity = true; + chatter_draw_tab_bar(chatter); + } + } + return buf_out_len; } @@ -789,36 +925,41 @@ chatter_autoscroll(struct chatter *chatter, TEHandle t 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_remove_channel(struct chatter *chatter, struct irc_channel *channel) { + struct chatter_tab *tab = NULL, *ttab = NULL, *next_tab = NULL; short n; - /* TODO: change active channel, update nick list, etc. */ + SLIST_FOREACH(ttab, &chatter->tabs_list, list) { + if (ttab->conn == channel->connection) { + if (ttab->channel == channel) + tab = ttab; + else + next_tab = ttab; + } + } + if (tab != NULL) { + LDispose(tab->nick_list); + DisposeControl(tab->messages_scroller); + TEDispose(tab->messages_te); + SLIST_REMOVE(&chatter->tabs_list, tab, chatter_tab, list); + + if (chatter->current_tab == tab) + chatter->current_tab = NULL; + } + + if (next_tab == NULL) + chatter_draw_tab_bar(chatter); + else + chatter_focus_tab(chatter, next_tab); + /* * 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 @@ -828,12 +969,16 @@ chatter_sync_nick_list(struct chatter *chatter, struct size_t n, i, j, tj, ops, voices; short ret, cellv; struct irc_channel_nick *nick = NULL; - - /* TODO: find correct nick list for channel */ + struct chatter_tab *tab; - if (!just_summary && channel == chatter->cur_channel) { - LDoDraw(false, chatter->nick_list); - LDelRow(0, 0, chatter->nick_list); + tab = chatter_find_tab_for_conn_and_channel(chatter, + channel->connection, channel); + if (!tab) + return; + + if (!just_summary && tab == chatter->current_tab) { + LDoDraw(false, tab->nick_list); + LDelRow(0, 0, tab->nick_list); } if (channel) { @@ -857,7 +1002,7 @@ chatter_sync_nick_list(struct chatter *chatter, struct } if (just_summary) - chatter_printf(chatter, NULL, channel, + chatter_printf(chatter, channel->connection, 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, @@ -867,8 +1012,8 @@ chatter_sync_nick_list(struct chatter *chatter, struct } if (!just_summary) { - LDoDraw(true, chatter->nick_list); - InvalRect(&(*(chatter->nick_list))->rView); + LDoDraw(true, tab->nick_list); + InvalRect(&(*(tab->nick_list))->rView); } } @@ -878,10 +1023,13 @@ chatter_insert_to_nick_list(struct chatter *chatter, { Cell cell = { 0, 0 }; struct irc_channel_nick tnick; - ListHandle nick_list; + struct chatter_tab *tab; short j = 0; - nick_list = chatter->nick_list; + tab = chatter_find_tab_for_conn_and_channel(chatter, + channel->connection, channel); + if (!tab) + return; if (nick->flags & IRC_NICK_FLAG_OP) { tnick.nick[0] = '@'; @@ -893,6 +1041,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, nick_list); - LSetCell(&tnick.nick, j, cell, nick_list); + LAddRow(1, cell.v, tab->nick_list); + LSetCell(&tnick.nick, j, cell, tab->nick_list); } --- chatter.h Sun Dec 11 15:01:04 2022 +++ chatter.h Tue Jan 10 10:22:54 2023 @@ -72,29 +72,35 @@ #define WAIT_TYPE_FOREGROUND (1 << 2) #define WAIT_TYPE_URGENT (1 << 3) +struct chatter_tab { + SLIST_ENTRY(chatter_tab) list; + struct irc_connection *conn; + struct irc_channel *channel; + short index; + TEHandle messages_te; + ControlHandle messages_scroller; + ListHandle nick_list; + Rect label_rect; + bool have_activity; +}; +SLIST_HEAD(chatter_tabs_head, chatter_tab); + struct chatter { struct focusable *focusable; WindowPtr win; - TEHandle messages_te; - ControlHandle messages_scroller; TEHandle input_te; - ListHandle nick_list; - - short nconns; - struct irc_connection *conns; - struct irc_connection *cur_conn; - struct irc_channel *cur_channel; + bool quitting; + short ntabs; + struct chatter_tabs_head tabs_list; + struct chatter_tab *current_tab; }; void notify(void); void cancel_notification(void); -struct chatter * chatter_init(void); -void chatter_connect(struct chatter *chatter, const char *server, +struct chatter * chatter_init(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, struct irc_connection *conn, struct irc_channel *channel, const char *format, ...); @@ -102,7 +108,9 @@ void chatter_insert_to_nick_list(struct chatter *chatt 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); +struct chatter_tab * chatter_add_tab(struct chatter *chatter, + Rect *win_bounds, struct irc_connection *conn, + struct irc_channel *channel); void chatter_remove_channel(struct chatter *chatter, struct irc_channel *channel); --- irc.c Sun Dec 11 21:55:39 2022 +++ irc.c Tue Jan 10 10:15:56 2023 @@ -22,7 +22,6 @@ #include "irc.h" #include "strnatcmp.h" -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); @@ -30,7 +29,6 @@ size_t irc_printf(struct irc_connection *conn, const c char * irc_get_line(struct irc_connection *conn, size_t *retsize); struct irc_user * irc_parse_user(char *str); 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); @@ -50,79 +48,55 @@ void irc_parse_channel_mode_change(struct irc_channel #define IRC_CAN_SEND(conn) ((conn)->send_pb.ioResult <= 0) -bool -irc_process(struct irc_connection *conn) -{ - short oldstate; - - if (conn->state >= IRC_STATE_CONNECTED) { - if (irc_recv(conn) < 0) - return false; - } - - oldstate = conn->state; - - switch (conn->state) { - case IRC_STATE_UNINITED: - conn->state = IRC_STATE_CONNECTING; - irc_connect(conn); - break; - case IRC_STATE_CONNECTED: - irc_login(conn); - break; - case IRC_STATE_IDLE: - if (!irc_process_server(conn)) - return false; - break; - } - - if (conn->state != oldstate) - chatter_update_titlebar(conn->chatter); - - return true; -} +struct irc_connections_head irc_connections_list = + SLIST_HEAD_INITIALIZER(irc_connections_list); -short -irc_verify_state(struct irc_connection *conn, short state) +struct irc_connection * +irc_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) { - if (conn->state != state) { - warn("Bad IRC state (in %d, expected %d)", conn->state, state); - conn->state = IRC_STATE_DISCONNECTED; - return 0; - } - - return 1; -} - -void -irc_connect(struct irc_connection *conn) -{ + struct irc_connection *conn; char ip_str[] = "255.255.255.255"; char *retname; ip_addr ip = 0, local_ip = 0; - ip_port port, local_port = 0; + ip_port local_port = 0; + size_t len; short err; - - if (!irc_verify_state(conn, IRC_STATE_CONNECTING)) - return; - + + if ((err = _TCPInit()) != 0) + panic("TCPInit failed (%d)", err); + + conn = xmalloczero(sizeof(struct irc_connection), "irc_connection"); + SLIST_APPEND(&irc_connections_list, conn, irc_connection, list); + SLIST_INIT(&conn->channels_list); + conn->chatter = chatter; + conn->state = IRC_STATE_DISCONNECTED; + 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_printf(conn->chatter, conn, NULL, "$B***$0 Connecting to $B%s:%d$0...", conn->hostname, conn->port); - if ((err = _TCPCreate(&conn->send_pb, &conn->stream, - (Ptr)conn->tcp_buf, sizeof(conn->tcp_buf), nil, nil, nil, - false)) != noErr) { + 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; + return conn; } 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; + conn->state = IRC_STATE_DEAD; + return conn; } long2ip(ip, ip_str); @@ -132,8 +106,8 @@ irc_connect(struct irc_connection *conn) 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; + conn->state = IRC_STATE_DEAD; + return conn; } chatter_printf(conn->chatter, conn, NULL, @@ -141,42 +115,107 @@ irc_connect(struct irc_connection *conn) conn->port); conn->state = IRC_STATE_CONNECTED; + + 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(conn->line, sizeof(conn->line), + "NICK %s\r\n", conn->nick); + irc_send(conn, conn->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); + + return conn; } void +irc_close_connection(struct irc_connection *conn) +{ + if (conn->stream) { + _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); + conn->stream = 0; + } + + conn->state = IRC_STATE_DISCONNECTED; +} + +void irc_dealloc_connection(struct irc_connection *conn) { + struct irc_connection *tconn; short n; - while (conn->nchannels > 0) - irc_dealloc_channel(&conn->channels[0]); + irc_close_connection(conn); - _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); + while (!SLIST_EMPTY(&conn->channels_list)) + irc_dealloc_channel(SLIST_FIRST(&conn->channels_list)); - chatter_dealloc_connection(conn->chatter, conn); + 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); + + SLIST_REMOVE(&irc_connections_list, conn, irc_connection, list); + + xfree(&conn); } void -irc_close(struct irc_connection *conn) +irc_process(struct irc_connection *conn) { - if (conn->state == IRC_STATE_DISCONNECTED) - return; - - 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 was_state = conn->state; + + if (conn->state >= IRC_STATE_CONNECTED) + irc_recv(conn); + + switch (conn->state) { + case IRC_STATE_DISCONNECTED: + break; + case IRC_STATE_DEAD: + irc_close_connection(conn); + break; + case IRC_STATE_CONNECTED: + irc_process_server(conn); + break; + } } short +irc_verify_state(struct irc_connection *conn, short state) +{ + if (conn->state != state) { + warn("Bad IRC state (in %d, expected %d)", conn->state, state); + conn->state = IRC_STATE_DEAD; + return 0; + } + + return 1; +} + +short irc_recv(struct irc_connection *conn) { unsigned short rlen; short error, rerror, n; + if (conn->state < IRC_STATE_CONNECTING) + return 0; + error = _TCPStatus(&conn->rcv_pb, conn->stream, &conn->status_pb, nil, nil, false); @@ -191,7 +230,7 @@ irc_recv(struct irc_connection *conn) if (rerror) { chatter_printf(conn->chatter, conn, NULL, "$B*!* TCPRecv failed (%d), disconnecting$0", error); - irc_dealloc_connection(conn); + conn->state = IRC_STATE_DEAD; return -1; } @@ -205,7 +244,7 @@ irc_recv(struct irc_connection *conn) chatter_printf(conn->chatter, conn, NULL, "$B*!* TCPStatus failed: %d$0", error); - irc_dealloc_connection(conn); + conn->state = IRC_STATE_DEAD; return -1; } @@ -220,17 +259,17 @@ irc_send(struct irc_connection *conn, char *line, size if (size > sizeof(conn->obuf)) panic("irc_send: too much data %lu", size); - if (conn->state == IRC_STATE_DISCONNECTED) - return 0; + if (!irc_verify_state(conn, IRC_STATE_CONNECTED)) + return; while (!IRC_CAN_SEND(conn)) SystemTask(); if (conn->send_pb.ioResult < 0) { chatter_printf(conn->chatter, conn, NULL, - "$B*!* TCPSend failed: %d$0", + "$B*!* TCPSend failed (%d), disconnecting$0", conn->send_pb.ioResult); - irc_dealloc_connection(conn); + conn->state = IRC_STATE_DEAD; return 0; } @@ -250,8 +289,8 @@ irc_send(struct irc_connection *conn, char *line, size true); if (error) { chatter_printf(conn->chatter, conn, NULL, - "$B*!* TCPSend failed: %d$0", error); - irc_dealloc_connection(conn); + "$B*!* TCPSend failed (%d), disconnecting$0", error); + conn->state = IRC_STATE_DEAD; return 0; } @@ -280,34 +319,6 @@ irc_printf(struct irc_connection *conn, const char *fo return len; } -void -irc_login(struct irc_connection *conn) -{ - size_t len; - - if (!irc_verify_state(conn, IRC_STATE_CONNECTED)) - return; - - if (!IRC_CAN_SEND(conn)) - return; - - 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(conn->line, sizeof(conn->line), - "NICK %s\r\n", conn->nick); - irc_send(conn, conn->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); - - conn->state = IRC_STATE_IDLE; -} - struct irc_user * irc_parse_user(char *str) { @@ -365,14 +376,15 @@ irc_get_line(struct irc_connection *conn, size_t *rets struct irc_channel * irc_find_channel(struct irc_connection *conn, char *channame) { + struct irc_channel *channel; 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]; + SLIST_FOREACH(channel, &conn->channels_list, list) { + if (strcasecmp(channel->name, channame) == 0) + return channel; } return NULL; @@ -393,11 +405,6 @@ irc_process_server(struct irc_connection *conn) return false; memset(&msg, 0, sizeof(msg)); - -#if 0 /* XXX: what is this for? */ - if (strstr(line, "services") != 0) - msg.code = 1; -#endif word = strsep(&line, " "); @@ -508,7 +515,7 @@ irc_process_server(struct irc_connection *conn) chatter_printf(conn->chatter, conn, NULL, "$B*!* Disconnected$0 from $B%s$0:$/ %s", conn->hostname, msg.msg); - irc_dealloc_connection(conn); + conn->state = IRC_STATE_DEAD; return true; } if (strcmp(msg.cmd, "JOIN") == 0) { @@ -544,15 +551,14 @@ irc_process_server(struct irc_connection *conn) /* we died :( */ warn("%s (%s@%s) has been killed: %s", user->nick, user->username, user->hostname, msg.msg); - irc_dealloc_connection(conn); + conn->state = IRC_STATE_DEAD; return false; } - for (n = 0; n < conn->nchannels; n++) { - if (!irc_nick_is_in_channel(&conn->channels[n], - user->nick)) + SLIST_FOREACH(channel, &conn->channels_list, list) { + if (!irc_nick_is_in_channel(channel, user->nick)) continue; - chatter_printf(conn->chatter, conn, &conn->channels[n], + chatter_printf(conn->chatter, conn, channel, "$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); @@ -595,8 +601,7 @@ irc_process_server(struct irc_connection *conn) } if (strcmp(msg.cmd, "NICK") == 0) { user = irc_parse_user(msg.source); - for (n = 0; n < conn->nchannels; n++) { - channel = &conn->channels[n]; + SLIST_FOREACH(channel, &conn->channels_list, list) { if (!irc_nick_is_in_channel(channel, user->nick)) continue; @@ -623,13 +628,14 @@ irc_process_server(struct irc_connection *conn) channel = irc_find_channel(conn, msg.arg[0]); if (strcmp(user->nick, conn->nick) == 0) { irc_dealloc_channel(channel); - channel = NULL; - } else + /* we don't need to print anything */ + } 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]); + 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) { @@ -638,8 +644,7 @@ irc_process_server(struct irc_connection *conn) } if (strcmp(msg.cmd, "QUIT") == 0) { user = irc_parse_user(msg.source); - for (n = 0; n < conn->nchannels; n++) { - channel = &conn->channels[n]; + SLIST_FOREACH(channel, &conn->channels_list, list) { if (!irc_nick_is_in_channel(channel, user->nick)) continue; @@ -649,7 +654,10 @@ irc_process_server(struct irc_connection *conn) user->nick, user->username, user->hostname, msg.msg); } if (strcmp(user->nick, conn->nick) == 0) { - irc_dealloc_connection(conn); + chatter_printf(conn->chatter, conn, NULL, + "$B*** %s ($0%s@%s$B)$0 has quit:$/ %s", + user->nick, user->username, user->hostname, msg.msg); + conn->state = IRC_STATE_DEAD; return false; } return true; @@ -831,6 +839,9 @@ irc_process_input(struct irc_connection *conn, struct char *arg0, *arg1; size_t n; + if (conn == NULL || conn->state < IRC_STATE_CONNECTED) + goto not_connected; + if (str[0] != '/') { if (channel == NULL) goto not_in_channel; @@ -847,6 +858,8 @@ irc_process_input(struct irc_connection *conn, struct arg0 = strsep(&str, " "); if (strcasecmp(arg0, "join") == 0) { + if (conn == NULL) + goto not_connected; if (str == NULL) goto not_enough_params; /* @@ -862,6 +875,8 @@ irc_process_input(struct irc_connection *conn, struct return; } if (strcasecmp(arg0, "me") == 0) { + if (conn == NULL) + goto not_connected; if (str == NULL) goto not_enough_params; if (channel == NULL) @@ -874,6 +889,8 @@ irc_process_input(struct irc_connection *conn, struct return; } if (strcasecmp(arg0, "msg") == 0) { + if (conn == NULL) + goto not_connected; arg1 = strsep(&str, " "); if (arg1 == NULL || str == NULL) goto not_enough_params; @@ -884,12 +901,16 @@ irc_process_input(struct irc_connection *conn, struct return; } if (strcasecmp(arg0, "nick") == 0) { + if (conn == NULL) + goto not_connected; if (str == NULL) goto not_enough_params; irc_printf(conn, "NICK %s\r\n", str); return; } if (strcasecmp(arg0, "part") == 0) { + if (conn == NULL) + goto not_connected; if (str == NULL && channel) irc_printf(conn, "PART %s\r\n", channel->name); else if (str) @@ -899,16 +920,22 @@ irc_process_input(struct irc_connection *conn, struct return; } if (strcasecmp(arg0, "quit") == 0) { + if (conn == NULL) + goto not_connected; irc_printf(conn, "QUIT :%s\r\n", str == NULL ? "Quitting" : str); return; } if (strcasecmp(arg0, "quote") == 0) { + if (conn == NULL) + goto not_connected; if (str == NULL) goto not_enough_params; irc_printf(conn, "%s\r\n", str); return; } if (strcasecmp(arg0, "whois") == 0) { + if (conn == NULL) + goto not_connected; if (str == NULL) goto not_enough_params; irc_printf(conn, "WHOIS %s\r\n", str); @@ -921,41 +948,43 @@ irc_process_input(struct irc_connection *conn, struct not_enough_params: chatter_printf(conn->chatter, conn, NULL, - "$B***$0 Not enough parameters given"); + "$B*!*$0 Not enough parameters given"); return; +not_connected: + chatter_printf(conn->chatter, conn, NULL, + "$B*!*$0 Not connected"); + return; not_in_channel: chatter_printf(conn->chatter, conn, NULL, - "$B***$0 Cannot send (not in a channel)"); + "$B*!*$0 Cannot send (not in a channel)"); return; } struct irc_channel * irc_create_channel(struct irc_connection *conn, char *channame) { - struct irc_channel *channel = NULL; + struct irc_channel *channel = NULL, *tchannel; short n; - for (n = 0; n < conn->nchannels; n++) { - channel = &conn->channels[n]; - + SLIST_FOREACH(channel, &conn->channels_list, list) { if (strcasecmp(channel->name, channame) == 0) { + /* TODO: chatter_switch_to_channel(channel) */ +#if 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); +#endif return channel; } } - 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 = xmalloczero(sizeof(struct irc_channel), "irc_channel"); + SLIST_APPEND(&conn->channels_list, channel, irc_channel, list); channel->connection = conn; strlcpy(channel->name, channame, sizeof(channel->name)); - channel->chatter = chatter_add_channel(channel); + channel->chatter = conn->chatter; + chatter_add_tab(channel->chatter, NULL, conn, channel); return channel; } @@ -964,40 +993,16 @@ void irc_dealloc_channel(struct irc_channel *channel) { struct irc_connection *conn = channel->connection; + struct irc_channel *tchannel; 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]; - } + SLIST_REMOVE(&conn->channels_list, channel, irc_channel, list); chatter_update_titlebar(chatter); } @@ -1010,7 +1015,7 @@ irc_parse_names(struct irc_channel *channel, char *lin bool last = false; short flags; - LDoDraw(false, channel->chatter->nick_list); +// LDoDraw(false, channel->chatter->nick_list); for (;;) { nick = strsep(&line, " "); @@ -1033,8 +1038,8 @@ irc_parse_names(struct irc_channel *channel, char *lin break; } - LDoDraw(true, channel->chatter->nick_list); - InvalRect(&(*(channel->chatter->nick_list))->rView); +// LDoDraw(true, channel->chatter->nick_list); +// InvalRect(&(*(channel->chatter->nick_list))->rView); } void @@ -1148,7 +1153,7 @@ irc_remove_nick_from_channel(struct irc_channel *chann channel->nnicks--; cnick->nick[0] = '\0'; - LDelRow(1, cidx, channel->chatter->nick_list); +// LDelRow(1, cidx, channel->chatter->nick_list); return; } } --- irc.h Sun Dec 11 16:33:59 2022 +++ irc.h Mon Jan 9 15:49:34 2023 @@ -19,13 +19,13 @@ #include "chatter.h" #include "tcp.h" +#include "util.h" enum { - IRC_STATE_UNINITED = 0, - IRC_STATE_DISCONNECTED, + IRC_STATE_DISCONNECTED = 0, + IRC_STATE_DEAD, IRC_STATE_CONNECTING, - IRC_STATE_CONNECTED, - IRC_STATE_IDLE + IRC_STATE_CONNECTED }; #define IRC_MSG_MAX_ARGS 6 @@ -53,17 +53,20 @@ struct irc_channel_nick { }; struct irc_channel { - char name[64]; + SLIST_ENTRY(irc_channel) list; struct chatter *chatter; struct irc_connection *connection; struct irc_channel_nick *nicks; size_t nnicks; size_t nicks_size; short first_nick; + char name[64]; char topic[400]; }; +SLIST_HEAD(irc_channels_head, irc_channel); struct irc_connection { + SLIST_ENTRY(irc_connection) list; short state; struct chatter *chatter; char *hostname; @@ -74,7 +77,7 @@ struct irc_connection { char *realname; char *channel_autojoin; bool did_autojoin; - struct irc_channel *channels; + struct irc_channels_head channels_list; short nchannels; TCPiopb rcv_pb, send_pb, close_pb; TCPStatusPB status_pb; @@ -88,15 +91,19 @@ struct irc_connection { /* docs say 4*MTU+1024, but MTU will probably be <1500 */ unsigned char tcp_buf[(4 * 1500) + 1024]; }; +SLIST_HEAD(irc_connections_head, irc_connection); +extern struct irc_connections_head irc_connections_list; -bool irc_process(struct irc_connection *conn); +struct irc_connection * irc_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 irc_close_connection(struct irc_connection *conn); +void irc_dealloc_connection(struct irc_connection *conn); +void 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 Fri Dec 2 15:44:51 2022 +++ main.c Mon Jan 9 16:42:00 2023 @@ -162,7 +162,7 @@ main(void) case inGrow: if (event_win != FrontWindow() && found_focusable) { cancel_notification(); - show_focusable(found_focusable); + focusable_show(found_focusable); } if (found_focusable && found_focusable->resize) found_focusable->resize(found_focusable, &event); @@ -170,13 +170,13 @@ main(void) case inGoAway: if (TrackGoAway(event_win, event.where) && found_focusable && found_focusable->close) - found_focusable->close(found_focusable, &event); + found_focusable->close(found_focusable); break; case inContent: if (event_win != FrontWindow()) { if (found_focusable) { cancel_notification(); - show_focusable(found_focusable); + focusable_show(found_focusable); } } if (found_focusable && found_focusable->mouse_down) @@ -269,13 +269,16 @@ show_connect_dialog(void) HLock(h); if (cf->type == CONFIG_TYPE_PASSWORD) { size = (*h)[0]; + if (size >= sizeof(cf->password_storage) - 1) + size = 0; for (m = 0; m < size; m++) cf->password_storage[m] = '•'; cf->password_storage[m] = '\0'; CtoPstr(cf->password_storage); SetIText(ihandle, cf->password_storage); - strlcpy(cf->password_storage, PtoCstr(*h), - sizeof(cf->password_storage)); + memcpy(cf->password_storage, *h, size); + cf->password_storage[size] = '\0'; + PtoCstr(cf->password_storage); } else { SetIText(ihandle, *h); } @@ -384,8 +387,7 @@ verify: DisposeDialog(dlg); ReleaseResource(dlgh); - chatter = chatter_init(); - chatter_connect(chatter, (char *)&server, port, (char *)&password, + chatter_init((char *)&server, port, (char *)&password, (char *)&nick, (char *)&ident, (char *)&realname, (char *)&channel); } @@ -425,15 +427,7 @@ handle_menu(long menu_id) break; case FILE_MENU_QUIT_ID: ret = true; - quit = true; - for (n = 0; n < nfocusables; n++) { - if (focusables[n] && focusables[n]->quit && - !focusables[n]->quit(focusables[n])) { - quit = false; - break; - } - } - if (quit) + if (focusables_quit()) ExitToShell(); break; } @@ -458,10 +452,15 @@ handle_exit(void) { short n; - for (n = 0; n < nfocusables; n++) { + while (nfocusables > 0) { if (focusables[n]->atexit) focusables[n]->atexit(focusables[n]); + else + focusable_close(focusables[n]); } + + while (!SLIST_EMPTY(&irc_connections_list)) + irc_dealloc_connection(SLIST_FIRST(&irc_connections_list)); } void