1 Commits
Author SHA1 Message Date
emmett1 f75d3c0cd0 updates 2026-06-14 11:56:40 +08:00
3 changed files with 102 additions and 36 deletions
+1
View File
@@ -1,2 +1,3 @@
sirc.o sirc.o
sirc sirc
CLAUDE.md
+2 -2
View File
@@ -499,7 +499,7 @@ PREFIX=/usr/local make install
.BR weechat (1), .BR weechat (1),
.BR nc (1), .BR nc (1),
.BR openssl (1) .BR openssl (1)
.SH AUTHORS .SH AUTHOR
Emmett and Claude. Emmett1 <me@emmett1.my>
.SH LICENSE .SH LICENSE
MIT MIT
+94 -29
View File
@@ -188,14 +188,14 @@ typedef struct {
char sasl_pass[256]; char sasl_pass[256];
/* runtime */ /* runtime */
int connected; volatile int connected;
volatile int net_stop; volatile int net_stop;
pthread_t net_tid; pthread_t net_tid;
pthread_mutex_t send_lock; pthread_mutex_t send_lock;
int sock; int sock;
SSL *ssl; SSL *ssl;
int reconnect_pending; volatile int reconnect_pending;
pthread_t reconnect_tid; pthread_t reconnect_tid;
int sasl_auth_sent; /* guard: only send AUTHENTICATE once per session */ int sasl_auth_sent; /* guard: only send AUTHENTICATE once per session */
@@ -208,11 +208,12 @@ typedef struct {
static Server g_srv[MAX_SERVERS]; static Server g_srv[MAX_SERVERS];
static int g_srv_count = 0; static int g_srv_count = 0;
static SSL_CTX *g_ssl_ctx = NULL; static SSL_CTX *g_ssl_ctx = NULL;
static pthread_mutex_t g_ssl_mutex = PTHREAD_MUTEX_INITIALIZER;
/* channels (flat array, each has a .srv index) */ /* channels (flat array, each has a .srv index) */
static Channel g_chans[MAX_CHANS_TOTAL]; static Channel g_chans[MAX_CHANS_TOTAL];
static int g_chan_count = 0; static int g_chan_count = 0;
static int g_active = 0; /* index into g_chans; -1 = no channels */ static int g_active = -1; /* index into g_chans; -1 = no channels */
/* global ignore list */ /* global ignore list */
static char g_ignore[MAX_IGNORE][MAX_NICK]; static char g_ignore[MAX_IGNORE][MAX_NICK];
@@ -254,6 +255,7 @@ static void str_upper(char *dst, const char *src, int n) {
static int str_icase_starts(const char *hay, const char *needle) { static int str_icase_starts(const char *hay, const char *needle) {
while (*needle) { while (*needle) {
if (!*hay) return 0;
if (tolower((unsigned char)*hay) != tolower((unsigned char)*needle)) if (tolower((unsigned char)*hay) != tolower((unsigned char)*needle))
return 0; return 0;
hay++; needle++; hay++; needle++;
@@ -284,7 +286,7 @@ static int chan_find(int srv, const char *name) {
static int chan_add(int srv, const char *name) { static int chan_add(int srv, const char *name) {
int i = chan_find(srv, name); int i = chan_find(srv, name);
if (i >= 0) return i; if (i >= 0) return i;
if (g_chan_count >= MAX_CHANS_TOTAL) return 0; if (g_chan_count >= MAX_CHANS_TOTAL) return -1;
i = g_chan_count++; i = g_chan_count++;
memset(&g_chans[i], 0, sizeof(Channel)); memset(&g_chans[i], 0, sizeof(Channel));
strncpy(g_chans[i].name, name, MAX_CHAN-1); strncpy(g_chans[i].name, name, MAX_CHAN-1);
@@ -345,7 +347,8 @@ static void chan_addline(Channel *ch, int flag, const char *text) {
/* get the status channel for a server (create if needed) */ /* get the status channel for a server (create if needed) */
static int srv_status_chan(int srv) { static int srv_status_chan(int srv) {
return chan_add(srv, "*status*"); int ci = chan_add(srv, "*status*");
return ci >= 0 ? ci : 0;
} }
static void srv_status_msg(int srv, const char *msg) { static void srv_status_msg(int srv, const char *msg) {
@@ -377,7 +380,7 @@ static int user_find(Channel *ch, const char *nick) {
static void chan_adduser(Channel *ch, char mode, const char *nick) { static void chan_adduser(Channel *ch, char mode, const char *nick) {
int idx = user_find(ch, nick); int idx = user_find(ch, nick);
char entry[MAX_NICK]; char entry[MAX_NICK] = {0};
entry[0] = mode ? mode : ' '; entry[0] = mode ? mode : ' ';
strncpy(entry+1, nick, MAX_NICK-2); strncpy(entry+1, nick, MAX_NICK-2);
if (idx >= 0) { if (idx >= 0) {
@@ -488,8 +491,13 @@ static void ignore_remove(const char *nick) {
/* ── event pipe ────────────────────────────────────────────────────────────── */ /* ── event pipe ────────────────────────────────────────────────────────────── */
static void ev_push(const Event *ev) { static void ev_push(const Event *ev) {
/* non-blocking pipe: retry once on EAGAIN so events aren't silently lost */
if (write(g_evpipe[1], ev, sizeof(Event)) < 0) {
struct timespec ts = {0, 10000000}; /* 10 ms */
nanosleep(&ts, NULL);
write(g_evpipe[1], ev, sizeof(Event)); write(g_evpipe[1], ev, sizeof(Event));
} }
}
static void ev_simple(EvType type, int srv, const char *nick, static void ev_simple(EvType type, int srv, const char *nick,
const char *chan, const char *text) { const char *chan, const char *text) {
@@ -521,8 +529,10 @@ static int srv_send_raw(int si, const char *line) {
static void srv_sendf(int si, const char *fmt, ...) { static void srv_sendf(int si, const char *fmt, ...) {
char buf[MAX_LINE]; char buf[MAX_LINE];
va_list ap; va_start(ap, fmt); va_list ap; va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap); int n = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap); va_end(ap);
if (n >= (int)sizeof(buf))
ev_simple(EV_ERROR, si, NULL, NULL, "send truncated (increase MAX_LINE)");
srv_send_raw(si, buf); srv_send_raw(si, buf);
} }
@@ -952,6 +962,9 @@ static void srv_close(int si) {
static int srv_connect(int si) { static int srv_connect(int si) {
Server *s=&g_srv[si]; Server *s=&g_srv[si];
/* close any previous connection first (e.g. leftover from /connect) */
if (s->use_tls && s->ssl) { SSL_shutdown(s->ssl); SSL_free(s->ssl); s->ssl=NULL; }
if (s->sock>=0) { close(s->sock); s->sock=-1; }
char portstr[8]; snprintf(portstr,sizeof(portstr),"%d",s->port); char portstr[8]; snprintf(portstr,sizeof(portstr),"%d",s->port);
struct addrinfo hints,*res,*r; struct addrinfo hints,*res,*r;
memset(&hints,0,sizeof(hints)); memset(&hints,0,sizeof(hints));
@@ -962,7 +975,7 @@ static int srv_connect(int si) {
int fd=socket(r->ai_family,r->ai_socktype,r->ai_protocol); int fd=socket(r->ai_family,r->ai_socktype,r->ai_protocol);
if (fd<0) continue; if (fd<0) continue;
int flags=fcntl(fd,F_GETFL,0); int flags=fcntl(fd,F_GETFL,0);
fcntl(fd,F_SETFL,flags|O_NONBLOCK); if (flags>=0) fcntl(fd,F_SETFL,flags|O_NONBLOCK);
int rc=connect(fd,r->ai_addr,r->ai_addrlen); int rc=connect(fd,r->ai_addr,r->ai_addrlen);
if (rc==0||errno==EINPROGRESS) { if (rc==0||errno==EINPROGRESS) {
fd_set wfds; FD_ZERO(&wfds); FD_SET(fd,&wfds); fd_set wfds; FD_ZERO(&wfds); FD_SET(fd,&wfds);
@@ -978,16 +991,20 @@ static int srv_connect(int si) {
freeaddrinfo(res); freeaddrinfo(res);
if (s->sock<0) return -1; if (s->sock<0) return -1;
int flags=fcntl(s->sock,F_GETFL,0); int flags=fcntl(s->sock,F_GETFL,0);
fcntl(s->sock,F_SETFL,flags&~O_NONBLOCK); if (flags>=0) fcntl(s->sock,F_SETFL,flags&~O_NONBLOCK);
if (s->use_tls) { if (s->use_tls) {
pthread_mutex_lock(&g_ssl_mutex);
if (!g_ssl_ctx) { if (!g_ssl_ctx) {
SSL_library_init(); SSL_load_error_strings(); SSL_library_init(); SSL_load_error_strings();
g_ssl_ctx=SSL_CTX_new(TLS_client_method()); g_ssl_ctx=SSL_CTX_new(TLS_client_method());
if (!g_ssl_ctx) return -1; if (g_ssl_ctx) {
SSL_CTX_set_verify(g_ssl_ctx,SSL_VERIFY_PEER,NULL); SSL_CTX_set_verify(g_ssl_ctx,SSL_VERIFY_PEER,NULL);
SSL_CTX_set_default_verify_paths(g_ssl_ctx); SSL_CTX_set_default_verify_paths(g_ssl_ctx);
} }
}
pthread_mutex_unlock(&g_ssl_mutex);
if (!g_ssl_ctx) return -1;
s->ssl=SSL_new(g_ssl_ctx); s->ssl=SSL_new(g_ssl_ctx);
SSL_set_fd(s->ssl,s->sock); SSL_set_fd(s->ssl,s->sock);
SSL_set_tlsext_host_name(s->ssl,s->host); SSL_set_tlsext_host_name(s->ssl,s->host);
@@ -1004,6 +1021,10 @@ static void *net_thread(void *arg) {
NetArg *na=(NetArg*)arg; int si=na->si; free(na); NetArg *na=(NetArg*)arg; int si=na->si; free(na);
Server *s=&g_srv[si]; Server *s=&g_srv[si];
/* snapshot config that may be changed concurrently by main thread */
char mynick[MAX_NICK];
strncpy(mynick, s->nick, MAX_NICK-1); mynick[MAX_NICK-1]='\0';
char buf[MAX_LINE]; char buf[MAX_LINE];
snprintf(buf,sizeof(buf),"Connecting to %s:%d%s...", snprintf(buf,sizeof(buf),"Connecting to %s:%d%s...",
s->host,s->port,s->use_tls?" (TLS)":""); s->host,s->port,s->use_tls?" (TLS)":"");
@@ -1021,14 +1042,15 @@ static void *net_thread(void *arg) {
/* CAP LS 302: proper negotiation, handler sends CAP REQ :sasl on LS reply */ /* CAP LS 302: proper negotiation, handler sends CAP REQ :sasl on LS reply */
srv_send_raw(si,"CAP LS 302"); srv_send_raw(si,"CAP LS 302");
} else { } else {
srv_sendf(si,"NICK %s",s->nick); srv_sendf(si,"NICK %s",mynick);
srv_sendf(si,"USER %s 0 * :sirc",s->nick); srv_sendf(si,"USER %s 0 * :sirc",mynick);
} }
char readbuf[8192], linebuf[MAX_LINE*4]; char readbuf[8192], linebuf[MAX_LINE*4];
int linelen=0; int linelen=0;
time_t last_ping=time(NULL); time_t last_ping=time(NULL);
int intentional = 0;
while (!s->net_stop) { while (!s->net_stop) {
if (time(NULL)-last_ping>PING_INTERVAL) { if (time(NULL)-last_ping>PING_INTERVAL) {
srv_sendf(si,"PING :%s",s->host); srv_sendf(si,"PING :%s",s->host);
@@ -1062,8 +1084,11 @@ static void *net_thread(void *arg) {
last_ping=time(NULL); last_ping=time(NULL);
} }
if (s->net_stop) intentional = 1;
if (!intentional) {
srv_close(si); srv_close(si);
ev_simple(EV_RECONNECT, si, NULL, NULL, NULL); ev_simple(EV_RECONNECT, si, NULL, NULL, NULL);
}
return NULL; return NULL;
} }
@@ -1083,7 +1108,9 @@ static void *reconnect_thread(void *arg) {
static void start_net_thread(int si) { static void start_net_thread(int si) {
g_srv[si].net_stop=0; g_srv[si].net_stop=0;
NetArg *na=malloc(sizeof(NetArg)); na->si=si; NetArg *na=malloc(sizeof(NetArg)); na->si=si;
pthread_create(&g_srv[si].net_tid, NULL, net_thread, na); pthread_t tid;
pthread_create(&tid, NULL, net_thread, na);
pthread_detach(tid);
} }
static void schedule_reconnect(int si) { static void schedule_reconnect(int si) {
@@ -1110,7 +1137,7 @@ static int srv_alloc(void) {
memset(s,0,sizeof(Server)); memset(s,0,sizeof(Server));
strncpy(s->host,"irc.libera.chat",MAX_HOST-1); strncpy(s->host,"irc.libera.chat",MAX_HOST-1);
s->port=6697; s->port=6697;
strncpy(s->nick,"circ_user",MAX_NICK-1); strncpy(s->nick,"sirc_user",MAX_NICK-1);
s->use_tls=1; s->use_tls=1;
s->sock=-1; s->sock=-1;
pthread_mutex_init(&s->send_lock,NULL); pthread_mutex_init(&s->send_lock,NULL);
@@ -1140,7 +1167,7 @@ static void srv_add_autojoin(int si, const char *chanlist) {
} }
static void load_config(const char *path) { static void load_config(const char *path) {
char candidates[2][MAX_HOST]; char candidates[2][MAX_HOST] = {{0},{0}};
const char *home=getenv("HOME"); const char *home=getenv("HOME");
if (home) { if (home) {
snprintf(candidates[0],MAX_HOST,"%s/.sirc",home); snprintf(candidates[0],MAX_HOST,"%s/.sirc",home);
@@ -1152,7 +1179,7 @@ static void load_config(const char *path) {
if (!f) return; if (!f) return;
/* defaults that apply before any [server] block */ /* defaults that apply before any [server] block */
char def_nick[MAX_NICK]="circ_user"; char def_nick[MAX_NICK]="sirc_user";
/* current server being parsed; -1 = not inside a [server] block */ /* current server being parsed; -1 = not inside a [server] block */
int cur_si=-1; int cur_si=-1;
@@ -1460,6 +1487,10 @@ static void draw_input(void) {
} }
static void draw_all(void) { static void draw_all(void) {
if (g_active < 0 || g_chan_count == 0) {
/* no channels yet — only draw input line */
draw_input(); doupdate(); return;
}
draw_status_bar(); draw_channels(); draw_chat(); draw_status_bar(); draw_channels(); draw_chat();
draw_users(); draw_input(); doupdate(); draw_users(); draw_input(); doupdate();
} }
@@ -1552,6 +1583,7 @@ static void handle_event(const Event *ev) {
if (ev->chan[0]) { if (ev->chan[0]) {
int ci=chan_find(si,ev->chan); int ci=chan_find(si,ev->chan);
if (ci<0) ci=chan_add(si,ev->chan); if (ci<0) ci=chan_add(si,ev->chan);
if (ci<0) ci=srv_status_chan(si);
chan_addline(&g_chans[ci],F_STATUS,ev->text); chan_addline(&g_chans[ci],F_STATUS,ev->text);
if (ci==g_active) g_chans[ci].unread=0; if (ci==g_active) g_chans[ci].unread=0;
} else { } else {
@@ -1568,6 +1600,7 @@ static void handle_event(const Event *ev) {
const char *target=ev->chan, *text=ev->text; const char *target=ev->chan, *text=ev->text;
const char *dest=(target[0]=='#')?target:ev->nick; const char *dest=(target[0]=='#')?target:ev->nick;
int ci=chan_find(si,dest); if(ci<0) ci=chan_add(si,dest); int ci=chan_find(si,dest); if(ci<0) ci=chan_add(si,dest);
if (ci<0) ci=srv_status_chan(si);
if (text[0]=='\x01'&&strncmp(text+1,"ACTION",6)==0) { if (text[0]=='\x01'&&strncmp(text+1,"ACTION",6)==0) {
const char *act=text+8; const char *act=text+8;
@@ -1593,6 +1626,7 @@ static void handle_event(const Event *ev) {
case EV_JOIN: { case EV_JOIN: {
int ci=chan_find(si,ev->chan); if(ci<0) ci=chan_add(si,ev->chan); int ci=chan_find(si,ev->chan); if(ci<0) ci=chan_add(si,ev->chan);
if (ci<0) ci=srv_status_chan(si);
if (strcmp(ev->nick,s->nick)==0) { if (strcmp(ev->nick,s->nick)==0) {
g_chans[ci].user_count=0; g_chans[ci].user_count=0;
g_active=ci; g_active=ci;
@@ -1602,6 +1636,7 @@ static void handle_event(const Event *ev) {
char msg[MAX_LINE]; snprintf(msg,sizeof(msg),"-> %s joined",ev->nick); char msg[MAX_LINE]; snprintf(msg,sizeof(msg),"-> %s joined",ev->nick);
chan_addline(&g_chans[ci],F_JOIN,msg); chan_addline(&g_chans[ci],F_JOIN,msg);
chan_adduser(&g_chans[ci], 0, ev->nick); chan_adduser(&g_chans[ci], 0, ev->nick);
chan_sort_users(&g_chans[ci]);
} }
break; break;
} }
@@ -1619,8 +1654,11 @@ static void handle_event(const Event *ev) {
for (int i=0;i<g_chan_count;i++) { for (int i=0;i<g_chan_count;i++) {
if (g_chans[i].srv!=si) continue; if (g_chans[i].srv!=si) continue;
int found=0; int found=0;
for (int j=0;j<g_chans[i].user_count;j++) for (int j=0;j<g_chans[i].user_count;j++) {
if (strcasecmp(g_chans[i].users[j],ev->nick)==0){found=1;break;} const char *sn = g_chans[i].users[j];
if (*sn=='@'||*sn=='+'||*sn=='~'||*sn=='&'||*sn=='%'||*sn==' ') sn++;
if (strcasecmp(sn,ev->nick)==0){found=1;break;}
}
if (found) { if (found) {
char msg[MAX_LINE]; snprintf(msg,sizeof(msg),"<- %s quit (%s)",ev->nick,ev->text); char msg[MAX_LINE]; snprintf(msg,sizeof(msg),"<- %s quit (%s)",ev->nick,ev->text);
chan_addline(&g_chans[i],F_JOIN,msg); chan_addline(&g_chans[i],F_JOIN,msg);
@@ -1669,7 +1707,8 @@ static void handle_event(const Event *ev) {
char msg[MAX_LINE]; char msg[MAX_LINE];
snprintf(msg,sizeof(msg),"X %s was kicked by %s (%s)",ev->extra,ev->nick,ev->text); snprintf(msg,sizeof(msg),"X %s was kicked by %s (%s)",ev->extra,ev->nick,ev->text);
chan_addline(&g_chans[ci],F_JOIN,msg); chan_addline(&g_chans[ci],F_JOIN,msg);
chan_removeuser(&g_chans[ci],ev->extra); if (strcmp(ev->extra,s->nick)==0) chan_remove(ci);
else chan_removeuser(&g_chans[ci],ev->extra);
break; break;
} }
@@ -1768,16 +1807,18 @@ static void process_input(const char *text) {
if (strcmp(cmd,"/join")==0) { if (strcmp(cmd,"/join")==0) {
char ch[MAX_CHAN]; strncpy(ch,arg[0]?arg:g_srv[si].autojoin_count?g_srv[si].autojoin[0]:"#general",MAX_CHAN-1); char ch[MAX_CHAN]; strncpy(ch,arg[0]?arg:g_srv[si].autojoin_count?g_srv[si].autojoin[0]:"#general",MAX_CHAN-1);
if(ch[0]!='#'){memmove(ch+1,ch,strlen(ch)+1);ch[0]='#';} if(ch[0]!='#'){memmove(ch+1,ch,strlen(ch)+1);ch[0]='#';}
chan_add(si,ch); if(chan_add(si,ch)<0) srv_status_msg(si,"Channel list full.");
if(s->connected) srv_sendf(si,"JOIN %s",ch); if(s->connected) srv_sendf(si,"JOIN %s",ch);
else srv_status_msg(si,"[not connected]"); else srv_status_msg(si,"[not connected]");
} else if (strcmp(cmd,"/part")==0) { } else if (strcmp(cmd,"/part")==0) {
if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;}
const char *ch=arg[0]?arg:g_chans[g_active].name; const char *ch=arg[0]?arg:g_chans[g_active].name;
if(s->connected) srv_sendf(si,"PART %s :bye",ch); if(s->connected) srv_sendf(si,"PART %s :bye",ch);
else srv_status_msg(si,"[not connected]"); else srv_status_msg(si,"[not connected]");
} else if (strcmp(cmd,"/cycle")==0) { } else if (strcmp(cmd,"/cycle")==0) {
if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;}
const char *ch=arg[0]?arg:g_chans[g_active].name; const char *ch=arg[0]?arg:g_chans[g_active].name;
if(ch[0]!='#'){srv_status_msg(si,"Not in a channel.");return;} if(ch[0]!='#'){srv_status_msg(si,"Not in a channel.");return;}
if(!s->connected){srv_status_msg(si,"[not connected]");return;} if(!s->connected){srv_status_msg(si,"[not connected]");return;}
@@ -1795,13 +1836,14 @@ static void process_input(const char *text) {
} else if (strcmp(cmd,"/msg")==0) { } else if (strcmp(cmd,"/msg")==0) {
char tgt[MAX_NICK],msg[MAX_LINE]; char tgt[MAX_NICK],msg[MAX_LINE];
if(sscanf(arg,"%63s %[^\n]",tgt,msg)==2) { if(sscanf(arg,"%63s %[^\n]",tgt,msg)==2) {
chan_add(si,tgt); int ci=chan_add(si,tgt);
if(ci<0) ci=srv_status_chan(si);
if(s->connected) srv_sendf(si,"PRIVMSG %s :%s",tgt,msg); if(s->connected) srv_sendf(si,"PRIVMSG %s :%s",tgt,msg);
int ci=chan_find(si,tgt); { char full[MAX_LINE]; snprintf(full,sizeof(full),"<%s> %s",s->nick,msg); chan_addline(&g_chans[ci],F_ME,full); }
if(ci>=0){ char full[MAX_LINE]; snprintf(full,sizeof(full),"<%s> %s",s->nick,msg); chan_addline(&g_chans[ci],F_ME,full); }
} }
} else if (strcmp(cmd,"/me")==0) { } else if (strcmp(cmd,"/me")==0) {
if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;}
const char *cn=g_chans[g_active].name; const char *cn=g_chans[g_active].name;
if(cn[0]=='*'){srv_status_msg(si,"Not in a channel.");return;} if(cn[0]=='*'){srv_status_msg(si,"Not in a channel.");return;}
if(!s->connected){srv_status_msg(si,"[not connected]");return;} if(!s->connected){srv_status_msg(si,"[not connected]");return;}
@@ -1827,6 +1869,7 @@ static void process_input(const char *text) {
} else srv_status_msg(si,"Usage: /ctcp <nick> <command>"); } else srv_status_msg(si,"Usage: /ctcp <nick> <command>");
} else if (strcmp(cmd,"/names")==0) { } else if (strcmp(cmd,"/names")==0) {
if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;}
const char *ch=arg[0]?arg:g_chans[g_active].name; const char *ch=arg[0]?arg:g_chans[g_active].name;
if(ch[0]=='*'){srv_status_msg(si,"Not in a channel.");return;} if(ch[0]=='*'){srv_status_msg(si,"Not in a channel.");return;}
if(s->connected) srv_sendf(si,"NAMES %s",ch); if(s->connected) srv_sendf(si,"NAMES %s",ch);
@@ -1845,6 +1888,7 @@ static void process_input(const char *text) {
} }
} else if (strcmp(cmd,"/topic")==0) { } else if (strcmp(cmd,"/topic")==0) {
if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;}
const char *ch=g_chans[g_active].name; const char *ch=g_chans[g_active].name;
if(ch[0]=='*'){srv_status_msg(si,"Not in a channel.");return;} if(ch[0]=='*'){srv_status_msg(si,"Not in a channel.");return;}
if(!s->connected){srv_status_msg(si,"[not connected]");return;} if(!s->connected){srv_status_msg(si,"[not connected]");return;}
@@ -1857,6 +1901,7 @@ static void process_input(const char *text) {
srv_sendf(si,"WHOIS %s",arg); srv_sendf(si,"WHOIS %s",arg);
} else if (strcmp(cmd,"/who")==0) { } else if (strcmp(cmd,"/who")==0) {
if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;}
const char *tgt=arg[0]?arg:g_chans[g_active].name; const char *tgt=arg[0]?arg:g_chans[g_active].name;
if(!s->connected){srv_status_msg(si,"[not connected]");return;} if(!s->connected){srv_status_msg(si,"[not connected]");return;}
srv_sendf(si,"WHO %s",tgt); srv_sendf(si,"WHO %s",tgt);
@@ -1875,6 +1920,7 @@ static void process_input(const char *text) {
srv_send_raw(si,"AWAY"); srv_send_raw(si,"AWAY");
} else if (strcmp(cmd,"/invite")==0) { } else if (strcmp(cmd,"/invite")==0) {
if(g_active<0||g_active>=g_chan_count){srv_status_msg(si,"Not in a channel.");return;}
char tgt[MAX_NICK],ch[MAX_CHAN]; ch[0]='\0'; char tgt[MAX_NICK],ch[MAX_CHAN]; ch[0]='\0';
if(sscanf(arg,"%63s %63s",tgt,ch)<1){srv_status_msg(si,"Usage: /invite <nick> [#chan]");return;} if(sscanf(arg,"%63s %63s",tgt,ch)<1){srv_status_msg(si,"Usage: /invite <nick> [#chan]");return;}
if(!ch[0]) strncpy(ch,g_chans[g_active].name,MAX_CHAN-1); if(!ch[0]) strncpy(ch,g_chans[g_active].name,MAX_CHAN-1);
@@ -1892,7 +1938,11 @@ static void process_input(const char *text) {
} else if (strcmp(cmd,"/mode")==0) { } else if (strcmp(cmd,"/mode")==0) {
if(!s->connected){srv_status_msg(si,"[not connected]");return;} if(!s->connected){srv_status_msg(si,"[not connected]");return;}
if(!arg[0]) srv_sendf(si,"MODE %s",g_chans[g_active].name); if(!arg[0]) {
if(g_active<0||g_active>=g_chan_count||g_chans[g_active].name[0]=='*')
{srv_status_msg(si,"Not in a channel.");return;}
srv_sendf(si,"MODE %s",g_chans[g_active].name);
}
else srv_sendf(si,"MODE %s",arg); else srv_sendf(si,"MODE %s",arg);
} else if (strcmp(cmd,"/raw")==0||strcmp(cmd,"/quote")==0) { } else if (strcmp(cmd,"/raw")==0||strcmp(cmd,"/quote")==0) {
@@ -1911,7 +1961,11 @@ static void process_input(const char *text) {
g_srv[nsi].port=newport; g_srv[nsi].port=newport;
/* inherit nick from current server */ /* inherit nick from current server */
strncpy(g_srv[nsi].nick,s->nick,MAX_NICK-1); strncpy(g_srv[nsi].nick,s->nick,MAX_NICK-1);
chan_add(nsi,"*status*"); if(chan_add(nsi,"*status*")<0) {
srv_status_msg(si,"Channel list full.");
g_srv_count--; /* undo srv_alloc */
return;
}
start_net_thread(nsi); start_net_thread(nsi);
srv_status_fmt(nsi,"Connecting to %s:%d...",newhost,newport); srv_status_fmt(nsi,"Connecting to %s:%d...",newhost,newport);
@@ -1933,6 +1987,7 @@ static void process_input(const char *text) {
else srv_status_fmt(si,"%s is not ignored",arg); else srv_status_fmt(si,"%s is not ignored",arg);
} else if (strcmp(cmd,"/clear")==0) { } else if (strcmp(cmd,"/clear")==0) {
if(g_active<0||g_active>=g_chan_count) return;
g_chans[g_active].line_count=0; g_chans[g_active].line_count=0;
g_chans[g_active].line_head=0; g_chans[g_active].line_head=0;
g_chans[g_active].scroll=0; g_chans[g_active].scroll=0;
@@ -1999,10 +2054,17 @@ static void handle_key(int key) {
if (key=='\t') { tab_complete(); return; } if (key=='\t') { tab_complete(); return; }
tab_reset(); tab_reset();
if (g_active<0||g_active>=g_chan_count) {
/* no channels — only accept resize and printable input */
if (key=='\n'||key=='\r'||key==KEY_ENTER) {
g_input[0]='\0'; g_input_len=0; g_input_cur=0;
}
return;
}
Channel *ch=&g_chans[g_active]; Channel *ch=&g_chans[g_active];
if (key==14) { g_active=chan_next(g_active,1); g_chans[g_active].unread=0; g_chans[g_active].mention=0; return; } if (key==14) { g_active=chan_next(g_active,1); if(g_active>=0){g_chans[g_active].unread=0; g_chans[g_active].mention=0;} return; }
if (key==16) { g_active=chan_next(g_active,-1); g_chans[g_active].unread=0; g_chans[g_active].mention=0; return; } if (key==16) { g_active=chan_next(g_active,-1); if(g_active>=0){g_chans[g_active].unread=0; g_chans[g_active].mention=0;} return; }
if (key==KEY_PPAGE) { int ch2; getmaxyx(win_chat,ch2,(int){0}); ch->scroll+=ch2/2; return; } if (key==KEY_PPAGE) { int ch2; getmaxyx(win_chat,ch2,(int){0}); ch->scroll+=ch2/2; return; }
if (key==KEY_NPAGE) { int ch2; getmaxyx(win_chat,ch2,(int){0}); ch->scroll-=ch2/2; if(ch->scroll<0)ch->scroll=0; return; } if (key==KEY_NPAGE) { int ch2; getmaxyx(win_chat,ch2,(int){0}); ch->scroll-=ch2/2; if(ch->scroll<0)ch->scroll=0; return; }
@@ -2118,8 +2180,11 @@ int main(int argc, char **argv) {
if (cli_chan && !cli_host) srv_add_autojoin(i, cli_chan); if (cli_chan && !cli_host) srv_add_autojoin(i, cli_chan);
} }
/* create status channels */ /* create status channels — failures non-fatal; srv_status_chan falls back */
for(int i=0;i<g_srv_count;i++) chan_add(i,"*status*"); for(int i=0;i<g_srv_count;i++) {
if (chan_add(i,"*status*") < 0) break;
}
if (g_active < 0 && g_chan_count > 0) g_active = 0;
/* event pipe */ /* event pipe */
if(pipe(g_evpipe)<0){perror("pipe");return 1;} if(pipe(g_evpipe)<0){perror("pipe");return 1;}