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