/* * Copyright (C) 2012-2013 Andy Spencer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _XOPEN_SOURCE #define _XOPEN_SOURCE_EXTENDED #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "conf.h" #include "chat.h" #include "view.h" /* View constants */ #define RE_FLAGS (REG_EXTENDED|REG_NOSUB) /* Extra colors */ #define COLOR_BROWN 130 /* Extra keys */ #define KEY_CTRL_D '\4' #define KEY_CTRL_E '\5' #define KEY_CTRL_G '\7' #define KEY_CTRL_L '\14' #define KEY_CTRL_N '\16' #define KEY_CTRL_P '\20' #define KEY_CTRL_U '\25' #define KEY_CTRL_X '\30' #define KEY_CTRL_Y '\31' #define KEY_TAB '\11' #define KEY_RETURN '\12' #define KEY_ESCAPE '\33' #define KEY_DELETE '\177' /* View types */ typedef struct window_t window_t; typedef struct window_t { char *name; int hide; int flag; int scroll; int pinned; int custom; char *filter; server_t *server; channel_t *channel; regex_t regex; channel_t *dest; channel_t *saved; window_t *next; } window_t; typedef struct { int top; int end; int row; int col; int indent; int margin; int print; int pick; int prow; int pcol; } printer_t; typedef enum { THEME_NORMAL, THEME_DARK, THEME_LIGHT, } theme_t; typedef enum { ABBREV_NONE, ABBREV_FIRST, ABBREV_LAST, ABBREV_FLAST, ABBREV_INITIALS, } abbrev_t; /* Local data */ static poll_t poll_in; static poll_t poll_sig; static int sig_fd; static int running; static int defocus; static int deadline; static int theme; static int abbrev; static int draw; static char cmd_buf[4096]; static int cmd_pos; static int cmd_len; static int cmd_row; static int cmd_col; static int hdr_lines; static int sts_lines; static int cmd_lines; static server_t sys_srv; static channel_t sys_chan; static window_t sys_win; static window_t *focus; static window_t *windows; static int ncolors = 1; static short colors[256+1][256+1]; static short color_title; static short color_date; static short color_error; static const char *themes[] = { [THEME_NORMAL] "normal", [THEME_DARK] "dark", [THEME_LIGHT] "light", }; static const char *abbrevs[] = { [ABBREV_NONE] "none", [ABBREV_FIRST] "first", [ABBREV_LAST] "last", [ABBREV_FLAST] "flast", [ABBREV_INITIALS] "initials", }; /* Helper functions */ static short color(int fg, int bg) { if (!colors[fg+1][bg+1]) { init_pair(ncolors, fg, bg); colors[fg+1][bg+1] = ncolors; ncolors++; } return COLOR_PAIR(colors[fg+1][bg+1]); } static void abbreviate(char **dst, const char *name) { char first[32]; char last[32]; char flast[32]; char initials[32]; int max = 32, fi = 0, fli = 0, li = 0, ii = 0; int word = 0, space = 1; if (!name) return; for (int i = 0; name[i]; i++) { if (name[i] == ' ' || name[i] == '-') { space = 1; word++; continue; } if (word == 0 && fi < max) first[fi++] = tolower(name[i]); if (word > 0 && li < max) last[li++] = tolower(name[i]); if (((space && word == 0) || word > 0) && fli < max) flast[fli++] = tolower(name[i]); if (space && ii < max) initials[ii++] = tolower(name[i]); if (space) space = 0; } first[fi] = '\0'; last[li] = '\0'; flast[fli] = '\0'; initials[ii] = '\0'; switch (abbrev) { case ABBREV_NONE: strset(dst, name); break; case ABBREV_FIRST: strset(dst, first); break; case ABBREV_LAST: strset(dst, li?last:first); break; case ABBREV_FLAST: strset(dst, li?flast:first); break; case ABBREV_INITIALS: strset(dst, initials); break; } } static int msg_compare(const void *_a, const void *_b) { const message_t *a = _a; const message_t *b = _b; return a->when < b->when ? -1 : a->when > b->when ? 1 : 0; } static unsigned str_hash(const char *str) { unsigned long hash = 0; for (int i = 0; str[i]; i++) hash = str[i] + (hash<<6) + (hash<<16) - hash; for (int i = 0; str[i]; i++) hash = str[i] + (hash<<6) + (hash<<16) - hash; return hash; } static int rgb_color(int r, int g, int b) { return MIN(r*6/0x100, 5)*6*6 + MIN(g*6/0x100, 5)*6 + MIN(b*6/0x100, 5) + 16; } static int str_color(const char *str) { int h,s,l, t,c,x,m, r,g,b; unsigned int n = str_hash(str); h = (n / 0x1) % 0x100; // 0..256 s = (n / 0x100) % 0x100; // 0..256 l = (n / 0x10000) % 0x100; // 0..256 h = h * 360 / 0x100; // 0..360 s = s / 0x4 + 0xC0; // 0..256 switch (theme) { case THEME_DARK: l = l / 0x4 + 0x80; break; case THEME_LIGHT: l = l / 0x4 + 0x30; break; case THEME_NORMAL: l = l / 0x4 + 0x60; break; } t = h / 60; // ,1,2,3,4,5 c = ((0x80-abs(l-0x80))*s)/0x80; // ..256 x = (c*(60-abs((h%120)-60)))/60; // ..256 m = l - c/2; // ..128 r = t==0 || t==5 ? c+m : t==1 || t==4 ? x+m : m; g = t==1 || t==2 ? c+m : t==0 || t==3 ? x+m : m; b = t==3 || t==4 ? c+m : t==2 || t==5 ? x+m : m; x = rgb_color(r, g, b); //debug("%12s %3d,%02x,%02x -> " // "tcxm=%d,%02x,%02x,%02x -> " // "rgb=%02x,%02x,%02x -> %d", // str, h,s,l, t,c,x,m, r,g,b, x); return color(x, -1); } /* Window functions */ static int in_window(message_t *msg, window_t *win) { if (win->server && win->server != msg->channel->server) return 0; if (win->channel && win->channel != msg->channel) return 0; if (win->filter && regexec(&win->regex, msg->channel->name, 0, 0, 0)) return 0; return 1; } static window_t *add_window(channel_t *dest) { window_t *win = new0(window_t); win->dest = dest; win->saved = dest; win->pinned = -1; abbreviate(&win->name, dest->name); window_t **last = &windows; while (*last) last = &(*last)->next; *last = win; return win; } static window_t *find_window(const char *name) { for (window_t *win = windows; win; win = win->next) if (match(win->name, name)) return win; return NULL; } static window_t *next_window(window_t *win) { for (window_t *cur = win->next; cur; cur = cur->next) if (!cur->hide) return cur; return NULL; } static window_t *prev_window(window_t *win) { window_t *prev = NULL; for (window_t *cur = windows; cur != win; cur = cur->next) if (!cur->hide) prev = cur; return prev; } static void cycle_channel(void) { /* Clear seen flags */ for (int i = 0; i < history; i++) messages[i].channel->seen = 0; /* Find current channel */ channel_t *first = NULL, *cur = NULL, *next = NULL; for (int i = 0; i < history && !next; i++) { message_t *msg = &messages[i]; if (msg->channel->seen) continue; msg->channel->seen = 1; if (!in_window(msg, focus)) continue; if (cur) next = msg->channel; if (msg->channel == focus->dest) cur = msg->channel; if (!first) first = msg->channel; } focus->dest = next ?: first ?: &sys_chan; } static void last_channel(void) { if (focus->dest == &sys_chan && focus->saved != &sys_chan) { focus->dest = focus->saved; return; } for (int i = history-1; i>=0; i--) { if (in_window(&messages[i], focus)) { focus->dest = messages[i].channel; return; } } } static void update_windows(void) { static int seen = 0; /* Update focus */ if (focus->hide) { focus = next_window(focus) ?: prev_window(focus) ?: &sys_win; focus->hide = 0; } /* Update names */ for (window_t *cur = windows; cur; cur = cur->next) if (cur->channel && !cur->custom) abbreviate(&cur->name, cur->channel->name); /* Process new messages */ for (; seen < history; seen++) { message_t *msg = &messages[seen]; channel_t *chan = msg->channel; window_t *prime = NULL; int found = 0; /* Skip focused messages */ if (in_window(msg, focus)) continue; /* Flag existing windows */ for (window_t *cur = windows; cur; cur = cur->next) { if (!in_window(msg, cur)) continue; if (cur->channel == chan) prime = cur; if (!cur->hide) found = 1; cur->flag += 1; } /* No window, create a new one */ if (!found) { if (prime) { prime->hide = 0; } else { prime = add_window(chan); prime->channel = chan; } } } } static server_t *find_server(const char *name) { for (server_t *cur = servers; cur; cur = cur->next) if (match(cur->name, name)) return cur; return NULL; } static channel_t *find_channel(const char *name) { for (channel_t *cur = channels; cur; cur = cur->next) if (match(cur->name, name)) return cur; return NULL; } /* Print functions */ static int word_wrap(printer_t *pr, const char *txt, const char **l, const char **r) { /* In previous word? */ if (txt > *l && txt < *r) return 0; /* Find left and right ends of the word */ *l = txt; while (*txt != '\0' && *txt != ' ' && *txt != '\t' && *txt != '\r' && *txt != '\n') txt++; *r = txt; /* End goes past the margin? */ int len = *r - *l; int edge = COLS - pr->margin; if (pr->col + len > edge) { if (pr->indent + len > edge) *r = *l + (edge - pr->indent); return pr->col != pr->indent; } return 0; } static int irc_number(const char *txt, int *i) { int n = 0; int r = 0; char a = txt[*i]; char b = a ? txt[(*i)+1] : 0; if ('0' == a) n = ('0' <= b && b <= '9') ? 2 : 1; if ('1' == a) n = ('0' <= b && b <= '5') ? 2 : 1; if ('2' <= a && a <= '9') n = 1; if (n == 0) return -1; for (int j = 0; j < n; j++) r = r*10 + txt[(*i)++]-'0'; return r; } static int irc_color(const char *txt, int *i) { static int color_map[16][3] = { [0x0] { 0xFF, 0xFF, 0xFF }, // White [0x1] { 0x00, 0x00, 0x00 }, // Black [0x2] { 0x00, 0x00, 0x80 }, // Navy Blue [0x3] { 0x00, 0x80, 0x00 }, // Green [0x4] { 0xFF, 0x00, 0x00 }, // Red [0x5] { 0x80, 0x40, 0x40 }, // Brown [0x6] { 0x80, 0x00, 0xFF }, // Purple [0x7] { 0x80, 0x80, 0x00 }, // Olive [0x8] { 0xFF, 0xFF, 0x00 }, // Yellow [0x9] { 0x00, 0xFF, 0x00 }, // Lime Green [0xA] { 0x00, 0x80, 0x80 }, // Teal [0xB] { 0x00, 0xFF, 0xFF }, // Aqua Light [0xC] { 0x00, 0x00, 0xFF }, // Royal Blue [0xD] { 0xFF, 0x00, 0xFF }, // Hot Pink [0xE] { 0x80, 0x80, 0x80 }, // Dark Gray [0xF] { 0xC0, 0xC0, 0xC0 }, // Light Gray }; int attr = 0; int reset = 0; int fg = -1; int bg = -1; switch (txt[*i]) { // Format attributes case '\002': attr = A_BOLD; break; // bold case '\011': attr = A_ITALIC; break; // italic case '\023': attr = A_UNDERLINE; break; // strike case '\025': attr = A_UNDERLINE; break; // underline case '\037': attr = A_UNDERLINE; break; // underline case '\026': attr = A_REVERSE; break; // reverse // Reset case '\017': reset = 1; break; // reset // Colors case '\003': case '\013': (*i)++; fg = irc_number(txt, i); if (txt[*i] == ',') { (*i)++; bg = irc_number(txt, i); if (bg == -1) (*i)--; } (*i)--; break; // Not IRC Colors default: return 0; } if (reset) { attrset(0); } else if (attr){ attr_t tmp = 0; short pair = 0; attr_get(&tmp, &pair, 0); attr_set(tmp^attr, pair, 0); } else if (fg != -1 || bg != -1) { if (0 <= fg && fg <= 15) fg = rgb_color(color_map[fg][0], color_map[fg][1], color_map[fg][2]); if (0 <= bg && bg <= 15) bg = rgb_color(color_map[bg][0], color_map[bg][1], color_map[bg][2]); attron(color(fg, bg)); } return 1; } static void print_string(printer_t *pr, const char *txt) { int i, n; wchar_t wc; const char *l = 0, *r = 0; for (i = 0; txt[i]; i++) { if (word_wrap(pr, &txt[i], &l, &r)) { pr->row += 1; pr->col = pr->indent; } if (i == pr->pick) { pr->prow = pr->row; pr->pcol = pr->col; } if (irc_color(txt, &i)) continue; switch (txt[i]) { case ' ': pr->col += 1; break; case '\t': pr->col -= pr->indent; pr->col /= 8; pr->col += 1; pr->col *= 8; pr->col += pr->indent; break; case '\r': case '\n': pr->row += 1; pr->col = pr->indent; break; default: if ((n = mbtowc(&wc, &txt[i], 1)) == -1) break; i += n-1; if (pr->print && pr->row >= pr->top && pr->row < pr->end) mvaddnwstr(pr->row, pr->col, &wc, 1); pr->col += 1; break; } } if (i <= pr->pick) { pr->prow = pr->row; pr->pcol = pr->col; } } static void print_format(printer_t *pr, const char *fmt, ...) { static char buf[4096]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); print_string(pr, buf); } static void print_message(printer_t *pr, message_t *msg) { if (!in_window(msg, focus)) return; time_t timep = msg->when; struct tm *tm = localtime(&timep); /* Start new line */ pr->col = 0; pr->indent = 0; /* Print time */ attron(color_date); print_format(pr, "%02d:%02d ", tm->tm_hour, tm->tm_min); attroff(color_date); /* Print channel */ if (!focus->channel) { attron(str_color(msg->channel->name) | A_BOLD); print_format(pr, "[%s] ", msg->channel->name); attroff(str_color(msg->channel->name) | A_BOLD); } /* Print from */ if (msg->from) { attron(str_color(msg->from->name)); print_format(pr, "%s: ", msg->from->name); attroff(str_color(msg->from->name)); } else { print_format(pr, "*** "); } /* Print message */ pr->indent = pr->col; print_string(pr, msg->text); pr->row++; } static void print_status(printer_t *pr) { pr->col = 0; print_format(pr, " Windows: "); pr->indent = pr->col; pr->margin = 1; for (window_t *cur = windows; cur; cur = cur->next) { if (cur->hide) continue; if (cur == focus) cur->flag = 0; if (cur == focus) attron(A_BOLD); if (cur->flag) print_format(pr, "[%s:%d]", cur->name, cur->flag); else print_format(pr, "[%s]", cur->name); if (cur == focus) attroff(A_BOLD); pr->col++; } } static void print_clear(printer_t *pr) { for (int i = pr->top; i < pr->end; i++) mvhline(i, 0, ' ', COLS); } /* Message info */ static int get_lines(int pos) { printer_t pr = {.print=0}; print_message(&pr, &messages[pos]); return pr.row; } static int next_message(int pos) { while (1) { if (--pos < 0) return -1; if (in_window(&messages[pos], focus)) return pos; } } static int prev_message(int pos) { while (1) { if (++pos >= history) return -1; if (in_window(&messages[pos], focus)) return pos; } } /* Commands */ static int send_command(const char *text) { const char *arg; window_t *win = focus; if (match(text, "/quit")) { poll_quit(); } else if (match(text, "/sort")) { qsort(messages, history, sizeof(message_t), msg_compare); } else if (prefix(text, "/theme", &arg)) { for (int i = 0; i < N_ELEMENTS(themes); i++) if (match(themes[i], arg)) theme = i; } else if (prefix(text, "/abbrev", &arg)) { for (int i = 0; i < N_ELEMENTS(abbrevs); i++) if (match(abbrevs[i], arg)) abbrev = i; } else if (match(text, "/open")) { win = find_window(focus->dest->name); if (!win || win == focus) { win = add_window(focus->dest); win->channel = focus->dest; } focus = win; focus->hide = 0; } else if (match(text, "/close")) { focus->hide = 1; } else if (prefix(text, "/name", &arg) && arg) { focus->custom = 1; strset(&focus->name, arg); } else if (prefix(text, "/server", &arg)) { win->server = find_server(arg); } else if (prefix(text, "/channel", &arg)) { win->channel = find_channel(arg); win->dest = win->channel ?: win->dest; } else if (prefix(text, "/filter", &arg)) { strset(&focus->filter, arg); if (focus->filter) regcomp(&focus->regex, focus->filter, RE_FLAGS); } else if (focus->dest != &sys_chan) { chat_send(focus->dest, text); } else { return 0; } return 1; } /* Drawing functions */ static void draw_header(void) { printer_t pr = { .print = 1, .row = 0, .col = 1, .indent = 1, .margin = 1, .end = 3, }; const char *topic = focus->dest->topic ?: "No Topic"; attron(color_title); print_clear(&pr); print_format(&pr, "%s", topic); attroff(color_title); hdr_lines = pr.row + 1; } static void draw_chat(void) { /* Clear pinned if scrolling */ if (focus->scroll && focus->pinned < 0) focus->pinned = history-1; /* Scroll up to previous messages */ while (focus->scroll < 0) { int lines = get_lines(focus->pinned); /* Scrolling to a warpped line in this message */ if (lines + focus->scroll > 0) break; /* Find the previous message */ int prev = next_message(focus->pinned); /* At the top message already, * just scroll to the top line. */ if (prev < 0) { focus->scroll = -lines + 1; break; } /* Scroll back to the previous message */ focus->pinned = prev; focus->scroll += lines; } /* Scroll down to next messages */ while (focus->scroll > 0) { /* Find the next message */ int next = prev_message(focus->pinned); /* At the bottom already, * remove pin and scroll to last line */ if (next < 0) { focus->pinned = -1; focus->scroll = 0; break; } /* Scroll to the next message */ int lines = get_lines(next); focus->pinned = next; focus->scroll -= lines; } /* Find pinned message */ int log = history-1; int skip = 0; if (focus->pinned >= 0) { log = focus->pinned; skip = focus->scroll; } /* Setup printer */ printer_t pr = { .top = hdr_lines, .end = LINES-cmd_lines-sts_lines, }; print_clear(&pr); /* Compute lines */ pr.print = 0; pr.row = pr.top + skip; while (pr.row < pr.end && log >= 0) print_message(&pr, &messages[log--]); /* Compute skip lines */ skip = pr.end - pr.row; log = log + 1; /* Print lines */ pr.print = 1; pr.row = pr.top + skip; while (pr.row < pr.end && log < history) print_message(&pr, &messages[log++]); } static void draw_status(void) { /* Compute lines */ printer_t pr = {}; print_status(&pr); sts_lines = pr.row + 1; /* Draw status */ attron(color_title); pr.print = 1; pr.row = LINES-cmd_lines-sts_lines; pr.top = LINES-cmd_lines-sts_lines; pr.end = LINES-cmd_lines; print_clear(&pr); print_status(&pr); attroff(color_title); } static void draw_cmdline(void) { const char *name = focus->dest->name; printer_t pr = { .indent = 1 + strlen(name) + 2, .margin = 1, }; /* Terminate buffer */ cmd_buf[cmd_len] = '\0'; /* Compute lines */ pr.col = pr.indent; print_string(&pr, cmd_buf); cmd_lines = pr.row + 1; /* Clear screen */ pr.print = 1; pr.col = pr.indent; pr.row = LINES-cmd_lines; pr.top = LINES-cmd_lines; pr.end = LINES; print_clear(&pr); /* Print cmdline */ mvprintw(pr.top, 0, "[%s]", name); pr.pick = cmd_pos; print_string(&pr, cmd_buf); cmd_row = pr.prow; cmd_col = pr.pcol; } /* Handle input */ static void process(wint_t chr) { /* Window management */ if (chr == KEY_RESIZE) { clear(); } else if (chr == KEY_CTRL_L) { clear(); } else if (chr == KEY_CTRL_G) { view_draw(); } /* View management */ else if (chr == KEY_CTRL_N) { focus = next_window(focus) ?: focus; } else if (chr == KEY_CTRL_P) { focus = prev_window(focus) ?: focus; } else if (chr == KEY_CTRL_X) { cycle_channel(); } else if (chr == KEY_CTRL_Y) { focus->scroll -= 1; } else if (chr == KEY_CTRL_E) { focus->scroll += 1; } else if (chr == KEY_CTRL_U) { focus->scroll -= (LINES-3)/2; } else if (chr == KEY_CTRL_D) { focus->scroll += (LINES-3)/2; } else if (chr == KEY_PPAGE) { focus->scroll -= (LINES-3)-1; } else if (chr == KEY_NPAGE) { focus->scroll += (LINES-3)-1; } /* Cmdline Input */ else if (chr == KEY_RETURN) { cmd_buf[cmd_len] = '\0'; if (send_command(cmd_buf)) { cmd_pos = 0; cmd_len = 0; } } else if (chr == KEY_TAB) { if (cmd_pos == 0) last_channel(); else debug("todo"); } else if (chr == KEY_ESCAPE) { cmd_pos = 0; cmd_len = 0; } else if (chr == KEY_LEFT) { if (cmd_pos > 0) cmd_pos--; } else if (chr == KEY_RIGHT) { if (cmd_pos < cmd_len) cmd_pos++; } else if (chr == KEY_HOME) { cmd_pos = 0; } else if (chr == KEY_END) { cmd_pos = cmd_len;; } else if (chr == KEY_BACKSPACE || chr == KEY_DELETE) { if (cmd_pos > 0) { memmove(&cmd_buf[cmd_pos-1], &cmd_buf[cmd_pos], (cmd_len-cmd_pos)+1); cmd_pos--; cmd_len--; } } else if (chr == KEY_DC) { if (cmd_pos < cmd_len) { memmove(&cmd_buf[cmd_pos], &cmd_buf[cmd_pos+1], (cmd_len-cmd_pos)+1); cmd_len--; } } else if (iswprint(chr)) { if (cmd_len+MB_CUR_MAX+1 < sizeof(cmd_buf)) { memmove(&cmd_buf[cmd_pos+1], &cmd_buf[cmd_pos], (cmd_len-cmd_pos)+1); int n = wctomb(&cmd_buf[cmd_pos], chr); cmd_pos += n; cmd_len += n; } else { debug("form: out of space"); } } /* Unknown control character */ else { debug("main: Unhandled key - Dec %3d, Hex %02x, Oct %03o, Chr <%c>", chr, chr, chr, chr); } } /* View init */ void view_init(void) { /* System windows */ sys_srv.name = strcopy("system"); sys_srv.protocol = -1; sys_chan.name = strcopy("system"); sys_chan.server = &sys_srv; sys_win.name = strcopy("system"); sys_win.dest = &sys_chan; sys_win.channel = &sys_chan; sys_win.pinned = -1; sys_win.next = windows; windows = focus = &sys_win; /* Setup windows */ for (window_t *win = windows; win; win = win->next) if (win->filter) regcomp(&win->regex, win->filter, RE_FLAGS); /* Print welcome message */ chat_recv(&sys_chan, NULL, "Welcome to lamechat!"); if (sys_win.next) { update_windows(); sys_win.hide = 1; } /* Set default escape timeout */ if (!getenv("ESCDELAY")) putenv("ESCDELAY=25"); /* Setup Curses */ setlocale(LC_ALL, "en_US.utf8"); initscr(); cbreak(); noecho(); keypad(stdscr, TRUE); start_color(); timeout(0); use_default_colors(); color_title = color(COLOR_WHITE, COLOR_BLUE); color_date = color(COLOR_BROWN, -1); color_error = color(COLOR_RED, -1); /* Create signal FD */ sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGWINCH); if ((sig_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) error("creating signal fd"); /* Register callback */ poll_add(&poll_in, 0, (cb_t)view_sync, NULL); poll_ctl(&poll_in, 1, 0, 1); poll_add(&poll_sig, sig_fd, (cb_t)view_sync, NULL); poll_ctl(&poll_sig, 1, 0, 1); /* Set running */ running = 1; /* Draw initial view */ view_draw(); view_sync(); } /* Config parser */ void view_config(const char *group, const char *name, const char *key, const char *value) { window_t *win; if (match(group, "general")) { if (match(key, "theme")) theme = get_map(value, themes); else if (match(key, "abbrev")) abbrev = get_map(value, abbrevs); else if (match(key, "defocus")) defocus = get_number(value); } if (match(group, "window")) { win = find_window(name); if (match(key, "")) { win = add_window(&sys_chan); win->custom = 1; strset(&win->name, get_name(name)); } else if (match(key, "server")) { win->server = find_server(get_string(value)); } else if (match(key, "channel")) { win->channel = find_channel(get_string(value)); win->dest = win->channel ?: win->dest; } else if (match(key, "filter")) { win->filter = get_string(value); } } } /* View event */ void view_sync(void) { wint_t wch = 0; while (get_wch(&wch) != ERR) { process(wch); deadline = time(NULL) + defocus; draw = 1; } if (defocus && time(NULL) > deadline && focus->dest != &sys_chan) { focus->saved = focus->dest; focus->dest = &sys_chan; draw = 1; } if (draw) { debug("view: flush draw"); update_windows(); draw_cmdline(); draw_status(); draw_header(); draw_chat(); move(cmd_row, cmd_col); refresh(); draw = 0; } } void view_draw(void) { debug("view: queue draw"); draw = 1; } void view_exit(void) { if (!running) return; endwin(); }