0 /* See LICENSE file for copyright and license details. */
1 #include <string.h>
2 #include <strings.h>
3 #include <termbox.h>
4 #include "input.h"
5 #include "gemini.h"
6 #include "display.h"
7 #include "cert.h"
8 #include <stdio.h>
9 #ifdef __linux__
10 #include <bsd/string.h>
11 #endif
12
13 int command() {
14 struct gmi_tab* tab = &client.tabs[client.tab];
15 struct gmi_page* page = &tab->page;
16
17 // Trim
18 for (int i=strnlen(client.input.field, sizeof(client.input.field)) - 1;
19 client.input.field[i] == ' ' || client.input.field[i] == '\t'; i--)
20 client.input.field[i] = '\0';
21
22 if (client.input.field[1] == 'q' && client.input.field[2] == '\0') {
23 client.tabs_count--;
24 gmi_freetab(&client.tabs[client.tab]);
25 for (int i = client.tab; i < client.tabs_count; i++) {
26 client.tabs[i] = client.tabs[i+1];
27 }
28 if (client.tab > 0 && client.tab == client.tabs_count)
29 client.tab--;
30 client.input.field[0] = '\0';
31 if (client.tabs_count < 1)
32 return 1;
33 return 0;
34 }
35 if (client.input.field[1] == 'q' && client.input.field[2] == 'a'
36 && client.input.field[3] == '\0')
37 return 1;
38 if (client.input.field[1] == 'n' && client.input.field[2] == 't'
39 && client.input.field[3] == '\0') {
40 gmi_newtab();
41 client.tab = client.tabs_count - 1;
42 client.input.field[0] = '\0';
43 return 0;
44 }
45 if (client.input.field[1] == 'n' && client.input.field[2] == 't'
46 && client.input.field[3] == ' ') {
47 client.input.cursor = 0;
48 int id = atoi(&client.input.field[4]);
49 if (id != 0 || (client.input.field[4] == '0' && client.input.field[5] == '\0')) {
50 gmi_goto_new(id);
51 client.input.field[0] = '\0';
52 } else {
53 gmi_newtab();
54 client.tab = client.tabs_count - 1;
55 client.input.field[0] = '\0';
56 gmi_request(&client.input.field[4]);
57 }
58 return 0;
59 }
60 if (client.input.field[1] == 'o' && client.input.field[2] == ' ') {
61 char urlbuf[MAX_URL];
62 if (strlcpy(urlbuf, &client.input.field[3], sizeof(urlbuf)) >= sizeof(urlbuf)) {
63 client.input.error = 1;
64 snprintf(tab->error, sizeof(tab->error), "Url too long");
65 return 0;
66 }
67 client.input.field[0] = '\0';
68 gmi_cleanforward(tab);
69 int bytes = gmi_request(urlbuf);
70 if (bytes > 0) {
71 tab->scroll = -1;
72 }
73 if (page->code == 11 || page->code == 10) {
74 client.input.mode = 1;
75 client.input.cursor = 0;
76 client.input.error = 0;
77 }
78 tab->selected = 0;
79 return 0;
80 }
81 if (client.input.field[1] == 's' && client.input.field[2] == ' ') {
82 char urlbuf[MAX_URL];
83 snprintf(urlbuf, sizeof(urlbuf),
84 "gemini://geminispace.info/search?%!s(MISSING)", &client.input.field[3]);
85 client.input.field[0] = '\0';
86 gmi_cleanforward(tab);
87 int bytes = gmi_request(urlbuf);
88 if (bytes > 0) {
89 tab->scroll = -1;
90 }
91 if (page->code == 11 || page->code == 10) {
92 client.input.mode = 1;
93 client.input.cursor = 0;
94 client.input.error = 0;
95 }
96 tab->selected = 0;
97 return 0;
98 }
99 if (client.input.field[1] == 'a' && client.input.field[2] == 'd'
100 && client.input.field[3] == 'd' &&
101 (client.input.field[4] == ' ' || client.input.field[4] == '\0')) {
102 char* title = client.input.field[4] == '\0'?NULL:&client.input.field[5];
103 gmi_addbookmark(tab->url, title);
104 client.input.field[0] = '\0';
105 tab->selected = 0;
106 if (!strcmp("about:home", tab->url)) {
107 gmi_freetab(tab);
108 gmi_gohome(tab);
109 }
110 return 0;
111 }
112 if (strcmp(client.input.field, ":gencert") == 0) {
113 char host[256];
114 gmi_parseurl(client.tabs[client.tab].url, host, sizeof(host), NULL, 0, NULL);
115 cert_create(host);
116 client.input.field[0] = '\0';
117 tab->selected = 0;
118 return 0;
119 }
120 if (atoi(&client.input.field[1]) ||
121 (client.input.field[1] == '0' && client.input.field[2] == '\0')) {
122 int bytes = gmi_goto(atoi(&client.input.field[1]));
123 if (bytes > 0) {
124 tab->scroll = -1;
125 }
126 client.input.field[0] = '\0';
127 tab->selected = 0;
128 return 0;
129 }
130 if (client.input.field[1] == '\0') client.input.field[0] = '\0';
131 else {
132 client.input.error = -1;
133 snprintf(tab->error, sizeof(tab->error),
134 "Unknown input: %!s(MISSING)", &client.input.field[1]);
135 }
136 return 0;
137 }
138
139 int input(struct tb_event ev) {
140 struct gmi_tab* tab = &client.tabs[client.tab];
141 struct gmi_page* page = &tab->page;
142 if (page->code == 11 || page->code == 10) {
143 if (!client.input.mode) client.input.cursor = 0;
144 client.input.mode = 1;
145 client.input.error = 0;
146 }
147 if (ev.type == TB_EVENT_RESIZE) {
148 int lines = gmi_render(tab);
149 tab->scroll -= page->lines - lines;
150 if (tab->scroll+tb_height()-2 > lines) tab->scroll = lines - tb_height() + 2;
151 tb_clear();
152 return 0;
153 }
154 if (ev.type != TB_EVENT_KEY) return 0;
155
156 if (!client.input.mode && ev.key == TB_KEY_ESC) {
157 bzero(client.vim.counter, sizeof(client.vim.counter));
158 client.vim.g = 0;
159 }
160 if (client.input.mode && ev.key == TB_KEY_ESC) {
161 client.input.mode = 0;
162 client.input.cursor = 0;
163 if (page->code == 11 || page->code == 10) {
164 page->code = 0;
165 gmi_request(tab->history->url);
166 }
167 client.input.field[0] = '\0';
168 tb_hide_cursor();
169 return 0;
170 }
171 if (client.input.mode && (ev.key == TB_KEY_BACKSPACE2 || ev.key == TB_KEY_BACKSPACE)) {
172 int i = client.input.cursor;
173 if (i>((page->code==10||page->code==11)?0:1)) {
174 strlcpy(&client.input.field[i-1],
175 &client.input.field[i], sizeof(client.input.field)-i);
176 client.input.cursor--;
177 }
178 return 0;
179 }
180 if (ev.key == TB_KEY_DELETE) {
181 if (strcmp(tab->url, "about:home")) return 0;
182 if (tab->selected && tab->selected > 0 && tab->selected <= page->links_count) {
183 gmi_removebookmark(tab->selected);
184 tab->selected = 0;
185 gmi_freetab(tab);
186 gmi_gohome(tab);
187 }
188 return 0;
189 }
190 if (ev.key == TB_KEY_BACK_TAB) {
191 if (tab->selected && tab->selected > 0 && tab->selected <= page->links_count) {
192 int linkid = tab->selected;
193 tab->selected = 0;
194 gmi_goto_new(linkid);
195 }
196 return 0;
197 }
198 if (!client.input.mode && ev.key == TB_KEY_TAB) {
199 if (client.vim.counter[0] != '\0' && atoi(client.vim.counter)) {
200 tab->selected = atoi(client.vim.counter);
201 bzero(client.vim.counter, sizeof(client.vim.counter));
202 if (tab->selected > page->links_count) {
203 snprintf(tab->error, sizeof(tab->error),
204 "Invalid link number");
205 tab->selected = 0;
206 client.input.error = 1;
207 }
208 else if (strlcpy(tab->selected_url, page->links[tab->selected - 1],
209 sizeof(tab->selected_url)) >= sizeof(tab->selected_url)) {
210 snprintf(tab->error, sizeof(tab->error),
211 "Invalid link, above %!l(MISSING)u characters",
212 sizeof(tab->selected_url));
213 tab->selected = 0;
214 client.input.error = 1;
215 }
216 }
217 else if (tab->selected) {
218 gmi_goto(tab->selected);
219 tab->selected = 0;
220 }
221 client.vim.g = 0;
222 return 0;
223 }
224 if (!client.input.mode && ev.key == TB_KEY_ENTER) {
225 if (client.vim.counter[0] != '\0' && atoi(client.vim.counter)) {
226 tab->scroll += atoi(client.vim.counter);
227 bzero(client.vim.counter, sizeof(client.vim.counter));
228 if (tab->scroll+tb_height()-2>page->lines)
229 tab->scroll = page->lines - tb_height() + 2;
230 }
231 else if (tab->scroll+tb_height()-2<page->lines) tab->scroll++;
232 client.vim.g = 0;
233 return 0;
234 }
235 if (client.input.mode && ev.key == TB_KEY_ENTER) {
236 client.input.mode = 0;
237 tb_hide_cursor();
238 if (page->code == 10 || page->code == 11) {
239 char urlbuf[MAX_URL];
240 char* start = strstr(tab->url, "gemini://");
241 char* request = strrchr(tab->url, '?');
242 if (!(start?strchr(&start[GMI], '/'):strchr(tab->url, '/')))
243 snprintf(urlbuf, sizeof(urlbuf),
244 "%!s(MISSING)/?%!s(MISSING)", tab->url, client.input.field);
245 else if (request && request > strrchr(tab->url, '/')) {
246 *request = '\0';
247 snprintf(urlbuf, sizeof(urlbuf),
248 "%!s(MISSING)?%!s(MISSING)", tab->url, client.input.field);
249 *request = '?';
250 } else
251 snprintf(urlbuf, sizeof(urlbuf),
252 "%!s(MISSING)?%!s(MISSING)", tab->url, client.input.field);
253 int bytes = gmi_request(urlbuf);
254 if (bytes>0) {
255 tab = &client.tabs[client.tab];
256 tab->scroll = -1;
257 }
258 client.input.field[0] = '\0';
259 return 0;
260 }
261 return command();
262 }
263 if (client.input.mode && ev.key == TB_KEY_ARROW_LEFT && ev.mod == TB_MOD_CTRL) {
264 while (client.input.cursor>1) {
265 client.input.cursor--;
266 if (client.input.field[client.input.cursor] == ' ' ||
267 client.input.field[client.input.cursor] == '.')
268 break;
269 }
270 return 0;
271 }
272 if (client.input.mode && ev.key == TB_KEY_ARROW_LEFT) {
273 if (client.input.cursor>1)
274 client.input.cursor--;
275 return 0;
276 }
277 if (client.input.mode && ev.key == TB_KEY_ARROW_RIGHT && ev.mod == TB_MOD_CTRL) {
278 while (client.input.field[client.input.cursor]) {
279 client.input.cursor++;
280 if (client.input.field[client.input.cursor] == ' ' ||
281 client.input.field[client.input.cursor] == '.')
282 break;
283 }
284 return 0;
285 }
286 if (client.input.mode && ev.key == TB_KEY_ARROW_RIGHT) {
287 if (client.input.field[client.input.cursor])
288 client.input.cursor++;
289 return 0;
290 }
291 unsigned int l = strnlen(client.input.field, sizeof(client.input.field));
292 if (client.input.mode && ev.ch && l < sizeof(client.input.field)) {
293 for (int i = l-1; i >= client.input.cursor; i--) {
294 client.input.field[i+1] = client.input.field[i];
295 }
296 client.input.field[client.input.cursor] = ev.ch;
297 client.input.cursor++;
298 l++;
299 client.input.field[l] = '\0';
300 return 0;
301 } else
302 tb_hide_cursor();
303
304 if (ev.key == TB_KEY_PGUP) {
305 int counter = atoi(client.vim.counter);
306 if (!counter) counter++;
307 int H = tb_height() - 2 - (client.tabs_count>1);
308 tab->scroll -= counter * H;
309 bzero(client.vim.counter, sizeof(client.vim.counter));
310 if (tab->scroll < -1) tab->scroll = -1;
311 client.vim.g = 0;
312 return 0;
313 }
314 if (ev.key == TB_KEY_PGDN) {
315 int counter = atoi(client.vim.counter);
316 if (!counter) counter++;
317 int H = tb_height() - 2 - (client.tabs_count>1);
318 tab->scroll += counter * H;
319 bzero(client.vim.counter, sizeof(client.vim.counter));
320 if (page->lines <= H) tab->scroll = -1;
321 else if (tab->scroll + H >page->lines)
322 tab->scroll = page->lines - H;
323 client.vim.g = 0;
324 return 0;
325 }
326 switch (ev.ch) {
327 case 'u':
328 client.input.mode = 1;
329 snprintf(client.input.field, sizeof(client.input.field), ":o %!s(MISSING)", tab->url);
330 client.input.cursor = strnlen(client.input.field, sizeof(client.input.field));
331 break;
332 case ':':
333 client.input.error = 0;
334 client.input.mode = 1;
335 client.input.cursor = 1;
336 client.input.field[0] = ':';
337 client.input.field[1] = '\0';
338 break;
339 case 'r': // Reload
340 if (!strcmp("about:home", tab->url)) {
341 gmi_freetab(tab);
342 gmi_gohome(tab);
343 break;
344 }
345 if (!tab->history) break;
346 gmi_request(tab->history->url);
347 struct gmi_link* prev = tab->history->prev;
348 prev->next = NULL;
349 free(tab->history);
350 tab->history = prev;
351 break;
352 case 'h': // Tab left
353 client.tab--;
354 if (client.tab < 0) client.tab = client.tabs_count - 1;
355 break;
356 case 'l': // Tab right
357 client.tab++;
358 if (client.tab >= client.tabs_count) client.tab = 0;
359 break;
360 case 'H': // Back
361 if (!tab->history) break;
362 if (page->code == 20 || page->code == 10 || page->code == 11) {
363 if (!tab->history->prev) break;
364 tab->history->scroll = tab->scroll;
365 tab->history = tab->history->prev;
366 tab->scroll = tab->history->scroll;
367 }
368 if (gmi_request(tab->history->url) < 0) break;
369 break;
370 case 'L': // Forward
371 if (!tab->history || !tab->history->next) break;
372 if (gmi_request(tab->history->next->url) < 0) break;
373 tab->history->scroll = tab->scroll;
374 tab->history = tab->history->next;
375 tab->scroll = tab->history->scroll;
376 break;
377 case 'k': // UP
378 if (client.vim.counter[0] != '\0' && atoi(client.vim.counter)) {
379 tab->scroll -= atoi(client.vim.counter);
380 bzero(client.vim.counter, sizeof(client.vim.counter));
381 if (tab->scroll < -1) tab->scroll = -1;
382 }
383 else if (tab->scroll>-1) tab->scroll--;
384 client.vim.g = 0;
385 break;
386 case 'j': // DOWN
387 if (client.vim.counter[0] != '\0' && atoi(client.vim.counter)) {
388 tab->scroll += atoi(client.vim.counter);
389 bzero(client.vim.counter, sizeof(client.vim.counter));
390 if (tab->scroll+tb_height()-2>page->lines)
391 tab->scroll = page->lines - tb_height() + 2;
392 }
393 else if (tab->scroll+(client.tabs_count==1?tb_height()-2:tb_height()-3)
394 < page->lines) tab->scroll++;
395 client.vim.g = 0;
396 break;
397 case 'f': // Show history
398 display_history();
399 break;
400 case 'g': // Start of file
401 if (client.vim.g) {
402 tab->scroll = -1;
403 client.vim.g = 0;
404 } else client.vim.g++;
405 break;
406 case 'G': // End of file
407 tab->scroll = page->lines-tb_height()+2;
408 if (client.tabs_count != 1) tab->scroll++;
409 if (tb_height()-2-(client.tabs_count>1) > page->lines) tab->scroll = -1;
410 break;
411 default:
412 if (!(ev.ch >= '0' && ev.ch <= '9'))
413 break;
414 unsigned int len = strnlen(client.vim.counter, sizeof(client.vim.counter));
415 if (len == 0 && ev.ch == '0') break;
416 if (len >= sizeof(client.vim.counter)) break;
417 client.vim.counter[len] = ev.ch;
418 client.vim.counter[len+1] = '\0';
419 }
420 return 0;
421 }
422