2021-01-31 11:12:07 +01:00
|
|
|
|
#include "url-mode.h"
|
|
|
|
|
|
|
2021-02-13 13:45:59 +01:00
|
|
|
|
#include <stdlib.h>
|
2021-01-31 11:12:07 +01:00
|
|
|
|
#include <string.h>
|
|
|
|
|
|
#include <wctype.h>
|
|
|
|
|
|
|
|
|
|
|
|
#define LOG_MODULE "url-mode"
|
2021-02-23 14:59:54 +01:00
|
|
|
|
#define LOG_ENABLE_DBG 0
|
2021-01-31 11:12:07 +01:00
|
|
|
|
#include "log.h"
|
|
|
|
|
|
#include "grid.h"
|
2021-02-06 11:51:58 +01:00
|
|
|
|
#include "render.h"
|
2021-02-04 20:55:08 +01:00
|
|
|
|
#include "selection.h"
|
2021-01-31 11:12:07 +01:00
|
|
|
|
#include "spawn.h"
|
|
|
|
|
|
#include "terminal.h"
|
2021-02-13 12:28:53 +01:00
|
|
|
|
#include "uri.h"
|
2021-01-31 11:12:07 +01:00
|
|
|
|
#include "util.h"
|
|
|
|
|
|
#include "xmalloc.h"
|
|
|
|
|
|
|
2021-02-14 13:42:37 +01:00
|
|
|
|
static void url_destroy(struct url *url);
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
static bool
|
|
|
|
|
|
execute_binding(struct seat *seat, struct terminal *term,
|
|
|
|
|
|
enum bind_action_url action, uint32_t serial)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
|
case BIND_ACTION_URL_NONE:
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
case BIND_ACTION_URL_CANCEL:
|
|
|
|
|
|
urls_reset(term);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
2021-02-14 14:18:11 +01:00
|
|
|
|
case BIND_ACTION_URL_TOGGLE_URL_ON_JUMP_LABEL:
|
|
|
|
|
|
term->urls_show_uri_on_jump_label = !term->urls_show_uri_on_jump_label;
|
|
|
|
|
|
render_refresh_urls(term);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
case BIND_ACTION_URL_COUNT:
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-04 21:19:30 +01:00
|
|
|
|
static void
|
|
|
|
|
|
activate_url(struct seat *seat, struct terminal *term, const struct url *url)
|
|
|
|
|
|
{
|
2021-02-13 12:28:53 +01:00
|
|
|
|
char *url_string = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
char *scheme, *host, *path;
|
|
|
|
|
|
if (uri_parse(url->url, strlen(url->url), &scheme, NULL, NULL,
|
2021-02-13 13:58:16 +01:00
|
|
|
|
&host, NULL, &path, NULL, NULL))
|
2021-02-13 12:28:53 +01:00
|
|
|
|
{
|
2021-02-13 13:58:16 +01:00
|
|
|
|
if (strcmp(scheme, "file") == 0 && hostname_is_localhost(host)) {
|
|
|
|
|
|
/*
|
|
|
|
|
|
* This is a file in *this* computer. Pass only the
|
|
|
|
|
|
* filename to the URL-launcher.
|
|
|
|
|
|
*
|
|
|
|
|
|
* I.e. strip the ‘file://user@host/’ prefix.
|
|
|
|
|
|
*/
|
|
|
|
|
|
url_string = path;
|
|
|
|
|
|
} else
|
|
|
|
|
|
free(path);
|
|
|
|
|
|
|
2021-02-13 12:28:53 +01:00
|
|
|
|
free(scheme);
|
|
|
|
|
|
free(host);
|
2021-02-13 13:58:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (url_string == NULL)
|
2021-02-13 12:28:53 +01:00
|
|
|
|
url_string = xstrdup(url->url);
|
|
|
|
|
|
|
|
|
|
|
|
switch (url->action) {
|
|
|
|
|
|
case URL_ACTION_COPY:
|
|
|
|
|
|
if (text_to_clipboard(seat, term, url_string, seat->kbd.serial)) {
|
|
|
|
|
|
/* Now owned by our clipboard “manager” */
|
|
|
|
|
|
url_string = NULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2021-02-04 21:19:30 +01:00
|
|
|
|
|
2021-02-13 12:28:53 +01:00
|
|
|
|
case URL_ACTION_LAUNCH: {
|
|
|
|
|
|
size_t argc;
|
|
|
|
|
|
char **argv;
|
2021-02-04 21:19:30 +01:00
|
|
|
|
|
2021-02-13 12:28:53 +01:00
|
|
|
|
if (spawn_expand_template(
|
|
|
|
|
|
&term->conf->url_launch, 1,
|
|
|
|
|
|
(const char *[]){"url"},
|
|
|
|
|
|
(const char *[]){url_string},
|
|
|
|
|
|
&argc, &argv))
|
|
|
|
|
|
{
|
|
|
|
|
|
spawn(term->reaper, term->cwd, argv, -1, -1, -1);
|
2021-02-04 21:19:30 +01:00
|
|
|
|
|
2021-02-13 12:28:53 +01:00
|
|
|
|
for (size_t i = 0; i < argc; i++)
|
|
|
|
|
|
free(argv[i]);
|
|
|
|
|
|
free(argv);
|
2021-02-04 21:19:30 +01:00
|
|
|
|
}
|
2021-02-13 12:28:53 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-02-04 21:19:30 +01:00
|
|
|
|
}
|
2021-02-13 12:28:53 +01:00
|
|
|
|
|
|
|
|
|
|
free(url_string);
|
2021-02-04 21:19:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
void
|
|
|
|
|
|
urls_input(struct seat *seat, struct terminal *term, uint32_t key,
|
|
|
|
|
|
xkb_keysym_t sym, xkb_mod_mask_t mods, uint32_t serial)
|
|
|
|
|
|
{
|
|
|
|
|
|
/* Key bindings */
|
|
|
|
|
|
tll_foreach(seat->kbd.bindings.url, it) {
|
2021-02-07 14:46:29 +01:00
|
|
|
|
if (it->item.mods != mods)
|
2021-01-31 11:12:07 +01:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
/* Match symbol */
|
2021-02-07 14:46:29 +01:00
|
|
|
|
if (it->item.sym == sym) {
|
2021-01-31 11:12:07 +01:00
|
|
|
|
execute_binding(seat, term, it->item.action, serial);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Match raw key code */
|
2021-02-07 14:46:29 +01:00
|
|
|
|
tll_foreach(it->item.key_codes, code) {
|
2021-01-31 11:12:07 +01:00
|
|
|
|
if (code->item == key) {
|
|
|
|
|
|
execute_binding(seat, term, it->item.action, serial);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-07 10:32:56 +01:00
|
|
|
|
size_t seq_len = wcslen(term->url_keys);
|
|
|
|
|
|
|
|
|
|
|
|
if (sym == XKB_KEY_BackSpace) {
|
|
|
|
|
|
if (seq_len > 0) {
|
|
|
|
|
|
term->url_keys[seq_len - 1] = L'\0';
|
|
|
|
|
|
render_refresh_urls(term);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
wchar_t wc = xkb_state_key_get_utf32(seat->kbd.xkb_state, key);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Determine if this is a “valid” key. I.e. if there is an URL
|
|
|
|
|
|
* label with a key combo where this key is the next in
|
|
|
|
|
|
* sequence.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
bool is_valid = false;
|
|
|
|
|
|
const struct url *match = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
tll_foreach(term->urls, it) {
|
2021-02-13 13:45:59 +01:00
|
|
|
|
if (it->item.key == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
const struct url *url = &it->item;
|
|
|
|
|
|
const size_t key_len = wcslen(it->item.key);
|
|
|
|
|
|
|
|
|
|
|
|
if (key_len >= seq_len + 1 &&
|
2021-02-13 11:41:04 +01:00
|
|
|
|
wcsncasecmp(url->key, term->url_keys, seq_len) == 0 &&
|
|
|
|
|
|
towlower(url->key[seq_len]) == towlower(wc))
|
2021-01-31 11:12:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
is_valid = true;
|
|
|
|
|
|
if (key_len == seq_len + 1) {
|
|
|
|
|
|
match = url;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (match) {
|
2021-02-04 21:19:30 +01:00
|
|
|
|
activate_url(seat, term, match);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
urls_reset(term);
|
2021-01-31 11:38:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
else if (is_valid) {
|
2021-01-31 11:12:07 +01:00
|
|
|
|
xassert(seq_len + 1 <= ALEN(term->url_keys));
|
|
|
|
|
|
term->url_keys[seq_len] = wc;
|
2021-02-06 20:53:06 +01:00
|
|
|
|
render_refresh_urls(term);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IGNORE_WARNING("-Wpedantic")
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2021-02-13 13:45:59 +01:00
|
|
|
|
auto_detected(const struct terminal *term, enum url_action action,
|
|
|
|
|
|
url_list_t *urls)
|
2021-01-31 11:12:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
static const wchar_t *const prots[] = {
|
|
|
|
|
|
L"http://",
|
|
|
|
|
|
L"https://",
|
2021-02-06 20:11:31 +01:00
|
|
|
|
L"ftp://",
|
|
|
|
|
|
L"ftps://",
|
|
|
|
|
|
L"file://",
|
|
|
|
|
|
L"gemini://",
|
|
|
|
|
|
L"gopher://",
|
2021-01-31 11:12:07 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
size_t max_prot_len = 0;
|
|
|
|
|
|
for (size_t i = 0; i < ALEN(prots); i++) {
|
|
|
|
|
|
size_t len = wcslen(prots[i]);
|
|
|
|
|
|
if (len > max_prot_len)
|
|
|
|
|
|
max_prot_len = len;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wchar_t proto_chars[max_prot_len];
|
|
|
|
|
|
struct coord proto_start[max_prot_len];
|
|
|
|
|
|
size_t proto_char_count = 0;
|
|
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
|
STATE_PROTOCOL,
|
|
|
|
|
|
STATE_URL,
|
|
|
|
|
|
} state = STATE_PROTOCOL;
|
|
|
|
|
|
|
|
|
|
|
|
struct coord start = {-1, -1};
|
|
|
|
|
|
wchar_t url[term->cols * term->rows + 1];
|
|
|
|
|
|
size_t len = 0;
|
|
|
|
|
|
|
2021-02-01 09:55:18 +01:00
|
|
|
|
ssize_t parenthesis = 0;
|
|
|
|
|
|
ssize_t brackets = 0;
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < term->cols; c++) {
|
|
|
|
|
|
const struct cell *cell = &row->cells[c];
|
2021-02-04 21:08:49 +01:00
|
|
|
|
wchar_t wc = cell->wc;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
|
|
case STATE_PROTOCOL:
|
|
|
|
|
|
for (size_t i = 0; i < max_prot_len - 1; i++) {
|
|
|
|
|
|
proto_chars[i] = proto_chars[i + 1];
|
|
|
|
|
|
proto_start[i] = proto_start[i + 1];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (proto_char_count == max_prot_len)
|
|
|
|
|
|
proto_char_count--;
|
|
|
|
|
|
|
|
|
|
|
|
proto_chars[proto_char_count] = wc;
|
|
|
|
|
|
proto_start[proto_char_count] = (struct coord){c, r};
|
|
|
|
|
|
proto_char_count++;
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ALEN(prots); i++) {
|
|
|
|
|
|
size_t prot_len = wcslen(prots[i]);
|
|
|
|
|
|
|
|
|
|
|
|
if (proto_char_count < prot_len)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
const wchar_t *proto = &proto_chars[max_prot_len - prot_len];
|
|
|
|
|
|
|
2021-02-04 21:08:49 +01:00
|
|
|
|
if (wcsncasecmp(prots[i], proto, prot_len) == 0) {
|
2021-01-31 11:12:07 +01:00
|
|
|
|
state = STATE_URL;
|
|
|
|
|
|
start = proto_start[max_prot_len - prot_len];
|
|
|
|
|
|
|
|
|
|
|
|
wcsncpy(url, proto, prot_len);
|
|
|
|
|
|
len = prot_len;
|
2021-02-01 09:55:18 +01:00
|
|
|
|
|
|
|
|
|
|
parenthesis = brackets = 0;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case STATE_URL: {
|
|
|
|
|
|
// static const wchar_t allowed[] =
|
|
|
|
|
|
// L"abcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=";
|
|
|
|
|
|
// static const wchar_t unwise[] = L"{}|\\^[]`";
|
|
|
|
|
|
// static const wchar_t reserved[] = L";/?:@&=+$,";
|
|
|
|
|
|
|
2021-02-01 09:55:18 +01:00
|
|
|
|
bool emit_url = false;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
switch (wc) {
|
|
|
|
|
|
case L'a'...L'z':
|
2021-02-04 21:08:49 +01:00
|
|
|
|
case L'A'...L'Z':
|
2021-01-31 11:12:07 +01:00
|
|
|
|
case L'0'...L'9':
|
|
|
|
|
|
case L'-': case L'.': case L'_': case L'~': case L':':
|
2021-02-01 09:55:18 +01:00
|
|
|
|
case L'/': case L'?': case L'#': case L'@': case L'!':
|
|
|
|
|
|
case L'$': case L'&': case L'\'': case L'*': case L'+':
|
2021-02-08 16:02:07 +01:00
|
|
|
|
case L',': case L';': case L'=': case L'"': case L'%':
|
2021-02-04 21:08:49 +01:00
|
|
|
|
url[len++] = wc;
|
2021-02-01 09:55:18 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case L'(':
|
|
|
|
|
|
parenthesis++;
|
2021-02-04 21:08:49 +01:00
|
|
|
|
url[len++] = wc;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
2021-02-01 09:55:18 +01:00
|
|
|
|
case L'[':
|
|
|
|
|
|
brackets++;
|
2021-02-04 21:08:49 +01:00
|
|
|
|
url[len++] = wc;
|
2021-02-01 09:55:18 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case L')':
|
|
|
|
|
|
if (--parenthesis < 0)
|
|
|
|
|
|
emit_url = true;
|
|
|
|
|
|
else
|
2021-02-04 21:08:49 +01:00
|
|
|
|
url[len++] = wc;
|
2021-02-01 09:55:18 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case L']':
|
|
|
|
|
|
if (--brackets < 0)
|
|
|
|
|
|
emit_url = true;
|
|
|
|
|
|
else
|
2021-02-04 21:08:49 +01:00
|
|
|
|
url[len++] = wc;
|
2021-02-01 09:55:18 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
emit_url = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-01 10:40:15 +01:00
|
|
|
|
if (c >= term->cols - 1 && row->linebreak)
|
|
|
|
|
|
emit_url = true;
|
|
|
|
|
|
|
2021-02-01 09:55:18 +01:00
|
|
|
|
if (emit_url) {
|
2021-01-31 11:12:07 +01:00
|
|
|
|
/* Heuristic to remove trailing characters that
|
|
|
|
|
|
* are valid URL characters, but typically not at
|
|
|
|
|
|
* the end of the URL */
|
|
|
|
|
|
bool done = false;
|
|
|
|
|
|
struct coord end = {c, r};
|
|
|
|
|
|
|
2021-02-06 11:45:58 +01:00
|
|
|
|
if (--end.col < 0) {
|
|
|
|
|
|
end.row--;
|
|
|
|
|
|
end.col = term->cols - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
do {
|
|
|
|
|
|
switch (url[len - 1]) {
|
|
|
|
|
|
case L'.': case L',': case L':': case L';': case L'?':
|
2021-02-08 16:02:07 +01:00
|
|
|
|
case L'!': case L'"': case L'\'': case L'%':
|
2021-01-31 11:12:07 +01:00
|
|
|
|
len--;
|
|
|
|
|
|
end.col--;
|
|
|
|
|
|
if (end.col < 0) {
|
|
|
|
|
|
end.row--;
|
|
|
|
|
|
end.col = term->cols - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
done = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
} while (!done);
|
|
|
|
|
|
|
|
|
|
|
|
url[len] = L'\0';
|
|
|
|
|
|
|
2021-02-06 11:47:59 +01:00
|
|
|
|
start.row += term->grid->view;
|
|
|
|
|
|
end.row += term->grid->view;
|
|
|
|
|
|
|
2021-02-13 12:28:53 +01:00
|
|
|
|
size_t chars = wcstombs(NULL, url, 0);
|
|
|
|
|
|
if (chars != (size_t)-1) {
|
|
|
|
|
|
char *url_utf8 = xmalloc((chars + 1) * sizeof(wchar_t));
|
|
|
|
|
|
wcstombs(url_utf8, url, chars + 1);
|
|
|
|
|
|
|
|
|
|
|
|
tll_push_back(
|
|
|
|
|
|
*urls,
|
|
|
|
|
|
((struct url){
|
2021-02-13 13:45:59 +01:00
|
|
|
|
.id = (uint64_t)rand() << 32 | rand(),
|
2021-02-13 12:28:53 +01:00
|
|
|
|
.url = url_utf8,
|
|
|
|
|
|
.start = start,
|
|
|
|
|
|
.end = end,
|
|
|
|
|
|
.action = action}));
|
|
|
|
|
|
}
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
|
|
state = STATE_PROTOCOL;
|
|
|
|
|
|
len = 0;
|
2021-02-01 09:55:18 +01:00
|
|
|
|
parenthesis = brackets = 0;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UNIGNORE_WARNINGS
|
|
|
|
|
|
|
2021-02-13 12:42:35 +01:00
|
|
|
|
static void
|
|
|
|
|
|
osc8_uris(const struct terminal *term, enum url_action action, url_list_t *urls)
|
|
|
|
|
|
{
|
2021-02-14 21:29:22 +01:00
|
|
|
|
bool dont_touch_url_attr = false;
|
|
|
|
|
|
|
|
|
|
|
|
switch (term->conf->osc8_underline) {
|
|
|
|
|
|
case OSC8_UNDERLINE_URL_MODE:
|
|
|
|
|
|
dont_touch_url_attr = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case OSC8_UNDERLINE_ALWAYS:
|
|
|
|
|
|
dont_touch_url_attr = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-13 12:42:35 +01:00
|
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
|
const struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
|
|
|
|
|
|
|
if (row->extra == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
tll_foreach(row->extra->uri_ranges, it) {
|
|
|
|
|
|
struct coord start = {
|
|
|
|
|
|
.col = it->item.start,
|
|
|
|
|
|
.row = r + term->grid->view,
|
|
|
|
|
|
};
|
|
|
|
|
|
struct coord end = {
|
|
|
|
|
|
.col = it->item.end,
|
|
|
|
|
|
.row = r + term->grid->view,
|
|
|
|
|
|
};
|
|
|
|
|
|
tll_push_back(
|
|
|
|
|
|
*urls,
|
|
|
|
|
|
((struct url){
|
2021-02-13 13:45:59 +01:00
|
|
|
|
.id = it->item.id,
|
2021-02-13 12:42:35 +01:00
|
|
|
|
.url = xstrdup(it->item.uri),
|
|
|
|
|
|
.start = start,
|
|
|
|
|
|
.end = end,
|
2021-02-14 21:29:22 +01:00
|
|
|
|
.action = action,
|
|
|
|
|
|
.url_mode_dont_change_url_attr = dont_touch_url_attr}));
|
2021-02-13 12:42:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-14 13:36:07 +01:00
|
|
|
|
static void
|
|
|
|
|
|
remove_duplicates(url_list_t *urls)
|
|
|
|
|
|
{
|
|
|
|
|
|
tll_foreach(*urls, outer) {
|
|
|
|
|
|
tll_foreach(*urls, inner) {
|
|
|
|
|
|
if (outer == inner)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (outer->item.start.row == inner->item.start.row &&
|
|
|
|
|
|
outer->item.start.col == inner->item.start.col &&
|
|
|
|
|
|
outer->item.end.row == inner->item.end.row &&
|
|
|
|
|
|
outer->item.end.col == inner->item.end.col)
|
|
|
|
|
|
{
|
2021-02-14 13:42:37 +01:00
|
|
|
|
url_destroy(&inner->item);
|
2021-02-14 13:36:07 +01:00
|
|
|
|
tll_remove(*urls, inner);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
void
|
2021-02-06 20:01:52 +01:00
|
|
|
|
urls_collect(const struct terminal *term, enum url_action action, url_list_t *urls)
|
2021-01-31 11:12:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
xassert(tll_length(term->urls) == 0);
|
2021-02-13 12:42:35 +01:00
|
|
|
|
osc8_uris(term, action, urls);
|
2021-02-14 13:36:07 +01:00
|
|
|
|
auto_detected(term, action, urls);
|
|
|
|
|
|
remove_duplicates(urls);
|
2021-02-06 20:01:52 +01:00
|
|
|
|
}
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
2021-02-07 00:01:29 +01:00
|
|
|
|
static int
|
|
|
|
|
|
wcscmp_qsort_wrapper(const void *_a, const void *_b)
|
|
|
|
|
|
{
|
|
|
|
|
|
const wchar_t *a = *(const wchar_t **)_a;
|
|
|
|
|
|
const wchar_t *b = *(const wchar_t **)_b;
|
|
|
|
|
|
return wcscmp(a, b);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-06 21:37:26 +01:00
|
|
|
|
static void
|
2021-02-13 11:43:28 +01:00
|
|
|
|
generate_key_combos(const struct config *conf,
|
|
|
|
|
|
size_t count, wchar_t *combos[static count])
|
2021-02-06 21:37:26 +01:00
|
|
|
|
{
|
2021-02-13 11:43:28 +01:00
|
|
|
|
const wchar_t *alphabet = conf->jump_label_letters;
|
|
|
|
|
|
const size_t alphabet_len = wcslen(alphabet);
|
2021-02-07 00:01:29 +01:00
|
|
|
|
|
|
|
|
|
|
size_t hints_count = 1;
|
2021-02-07 15:10:48 +01:00
|
|
|
|
wchar_t **hints = xmalloc(hints_count * sizeof(hints[0]));
|
2021-02-06 21:37:26 +01:00
|
|
|
|
|
2021-02-07 00:01:29 +01:00
|
|
|
|
hints[0] = xwcsdup(L"");
|
2021-02-06 21:37:26 +01:00
|
|
|
|
|
|
|
|
|
|
size_t offset = 0;
|
2021-02-07 16:03:11 +01:00
|
|
|
|
do {
|
2021-02-07 00:01:29 +01:00
|
|
|
|
const wchar_t *prefix = hints[offset++];
|
2021-02-07 16:03:11 +01:00
|
|
|
|
const size_t prefix_len = wcslen(prefix);
|
2021-02-06 21:37:26 +01:00
|
|
|
|
|
2021-02-07 15:10:48 +01:00
|
|
|
|
hints = xrealloc(hints, (hints_count + alphabet_len) * sizeof(hints[0]));
|
2021-02-07 00:01:29 +01:00
|
|
|
|
|
2021-02-07 16:03:11 +01:00
|
|
|
|
const wchar_t *wc = &alphabet[0];
|
|
|
|
|
|
for (size_t i = 0; i < alphabet_len; i++, wc++) {
|
|
|
|
|
|
wchar_t *hint = xmalloc((prefix_len + 1 + 1) * sizeof(wchar_t));
|
|
|
|
|
|
hints[hints_count + i] = hint;
|
2021-02-07 00:01:29 +01:00
|
|
|
|
|
|
|
|
|
|
/* Will be reversed later */
|
2021-02-07 16:03:11 +01:00
|
|
|
|
hint[0] = *wc;
|
2021-02-07 00:01:29 +01:00
|
|
|
|
wcscpy(&hint[1], prefix);
|
2021-02-06 21:37:26 +01:00
|
|
|
|
}
|
2021-02-07 00:01:29 +01:00
|
|
|
|
hints_count += alphabet_len;
|
2021-02-07 16:03:11 +01:00
|
|
|
|
} while (hints_count - offset < count);
|
2021-02-06 21:37:26 +01:00
|
|
|
|
|
2021-02-07 00:01:29 +01:00
|
|
|
|
xassert(hints_count - offset >= count);
|
2021-02-06 21:37:26 +01:00
|
|
|
|
|
2021-02-07 00:01:29 +01:00
|
|
|
|
/* Copy slice of ‘hints’ array to the caller provided array */
|
|
|
|
|
|
for (size_t i = 0; i < hints_count; i++) {
|
|
|
|
|
|
if (i >= offset && i < offset + count)
|
|
|
|
|
|
combos[i - offset] = hints[i];
|
2021-02-06 21:37:26 +01:00
|
|
|
|
else
|
2021-02-07 00:01:29 +01:00
|
|
|
|
free(hints[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
free(hints);
|
|
|
|
|
|
|
|
|
|
|
|
/* Sorting is a kind of shuffle, since we’re sorting on the
|
|
|
|
|
|
* *reversed* strings */
|
|
|
|
|
|
qsort(combos, count, sizeof(wchar_t *), &wcscmp_qsort_wrapper);
|
|
|
|
|
|
|
|
|
|
|
|
/* Reverse all strings */
|
|
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
|
|
const size_t len = wcslen(combos[i]);
|
|
|
|
|
|
for (size_t j = 0; j < len / 2; j++) {
|
|
|
|
|
|
wchar_t tmp = combos[i][j];
|
|
|
|
|
|
combos[i][j] = combos[i][len - j - 1];
|
|
|
|
|
|
combos[i][len - j - 1] = tmp;
|
|
|
|
|
|
}
|
2021-02-06 21:37:26 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-06 20:01:52 +01:00
|
|
|
|
void
|
2021-02-13 11:43:28 +01:00
|
|
|
|
urls_assign_key_combos(const struct config *conf, url_list_t *urls)
|
2021-02-06 20:01:52 +01:00
|
|
|
|
{
|
2021-02-07 00:01:29 +01:00
|
|
|
|
const size_t count = tll_length(*urls);
|
2021-02-07 14:29:34 +01:00
|
|
|
|
if (count == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2021-02-13 13:45:59 +01:00
|
|
|
|
uint64_t seen_ids[count];
|
2021-02-07 00:01:29 +01:00
|
|
|
|
wchar_t *combos[count];
|
2021-02-13 11:43:28 +01:00
|
|
|
|
generate_key_combos(conf, count, combos);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
2021-02-21 20:10:24 +01:00
|
|
|
|
size_t combo_idx = 0;
|
|
|
|
|
|
size_t id_idx = 0;
|
|
|
|
|
|
|
2021-02-13 13:45:59 +01:00
|
|
|
|
tll_foreach(*urls, it) {
|
|
|
|
|
|
bool id_already_seen = false;
|
2021-02-21 20:10:24 +01:00
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < id_idx; i++) {
|
2021-02-13 13:45:59 +01:00
|
|
|
|
if (it->item.id == seen_ids[i]) {
|
|
|
|
|
|
id_already_seen = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (id_already_seen)
|
|
|
|
|
|
continue;
|
2021-02-21 20:10:24 +01:00
|
|
|
|
seen_ids[id_idx++] = it->item.id;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Scan previous URLs, and check if *this* URL matches any of
|
|
|
|
|
|
* them; if so, re-use the *same* key combo.
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool url_already_seen = false;
|
|
|
|
|
|
tll_foreach(*urls, it2) {
|
|
|
|
|
|
if (&it->item == &it2->item)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
if (strcmp(it->item.url, it2->item.url) == 0) {
|
|
|
|
|
|
it->item.key = xwcsdup(it2->item.key);
|
|
|
|
|
|
url_already_seen = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-02-13 13:45:59 +01:00
|
|
|
|
|
2021-02-21 20:10:24 +01:00
|
|
|
|
if (!url_already_seen)
|
|
|
|
|
|
it->item.key = combos[combo_idx++];
|
2021-02-13 13:45:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Free combos we didn’t use up */
|
2021-02-21 20:10:24 +01:00
|
|
|
|
for (size_t i = combo_idx; i < count; i++)
|
2021-02-13 13:45:59 +01:00
|
|
|
|
free(combos[i]);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
|
|
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
2021-02-06 20:01:52 +01:00
|
|
|
|
tll_foreach(*urls, it) {
|
2021-02-13 13:45:59 +01:00
|
|
|
|
if (it->item.key == NULL)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
char key[32];
|
|
|
|
|
|
wcstombs(key, it->item.key, sizeof(key) - 1);
|
2021-02-13 12:28:53 +01:00
|
|
|
|
LOG_DBG("URL: %s (%s)", it->item.url, key);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2021-02-06 20:01:52 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
tag_cells_for_url(struct terminal *term, const struct url *url, bool value)
|
|
|
|
|
|
{
|
2021-02-14 21:29:22 +01:00
|
|
|
|
if (url->url_mode_dont_change_url_attr)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2021-02-06 20:01:52 +01:00
|
|
|
|
const struct coord *start = &url->start;
|
|
|
|
|
|
const struct coord *end = &url->end;
|
|
|
|
|
|
|
|
|
|
|
|
size_t end_r = end->row & (term->grid->num_rows - 1);
|
|
|
|
|
|
|
|
|
|
|
|
size_t r = start->row & (term->grid->num_rows - 1);
|
|
|
|
|
|
size_t c = start->col;
|
|
|
|
|
|
|
|
|
|
|
|
struct row *row = term->grid->rows[r];
|
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
struct cell *cell = &row->cells[c];
|
|
|
|
|
|
cell->attrs.url = value;
|
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (r == end_r && c == end->col)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
if (++c >= term->cols) {
|
|
|
|
|
|
r = (r + 1) & (term->grid->num_rows - 1);
|
|
|
|
|
|
c = 0;
|
|
|
|
|
|
|
|
|
|
|
|
row = term->grid->rows[r];
|
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
urls_render(struct terminal *term)
|
|
|
|
|
|
{
|
2021-01-31 11:12:07 +01:00
|
|
|
|
struct wl_window *win = term->window;
|
|
|
|
|
|
|
2021-02-07 16:05:02 +01:00
|
|
|
|
if (tll_length(win->term->urls) == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
xassert(tll_length(win->urls) == 0);
|
|
|
|
|
|
tll_foreach(win->term->urls, it) {
|
2021-02-12 11:31:31 +01:00
|
|
|
|
struct wl_url url = {.url = &it->item};
|
|
|
|
|
|
wayl_win_subsurface_new(win, &url.surf);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
|
|
tll_push_back(win->urls, url);
|
2021-02-06 20:01:52 +01:00
|
|
|
|
tag_cells_for_url(term, &it->item, true);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
}
|
2021-02-06 11:51:58 +01:00
|
|
|
|
|
2021-02-06 20:01:52 +01:00
|
|
|
|
render_refresh_urls(term);
|
2021-02-06 11:51:58 +01:00
|
|
|
|
render_refresh(term);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-06 20:26:01 +01:00
|
|
|
|
static void
|
|
|
|
|
|
url_destroy(struct url *url)
|
|
|
|
|
|
{
|
|
|
|
|
|
free(url->url);
|
2021-02-07 00:01:29 +01:00
|
|
|
|
free(url->key);
|
2021-02-06 20:26:01 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
void
|
|
|
|
|
|
urls_reset(struct terminal *term)
|
|
|
|
|
|
{
|
2021-01-31 11:39:23 +01:00
|
|
|
|
if (likely(tll_length(term->urls) == 0))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
if (term->window != NULL) {
|
|
|
|
|
|
tll_foreach(term->window->urls, it) {
|
2021-02-12 11:31:31 +01:00
|
|
|
|
wayl_win_subsurface_destroy(&it->item.surf);
|
2021-02-07 14:52:04 +01:00
|
|
|
|
tll_remove(term->window->urls, it);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tll_foreach(term->urls, it) {
|
2021-02-06 11:51:58 +01:00
|
|
|
|
tag_cells_for_url(term, &it->item, false);
|
2021-02-06 20:26:01 +01:00
|
|
|
|
url_destroy(&it->item);
|
2021-02-07 14:52:04 +01:00
|
|
|
|
tll_remove(term->urls, it);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-14 14:18:11 +01:00
|
|
|
|
term->urls_show_uri_on_jump_label = false;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
memset(term->url_keys, 0, sizeof(term->url_keys));
|
2021-02-06 11:51:58 +01:00
|
|
|
|
render_refresh(term);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
}
|