├── .gitignore
├── Makefile
├── README.md
├── config.h
├── irc.c
└── license
/.gitignore:
--------------------------------------------------------------------------------
1 | irc
2 | *.sw[po]
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BIN = irc
2 |
3 | CFLAGS = -std=c99 -Os -D_POSIX_C_SOURCE=201112 -D_GNU_SOURCE -D_XOPEN_CURSES -D_XOPEN_SOURCE_EXTENDED=1 -D_DEFAULT_SOURCE -D_BSD_SOURCE
4 | LDFLAGS = -lncursesw -lssl -lcrypto
5 |
6 | all: ${BIN}
7 |
8 | install:
9 | install -Dm755 ${BIN} $(DESTDIR)$(PREFIX)/bin/${BIN}
10 |
11 | uninstall:
12 | rm -f $(DESTDIR)$(PREFIX)/bin/${BIN}
13 |
14 | clean:
15 | rm -f ${BIN} *.o
16 |
17 | .PHONY: all clean
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # icyrc
2 | > no bs irc client
3 |
4 | 
5 |
6 | Built on top of https://c9x.me/irc/.
7 |
8 | ## Installing
9 |
10 | Requires `ncurses` development files.
11 | Clone this repo and:
12 |
13 | ```
14 | $ make
15 | # make install
16 | ```
17 |
18 | Similarly, to uninstall:
19 |
20 | ```
21 | # make uninstall
22 | ```
23 |
24 | ## Usage
25 |
26 | ```
27 | usage: irc [-n NICK] [-u USER] [-s SERVER] [-p PORT] [-l LOGFILE ] [-t] [-h]
28 | ```
29 |
30 | The nick, user and password can be specified using `IRCNICK`,
31 | `USER` and `IRCPASS` environment variables.
32 |
33 | ### Commands
34 |
35 | - `/j #channel` — Join channel
36 | - `/l #channel` — Leave channel
37 | - `/me msg` — ACTION
38 | - `/q user msg` — Send private message
39 | - `/r something` — Send raw command
40 | - `/x` — Quit
41 |
42 | ### Hotkeys
43 |
44 | - Ctrl+n/p to cycle through buffers.
45 | - Emacs-like line editing commands: Ctrl+w/e/a etc.
46 | - PgUp and PgDn to scroll.
47 |
48 | ## Configuration
49 |
50 | In true suckless fashion, configuration is done via a `config.h` file.
51 | Recompile for changes to take effect.
52 |
--------------------------------------------------------------------------------
/config.h:
--------------------------------------------------------------------------------
1 | #define VERSION "icyrc 0.2 (https://github.com/icyphox/icyrc)"
2 |
3 | #define SCROLL 15
4 | #define INDENT 25
5 |
6 | /* uncomment to enable date formatting; prepends to each msg */
7 | // #define DATEFMT "%H:%M"
8 |
9 | /* normal msg "nick msg" */
10 | #define PFMT "%-15s %s"
11 | /* action msg "nick msg" */
12 | #define AFMT "* %-15s %s"
13 | /* highlight msg "nick msg" */
14 | #define PFMTHIGH "%-15s] %s"
15 |
16 | /* command that STDOUTs a password in a single line */
17 | #define PWCMD "pw -s ircpass"
18 |
19 | /* enable notifications (notify-send) */
20 |
21 | #define NOTIFY 1
22 |
23 | /* server */
24 | #define SRV "irc.icyphox.sh"
25 | /* port */
26 | #define PORT "6666"
27 |
28 |
--------------------------------------------------------------------------------
/irc.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #undef CTRL
26 | #define CTRL(x) (x & 037)
27 |
28 | #include "config.h"
29 |
30 | enum {
31 | ChanLen = 64,
32 | LineLen = 512,
33 | MaxChans = 16,
34 | BufSz = 2048,
35 | LogSz = 4096,
36 | MaxRecons = 10, /* -1 for infinitely many */
37 | UtfSz = 4,
38 | RuneInvalid = 0xFFFD,
39 | };
40 |
41 | typedef wchar_t Rune;
42 |
43 | static struct {
44 | int x;
45 | int y;
46 | WINDOW *sw, *mw, *iw;
47 | } scr;
48 |
49 | static struct Chan {
50 | char name[ChanLen];
51 | char *buf, *eol;
52 | int n; /* Scroll offset. */
53 | size_t sz; /* Size of buf. */
54 | char high; /* Nick highlight. */
55 | char new; /* New message. */
56 | char join; /* Channel was 'j'-oined. */
57 | } chl[MaxChans];
58 |
59 | static int ssl;
60 | static struct {
61 | int fd;
62 | SSL *ssl;
63 | SSL_CTX *ctx;
64 | } srv;
65 | static char nick[64];
66 | static int quit, winchg;
67 | static int nch, ch; /* Current number of channels, and current channel. */
68 | static char outb[BufSz], *outp = outb; /* Output buffer. */
69 | static FILE *logfp;
70 |
71 | static unsigned char utfbyte[UtfSz + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
72 | static unsigned char utfmask[UtfSz + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
73 | static Rune utfmin[UtfSz + 1] = { 0, 0, 0x80, 0x800, 0x10000};
74 | static Rune utfmax[UtfSz + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
75 |
76 | static void scmd(char *, char *, char *, char *);
77 | static void tdrawbar(void);
78 | static void tredraw(void);
79 | static void treset(void);
80 |
81 | static void
82 | panic(const char *m)
83 | {
84 | treset();
85 | fprintf(stderr, "error: %s\n", m);
86 | exit(1);
87 | }
88 |
89 | static size_t
90 | utf8validate(Rune *u, size_t i)
91 | {
92 | if (*u < utfmin[i] || *u > utfmax[i] || (0xD800 <= *u && *u <= 0xDFFF))
93 | *u = RuneInvalid;
94 | for (i = 1; *u > utfmax[i]; ++i)
95 | ;
96 | return i;
97 | }
98 |
99 | static Rune
100 | utf8decodebyte(unsigned char c, size_t *i)
101 | {
102 | for (*i = 0; *i < UtfSz + 1; ++(*i))
103 | if ((c & utfmask[*i]) == utfbyte[*i])
104 | return c & ~utfmask[*i];
105 | return 0;
106 | }
107 |
108 | static size_t
109 | utf8decode(char *c, Rune *u, size_t clen)
110 | {
111 | size_t i, j, len, type;
112 | Rune udecoded;
113 |
114 | *u = RuneInvalid;
115 | if (!clen)
116 | return 0;
117 | udecoded = utf8decodebyte(c[0], &len);
118 | if (len < 1 || len > UtfSz)
119 | return 1;
120 | for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
121 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
122 | if (type != 0)
123 | return j;
124 | }
125 | if (j < len)
126 | return 0;
127 | *u = udecoded;
128 | utf8validate(u, len);
129 | return len;
130 | }
131 |
132 | static char
133 | utf8encodebyte(Rune u, size_t i)
134 | {
135 | return utfbyte[i] | (u & ~utfmask[i]);
136 | }
137 |
138 | static size_t
139 | utf8encode(Rune u, char *c)
140 | {
141 | size_t len, i;
142 |
143 | len = utf8validate(&u, 0);
144 | if (len > UtfSz)
145 | return 0;
146 | for (i = len - 1; i != 0; --i) {
147 | c[i] = utf8encodebyte(u, 0);
148 | u >>= 6;
149 | }
150 | c[0] = utf8encodebyte(u, len);
151 | return len;
152 | }
153 |
154 | static void
155 | sndf(const char *fmt, ...)
156 | {
157 | va_list vl;
158 | size_t n, l = BufSz - (outp - outb);
159 |
160 | if (l < 2)
161 | return;
162 | va_start(vl, fmt);
163 | n = vsnprintf(outp, l - 2, fmt, vl);
164 | va_end(vl);
165 | outp += n > l - 2 ? l - 2 : n;
166 | *outp++ = '\r';
167 | *outp++ = '\n';
168 | }
169 |
170 | static int
171 | srd(void)
172 | {
173 | static char l[BufSz], *p = l;
174 | char *s, *usr, *cmd, *par, *data;
175 | int rd;
176 | if (p - l >= BufSz)
177 | p = l; /* Input buffer overflow, there should something better to do. */
178 | if (ssl)
179 | rd = SSL_read(srv.ssl, p, BufSz - (p - l));
180 | else
181 | rd = read(srv.fd, p, BufSz - (p - l));
182 | if (rd <= 0)
183 | return 0;
184 | p += rd;
185 | for (;;) { /* Cycle on all received lines. */
186 | if (!(s = memchr(l, '\n', p - l)))
187 | return 1;
188 | if (s > l && s[-1] == '\r')
189 | s[-1] = 0;
190 | *s++ = 0;
191 | if (*l == ':') {
192 | if (!(cmd = strchr(l, ' ')))
193 | goto lskip;
194 | *cmd++ = 0;
195 | usr = l + 1;
196 | } else {
197 | usr = 0;
198 | cmd = l;
199 | }
200 | if (!(par = strchr(cmd, ' ')))
201 | goto lskip;
202 | *par++ = 0;
203 | if ((data = strchr(par, ':')))
204 | *data++ = 0;
205 | scmd(usr, cmd, par, data);
206 | lskip:
207 | memmove(l, s, p - s);
208 | p -= s - l;
209 | }
210 | }
211 |
212 | static void
213 | sinit(const char *key, const char *nick, const char *user)
214 | {
215 | if (key)
216 | sndf("PASS %s", key);
217 | sndf("NICK %s", nick);
218 | sndf("USER %s 8 * :%s", user, user);
219 | sndf("MODE %s +i", nick);
220 | }
221 |
222 | static char *
223 | dial(const char *host, const char *service)
224 | {
225 | struct addrinfo hints, *res = NULL, *rp;
226 | int fd = -1, e;
227 |
228 | memset(&hints, 0, sizeof(hints));
229 | hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */
230 | hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */
231 | hints.ai_socktype = SOCK_STREAM;
232 | if ((e = getaddrinfo(host, service, &hints, &res)))
233 | return "Getaddrinfo failed.";
234 | for (rp = res; rp; rp = rp->ai_next) {
235 | if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1)
236 | continue;
237 | if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) {
238 | close(fd);
239 | continue;
240 | }
241 | break;
242 | }
243 | if (fd == -1)
244 | return "Cannot connect to host.";
245 | srv.fd = fd;
246 | if (ssl) {
247 | SSL_load_error_strings();
248 | SSL_library_init();
249 | srv.ctx = SSL_CTX_new(SSLv23_client_method());
250 | if (!srv.ctx)
251 | return "Could not initialize ssl context.";
252 | srv.ssl = SSL_new(srv.ctx);
253 | if (SSL_set_fd(srv.ssl, srv.fd) == 0
254 | || SSL_connect(srv.ssl) != 1)
255 | return "Could not connect with ssl.";
256 | }
257 | freeaddrinfo(res);
258 | return 0;
259 | }
260 |
261 | static void
262 | hangup(void)
263 | {
264 | if (srv.ssl) {
265 | SSL_shutdown(srv.ssl);
266 | SSL_free(srv.ssl);
267 | srv.ssl = 0;
268 | }
269 | if (srv.fd) {
270 | close(srv.fd);
271 | srv.fd = 0;
272 | }
273 | if (srv.ctx) {
274 | SSL_CTX_free(srv.ctx);
275 | srv.ctx = 0;
276 | }
277 | }
278 |
279 | static inline int
280 | chfind(const char *name)
281 | {
282 | int i;
283 |
284 | assert(name);
285 | for (i = nch - 1; i > 0; i--)
286 | if (!strcmp(chl[i].name, name))
287 | break;
288 | return i;
289 | }
290 |
291 | static int
292 | chadd(const char *name, int joined)
293 | {
294 | int n;
295 |
296 | if (nch >= MaxChans || strlen(name) >= ChanLen)
297 | return -1;
298 | if ((n = chfind(name)) > 0)
299 | return n;
300 | strcpy(chl[nch].name, name);
301 | chl[nch].sz = LogSz;
302 | chl[nch].buf = malloc(LogSz);
303 | if (!chl[nch].buf)
304 | panic("out of memory");
305 | chl[nch].eol = chl[nch].buf;
306 | chl[nch].n = 0;
307 | chl[nch].join = joined;
308 | if (joined)
309 | ch = nch;
310 | nch++;
311 | tdrawbar();
312 | return nch;
313 | }
314 |
315 | static int
316 | chdel(char *name)
317 | {
318 | int n;
319 |
320 | if (!(n = chfind(name)))
321 | return 0;
322 | nch--;
323 | free(chl[n].buf);
324 | memmove(&chl[n], &chl[n + 1], (nch - n) * sizeof(struct Chan));
325 | ch = nch - 1;
326 | tdrawbar();
327 | return 1;
328 | }
329 |
330 | static char *
331 | pushl(char *p, char *e)
332 | {
333 | int x, cl;
334 | char *w;
335 | Rune u[2];
336 | cchar_t cc;
337 |
338 | u[1] = 0;
339 | if ((w = memchr(p, '\n', e - p)))
340 | e = w + 1;
341 | w = p;
342 | x = 0;
343 | for (;;) {
344 | if (x >= scr.x) {
345 | waddch(scr.mw, '\n');
346 | for (x = 0; x < INDENT; x++)
347 | waddch(scr.mw, ' ');
348 | if (*w == ' ')
349 | w++;
350 | x += p - w;
351 | }
352 | if (p >= e || *p == ' ' || p - w + INDENT >= scr.x - 1) {
353 | while (w < p) {
354 | w += utf8decode(w, u, UtfSz);
355 | if (wcwidth(*u) > 0 || *u == '\n') {
356 | setcchar(&cc, u, 0, 0, 0);
357 | wadd_wch(scr.mw, &cc);
358 | }
359 | }
360 | if (p >= e)
361 | return e;
362 | }
363 | p += utf8decode(p, u, UtfSz);
364 | if ((cl = wcwidth(*u)) >= 0)
365 | x += cl;
366 | }
367 | }
368 |
369 | static void
370 | pushf(int cn, const char *fmt, ...)
371 | {
372 | struct Chan *const c = &chl[cn];
373 | size_t n, blen = c->eol - c->buf;
374 | va_list vl;
375 | time_t t;
376 | char *s;
377 | struct tm *tm, *gmtm;
378 |
379 | if (blen + LineLen >= c->sz) {
380 | c->sz *= 2;
381 | c->buf = realloc(c->buf, c->sz);
382 | if (!c->buf)
383 | panic("out of memory");
384 | c->eol = c->buf + blen;
385 | }
386 | t = time(0);
387 | if (!(tm = localtime(&t)))
388 | panic("localtime failed");
389 | #ifdef DATEFMT
390 | n = strftime(c->eol, LineLen, DATEFMT, tm);
391 | #endif
392 | if (!(gmtm = gmtime(&t)))
393 | panic("gmtime failed");
394 | c->eol[n++] = ' ';
395 | va_start(vl, fmt);
396 | s = c->eol + n;
397 | n += vsnprintf(s, LineLen - n - 1, fmt, vl);
398 | va_end(vl);
399 |
400 | if (logfp) {
401 | fprintf(logfp, "%-12.12s\t%04d-%02d-%02dT%02d:%02d:%02dZ\t%s\n",
402 | c->name,
403 | gmtm->tm_year + 1900, gmtm->tm_mon + 1, gmtm->tm_mday,
404 | gmtm->tm_hour, gmtm->tm_min, gmtm->tm_sec, s);
405 | fflush(logfp);
406 | }
407 |
408 | strcat(c->eol, "\n");
409 | if (n >= LineLen - 1)
410 | c->eol += LineLen - 1;
411 | else
412 | c->eol += n + 1;
413 | if (cn == ch && c->n == 0) {
414 | char *p = c->eol - n - 1;
415 |
416 | if (p != c->buf)
417 | waddch(scr.mw, '\n');
418 | pushl(p, c->eol - 1);
419 | wrefresh(scr.mw);
420 | }
421 | }
422 |
423 | char
424 | *strremove(char *str, const char *sub) {
425 | size_t len = strlen(sub);
426 | if (len > 0) {
427 | char *p = str;
428 | size_t size = 0;
429 | while ((p = strstr(p, sub)) != NULL) {
430 | size = (size == 0) ? (p - str) + strlen(p + len) + 1 : size - len;
431 | memmove(p, p + len, size - (p - str));
432 | }
433 | }
434 | return str;
435 | }
436 |
437 | static void
438 | scmd(char *usr, char *cmd, char *par, char *data)
439 | {
440 | int s, c;
441 | int pushed = 0;
442 | char *pm = strtok(par, " "), *chan;
443 | if (!usr)
444 | usr = "?";
445 | else {
446 | char *bang = strchr(usr, '!');
447 | if (bang)
448 | *bang = 0;
449 | }
450 | if (!strcmp(cmd, "PRIVMSG")) {
451 | if (!strcmp(data, "\001VERSION\001"))
452 | sndf("NOTICE %s :\001VERSION %s\001", usr, VERSION);
453 | if (strstr(data, "\001PING") != NULL)
454 | sndf("NOTICE %s :%s", usr, data);
455 | if (!pm || !data)
456 | return;
457 | if (strchr("!+.~", pm[0]))
458 | chan = pm;
459 | else
460 | chan = usr;
461 | if (!(c = chfind(chan))) {
462 | if (chadd(chan, 0) < 0)
463 | return;
464 | tredraw();
465 | }
466 | c = chfind(chan);
467 | if (strstr(data, "\001ACTION") != NULL) {
468 | char *s = strremove(data, "\001ACTION ");
469 | pushf(c, AFMT, usr, s);
470 | pushed = 1;
471 | }
472 | if (strcasestr(data, nick)) {
473 | pushf(c, PFMTHIGH, usr, data);
474 | pushed = 1;
475 | char cmd[256];
476 | if (NOTIFY) {
477 | /* packs all of notify-send into cmd */
478 | snprintf(cmd, sizeof(cmd), "notify-send \"%s @ %s\" \"%s\"", usr, chan, data);
479 | system(cmd);
480 | }
481 | chl[c].high |= ch != c;
482 | }
483 | if (!pushed) {
484 | pushf(c, PFMT, usr, data);
485 | }
486 | if (ch != c) {
487 | chl[c].new = 1;
488 | tdrawbar();
489 | }
490 | } else if (!strcmp(cmd, "PING")) {
491 | sndf("PONG :%s", data ? data : "(null)");
492 | } else if (!strcmp(cmd, "PART")) {
493 | if (!pm)
494 | return;
495 | pushf(chfind(pm), "! %-12s has left %s", usr, pm);
496 | } else if (!strcmp(cmd, "JOIN")) {
497 | if (!pm)
498 | return;
499 | pushf(chfind(pm), "! %-12s has joined %s", usr, pm);
500 | } else if (!strcmp(cmd, "470")) { /* Channel forwarding. */
501 | char *ch = strtok(0, " "), *fch = strtok(0, " ");
502 |
503 | if (!ch || !fch || !(s = chfind(ch)))
504 | return;
505 | chl[s].name[0] = 0;
506 | strncat(chl[s].name, fch, ChanLen - 1);
507 | tdrawbar();
508 | } else if (!strcmp(cmd, "471") || !strcmp(cmd, "473")
509 | || !strcmp(cmd, "474") || !strcmp(cmd, "475")) { /* Join error. */
510 | if ((pm = strtok(0, " "))) {
511 | chdel(pm);
512 | pushf(0, "-!- Cannot join channel %s (%s)", pm, cmd);
513 | tredraw();
514 | }
515 | } else if (!strcmp(cmd, "QUIT")) { /* Commands we don't care about. */
516 | return;
517 | } else if (!strcmp(cmd, "NOTICE") || !strcmp(cmd, "375")
518 | || !strcmp(cmd, "372") || !strcmp(cmd, "376")) {
519 | pushf(0, "%s", data ? data : "");
520 | } else
521 | pushf(0, "%s - %s %s", cmd, par, data ? data : "(null)");
522 | }
523 |
524 | static void
525 | uparse(char *m)
526 | {
527 | char *p = m;
528 | //if (!p[0]|| (p[1] != ' ' && p[1] != 0)) {
529 | if (!strncmp("/j", p, 2)) { /* Join channels. */
530 | p += 1 + (p[2] == ' ');
531 | p = strtok(p, " ");
532 | while (p) {
533 | if (chadd(p, 1) < 0)
534 | break;
535 | sndf("JOIN %s", p);
536 | p = strtok(0, " ");
537 | }
538 | tredraw();
539 | return;
540 | }
541 | if (!strncmp("/l", p, 2)) {/* Leave channels. */
542 | p += 1 + (p[2] == ' ');
543 | if (!*p) {
544 | if (ch == 0)
545 | return; /* Cannot leave server window. */
546 | strcat(p, chl[ch].name);
547 | }
548 | p = strtok(p, " ");
549 | while (p) {
550 | if (chdel(p))
551 | sndf("PART %s", p);
552 | p = strtok(0, " ");
553 | }
554 | tredraw();
555 | return;
556 | }
557 | if (!strncmp("/q", p, 2)) { /* Private message. */
558 | int len = strlen(m);
559 | char *temp = (char *) malloc (len * (sizeof(char))), *msg;
560 | strcpy(temp, p);
561 | temp += 3;
562 | char *u = strtok(temp, " ");
563 | msg = strtok(NULL, "\0");
564 | chadd(u, 1);
565 | pushf(chfind(u), PFMT, nick, msg);
566 | sndf("PRIVMSG %s :%s", u, msg);
567 | tredraw();
568 | return;
569 | }
570 | if (!strncmp("/r", p, 2)) { /* Send raw. */
571 | if (p[1])
572 | sndf("%s", &p[3]);
573 | return;
574 | }
575 | if (!strncmp("/x", p, 2)) {/* Quit. */
576 | quit = 1;
577 | return;
578 | }
579 | if (!strncmp("/me", p, 3)) {
580 | char *s = strremove(p, "/me");
581 | pushf(ch, AFMT, nick, s);
582 | sndf("PRIVMSG %s :\001ACTION %s\001", chl[ch].name, s);
583 | }
584 | else {
585 | if (ch == 0)
586 | return;
587 | m += strspn(m, " ");
588 | if (!*m)
589 | return;
590 | pushf(ch, PFMT, nick, m);
591 | sndf("PRIVMSG %s :%s", chl[ch].name, m);
592 | return;
593 | }/* Send on current channel. */
594 | }
595 |
596 | static void
597 | sigwinch(int sig)
598 | {
599 | if (sig)
600 | winchg = 1;
601 | }
602 |
603 | static void
604 | tinit(void)
605 | {
606 | setlocale(LC_ALL, "");
607 | signal(SIGWINCH, sigwinch);
608 | initscr();
609 | raw();
610 | noecho();
611 | getmaxyx(stdscr, scr.y, scr.x);
612 | if (scr.y < 4)
613 | panic("screen too small");
614 | if ((scr.sw = newwin(1, scr.x, 0, 0)) == 0
615 | || (scr.mw = newwin(scr.y - 2, scr.x, 1, 0)) == 0
616 | || (scr.iw = newwin(1, scr.x, scr.y - 1, 0)) == 0)
617 | panic("cannot create windows");
618 | keypad(scr.iw, 1);
619 | scrollok(scr.mw, 1);
620 | if (has_colors() == TRUE) {
621 | start_color();
622 | use_default_colors();
623 | init_pair(1, COLOR_BLACK, COLOR_WHITE);
624 | init_pair(2, COLOR_RED, COLOR_WHITE);
625 | init_pair(3, COLOR_GREEN, COLOR_WHITE);
626 | wbkgd(scr.sw, COLOR_PAIR(1));
627 | }
628 | }
629 |
630 | static void
631 | tresize(void)
632 | {
633 | struct winsize ws;
634 |
635 | winchg = 0;
636 | if (ioctl(0, TIOCGWINSZ, &ws) < 0)
637 | panic("ioctl (TIOCGWINSZ) failed");
638 | if (ws.ws_row <= 2)
639 | return;
640 | resizeterm(scr.y = ws.ws_row, scr.x = ws.ws_col);
641 | wresize(scr.mw, scr.y - 2, scr.x);
642 | wresize(scr.iw, 1, scr.x);
643 | wresize(scr.sw, 1, scr.x);
644 | mvwin(scr.iw, scr.y - 1, 0);
645 | tredraw();
646 | tdrawbar();
647 | }
648 |
649 | static void
650 | tredraw(void)
651 | {
652 | struct Chan *const c = &chl[ch];
653 | char *q, *p;
654 | int nl = -1;
655 |
656 | if (c->eol == c->buf) {
657 | wclear(scr.mw);
658 | wrefresh(scr.mw);
659 | return;
660 | }
661 | p = c->eol - 1;
662 | if (c->n) {
663 | int i = c->n;
664 | for (; p > c->buf; p--)
665 | if (*p == '\n' && !i--)
666 | break;
667 | if (p == c->buf)
668 | c->n -= i;
669 | }
670 | q = p;
671 | while (nl < scr.y - 2) {
672 | while (*q != '\n' && q > c->buf)
673 | q--;
674 | nl++;
675 | if (q == c->buf)
676 | break;
677 | q--;
678 | }
679 | if (q != c->buf)
680 | q += 2;
681 | wclear(scr.mw);
682 | wmove(scr.mw, 0, 0);
683 | while (q < p)
684 | q = pushl(q, p);
685 | wrefresh(scr.mw);
686 | }
687 |
688 | static void
689 | tdrawbar(void)
690 | {
691 | size_t l;
692 | int fst = ch;
693 |
694 | for (l = 0; fst > 0 && l < scr.x / 2; fst--)
695 | l += strlen(chl[fst].name) + 3;
696 |
697 | werase(scr.sw);
698 | for (l = 0; fst < nch && l < scr.x; fst++) {
699 | char *p = chl[fst].name;
700 | if (fst == ch)
701 | wattron(scr.sw, A_REVERSE);
702 | waddstr(scr.sw, " "), l++;
703 | if (chl[fst].high) {
704 | wattron(scr.sw, COLOR_PAIR(2)), l++;
705 | }
706 | else if (chl[fst].new)
707 | wattron(scr.sw, COLOR_PAIR(3)), l++;
708 | for (; *p && l < scr.x; p++, l++)
709 | waddch(scr.sw, *p);
710 | if (l < scr.x - 1)
711 | waddstr(scr.sw, " "), l += 2;
712 | if (fst == ch)
713 | wattroff(scr.sw, A_REVERSE);
714 | wattroff(scr.sw, COLOR_PAIR(2));
715 | wattroff(scr.sw, COLOR_PAIR(3));
716 | }
717 | wrefresh(scr.sw);
718 | }
719 |
720 | static void
721 | tgetch(void)
722 | {
723 | static char l[BufSz];
724 | static size_t shft, cu, len;
725 | size_t dirty = len + 1, i;
726 | int c;
727 |
728 | c = wgetch(scr.iw);
729 | switch (c) {
730 | case CTRL('n'):
731 | ch = (ch + 1) % nch;
732 | chl[ch].high = chl[ch].new = 0;
733 | tdrawbar();
734 | tredraw();
735 | return;
736 | case CTRL('p'):
737 | ch = (ch + nch - 1) % nch;
738 | chl[ch].high = chl[ch].new = 0;
739 | tdrawbar();
740 | tredraw();
741 | return;
742 | case KEY_PPAGE:
743 | chl[ch].n += SCROLL;
744 | tredraw();
745 | return;
746 | case KEY_NPAGE:
747 | chl[ch].n -= SCROLL;
748 | if (chl[ch].n < 0)
749 | chl[ch].n = 0;
750 | tredraw();
751 | return;
752 | case CTRL('a'):
753 | case KEY_HOME:
754 | cu = 0;
755 | break;
756 | case CTRL('e'):
757 | case KEY_END:
758 | cu = len;
759 | break;
760 | case CTRL('b'):
761 | case KEY_LEFT:
762 | if (cu)
763 | cu--;
764 | break;
765 | case KEY_RIGHT:
766 | if (cu < len)
767 | cu++;
768 | break;
769 | case CTRL('k'):
770 | dirty = len = cu;
771 | break;
772 | case CTRL('u'):
773 | if (cu == 0)
774 | return;
775 | len -= cu;
776 | memmove(l, &l[cu], len);
777 | dirty = cu = 0;
778 | break;
779 | case CTRL('d'):
780 | if (cu >= len)
781 | return;
782 | memmove(&l[cu], &l[cu + 1], len - cu - 1);
783 | dirty = cu;
784 | len--;
785 | break;
786 | case CTRL('h'):
787 | case KEY_BACKSPACE:
788 | if (cu == 0)
789 | return;
790 | memmove(&l[cu - 1], &l[cu], len - cu);
791 | dirty = --cu;
792 | len--;
793 | break;
794 | case CTRL('w'):
795 | if (cu == 0)
796 | break;
797 | i = 1;
798 | while (l[cu - i] == ' ' && cu - i != 0) i++;
799 | while (l[cu - i] != ' ' && cu - i != 0) i++;
800 | if (cu - i != 0) i--;
801 | memmove(&l[cu - i], &l[cu], len - cu);
802 | cu -= i;
803 | dirty = cu;
804 | len -= i;
805 | break;
806 | case '\n':
807 | l[len] = 0;
808 | uparse(l);
809 | dirty = cu = len = 0;
810 | break;
811 | default:
812 | if (c > CHAR_MAX || len >= BufSz - 1)
813 | return; /* Skip other curses codes. */
814 | memmove(&l[cu + 1], &l[cu], len - cu);
815 | dirty = cu;
816 | len++;
817 | l[cu++] = c;
818 | break;
819 | }
820 | while (cu < shft)
821 | dirty = 0, shft -= shft >= scr.x / 2 ? scr.x / 2 : shft;
822 | while (cu >= scr.x + shft)
823 | dirty = 0, shft += scr.x / 2;
824 | if (dirty <= shft)
825 | i = shft;
826 | else if (dirty > scr.x + shft || dirty > len)
827 | goto mvcur;
828 | else
829 | i = dirty;
830 | wmove(scr.iw, 0, i - shft);
831 | wclrtoeol(scr.iw);
832 | for (; i - shft < scr.x && i < len; i++)
833 | waddch(scr.iw, l[i]);
834 | mvcur: wmove(scr.iw, 0, cu - shft);
835 | }
836 |
837 | static void
838 | treset(void)
839 | {
840 | if (scr.mw)
841 | delwin(scr.mw);
842 | if (scr.sw)
843 | delwin(scr.sw);
844 | if (scr.iw)
845 | delwin(scr.iw);
846 | endwin();
847 | }
848 |
849 | int
850 | main(int argc, char *argv[])
851 | {
852 | const char *user = getenv("USER");
853 | const char *ircnick = getenv("IRCNICK");
854 | char key[128];
855 | #ifdef PWCMD
856 | FILE *fp = popen(PWCMD, "r");
857 | if (fp == NULL) {
858 | panic("failed to run command");
859 | }
860 | fgets(key, 128, fp);
861 | key[strcspn(key, "\n")] = 0;
862 | pclose(fp);
863 | #else
864 | if(getenv("IRCPASS"))
865 | strcpy(key, getenv("IRCPASS"));
866 | else
867 | panic("error: IRCPASS environment variable not set");
868 | #endif
869 | const char *server = SRV;
870 | const char *port = PORT;
871 | char *err;
872 | int o, reconn;
873 |
874 | signal(SIGPIPE, SIG_IGN);
875 | while ((o = getopt(argc, argv, "thk:n:u:s:p:l:")) >= 0)
876 | switch (o) {
877 | case 'h':
878 | case '?':
879 | usage:
880 | fputs("usage: irc [-n NICK] [-u USER] [-s SERVER] [-p PORT] [-l LOGFILE ] [-t] [-h]\n", stderr);
881 | exit(0);
882 | case 'l':
883 | if (!(logfp = fopen(optarg, "a")))
884 | panic("fopen: logfile");
885 | break;
886 | case 'n':
887 | if (strlen(optarg) >= sizeof nick)
888 | goto usage;
889 | strcpy(nick, optarg);
890 | break;
891 | case 't':
892 | ssl = 1;
893 | break;
894 | case 'u':
895 | user = optarg;
896 | break;
897 | case 's':
898 | server = optarg;
899 | break;
900 | case 'p':
901 | port = optarg;
902 | break;
903 | }
904 | if (!user)
905 | user = "anonymous";
906 | if (!nick[0] && ircnick && strlen(ircnick) < sizeof nick)
907 | strcpy(nick, ircnick);
908 | if (!nick[0] && strlen(user) < sizeof nick)
909 | strcpy(nick, user);
910 | if (!nick[0])
911 | goto usage;
912 | tinit();
913 | err = dial(server, port);
914 | if (err)
915 | panic(err);
916 | chadd(server, 0);
917 | sinit(key, nick, user);
918 | reconn = 0;
919 | while (!quit) {
920 | struct timeval t = {.tv_sec = 5};
921 | struct Chan *c;
922 | fd_set rfs, wfs;
923 | int ret;
924 |
925 | if (winchg)
926 | tresize();
927 | FD_ZERO(&wfs);
928 | FD_ZERO(&rfs);
929 | FD_SET(0, &rfs);
930 | if (!reconn) {
931 | FD_SET(srv.fd, &rfs);
932 | if (outp != outb)
933 | FD_SET(srv.fd, &wfs);
934 | }
935 | ret = select(srv.fd + 1, &rfs, &wfs, 0, &t);
936 | if (ret < 0) {
937 | if (errno == EINTR)
938 | continue;
939 | panic("select failed");
940 | }
941 | if (reconn) {
942 | hangup();
943 | if (reconn++ == MaxRecons + 1)
944 | panic("link lost");
945 | pushf(0, "-!- Link lost, attempting reconnection...");
946 | if (dial(server, port) != 0)
947 | continue;
948 | sinit(key, nick, user);
949 | for (c = chl; c < &chl[nch]; ++c)
950 | if (c->join)
951 | sndf("JOIN %s", c->name);
952 | reconn = 0;
953 | }
954 | if (FD_ISSET(srv.fd, &rfs)) {
955 | if (!srd()) {
956 | reconn = 1;
957 | continue;
958 | }
959 | }
960 | if (FD_ISSET(srv.fd, &wfs)) {
961 | int wr;
962 |
963 | if (ssl)
964 | wr = SSL_write(srv.ssl, outb, outp - outb);
965 | else
966 | wr = write(srv.fd, outb, outp - outb);
967 | if (wr <= 0) {
968 | reconn = wr < 0;
969 | continue;
970 | }
971 | outp -= wr;
972 | memmove(outb, outb + wr, outp - outb);
973 | }
974 | if (FD_ISSET(0, &rfs)) {
975 | tgetch();
976 | wrefresh(scr.iw);
977 | }
978 | }
979 | hangup();
980 | while (nch--)
981 | free(chl[nch].buf);
982 | treset();
983 | exit(0);
984 | }
985 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Anirudh Oppiliappan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------