2 * Copyright (C) 2012-2013 Andy Spencer <andy753421@gmail.com>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #define _XOPEN_SOURCE_EXTENDED
28 #include <sys/signalfd.h>
38 #define RE_FLAGS (REG_EXTENDED|REG_NOSUB)
41 #define COLOR_BROWN 130
44 #define KEY_CTRL_D '\4'
45 #define KEY_CTRL_E '\5'
46 #define KEY_CTRL_G '\7'
47 #define KEY_CTRL_L '\14'
48 #define KEY_CTRL_N '\16'
49 #define KEY_CTRL_P '\20'
50 #define KEY_CTRL_U '\25'
51 #define KEY_CTRL_X '\30'
52 #define KEY_CTRL_Y '\31'
53 #define KEY_RETURN '\12'
54 #define KEY_ESCAPE '\33'
57 typedef struct window_t window_t;
59 typedef struct window_t {
78 static poll_t poll_in;
79 static poll_t poll_sig;
85 static char cmd_buf[512];
89 static window_t *focus;
90 static window_t *windows;
92 static int ncolors = 1;
93 static short colors[256+1][256+1];
94 static short color_title;
95 static short color_date;
96 static short color_error;
98 static const char *themes[] = {
99 [THEME_NORMAL] "normal",
101 [THEME_LIGHT] "light",
104 /* Helper functions */
105 static short color(int fg, int bg)
107 if (!colors[fg+1][bg+1]) {
108 init_pair(ncolors, fg, bg);
109 colors[fg+1][bg+1] = ncolors;
112 return COLOR_PAIR(colors[fg+1][bg+1]);
115 static int msg_compare(const void *_a, const void *_b)
117 const message_t *a = _a;
118 const message_t *b = _b;
119 return a->when < b->when ? -1 :
120 a->when > b->when ? 1 : 0;
123 static unsigned str_hash(const char *str)
125 unsigned long hash = 0;
126 for (int i = 0; str[i]; i++)
127 hash = str[i] + (hash<<6) + (hash<<16) - hash;
128 for (int i = 0; str[i]; i++)
129 hash = str[i] + (hash<<6) + (hash<<16) - hash;
133 static int str_color(const char *str)
135 int h,s,l, t,c,x,m, r,g,b;
137 int n = str_hash(str);
139 h = (n / 0x1) % 0x100; // 0..256
140 s = (n / 0x100) % 0x100; // 0..256
141 l = (n / 0x10000) % 0x100; // 0..256
143 h = h * 360 / 0x100; // 0..360
144 s = s / 0x4 + 0xC0; // 0..256
147 case THEME_NORMAL: l = l / 0x4 + 0x80; break;
148 case THEME_DARK: l = l / 0x4 + 0x30; break;
149 case THEME_LIGHT: l = l / 0x4 + 0x60; break;
152 t = h / 60; // ,1,2,3,4,5
153 c = ((0x80-abs(l-0x80))*s)/0x80; // ..256
154 x = (c*(60-abs((h%120)-60)))/60; // ..256
155 m = l - c/2; // ..128
157 r = t==0 || t==5 ? c+m :
158 t==1 || t==4 ? x+m : m;
159 g = t==1 || t==2 ? c+m :
160 t==0 || t==3 ? x+m : m;
161 b = t==3 || t==4 ? c+m :
162 t==2 || t==5 ? x+m : m;
164 x = MIN(r*6/0x100, 5)*6*6 +
165 MIN(g*6/0x100, 5)*6 +
166 MIN(b*6/0x100, 5) + 16;
168 //debug("%12s %3d,%02x,%02x -> "
169 // "tcxm=%d,%02x,%02x,%02x -> "
170 // "rgb=%02x,%02x,%02x -> %d",
171 // name, h,s,l, t,c,x,m, r,g,b, x);
176 /* Window functions */
177 static int in_window(message_t *msg, window_t *win)
179 channel_t *chan = msg->channel;
180 server_t *srv = chan->server;
181 const char *name = chan->name;
187 if (srv != win->server)
190 return !regexec(&win->regex, name, 0, 0, 0);
192 return win->channel == chan;
195 static void add_window(window_t *win)
197 window_t **last = &windows;
199 last = &(*last)->next;
203 static window_t *find_window(const char *name, int create)
206 for (win = windows; win; win = win->next)
207 if (match(win->name, name))
209 if (!win && create) {
210 win = new0(window_t);
211 win->name = strcopy(name);
217 static window_t *next_window(window_t *win)
221 for (window_t *cur = win->next; cur; cur = cur->next)
224 return win && !win->hide ? win : NULL;
227 static window_t *prev_window(window_t *win)
231 window_t *prev = windows;
232 for (window_t *cur = windows; cur != win; cur = cur->next)
235 return prev && !prev->hide ? prev : NULL;
238 static void cycle_channel(void)
243 /* Clear seen flags */
244 for (int i = 0; i < history; i++)
245 messages[i].channel->seen = 0;
247 /* Find current channel */
248 channel_t *first = NULL, *cur = NULL, *next = NULL;
249 for (int i = 0; i < history && !next; i++) {
250 message_t *msg = &messages[i];
251 if (msg->channel->seen)
253 msg->channel->seen = 1;
254 if (!in_window(msg, focus))
258 if (msg->channel == focus->channel)
261 first = msg->channel;
263 focus->channel = next ?: first;
266 static void update_windows(void)
269 for (; seen < history; seen++) {
270 window_t *win = NULL;
271 message_t *msg = &messages[seen];
272 channel_t *chan = msg->channel;
274 /* Flag existing windows */
275 for (window_t *cur = windows; cur; cur = cur->next) {
276 if (in_window(msg, cur)) {
283 /* No window, create a new one */
285 win = new0(window_t);
287 win->name = strcopy(chan->name);
295 if (focus && focus->hide)
296 focus = next_window(focus) ?:
300 /* Print functions */
301 static void print_word(char **msg, int *row, int *col, int indent, int print)
304 while (*start && isspace(*start))
306 int space = start-*msg;
309 while (*end && !isspace(*end))
313 if ((*col != indent) && (*col + space + len > COLS)) {
319 if (*row < 1 || *row > LINES-2)
323 mvaddnstr(*row, *col, *msg, space);
326 mvaddnstr(*row, *col, start, len);
332 static void print_msg(message_t *msg, int *row, int print)
334 static char buf[512];
336 char *txt = msg->text;
337 int col = 0, indent = 0;
338 time_t timep = msg->when;
339 struct tm *tm = localtime(&timep);
341 if (!in_window(msg, focus))
344 if (focus->filter && msg->from)
345 snprintf(buf, sizeof(buf), "%02d:%02d [%s] %s: ",
346 tm->tm_hour, tm->tm_min,
347 msg->channel->name, msg->from);
348 else if (focus->filter)
349 snprintf(buf, sizeof(buf), "%02d:%02d [%s] *** ",
350 tm->tm_hour, tm->tm_min, msg->channel->name);
352 snprintf(buf, sizeof(buf), "%02d:%02d %s: ",
353 tm->tm_hour, tm->tm_min, msg->from);
355 snprintf(buf, sizeof(buf), "%02d:%02d *** ",
356 tm->tm_hour, tm->tm_min);
358 const char *chan = focus->filter ? msg->channel->name : NULL;
359 const char *from = msg->from;
362 if (word==0) attr = color_date;
363 else if (word==1 && chan) attr = str_color(chan) | A_BOLD;
364 else if (word==1 && from) attr = str_color(from);
365 else if (word>=2 && from) attr = str_color(from);
368 print_word(&hdr, row, &col, indent, print);
374 print_word(&txt, row, &col, indent, print);
380 static int send_command(const char *text)
383 window_t *win = focus;
385 if (match(text, "/quit")) {
388 else if (match(text, "/sort")) {
389 qsort(messages, history, sizeof(message_t), msg_compare);
391 else if (prefix(text, "/open", &arg)) {
393 arg = focus->channel->name;
396 focus = find_window(arg, 1);
399 else if (prefix(text, "/close", &arg)) {
401 win = find_window(arg, 0);
405 else if (focus && prefix(text, "/name", &arg)) {
406 strset(&focus->name, arg);
408 else if (focus && prefix(text, "/server", &arg)) {
409 server_t *srv = NULL;
411 srv = find_server(arg);
414 else if (focus && prefix(text, "/channel", &arg)) {
415 channel_t *chan = NULL;
417 chan = find_channel(arg);
420 else if (focus && prefix(text, "/filter", &arg)) {
421 strset(&focus->filter, arg);
423 regcomp(&focus->regex, focus->filter, RE_FLAGS);
426 chat_send(focus->channel, text);
434 /* Drawing functions */
435 void draw_header(void)
440 printw("%-*s", COLS, "Header Bar");
441 attroff(color_title);
451 for (int i = 1; i < LINES-2; i++) {
457 while (row < space && log >= 0)
458 print_msg(&messages[log--], &row, 0);
460 /* Compute skip lines */
461 row = 1 + (space - row);
465 while (row <= space && log < history)
466 print_msg(&messages[log++], &row, 1);
469 void draw_status(void)
472 mvhline(LINES-2, 0, ' ', COLS);
477 for (window_t *cur = windows; cur; cur = cur->next) {
486 printw("[%s:%d]", cur->name, cur->flag);
488 printw("[%s]", cur->name);
492 attroff(color_title);
495 void draw_cmdline(void)
498 if (focus && focus->channel)
499 name = focus->channel->name;
505 printw("[%s] %.*s", name, cmd_len, cmd_buf);
506 move(LINES-1, 1 + strlen(name) + 2 + cmd_pos);
513 for (window_t *win = windows; win; win = win->next)
515 regcomp(&win->regex, win->filter, RE_FLAGS);
517 /* Set default escape timeout */
518 if (!getenv("ESCDELAY"))
519 putenv("ESCDELAY=25");
522 setlocale(LC_ALL, "");
526 keypad(stdscr, TRUE);
529 use_default_colors();
531 color_title = color(COLOR_WHITE, COLOR_BLUE);
532 color_date = color(COLOR_BROWN, -1);
533 color_error = color(COLOR_RED, -1);
535 /* Create signal FD */
538 sigaddset(&mask, SIGWINCH);
539 if ((sig_fd = signalfd(-1, &mask, SFD_CLOEXEC)) < 0)
540 error("creating signal fd");
542 /* Register callback */
543 poll_add(&poll_in, 0, (cb_t)view_sync, NULL);
544 poll_ctl(&poll_in, 1, 0, 1);
546 poll_add(&poll_sig, sig_fd, (cb_t)view_sync, NULL);
547 poll_ctl(&poll_sig, 1, 0, 1);
552 /* Draw initial view */
558 void view_config(const char *group, const char *name, const char *key, const char *value)
562 if (match(group, "general")) {
563 if (match(key, "theme"))
564 theme = get_enum(value, themes, sizeof(themes));
567 if (match(group, "window")) {
570 win = find_window(name, 1);
571 if (match(key, "server"))
572 win->server = find_server(get_string(value));
573 if (match(key, "channel"))
574 win->channel = find_channel(get_string(value));
575 if (match(key, "filter"))
576 win->filter = get_string(value);
590 /* Window management */
591 if (chr == KEY_RESIZE) {
595 else if (chr == KEY_CTRL_L) {
599 else if (chr == KEY_CTRL_G) {
603 /* View management */
604 else if (chr == KEY_CTRL_N) {
605 focus = next_window(focus) ?: focus;
608 else if (chr == KEY_CTRL_P) {
609 focus = prev_window(focus) ?: focus;
612 else if (chr == KEY_CTRL_X) {
616 else if (chr == KEY_CTRL_Y && focus) {
620 else if (chr == KEY_CTRL_E && focus) {
624 else if (chr == KEY_CTRL_U && focus) {
625 focus->scroll -= (LINES-3)/2;
628 else if (chr == KEY_CTRL_D && focus) {
629 focus->scroll += (LINES-3)/2;
632 else if (chr == KEY_PPAGE && focus) {
633 focus->scroll -= (LINES-3)-1;
636 else if (chr == KEY_NPAGE && focus) {
637 focus->scroll += (LINES-3)-1;
642 else if (chr == KEY_RETURN) {
643 cmd_buf[cmd_len] = '\0';
644 if (send_command(cmd_buf)) {
650 else if (chr == KEY_ESCAPE) {
655 else if (chr == KEY_LEFT) {
660 else if (chr == KEY_RIGHT) {
661 if (cmd_pos < cmd_len)
665 else if (chr == KEY_BACKSPACE) {
667 memmove(&cmd_buf[cmd_pos-1],
669 (cmd_len-cmd_pos)+1);
675 else if (chr == KEY_DC) {
676 if (cmd_pos < cmd_len) {
677 memmove(&cmd_buf[cmd_pos],
679 (cmd_len-cmd_pos)+1);
684 else if (isprint(chr)) {
685 if (cmd_len+2 < sizeof(cmd_buf)) {
686 memmove(&cmd_buf[cmd_pos+1],
688 (cmd_len-cmd_pos)+1);
689 cmd_buf[cmd_pos] = chr;
694 debug("form: out of space");
698 /* Unknown control character */
700 debug("main: Unhandled key - Dec %3d, Hex %02x, Oct %03o, Chr <%c>",