]> Pileus Git - ~andy/lamechat/blob - view.c
1d083a400bf4544ffb235ce9b9e9dcfd147c3d60
[~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 void print_word(printer_t *pr, const char **msg)
350 {
351         const char *start = *msg;
352         while (*start && isspace(*start))
353                 start++;
354         int space = start-*msg;
355
356         const char *end = start;
357         while (*end && !isspace(*end))
358                 end++;
359         int len = end-start;
360
361         if ((pr->col != pr->indent) &&
362             (pr->col + space + len > COLS - pr->margin)) {
363                 pr->col = pr->indent;
364                 pr->row = pr->row+1;
365                 space = 0;
366         }
367
368         int print = pr->print;
369         if (pr->row < pr->top || pr->row >= pr->end)
370                 print = 0;
371
372         if (print)
373                 mvaddnstr(pr->row, pr->col, *msg, space);
374         pr->col += space;
375         if (print)
376                 mvaddnstr(pr->row, pr->col, start, len);
377         pr->col += len;
378
379         *msg = end;
380 }
381
382 static void print_string(printer_t *pr, const char *txt)
383 {
384         const char *pick = &txt[pr->pick];
385         if (!pr->pick) {
386                 pr->prow = pr->row;
387                 pr->pcol = pr->col;
388         }
389         while (*txt) {
390                 print_word(pr, &txt);
391                 if (pr->pick && txt > pick) {
392                         pr->prow = pr->row;
393                         pr->pcol = pr->col - (txt-pick);
394                         pr->pick = 0;
395                 }
396         }
397         if (pr->pick) {
398                 pr->prow = pr->row;
399                 pr->pcol = pr->col;
400         }
401         if (pr->pcol < pr->indent)
402                 pr->pcol = pr->indent;
403 }
404
405 static void print_format(printer_t *pr, const char *fmt, ...)
406 {
407         static char buf[4096];
408         va_list ap;
409         va_start(ap, fmt);
410         vsnprintf(buf, sizeof(buf), fmt, ap);
411         va_end(ap);
412
413         print_string(pr, buf);
414 }
415
416 static void print_message(printer_t *pr, message_t *msg)
417 {
418         if (!in_window(msg, focus))
419                 return;
420
421         time_t timep = msg->when;
422         struct tm *tm = localtime(&timep);
423
424         /* Start new line */
425         pr->col = 0;
426         pr->indent = 0;
427
428         /* Print time */
429         attron(color_date);
430         print_format(pr, "%02d:%02d ", tm->tm_hour, tm->tm_min);
431         attroff(color_date);
432
433         /* Print channel */
434         if (!focus->channel) {
435                 attron(str_color(msg->channel->name) | A_BOLD);
436                 print_format(pr, "[%s] ", msg->channel->name);
437                 attroff(str_color(msg->channel->name) | A_BOLD);
438         }
439
440         /* Print from */
441         if (msg->from) {
442                 attron(str_color(msg->from->name));
443                 print_format(pr, "%s: ", msg->from->name);
444                 attroff(str_color(msg->from->name));
445         } else {
446                 print_format(pr, "*** ");
447         }
448
449         /* Print message */
450         pr->indent = pr->col;
451         print_string(pr, msg->text);
452         pr->row++;
453 }
454
455 static void print_status(printer_t *pr)
456 {
457         pr->col = 0;
458         print_format(pr, " Windows: ");
459         pr->indent = pr->col;
460         pr->margin = 1;
461         for (window_t *cur = windows; cur; cur = cur->next) {
462                 if (cur->hide)
463                         continue;
464                 if (cur == focus)
465                         cur->flag = 0;
466                 if (cur == focus)
467                         attron(A_BOLD);
468                 if (cur->flag)
469                         print_format(pr, "[%s:%d]", cur->name, cur->flag);
470                 else
471                         print_format(pr, "[%s]", cur->name);
472                 if (cur == focus)
473                         attroff(A_BOLD);
474                 pr->col++;
475         }
476 }
477
478 static void print_clear(printer_t *pr)
479 {
480         for (int i = pr->top; i < pr->end; i++)
481                 mvhline(i, 0, ' ', COLS);
482 }
483
484 /* Message info */
485 static int get_lines(int pos)
486 {
487         printer_t pr = {.print=0};
488         print_message(&pr, &messages[pos]);
489         return pr.row;
490 }
491
492 static int next_message(int pos)
493 {
494         while (1) {
495                 if (--pos < 0)
496                         return -1;
497                 if (in_window(&messages[pos], focus))
498                         return pos;
499         }
500 }
501
502 static int prev_message(int pos)
503 {
504         while (1) {
505                 if (++pos >= history)
506                         return -1;
507                 if (in_window(&messages[pos], focus))
508                         return pos;
509         }
510 }
511
512 /* Commands */
513 static int send_command(const char *text)
514 {
515         const char *arg;
516         window_t   *win = focus;
517
518         if (match(text, "/quit")) {
519                 poll_quit();
520         }
521         else if (match(text, "/sort")) {
522                 qsort(messages, history, sizeof(message_t), msg_compare);
523         }
524         else if (prefix(text, "/theme", &arg)) {
525                 for (int i = 0; i < N_ELEMENTS(themes); i++)
526                         if (match(themes[i], arg))
527                                 theme = i;
528         }
529         else if (match(text, "/open")) {
530                 win = find_window(focus->dest->name);
531                 if (!win || win == focus) {
532                         win = add_window(focus->dest);
533                         win->channel = focus->dest;
534                 }
535                 focus = win;
536                 focus->hide = 0;
537         }
538         else if (match(text, "/close")) {
539                 focus->hide = 1;
540         }
541         else if (prefix(text, "/name", &arg) && arg) {
542                 strset(&focus->name, arg);
543         }
544         else if (prefix(text, "/server", &arg)) {
545                 win->server = find_server(arg);
546         }
547         else if (prefix(text, "/channel", &arg)) {
548                 win->channel = find_channel(arg);
549                 win->dest    = win->channel ?: win->dest;
550         }
551         else if (prefix(text, "/filter", &arg)) {
552                 strset(&focus->filter, arg);
553                 if (focus->filter)
554                         regcomp(&focus->regex, focus->filter, RE_FLAGS);
555         }
556         else if (focus->dest != &sys_chan) {
557                 chat_send(focus->dest, text);
558         }
559         else {
560                 return 0;
561         }
562         return 1;
563 }
564
565 /* Drawing functions */
566 static void draw_header(void)
567 {
568         printer_t pr = {
569                 .print  = 1,
570                 .row    = 0,
571                 .col    = 1,
572                 .indent = 1,
573                 .margin = 1,
574                 .end    = 5,
575         };
576         const char *topic = focus->dest->topic ?: "No Topic";
577         attron(color_title);
578         print_clear(&pr);
579         print_format(&pr, "%s", topic);
580         attroff(color_title);
581         hdr_lines = pr.row + 1;
582 }
583
584 static void draw_chat(void)
585 {
586         /* Clear pinned if scrolling */
587         if (focus->scroll && focus->pinned < 0)
588                 focus->pinned = history-1;
589
590         /* Scroll up to previous messages */
591         while (focus->scroll < 0) {
592                 int lines = get_lines(focus->pinned);
593
594                 /* Scrolling to a warpped line in this message */
595                 if (lines + focus->scroll > 0)
596                         break;
597
598                 /* Find the previous message */
599                 int prev = next_message(focus->pinned);
600
601                 /* At the top message already,
602                  * just scroll to the top line. */
603                 if (prev < 0) {
604                         focus->scroll = -lines + 1;
605                         break;
606                 }
607
608                 /* Scroll back to the previous message */
609                 focus->pinned  = prev;
610                 focus->scroll += lines;
611         }
612
613         /* Scroll down to next messages */
614         while (focus->scroll > 0) {
615                 /* Find the next message */
616                 int next = prev_message(focus->pinned);
617
618                 /* At the bottom already,
619                  * remove pin and scroll to last line */
620                 if (next < 0) {
621                         focus->pinned = -1;
622                         focus->scroll = 0;
623                         break;
624                 }
625
626                 /* Scroll to the next message */
627                 int lines = get_lines(next);
628                 focus->pinned  = next;
629                 focus->scroll -= lines;
630         }
631
632         /* Find pinned message */
633         int log  = history-1;
634         int skip = 0;
635
636         if (focus->pinned >= 0) {
637                 log = focus->pinned;
638                 skip = focus->scroll;
639         }
640
641         /* Setup printer */
642         printer_t pr = {
643                 .top = hdr_lines,
644                 .end = LINES-cmd_lines-sts_lines,
645         };
646         print_clear(&pr);
647
648         /* Compute lines */
649         pr.print = 0;
650         pr.row = pr.top + skip;
651         while (pr.row < pr.end && log >= 0)
652                 print_message(&pr, &messages[log--]);
653
654         /* Compute skip lines */
655         skip = pr.end - pr.row;
656         log = log + 1;
657
658         /* Print lines */
659         pr.print = 1;
660         pr.row = pr.top + skip;
661         while (pr.row < pr.end && log < history)
662                 print_message(&pr, &messages[log++]);
663 }
664
665 static void draw_status(void)
666 {
667         /* Compute lines */
668         printer_t pr = {};
669         print_status(&pr);
670         sts_lines = pr.row + 1;
671
672         /* Draw status */
673         attron(color_title);
674         pr.print = 1;
675         pr.row = LINES-cmd_lines-sts_lines;
676         pr.top = LINES-cmd_lines-sts_lines;
677         pr.end = LINES-cmd_lines;
678         print_clear(&pr);
679         print_status(&pr);
680         attroff(color_title);
681 }
682
683 static void draw_cmdline(void)
684 {
685         const char *name = focus->dest->name;
686         printer_t pr = {
687                 .indent = 1 + strlen(name) + 2,
688                 .margin = 1,
689         };
690
691         /* Terminate buffer */
692         cmd_buf[cmd_len] = '\0';
693
694         /* Compute lines */
695         pr.col = pr.indent;
696         print_string(&pr, cmd_buf);
697         cmd_lines = pr.row + 1;
698
699         /* Clear screen */
700         pr.print = 1;
701         pr.col = pr.indent;
702         pr.row = LINES-cmd_lines;
703         pr.top = LINES-cmd_lines;
704         pr.end = LINES;
705         print_clear(&pr);
706
707         /* Print cmdline */
708         mvprintw(pr.top, 0, "[%s]", name);
709         pr.pick  = cmd_pos;
710         print_string(&pr, cmd_buf);
711         cmd_row = pr.prow;
712         cmd_col = pr.pcol;
713 }
714
715 /* Handle input */
716 static void process(int chr)
717 {
718         /* Window management */
719         if (chr == KEY_RESIZE) {
720                 clear();
721         }
722         else if (chr == KEY_CTRL_L) {
723                 clear();
724         }
725         else if (chr == KEY_CTRL_G) {
726                 view_draw();
727         }
728
729         /* View management */
730         else if (chr == KEY_CTRL_N) {
731                 focus = next_window(focus) ?: focus;
732         }
733         else if (chr == KEY_CTRL_P) {
734                 focus = prev_window(focus) ?: focus;
735         }
736         else if (chr == KEY_CTRL_X) {
737                 cycle_channel();
738         }
739         else if (chr == KEY_CTRL_Y) {
740                 focus->scroll -= 1;
741         }
742         else if (chr == KEY_CTRL_E) {
743                 focus->scroll += 1;
744         }
745         else if (chr == KEY_CTRL_U) {
746                 focus->scroll -= (LINES-3)/2;
747         }
748         else if (chr == KEY_CTRL_D) {
749                 focus->scroll += (LINES-3)/2;
750         }
751         else if (chr == KEY_PPAGE) {
752                 focus->scroll -= (LINES-3)-1;
753         }
754         else if (chr == KEY_NPAGE) {
755                 focus->scroll += (LINES-3)-1;
756         }
757
758         /* Cmdline Input */
759         else if (chr == KEY_RETURN) {
760                 cmd_buf[cmd_len] = '\0';
761                 if (send_command(cmd_buf)) {
762                         cmd_pos = 0;
763                         cmd_len = 0;
764                 }
765         }
766         else if (chr == KEY_TAB) {
767                 if (cmd_pos == 0)
768                         last_channel();
769                 else
770                         debug("todo");
771         }
772         else if (chr == KEY_ESCAPE) {
773                 cmd_pos = 0;
774                 cmd_len = 0;
775         }
776         else if (chr == KEY_LEFT) {
777                 if (cmd_pos > 0)
778                         cmd_pos--;
779         }
780         else if (chr == KEY_RIGHT) {
781                 if (cmd_pos < cmd_len)
782                         cmd_pos++;
783         }
784         else if (chr == KEY_HOME) {
785                 cmd_pos = 0;
786         }
787         else if (chr == KEY_END) {
788                 cmd_pos = cmd_len;;
789         }
790         else if (chr == KEY_BACKSPACE ||
791                  chr == KEY_DELETE) {
792                 if (cmd_pos > 0) {
793                         memmove(&cmd_buf[cmd_pos-1],
794                                 &cmd_buf[cmd_pos],
795                                 (cmd_len-cmd_pos)+1);
796                         cmd_pos--;
797                         cmd_len--;
798                 }
799         }
800         else if (chr == KEY_DC) {
801                 if (cmd_pos < cmd_len) {
802                         memmove(&cmd_buf[cmd_pos],
803                                 &cmd_buf[cmd_pos+1],
804                                 (cmd_len-cmd_pos)+1);
805                         cmd_len--;
806                 }
807         }
808         else if (isprint(chr)) {
809                 if (cmd_len+2 < sizeof(cmd_buf)) {
810                         memmove(&cmd_buf[cmd_pos+1],
811                                 &cmd_buf[cmd_pos],
812                                 (cmd_len-cmd_pos)+1);
813                         cmd_buf[cmd_pos] = chr;
814                         cmd_pos++;
815                         cmd_len++;
816                 } else {
817                         debug("form: out of space");
818                 }
819         }
820
821         /* Unknown control character */
822         else {
823                 debug("main: Unhandled key - Dec %3d,  Hex %02x,  Oct %03o,  Chr <%c>",
824                                 chr, chr, chr, chr);
825         }
826 }
827
828 /* View init */
829 void view_init(void)
830 {
831         /* System windows */
832         sys_srv.name = strcopy("system");
833         sys_srv.protocol = -1;
834
835         sys_chan.name = strcopy("system");
836         sys_chan.server = &sys_srv;
837
838         sys_win.name = strcopy("system");
839         sys_win.dest = &sys_chan;
840         sys_win.channel = &sys_chan;
841         sys_win.pinned = -1;
842         sys_win.next = windows;
843
844         windows = focus = &sys_win;
845
846         /* Setup windows */
847         for (window_t *win = windows; win; win = win->next)
848                 if (win->filter)
849                         regcomp(&win->regex, win->filter, RE_FLAGS);
850
851         /* Print welcome message */
852         chat_recv(&sys_chan, NULL, "Welcome to lamechat!");
853
854         if (sys_win.next) {
855                 update_windows();
856                 sys_win.hide = 1;
857         }
858
859         /* Set default escape timeout */
860         if (!getenv("ESCDELAY"))
861                 putenv("ESCDELAY=25");
862
863         /* Setup Curses */
864         setlocale(LC_ALL, "");
865         initscr();
866         cbreak();
867         noecho();
868         keypad(stdscr, TRUE);
869         start_color();
870         timeout(0);
871         use_default_colors();
872
873         color_title = color(COLOR_WHITE, COLOR_BLUE);
874         color_date  = color(COLOR_BROWN, -1);
875         color_error = color(COLOR_RED,   -1);
876
877         /* Create signal FD */
878         sigset_t mask;
879         sigemptyset(&mask);
880         sigaddset(&mask, SIGWINCH);
881         if ((sig_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0)
882                 error("creating signal fd");
883
884         /* Register callback */
885         poll_add(&poll_in, 0, (cb_t)view_sync, NULL);
886         poll_ctl(&poll_in, 1, 0, 1);
887
888         poll_add(&poll_sig, sig_fd, (cb_t)view_sync, NULL);
889         poll_ctl(&poll_sig, 1, 0, 1);
890
891         /* Set running */
892         running = 1;
893
894         /* Draw initial view */
895         view_draw();
896         view_sync();
897 }
898
899 /* Config parser */
900 void view_config(const char *group, const char *name, const char *key, const char *value)
901 {
902         window_t *win;
903
904         if (match(group, "general")) {
905                 if (match(key, "theme"))
906                         theme = get_map(value, themes);
907                 else if (match(key, "defocus"))
908                         defocus = get_number(value);
909         }
910
911         if (match(group, "window")) {
912                 win = find_window(name);
913                 if (match(key, "")) {
914                         win = add_window(&sys_chan);
915                         strset(&win->name, get_name(name));
916                 }
917                 else if (match(key, "server")) {
918                         win->server = find_server(get_string(value));
919                 }
920                 else if (match(key, "channel")) {
921                         win->channel = find_channel(get_string(value));
922                         win->dest    = win->channel ?: win->dest;
923                 }
924                 else if (match(key, "filter")) {
925                         win->filter = get_string(value);
926                 }
927         }
928 }
929
930 /* View event */
931 void view_sync(void)
932 {
933         int chr;
934         while ((chr = getch()) != ERR) {
935                 process(chr);
936                 deadline = time(NULL) + defocus;
937                 draw = 1;
938         }
939
940         if (defocus && time(NULL) > deadline &&
941             focus->dest != &sys_chan) {
942                 focus->saved = focus->dest;
943                 focus->dest = &sys_chan;
944                 draw  = 1;
945         }
946
947         if (draw) {
948                 debug("view: flush draw");
949
950                 update_windows();
951
952                 draw_cmdline();
953                 draw_status();
954                 draw_header();
955                 draw_chat();
956
957                 move(cmd_row, cmd_col);
958
959                 refresh();
960
961                 draw = 0;
962         }
963 }
964
965 void view_draw(void)
966 {
967         debug("view: queue draw");
968         draw = 1;
969 }
970
971 void view_exit(void)
972 {
973         if (!running)
974                 return;
975         endwin();
976 }