2 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35 #include "minibuffer.h"
40 struct history history;
42 static unsigned int autosavetimer;
45 switch_to_tab(struct tab *tab)
48 tab->flags &= ~TAB_URGENT;
50 if (operating && tab->flags & TAB_LAZY)
51 load_url_in_tab(tab, hist_cur(tab->hist), NULL,
58 static uint32_t tab_counter;
64 new_tab(const char *url, const char *base, struct tab *after)
71 if ((tab = calloc(1, sizeof(*tab))) == NULL) {
76 if ((tab->hist = hist_new(HIST_LINEAR)) == NULL) {
82 TAILQ_INIT(&tab->buffer.head);
83 TAILQ_INIT(&tab->buffer.page.head);
85 tab->id = tab_new_id();
88 TAILQ_INSERT_AFTER(&tabshead, after, tab, tabs);
90 TAILQ_INSERT_TAIL(&tabshead, tab, tabs);
93 tab->flags |= TAB_LAZY;
94 load_url_in_tab(tab, url, base, 0);
100 * Move a tab from the tablist to the killed tab list and erase its
101 * contents. Append should always be 0 to prepend tabs so unkill_tab
102 * can work correctly; appending is only useful during startup when
103 * receiving the list of killed tabs to keep the correct order.
104 * NB: doesn't update the current_tab.
107 kill_tab(struct tab *tab, int append)
112 erase_buffer(&tab->buffer);
113 TAILQ_REMOVE(&tabshead, tab, tabs);
114 ui_schedule_redraw();
117 ev_timer_cancel(tab->loading_timer);
120 TAILQ_INSERT_TAIL(&ktabshead, tab, tabs);
122 TAILQ_INSERT_HEAD(&ktabshead, tab, tabs);
126 TAILQ_FOREACH(tab, &ktabshead, tabs)
128 while (count > max_killed_tabs) {
130 free_tab(TAILQ_LAST(&ktabshead, tabshead));
135 * Resurrects the lastest killed tab and returns it. The tab is already
136 * added to the tab list with the TAB_LAZY flag set. NB: this doesn't
137 * update current_tab.
144 if (TAILQ_EMPTY(&ktabshead))
147 ui_schedule_redraw();
150 t = TAILQ_FIRST(&ktabshead);
151 TAILQ_REMOVE(&ktabshead, t, tabs);
152 TAILQ_INSERT_TAIL(&tabshead, t, tabs);
153 t->flags |= TAB_LAZY;
158 * Free every resource linked to the tab, including the tab itself, and
159 * removes it from the *killed* tablist.
162 free_tab(struct tab *tab)
164 TAILQ_REMOVE(&ktabshead, tab, tabs);
165 hist_free(tab->hist);
170 stop_tab(struct tab *tab)
172 ui_send_net(IMSG_STOP, tab->id, -1, NULL, 0);
176 savetab(FILE *fp, struct tab *tab, int killed)
179 size_t top_line, current_line;
181 get_scroll_position(tab, &top_line, ¤t_line);
183 fprintf(fp, "%s ", hist_cur(tab->hist));
184 if (tab == current_tab)
185 fprintf(fp, "current,");
187 fprintf(fp, "killed,");
189 fprintf(fp, "top=%zu,cur=%zu %s\n", top_line, current_line,
190 tab->buffer.page.title);
192 cur = hist_off(tab->hist);
193 size = hist_size(tab->hist);
194 for (i = 0; i < size; ++i) {
197 fprintf(fp, "%s %s\n", i > cur ? ">" : "<",
198 hist_nth(tab->hist, i));
210 strlcpy(sfn, session_file_tmp, sizeof(sfn));
211 if ((fd = mkstemp(sfn)) == -1 ||
212 (fp = fdopen(fd, "w")) == NULL) {
220 TAILQ_FOREACH(tab, &tabshead, tabs)
222 TAILQ_FOREACH(tab, &ktabshead, tabs)
225 err = fflush(fp) == EOF;
228 if (err || rename(sfn, session_file) == -1)
233 save_all_history(void)
240 strlcpy(sfn, history_file_tmp, sizeof(sfn));
241 if ((fd = mkstemp(sfn)) == -1 ||
242 (fp = fdopen(fd, "w")) == NULL) {
250 for (i = 0; i < history.len; ++i) {
251 history.items[i].dirty = 0;
252 fprintf(fp, "%lld %s\n", (long long)history.items[i].ts,
253 history.items[i].uri);
256 err = fflush(fp) == EOF;
259 if (err || rename(sfn, history_file) == -1) {
269 save_dirty_history(void)
274 if ((fp = fopen(history_file, "a")) == NULL)
277 for (i = 0; i < history.len && history.dirty > 0; ++i) {
278 if (!history.items[i].dirty)
281 history.items[i].dirty = 0;
282 fprintf(fp, "%lld %s\n", (long long)history.items[i].ts,
283 history.items[i].uri);
298 if (history.extra > HISTORY_CAP/2)
300 else if (history.dirty)
301 save_dirty_history();
305 history_push(struct histitem *hi)
307 size_t i, oldest = 0;
310 for (i = 0; i < history.len; ++i) {
311 if (history.items[i].ts < history.items[oldest].ts)
314 /* remove duplicates */
315 if (!strcmp(history.items[i].uri, hi->uri))
319 if ((uri = strdup(hi->uri)) == NULL)
322 /* don't grow too much; replace the oldest */
323 if (history.len == HISTORY_CAP) {
324 history.items[oldest].ts = hi->ts;
325 free(history.items[oldest].uri);
326 history.items[oldest].uri = uri;
328 /* Growed past the max value, signal to regen the file. */
333 history.items[history.len].ts = hi->ts;
334 history.items[history.len].uri = uri;
339 history_cmp(const void *a, const void *b)
341 const struct history_item *i = a, *j = b;
342 return strcmp(i->uri, j->uri);
348 qsort(history.items, history.len, sizeof(history.items[0]),
353 history_add(const char *uri)
355 size_t i, j, insert = 0, oldest = 0;
359 for (i = 0; i < history.len; ++i) {
360 if (history.items[i].ts < history.items[oldest].ts)
363 if (insert != 0 && insert < i)
366 c = strcmp(uri, history.items[i].uri);
368 history.items[i].ts = time(NULL);
369 history.items[i].dirty = 1;
379 if ((u = strdup(uri)) == NULL)
382 /* if history is full, replace the oldest one */
383 if (history.len == HISTORY_CAP) {
384 free(history.items[oldest].uri);
385 history.items[oldest].uri = u;
386 history.items[oldest].ts = time(NULL);
387 history.items[oldest].dirty = 1;
394 /* otherwise just insert in the right spot */
396 for (j = history.len; j > insert; --j)
397 memcpy(&history.items[j], &history.items[j-1],
398 sizeof(history.items[j]));
400 history.items[insert].ts = time(NULL);
401 history.items[insert].uri = u;
402 history.items[insert].dirty = 1;
415 autosave_timer(int fd, int event, void *data)
421 * Function to be called in "interesting" places where we may want to
422 * schedule an autosave (like on new tab or before loading an url.)
432 if (!ev_timer_pending(autosavetimer)) {
433 tv.tv_sec = autosave;
436 autosavetimer = ev_timer(&tv, autosave_timer, NULL);
441 parse_khost_line(char *line, char *tmp[3])
445 for (ap = tmp; ap < &tmp[3] &&
446 (*ap = strsep(&line, " \t\n")) != NULL;) {
451 return ap == &tmp[3] && *line == '\0';
455 load_certs(struct ohash *certs)
457 char *tmp[3], *line = NULL;
459 size_t lineno = 0, linesize = 0;
462 struct tofu_entry *e;
464 if ((f = fopen(known_hosts_file, "r")) == NULL)
467 if ((e = calloc(1, sizeof(*e))) == NULL) {
472 while ((linelen = getline(&line, &linesize, f)) != -1) {
475 if (parse_khost_line(line, tmp)) {
476 strlcpy(e->domain, tmp[0], sizeof(e->domain));
477 strlcpy(e->hash, tmp[1], sizeof(e->hash));
479 e->verified = strtonum(tmp[2], 0, 1, &errstr);
481 errx(1, "%s:%zu verification for %s is %s: %s",
482 known_hosts_file, lineno,
483 e->domain, errstr, tmp[2]);
487 warnx("%s:%zu invalid entry",
488 known_hosts_file, lineno);
503 char *nl, *spc, *line = NULL;
507 if ((hist = fopen(history_file, "r")) == NULL)
510 while ((linelen = getline(&line, &linesize, hist)) != -1) {
511 if ((nl = strchr(line, '\n')) != NULL)
513 if ((spc = strchr(line, ' ')) == NULL)
518 memset(&hi, 0, sizeof(hi));
519 hi.ts = strtonum(line, INT64_MIN, INT64_MAX, &errstr);
522 if (strlcpy(hi.uri, spc, sizeof(hi.uri)) >= sizeof(hi.uri))
535 * Check if the last time telescope crashed. The check is done by
536 * looking at `crashed_file': if it exists then last time we crashed.
537 * Then, while here, touch the file too, it's removed during the
541 last_time_crashed(void)
548 if (unlink(crashed_file) == -1 && errno == ENOENT)
551 if ((fd = open(crashed_file, O_CREAT|O_WRONLY, 0600)) == -1)
559 * Parse and restore a tab from the session file. The format is:
561 * URL [flags,...] [title]\n
563 static inline struct tab *
564 parse_tab_line(char *line, struct tab **ct)
568 const char *uri, *title = "";
569 int current = 0, killed = 0;
570 size_t tline = 0, cline = 0;
573 if ((s = strchr(line, ' ')) == NULL)
577 if ((t = strchr(s, ' ')) != NULL) {
582 while ((ap = strsep(&s, ",")) != NULL) {
583 if (!strcmp(ap, "current"))
585 else if (!strcmp(ap, "killed"))
587 else if (!strncmp(ap, "top=", 4))
588 tline = strtonum(ap+4, 0, UINT32_MAX, NULL);
589 else if (!strncmp(ap, "cur=", 4))
590 cline = strtonum(ap + 4, 0, UINT32_MAX, NULL);
598 if ((tab = new_tab(uri, NULL, NULL)) == NULL)
600 hist_set_offs(tab->hist, tline, cline);
601 strlcpy(tab->buffer.page.title, title, sizeof(tab->buffer.page.title));
614 struct tab *tab = NULL, *ct = NULL;
616 size_t lineno = 0, linesize = 0;
618 char *uri, *line = NULL;
620 if ((session = fopen(session_file, "r")) == NULL) {
621 new_tab("about:new", NULL, NULL);
622 new_tab("about:help", NULL, NULL);
626 while ((linelen = getline(&line, &linesize, session)) != -1) {
629 if (linelen > 0 && line[linelen-1] == '\n')
630 line[linelen-1] = '\0';
632 if (*line == '<' || *line == '>') {
634 if (*uri != ' ' || tab == NULL) {
635 fprintf(stderr, "%s:%zu invalid line\n",
636 session_file, lineno);
641 if (*line == '>') /* future hist */
642 hist_append(tab->hist, uri);
644 hist_prepend(tab->hist, uri);
646 tab = parse_tab_line(line, &ct);
652 if (ct == NULL || TAILQ_EMPTY(&tabshead))
653 ct = new_tab("about:new", NULL, NULL);
657 if (last_time_crashed())
658 new_tab("about:crash", NULL, NULL);
662 load_session(struct ohash *certs)
676 if ((fd = open(lockfile_path, O_WRONLY|O_CREAT, 0600)) == -1)
681 lock.l_type = F_WRLCK;
682 lock.l_whence = SEEK_SET;
684 if (fcntl(fd, F_SETLK, &lock) == -1) {