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