sfeed_curses.c 51 KB

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