sfeed_curses.c - sfeed - RSS and Atom parser
(HTM) git clone git://git.codemadness.org/sfeed
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
sfeed_curses.c (52209B)
---
1 #include <sys/ioctl.h>
2 #include <sys/select.h>
3 #include <sys/wait.h>
4
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <locale.h>
8 #include <signal.h>
9 #include <stdarg.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <termios.h>
14 #include <time.h>
15 #include <unistd.h>
16 #include <wchar.h>
17
18 #include "util.h"
19
20 /* curses */
21 #ifndef SFEED_MINICURSES
22 #include <curses.h>
23 #include <term.h>
24 #else
25 #include "minicurses.h"
26 #endif
27
28 #define LEN(a) sizeof((a))/sizeof((a)[0])
29 #define MAX(a,b) ((a) > (b) ? (a) : (b))
30 #define MIN(a,b) ((a) < (b) ? (a) : (b))
31
32 #ifndef SFEED_DUMBTERM
33 #define SCROLLBAR_SYMBOL_BAR "\xe2\x94\x82" /* symbol: "light vertical" */
34 #define SCROLLBAR_SYMBOL_TICK " "
35 #define LINEBAR_SYMBOL_BAR "\xe2\x94\x80" /* symbol: "light horizontal" */
36 #define LINEBAR_SYMBOL_RIGHT "\xe2\x94\xa4" /* symbol: "light vertical and left" */
37 #else
38 #define SCROLLBAR_SYMBOL_BAR "|"
39 #define SCROLLBAR_SYMBOL_TICK " "
40 #define LINEBAR_SYMBOL_BAR "-"
41 #define LINEBAR_SYMBOL_RIGHT "|"
42 #endif
43
44 /* color-theme */
45 #ifndef SFEED_THEME
46 #define SFEED_THEME "themes/mono.h"
47 #endif
48 #include SFEED_THEME
49
50 enum {
51 ATTR_RESET = 0, ATTR_BOLD_ON = 1, ATTR_FAINT_ON = 2, ATTR_REVERSE_ON = 7
52 };
53
54 enum Layout {
55 LayoutVertical = 0, LayoutHorizontal, LayoutMonocle, LayoutLast
56 };
57
58 enum Pane { PaneFeeds, PaneItems, PaneLast };
59
60 struct win {
61 int width; /* absolute width of the window */
62 int height; /* absolute height of the window */
63 int dirty; /* needs draw update: clears screen */
64 };
65
66 struct row {
67 char *text; /* text string, optional if using row_format() callback */
68 int bold;
69 void *data; /* data binding */
70 };
71
72 struct pane {
73 int x; /* absolute x position on the screen */
74 int y; /* absolute y position on the screen */
75 int width; /* absolute width of the pane */
76 int height; /* absolute height of the pane, should be > 0 */
77 off_t pos; /* focused row position */
78 struct row *rows;
79 size_t nrows; /* total amount of rows */
80 int focused; /* has focus or not */
81 int hidden; /* is visible or not */
82 int dirty; /* needs draw update */
83 /* (optional) callback functions */
84 struct row *(*row_get)(struct pane *, off_t);
85 char *(*row_format)(struct pane *, struct row *);
86 int (*row_match)(struct pane *, struct row *, const char *);
87 };
88
89 struct scrollbar {
90 int tickpos;
91 int ticksize;
92 int x; /* absolute x position on the screen */
93 int y; /* absolute y position on the screen */
94 int size; /* absolute size of the bar, should be > 0 */
95 int focused; /* has focus or not */
96 int hidden; /* is visible or not */
97 int dirty; /* needs draw update */
98 };
99
100 struct statusbar {
101 int x; /* absolute x position on the screen */
102 int y; /* absolute y position on the screen */
103 int width; /* absolute width of the bar */
104 char *text; /* data */
105 int hidden; /* is visible or not */
106 int dirty; /* needs draw update */
107 };
108
109 struct linebar {
110 int x; /* absolute x position on the screen */
111 int y; /* absolute y position on the screen */
112 int width; /* absolute width of the line */
113 int hidden; /* is visible or not */
114 int dirty; /* needs draw update */
115 };
116
117 /* /UI */
118
119 struct item {
120 char *fields[FieldLast];
121 char *line; /* allocated split line */
122 /* field to match new items, if link is set match on link, else on id */
123 char *matchnew;
124 time_t timestamp;
125 int timeok;
126 int isnew;
127 off_t offset; /* line offset in file for lazyload */
128 };
129
130 struct urls {
131 char **items; /* array of URLs */
132 size_t len; /* amount of items */
133 size_t cap; /* available capacity */
134 };
135
136 struct items {
137 struct item *items; /* array of items */
138 size_t len; /* amount of items */
139 size_t cap; /* available capacity */
140 };
141
142 static void alldirty(void);
143 static void cleanup(void);
144 static void draw(void);
145 static int getsidebarsize(void);
146 static void markread(struct pane *, off_t, off_t, int);
147 static void pane_draw(struct pane *);
148 static void sighandler(int);
149 static void updategeom(void);
150 static void updatesidebar(void);
151 static void urls_free(struct urls *);
152 static int urls_hasmatch(struct urls *, const char *);
153 static void urls_read(struct urls *, const char *);
154
155 static struct linebar linebar;
156 static struct statusbar statusbar;
157 static struct pane panes[PaneLast];
158 static struct scrollbar scrollbars[PaneLast]; /* each pane has a scrollbar */
159 static struct win win;
160 static size_t selpane;
161 /* fixed sidebar size, < 0 is automatic */
162 static int fixedsidebarsizes[LayoutLast] = { -1, -1, -1 };
163 static int layout = LayoutVertical, prevlayout = LayoutVertical;
164 static int onlynew = 0; /* show only new in sidebar */
165 static int usemouse = 1; /* use xterm mouse tracking */
166
167 static struct termios tsave; /* terminal state at startup */
168 static struct termios tcur;
169 static int devnullfd;
170 static int istermsetup, needcleanup;
171
172 static struct feed *feeds;
173 static struct feed *curfeed;
174 static size_t nfeeds; /* amount of feeds */
175 static time_t comparetime;
176 static struct urls urls;
177 static char *urlfile;
178
179 volatile sig_atomic_t state_sigchld = 0, state_sighup = 0, state_sigint = 0;
180 volatile sig_atomic_t state_sigterm = 0, state_sigwinch = 0;
181
182 static char *plumbercmd = "xdg-open"; /* env variable: $SFEED_PLUMBER */
183 static char *pipercmd = "sfeed_content"; /* env variable: $SFEED_PIPER */
184 static char *yankercmd = "xclip -r"; /* env variable: $SFEED_YANKER */
185 static char *markreadcmd = "sfeed_markread read"; /* env variable: $SFEED_MARK_READ */
186 static char *markunreadcmd = "sfeed_markread unread"; /* env variable: $SFEED_MARK_UNREAD */
187 static char *cmdenv; /* env variable: $SFEED_AUTOCMD */
188 static int plumberia = 0; /* env variable: $SFEED_PLUMBER_INTERACTIVE */
189 static int piperia = 1; /* env variable: $SFEED_PIPER_INTERACTIVE */
190 static int yankeria = 0; /* env variable: $SFEED_YANKER_INTERACTIVE */
191 static int lazyload = 0; /* env variable: $SFEED_LAZYLOAD */
192
193 static int
194 ttywritef(const char *fmt, ...)
195 {
196 va_list ap;
197 int n;
198
199 va_start(ap, fmt);
200 n = vfprintf(stdout, fmt, ap);
201 va_end(ap);
202 fflush(stdout);
203
204 return n;
205 }
206
207 static int
208 ttywrite(const char *s)
209 {
210 if (!s)
211 return 0; /* for tparm() returning NULL */
212 return write(1, s, strlen(s));
213 }
214
215 /* Print to stderr, call cleanup() and _exit(). */
216 __dead static void
217 die(const char *fmt, ...)
218 {
219 va_list ap;
220 int saved_errno;
221
222 saved_errno = errno;
223 cleanup();
224
225 va_start(ap, fmt);
226 vfprintf(stderr, fmt, ap);
227 va_end(ap);
228
229 if (saved_errno)
230 fprintf(stderr, ": %s", strerror(saved_errno));
231 putc('\n', stderr);
232 fflush(stderr);
233
234 _exit(1);
235 }
236
237 static void *
238 erealloc(void *ptr, size_t size)
239 {
240 void *p;
241
242 if (!(p = realloc(ptr, size)))
243 die("realloc");
244 return p;
245 }
246
247 static void *
248 ecalloc(size_t nmemb, size_t size)
249 {
250 void *p;
251
252 if (!(p = calloc(nmemb, size)))
253 die("calloc");
254 return p;
255 }
256
257 static char *
258 estrdup(const char *s)
259 {
260 char *p;
261
262 if (!(p = strdup(s)))
263 die("strdup");
264 return p;
265 }
266
267 /* Wrapper for tparm() which allows NULL parameter for str. */
268 static char *
269 tparmnull(const char *str, long p1, long p2, long p3, long p4, long p5, long p6,
270 long p7, long p8, long p9)
271 {
272 if (!str)
273 return NULL;
274 /* some tparm() implementations have char *, some have const char * */
275 return tparm((char *)str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
276 }
277
278 /* Format `len` columns of characters. If string is shorter pad the rest
279 * with characters `pad`. */
280 static int
281 utf8pad(char *buf, size_t bufsiz, const char *s, size_t len, int pad)
282 {
283 wchar_t wc;
284 size_t col = 0, i, slen, siz = 0;
285 int inc, rl, w;
286
287 if (!bufsiz)
288 return -1;
289 if (!len) {
290 buf[0] = '\0';
291 return 0;
292 }
293
294 slen = strlen(s);
295 for (i = 0; i < slen; i += inc) {
296 inc = 1; /* next byte */
297 if ((unsigned char)s[i] < 32)
298 continue;
299
300 rl = mbtowc(&wc, &s[i], slen - i < 4 ? slen - i : 4);
301 inc = rl;
302 if (rl < 0) {
303 mbtowc(NULL, NULL, 0); /* reset state */
304 inc = 1; /* invalid, seek next byte */
305 w = 1; /* replacement char is one width */
306 } else if ((w = wcwidth(wc)) == -1) {
307 continue;
308 }
309
310 if (col + w > len || (col + w == len && s[i + inc])) {
311 if (siz + 4 >= bufsiz)
312 return -1;
313 memcpy(&buf[siz], PAD_TRUNCATE_SYMBOL, sizeof(PAD_TRUNCATE_SYMBOL) - 1);
314 siz += sizeof(PAD_TRUNCATE_SYMBOL) - 1;
315 buf[siz] = '\0';
316 col++;
317 break;
318 } else if (rl < 0) {
319 if (siz + 4 >= bufsiz)
320 return -1;
321 memcpy(&buf[siz], UTF_INVALID_SYMBOL, sizeof(UTF_INVALID_SYMBOL) - 1);
322 siz += sizeof(UTF_INVALID_SYMBOL) - 1;
323 buf[siz] = '\0';
324 col++;
325 continue;
326 }
327 if (siz + inc + 1 >= bufsiz)
328 return -1;
329 memcpy(&buf[siz], &s[i], inc);
330 siz += inc;
331 buf[siz] = '\0';
332 col += w;
333 }
334
335 len -= col;
336 if (siz + len + 1 >= bufsiz)
337 return -1;
338 memset(&buf[siz], pad, len);
339 siz += len;
340 buf[siz] = '\0';
341
342 return 0;
343 }
344
345 static void
346 resetstate(void)
347 {
348 ttywrite("\x1b""c"); /* rs1: reset title and state */
349 }
350
351 static void
352 updatetitle(void)
353 {
354 unsigned long totalnew = 0, total = 0;
355 size_t i;
356
357 for (i = 0; i < nfeeds; i++) {
358 totalnew += feeds[i].totalnew;
359 total += feeds[i].total;
360 }
361 ttywritef("\x1b]2;(%lu/%lu) - sfeed_curses\x1b\\", totalnew, total);
362 }
363
364 static void
365 appmode(int on)
366 {
367 ttywrite(tparmnull(on ? enter_ca_mode : exit_ca_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
368 }
369
370 static void
371 mousemode(int on)
372 {
373 ttywrite(on ? "\x1b[?1000h" : "\x1b[?1000l"); /* xterm X10 mouse mode */
374 ttywrite(on ? "\x1b[?1006h" : "\x1b[?1006l"); /* extended SGR mouse mode */
375 }
376
377 static void
378 cursormode(int on)
379 {
380 ttywrite(tparmnull(on ? cursor_normal : cursor_invisible, 0, 0, 0, 0, 0, 0, 0, 0, 0));
381 }
382
383 static void
384 cursormove(int x, int y)
385 {
386 ttywrite(tparmnull(cursor_address, y, x, 0, 0, 0, 0, 0, 0, 0));
387 }
388
389 static void
390 cursorsave(void)
391 {
392 /* do not save the cursor if it won't be restored anyway */
393 if (cursor_invisible)
394 ttywrite(tparmnull(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
395 }
396
397 static void
398 cursorrestore(void)
399 {
400 /* if the cursor cannot be hidden then move to a consistent position */
401 if (cursor_invisible)
402 ttywrite(tparmnull(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
403 else
404 cursormove(0, 0);
405 }
406
407 static void
408 attrmode(int mode)
409 {
410 switch (mode) {
411 case ATTR_RESET:
412 ttywrite(tparmnull(exit_attribute_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
413 break;
414 case ATTR_BOLD_ON:
415 ttywrite(tparmnull(enter_bold_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
416 break;
417 case ATTR_FAINT_ON:
418 ttywrite(tparmnull(enter_dim_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
419 break;
420 case ATTR_REVERSE_ON:
421 ttywrite(tparmnull(enter_reverse_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
422 break;
423 default:
424 break;
425 }
426 }
427
428 static void
429 cleareol(void)
430 {
431 ttywrite(tparmnull(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
432 }
433
434 static void
435 clearscreen(void)
436 {
437 ttywrite(tparmnull(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
438 }
439
440 static void
441 cleanup(void)
442 {
443 struct sigaction sa;
444
445 if (!needcleanup)
446 return;
447 needcleanup = 0;
448
449 if (istermsetup) {
450 resetstate();
451 cursormode(1);
452 appmode(0);
453 clearscreen();
454
455 if (usemouse)
456 mousemode(0);
457 }
458
459 /* restore terminal settings */
460 tcsetattr(0, TCSANOW, &tsave);
461
462 memset(&sa, 0, sizeof(sa));
463 sigemptyset(&sa.sa_mask);
464 sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
465 sa.sa_handler = SIG_DFL;
466 sigaction(SIGWINCH, &sa, NULL);
467 }
468
469 static void
470 win_update(struct win *w, int width, int height)
471 {
472 if (width != w->width || height != w->height)
473 w->dirty = 1;
474 w->width = width;
475 w->height = height;
476 }
477
478 static void
479 resizewin(void)
480 {
481 struct winsize winsz;
482 int width, height;
483
484 if (ioctl(1, TIOCGWINSZ, &winsz) != -1) {
485 width = winsz.ws_col > 0 ? winsz.ws_col : 80;
486 height = winsz.ws_row > 0 ? winsz.ws_row : 24;
487 win_update(&win, width, height);
488 }
489 if (win.dirty)
490 alldirty();
491 }
492
493 static void
494 init(void)
495 {
496 struct sigaction sa;
497 int errret = 1;
498
499 needcleanup = 1;
500
501 tcgetattr(0, &tsave);
502 memcpy(&tcur, &tsave, sizeof(tcur));
503 tcur.c_lflag &= ~(ECHO|ICANON);
504 tcur.c_cc[VMIN] = 1;
505 tcur.c_cc[VTIME] = 0;
506 tcsetattr(0, TCSANOW, &tcur);
507
508 if (!istermsetup &&
509 (setupterm(NULL, 1, &errret) != OK || errret != 1)) {
510 errno = 0;
511 die("setupterm: terminfo database or entry for $TERM not found");
512 }
513 istermsetup = 1;
514 resizewin();
515
516 appmode(1);
517 cursormode(0);
518
519 if (usemouse)
520 mousemode(1);
521
522 memset(&sa, 0, sizeof(sa));
523 sigemptyset(&sa.sa_mask);
524 sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
525 sa.sa_handler = sighandler;
526 sigaction(SIGCHLD, &sa, NULL);
527 sigaction(SIGHUP, &sa, NULL);
528 sigaction(SIGINT, &sa, NULL);
529 sigaction(SIGTERM, &sa, NULL);
530 sigaction(SIGWINCH, &sa, NULL);
531 }
532
533 static void
534 processexit(pid_t pid, int interactive)
535 {
536 struct sigaction sa;
537
538 if (interactive) {
539 memset(&sa, 0, sizeof(sa));
540 sigemptyset(&sa.sa_mask);
541 sa.sa_flags = SA_RESTART; /* require BSD signal semantics */
542
543 /* ignore SIGINT (^C) in parent for interactive applications */
544 sa.sa_handler = SIG_IGN;
545 sigaction(SIGINT, &sa, NULL);
546
547 sa.sa_flags = 0; /* SIGTERM: interrupt waitpid(), no SA_RESTART */
548 sa.sa_handler = sighandler;
549 sigaction(SIGTERM, &sa, NULL);
550
551 /* wait for process to change state, ignore errors */
552 waitpid(pid, NULL, 0);
553
554 init();
555 updatesidebar();
556 updategeom();
557 updatetitle();
558 }
559 }
560
561 /* Pipe item line or item field to a program.
562 * If `field` is -1 then pipe the TSV line, else a specified field.
563 * if `interactive` is 1 then cleanup and restore the tty and wait on the
564 * process.
565 * if 0 then don't do that and also write stdout and stderr to /dev/null. */
566 static void
567 pipeitem(const char *cmd, struct item *item, int field, int interactive)
568 {
569 FILE *fp;
570 pid_t pid;
571 int i, status;
572
573 if (interactive)
574 cleanup();
575
576 switch ((pid = fork())) {
577 case -1:
578 die("fork");
579 case 0:
580 if (!interactive) {
581 dup2(devnullfd, 1); /* stdout */
582 dup2(devnullfd, 2); /* stderr */
583 }
584
585 errno = 0;
586 if (!(fp = popen(cmd, "w")))
587 die("popen: %s", cmd);
588 if (field == -1) {
589 for (i = 0; i < FieldLast; i++) {
590 if (i)
591 putc('\t', fp);
592 fputs(item->fields[i], fp);
593 }
594 } else {
595 fputs(item->fields[field], fp);
596 }
597 putc('\n', fp);
598 status = pclose(fp);
599 status = WIFEXITED(status) ? WEXITSTATUS(status) : 127;
600 _exit(status);
601 default:
602 processexit(pid, interactive);
603 }
604 }
605
606 static void
607 forkexec(char *argv[], int interactive)
608 {
609 pid_t pid;
610
611 if (interactive)
612 cleanup();
613
614 switch ((pid = fork())) {
615 case -1:
616 die("fork");
617 case 0:
618 if (!interactive) {
619 dup2(devnullfd, 0); /* stdin */
620 dup2(devnullfd, 1); /* stdout */
621 dup2(devnullfd, 2); /* stderr */
622 }
623 if (execvp(argv[0], argv) == -1)
624 _exit(1);
625 default:
626 processexit(pid, interactive);
627 }
628 }
629
630 static struct row *
631 pane_row_get(struct pane *p, off_t pos)
632 {
633 if (pos < 0 || pos >= p->nrows)
634 return NULL;
635
636 if (p->row_get)
637 return p->row_get(p, pos);
638 return p->rows + pos;
639 }
640
641 static char *
642 pane_row_text(struct pane *p, struct row *row)
643 {
644 /* custom formatter */
645 if (p->row_format)
646 return p->row_format(p, row);
647 return row->text;
648 }
649
650 static int
651 pane_row_match(struct pane *p, struct row *row, const char *s)
652 {
653 if (p->row_match)
654 return p->row_match(p, row, s);
655 return (strcasestr(pane_row_text(p, row), s) != NULL);
656 }
657
658 static void
659 pane_row_draw(struct pane *p, off_t pos, int selected)
660 {
661 struct row *row;
662
663 if (p->hidden || !p->width || !p->height ||
664 p->x >= win.width || p->y + (pos % p->height) >= win.height)
665 return;
666
667 row = pane_row_get(p, pos);
668
669 cursorsave();
670 cursormove(p->x, p->y + (pos % p->height));
671
672 if (p->focused)
673 THEME_ITEM_FOCUS();
674 else
675 THEME_ITEM_NORMAL();
676 if (row && row->bold)
677 THEME_ITEM_BOLD();
678 if (selected)
679 THEME_ITEM_SELECTED();
680 if (row) {
681 printutf8pad(stdout, pane_row_text(p, row), p->width, ' ');
682 fflush(stdout);
683 } else {
684 ttywritef("%-*.*s", p->width, p->width, "");
685 }
686
687 attrmode(ATTR_RESET);
688 cursorrestore();
689 }
690
691 static void
692 pane_setpos(struct pane *p, off_t pos)
693 {
694 if (pos < 0)
695 pos = 0; /* clamp */
696 if (!p->nrows)
697 return; /* invalid */
698 if (pos >= p->nrows)
699 pos = p->nrows - 1; /* clamp */
700 if (pos == p->pos)
701 return; /* no change */
702
703 /* is on different scroll region? mark whole pane dirty */
704 if (((p->pos - (p->pos % p->height)) / p->height) !=
705 ((pos - (pos % p->height)) / p->height)) {
706 p->dirty = 1;
707 } else {
708 /* only redraw the 2 dirty rows */
709 pane_row_draw(p, p->pos, 0);
710 pane_row_draw(p, pos, 1);
711 }
712 p->pos = pos;
713 }
714
715 static void
716 pane_scrollpage(struct pane *p, int pages)
717 {
718 off_t pos;
719
720 if (pages < 0) {
721 pos = p->pos - (-pages * p->height);
722 pos -= (p->pos % p->height);
723 pos += p->height - 1;
724 pane_setpos(p, pos);
725 } else if (pages > 0) {
726 pos = p->pos + (pages * p->height);
727 if ((p->pos % p->height))
728 pos -= (p->pos % p->height);
729 pane_setpos(p, pos);
730 }
731 }
732
733 static void
734 pane_scrolln(struct pane *p, int n)
735 {
736 pane_setpos(p, p->pos + n);
737 }
738
739 static void
740 pane_setfocus(struct pane *p, int on)
741 {
742 if (p->focused != on) {
743 p->focused = on;
744 p->dirty = 1;
745 }
746 }
747
748 static void
749 pane_draw(struct pane *p)
750 {
751 off_t pos, y;
752
753 if (!p->dirty)
754 return;
755 p->dirty = 0;
756 if (p->hidden || !p->width || !p->height)
757 return;
758
759 /* draw visible rows */
760 pos = p->pos - (p->pos % p->height);
761 for (y = 0; y < p->height; y++)
762 pane_row_draw(p, y + pos, (y + pos) == p->pos);
763 }
764
765 static void
766 setlayout(int n)
767 {
768 if (layout != LayoutMonocle)
769 prevlayout = layout; /* previous non-monocle layout */
770 layout = n;
771 }
772
773 static void
774 updategeom(void)
775 {
776 int h, w, x = 0, y = 0;
777
778 panes[PaneFeeds].hidden = layout == LayoutMonocle && (selpane != PaneFeeds);
779 panes[PaneItems].hidden = layout == LayoutMonocle && (selpane != PaneItems);
780 linebar.hidden = layout != LayoutHorizontal;
781
782 w = win.width;
783 /* always reserve space for statusbar */
784 h = MAX(win.height - 1, 1);
785
786 panes[PaneFeeds].x = x;
787 panes[PaneFeeds].y = y;
788
789 switch (layout) {
790 case LayoutVertical:
791 panes[PaneFeeds].width = getsidebarsize();
792
793 x += panes[PaneFeeds].width;
794 w -= panes[PaneFeeds].width;
795
796 /* space for scrollbar if sidebar is visible */
797 w--;
798 x++;
799
800 panes[PaneFeeds].height = MAX(h, 1);
801 break;
802 case LayoutHorizontal:
803 panes[PaneFeeds].height = getsidebarsize();
804
805 h -= panes[PaneFeeds].height;
806 y += panes[PaneFeeds].height;
807
808 linebar.x = 0;
809 linebar.y = y;
810 linebar.width = win.width;
811
812 h--;
813 y++;
814
815 panes[PaneFeeds].width = MAX(w - 1, 0);
816 break;
817 case LayoutMonocle:
818 panes[PaneFeeds].height = MAX(h, 1);
819 panes[PaneFeeds].width = MAX(w - 1, 0);
820 break;
821 }
822
823 panes[PaneItems].x = x;
824 panes[PaneItems].y = y;
825 panes[PaneItems].width = MAX(w - 1, 0);
826 panes[PaneItems].height = MAX(h, 1);
827 if (x >= win.width || y + 1 >= win.height)
828 panes[PaneItems].hidden = 1;
829
830 scrollbars[PaneFeeds].x = panes[PaneFeeds].x + panes[PaneFeeds].width;
831 scrollbars[PaneFeeds].y = panes[PaneFeeds].y;
832 scrollbars[PaneFeeds].size = panes[PaneFeeds].height;
833 scrollbars[PaneFeeds].hidden = panes[PaneFeeds].hidden;
834
835 scrollbars[PaneItems].x = panes[PaneItems].x + panes[PaneItems].width;
836 scrollbars[PaneItems].y = panes[PaneItems].y;
837 scrollbars[PaneItems].size = panes[PaneItems].height;
838 scrollbars[PaneItems].hidden = panes[PaneItems].hidden;
839
840 statusbar.width = win.width;
841 statusbar.x = 0;
842 statusbar.y = MAX(win.height - 1, 0);
843
844 alldirty();
845 }
846
847 static void
848 scrollbar_setfocus(struct scrollbar *s, int on)
849 {
850 if (s->focused != on) {
851 s->focused = on;
852 s->dirty = 1;
853 }
854 }
855
856 static void
857 scrollbar_update(struct scrollbar *s, off_t pos, off_t nrows, int pageheight)
858 {
859 int tickpos = 0, ticksize = 0;
860
861 /* do not show a scrollbar if all items fit on the page */
862 if (nrows > pageheight) {
863 ticksize = s->size / ((double)nrows / (double)pageheight);
864 if (ticksize == 0)
865 ticksize = 1;
866
867 tickpos = (pos / (double)nrows) * (double)s->size;
868
869 /* fixup due to cell precision */
870 if (pos + pageheight >= nrows ||
871 tickpos + ticksize >= s->size)
872 tickpos = s->size - ticksize;
873 }
874
875 if (s->tickpos != tickpos || s->ticksize != ticksize)
876 s->dirty = 1;
877 s->tickpos = tickpos;
878 s->ticksize = ticksize;
879 }
880
881 static void
882 scrollbar_draw(struct scrollbar *s)
883 {
884 off_t y;
885
886 if (!s->dirty)
887 return;
888 s->dirty = 0;
889 if (s->hidden || !s->size || s->x >= win.width || s->y >= win.height)
890 return;
891
892 cursorsave();
893
894 /* draw bar (not tick) */
895 if (s->focused)
896 THEME_SCROLLBAR_FOCUS();
897 else
898 THEME_SCROLLBAR_NORMAL();
899 for (y = 0; y < s->size; y++) {
900 if (y >= s->tickpos && y < s->tickpos + s->ticksize)
901 continue; /* skip tick */
902 cursormove(s->x, s->y + y);
903 ttywrite(SCROLLBAR_SYMBOL_BAR);
904 }
905
906 /* draw tick */
907 if (s->focused)
908 THEME_SCROLLBAR_TICK_FOCUS();
909 else
910 THEME_SCROLLBAR_TICK_NORMAL();
911 for (y = s->tickpos; y < s->size && y < s->tickpos + s->ticksize; y++) {
912 cursormove(s->x, s->y + y);
913 ttywrite(SCROLLBAR_SYMBOL_TICK);
914 }
915
916 attrmode(ATTR_RESET);
917 cursorrestore();
918 }
919
920 static int
921 readch(void)
922 {
923 unsigned char b;
924 fd_set readfds;
925 struct timeval tv;
926
927 if (cmdenv && *cmdenv) {
928 b = *(cmdenv++); /* $SFEED_AUTOCMD */
929 return (int)b;
930 }
931
932 for (;;) {
933 FD_ZERO(&readfds);
934 FD_SET(0, &readfds);
935 tv.tv_sec = 0;
936 tv.tv_usec = 250000; /* 250ms */
937 switch (select(1, &readfds, NULL, NULL, &tv)) {
938 case -1:
939 if (errno != EINTR)
940 die("select");
941 return -2; /* EINTR: like a signal */
942 case 0:
943 return -3; /* time-out */
944 }
945
946 switch (read(0, &b, 1)) {
947 case -1: die("read");
948 case 0: return EOF;
949 default: return (int)b;
950 }
951 }
952 }
953
954 static char *
955 lineeditor(void)
956 {
957 char *input = NULL;
958 size_t cap = 0, nchars = 0;
959 int ch;
960
961 if (usemouse)
962 mousemode(0);
963 for (;;) {
964 if (nchars + 2 >= cap) {
965 cap = cap ? cap * 2 : 32;
966 input = erealloc(input, cap);
967 }
968
969 ch = readch();
970 if (ch == EOF || ch == '\r' || ch == '\n') {
971 input[nchars] = '\0';
972 break;
973 } else if (ch == '\b' || ch == 0x7f) {
974 if (!nchars)
975 continue;
976 input[--nchars] = '\0';
977 ttywrite("\b \b"); /* back, blank, back */
978 } else if (ch >= ' ') {
979 input[nchars] = ch;
980 input[nchars + 1] = '\0';
981 ttywrite(&input[nchars]);
982 nchars++;
983 } else if (ch < 0) {
984 if (state_sigchld) {
985 state_sigchld = 0;
986 /* wait on child processes so they don't become a zombie */
987 while (waitpid((pid_t)-1, NULL, WNOHANG) > 0)
988 ;
989 }
990 if (state_sigint)
991 state_sigint = 0; /* cancel prompt and don't handle this signal */
992 else if (state_sighup || state_sigterm)
993 ; /* cancel prompt and handle these signals */
994 else /* no signal, time-out or SIGCHLD or SIGWINCH */
995 continue; /* do not cancel: process signal later */
996
997 free(input);
998 input = NULL;
999 break; /* cancel prompt */
1000 }
1001 }
1002 if (usemouse)
1003 mousemode(1);
1004 return input;
1005 }
1006
1007 static char *
1008 uiprompt(int x, int y, char *fmt, ...)
1009 {
1010 va_list ap;
1011 char *input, buf[32];
1012
1013 va_start(ap, fmt);
1014 vsnprintf(buf, sizeof(buf), fmt, ap);
1015 va_end(ap);
1016
1017 cursorsave();
1018 cursormove(x, y);
1019 THEME_INPUT_LABEL();
1020 ttywrite(buf);
1021 attrmode(ATTR_RESET);
1022
1023 THEME_INPUT_NORMAL();
1024 cleareol();
1025 cursormode(1);
1026 cursormove(x + colw(buf) + 1, y);
1027
1028 input = lineeditor();
1029 attrmode(ATTR_RESET);
1030
1031 cursormode(0);
1032 cursorrestore();
1033
1034 return input;
1035 }
1036
1037 static void
1038 linebar_draw(struct linebar *b)
1039 {
1040 int i;
1041
1042 if (!b->dirty)
1043 return;
1044 b->dirty = 0;
1045 if (b->hidden || !b->width)
1046 return;
1047
1048 cursorsave();
1049 cursormove(b->x, b->y);
1050 THEME_LINEBAR();
1051 for (i = 0; i < b->width - 1; i++)
1052 ttywrite(LINEBAR_SYMBOL_BAR);
1053 ttywrite(LINEBAR_SYMBOL_RIGHT);
1054 attrmode(ATTR_RESET);
1055 cursorrestore();
1056 }
1057
1058 static void
1059 statusbar_draw(struct statusbar *s)
1060 {
1061 if (!s->dirty)
1062 return;
1063 s->dirty = 0;
1064 if (s->hidden || !s->width || s->x >= win.width || s->y >= win.height)
1065 return;
1066
1067 cursorsave();
1068 cursormove(s->x, s->y);
1069 THEME_STATUSBAR();
1070 /* terminals without xenl (eat newline glitch) mess up scrolling when
1071 * using the last cell on the last line on the screen. */
1072 printutf8pad(stdout, s->text, s->width - (!eat_newline_glitch), ' ');
1073 fflush(stdout);
1074 attrmode(ATTR_RESET);
1075 cursorrestore();
1076 }
1077
1078 static void
1079 statusbar_update(struct statusbar *s, const char *text)
1080 {
1081 if (s->text && !strcmp(s->text, text))
1082 return;
1083
1084 free(s->text);
1085 s->text = estrdup(text);
1086 s->dirty = 1;
1087 }
1088
1089 /* Line to item, modifies and splits line in-place. */
1090 static int
1091 linetoitem(char *line, struct item *item)
1092 {
1093 char *fields[FieldLast];
1094 time_t parsedtime;
1095
1096 item->line = line;
1097 parseline(line, fields);
1098 memcpy(item->fields, fields, sizeof(fields));
1099 if (urlfile)
1100 item->matchnew = estrdup(fields[fields[FieldLink][0] ? FieldLink : FieldId]);
1101 else
1102 item->matchnew = NULL;
1103
1104 parsedtime = 0;
1105 if (!strtotime(fields[FieldUnixTimestamp], &parsedtime)) {
1106 item->timestamp = parsedtime;
1107 item->timeok = 1;
1108 } else {
1109 item->timestamp = 0;
1110 item->timeok = 0;
1111 }
1112
1113 return 0;
1114 }
1115
1116 static void
1117 feed_items_free(struct items *items)
1118 {
1119 size_t i;
1120
1121 for (i = 0; i < items->len; i++) {
1122 free(items->items[i].line);
1123 free(items->items[i].matchnew);
1124 }
1125 free(items->items);
1126 items->items = NULL;
1127 items->len = 0;
1128 items->cap = 0;
1129 }
1130
1131 static void
1132 feed_items_get(struct feed *f, FILE *fp, struct items *itemsret)
1133 {
1134 struct item *item, *items = NULL;
1135 char *line = NULL;
1136 size_t cap, i, linesize = 0, nitems;
1137 ssize_t linelen, n;
1138 off_t offset;
1139
1140 cap = nitems = 0;
1141 offset = 0;
1142 for (i = 0; ; i++) {
1143 if (i + 1 >= cap) {
1144 cap = cap ? cap * 2 : 16;
1145 items = erealloc(items, cap * sizeof(struct item));
1146 }
1147 if ((n = linelen = getline(&line, &linesize, fp)) > 0) {
1148 item = &items[i];
1149
1150 item->offset = offset;
1151 offset += linelen;
1152
1153 if (line[linelen - 1] == '\n')
1154 line[--linelen] = '\0';
1155
1156 if (lazyload && f->path) {
1157 linetoitem(line, item);
1158
1159 /* data is ignored here, will be lazy-loaded later. */
1160 item->line = NULL;
1161 memset(item->fields, 0, sizeof(item->fields));
1162 } else {
1163 linetoitem(estrdup(line), item);
1164 }
1165
1166 nitems++;
1167 }
1168 if (ferror(fp))
1169 die("getline: %s", f->name);
1170 if (n <= 0 || feof(fp))
1171 break;
1172 }
1173 itemsret->items = items;
1174 itemsret->len = nitems;
1175 itemsret->cap = cap;
1176 free(line);
1177 }
1178
1179 static void
1180 updatenewitems(struct feed *f)
1181 {
1182 struct pane *p;
1183 struct row *row;
1184 struct item *item;
1185 size_t i;
1186
1187 p = &panes[PaneItems];
1188 p->dirty = 1;
1189 f->totalnew = 0;
1190 for (i = 0; i < p->nrows; i++) {
1191 row = &(p->rows[i]); /* do not use pane_row_get() */
1192 item = row->data;
1193 if (urlfile)
1194 item->isnew = !urls_hasmatch(&urls, item->matchnew);
1195 else
1196 item->isnew = (item->timeok && item->timestamp >= comparetime);
1197 row->bold = item->isnew;
1198 f->totalnew += item->isnew;
1199 }
1200 f->total = p->nrows;
1201 }
1202
1203 static void
1204 feed_load(struct feed *f, FILE *fp)
1205 {
1206 /* static, reuse local buffers */
1207 static struct items items;
1208 struct pane *p;
1209 size_t i;
1210
1211 feed_items_free(&items);
1212 feed_items_get(f, fp, &items);
1213 p = &panes[PaneItems];
1214 p->pos = 0;
1215 p->nrows = items.len;
1216 free(p->rows);
1217 p->rows = ecalloc(sizeof(p->rows[0]), items.len + 1);
1218 for (i = 0; i < items.len; i++)
1219 p->rows[i].data = &(items.items[i]); /* do not use pane_row_get() */
1220
1221 updatenewitems(f);
1222 }
1223
1224 static void
1225 feed_count(struct feed *f, FILE *fp)
1226 {
1227 char *fields[FieldLast];
1228 char *line = NULL;
1229 size_t linesize = 0;
1230 ssize_t linelen;
1231 time_t parsedtime;
1232
1233 f->totalnew = f->total = 0;
1234 while ((linelen = getline(&line, &linesize, fp)) > 0) {
1235 if (line[linelen - 1] == '\n')
1236 line[--linelen] = '\0';
1237 parseline(line, fields);
1238
1239 if (urlfile) {
1240 f->totalnew += !urls_hasmatch(&urls, fields[fields[FieldLink][0] ? FieldLink : FieldId]);
1241 } else {
1242 parsedtime = 0;
1243 if (!strtotime(fields[FieldUnixTimestamp], &parsedtime))
1244 f->totalnew += (parsedtime >= comparetime);
1245 }
1246 f->total++;
1247 }
1248 if (ferror(fp))
1249 die("getline: %s", f->name);
1250 free(line);
1251 }
1252
1253 static void
1254 feed_setenv(struct feed *f)
1255 {
1256 if (f && f->path)
1257 setenv("SFEED_FEED_PATH", f->path, 1);
1258 else
1259 unsetenv("SFEED_FEED_PATH");
1260 }
1261
1262 /* Change feed, have one file open, reopen file if needed. */
1263 static void
1264 feeds_set(struct feed *f)
1265 {
1266 if (curfeed) {
1267 if (curfeed->path && curfeed->fp) {
1268 fclose(curfeed->fp);
1269 curfeed->fp = NULL;
1270 }
1271 }
1272
1273 if (f && f->path) {
1274 if (!f->fp && !(f->fp = fopen(f->path, "rb")))
1275 die("fopen: %s", f->path);
1276 }
1277
1278 feed_setenv(f);
1279
1280 curfeed = f;
1281 }
1282
1283 static void
1284 feeds_load(struct feed *feeds, size_t nfeeds)
1285 {
1286 struct feed *f;
1287 size_t i;
1288
1289 errno = 0;
1290 if ((comparetime = getcomparetime()) == (time_t)-1)
1291 die("getcomparetime");
1292
1293 for (i = 0; i < nfeeds; i++) {
1294 f = &feeds[i];
1295
1296 if (f->path) {
1297 if (f->fp) {
1298 if (fseek(f->fp, 0, SEEK_SET))
1299 die("fseek: %s", f->path);
1300 } else {
1301 if (!(f->fp = fopen(f->path, "rb")))
1302 die("fopen: %s", f->path);
1303 }
1304 }
1305 if (!f->fp) {
1306 /* reading from stdin, just recount new */
1307 if (f == curfeed)
1308 updatenewitems(f);
1309 continue;
1310 }
1311
1312 /* load first items, because of first selection or stdin. */
1313 if (f == curfeed) {
1314 feed_load(f, f->fp);
1315 } else {
1316 feed_count(f, f->fp);
1317 if (f->path && f->fp) {
1318 fclose(f->fp);
1319 f->fp = NULL;
1320 }
1321 }
1322 }
1323 }
1324
1325 /* find row position of the feed if visible, else return -1 */
1326 static off_t
1327 feeds_row_get(struct pane *p, struct feed *f)
1328 {
1329 struct row *row;
1330 struct feed *fr;
1331 off_t pos;
1332
1333 for (pos = 0; pos < p->nrows; pos++) {
1334 if (!(row = pane_row_get(p, pos)))
1335 continue;
1336 fr = row->data;
1337 if (!strcmp(fr->name, f->name))
1338 return pos;
1339 }
1340 return -1;
1341 }
1342
1343 static void
1344 feeds_reloadall(void)
1345 {
1346 struct pane *p;
1347 struct feed *f = NULL;
1348 struct row *row;
1349 off_t pos;
1350
1351 p = &panes[PaneFeeds];
1352 if ((row = pane_row_get(p, p->pos)))
1353 f = row->data;
1354
1355 pos = panes[PaneItems].pos; /* store numeric item position */
1356 feeds_set(curfeed); /* close and reopen feed if possible */
1357 urls_read(&urls, urlfile);
1358 feeds_load(feeds, nfeeds);
1359 urls_free(&urls);
1360 /* restore numeric item position */
1361 pane_setpos(&panes[PaneItems], pos);
1362 updatesidebar();
1363 updatetitle();
1364
1365 /* try to find the same feed in the pane */
1366 if (f && (pos = feeds_row_get(p, f)) != -1)
1367 pane_setpos(p, pos);
1368 else
1369 pane_setpos(p, 0);
1370 }
1371
1372 static void
1373 feed_open_selected(struct pane *p)
1374 {
1375 struct feed *f;
1376 struct row *row;
1377
1378 if (!(row = pane_row_get(p, p->pos)))
1379 return;
1380 f = row->data;
1381 feeds_set(f);
1382 urls_read(&urls, urlfile);
1383 if (f->fp)
1384 feed_load(f, f->fp);
1385 urls_free(&urls);
1386 /* redraw row: counts could be changed */
1387 updatesidebar();
1388 updatetitle();
1389
1390 if (layout == LayoutMonocle) {
1391 selpane = PaneItems;
1392 updategeom();
1393 }
1394 }
1395
1396 static void
1397 feed_plumb_selected_item(struct pane *p, int field)
1398 {
1399 struct row *row;
1400 struct item *item;
1401 char *cmd[3]; /* will have: { plumbercmd, arg, NULL } */
1402
1403 if (!(row = pane_row_get(p, p->pos)))
1404 return;
1405 markread(p, p->pos, p->pos, 1);
1406 item = row->data;
1407 cmd[0] = plumbercmd;
1408 cmd[1] = item->fields[field]; /* set first argument for plumber */
1409 cmd[2] = NULL;
1410 forkexec(cmd, plumberia);
1411 }
1412
1413 static void
1414 feed_pipe_selected_item(struct pane *p)
1415 {
1416 struct row *row;
1417 struct item *item;
1418
1419 if (!(row = pane_row_get(p, p->pos)))
1420 return;
1421 item = row->data;
1422 markread(p, p->pos, p->pos, 1);
1423 pipeitem(pipercmd, item, -1, piperia);
1424 }
1425
1426 static void
1427 feed_yank_selected_item(struct pane *p, int field)
1428 {
1429 struct row *row;
1430 struct item *item;
1431
1432 if (!(row = pane_row_get(p, p->pos)))
1433 return;
1434 item = row->data;
1435 pipeitem(yankercmd, item, field, yankeria);
1436 }
1437
1438 /* calculate optimal (default) size */
1439 static int
1440 getsidebarsizedefault(void)
1441 {
1442 struct feed *feed;
1443 size_t i;
1444 int len, size;
1445
1446 switch (layout) {
1447 case LayoutVertical:
1448 for (i = 0, size = 0; i < nfeeds; i++) {
1449 feed = &feeds[i];
1450 len = snprintf(NULL, 0, " (%lu/%lu)",
1451 feed->totalnew, feed->total) +
1452 colw(feed->name);
1453 if (len > size)
1454 size = len;
1455
1456 if (onlynew && feed->totalnew == 0)
1457 continue;
1458 }
1459 return MAX(MIN(win.width - 1, size), 0);
1460 case LayoutHorizontal:
1461 for (i = 0, size = 0; i < nfeeds; i++) {
1462 feed = &feeds[i];
1463 if (onlynew && feed->totalnew == 0)
1464 continue;
1465 size++;
1466 }
1467 return MAX(MIN((win.height - 1) / 2, size), 1);
1468 }
1469 return 0;
1470 }
1471
1472 static int
1473 getsidebarsize(void)
1474 {
1475 int size;
1476
1477 if ((size = fixedsidebarsizes[layout]) < 0)
1478 size = getsidebarsizedefault();
1479 return size;
1480 }
1481
1482 static void
1483 adjustsidebarsize(int n)
1484 {
1485 int size;
1486
1487 if ((size = fixedsidebarsizes[layout]) < 0)
1488 size = getsidebarsizedefault();
1489 if (n > 0) {
1490 if ((layout == LayoutVertical && size + 1 < win.width) ||
1491 (layout == LayoutHorizontal && size + 1 < win.height))
1492 size++;
1493 } else if (n < 0) {
1494 if ((layout == LayoutVertical && size > 0) ||
1495 (layout == LayoutHorizontal && size > 1))
1496 size--;
1497 }
1498
1499 if (size != fixedsidebarsizes[layout]) {
1500 fixedsidebarsizes[layout] = size;
1501 updategeom();
1502 }
1503 }
1504
1505 static void
1506 updatesidebar(void)
1507 {
1508 struct pane *p;
1509 struct row *row;
1510 struct feed *feed;
1511 size_t i, nrows;
1512 int oldvalue = 0, newvalue = 0;
1513
1514 p = &panes[PaneFeeds];
1515 if (!p->rows)
1516 p->rows = ecalloc(sizeof(p->rows[0]), nfeeds + 1);
1517
1518 switch (layout) {
1519 case LayoutVertical:
1520 oldvalue = p->width;
1521 newvalue = getsidebarsize();
1522 p->width = newvalue;
1523 break;
1524 case LayoutHorizontal:
1525 oldvalue = p->height;
1526 newvalue = getsidebarsize();
1527 p->height = newvalue;
1528 break;
1529 }
1530
1531 nrows = 0;
1532 for (i = 0; i < nfeeds; i++) {
1533 feed = &feeds[i];
1534
1535 row = &(p->rows[nrows]);
1536 row->bold = (feed->totalnew > 0);
1537 row->data = feed;
1538
1539 if (onlynew && feed->totalnew == 0)
1540 continue;
1541
1542 nrows++;
1543 }
1544 p->nrows = nrows;
1545
1546 if (oldvalue != newvalue)
1547 updategeom();
1548 else
1549 p->dirty = 1;
1550
1551 if (!p->nrows)
1552 p->pos = 0;
1553 else if (p->pos >= p->nrows)
1554 p->pos = p->nrows - 1;
1555 }
1556
1557 static void
1558 sighandler(int signo)
1559 {
1560 switch (signo) {
1561 case SIGCHLD: state_sigchld = 1; break;
1562 case SIGHUP: state_sighup = 1; break;
1563 case SIGINT: state_sigint = 1; break;
1564 case SIGTERM: state_sigterm = 1; break;
1565 case SIGWINCH: state_sigwinch = 1; break;
1566 }
1567 }
1568
1569 static void
1570 alldirty(void)
1571 {
1572 win.dirty = 1;
1573 panes[PaneFeeds].dirty = 1;
1574 panes[PaneItems].dirty = 1;
1575 scrollbars[PaneFeeds].dirty = 1;
1576 scrollbars[PaneItems].dirty = 1;
1577 linebar.dirty = 1;
1578 statusbar.dirty = 1;
1579 }
1580
1581 static void
1582 draw(void)
1583 {
1584 struct row *row;
1585 struct item *item;
1586 size_t i;
1587
1588 if (win.dirty)
1589 win.dirty = 0;
1590
1591 for (i = 0; i < LEN(panes); i++) {
1592 pane_setfocus(&panes[i], i == selpane);
1593 pane_draw(&panes[i]);
1594
1595 /* each pane has a scrollbar */
1596 scrollbar_setfocus(&scrollbars[i], i == selpane);
1597 scrollbar_update(&scrollbars[i],
1598 panes[i].pos - (panes[i].pos % panes[i].height),
1599 panes[i].nrows, panes[i].height);
1600 scrollbar_draw(&scrollbars[i]);
1601 }
1602
1603 linebar_draw(&linebar);
1604
1605 /* if item selection text changed then update the status text */
1606 if ((row = pane_row_get(&panes[PaneItems], panes[PaneItems].pos))) {
1607 item = row->data;
1608 statusbar_update(&statusbar, item->fields[FieldLink]);
1609 } else {
1610 statusbar_update(&statusbar, "");
1611 }
1612 statusbar_draw(&statusbar);
1613 }
1614
1615 static void
1616 mousereport(int button, int release, int keymask, int x, int y)
1617 {
1618 struct pane *p;
1619 size_t i;
1620 off_t pos;
1621 int changedpane, dblclick;
1622
1623 if (!usemouse || release || button == -1)
1624 return;
1625
1626 for (i = 0; i < LEN(panes); i++) {
1627 p = &panes[i];
1628 if (p->hidden || !p->width || !p->height)
1629 continue;
1630
1631 /* these button actions are done regardless of the position */
1632 switch (button) {
1633 case 7: /* side-button: backward */
1634 if (selpane == PaneFeeds)
1635 return;
1636 selpane = PaneFeeds;
1637 if (layout == LayoutMonocle)
1638 updategeom();
1639 return;
1640 case 8: /* side-button: forward */
1641 if (selpane == PaneItems)
1642 return;
1643 selpane = PaneItems;
1644 if (layout == LayoutMonocle)
1645 updategeom();
1646 return;
1647 }
1648
1649 /* check if mouse position is in pane or in its scrollbar */
1650 if (!(x >= p->x && x < p->x + p->width + (!scrollbars[i].hidden) &&
1651 y >= p->y && y < p->y + p->height))
1652 continue;
1653
1654 changedpane = (selpane != i);
1655 selpane = i;
1656 /* relative position on screen */
1657 pos = y - p->y + p->pos - (p->pos % p->height);
1658 dblclick = (pos == p->pos); /* clicking the already selected row */
1659
1660 switch (button) {
1661 case 0: /* left-click */
1662 if (!p->nrows || pos >= p->nrows)
1663 break;
1664 pane_setpos(p, pos);
1665 if (i == PaneFeeds)
1666 feed_open_selected(&panes[PaneFeeds]);
1667 else if (i == PaneItems && dblclick && !changedpane)
1668 feed_plumb_selected_item(&panes[PaneItems], FieldLink);
1669 break;
1670 case 2: /* right-click */
1671 if (!p->nrows || pos >= p->nrows)
1672 break;
1673 pane_setpos(p, pos);
1674 if (i == PaneItems)
1675 feed_pipe_selected_item(&panes[PaneItems]);
1676 break;
1677 case 3: /* scroll up */
1678 case 4: /* scroll down */
1679 pane_scrollpage(p, button == 3 ? -1 : +1);
1680 break;
1681 }
1682 return; /* do not bubble events */
1683 }
1684 }
1685
1686 /* Custom formatter for feed row. */
1687 static char *
1688 feed_row_format(struct pane *p, struct row *row)
1689 {
1690 /* static, reuse local buffers */
1691 static char *bufw, *text;
1692 static size_t bufwsize, textsize;
1693 struct feed *feed;
1694 size_t needsize;
1695 char counts[128];
1696 int len, w;
1697
1698 feed = row->data;
1699
1700 /* align counts to the right and pad the rest with spaces */
1701 len = snprintf(counts, sizeof(counts), "(%lu/%lu)",
1702 feed->totalnew, feed->total);
1703 if (len > p->width)
1704 w = p->width;
1705 else
1706 w = p->width - len;
1707
1708 needsize = (w + 1) * 4;
1709 if (needsize > bufwsize) {
1710 bufw = erealloc(bufw, needsize);
1711 bufwsize = needsize;
1712 }
1713
1714 needsize = bufwsize + sizeof(counts) + 1;
1715 if (needsize > textsize) {
1716 text = erealloc(text, needsize);
1717 textsize = needsize;
1718 }
1719
1720 if (utf8pad(bufw, bufwsize, feed->name, w, ' ') != -1)
1721 snprintf(text, textsize, "%s%s", bufw, counts);
1722 else
1723 text[0] = '\0';
1724
1725 return text;
1726 }
1727
1728 static int
1729 feed_row_match(struct pane *p, struct row *row, const char *s)
1730 {
1731 struct feed *feed;
1732
1733 feed = row->data;
1734
1735 return (strcasestr(feed->name, s) != NULL);
1736 }
1737
1738 static struct row *
1739 item_row_get(struct pane *p, off_t pos)
1740 {
1741 struct row *itemrow;
1742 struct item *item;
1743 struct feed *f;
1744 char *line = NULL;
1745 size_t linesize = 0;
1746 ssize_t linelen;
1747
1748 itemrow = p->rows + pos;
1749 item = itemrow->data;
1750
1751 f = curfeed;
1752 if (f && f->path && f->fp && !item->line) {
1753 if (fseek(f->fp, item->offset, SEEK_SET))
1754 die("fseek: %s", f->path);
1755
1756 if ((linelen = getline(&line, &linesize, f->fp)) <= 0) {
1757 if (ferror(f->fp))
1758 die("getline: %s", f->path);
1759 free(line);
1760 return NULL;
1761 }
1762
1763 if (line[linelen - 1] == '\n')
1764 line[--linelen] = '\0';
1765
1766 linetoitem(estrdup(line), item);
1767 free(line);
1768 }
1769 return itemrow;
1770 }
1771
1772 /* Custom formatter for item row. */
1773 static char *
1774 item_row_format(struct pane *p, struct row *row)
1775 {
1776 /* static, reuse local buffers */
1777 static char *text;
1778 static size_t textsize;
1779 struct item *item;
1780 struct tm tm;
1781 size_t needsize;
1782
1783 item = row->data;
1784
1785 needsize = strlen(item->fields[FieldTitle]) + 21;
1786 if (needsize > textsize) {
1787 text = erealloc(text, needsize);
1788 textsize = needsize;
1789 }
1790
1791 if (item->timeok && localtime_r(&(item->timestamp), &tm)) {
1792 snprintf(text, textsize, "%c %04d-%02d-%02d %02d:%02d %s",
1793 item->fields[FieldEnclosure][0] ? '@' : ' ',
1794 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
1795 tm.tm_hour, tm.tm_min, item->fields[FieldTitle]);
1796 } else {
1797 snprintf(text, textsize, "%c %s",
1798 item->fields[FieldEnclosure][0] ? '@' : ' ',
1799 item->fields[FieldTitle]);
1800 }
1801
1802 return text;
1803 }
1804
1805 static void
1806 markread(struct pane *p, off_t from, off_t to, int isread)
1807 {
1808 struct row *row;
1809 struct item *item;
1810 FILE *fp;
1811 off_t i;
1812 const char *cmd;
1813 int isnew = !isread, pid, status = -1, visstart;
1814
1815 if (!urlfile || !p->nrows)
1816 return;
1817
1818 cmd = isread ? markreadcmd : markunreadcmd;
1819
1820 switch ((pid = fork())) {
1821 case -1:
1822 die("fork");
1823 case 0:
1824 dup2(devnullfd, 1); /* stdout */
1825 dup2(devnullfd, 2); /* stderr */
1826
1827 errno = 0;
1828 if (!(fp = popen(cmd, "w")))
1829 die("popen: %s", cmd);
1830
1831 for (i = from; i <= to && i < p->nrows; i++) {
1832 /* do not use pane_row_get(): no need for lazyload */
1833 row = &(p->rows[i]);
1834 item = row->data;
1835 if (item->isnew != isnew) {
1836 fputs(item->matchnew, fp);
1837 putc('\n', fp);
1838 }
1839 }
1840 status = pclose(fp);
1841 status = WIFEXITED(status) ? WEXITSTATUS(status) : 127;
1842 _exit(status);
1843 default:
1844 /* waitpid() and block on process status change,
1845 * fail if the exit status code was unavailable or non-zero */
1846 if (waitpid(pid, &status, 0) <= 0 || status)
1847 break;
1848
1849 visstart = p->pos - (p->pos % p->height); /* visible start */
1850 for (i = from; i <= to && i < p->nrows; i++) {
1851 row = &(p->rows[i]);
1852 item = row->data;
1853 if (item->isnew == isnew)
1854 continue;
1855
1856 row->bold = item->isnew = isnew;
1857 curfeed->totalnew += isnew ? 1 : -1;
1858
1859 /* draw if visible on screen */
1860 if (i >= visstart && i < visstart + p->height)
1861 pane_row_draw(p, i, i == p->pos);
1862 }
1863 updatesidebar();
1864 updatetitle();
1865 }
1866 }
1867
1868 static int
1869 urls_cmp(const void *v1, const void *v2)
1870 {
1871 return strcmp(*((char **)v1), *((char **)v2));
1872 }
1873
1874 static void
1875 urls_free(struct urls *urls)
1876 {
1877 while (urls->len > 0) {
1878 urls->len--;
1879 free(urls->items[urls->len]);
1880 }
1881 free(urls->items);
1882 urls->items = NULL;
1883 urls->len = 0;
1884 urls->cap = 0;
1885 }
1886
1887 static int
1888 urls_hasmatch(struct urls *urls, const char *url)
1889 {
1890 return (urls->len &&
1891 bsearch(&url, urls->items, urls->len, sizeof(char *), urls_cmp));
1892 }
1893
1894 static void
1895 urls_read(struct urls *urls, const char *urlfile)
1896 {
1897 FILE *fp;
1898 char *line = NULL;
1899 size_t linesiz = 0;
1900 ssize_t n;
1901
1902 urls_free(urls);
1903
1904 if (!urlfile)
1905 return;
1906 if (!(fp = fopen(urlfile, "rb")))
1907 die("fopen: %s", urlfile);
1908
1909 while ((n = getline(&line, &linesiz, fp)) > 0) {
1910 if (line[n - 1] == '\n')
1911 line[--n] = '\0';
1912 if (urls->len + 1 >= urls->cap) {
1913 urls->cap = urls->cap ? urls->cap * 2 : 16;
1914 urls->items = erealloc(urls->items, urls->cap * sizeof(char *));
1915 }
1916 urls->items[urls->len++] = estrdup(line);
1917 }
1918 if (ferror(fp))
1919 die("getline: %s", urlfile);
1920 fclose(fp);
1921 free(line);
1922
1923 if (urls->len > 0)
1924 qsort(urls->items, urls->len, sizeof(char *), urls_cmp);
1925 }
1926
1927 int
1928 main(int argc, char *argv[])
1929 {
1930 struct pane *p;
1931 struct feed *f;
1932 struct row *row;
1933 char *name, *tmp;
1934 char *search = NULL; /* search text */
1935 int button, ch, fd, i, keymask, release, x, y;
1936 off_t pos;
1937
1938 #ifdef __OpenBSD__
1939 if (pledge("stdio rpath tty proc exec", NULL) == -1)
1940 die("pledge");
1941 #endif
1942
1943 setlocale(LC_CTYPE, "");
1944
1945 if ((tmp = getenv("SFEED_PLUMBER")))
1946 plumbercmd = tmp;
1947 if ((tmp = getenv("SFEED_PIPER")))
1948 pipercmd = tmp;
1949 if ((tmp = getenv("SFEED_YANKER")))
1950 yankercmd = tmp;
1951 if ((tmp = getenv("SFEED_PLUMBER_INTERACTIVE")))
1952 plumberia = !strcmp(tmp, "1");
1953 if ((tmp = getenv("SFEED_PIPER_INTERACTIVE")))
1954 piperia = !strcmp(tmp, "1");
1955 if ((tmp = getenv("SFEED_YANKER_INTERACTIVE")))
1956 yankeria = !strcmp(tmp, "1");
1957 if ((tmp = getenv("SFEED_MARK_READ")))
1958 markreadcmd = tmp;
1959 if ((tmp = getenv("SFEED_MARK_UNREAD")))
1960 markunreadcmd = tmp;
1961 if ((tmp = getenv("SFEED_LAZYLOAD")))
1962 lazyload = !strcmp(tmp, "1");
1963 urlfile = getenv("SFEED_URL_FILE"); /* can be NULL */
1964 cmdenv = getenv("SFEED_AUTOCMD"); /* can be NULL */
1965
1966 setlayout(argc <= 1 ? LayoutMonocle : LayoutVertical);
1967 selpane = layout == LayoutMonocle ? PaneItems : PaneFeeds;
1968
1969 panes[PaneFeeds].row_format = feed_row_format;
1970 panes[PaneFeeds].row_match = feed_row_match;
1971 panes[PaneItems].row_format = item_row_format;
1972 if (lazyload)
1973 panes[PaneItems].row_get = item_row_get;
1974
1975 feeds = ecalloc(argc <= 1 ? 1 : argc, sizeof(struct feed));
1976 if (argc <= 1) {
1977 nfeeds = 1;
1978 f = &feeds[0];
1979 f->name = "stdin";
1980 if (!(f->fp = fdopen(0, "rb")))
1981 die("fdopen");
1982 } else {
1983 for (i = 1; i < argc; i++) {
1984 f = &feeds[i - 1];
1985 f->path = argv[i];
1986 name = ((name = strrchr(argv[i], '/'))) ? name + 1 : argv[i];
1987 f->name = name;
1988 }
1989 nfeeds = argc - 1;
1990 }
1991 feeds_set(&feeds[0]);
1992 urls_read(&urls, urlfile);
1993 feeds_load(feeds, nfeeds);
1994 urls_free(&urls);
1995
1996 if (!isatty(0)) {
1997 if ((fd = open("/dev/tty", O_RDONLY)) == -1)
1998 die("open: /dev/tty");
1999 if (dup2(fd, 0) == -1)
2000 die("dup2(%d, 0): /dev/tty -> stdin", fd);
2001 close(fd);
2002 }
2003 if (argc <= 1)
2004 feeds[0].fp = NULL;
2005
2006 if ((devnullfd = open("/dev/null", O_WRONLY)) == -1)
2007 die("open: /dev/null");
2008
2009 init();
2010 updatesidebar();
2011 updategeom();
2012 updatetitle();
2013 draw();
2014
2015 while (1) {
2016 if ((ch = readch()) < 0)
2017 goto event;
2018 switch (ch) {
2019 case '\x1b':
2020 if ((ch = readch()) < 0)
2021 goto event;
2022 if (ch != '[' && ch != 'O')
2023 continue; /* unhandled */
2024 if ((ch = readch()) < 0)
2025 goto event;
2026 switch (ch) {
2027 case 'M': /* mouse: X10 encoding */
2028 if ((ch = readch()) < 0)
2029 goto event;
2030 button = ch - 32;
2031 if ((ch = readch()) < 0)
2032 goto event;
2033 x = ch - 32;
2034 if ((ch = readch()) < 0)
2035 goto event;
2036 y = ch - 32;
2037
2038 keymask = button & (4 | 8 | 16); /* shift, meta, ctrl */
2039 button &= ~keymask; /* unset key mask */
2040
2041 /* button numbers (0 - 2) encoded in lowest 2 bits
2042 * release does not indicate which button (so set to 0).
2043 * Handle extended buttons like scrollwheels
2044 * and side-buttons by each range. */
2045 release = 0;
2046 if (button == 3) {
2047 button = -1;
2048 release = 1;
2049 } else if (button >= 128) {
2050 button -= 121;
2051 } else if (button >= 64) {
2052 button -= 61;
2053 }
2054 mousereport(button, release, keymask, x - 1, y - 1);
2055 break;
2056 case '<': /* mouse: SGR encoding */
2057 for (button = 0; ; button *= 10, button += ch - '0') {
2058 if ((ch = readch()) < 0)
2059 goto event;
2060 else if (ch == ';')
2061 break;
2062 }
2063 for (x = 0; ; x *= 10, x += ch - '0') {
2064 if ((ch = readch()) < 0)
2065 goto event;
2066 else if (ch == ';')
2067 break;
2068 }
2069 for (y = 0; ; y *= 10, y += ch - '0') {
2070 if ((ch = readch()) < 0)
2071 goto event;
2072 else if (ch == 'm' || ch == 'M')
2073 break; /* release or press */
2074 }
2075 release = ch == 'm';
2076 keymask = button & (4 | 8 | 16); /* shift, meta, ctrl */
2077 button &= ~keymask; /* unset key mask */
2078
2079 if (button >= 128)
2080 button -= 121;
2081 else if (button >= 64)
2082 button -= 61;
2083
2084 mousereport(button, release, keymask, x - 1, y - 1);
2085 break;
2086 /* DEC/SUN: ESC O char, HP: ESC char or SCO: ESC [ char */
2087 case 'A': goto keyup; /* arrow up */
2088 case 'B': goto keydown; /* arrow down */
2089 case 'C': goto keyright; /* arrow right */
2090 case 'D': goto keyleft; /* arrow left */
2091 case 'F': goto endpos; /* end */
2092 case 'G': goto nextpage; /* page down */
2093 case 'H': goto startpos; /* home */
2094 case 'I': goto prevpage; /* page up */
2095 default:
2096 if (!(ch >= '0' && ch <= '9'))
2097 break;
2098 for (i = ch - '0'; ;) {
2099 if ((ch = readch()) < 0) {
2100 goto event;
2101 } else if (ch >= '0' && ch <= '9') {
2102 i = (i * 10) + (ch - '0');
2103 continue;
2104 } else if (ch == '~') { /* DEC: ESC [ num ~ */
2105 switch (i) {
2106 case 1: goto startpos; /* home */
2107 case 4: goto endpos; /* end */
2108 case 5: goto prevpage; /* page up */
2109 case 6: goto nextpage; /* page down */
2110 case 7: goto startpos; /* home: urxvt */
2111 case 8: goto endpos; /* end: urxvt */
2112 }
2113 } else if (ch == 'z') { /* SUN: ESC [ num z */
2114 switch (i) {
2115 case 214: goto startpos; /* home */
2116 case 216: goto prevpage; /* page up */
2117 case 220: goto endpos; /* end */
2118 case 222: goto nextpage; /* page down */
2119 }
2120 }
2121 break;
2122 }
2123 }
2124 break;
2125 keyup:
2126 case 'k':
2127 pane_scrolln(&panes[selpane], -1);
2128 break;
2129 keydown:
2130 case 'j':
2131 pane_scrolln(&panes[selpane], +1);
2132 break;
2133 keyleft:
2134 case 'h':
2135 if (selpane == PaneFeeds)
2136 break;
2137 selpane = PaneFeeds;
2138 if (layout == LayoutMonocle)
2139 updategeom();
2140 break;
2141 keyright:
2142 case 'l':
2143 if (selpane == PaneItems)
2144 break;
2145 selpane = PaneItems;
2146 if (layout == LayoutMonocle)
2147 updategeom();
2148 break;
2149 case 'K':
2150 p = &panes[selpane];
2151 if (!p->nrows)
2152 break;
2153 for (pos = p->pos - 1; pos >= 0; pos--) {
2154 if ((row = pane_row_get(p, pos)) && row->bold) {
2155 pane_setpos(p, pos);
2156 break;
2157 }
2158 }
2159 break;
2160 case 'J':
2161 p = &panes[selpane];
2162 if (!p->nrows)
2163 break;
2164 for (pos = p->pos + 1; pos < p->nrows; pos++) {
2165 if ((row = pane_row_get(p, pos)) && row->bold) {
2166 pane_setpos(p, pos);
2167 break;
2168 }
2169 }
2170 break;
2171 case '\t':
2172 selpane = selpane == PaneFeeds ? PaneItems : PaneFeeds;
2173 if (layout == LayoutMonocle)
2174 updategeom();
2175 break;
2176 startpos:
2177 case 'g':
2178 pane_setpos(&panes[selpane], 0);
2179 break;
2180 endpos:
2181 case 'G':
2182 p = &panes[selpane];
2183 if (p->nrows)
2184 pane_setpos(p, p->nrows - 1);
2185 break;
2186 prevpage:
2187 case 2: /* ^B */
2188 pane_scrollpage(&panes[selpane], -1);
2189 break;
2190 nextpage:
2191 case ' ':
2192 case 6: /* ^F */
2193 pane_scrollpage(&panes[selpane], +1);
2194 break;
2195 case '[':
2196 case ']':
2197 pane_scrolln(&panes[PaneFeeds], ch == '[' ? -1 : +1);
2198 feed_open_selected(&panes[PaneFeeds]);
2199 break;
2200 case '/': /* new search (forward) */
2201 case '?': /* new search (backward) */
2202 case 'n': /* search again (forward) */
2203 case 'N': /* search again (backward) */
2204 p = &panes[selpane];
2205
2206 /* prompt for new input */
2207 if (ch == '?' || ch == '/') {
2208 tmp = ch == '?' ? "backward" : "forward";
2209 free(search);
2210 search = uiprompt(statusbar.x, statusbar.y,
2211 "Search (%s):", tmp);
2212 statusbar.dirty = 1;
2213 }
2214 if (!search || !p->nrows)
2215 break;
2216
2217 if (ch == '/' || ch == 'n') {
2218 /* forward */
2219 for (pos = p->pos + 1; pos < p->nrows; pos++) {
2220 if (pane_row_match(p, pane_row_get(p, pos), search)) {
2221 pane_setpos(p, pos);
2222 break;
2223 }
2224 }
2225 } else {
2226 /* backward */
2227 for (pos = p->pos - 1; pos >= 0; pos--) {
2228 if (pane_row_match(p, pane_row_get(p, pos), search)) {
2229 pane_setpos(p, pos);
2230 break;
2231 }
2232 }
2233 }
2234 break;
2235 case 12: /* ^L, redraw */
2236 alldirty();
2237 break;
2238 case 'R': /* reload all files */
2239 feeds_reloadall();
2240 break;
2241 case 'a': /* attachment */
2242 case 'e': /* enclosure */
2243 case '@':
2244 if (selpane == PaneItems)
2245 feed_plumb_selected_item(&panes[selpane], FieldEnclosure);
2246 break;
2247 case 'm': /* toggle mouse mode */
2248 usemouse = !usemouse;
2249 mousemode(usemouse);
2250 break;
2251 case '<': /* decrease fixed sidebar width */
2252 case '>': /* increase fixed sidebar width */
2253 adjustsidebarsize(ch == '<' ? -1 : +1);
2254 break;
2255 case '=': /* reset fixed sidebar to automatic size */
2256 fixedsidebarsizes[layout] = -1;
2257 updategeom();
2258 break;
2259 case 't': /* toggle showing only new in sidebar */
2260 p = &panes[PaneFeeds];
2261 if ((row = pane_row_get(p, p->pos)))
2262 f = row->data;
2263 else
2264 f = NULL;
2265
2266 onlynew = !onlynew;
2267 updatesidebar();
2268
2269 /* try to find the same feed in the pane */
2270 if (f && f->totalnew &&
2271 (pos = feeds_row_get(p, f)) != -1)
2272 pane_setpos(p, pos);
2273 else
2274 pane_setpos(p, 0);
2275 break;
2276 case 'o': /* feeds: load, items: plumb URL */
2277 case '\n':
2278 if (selpane == PaneFeeds && panes[selpane].nrows)
2279 feed_open_selected(&panes[selpane]);
2280 else if (selpane == PaneItems && panes[selpane].nrows)
2281 feed_plumb_selected_item(&panes[selpane], FieldLink);
2282 break;
2283 case 'c': /* items: pipe TSV line to program */
2284 case 'p':
2285 case '|':
2286 if (selpane == PaneItems)
2287 feed_pipe_selected_item(&panes[selpane]);
2288 break;
2289 case 'y': /* yank: pipe TSV field to yank URL to clipboard */
2290 case 'E': /* yank: pipe TSV field to yank enclosure to clipboard */
2291 if (selpane == PaneItems)
2292 feed_yank_selected_item(&panes[selpane],
2293 ch == 'y' ? FieldLink : FieldEnclosure);
2294 break;
2295 case 'f': /* mark all read */
2296 case 'F': /* mark all unread */
2297 if (panes[PaneItems].nrows) {
2298 p = &panes[PaneItems];
2299 markread(p, 0, p->nrows - 1, ch == 'f');
2300 }
2301 break;
2302 case 'r': /* mark item as read */
2303 case 'u': /* mark item as unread */
2304 if (selpane == PaneItems && panes[selpane].nrows) {
2305 p = &panes[selpane];
2306 markread(p, p->pos, p->pos, ch == 'r');
2307 pane_scrolln(&panes[selpane], +1);
2308 }
2309 break;
2310 case 's': /* toggle layout between monocle or non-monocle */
2311 setlayout(layout == LayoutMonocle ? prevlayout : LayoutMonocle);
2312 updategeom();
2313 break;
2314 case '1': /* vertical layout */
2315 case '2': /* horizontal layout */
2316 case '3': /* monocle layout */
2317 setlayout(ch - '1');
2318 updategeom();
2319 break;
2320 case 4: /* EOT */
2321 case 'q': goto end;
2322 }
2323 event:
2324 if (ch == EOF)
2325 goto end;
2326 else if (ch == -3 && !state_sigchld && !state_sighup &&
2327 !state_sigint && !state_sigterm && !state_sigwinch)
2328 continue; /* just a time-out, nothing to do */
2329
2330 /* handle signals in a particular order */
2331 if (state_sigchld) {
2332 state_sigchld = 0;
2333 /* wait on child processes so they don't become a zombie,
2334 * do not block the parent process if there is no status,
2335 * ignore errors */
2336 while (waitpid((pid_t)-1, NULL, WNOHANG) > 0)
2337 ;
2338 }
2339 if (state_sigterm) {
2340 cleanup();
2341 _exit(128 + SIGTERM);
2342 }
2343 if (state_sigint) {
2344 cleanup();
2345 _exit(128 + SIGINT);
2346 }
2347 if (state_sighup) {
2348 state_sighup = 0;
2349 feeds_reloadall();
2350 }
2351 if (state_sigwinch) {
2352 state_sigwinch = 0;
2353 resizewin();
2354 updategeom();
2355 }
2356
2357 draw();
2358 }
2359 end:
2360 cleanup();
2361
2362 return 0;
2363 }