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