You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

567 lines
12 KiB

  1. #include <stdarg.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <term.h>
  6. #include <termios.h>
  7. #include <unistd.h>
  8. #include <sys/types.h>
  9. #include "config.h"
  10. #include "common.h"
  11. #define C(c) #c
  12. #define S(c) C(c)
  13. static char bufout[256];
  14. static struct termios tsave;
  15. static struct termios tsacc;
  16. static Item *curentry;
  17. #if defined(__NetBSD__)
  18. #undef tparm
  19. #define tparm tiparm
  20. #endif
  21. void
  22. uisetup(void)
  23. {
  24. tcgetattr(0, &tsave);
  25. tsacc = tsave;
  26. tsacc.c_lflag &= ~(ECHO|ICANON);
  27. tcsetattr(0, TCSANOW, &tsacc);
  28. setupterm(NULL, 1, NULL);
  29. putp(tparm(clear_screen));
  30. putp(tparm(save_cursor));
  31. putp(tparm(change_scroll_region, 0, lines-2));
  32. putp(tparm(restore_cursor));
  33. fflush(stdout);
  34. }
  35. void
  36. uicleanup(void)
  37. {
  38. putp(tparm(change_scroll_region, 0, lines-1));
  39. putp(tparm(clear_screen));
  40. tcsetattr(0, TCSANOW, &tsave);
  41. fflush(stdout);
  42. }
  43. char *
  44. uiprompt(char *fmt, ...)
  45. {
  46. va_list ap;
  47. char *input = NULL;
  48. size_t n;
  49. ssize_t r;
  50. putp(tparm(save_cursor));
  51. putp(tparm(cursor_address, lines-1, 0));
  52. putp(tparm(clr_eol));
  53. putp(tparm(enter_standout_mode));
  54. va_start(ap, fmt);
  55. if (vsnprintf(bufout, sizeof(bufout), fmt, ap) >= sizeof(bufout))
  56. bufout[sizeof(bufout)-1] = '\0';
  57. va_end(ap);
  58. n = mbsprint(bufout, columns);
  59. putp(tparm(exit_standout_mode));
  60. if (n < columns)
  61. printf("%*s", columns - n, " ");
  62. putp(tparm(cursor_address, lines-1, n));
  63. tsacc.c_lflag |= (ECHO|ICANON);
  64. tcsetattr(0, TCSANOW, &tsacc);
  65. fflush(stdout);
  66. n = 0;
  67. r = getline(&input, &n, stdin);
  68. tsacc.c_lflag &= ~(ECHO|ICANON);
  69. tcsetattr(0, TCSANOW, &tsacc);
  70. putp(tparm(restore_cursor));
  71. fflush(stdout);
  72. if (r < 0) {
  73. clearerr(stdin);
  74. clear(&input);
  75. } else if (input[r - 1] == '\n') {
  76. input[--r] = '\0';
  77. }
  78. return input;
  79. }
  80. static void
  81. printitem(Item *item)
  82. {
  83. if (snprintf(bufout, sizeof(bufout), "%s %s", typedisplay(item->type),
  84. item->username) >= sizeof(bufout))
  85. bufout[sizeof(bufout)-1] = '\0';
  86. mbsprint(bufout, columns);
  87. putchar('\r');
  88. }
  89. static Item *
  90. help(Item *entry)
  91. {
  92. static Item item = {
  93. .type = '0',
  94. .raw = "Commands:\n"
  95. "Down, " S(_key_lndown) ": move one line down.\n"
  96. "Up, " S(_key_lnup) ": move one line up.\n"
  97. "PgDown, " S(_key_pgdown) ": move one page down.\n"
  98. "PgUp, " S(_key_pgup) ": move one page up.\n"
  99. "Home, " S(_key_home) ": move to top of the page.\n"
  100. "End, " S(_key_end) ": move to end of the page.\n"
  101. "Right, " S(_key_pgnext) ": view highlighted item.\n"
  102. "Left, " S(_key_pgprev) ": view previous item.\n"
  103. S(_key_search) ": search current page.\n"
  104. S(_key_search_next) ": search string forward.\n"
  105. S(_key_search_prev) ": search string backward.\n"
  106. S(_key_uri) ": print item uri.\n"
  107. S(_key_help) ": show this help.\n"
  108. "^D, " S(_key_quit) ": exit sacc.\n"
  109. };
  110. item.entry = entry;
  111. return &item;
  112. }
  113. void
  114. uistatus(char *fmt, ...)
  115. {
  116. va_list ap;
  117. size_t n;
  118. putp(tparm(save_cursor));
  119. putp(tparm(cursor_address, lines-1, 0));
  120. putp(tparm(enter_standout_mode));
  121. va_start(ap, fmt);
  122. n = vsnprintf(bufout, sizeof(bufout), fmt, ap);
  123. va_end(ap);
  124. if (n < sizeof(bufout)-1) {
  125. n += snprintf(bufout + n, sizeof(bufout) - n,
  126. " [Press a key to continue \xe2\x98\x83]");
  127. }
  128. if (n >= sizeof(bufout))
  129. bufout[sizeof(bufout)-1] = '\0';
  130. n = mbsprint(bufout, columns);
  131. putp(tparm(exit_standout_mode));
  132. if (n < columns)
  133. printf("%*s", columns - n, " ");
  134. putp(tparm(restore_cursor));
  135. fflush(stdout);
  136. getchar();
  137. }
  138. static void
  139. displaystatus(Item *item)
  140. {
  141. Dir *dir = item->dat;
  142. char *fmt;
  143. size_t n, nitems = dir ? dir->nitems : 0;
  144. unsigned long long printoff = dir ? dir->printoff : 0;
  145. putp(tparm(save_cursor));
  146. putp(tparm(cursor_address, lines-1, 0));
  147. putp(tparm(enter_standout_mode));
  148. fmt = (strcmp(item->port, "70") && strcmp(item->port, "gopher")) ?
  149. "%1$3lld%%| %2$s:%5$s/%3$c%4$s" : "%3lld%%| %s/%c%s";
  150. if (snprintf(bufout, sizeof(bufout), fmt,
  151. (printoff + lines-1 >= nitems) ? 100 :
  152. (printoff + lines-1) * 100 / nitems,
  153. item->host, item->type, item->selector, item->port)
  154. >= sizeof(bufout))
  155. bufout[sizeof(bufout)-1] = '\0';
  156. n = mbsprint(bufout, columns);
  157. putp(tparm(exit_standout_mode));
  158. if (n < columns)
  159. printf("%*s", columns - n, " ");
  160. putp(tparm(restore_cursor));
  161. fflush(stdout);
  162. }
  163. static void
  164. displayuri(Item *item)
  165. {
  166. char *fmt;
  167. size_t n;
  168. if (item->type == 0 || item->type == 'i')
  169. return;
  170. putp(tparm(save_cursor));
  171. putp(tparm(cursor_address, lines-1, 0));
  172. putp(tparm(enter_standout_mode));
  173. switch (item->type) {
  174. case '8':
  175. n = snprintf(bufout, sizeof(bufout), "telnet://%s@%s:%s",
  176. item->selector, item->host, item->port);
  177. break;
  178. case 'h':
  179. n = snprintf(bufout, sizeof(bufout), "%s: %s",
  180. item->username, item->selector);
  181. break;
  182. case 'T':
  183. n = snprintf(bufout, sizeof(bufout), "tn3270://%s@%s:%s",
  184. item->selector, item->host, item->port);
  185. break;
  186. default:
  187. fmt = strcmp(item->port, "70") ?
  188. "%1$s: gopher://%2$s:%5$s/%3$c%4$s" :
  189. "%s: gopher://%s/%c%s";
  190. n = snprintf(bufout, sizeof(bufout), fmt,
  191. item->username, item->host, item->type,
  192. item->selector, item->port);
  193. break;
  194. }
  195. if (n >= sizeof(bufout))
  196. bufout[sizeof(bufout)-1] = '\0';
  197. n = mbsprint(bufout, columns);
  198. putp(tparm(exit_standout_mode));
  199. if (n < columns)
  200. printf("%*s", columns - n, " ");
  201. putp(tparm(restore_cursor));
  202. fflush(stdout);
  203. }
  204. void
  205. uidisplay(Item *entry)
  206. {
  207. Item *items;
  208. Dir *dir;
  209. size_t i, curln, lastln, nitems, printoff;
  210. if (!entry ||
  211. !(entry->type == '1' || entry->type == '+' || entry->type == '7'))
  212. return;
  213. curentry = entry;
  214. putp(tparm(clear_screen));
  215. displaystatus(entry);
  216. if (!(dir = entry->dat))
  217. return;
  218. putp(tparm(save_cursor));
  219. items = dir->items;
  220. nitems = dir->nitems;
  221. printoff = dir->printoff;
  222. curln = dir->curline;
  223. lastln = printoff + lines-1; /* one off for status bar */
  224. for (i = printoff; i < nitems && i < lastln; ++i) {
  225. if (i != printoff)
  226. putp(tparm(cursor_down));
  227. if (i == curln) {
  228. putp(tparm(save_cursor));
  229. putp(tparm(enter_standout_mode));
  230. }
  231. printitem(&items[i]);
  232. putp(tparm(column_address, 0));
  233. if (i == curln)
  234. putp(tparm(exit_standout_mode));
  235. }
  236. putp(tparm(restore_cursor));
  237. fflush(stdout);
  238. }
  239. static void
  240. movecurline(Item *item, int l)
  241. {
  242. Dir *dir = item->dat;
  243. size_t nitems;
  244. ssize_t curline, offline;
  245. int plines = lines-2;
  246. if (dir == NULL)
  247. return;
  248. curline = dir->curline + l;
  249. nitems = dir->nitems;
  250. if (curline < 0 || curline >= nitems)
  251. return;
  252. printitem(&dir->items[dir->curline]);
  253. dir->curline = curline;
  254. if (l > 0) {
  255. offline = dir->printoff + lines-1;
  256. if (curline - dir->printoff >= plines / 2 && offline < nitems) {
  257. putp(tparm(save_cursor));
  258. putp(tparm(cursor_address, plines, 0));
  259. putp(tparm(scroll_forward));
  260. printitem(&dir->items[offline]);
  261. putp(tparm(restore_cursor));
  262. dir->printoff += l;
  263. }
  264. } else {
  265. offline = dir->printoff + l;
  266. if (curline - offline <= plines / 2 && offline >= 0) {
  267. putp(tparm(save_cursor));
  268. putp(tparm(cursor_address, 0, 0));
  269. putp(tparm(scroll_reverse));
  270. printitem(&dir->items[offline]);
  271. putchar('\n');
  272. putp(tparm(restore_cursor));
  273. dir->printoff += l;
  274. }
  275. }
  276. putp(tparm(cursor_address, curline - dir->printoff, 0));
  277. putp(tparm(enter_standout_mode));
  278. printitem(&dir->items[curline]);
  279. putp(tparm(exit_standout_mode));
  280. displaystatus(item);
  281. fflush(stdout);
  282. }
  283. static void
  284. jumptoline(Item *entry, ssize_t line, int absolute)
  285. {
  286. Dir *dir = entry->dat;
  287. size_t lastitem;
  288. int lastpagetop, plines = lines-2;
  289. if (!dir)
  290. return;
  291. lastitem = dir->nitems-1;
  292. if (line < 0)
  293. line = 0;
  294. if (line > lastitem)
  295. line = lastitem;
  296. if (dir->curline == line)
  297. return;
  298. if (lastitem <= plines) { /* all items fit on one page */
  299. dir->curline = line;
  300. } else if (line == 0) { /* jump to top */
  301. if (absolute || dir->curline > plines || dir->printoff == 0)
  302. dir->curline = 0;
  303. dir->printoff = 0;
  304. } else if (line + plines < lastitem) { /* jump before last page */
  305. dir->curline = line;
  306. dir->printoff = line;
  307. } else { /* jump within the last page */
  308. lastpagetop = lastitem - plines;
  309. if (dir->printoff == lastpagetop || absolute)
  310. dir->curline = line;
  311. else if (dir->curline < lastpagetop)
  312. dir->curline = lastpagetop;
  313. dir->printoff = lastpagetop;
  314. }
  315. uidisplay(entry);
  316. return;
  317. }
  318. void
  319. searchinline(const char *searchstr, Item *entry, int pos)
  320. {
  321. Dir *dir;
  322. int i;
  323. if (!searchstr || !(dir = entry->dat))
  324. return;
  325. if (pos > 0) {
  326. for (i = dir->curline + 1; i < dir->nitems; ++i) {
  327. if (strstr(dir->items[i].username, searchstr)) {
  328. jumptoline(entry, i, 1);
  329. break;
  330. }
  331. }
  332. } else {
  333. for (i = dir->curline - 1; i > -1; --i) {
  334. if (strstr(dir->items[i].username, searchstr)) {
  335. jumptoline(entry, i, 1);
  336. break;
  337. }
  338. }
  339. }
  340. }
  341. static ssize_t
  342. nearentry(Item *entry, int direction)
  343. {
  344. Dir *dir = entry->dat;
  345. size_t item, lastitem;
  346. if (!dir)
  347. return -1;
  348. lastitem = dir->nitems;
  349. item = dir->curline + direction;
  350. for (; item >= 0 && item < lastitem; item += direction) {
  351. if (dir->items[item].type != 'i')
  352. return item;
  353. }
  354. return dir->curline;
  355. }
  356. Item *
  357. uiselectitem(Item *entry)
  358. {
  359. Dir *dir;
  360. char *searchstr = NULL;
  361. int plines = lines-2;
  362. if (!entry || !(dir = entry->dat))
  363. return NULL;
  364. for (;;) {
  365. switch (getchar()) {
  366. case 0x1b: /* ESC */
  367. switch (getchar()) {
  368. case 0x1b:
  369. goto quit;
  370. case '[':
  371. break;
  372. default:
  373. continue;
  374. }
  375. switch (getchar()) {
  376. case '4':
  377. if (getchar() != '~')
  378. continue;
  379. goto end;
  380. case '5':
  381. if (getchar() != '~')
  382. continue;
  383. goto pgup;
  384. case '6':
  385. if (getchar() != '~')
  386. continue;
  387. goto pgdown;
  388. case 'A':
  389. goto lnup;
  390. case 'B':
  391. goto lndown;
  392. case 'C':
  393. goto pgnext;
  394. case 'D':
  395. goto pgprev;
  396. case 'H':
  397. goto home;
  398. case 0x1b:
  399. goto quit;
  400. }
  401. continue;
  402. case _key_pgprev:
  403. pgprev:
  404. return entry->entry;
  405. case _key_pgnext:
  406. case '\n':
  407. pgnext:
  408. if (dir)
  409. return &dir->items[dir->curline];
  410. continue;
  411. case _key_lndown:
  412. lndown:
  413. movecurline(entry, 1);
  414. continue;
  415. case _key_entrydown:
  416. jumptoline(entry, nearentry(entry, 1), 1);
  417. continue;
  418. case _key_pgdown:
  419. pgdown:
  420. jumptoline(entry, dir->printoff + plines, 0);
  421. continue;
  422. case _key_end:
  423. end:
  424. jumptoline(entry, dir->nitems, 0);
  425. continue;
  426. case _key_lnup:
  427. lnup:
  428. movecurline(entry, -1);
  429. continue;
  430. case _key_entryup:
  431. jumptoline(entry, nearentry(entry, -1), 1);
  432. continue;
  433. case _key_pgup:
  434. pgup:
  435. jumptoline(entry, dir->printoff - plines, 0);
  436. continue;
  437. case _key_home:
  438. home:
  439. jumptoline(entry, 0, 0);
  440. continue;
  441. case _key_search:
  442. search:
  443. free(searchstr);
  444. if ((searchstr = uiprompt("Search for: ")) &&
  445. searchstr[0])
  446. goto searchnext;
  447. clear(&searchstr);
  448. continue;
  449. case _key_searchnext:
  450. searchnext:
  451. searchinline(searchstr, entry, +1);
  452. continue;
  453. case _key_searchprev:
  454. searchinline(searchstr, entry, -1);
  455. continue;
  456. case _key_quit:
  457. quit:
  458. return NULL;
  459. case _key_fetch:
  460. fetch:
  461. if (entry->raw)
  462. continue;
  463. return entry;
  464. case _key_uri:
  465. if (dir)
  466. displayuri(&dir->items[dir->curline]);
  467. continue;
  468. case _key_help: /* FALLTHROUGH */
  469. return help(entry);
  470. default:
  471. continue;
  472. }
  473. }
  474. }
  475. void
  476. uisigwinch(int signal)
  477. {
  478. Dir *dir;
  479. setupterm(NULL, 1, NULL);
  480. putp(tparm(change_scroll_region, 0, lines-2));
  481. if (!curentry || !(dir = curentry->dat))
  482. return;
  483. if (dir->curline - dir->printoff > lines-2)
  484. dir->curline = dir->printoff + lines-2;
  485. uidisplay(curentry);
  486. }