💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Vgmi › files › 2174e4ab7b18fdf96b407b2d4a4d6… captured on 2022-07-16 at 17:12:27. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
0 #include <string.h>
1 #include <strings.h>
2 #include <unistd.h>
3 #define TB_IMPL
4 #include "wcwidth.h"
5 #undef wcwidth
6 #define wcwidth(x) mk_wcwidth(x)
7 #include <termbox.h>
8 #include "gemini.h"
9 #include "display.h"
10 #include "cert.h"
11 #include "str.h"
12 #include <stdio.h>
13
14 void fix_scroll(struct gmi_tab* tab) {
15 int h = tab->page.lines - tb_height() + 2 + (client.tabs_count>1);
16 if (tab->scroll > h)
17 tab->scroll = h;
18 if (tab->scroll < 0)
19 tab->scroll = -1;
20 }
21
22 int command() {
23 struct gmi_tab* tab = &client.tabs[client.tab];
24 struct gmi_page* page = &tab->page;
25
26 // Trim
27 for (int i=strnlen(client.input.field, sizeof(client.input.field)) - 1;
28 client.input.field[i] == ' ' || client.input.field[i] == '\t'; i--)
29 client.input.field[i] = '\0';
30
31 if (client.input.field[1] == 'q' && client.input.field[2] == '\0') {
32 client.tabs_count--;
33 gmi_freetab(&client.tabs[client.tab]);
34 for (int i = client.tab; i < client.tabs_count; i++) {
35 client.tabs[i] = client.tabs[i+1];
36 }
37 if (client.tab > 0 && client.tab == client.tabs_count)
38 client.tab--;
39 client.input.field[0] = '\0';
40 if (client.tabs_count < 1)
41 return 1;
42 if (client.tabs_count == 1)
43 fix_scroll(&client.tabs[0]);
44 return 0;
45 }
46 if (client.input.field[1] == 'q' && client.input.field[2] == 'a'
47 && client.input.field[3] == '\0')
48 return 1;
49 if (client.input.field[1] == 'n' && client.input.field[2] == 't'
50 && client.input.field[3] == '\0') {
51 gmi_newtab();
52 client.tab = client.tabs_count - 1;
53 client.input.field[0] = '\0';
54 return 0;
55 }
56 if (client.input.field[1] == 'n' && client.input.field[2] == 't'
57 && client.input.field[3] == ' ') {
58 client.input.cursor = 0;
59 int id = atoi(&client.input.field[4]);
60 if (id != 0 ||
61 (client.input.field[4] == '0' && client.input.field[5] == '\0')) {
62 gmi_goto_new(tab, id);
63 client.input.field[0] = '\0';
64 } else {
65 gmi_newtab_url(&client.input.field[4]);
66 client.tab = client.tabs_count - 1;
67 client.input.field[0] = '\0';
68 }
69 return 0;
70 }
71 if (client.input.field[1] == 'o' && client.input.field[2] == ' ') {
72 char urlbuf[MAX_URL];
73 if (strlcpy(urlbuf, &client.input.field[3], sizeof(urlbuf)) >= sizeof(urlbuf)) {
74 tab->show_error = 1;
75 snprintf(tab->error, sizeof(tab->error), "Url too long");
76 return 0;
77 }
78 client.input.field[0] = '\0';
79 gmi_cleanforward(tab);
80 int bytes = gmi_request(tab, urlbuf, 1);
81 if (bytes > 0) {
82 tab->scroll = -1;
83 }
84 if (page->code == 11 || page->code == 10) {
85 client.input.mode = 1;
86 client.input.cursor = 0;
87 }
88 tab->selected = 0;
89 return 0;
90 }
91 if (client.input.field[1] == 's' && client.input.field[2] == ' ') {
92 char urlbuf[MAX_URL];
93 snprintf(urlbuf, sizeof(urlbuf),
94 "gemini://geminispace.info/search?%!s(MISSING)", &client.input.field[3]);
95 client.input.field[0] = '\0';
96 gmi_cleanforward(tab);
97 int bytes = gmi_request(tab, urlbuf, 1);
98 if (bytes > 0) {
99 tab->scroll = -1;
100 }
101 if (page->code == 11 || page->code == 10) {
102 client.input.mode = 1;
103 client.input.cursor = 0;
104 }
105 tab->selected = 0;
106 return 0;
107 }
108 if (client.input.field[1] == 'a' && client.input.field[2] == 'd'
109 && client.input.field[3] == 'd' &&
110 (client.input.field[4] == ' ' || client.input.field[4] == '\0')) {
111 char* title = client.input.field[4] == '\0'?NULL:&client.input.field[5];
112 gmi_addbookmark(tab, tab->url, title);
113 client.input.field[0] = '\0';
114 tab->selected = 0;
115 if (!strcmp("about:home", tab->url)) {
116 gmi_freetab(tab);
117 gmi_gohome(tab, 1);
118 }
119 return 0;
120 }
121 if (strncmp(client.input.field, ":forget", sizeof(":forget") - 1) == 0) {
122 char* ptr = client.input.field + sizeof(":forget") - 1;
123 int space = 0;
124 for (; *ptr; ptr++) {
125 if (*ptr != ' ') break;
126 space++;
127 }
128 if (space == 0 || !*ptr) goto unknown;
129 if (cert_forget(ptr)) {
130 tab->show_error = 1;
131 snprintf(tab->error, sizeof(tab->error),
132 "Unknown certificate %!s(MISSING)", ptr);
133 return 0;
134 }
135 tab->show_info = 1;
136 snprintf(tab->info, sizeof(tab->info),
137 "%!s(MISSING) certificate removed from known hosts", ptr);
138 return 0;
139 }
140 if (strcmp(client.input.field, ":gencert") == 0) {
141 char host[256];
142 gmi_parseurl(tab->url, host, sizeof(host), NULL, 0, NULL);
143 if (cert_create(host, tab->error, sizeof(tab->error))) {
144 tab->show_error = 1;
145 }
146 client.input.field[0] = '\0';
147 tab->selected = 0;
148 return 0;
149 }
150 if (strncmp(client.input.field, ":download", sizeof(":download") - 1) == 0) {
151 char* ptr = client.input.field + sizeof(":download") - 1;
152 int space = 0;
153 for (; *ptr; ptr++) {
154 if (*ptr != ' ') break;
155 space++;
156 }
157 if (space == 0 && *ptr) goto unknown;
158 char urlbuf[1024];
159 char* url = strrchr(tab->history->url, '/');
160 if (url && (*(url+1) == '\0')) {
161 while (*url == '/' && url > tab->history->url)
162 url--;
163 char* ptr = url + 1;
164 while (*url != '/' && url > tab->history->url)
165 url--;
166 if (*url == '/') url++;
167 if (strlcpy(urlbuf, url, ptr - url + 1) >= sizeof(urlbuf))
168 return fatalI();
169 url = urlbuf;
170 } else if (url) url++;
171 else url = tab->history->url;
172 int fd = openat(getdownloadfd(), *ptr?ptr:url,
173 O_CREAT|O_EXCL|O_RDWR, 0600);
174 if (fd < 0) {
175 tab->show_error = -1;
176 snprintf(tab->error, sizeof(tab->error),
177 "Failed to write file : %!s(MISSING)", strerror(errno));
178 return 0;
179 }
180 char* data = strnstr(tab->page.data, "\r\n", tab->page.data_len);
181 int data_len = tab->page.data_len;
182 if (!data) data = tab->page.data;
183 else {
184 data += 2;
185 data_len -= (data - tab->page.data);
186 }
187 write(fd, data, data_len);
188 close(fd);
189 tab->show_info = 1;
190 snprintf(tab->info, sizeof(tab->info),
191 "File downloaded : %!s(MISSING)", *ptr?ptr:url);
192 client.input.field[0] = '\0';
193 tab->selected = 0;
194 return 0;
195 }
196 if (client.input.field[0] == ':' && (atoi(&client.input.field[1]) ||
197 (client.input.field[1] == '0' && client.input.field[2] == '\0'))) {
198 gmi_goto(tab, atoi(&client.input.field[1]));
199 client.input.field[0] = '\0';
200 tab->selected = 0;
201 return 0;
202 }
203 if (client.input.field[0] == '/') {
204 strlcpy(tab->search.entry, &client.input.field[1],
205 sizeof(tab->search.entry));
206 client.input.field[0] = '\0';
207 } else if (client.input.field[1] == '\0') client.input.field[0] = '\0';
208 else {
209 unknown:
210 tab->show_error = -1;
211 snprintf(tab->error, sizeof(tab->error),
212 "Unknown input: %!s(MISSING)", &client.input.field[1]);
213 }
214 return 0;
215 }
216
217 int input(struct tb_event ev) {
218 struct gmi_tab* tab = &client.tabs[client.tab];
219 struct gmi_page* page = &tab->page;
220 if (page->code == 11 || page->code == 10) {
221 if (!client.input.mode) client.input.cursor = 0;
222 client.input.mode = 1;
223 }
224 if (ev.type == TB_EVENT_RESIZE) {
225 fix_scroll(tab);
226 return 0;
227 }
228 if (ev.type != TB_EVENT_KEY) return 0;
229
230 if (!client.input.mode && ev.key == TB_KEY_ESC) {
231 bzero(client.vim.counter, sizeof(client.vim.counter));
232 client.vim.g = 0;
233 }
234 if (client.input.mode && ev.key == TB_KEY_ESC) {
235 client.input.mode = 0;
236 client.input.cursor = 0;
237 if (page->code == 11 || page->code == 10)
238 page->code = 20;
239 client.input.field[0] = '\0';
240 tb_hide_cursor();
241 return 0;
242 }
243 if (client.input.mode && (ev.key == TB_KEY_BACKSPACE2 || ev.key == TB_KEY_BACKSPACE)) {
244 int i = client.input.cursor;
245 if (i>((page->code==10||page->code==11)?0:1)) {
246 if (client.input.field[0] == '/' &&
247 page->code == 20)
248 tab->search.scroll = 1;
249 strlcpy(&client.input.field[i-1],
250 &client.input.field[i], sizeof(client.input.field)-i);
251 client.input.cursor--;
252 }
253 return 0;
254 }
255 if (ev.key == TB_KEY_DELETE) {
256 if (strcmp(tab->url, "about:home")) return 0;
257 if (tab->selected && tab->selected > 0 && tab->selected <= page->links_count) {
258 gmi_removebookmark(tab->selected);
259 tab->selected = 0;
260 gmi_request(tab, tab->url, 0);
261 }
262 return 0;
263 }
264 if (ev.key == TB_KEY_BACK_TAB) {
265 if (tab->selected && tab->selected > 0 && tab->selected <= page->links_count) {
266 int linkid = tab->selected;
267 tab->selected = 0;
268 gmi_goto_new(tab, linkid);
269 }
270 return 0;
271 }
272 if (!client.input.mode && ev.key == TB_KEY_ARROW_LEFT &&
273 ev.mod == TB_MOD_SHIFT)
274 goto go_back;
275 if (!client.input.mode && ev.key == TB_KEY_ARROW_RIGHT &&
276 ev.mod == TB_MOD_SHIFT)
277 goto go_forward;
278 if (!client.input.mode && ev.key == TB_KEY_ARROW_LEFT)
279 goto tab_prev;
280 if (!client.input.mode && ev.key == TB_KEY_ARROW_RIGHT)
281 goto tab_next;
282 if (!client.input.mode && ev.key == TB_KEY_ARROW_UP)
283 goto move_up;
284 if (!client.input.mode && ev.key == TB_KEY_ARROW_DOWN)
285 goto move_down;
286 if (!client.input.mode && ev.key == TB_KEY_TAB) {
287 if (client.vim.counter[0] != '\0' && atoi(client.vim.counter)) {
288 tab->selected = atoi(client.vim.counter);
289 bzero(client.vim.counter, sizeof(client.vim.counter));
290 if (tab->selected > page->links_count) {
291 snprintf(tab->error, sizeof(tab->error),
292 "Invalid link number");
293 tab->selected = 0;
294 tab->show_error = 1;
295 }
296 else if (strlcpy(tab->selected_url, page->links[tab->selected - 1],
297 sizeof(tab->selected_url)) >= sizeof(tab->selected_url)) {
298 snprintf(tab->error, sizeof(tab->error),
299 "Invalid link, above %!l(MISSING)u characters",
300 sizeof(tab->selected_url));
301 tab->selected = 0;
302 tab->show_error = 1;
303 }
304 }
305 else if (tab->selected) {
306 gmi_goto(tab, tab->selected);
307 tab->selected = 0;
308 }
309 client.vim.g = 0;
310 return 0;
311 }
312 if (!client.input.mode && ev.key == TB_KEY_ENTER) {
313 if (client.vim.counter[0] != '\0' && atoi(client.vim.counter)) {
314 tab->scroll += atoi(client.vim.counter);
315 bzero(client.vim.counter, sizeof(client.vim.counter));
316 }
317 else tab->scroll++;
318 fix_scroll(tab);
319 client.vim.g = 0;
320 return 0;
321 }
322 if (client.input.mode && ev.key == TB_KEY_ENTER) {
323 client.input.mode = 0;
324 tb_hide_cursor();
325 if (page->code == 10 || page->code == 11) {
326 char urlbuf[MAX_URL];
327 char* start = strstr(tab->url, "gemini://");
328 char* request = strrchr(tab->url, '?');
329 if (!(start?strchr(&start[GMI], '/'):strchr(tab->url, '/')))
330 snprintf(urlbuf, sizeof(urlbuf),
331 "%!s(MISSING)/?%!s(MISSING)", tab->url, client.input.field);
332 else if (request && request > strrchr(tab->url, '/')) {
333 *request = '\0';
334 snprintf(urlbuf, sizeof(urlbuf),
335 "%!s(MISSING)?%!s(MISSING)", tab->url, client.input.field);
336 *request = '?';
337 } else
338 snprintf(urlbuf, sizeof(urlbuf),
339 "%!s(MISSING)?%!s(MISSING)", tab->url, client.input.field);
340 int bytes = gmi_request(tab, urlbuf, 1);
341 if (bytes>0) {
342 tab = &client.tabs[client.tab];
343 tab->scroll = -1;
344 }
345 client.input.field[0] = '\0';
346 return 0;
347 }
348 return command();
349 }
350 if (client.input.mode && ev.key == TB_KEY_ARROW_LEFT && ev.mod == TB_MOD_CTRL) {
351 while (client.input.cursor>1) {
352 client.input.cursor--;
353 if (client.input.field[client.input.cursor] == ' ' ||
354 client.input.field[client.input.cursor] == '.')
355 break;
356 }
357 return 0;
358 }
359 if (client.input.mode && ev.key == TB_KEY_ARROW_LEFT) {
360 if (client.input.cursor>1)
361 client.input.cursor--;
362 return 0;
363 }
364 if (client.input.mode && ev.key == TB_KEY_ARROW_RIGHT && ev.mod == TB_MOD_CTRL) {
365 while (client.input.field[client.input.cursor]) {
366 client.input.cursor++;
367 if (client.input.field[client.input.cursor] == ' ' ||
368 client.input.field[client.input.cursor] == '.')
369 break;
370 }
371 return 0;
372 }
373 if (client.input.mode && ev.key == TB_KEY_ARROW_RIGHT) {
374 if (client.input.field[client.input.cursor])
375 client.input.cursor++;
376 return 0;
377 }
378 unsigned int l = strnlen(client.input.field, sizeof(client.input.field));
379 if (client.input.mode && ev.ch && l < sizeof(client.input.field)) {
380 for (int i = l-1; i >= client.input.cursor; i--) {
381 client.input.field[i+1] = client.input.field[i];
382 }
383 client.input.field[client.input.cursor] = ev.ch;
384 client.input.cursor++;
385 l++;
386 client.input.field[l] = '\0';
387 if (client.input.field[0] != '/' ||
388 page->code != 20)
389 return 0;
390 int lines = 0;
391 int posx = 0;
392 int w = tb_width();
393 int found = 0;
394 for (int i = 0; i < tab->page.data_len - 1; i++) {
395 if (posx == 0 && tab->page.data[i] == '=' &&
396 tab->page.data[i + 1] == '>') {
397 int ignore = 0;
398 int firstspace = 0;
399 while (i + ignore < tab->page.data_len) {
400 if (tab->page.data[i + ignore] != '\n') {
401 i += 2 + firstspace;
402 break;
403 }
404 if (tab->page.data[i + ignore] == ' ') {
405 ignore++;
406 if (firstspace) continue;
407 i += ignore;
408 break;
409 }
410 ignore++;
411 firstspace = 1;
412 }
413 if (i > tab->page.data_len)
414 break;
415 }
416 if (lines && !strncasecmp(&client.input.field[1],
417 &tab->page.data[i],
418 strnlen(&client.input.field[1],
419 sizeof(client.input.field) - 1)
420 )) {
421 found = 1;
422 break;
423 }
424 if (posx == w || tab->page.data[i] == '\n') {
425 lines++;
426 posx=0;
427 continue;
428 }
429 posx++;
430 }
431 if (found) {
432 tab->scroll = lines - tb_height()/2;
433 fix_scroll(tab);
434 }
435 return 0;
436 } else
437 tb_hide_cursor();
438
439 if (ev.key == TB_KEY_PGUP) {
440 int counter = atoi(client.vim.counter);
441 if (!counter) counter++;
442 int H = tb_height() - 2 - (client.tabs_count>1);
443 tab->scroll -= counter * H;
444 bzero(client.vim.counter, sizeof(client.vim.counter));
445 fix_scroll(tab);
446 client.vim.g = 0;
447 return 0;
448 }
449 if (ev.key == TB_KEY_PGDN) {
450 int counter = atoi(client.vim.counter);
451 if (!counter) counter++;
452 int H = tb_height() - 2 - (client.tabs_count>1);
453 tab->scroll += counter * H;
454 bzero(client.vim.counter, sizeof(client.vim.counter));
455 fix_scroll(tab);
456 client.vim.g = 0;
457 return 0;
458 }
459 switch (ev.ch) {
460 case 'u':
461 tab->show_error = 0;
462 client.input.mode = 1;
463 snprintf(client.input.field, sizeof(client.input.field), ":o %!s(MISSING)", tab->url);
464 client.input.cursor = strnlen(client.input.field, sizeof(client.input.field));
465 break;
466 case ':':
467 tab->show_error = 0;
468 client.input.mode = 1;
469 client.input.cursor = 1;
470 client.input.field[0] = ':';
471 client.input.field[1] = '\0';
472 break;
473 case '/':
474 tab->show_error = 0;
475 client.input.mode = 1;
476 client.input.cursor = 1;
477 client.input.field[0] = '/';
478 client.input.field[1] = '\0';
479 break;
480 case 'r': // Reload
481 if (!tab->history) break;
482 gmi_request(tab, tab->history->url, 0);
483 break;
484 case 'h': // Tab left
485 tab_prev:
486 client.tab--;
487 if (client.tab < 0) client.tab = client.tabs_count - 1;
488 break;
489 case 'l': // Tab right
490 tab_next:
491 client.tab++;
492 if (client.tab >= client.tabs_count) client.tab = 0;
493 break;
494 case 'H': // Back
495 go_back:
496 if (!tab->history || !tab->history->prev) break;
497 if (page->code == 20 || page->code == 10 || page->code == 11) {
498 tab->history->scroll = tab->scroll;
499 if (!tab->history->next) {
500 tab->history->page = tab->page;
501 tab->history->cached = 1;
502 }
503 tab->history = tab->history->prev;
504 tab->scroll = tab->history->scroll;
505 if (tab->history->cached)
506 tab->page = tab->history->page;
507 }
508 if (tab->history->cached) {
509 tab->page = tab->history->page;
510 strlcpy(tab->url, tab->history->url, sizeof(tab->url));
511 } else if (gmi_request(tab, tab->history->url, 1) < 0) break;
512 break;
513 case 'L': // Forward
514 go_forward:
515 if (!tab->history || !tab->history->next) break;
516 if (!tab->history->cached) {
517 tab->history->page = tab->page;
518 tab->history->cached = 1;
519 }
520 if (tab->history->next->cached)
521 tab->page = tab->history->next->page;
522 else if (gmi_request(tab, tab->history->next->url, 1) < 0) break;
523 tab->history->scroll = tab->scroll;
524 tab->history = tab->history->next;
525 tab->scroll = tab->history->scroll;
526 strlcpy(tab->url, tab->history->url, sizeof(tab->url));
527 break;
528 case 'k': // UP
529 move_up:
530 if (client.vim.counter[0] != '\0' && atoi(client.vim.counter)) {
531 tab->scroll -= atoi(client.vim.counter);
532 bzero(client.vim.counter, sizeof(client.vim.counter));
533 }
534 else tab->scroll--;
535 fix_scroll(tab);
536 client.vim.g = 0;
537 break;
538 case 'j': // DOWN
539 move_down:
540 if (client.vim.counter[0] != '\0' && atoi(client.vim.counter)) {
541 tab->scroll += atoi(client.vim.counter);
542 bzero(client.vim.counter, sizeof(client.vim.counter));
543 }
544 else tab->scroll++;
545 fix_scroll(tab);
546 client.vim.g = 0;
547 break;
548 case 'f': // Show history
549 display_history();
550 break;
551 case 'g': // Start of file
552 if (client.vim.g) {
553 tab->scroll = -1;
554 client.vim.g = 0;
555 } else client.vim.g++;
556 break;
557 case 'G': // End of file
558 tab->scroll = page->lines-tb_height()+2;
559 if (client.tabs_count != 1) tab->scroll++;
560 if (tb_height()-2-(client.tabs_count>1) > page->lines) tab->scroll = -1;
561 break;
562 case 'n': // Next occurence
563 tab->search.cursor++;
564 tab->scroll = tab->search.pos[1] - tb_height()/2;
565 fix_scroll(tab);
566 break;
567 case 'N': // Previous occurence
568 tab->search.cursor--;
569 tab->scroll = tab->search.pos[0] - tb_height()/2;
570 fix_scroll(tab);
571 break;
572 default:
573 if (!(ev.ch >= '0' && ev.ch <= '9'))
574 break;
575 tab->show_error = 0;
576 tab->show_info = 0;
577 unsigned int len = strnlen(client.vim.counter, sizeof(client.vim.counter));
578 if (len == 0 && ev.ch == '0') break;
579 if (len >= sizeof(client.vim.counter)) break;
580 client.vim.counter[len] = ev.ch;
581 client.vim.counter[len+1] = '\0';
582 }
583 return 0;
584 }
585
586 int tb_interupt() {
587 int sig = 0;
588 return write(global.resize_pipefd[1], &sig, sizeof(sig))==sizeof(sig)?0:-1;
589 }
590