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