|
|
sfeed_curses.c - sfeed_curses - sfeed curses UI (now part of sfeed, development is in sfeed) |
|
|
 |
git clone git://git.codemadness.org/sfeed_curses (git://git.codemadness.org) |
|
|
 |
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 horizontal" */ |
|
|
|
37 #define LINEBAR_SYMBOL_RIGHT "\xe2\x94\xa4" /* symbol: "light vertical and left" */ |
|
|
|
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_REVERSE_ON = 7 |
|
|
|
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() callback */ |
|
|
|
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 on id */ |
|
|
|
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 scrollbar */ |
|
|
|
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: $SFEED_MARK_READ */ |
|
|
|
189 static char *markunreadcmd = "sfeed_markread unread"; /* env variable: $SFEED_MARK_UNREAD */ |
|
|
|
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 ('\0') |
|
|
|
307 terminators and assign these fields as pointers. If there are less fields |
|
|
|
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 : 4); |
|
|
|
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(PAD_TRUNCATE_SYMBOL) - 1); |
|
|
|
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_INVALID_SYMBOL) - 1); |
|
|
|
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 with |
|
|
|
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 : 4); |
|
|
|
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 + inc])) { |
|
|
|
473 fputs("\xe2\x80\xa6", fp); /* ellipsis */ |
|
|
|
474 col++; |
|
|
|
475 break; |
|
|
|
476 } else if (rl < 0) { |
|
|
|
477 fputs("\xef\xbf\xbd", fp); /* replacement */ |
|
|
|
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, total); |
|
|
|
516 } |
|
|
|
517 |
|
|
|
518 void |
|
|
|
519 appmode(int on) |
|
|
|
520 { |
|
|
|
521 ttywrite(tparmnull(on ? enter_ca_mode : exit_ca_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); |
|
|
|
522 } |
|
|
|
523 |
|
|
|
524 void |
|
|
|
525 mousemode(int on) |
|
|
|
526 { |
|
|
|
527 ttywrite(on ? "\x1b[?1000h" : "\x1b[?1000l"); /* xterm X10 mouse mode */ |
|
|
|
528 ttywrite(on ? "\x1b[?1006h" : "\x1b[?1006l"); /* extended SGR mouse mode */ |
|
|
|
529 } |
|
|
|
530 |
|
|
|
531 void |
|
|
|
532 cursormode(int on) |
|
|
|
533 { |
|
|
|
534 ttywrite(tparmnull(on ? cursor_normal : cursor_invisible, 0, 0, 0, 0, 0, 0, 0, 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, 0)); |
|
|
|
549 } |
|
|
|
550 |
|
|
|
551 void |
|
|
|
552 cursorrestore(void) |
|
|
|
553 { |
|
|
|
554 /* if the cursor cannot be hidden then move to a consistent position */ |
|
|
|
555 if (cursor_invisible) |
|
|
|
556 ttywrite(tparmnull(restore_cursor, 0, 0, 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, 0, 0, 0)); |
|
|
|
567 break; |
|
|
|
568 case ATTR_BOLD_ON: |
|
|
|
569 ttywrite(tparmnull(enter_bold_mode, 0, 0, 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, 0, 0)); |
|
|
|
573 break; |
|
|
|
574 case ATTR_REVERSE_ON: |
|
|
|
575 ttywrite(tparmnull(enter_reverse_mode, 0, 0, 0, 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 found"); |
|
|
|
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 != PaneFeeds); |
|
|
|
928 panes[PaneItems].hidden = layout == LayoutMonocle && (selpane != PaneItems); |
|
|
|
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].width; |
|
|
|
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].width; |
|
|
|
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 pageheight) |
|
|
|
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.height) |
|
|
|
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; y++) { |
|
|
|
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 quit */ |
|
|
|
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.height) |
|
|
|
1203 return; |
|
|
|
1204 |
|
|
|
1205 cursorsave(); |
|
|
|
1206 cursormove(s->x, s->y); |
|
|
|
1207 THEME_STATUSBAR(); |
|
|
|
1208 /* terminals without xenl (eat newline glitch) mess up scrolling when |
|
|
|
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] ? FieldLink : FieldId]); |
|
|
|
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-loaded later. */ |
|
|
|
1298 item->line = NULL; |
|
|
|
1299 memset(item->fields, 0, sizeof(item->fields)); |
|
|
|
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 >= comparetime); |
|
|
|
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_row_get */ |
|
|
|
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[FieldLink][0] ? FieldLink : FieldId]); |
|
|
|
1380 } else { |
|
|
|
1381 parsedtime = 0; |
|
|
|
1382 if (!strtotime(fields[FieldUnixTimestamp], &parsedtime)) |
|
|
|
1383 f->totalnew += (parsedtime >= comparetime); |
|
|
|
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 }, plumberia); |
|
|
|
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].height), |
|
|
|
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 position */ |
|
|
|
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 scrollbar */ |
|
|
|
1788 if (!(x >= p->x && x < p->x + p->width + (!scrollbars[i].hidden) && |
|
|
|
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 twice */ |
|
|
|
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 && !changedpane) |
|
|
|
1806 feed_plumb_selected_item(&panes[PaneItems], FieldLink); |
|
|
|
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 start */ |
|
|
|
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) == NULL; |
|
|
|
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 + 1 : argv[i]; |
|
|
|
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); /* shift, meta, ctrl */ |
|
|
|
2176 button &= ~keymask; /* unset key mask */ |
|
|
|
2177 |
|
|
|
2178 /* button numbers (0 - 2) encoded in lowest 2 bits |
|
|
|
2179 release does not indicate which button (so set to 0). |
|
|
|
2180 Handle extended buttons like scrollwheels |
|
|
|
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 - 1, y - 1); |
|
|
|
2192 break; |
|
|
|
2193 case '<': /* mouse: SGR encoding */ |
|
|
|
2194 for (button = 0; ; button *= 10, button += ch - '0') { |
|
|
|
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 press */ |
|
|
|
2211 } |
|
|
|
2212 release = ch == 'm'; |
|
|
|
2213 keymask = button & (4 | 8 | 16); /* shift, meta, ctrl */ |
|
|
|
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 - 1, y - 1); |
|
|
|
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 : PaneFeeds; |
|
|
|
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 : +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.y, |
|
|
|
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; pos++) { |
|
|
|
2328 if (pane_row_match(p, pane_row_get(p, pos), search)) { |
|
|
|
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_get(p, pos), search)) { |
|
|
|
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], FieldEnclosure); |
|
|
|
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].nrows) |
|
|
|
2390 feed_plumb_selected_item(&panes[selpane], FieldLink); |
|
|
|
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 clipboard */ |
|
|
|
2399 case 'E': /* yank: pipe TSV field to yank enclosure to clipboard */ |
|
|
|
2400 if (selpane == PaneItems) |
|
|
|
2401 feed_yank_selected_item(&panes[selpane], |
|
|
|
2402 ch == 'y' ? FieldLink : FieldEnclosure); |
|
|
|
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-monocle */ |
|
|
|
2419 setlayout(layout == LayoutMonocle ? prevlayout : LayoutMonocle); |
|
|
|
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 } |
|