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 #ifdef sun
8 #include <termios.h>
9 void cfmakeraw(struct termios *t);
10 #endif
11 #include <termbox.h>
12 #include "gemini.h"
13 #include "display.h"
14 #include "cert.h"
15 #include "str.h"
16 #include "url.h"
17 #include "sandbox.h"
18 #include <stdio.h>
19
20 int get_cursor_pos() {
21 int pos = 0;
22 for (int i = 0; i < client.input.cursor; i++) {
23 pos += tb_utf8_char_length(client.input.field[pos]);
24 if (!client.input.field[pos]) break;
25 }
26 return pos;
27 }
28
29 int vim_counter() {
30 int counter = atoi(client.vim.counter);
31 bzero(client.vim.counter, sizeof(client.vim.counter));
32 return counter?counter:1;
33 }
34
35 void fix_scroll(struct gmi_tab* tab) {
36 int h = tab->page.lines - tb_height() + 2 + (client.tabs_count > 1);
37 if (tab->scroll > h)
38 tab->scroll = h;
39 if (tab->scroll < 0)
40 tab->scroll = -1;
41 }
42
43 int command() {
44 struct gmi_tab* tab = client.tab;
45 struct gmi_page* page = &tab->page;
46
47 // Trim
48 for (int i=strnlen(client.input.field, sizeof(client.input.field)) - 1;
49 client.input.field[i] == ' ' || client.input.field[i] == '\t';
50 i--)
51 client.input.field[i] = '\0';
52
53 if (client.input.field[1] == 'q' && client.input.field[2] == '\0') {
54 gmi_freetab(tab);
55 client.input.field[0] = '\0';
56 if (client.tabs_count < 1)
57 return 1;
58 if (client.tabs_count == 1)
59 fix_scroll(client.tab);
60 return 0;
61 }
62 if (client.input.field[1] == 'q' && client.input.field[2] == 'a'
63 && client.input.field[3] == '\0')
64 return 1;
65 if (client.input.field[1] == 'n' && client.input.field[2] == 't'
66 && client.input.field[3] == '\0') {
67 client.tab = gmi_newtab();
68 client.input.field[0] = '\0';
69 return 0;
70 }
71 if (client.input.field[1] == 'n' && client.input.field[2] == 't'
72 && client.input.field[3] == ' ') {
73 client.input.cursor = 0;
74 int id = atoi(&client.input.field[4]);
75 if (id != 0 ||
76 (client.input.field[4] == '0' &&
77 client.input.field[5] == '\0')) {
78 gmi_goto_new(tab, id);
79 client.input.field[0] = '\0';
80 } else {
81 client.tab = gmi_newtab_url(&client.input.field[4]);
82 client.input.field[0] = '\0';
83 }
84 return 0;
85 }
86 if (client.input.field[1] == 'o' && client.input.field[2] == ' ') {
87 char urlbuf[MAX_URL];
88 if (strlcpy(urlbuf, &client.input.field[3],
89 sizeof(urlbuf)) >= sizeof(urlbuf)) {
90 tab->show_error = 1;
91 snprintf(tab->error, sizeof(tab->error),
92 "Url too long");
93 return 0;
94 }
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] == 's' && client.input.field[2] == ' ') {
109 char urlbuf[MAX_URL];
110 snprintf(urlbuf, sizeof(urlbuf),
111 "gemini://geminispace.info/search?%s",
112 &client.input.field[3]);
113 client.input.field[0] = '\0';
114 gmi_cleanforward(tab);
115 int bytes = gmi_request(tab, urlbuf, 1);
116 if (bytes > 0) {
117 tab->scroll = -1;
118 }
119 if (page->code == 11 || page->code == 10) {
120 client.input.mode = 1;
121 client.input.cursor = 0;
122 }
123 tab->selected = 0;
124 return 0;
125 }
126 if (client.input.field[1] == 'a' &&
127 client.input.field[2] == 'd' &&
128 client.input.field[3] == 'd' &&
129 (client.input.field[4] == ' ' || client.input.field[4] == '\0')) {
130 char* title = client.input.field[4] == '\0'?
131 NULL:&client.input.field[5];
132 gmi_addbookmark(tab, tab->url, title);
133 client.input.field[0] = '\0';
134 tab->selected = 0;
135 if (!strcmp("about:home", tab->url)) {
136 gmi_freetab(tab);
137 gmi_gohome(tab, 1);
138 }
139 return 0;
140 }
141 int ignore = !strncmp(client.input.field, ":ignore",
142 sizeof(":ignore") - 1);
143 int forget = !strncmp(client.input.field, ":forget",
144 sizeof(":forget") - 1);
145 if (forget || ignore) {
146 char* ptr = client.input.field + sizeof(":forget") - 1;
147 int space = 0;
148 for (; *ptr; ptr++) {
149 if (*ptr != ' ') break;
150 space++;
151 }
152 if (space == 0 || !*ptr) goto unknown;
153 if ((ignore && cert_ignore_expiration(ptr)) ||
154 (forget && cert_forget(ptr))) {
155 tab->show_error = 1;
156 if (forget) {
157 snprintf(tab->error, sizeof(tab->error),
158 "Unknown %s certificate", ptr);
159 } else {
160 strerror_r(errno, tab->error,
161 sizeof(tab->error));
162 }
163 return 0;
164 }
165 tab->show_info = 1;
166 snprintf(tab->info, sizeof(tab->info),
167 "%s %s", ptr, (forget ?
168 "certificate removed from known hosts" :
169 "certificate expiration will be ignored"));
170 client.input.field[0] = '\0';
171 return 0;
172 }
173 if (strcmp(client.input.field, ":gencert") == 0) {
174 client.input.field[0] = '\0';
175 tab->selected = 0;
176 if (!strncmp(tab->url, "about:home", sizeof(tab->url))) {
177 tab->show_error = 1;
178 snprintf(tab->error, sizeof(tab->error),
179 "Cannot create a certificate for this page");
180 return 0;
181 }
182 char host[256];
183 parse_url(tab->url, host, sizeof(host), NULL, 0, NULL);
184 if (cert_create(host, tab->error, sizeof(tab->error))) {
185 tab->show_error = 1;
186 return 0;
187 }
188 cert_getcert(host, 1);
189 tab->show_info = 1;
190 snprintf(tab->info, sizeof(tab->info),
191 "Certificate generated for %s", host);
192 return 0;
193 }
194 if (strcmp(client.input.field, ":exec") == 0) {
195 #ifdef DISABLE_XDG
196 tab->show_error = 1;
197 snprintf(tab->error, sizeof(tab->error),
198 "xdg is disabled");
199 return 0;
200 #else
201 if (!*client.input.download) {
202 snprintf(tab->error, sizeof(tab->error),
203 "No file was downloaded");
204 tab->show_error = 1;
205 client.input.field[0] = '\0';
206 return 0;
207 }
208 xdg_open(client.input.download);
209 client.input.field[0] = '\0';
210 client.input.download[0] = '\0';
211 return 0;
212 #endif
213 }
214 if (strncmp(client.input.field, ":download",
215 sizeof(":download") - 1) == 0) {
216 char* ptr = client.input.field + sizeof(":download") - 1;
217 int space = 0;
218 for (; *ptr; ptr++) {
219 if (*ptr != ' ') break;
220 space++;
221 }
222 if (space == 0 && *ptr) goto unknown;
223 char urlbuf[1024];
224 char* url = strrchr(tab->history->url, '/');
225 if (url && (*(url+1) == '\0')) {
226 while (*url == '/' && url > tab->history->url)
227 url--;
228 char* ptr = url + 1;
229 while (*url != '/' && url > tab->history->url)
230 url--;
231 if (*url == '/') url++;
232 if (strlcpy(urlbuf, url, ptr - url + 1) >=
233 sizeof(urlbuf))
234 return fatalI();
235 url = urlbuf;
236 } else if (url) url++;
237 else url = tab->history->url;
238 char* download = *ptr ? ptr : url;
239 #ifdef SANDBOX_SUN
240 if (sandbox_download(tab, download))
241 return -1;
242 int fd = wr_pair[1];
243 #else
244 int fd = openat(getdownloadfd(), download,
245 O_CREAT|O_EXCL|O_WRONLY, 0600);
246 char buf[1024];
247 if (fd < 0 && errno == EEXIST) {
248 #ifdef __OpenBSD__
249 snprintf(buf, sizeof(buf), "%lld_%s",
250 #else
251 snprintf(buf, sizeof(buf), "%ld_%s",
252 #endif
253 time(NULL), download);
254 fd = openat(getdownloadfd(), buf,
255 O_CREAT|O_EXCL|O_WRONLY, 0600);
256 download = buf;
257 }
258 if (fd < 0) {
259 tab->show_error = -1;
260 snprintf(tab->error, sizeof(tab->error),
261 "Failed to write file : %s", strerror(errno));
262 return 0;
263 }
264 #endif
265 char* data = strnstr(tab->page.data, "\r\n",
266 tab->page.data_len);
267 uint64_t data_len = tab->page.data_len;
268 if (!data) data = tab->page.data;
269 else {
270 data += 2;
271 data_len -= (data - tab->page.data);
272 }
273 #ifdef SANDBOX_SUN
274 sandbox_dl_length(data_len);
275 #endif
276 write(fd, data, data_len);
277 #ifndef SANDBOX_SUN
278 close(fd);
279 #endif
280 tab->show_info = 1;
281 snprintf(tab->info, sizeof(tab->info),
282 "File downloaded : %s", download);
283 strlcpy(client.input.download, download,
284 sizeof(client.input.download));
285 client.input.field[0] = '\0';
286 tab->selected = 0;
287 return 0;
288 }
289 if (client.input.field[0] == ':' && (atoi(&client.input.field[1]) ||
290 (client.input.field[1] == '0' && client.input.field[2] == '\0'))) {
291 client.tab->scroll = atoi(&client.input.field[1]) - 1 -
292 !!client.tabs_count;
293 if (client.tab->scroll < 0)
294 client.tab->scroll = -!!client.tabs_count;
295 fix_scroll(client.tab);
296 client.input.field[0] = '\0';
297 tab->selected = 0;
298 return 0;
299 }
300 if (client.input.field[0] == '/') {
301 strlcpy(tab->search.entry, &client.input.field[1],
302 sizeof(tab->search.entry));
303 client.input.field[0] = '\0';
304 } else if (client.input.field[1] == '\0') client.input.field[0] = '\0';
305 else {
306 unknown:
307 tab->show_error = -1;
308 snprintf(tab->error, sizeof(tab->error),
309 "Unknown input: %s", &client.input.field[1]);
310 }
311 return 0;
312 }
313
314 int input_page(struct tb_event ev) {
315 struct gmi_tab* tab = client.tab;
316 struct gmi_page* page = &tab->page;
317 int counter;
318 switch (ev.key) {
319 case TB_KEY_ESC:
320 tab->selected = 0;
321 bzero(client.vim.counter, sizeof(client.vim.counter));
322 client.vim.g = 0;
323 return 0;
324 case TB_KEY_DELETE:
325 if (strcmp(tab->url, "about:home")) return 0;
326 if (tab->selected && tab->selected > 0 &&
327 tab->selected <= page->links_count) {
328 gmi_removebookmark(tab->selected);
329 tab->selected = 0;
330 gmi_request(tab, tab->url, 0);
331 }
332 return 0;
333 case TB_KEY_BACK_TAB:
334 if (tab->selected && tab->selected > 0 &&
335 tab->selected <= page->links_count) {
336 int linkid = tab->selected;
337 tab->selected = 0;
338 gmi_goto_new(tab, linkid);
339 }
340 return 0;
341 case TB_KEY_ARROW_LEFT:
342 if (ev.mod == TB_MOD_SHIFT)
343 goto go_back;
344 goto tab_prev;
345 case TB_KEY_ARROW_RIGHT:
346 if (ev.mod == TB_MOD_SHIFT)
347 goto go_forward;
348 goto tab_next;
349 case TB_KEY_ARROW_UP:
350 goto move_up;
351 case TB_KEY_ARROW_DOWN:
352 goto move_down;
353 case TB_KEY_TAB:
354 client.vim.g = 0;
355 if (client.vim.counter[0] == '\0' ||
356 !atoi(client.vim.counter)) {
357 if (!tab->selected)
358 return 0;
359 gmi_goto(tab, tab->selected);
360 tab->selected = 0;
361 bzero(client.vim.counter, sizeof(client.vim.counter));
362 return 0;
363 }
364 tab->selected = atoi(client.vim.counter);
365 bzero(client.vim.counter, sizeof(client.vim.counter));
366 if (tab->selected > page->links_count) {
367 snprintf(tab->error, sizeof(tab->error),
368 "Invalid link number");
369 tab->selected = 0;
370 tab->show_error = 1;
371 return 0;
372 }
373 size_t len = strlcpy(tab->selected_url,
374 page->links[tab->selected - 1],
375 sizeof(tab->selected_url));
376 if (len >= sizeof(tab->selected_url)) {
377 snprintf(tab->error, sizeof(tab->error),
378 "Invalid link, above %lu characters",
379 sizeof(tab->selected_url));
380 tab->selected = 0;
381 tab->show_error = 1;
382 }
383 return 0;
384 case TB_KEY_ENTER:
385 if (client.vim.counter[0] != '\0' &&
386 atoi(client.vim.counter)) {
387 tab->scroll += atoi(client.vim.counter);
388 bzero(client.vim.counter, sizeof(client.vim.counter));
389 }
390 else tab->scroll++;
391 fix_scroll(tab);
392 client.vim.g = 0;
393 return 0;
394 case TB_KEY_PGUP:
395 tab->scroll -= vim_counter() * tb_height() -
396 2 - (client.tabs_count>1);
397 fix_scroll(tab);
398 client.vim.g = 0;
399 return 0;
400 case TB_KEY_PGDN:
401 tab->scroll += vim_counter() * tb_height() -
402 2 - (client.tabs_count>1);
403 fix_scroll(tab);
404 client.vim.g = 0;
405 return 0;
406 }
407 switch (ev.ch) {
408 case 'u':
409 tab->show_error = 0;
410 client.input.mode = 1;
411 snprintf(client.input.field,
412 sizeof(client.input.field),
413 ":o %s", tab->url);
414 client.input.cursor = utf8_len(client.input.field,
415 sizeof(client.input.field));
416 break;
417 case ':':
418 tab->show_error = 0;
419 client.input.mode = 1;
420 client.input.cursor = 1;
421 client.input.field[0] = ':';
422 client.input.field[1] = '\0';
423 break;
424 case '/':
425 tab->show_error = 0;
426 client.input.mode = 1;
427 client.input.cursor = 1;
428 client.input.field[0] = '/';
429 client.input.field[1] = '\0';
430 break;
431 case 'r': // Reload
432 if (!tab->history) break;
433 gmi_request(tab, tab->history->url, 0);
434 break;
435 case 'T': // Tab left
436 tab_prev:
437 if (!client.vim.g) break;
438 client.vim.g = 0;
439 counter = vim_counter();
440 if (!client.tab->next && !client.tab->prev) break;
441 for (int i = counter; i > 0; i--) {
442 if (client.tab->prev)
443 client.tab = client.tab->prev;
444 else
445 while (client.tab->next)
446 client.tab = client.tab->next;
447 fix_scroll(client.tab);
448 }
449 break;
450 case 't': // Tab right
451 tab_next:
452 if (!client.vim.g) break;
453 client.vim.g = 0;
454 counter = vim_counter() % client.tabs_count;
455 if (!client.tab->next && !client.tab->prev) break;
456 for (int i = counter; i > 0; i--) {
457 if (client.tab->next)
458 client.tab = client.tab->next;
459 else
460 while (client.tab->prev)
461 client.tab = client.tab->prev;
462 fix_scroll(client.tab);
463 }
464 break;
465 case 'H': // Back
466 go_back:
467 if (!tab->history || !tab->history->prev) break;
468 tab->selected = 0;
469 if (page->code == 20 || page->code == 10 || page->code == 11) {
470 tab->history->scroll = tab->scroll;
471 if (!tab->history->next) {
472 tab->history->page = tab->page;
473 tab->history->cached = 1;
474 }
475 tab->history = tab->history->prev;
476 tab->scroll = tab->history->scroll;
477 if (tab->history->cached)
478 tab->page = tab->history->page;
479 }
480 if (tab->history->cached) {
481 tab->page = tab->history->page;
482 strlcpy(tab->url, tab->history->url, sizeof(tab->url));
483 } else if (gmi_request(tab, tab->history->url, 1) < 0) break;
484 fix_scroll(client.tab);
485 break;
486 case 'L': // Forward
487 go_forward:
488 if (!tab->history || !tab->history->next) break;
489 tab->selected = 0;
490 if (!tab->history->cached) {
491 tab->history->page = tab->page;
492 tab->history->cached = 1;
493 }
494 if (tab->history->next->cached)
495 tab->page = tab->history->next->page;
496 else if (gmi_request(tab, tab->history->next->url, 1) < 0)
497 break;
498 tab->history->scroll = tab->scroll;
499 tab->history = tab->history->next;
500 tab->scroll = tab->history->scroll;
501 strlcpy(tab->url, tab->history->url, sizeof(tab->url));
502 fix_scroll(client.tab);
503 break;
504 case 'k': // UP
505 move_up:
506 tab->scroll -= vim_counter();
507 fix_scroll(tab);
508 client.vim.g = 0;
509 break;
510 case 'j': // DOWN
511 move_down:
512 tab->scroll += vim_counter();
513 fix_scroll(tab);
514 client.vim.g = 0;
515 break;
516 case 'f': // Show history
517 display_history();
518 break;
519 case 'g': // Start of file
520 if (client.vim.g) {
521 tab->scroll = -1;
522 client.vim.g = 0;
523 } else client.vim.g++;
524 break;
525 case 'G': // End of file
526 tab->scroll = page->lines-tb_height()+2;
527 if (client.tabs_count != 1)
528 tab->scroll++;
529 if (tb_height()-2-(client.tabs_count>1) > page->lines)
530 tab->scroll = -1;
531 break;
532 case 'n': // Next occurence
533 tab->search.cursor++;
534 tab->scroll = tab->search.pos[1] - tb_height()/2;
535 fix_scroll(tab);
536 break;
537 case 'N': // Previous occurence
538 tab->search.cursor--;
539 tab->scroll = tab->search.pos[0] - tb_height()/2;
540 fix_scroll(tab);
541 break;
542 default:
543 if (!(ev.ch >= '0' && ev.ch <= '9'))
544 break;
545 tab->show_error = 0;
546 tab->show_info = 0;
547 unsigned int len = strnlen(client.vim.counter,
548 sizeof(client.vim.counter));
549 if (len == 0 && ev.ch == '0') break;
550 if (len >= sizeof(client.vim.counter)) break;
551 client.vim.counter[len] = ev.ch;
552 client.vim.counter[len+1] = '\0';
553 }
554 return 0;
555 }
556
557 int input_field(struct tb_event ev) {
558 struct gmi_tab* tab = client.tab;
559 struct gmi_page* page = &tab->page;
560 int pos = 0;
561 switch (ev.key) {
562 case TB_KEY_ESC:
563 client.input.mode = 0;
564 client.input.cursor = 0;
565 if (page->code == 11 || page->code == 10)
566 page->code = 20;
567 client.input.field[0] = '\0';
568 tb_hide_cursor();
569 return 0;
570 case TB_KEY_BACKSPACE:
571 case TB_KEY_BACKSPACE2:
572 if (client.input.cursor >
573 ((page->code == 10 || page->code == 11)?0:1)) {
574 if (client.input.field[0] == '/' &&
575 page->code == 20)
576 tab->search.scroll = 1;
577 char* ptr = client.input.field;
578 int l = 1;
579 int pos = 0;
580 while (*ptr) {
581 l = tb_utf8_char_length(*ptr);
582 pos++;
583 if (pos == client.input.cursor)
584 break;
585 ptr += l;
586 }
587
588 strlcpy(ptr, ptr + l,
589 sizeof(client.input.field) -
590 (ptr - client.input.field));
591 client.input.cursor--;
592 }
593 return 0;
594 case TB_KEY_ENTER:
595 client.input.mode = 0;
596 tb_hide_cursor();
597 if (page->code == 10 || page->code == 11) {
598 char urlbuf[MAX_URL];
599 char* start = strstr(tab->url, "gemini://");
600 char* request = strrchr(tab->url, '?');
601 if (!(start?
602 strchr(&start[GMI], '/'):strchr(tab->url, '/')))
603 snprintf(urlbuf, sizeof(urlbuf),
604 "%s/?%s", tab->url,
605 client.input.field);
606 else if (request && request > strrchr(tab->url, '/')) {
607 *request = '\0';
608 snprintf(urlbuf, sizeof(urlbuf),
609 "%s?%s", tab->url,
610 client.input.field);
611 *request = '?';
612 } else
613 snprintf(urlbuf, sizeof(urlbuf),
614 "%s?%s", tab->url,
615 client.input.field);
616 int bytes = gmi_request(tab, urlbuf, 1);
617 if (bytes>0) {
618 tab = client.tab;
619 tab->scroll = -1;
620 }
621 client.input.field[0] = '\0';
622 return 0;
623 }
624 return command();
625 case TB_KEY_ARROW_LEFT:
626 {
627 int min = 1;
628 if (tab->page.code == 10 || tab->page.code == 11)
629 min = 0;
630 if (ev.mod != TB_MOD_CTRL) {
631 if (client.input.cursor > min)
632 client.input.cursor--;
633 return 0;
634 }
635 pos = get_cursor_pos();
636 while (client.input.cursor > min) {
637 pos -= tb_utf8_char_length(client.input.field[pos]);
638 client.input.cursor--;
639 if (client.input.field[pos] == ' ' ||
640 client.input.field[pos] == '.')
641 break;
642 }
643 return 0;
644 }
645 case TB_KEY_ARROW_RIGHT:
646 pos = get_cursor_pos();
647 if (ev.mod != TB_MOD_CTRL) {
648 if (client.input.field[pos])
649 client.input.cursor++;
650 return 0;
651 }
652 while (client.input.field[pos]) {
653 pos += tb_utf8_char_length(client.input.field[pos]);
654 client.input.cursor++;
655 if (!client.input.field[pos]) break;
656 if (client.input.field[pos] == ' ' ||
657 client.input.field[pos] == '.')
658 break;
659 }
660 return 0;
661 }
662 if (!ev.ch) {
663 tb_hide_cursor();
664 return 0;
665 }
666 char* end = client.input.field;
667 while (*end)
668 end += tb_utf8_char_length(*end);
669 if ((size_t)(end - client.input.field) >=
670 sizeof(client.input.field) - 1)
671 return 0;
672
673 char insert[16];
674 int insert_len = tb_utf8_unicode_to_char(insert, ev.ch);
675 char* start = client.input.field;
676 for (int i = 0; *start && i < client.input.cursor; i++)
677 start += tb_utf8_char_length(*start);
678 for (char* ptr = end; start <= ptr; ptr--)
679 ptr[insert_len] = *ptr;
680
681 memcpy(start, insert, insert_len);
682 client.input.cursor++;
683 end += insert_len;
684 *end = '\0';
685 if (client.input.field[0] != '/' || page->code != 20)
686 return 0;
687 int lines = 0;
688 int posx = 0;
689 int w = tb_width();
690 int found = 0;
691 for (int i = 0; i < tab->page.data_len - 1; i++) {
692 if (posx == 0 && tab->page.data[i] == '=' &&
693 tab->page.data[i + 1] == '>') {
694 int ignore = 0;
695 int firstspace = 0;
696 while (i + ignore < tab->page.data_len) {
697 if (tab->page.data[i + ignore] != '\n') {
698 i += 2 + firstspace;
699 break;
700 }
701 if (tab->page.data[i + ignore] == ' ') {
702 ignore++;
703 if (firstspace) continue;
704 i += ignore;
705 break;
706 }
707 ignore++;
708 firstspace = 1;
709 }
710 if (i > tab->page.data_len)
711 break;
712 }
713 if (lines &&
714 !strncasecmp(&client.input.field[1],
715 &tab->page.data[i],
716 strnlen(&client.input.field[1],
717 sizeof(client.input.field) - 1))) {
718 found = 1;
719 break;
720 }
721 if (posx == w || tab->page.data[i] == '\n') {
722 lines++;
723 posx = 0;
724 continue;
725 }
726 posx++;
727 }
728 if (found) {
729 tab->scroll = lines - tb_height()/2;
730 fix_scroll(tab);
731 }
732 return 0;
733 }
734
735 int input(struct tb_event ev) {
736 struct gmi_tab* tab = client.tab;
737 struct gmi_page* page = &tab->page;
738 if (page->code == 11 || page->code == 10) {
739 if (!client.input.mode) client.input.cursor = 0;
740 client.input.mode = 1;
741 }
742 if (ev.type == TB_EVENT_RESIZE) {
743 fix_scroll(tab);
744 return 0;
745 }
746 if (ev.type != TB_EVENT_KEY) return 0;
747 if (client.input.mode)
748 return input_field(ev);
749 return input_page(ev);
750 }
751
752 int tb_interupt() {
753 int sig = 0;
754 return write(global.resize_pipefd[1], &sig, sizeof(sig)) ==
755 sizeof(sig) ? 0 : -1;
756 }
757