]> Pileus Git - ~andy/lamechat/blob - view.c
Support pre-formatted text.
[~andy/lamechat] / view.c
1 /*
2  * Copyright (C) 2012-2013 Andy Spencer <andy753421@gmail.com>
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 #define _XOPEN_SOURCE
19 #define _XOPEN_SOURCE_EXTENDED
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24 #include <ctype.h>
25 #include <locale.h>
26 #include <ncurses.h>
27 #include <signal.h>
28 #include <sys/signalfd.h>
29 #include <unistd.h>
30 #include <regex.h>
31
32 #include "util.h"
33 #include "conf.h"
34 #include "chat.h"
35 #include "view.h"
36
37 /* View constants */
38 #define RE_FLAGS   (REG_EXTENDED|REG_NOSUB)
39
40 /* Extra colors */
41 #define COLOR_BROWN 130
42
43 /* Extra keys */
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_TAB    '\11'
54 #define KEY_RETURN '\12'
55 #define KEY_ESCAPE '\33'
56 #define KEY_DELETE '\177'
57
58 /* View types */
59 typedef struct window_t window_t;
60
61 typedef struct window_t {
62         char      *name;
63         int        hide;
64         int        flag;
65         int        scroll;
66         int        pinned;
67
68         char      *filter;
69         server_t  *server;
70         channel_t *channel;
71         regex_t    regex;
72
73         channel_t *dest;
74         channel_t *saved;
75         window_t  *next;
76 } window_t;
77
78 typedef struct {
79         int top;
80         int end;
81         int row;
82         int col;
83         int indent;
84         int margin;
85         int print;
86         int pick;
87         int prow;
88         int pcol;
89 } printer_t;
90
91 typedef enum {
92         THEME_NORMAL,
93         THEME_DARK,
94         THEME_LIGHT,
95 } theme_t;
96
97 /* Local data */
98 static poll_t poll_in;
99 static poll_t poll_sig;
100 static int    sig_fd;
101 static int    running;
102 static int    defocus;
103 static int    deadline;
104 static int    theme;
105 static int    draw;
106
107 static char   cmd_buf[4096];
108 static int    cmd_pos;
109 static int    cmd_len;
110 static int    cmd_row;
111 static int    cmd_col;
112
113 static int    hdr_lines;
114 static int    sts_lines;
115 static int    cmd_lines;
116
117 static server_t   sys_srv;
118 static channel_t  sys_chan;
119 static window_t   sys_win;
120
121 static window_t  *focus;
122 static window_t  *windows;
123
124 static int   ncolors = 1;
125 static short colors[256+1][256+1];
126 static short color_title;
127 static short color_date;
128 static short color_error;
129
130 static const char *themes[] = {
131         [THEME_NORMAL] "normal",
132         [THEME_DARK]   "dark",
133         [THEME_LIGHT]  "light",
134 };
135
136 /* Helper functions */
137 static short color(int fg, int bg)
138 {
139         if (!colors[fg+1][bg+1]) {
140                 init_pair(ncolors, fg, bg);
141                 colors[fg+1][bg+1] = ncolors;
142                 ncolors++;
143         }
144         return COLOR_PAIR(colors[fg+1][bg+1]);
145 }
146
147 static int msg_compare(const void *_a, const void *_b)
148 {
149         const message_t *a = _a;
150         const message_t *b = _b;
151         return a->when < b->when ? -1 :
152                a->when > b->when ?  1 : 0;
153 }
154
155 static unsigned str_hash(const char *str)
156 {
157         unsigned long hash = 0;
158         for (int i = 0; str[i]; i++)
159                 hash = str[i] + (hash<<6) + (hash<<16) - hash;
160         for (int i = 0; str[i]; i++)
161                 hash = str[i] + (hash<<6) + (hash<<16) - hash;
162         return hash;
163 }
164
165 static int str_color(const char *str)
166 {
167         int h,s,l, t,c,x,m, r,g,b;
168
169         unsigned int n = str_hash(str);
170
171         h = (n /     0x1) % 0x100;            // 0..256
172         s = (n /   0x100) % 0x100;            // 0..256
173         l = (n / 0x10000) % 0x100;            // 0..256
174
175         h = h * 360 / 0x100;                  // 0..360
176         s = s / 0x4 + 0xC0;                   // 0..256
177
178         switch (theme) {
179                 case THEME_DARK:   l =  l / 0x4 + 0x80; break;
180                 case THEME_LIGHT:  l =  l / 0x4 + 0x30; break;
181                 case THEME_NORMAL: l =  l / 0x4 + 0x60; break;
182         }
183
184         t = h / 60;                           // ,1,2,3,4,5
185         c = ((0x80-abs(l-0x80))*s)/0x80;      // ..256
186         x = (c*(60-abs((h%120)-60)))/60;      // ..256
187         m = l - c/2;                          // ..128
188
189         r = t==0 || t==5 ? c+m :
190             t==1 || t==4 ? x+m : m;
191         g = t==1 || t==2 ? c+m :
192             t==0 || t==3 ? x+m : m;
193         b = t==3 || t==4 ? c+m :
194             t==2 || t==5 ? x+m : m;
195
196         x = MIN(r*6/0x100, 5)*6*6 +
197             MIN(g*6/0x100, 5)*6   +
198             MIN(b*6/0x100, 5)     + 16;
199
200         //debug("%12s %3d,%02x,%02x -> "
201         //      "tcxm=%d,%02x,%02x,%02x -> "
202         //      "rgb=%02x,%02x,%02x -> %d",
203         //           str, h,s,l, t,c,x,m, r,g,b, x);
204
205         return color(x, -1);
206 }
207
208 /* Window functions */
209 static int in_window(message_t *msg, window_t *win)
210 {
211         if (win->server && win->server != msg->channel->server)
212                 return 0;
213         if (win->channel && win->channel != msg->channel)
214                 return 0;
215         if (win->filter && regexec(&win->regex, msg->channel->name, 0, 0, 0))
216                 return 0;
217         return 1;
218 }
219
220 static window_t *add_window(channel_t *dest)
221 {
222         window_t *win = new0(window_t);
223         win->name = strcopy(dest->name);
224         win->dest = dest;
225         win->saved = dest;
226         win->pinned = -1;
227
228         window_t **last = &windows;
229         while (*last)
230                 last = &(*last)->next;
231         *last = win;
232         return win;
233 }
234
235 static window_t *find_window(const char *name)
236 {
237         for (window_t *win = windows; win; win = win->next)
238                 if (match(win->name, name))
239                         return win;
240         return NULL;
241 }
242
243 static window_t *next_window(window_t *win)
244 {
245         for (window_t *cur = win->next; cur; cur = cur->next)
246                 if (!cur->hide)
247                         return cur;
248         return NULL;
249 }
250
251 static window_t *prev_window(window_t *win)
252 {
253         window_t *prev = NULL;
254         for (window_t *cur = windows; cur != win; cur = cur->next)
255                 if (!cur->hide)
256                         prev = cur;
257         return prev;
258 }
259
260 static void cycle_channel(void)
261 {
262         /* Clear seen flags */
263         for (int i = 0; i < history; i++)
264                 messages[i].channel->seen = 0;
265
266         /* Find current channel */
267         channel_t *first = NULL, *cur = NULL, *next = NULL;
268         for (int i = 0; i < history && !next; i++) {
269                 message_t *msg = &messages[i];
270                 if (msg->channel->seen)
271                         continue;
272                 msg->channel->seen = 1;
273                 if (!in_window(msg, focus))
274                         continue;
275                 if (cur)
276                         next = msg->channel;
277                 if (msg->channel == focus->dest)
278                         cur = msg->channel;
279                 if (!first)
280                         first = msg->channel;
281         }
282         focus->dest = next ?: first ?: &sys_chan;
283 }
284
285 static void last_channel(void)
286 {
287         if (focus->dest  == &sys_chan &&
288             focus->saved != &sys_chan) {
289                 focus->dest = focus->saved;
290                 return;
291         }
292         for (int i = history-1; i>=0; i--) {
293                 if (in_window(&messages[i], focus)) {
294                         focus->dest = messages[i].channel;
295                         return;
296                 }
297         }
298 }
299
300 static void update_windows(void)
301 {
302         static int seen = 0;
303         for (; seen < history; seen++) {
304                 window_t  *win  = NULL;
305                 message_t *msg  = &messages[seen];
306                 channel_t *chan = msg->channel;
307
308                 /* Flag existing windows */
309                 for (window_t *cur = windows; cur; cur = cur->next) {
310                         if (in_window(msg, cur)) {
311                                 win = cur;
312                                 win->hide  = 0;
313                                 win->flag += 1;
314                         }
315                 }
316
317                 /* No window, create a new one */
318                 if (!win) {
319                         win = add_window(chan);
320                         win->channel = chan;
321                 }
322         }
323
324         /* Update focus */
325         if (focus->hide) {
326                 focus = next_window(focus) ?:
327                         prev_window(focus) ?: &sys_win;
328                 focus->hide = 0;
329         }
330 }
331
332 static server_t *find_server(const char *name)
333 {
334         for (server_t *cur = servers; cur; cur = cur->next)
335                 if (match(cur->name, name))
336                         return cur;
337         return NULL;
338 }
339
340 static channel_t *find_channel(const char *name)
341 {
342         for (channel_t *cur = channels; cur; cur = cur->next)
343                 if (match(cur->name, name))
344                         return cur;
345         return NULL;
346 }
347
348 /* Print functions */
349 static int word_wrap(printer_t *pr, const char *txt,
350                       const char **l, const char **r)
351 {
352         /* In previous word? */
353         if (txt > *l && txt < *r)
354                 return 0;
355
356         /* Find left and right ends of the word */
357         *l = txt;
358         while (*txt != '\0' &&
359                *txt != ' '  && *txt != '\t' &&
360                *txt != '\r' && *txt != '\n')
361                 txt++;
362         *r = txt;
363
364         /* End goes past the margin? */
365         int len = *r - *l;
366         int edge = COLS - pr->margin;
367         if (pr->col + len > edge) {
368                 if (pr->indent + len > edge)
369                         *r = *l + (edge - pr->indent);
370                 return pr->col != pr->indent;
371         }
372
373         return 0;
374 }
375
376 static void print_string(printer_t *pr, const char *txt)
377 {
378         int i;
379         const char *l = 0, *r = 0;
380         for (i = 0; txt[i]; i++) {
381                 if (word_wrap(pr, &txt[i], &l, &r)) {
382                         pr->row += 1;
383                         pr->col  = pr->indent;
384                 }
385                 if (i == pr->pick) {
386                         pr->prow = pr->row;
387                         pr->pcol = pr->col;
388                 }
389                 switch (txt[i]) {
390                         case ' ':
391                                 pr->col += 1;
392                                 break;
393                         case '\t':
394                                 pr->col -= pr->indent;
395                                 pr->col /= 8;
396                                 pr->col += 1;
397                                 pr->col *= 8;
398                                 pr->col += pr->indent;
399                                 break;
400                         case '\r':
401                         case '\n':
402                                 pr->row += 1;
403                                 pr->col  = pr->indent;
404                                 break;
405                         default:
406                                 if (pr->print &&
407                                     pr->row >= pr->top &&
408                                     pr->row <  pr->end)
409                                         mvaddch(pr->row, pr->col, txt[i]);
410                                 pr->col += 1;
411                                 break;
412                 }
413         }
414         if (i <= pr->pick) {
415                 pr->prow = pr->row;
416                 pr->pcol = pr->col;
417         }
418 }
419
420 static void print_format(printer_t *pr, const char *fmt, ...)
421 {
422         static char buf[4096];
423         va_list ap;
424         va_start(ap, fmt);
425         vsnprintf(buf, sizeof(buf), fmt, ap);
426         va_end(ap);
427
428         print_string(pr, buf);
429 }
430
431 static void print_message(printer_t *pr, message_t *msg)
432 {
433         if (!in_window(msg, focus))
434                 return;
435
436         time_t timep = msg->when;
437         struct tm *tm = localtime(&timep);
438
439         /* Start new line */
440         pr->col = 0;
441         pr->indent = 0;
442
443         /* Print time */
444         attron(color_date);
445         print_format(pr, "%02d:%02d ", tm->tm_hour, tm->tm_min);
446         attroff(color_date);
447
448         /* Print channel */
449         if (!focus->channel) {
450                 attron(str_color(msg->channel->name) | A_BOLD);
451                 print_format(pr, "[%s] ", msg->channel->name);
452                 attroff(str_color(msg->channel->name) | A_BOLD);
453         }
454
455         /* Print from */
456         if (msg->from) {
457                 attron(str_color(msg->from->name));
458                 print_format(pr, "%s: ", msg->from->name);
459                 attroff(str_color(msg->from->name));
460         } else {
461                 print_format(pr, "*** ");
462         }
463
464         /* Print message */
465         pr->indent = pr->col;
466         print_string(pr, msg->text);
467         pr->row++;
468 }
469
470 static void print_status(printer_t *pr)
471 {
472         pr->col = 0;
473         print_format(pr, " Windows: ");
474         pr->indent = pr->col;
475         pr->margin = 1;
476         for (window_t *cur = windows; cur; cur = cur->next) {
477                 if (cur->hide)
478                         continue;
479                 if (cur == focus)
480                         cur->flag = 0;
481                 if (cur == focus)
482                         attron(A_BOLD);
483                 if (cur->flag)
484                         print_format(pr, "[%s:%d]", cur->name, cur->flag);
485                 else
486                         print_format(pr, "[%s]", cur->name);
487                 if (cur == focus)
488                         attroff(A_BOLD);
489                 pr->col++;
490         }
491 }
492
493 static void print_clear(printer_t *pr)
494 {
495         for (int i = pr->top; i < pr->end; i++)
496                 mvhline(i, 0, ' ', COLS);
497 }
498
499 /* Message info */
500 static int get_lines(int pos)
501 {
502         printer_t pr = {.print=0};
503         print_message(&pr, &messages[pos]);
504         return pr.row;
505 }
506
507 static int next_message(int pos)
508 {
509         while (1) {
510                 if (--pos < 0)
511                         return -1;
512                 if (in_window(&messages[pos], focus))
513                         return pos;
514         }
515 }
516
517 static int prev_message(int pos)
518 {
519         while (1) {
520                 if (++pos >= history)
521                         return -1;
522                 if (in_window(&messages[pos], focus))
523                         return pos;
524         }
525 }
526
527 /* Commands */
528 static int send_command(const char *text)
529 {
530         const char *arg;
531         window_t   *win = focus;
532
533         if (match(text, "/quit")) {
534                 poll_quit();
535         }
536         else if (match(text, "/sort")) {
537                 qsort(messages, history, sizeof(message_t), msg_compare);
538         }
539         else if (prefix(text, "/theme", &arg)) {
540                 for (int i = 0; i < N_ELEMENTS(themes); i++)
541                         if (match(themes[i], arg))
542                                 theme = i;
543         }
544         else if (match(text, "/open")) {
545                 win = find_window(focus->dest->name);
546                 if (!win || win == focus) {
547                         win = add_window(focus->dest);
548                         win->channel = focus->dest;
549                 }
550                 focus = win;
551                 focus->hide = 0;
552         }
553         else if (match(text, "/close")) {
554                 focus->hide = 1;
555         }
556         else if (prefix(text, "/name", &arg) && arg) {
557                 strset(&focus->name, arg);
558         }
559         else if (prefix(text, "/server", &arg)) {
560                 win->server = find_server(arg);
561         }
562         else if (prefix(text, "/channel", &arg)) {
563                 win->channel = find_channel(arg);
564                 win->dest    = win->channel ?: win->dest;
565         }
566         else if (prefix(text, "/filter", &arg)) {
567                 strset(&focus->filter, arg);
568                 if (focus->filter)
569                         regcomp(&focus->regex, focus->filter, RE_FLAGS);
570         }
571         else if (focus->dest != &sys_chan) {
572                 chat_send(focus->dest, text);
573         }
574         else {
575                 return 0;
576         }
577         return 1;
578 }
579
580 /* Drawing functions */
581 static void draw_header(void)
582 {
583         printer_t pr = {
584                 .print  = 1,
585                 .row    = 0,
586                 .col    = 1,
587                 .indent = 1,
588                 .margin = 1,
589                 .end    = 5,
590         };
591         const char *topic = focus->dest->topic ?: "No Topic";
592         attron(color_title);
593         print_clear(&pr);
594         print_format(&pr, "%s", topic);
595         attroff(color_title);
596         hdr_lines = pr.row + 1;
597 }
598
599 static void draw_chat(void)
600 {
601         /* Clear pinned if scrolling */
602         if (focus->scroll && focus->pinned < 0)
603                 focus->pinned = history-1;
604
605         /* Scroll up to previous messages */
606         while (focus->scroll < 0) {
607                 int lines = get_lines(focus->pinned);
608
609                 /* Scrolling to a warpped line in this message */
610                 if (lines + focus->scroll > 0)
611                         break;
612
613                 /* Find the previous message */
614                 int prev = next_message(focus->pinned);
615
616                 /* At the top message already,
617                  * just scroll to the top line. */
618                 if (prev < 0) {
619                         focus->scroll = -lines + 1;
620                         break;
621                 }
622
623                 /* Scroll back to the previous message */
624                 focus->pinned  = prev;
625                 focus->scroll += lines;
626         }
627
628         /* Scroll down to next messages */
629         while (focus->scroll > 0) {
630                 /* Find the next message */
631                 int next = prev_message(focus->pinned);
632
633                 /* At the bottom already,
634                  * remove pin and scroll to last line */
635                 if (next < 0) {
636                         focus->pinned = -1;
637                         focus->scroll = 0;
638                         break;
639                 }
640
641                 /* Scroll to the next message */
642                 int lines = get_lines(next);
643                 focus->pinned  = next;
644                 focus->scroll -= lines;
645         }
646
647         /* Find pinned message */
648         int log  = history-1;
649         int skip = 0;
650
651         if (focus->pinned >= 0) {
652                 log = focus->pinned;
653                 skip = focus->scroll;
654         }
655
656         /* Setup printer */
657         printer_t pr = {
658                 .top = hdr_lines,
659                 .end = LINES-cmd_lines-sts_lines,
660         };
661         print_clear(&pr);
662
663         /* Compute lines */
664         pr.print = 0;
665         pr.row = pr.top + skip;
666         while (pr.row < pr.end && log >= 0)
667                 print_message(&pr, &messages[log--]);
668
669         /* Compute skip lines */
670         skip = pr.end - pr.row;
671         log = log + 1;
672
673         /* Print lines */
674         pr.print = 1;
675         pr.row = pr.top + skip;
676         while (pr.row < pr.end && log < history)
677                 print_message(&pr, &messages[log++]);
678 }
679
680 static void draw_status(void)
681 {
682         /* Compute lines */
683         printer_t pr = {};
684         print_status(&pr);
685         sts_lines = pr.row + 1;
686
687         /* Draw status */
688         attron(color_title);
689         pr.print = 1;
690         pr.row = LINES-cmd_lines-sts_lines;
691         pr.top = LINES-cmd_lines-sts_lines;
692         pr.end = LINES-cmd_lines;
693         print_clear(&pr);
694         print_status(&pr);
695         attroff(color_title);
696 }
697
698 static void draw_cmdline(void)
699 {
700         const char *name = focus->dest->name;
701         printer_t pr = {
702                 .indent = 1 + strlen(name) + 2,
703                 .margin = 1,
704         };
705
706         /* Terminate buffer */
707         cmd_buf[cmd_len] = '\0';
708
709         /* Compute lines */
710         pr.col = pr.indent;
711         print_string(&pr, cmd_buf);
712         cmd_lines = pr.row + 1;
713
714         /* Clear screen */
715         pr.print = 1;
716         pr.col = pr.indent;
717         pr.row = LINES-cmd_lines;
718         pr.top = LINES-cmd_lines;
719         pr.end = LINES;
720         print_clear(&pr);
721
722         /* Print cmdline */
723         mvprintw(pr.top, 0, "[%s]", name);
724         pr.pick  = cmd_pos;
725         print_string(&pr, cmd_buf);
726         cmd_row = pr.prow;
727         cmd_col = pr.pcol;
728 }
729
730 /* Handle input */
731 static void process(int chr)
732 {
733         /* Window management */
734         if (chr == KEY_RESIZE) {
735                 clear();
736         }
737         else if (chr == KEY_CTRL_L) {
738                 clear();
739         }
740         else if (chr == KEY_CTRL_G) {
741                 view_draw();
742         }
743
744         /* View management */
745         else if (chr == KEY_CTRL_N) {
746                 focus = next_window(focus) ?: focus;
747         }
748         else if (chr == KEY_CTRL_P) {
749                 focus = prev_window(focus) ?: focus;
750         }
751         else if (chr == KEY_CTRL_X) {
752                 cycle_channel();
753         }
754         else if (chr == KEY_CTRL_Y) {
755                 focus->scroll -= 1;
756         }
757         else if (chr == KEY_CTRL_E) {
758                 focus->scroll += 1;
759         }
760         else if (chr == KEY_CTRL_U) {
761                 focus->scroll -= (LINES-3)/2;
762         }
763         else if (chr == KEY_CTRL_D) {
764                 focus->scroll += (LINES-3)/2;
765         }
766         else if (chr == KEY_PPAGE) {
767                 focus->scroll -= (LINES-3)-1;
768         }
769         else if (chr == KEY_NPAGE) {
770                 focus->scroll += (LINES-3)-1;
771         }
772
773         /* Cmdline Input */
774         else if (chr == KEY_RETURN) {
775                 cmd_buf[cmd_len] = '\0';
776                 if (send_command(cmd_buf)) {
777                         cmd_pos = 0;
778                         cmd_len = 0;
779                 }
780         }
781         else if (chr == KEY_TAB) {
782                 if (cmd_pos == 0)
783                         last_channel();
784                 else
785                         debug("todo");
786         }
787         else if (chr == KEY_ESCAPE) {
788                 cmd_pos = 0;
789                 cmd_len = 0;
790         }
791         else if (chr == KEY_LEFT) {
792                 if (cmd_pos > 0)
793                         cmd_pos--;
794         }
795         else if (chr == KEY_RIGHT) {
796                 if (cmd_pos < cmd_len)
797                         cmd_pos++;
798         }
799         else if (chr == KEY_HOME) {
800                 cmd_pos = 0;
801         }
802         else if (chr == KEY_END) {
803                 cmd_pos = cmd_len;;
804         }
805         else if (chr == KEY_BACKSPACE ||
806                  chr == KEY_DELETE) {
807                 if (cmd_pos > 0) {
808                         memmove(&cmd_buf[cmd_pos-1],
809                                 &cmd_buf[cmd_pos],
810                                 (cmd_len-cmd_pos)+1);
811                         cmd_pos--;
812                         cmd_len--;
813                 }
814         }
815         else if (chr == KEY_DC) {
816                 if (cmd_pos < cmd_len) {
817                         memmove(&cmd_buf[cmd_pos],
818                                 &cmd_buf[cmd_pos+1],
819                                 (cmd_len-cmd_pos)+1);
820                         cmd_len--;
821                 }
822         }
823         else if (isprint(chr)) {
824                 if (cmd_len+2 < sizeof(cmd_buf)) {
825                         memmove(&cmd_buf[cmd_pos+1],
826                                 &cmd_buf[cmd_pos],
827                                 (cmd_len-cmd_pos)+1);
828                         cmd_buf[cmd_pos] = chr;
829                         cmd_pos++;
830                         cmd_len++;
831                 } else {
832                         debug("form: out of space");
833                 }
834         }
835
836         /* Unknown control character */
837         else {
838                 debug("main: Unhandled key - Dec %3d,  Hex %02x,  Oct %03o,  Chr <%c>",
839                                 chr, chr, chr, chr);
840         }
841 }
842
843 /* View init */
844 void view_init(void)
845 {
846         /* System windows */
847         sys_srv.name = strcopy("system");
848         sys_srv.protocol = -1;
849
850         sys_chan.name = strcopy("system");
851         sys_chan.server = &sys_srv;
852
853         sys_win.name = strcopy("system");
854         sys_win.dest = &sys_chan;
855         sys_win.channel = &sys_chan;
856         sys_win.pinned = -1;
857         sys_win.next = windows;
858
859         windows = focus = &sys_win;
860
861         /* Setup windows */
862         for (window_t *win = windows; win; win = win->next)
863                 if (win->filter)
864                         regcomp(&win->regex, win->filter, RE_FLAGS);
865
866         /* Print welcome message */
867         chat_recv(&sys_chan, NULL, "Welcome to lamechat!");
868
869         if (sys_win.next) {
870                 update_windows();
871                 sys_win.hide = 1;
872         }
873
874         /* Set default escape timeout */
875         if (!getenv("ESCDELAY"))
876                 putenv("ESCDELAY=25");
877
878         /* Setup Curses */
879         setlocale(LC_ALL, "");
880         initscr();
881         cbreak();
882         noecho();
883         keypad(stdscr, TRUE);
884         start_color();
885         timeout(0);
886         use_default_colors();
887
888         color_title = color(COLOR_WHITE, COLOR_BLUE);
889         color_date  = color(COLOR_BROWN, -1);
890         color_error = color(COLOR_RED,   -1);
891
892         /* Create signal FD */
893         sigset_t mask;
894         sigemptyset(&mask);
895         sigaddset(&mask, SIGWINCH);
896         if ((sig_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0)
897                 error("creating signal fd");
898
899         /* Register callback */
900         poll_add(&poll_in, 0, (cb_t)view_sync, NULL);
901         poll_ctl(&poll_in, 1, 0, 1);
902
903         poll_add(&poll_sig, sig_fd, (cb_t)view_sync, NULL);
904         poll_ctl(&poll_sig, 1, 0, 1);
905
906         /* Set running */
907         running = 1;
908
909         /* Draw initial view */
910         view_draw();
911         view_sync();
912 }
913
914 /* Config parser */
915 void view_config(const char *group, const char *name, const char *key, const char *value)
916 {
917         window_t *win;
918
919         if (match(group, "general")) {
920                 if (match(key, "theme"))
921                         theme = get_map(value, themes);
922                 else if (match(key, "defocus"))
923                         defocus = get_number(value);
924         }
925
926         if (match(group, "window")) {
927                 win = find_window(name);
928                 if (match(key, "")) {
929                         win = add_window(&sys_chan);
930                         strset(&win->name, get_name(name));
931                 }
932                 else if (match(key, "server")) {
933                         win->server = find_server(get_string(value));
934                 }
935                 else if (match(key, "channel")) {
936                         win->channel = find_channel(get_string(value));
937                         win->dest    = win->channel ?: win->dest;
938                 }
939                 else if (match(key, "filter")) {
940                         win->filter = get_string(value);
941                 }
942         }
943 }
944
945 /* View event */
946 void view_sync(void)
947 {
948         int chr;
949         while ((chr = getch()) != ERR) {
950                 process(chr);
951                 deadline = time(NULL) + defocus;
952                 draw = 1;
953         }
954
955         if (defocus && time(NULL) > deadline &&
956             focus->dest != &sys_chan) {
957                 focus->saved = focus->dest;
958                 focus->dest = &sys_chan;
959                 draw  = 1;
960         }
961
962         if (draw) {
963                 debug("view: flush draw");
964
965                 update_windows();
966
967                 draw_cmdline();
968                 draw_status();
969                 draw_header();
970                 draw_chat();
971
972                 move(cmd_row, cmd_col);
973
974                 refresh();
975
976                 draw = 0;
977         }
978 }
979
980 void view_draw(void)
981 {
982         debug("view: queue draw");
983         draw = 1;
984 }
985
986 void view_exit(void)
987 {
988         if (!running)
989                 return;
990         endwin();
991 }