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>
|
2021-02-24 21:30:58 +01:00
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
#include <fcntl.h>
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
|
|
#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-24 21:30:58 +01:00
|
|
|
|
int dev_null = open("/dev/null", O_RDWR);
|
|
|
|
|
|
|
|
|
|
|
|
if (dev_null < 0) {
|
|
|
|
|
|
LOG_ERRNO("failed to open /dev/null");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-13 12:28:53 +01:00
|
|
|
|
if (spawn_expand_template(
|
2021-05-20 17:56:56 +02:00
|
|
|
|
&term->conf->url.launch, 1,
|
2021-02-13 12:28:53 +01:00
|
|
|
|
(const char *[]){"url"},
|
|
|
|
|
|
(const char *[]){url_string},
|
|
|
|
|
|
&argc, &argv))
|
|
|
|
|
|
{
|
2021-02-24 21:30:58 +01:00
|
|
|
|
spawn(term->reaper, term->cwd, argv, dev_null, dev_null, dev_null);
|
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-24 21:30:58 +01:00
|
|
|
|
|
|
|
|
|
|
close(dev_null);
|
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,
|
input: rewrite of how we match foot’s own key bindings
Bindings are matched in one out of three ways:
* By translated (by XKB) symbols
* By untranslated symbols
* By raw key codes
A translated symbol is affected by pressed modifiers, some of which
can be “consumed”. Consumed modifiers to not partake in the comparison
with the binding’s modifiers. In this mode, ctrl+shift+2 maps to
ctrl+@ on a US layout.
Untranslated symbols, or un-shifted symbols refer to the “base” symbol
of the pressed key, i.e. it’s unaffected by modifiers. In this mode,
consumed modifiers *do* partake in the comparison with the binding’s
modifiers, and ctrl+shift+2 maps to ctrl+shift+2 on a US layout.
More examples: ctrl+shift+u maps to ctrl+U in the translated lookup,
while ctrl+shift+u maps to ctrl+shift+u in the untranslated lookup.
Finally, we also match raw key codes. This allows our bindings to work
using the same physical keys when the user switches between latin and
non-latin layouts.
This means key bindings in foot.ini *must* not include both +shift+
and a *shifted* key. I.e. ctrl+shift+U is not a valid combo as it
cannot be triggered. Unfortunately, this was how you were supposed to
write bindings up until now... so, we try to detect such bindings, log
a deprecation warning and then “fix” the binding for the user.
When specifying bindings in foot.ini, both ctrl+U and ctrl+shift+u are
valid, and will work. The latter is preferred though, since we cannot
detect the raw key code for the former variant. Personally, I also
prefer the latter one because it is more explicit; it’s more obvious
which keys are involved.
However, in some cases it makes more sense to use the other
variant. Typically for non-letter combos.
2021-02-27 20:42:31 +01:00
|
|
|
|
xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed,
|
|
|
|
|
|
const xkb_keysym_t *raw_syms, size_t raw_count,
|
|
|
|
|
|
uint32_t serial)
|
2021-01-31 11:12:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
/* Key bindings */
|
|
|
|
|
|
tll_foreach(seat->kbd.bindings.url, it) {
|
input: rewrite of how we match foot’s own key bindings
Bindings are matched in one out of three ways:
* By translated (by XKB) symbols
* By untranslated symbols
* By raw key codes
A translated symbol is affected by pressed modifiers, some of which
can be “consumed”. Consumed modifiers to not partake in the comparison
with the binding’s modifiers. In this mode, ctrl+shift+2 maps to
ctrl+@ on a US layout.
Untranslated symbols, or un-shifted symbols refer to the “base” symbol
of the pressed key, i.e. it’s unaffected by modifiers. In this mode,
consumed modifiers *do* partake in the comparison with the binding’s
modifiers, and ctrl+shift+2 maps to ctrl+shift+2 on a US layout.
More examples: ctrl+shift+u maps to ctrl+U in the translated lookup,
while ctrl+shift+u maps to ctrl+shift+u in the untranslated lookup.
Finally, we also match raw key codes. This allows our bindings to work
using the same physical keys when the user switches between latin and
non-latin layouts.
This means key bindings in foot.ini *must* not include both +shift+
and a *shifted* key. I.e. ctrl+shift+U is not a valid combo as it
cannot be triggered. Unfortunately, this was how you were supposed to
write bindings up until now... so, we try to detect such bindings, log
a deprecation warning and then “fix” the binding for the user.
When specifying bindings in foot.ini, both ctrl+U and ctrl+shift+u are
valid, and will work. The latter is preferred though, since we cannot
detect the raw key code for the former variant. Personally, I also
prefer the latter one because it is more explicit; it’s more obvious
which keys are involved.
However, in some cases it makes more sense to use the other
variant. Typically for non-letter combos.
2021-02-27 20:42:31 +01:00
|
|
|
|
const struct key_binding *bind = &it->item;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
input: rewrite of how we match foot’s own key bindings
Bindings are matched in one out of three ways:
* By translated (by XKB) symbols
* By untranslated symbols
* By raw key codes
A translated symbol is affected by pressed modifiers, some of which
can be “consumed”. Consumed modifiers to not partake in the comparison
with the binding’s modifiers. In this mode, ctrl+shift+2 maps to
ctrl+@ on a US layout.
Untranslated symbols, or un-shifted symbols refer to the “base” symbol
of the pressed key, i.e. it’s unaffected by modifiers. In this mode,
consumed modifiers *do* partake in the comparison with the binding’s
modifiers, and ctrl+shift+2 maps to ctrl+shift+2 on a US layout.
More examples: ctrl+shift+u maps to ctrl+U in the translated lookup,
while ctrl+shift+u maps to ctrl+shift+u in the untranslated lookup.
Finally, we also match raw key codes. This allows our bindings to work
using the same physical keys when the user switches between latin and
non-latin layouts.
This means key bindings in foot.ini *must* not include both +shift+
and a *shifted* key. I.e. ctrl+shift+U is not a valid combo as it
cannot be triggered. Unfortunately, this was how you were supposed to
write bindings up until now... so, we try to detect such bindings, log
a deprecation warning and then “fix” the binding for the user.
When specifying bindings in foot.ini, both ctrl+U and ctrl+shift+u are
valid, and will work. The latter is preferred though, since we cannot
detect the raw key code for the former variant. Personally, I also
prefer the latter one because it is more explicit; it’s more obvious
which keys are involved.
However, in some cases it makes more sense to use the other
variant. Typically for non-letter combos.
2021-02-27 20:42:31 +01:00
|
|
|
|
/* Match translated symbol */
|
|
|
|
|
|
if (bind->sym == sym &&
|
|
|
|
|
|
bind->mods == (mods & ~consumed))
|
|
|
|
|
|
{
|
|
|
|
|
|
execute_binding(seat, term, bind->action, serial);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
input: rewrite of how we match foot’s own key bindings
Bindings are matched in one out of three ways:
* By translated (by XKB) symbols
* By untranslated symbols
* By raw key codes
A translated symbol is affected by pressed modifiers, some of which
can be “consumed”. Consumed modifiers to not partake in the comparison
with the binding’s modifiers. In this mode, ctrl+shift+2 maps to
ctrl+@ on a US layout.
Untranslated symbols, or un-shifted symbols refer to the “base” symbol
of the pressed key, i.e. it’s unaffected by modifiers. In this mode,
consumed modifiers *do* partake in the comparison with the binding’s
modifiers, and ctrl+shift+2 maps to ctrl+shift+2 on a US layout.
More examples: ctrl+shift+u maps to ctrl+U in the translated lookup,
while ctrl+shift+u maps to ctrl+shift+u in the untranslated lookup.
Finally, we also match raw key codes. This allows our bindings to work
using the same physical keys when the user switches between latin and
non-latin layouts.
This means key bindings in foot.ini *must* not include both +shift+
and a *shifted* key. I.e. ctrl+shift+U is not a valid combo as it
cannot be triggered. Unfortunately, this was how you were supposed to
write bindings up until now... so, we try to detect such bindings, log
a deprecation warning and then “fix” the binding for the user.
When specifying bindings in foot.ini, both ctrl+U and ctrl+shift+u are
valid, and will work. The latter is preferred though, since we cannot
detect the raw key code for the former variant. Personally, I also
prefer the latter one because it is more explicit; it’s more obvious
which keys are involved.
However, in some cases it makes more sense to use the other
variant. Typically for non-letter combos.
2021-02-27 20:42:31 +01:00
|
|
|
|
if (bind->mods != mods)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < raw_count; i++) {
|
|
|
|
|
|
if (bind->sym == raw_syms[i]) {
|
|
|
|
|
|
execute_binding(seat, term, bind->action, serial);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-31 11:12:07 +01:00
|
|
|
|
/* Match raw key code */
|
input: rewrite of how we match foot’s own key bindings
Bindings are matched in one out of three ways:
* By translated (by XKB) symbols
* By untranslated symbols
* By raw key codes
A translated symbol is affected by pressed modifiers, some of which
can be “consumed”. Consumed modifiers to not partake in the comparison
with the binding’s modifiers. In this mode, ctrl+shift+2 maps to
ctrl+@ on a US layout.
Untranslated symbols, or un-shifted symbols refer to the “base” symbol
of the pressed key, i.e. it’s unaffected by modifiers. In this mode,
consumed modifiers *do* partake in the comparison with the binding’s
modifiers, and ctrl+shift+2 maps to ctrl+shift+2 on a US layout.
More examples: ctrl+shift+u maps to ctrl+U in the translated lookup,
while ctrl+shift+u maps to ctrl+shift+u in the untranslated lookup.
Finally, we also match raw key codes. This allows our bindings to work
using the same physical keys when the user switches between latin and
non-latin layouts.
This means key bindings in foot.ini *must* not include both +shift+
and a *shifted* key. I.e. ctrl+shift+U is not a valid combo as it
cannot be triggered. Unfortunately, this was how you were supposed to
write bindings up until now... so, we try to detect such bindings, log
a deprecation warning and then “fix” the binding for the user.
When specifying bindings in foot.ini, both ctrl+U and ctrl+shift+u are
valid, and will work. The latter is preferred though, since we cannot
detect the raw key code for the former variant. Personally, I also
prefer the latter one because it is more explicit; it’s more obvious
which keys are involved.
However, in some cases it makes more sense to use the other
variant. Typically for non-letter combos.
2021-02-27 20:42:31 +01:00
|
|
|
|
tll_foreach(bind->key_codes, code) {
|
2021-01-31 11:12:07 +01:00
|
|
|
|
if (code->item == key) {
|
input: rewrite of how we match foot’s own key bindings
Bindings are matched in one out of three ways:
* By translated (by XKB) symbols
* By untranslated symbols
* By raw key codes
A translated symbol is affected by pressed modifiers, some of which
can be “consumed”. Consumed modifiers to not partake in the comparison
with the binding’s modifiers. In this mode, ctrl+shift+2 maps to
ctrl+@ on a US layout.
Untranslated symbols, or un-shifted symbols refer to the “base” symbol
of the pressed key, i.e. it’s unaffected by modifiers. In this mode,
consumed modifiers *do* partake in the comparison with the binding’s
modifiers, and ctrl+shift+2 maps to ctrl+shift+2 on a US layout.
More examples: ctrl+shift+u maps to ctrl+U in the translated lookup,
while ctrl+shift+u maps to ctrl+shift+u in the untranslated lookup.
Finally, we also match raw key codes. This allows our bindings to work
using the same physical keys when the user switches between latin and
non-latin layouts.
This means key bindings in foot.ini *must* not include both +shift+
and a *shifted* key. I.e. ctrl+shift+U is not a valid combo as it
cannot be triggered. Unfortunately, this was how you were supposed to
write bindings up until now... so, we try to detect such bindings, log
a deprecation warning and then “fix” the binding for the user.
When specifying bindings in foot.ini, both ctrl+U and ctrl+shift+u are
valid, and will work. The latter is preferred though, since we cannot
detect the raw key code for the former variant. Personally, I also
prefer the latter one because it is more explicit; it’s more obvious
which keys are involved.
However, in some cases it makes more sense to use the other
variant. Typically for non-letter combos.
2021-02-27 20:42:31 +01:00
|
|
|
|
execute_binding(seat, term, bind->action, serial);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
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-03-04 09:36:25 +01:00
|
|
|
|
if (mods & ~consumed)
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-08-09 18:25:36 +02:00
|
|
|
|
static int
|
|
|
|
|
|
wccmp(const void *_a, const void *_b)
|
|
|
|
|
|
{
|
|
|
|
|
|
const wchar_t *a = _a;
|
|
|
|
|
|
const wchar_t *b = _b;
|
|
|
|
|
|
return *a - *b;
|
|
|
|
|
|
}
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2021-05-20 17:58:06 +02:00
|
|
|
|
const struct config *conf = term->conf;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
2021-08-09 18:25:36 +02:00
|
|
|
|
const wchar_t *uri_characters = conf->url.uri_characters;
|
|
|
|
|
|
if (uri_characters == NULL)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
const size_t uri_characters_count = wcslen(uri_characters);
|
|
|
|
|
|
if (uri_characters_count == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2021-05-20 17:58:06 +02:00
|
|
|
|
size_t max_prot_len = conf->url.max_prot_len;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
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-08-09 18:25:36 +02:00
|
|
|
|
ssize_t ltgts = 0;
|
2021-02-01 09:55:18 +01:00
|
|
|
|
|
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];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-06 13:06:54 +01:00
|
|
|
|
if (proto_char_count >= max_prot_len)
|
|
|
|
|
|
proto_char_count = max_prot_len - 1;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
2021-03-06 13:06:54 +01:00
|
|
|
|
proto_chars[max_prot_len - 1] = wc;
|
|
|
|
|
|
proto_start[max_prot_len - 1] = (struct coord){c, r};
|
2021-01-31 11:12:07 +01:00
|
|
|
|
proto_char_count++;
|
|
|
|
|
|
|
2021-05-20 17:58:06 +02:00
|
|
|
|
for (size_t i = 0; i < conf->url.prot_count; i++) {
|
|
|
|
|
|
size_t prot_len = wcslen(conf->url.protocols[i]);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
|
|
if (proto_char_count < prot_len)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
const wchar_t *proto = &proto_chars[max_prot_len - prot_len];
|
|
|
|
|
|
|
2021-05-20 17:58:06 +02:00
|
|
|
|
if (wcsncasecmp(conf->url.protocols[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
|
|
|
|
|
2021-08-09 18:25:36 +02:00
|
|
|
|
parenthesis = brackets = ltgts = 0;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case STATE_URL: {
|
2021-08-09 18:25:36 +02:00
|
|
|
|
const wchar_t *match = bsearch(
|
|
|
|
|
|
&wc,
|
|
|
|
|
|
uri_characters,
|
|
|
|
|
|
uri_characters_count,
|
|
|
|
|
|
sizeof(uri_characters[0]),
|
|
|
|
|
|
&wccmp);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
2021-02-01 09:55:18 +01:00
|
|
|
|
bool emit_url = false;
|
2021-08-09 18:25:36 +02:00
|
|
|
|
|
|
|
|
|
|
if (match == NULL) {
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Character is not a valid URI character. Emit
|
|
|
|
|
|
* the URL we’ve collected so far, *without*
|
|
|
|
|
|
* including _this_ character.
|
|
|
|
|
|
*/
|
|
|
|
|
|
emit_url = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
xassert(*match == wc);
|
|
|
|
|
|
|
|
|
|
|
|
switch (wc) {
|
|
|
|
|
|
default:
|
2021-02-04 21:08:49 +01:00
|
|
|
|
url[len++] = wc;
|
2021-08-09 18:25:36 +02:00
|
|
|
|
break;
|
2021-02-01 09:55:18 +01:00
|
|
|
|
|
2021-08-09 18:25:36 +02:00
|
|
|
|
case L'(':
|
|
|
|
|
|
parenthesis++;
|
2021-02-04 21:08:49 +01:00
|
|
|
|
url[len++] = wc;
|
2021-08-09 18:25:36 +02:00
|
|
|
|
break;
|
2021-02-01 09:55:18 +01:00
|
|
|
|
|
2021-08-09 18:25:36 +02:00
|
|
|
|
case L'[':
|
|
|
|
|
|
brackets++;
|
|
|
|
|
|
url[len++] = wc;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case L'<':
|
|
|
|
|
|
ltgts++;
|
|
|
|
|
|
url[len++] = wc;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case L')':
|
|
|
|
|
|
if (--parenthesis < 0)
|
|
|
|
|
|
emit_url = true;
|
|
|
|
|
|
else
|
|
|
|
|
|
url[len++] = wc;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case L']':
|
|
|
|
|
|
if (--brackets < 0)
|
|
|
|
|
|
emit_url = true;
|
|
|
|
|
|
else
|
|
|
|
|
|
url[len++] = wc;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case L'>':
|
|
|
|
|
|
if (--ltgts < 0)
|
|
|
|
|
|
emit_url = true;
|
|
|
|
|
|
else
|
|
|
|
|
|
url[len++] = wc;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-02-01 09:55:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-01 18:58:26 +02:00
|
|
|
|
if (c >= term->cols - 1 && row->linebreak) {
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Endpoint is inclusive, and we’ll be subtracting
|
|
|
|
|
|
* 1 from the column when emitting the URL.
|
|
|
|
|
|
*/
|
|
|
|
|
|
c++;
|
2021-02-01 10:40:15 +01:00
|
|
|
|
emit_url = true;
|
2021-06-01 18:58:26 +02:00
|
|
|
|
}
|
2021-02-01 10:40:15 +01:00
|
|
|
|
|
2021-02-01 09:55:18 +01:00
|
|
|
|
if (emit_url) {
|
2021-01-31 11:12:07 +01:00
|
|
|
|
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-06-01 18:58:26 +02:00
|
|
|
|
/* Heuristic to remove trailing characters that
|
|
|
|
|
|
* are valid URL characters, but typically not at
|
|
|
|
|
|
* the end of the URL */
|
|
|
|
|
|
bool done = false;
|
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,
|
2021-05-22 16:42:43 +02:00
|
|
|
|
.action = action,
|
|
|
|
|
|
.osc8 = false}));
|
2021-02-13 12:28:53 +01:00
|
|
|
|
}
|
2021-01-31 11:12:07 +01:00
|
|
|
|
|
|
|
|
|
|
state = STATE_PROTOCOL;
|
|
|
|
|
|
len = 0;
|
2021-08-09 18:25:36 +02:00
|
|
|
|
parenthesis = brackets = ltgts = 0;
|
2021-01-31 11:12:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2021-05-20 17:56:56 +02:00
|
|
|
|
switch (term->conf->url.osc8_underline) {
|
2021-02-14 21:29:22 +01:00
|
|
|
|
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,
|
2021-05-22 16:42:43 +02:00
|
|
|
|
.url_mode_dont_change_url_attr = dont_touch_url_attr,
|
|
|
|
|
|
.osc8 = true}));
|
2021-02-13 12:42:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-14 13:36:07 +01:00
|
|
|
|
static void
|
2021-05-22 16:42:43 +02:00
|
|
|
|
remove_overlapping(url_list_t *urls, int cols)
|
2021-02-14 13:36:07 +01:00
|
|
|
|
{
|
|
|
|
|
|
tll_foreach(*urls, outer) {
|
|
|
|
|
|
tll_foreach(*urls, inner) {
|
|
|
|
|
|
if (outer == inner)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2021-05-22 16:42:43 +02:00
|
|
|
|
const struct url *out = &outer->item;
|
|
|
|
|
|
const struct url *in = &inner->item;
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t in_start = in->start.row * cols + in->start.col;
|
|
|
|
|
|
uint64_t in_end = in->end.row * cols + in->end.col;
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t out_start = out->start.row * cols + out->start.col;
|
|
|
|
|
|
uint64_t out_end = out->end.row * cols + out->end.col;
|
|
|
|
|
|
|
|
|
|
|
|
if ((in_start <= out_start && in_end >= out_start) ||
|
|
|
|
|
|
(in_start <= out_end && in_end >= out_end) ||
|
|
|
|
|
|
(in_start >= out_start && in_end <= out_end))
|
2021-02-14 13:36:07 +01:00
|
|
|
|
{
|
2021-05-22 16:42:43 +02:00
|
|
|
|
/*
|
|
|
|
|
|
* OSC-8 URLs can’t overlap with each
|
|
|
|
|
|
* other.
|
|
|
|
|
|
*
|
2021-05-22 17:08:14 +02:00
|
|
|
|
* Similarly, auto-detected URLs cannot overlap with
|
2021-05-22 16:42:43 +02:00
|
|
|
|
* each other.
|
|
|
|
|
|
*
|
|
|
|
|
|
* But OSC-8 URLs can overlap with auto-detected ones.
|
|
|
|
|
|
*/
|
|
|
|
|
|
xassert(in->osc8 || out->osc8);
|
|
|
|
|
|
|
2021-07-11 10:06:12 +02:00
|
|
|
|
if (in->osc8)
|
|
|
|
|
|
outer->item.duplicate = true;
|
|
|
|
|
|
else
|
|
|
|
|
|
inner->item.duplicate = true;
|
2021-02-14 13:36:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-07-11 10:06:12 +02:00
|
|
|
|
|
|
|
|
|
|
tll_foreach(*urls, it) {
|
|
|
|
|
|
if (it->item.duplicate) {
|
|
|
|
|
|
url_destroy(&it->item);
|
|
|
|
|
|
tll_remove(*urls, it);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-02-14 13:36:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
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);
|
2021-05-22 16:42:43 +02:00
|
|
|
|
remove_overlapping(urls, term->grid->num_cols);
|
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-05-20 17:56:56 +02:00
|
|
|
|
const wchar_t *alphabet = conf->url.label_letters;
|
2021-02-13 11:43:28 +01:00
|
|
|
|
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];
|
url-mode: abort when running into un-allocated scrollback memory
When tagging URL cells (in preparation for rendering URL mode), we
loop the URL’s entire range, setting the `url` attribute of all cells,
and dirtying the rows.
It is possible to create URLs that are invalid, and wrap around the
scrollback, even though the scrollback hasn’t yet been filled. For
example, by starting an OSC-8 URL, moving the cursor, and then closing
the OSC-8 URL.
These URLs are invalid, but are still rendered just fine. “Fine” being
relative - they will typically fill the entire screen. But at least
that’s a very clear indication for the user that’s something is wrong.
The problem is when we hit un-allocated scrollback rows. We didn’t
check for NULL rows, and crashed.
This has now been fixed.
2021-07-11 11:31:11 +02:00
|
|
|
|
if (row == NULL) {
|
|
|
|
|
|
/* Un-allocated scrollback. This most likely means a
|
|
|
|
|
|
* runaway OSC-8 URL. */
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-02-06 20:01:52 +01:00
|
|
|
|
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-23 09:24:50 +01:00
|
|
|
|
/* Dirty the last cursor, to ensure it is erased */
|
|
|
|
|
|
{
|
|
|
|
|
|
struct row *cursor_row = term->render.last_cursor.row;
|
|
|
|
|
|
if (cursor_row != NULL) {
|
|
|
|
|
|
struct cell *cell = &cursor_row->cells[term->render.last_cursor.col];
|
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
|
cursor_row->dirty = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
term->render.last_cursor.row = NULL;
|
|
|
|
|
|
|
2021-02-24 21:39:29 +01:00
|
|
|
|
/* Clear scroll damage, to ensure we don’t apply it twice (once on
|
|
|
|
|
|
* the snapshot:ed grid, and then later again on the real grid) */
|
|
|
|
|
|
tll_free(term->grid->scroll_damage);
|
|
|
|
|
|
|
|
|
|
|
|
/* Damage the entire view, to ensure a full screen redraw, both
|
|
|
|
|
|
* now, when entering URL mode, and later, when exiting it. */
|
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
|
|
url-mode: snapshot screen state when entering URL mode
Previously, we automatically exited URL mode whenever we received data
on the PTY. This was done since we don’t know _what_ has changed on
the screen, and we don’t want to display misleading jump labels.
However, this becomes a problem in curses-like applications that
periodically updates part of the screen. For example, a statusbar with
a clock.
This patch changes this behavior; instead of cancelling URL mode when
receiving PTY data, we snapshot the grid when entering URL mode.
When *rendering*, we use the snapshot:ed grid, while PTY updates
modify the “real” grid.
Snapshot:ing the grid means taking a full/deep copy of the current
grid, including sixel images etc.
Finally, it isn’t necessary to “damage” the entire view
when *entering* URL mode, since we’re at that point the renderer is in
sync with the grid. But we *do* need to damage the entire view when
exiting URL mode, since the grid changes on the “real” grid hasn’t
been tracked by the renderer.
2021-02-22 10:22:41 +01:00
|
|
|
|
/* Snapshot the current grid */
|
|
|
|
|
|
term->url_grid_snapshot = grid_snapshot(term->grid);
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
url-mode: snapshot screen state when entering URL mode
Previously, we automatically exited URL mode whenever we received data
on the PTY. This was done since we don’t know _what_ has changed on
the screen, and we don’t want to display misleading jump labels.
However, this becomes a problem in curses-like applications that
periodically updates part of the screen. For example, a statusbar with
a clock.
This patch changes this behavior; instead of cancelling URL mode when
receiving PTY data, we snapshot the grid when entering URL mode.
When *rendering*, we use the snapshot:ed grid, while PTY updates
modify the “real” grid.
Snapshot:ing the grid means taking a full/deep copy of the current
grid, including sixel images etc.
Finally, it isn’t necessary to “damage” the entire view
when *entering* URL mode, since we’re at that point the renderer is in
sync with the grid. But we *do* need to damage the entire view when
exiting URL mode, since the grid changes on the “real” grid hasn’t
been tracked by the renderer.
2021-02-22 10:22:41 +01:00
|
|
|
|
if (likely(tll_length(term->urls) == 0)) {
|
|
|
|
|
|
xassert(term->url_grid_snapshot == NULL);
|
2021-01-31 11:39:23 +01:00
|
|
|
|
return;
|
url-mode: snapshot screen state when entering URL mode
Previously, we automatically exited URL mode whenever we received data
on the PTY. This was done since we don’t know _what_ has changed on
the screen, and we don’t want to display misleading jump labels.
However, this becomes a problem in curses-like applications that
periodically updates part of the screen. For example, a statusbar with
a clock.
This patch changes this behavior; instead of cancelling URL mode when
receiving PTY data, we snapshot the grid when entering URL mode.
When *rendering*, we use the snapshot:ed grid, while PTY updates
modify the “real” grid.
Snapshot:ing the grid means taking a full/deep copy of the current
grid, including sixel images etc.
Finally, it isn’t necessary to “damage” the entire view
when *entering* URL mode, since we’re at that point the renderer is in
sync with the grid. But we *do* need to damage the entire view when
exiting URL mode, since the grid changes on the “real” grid hasn’t
been tracked by the renderer.
2021-02-22 10:22:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
grid_free(term->url_grid_snapshot);
|
|
|
|
|
|
free(term->url_grid_snapshot);
|
|
|
|
|
|
term->url_grid_snapshot = NULL;
|
2021-02-23 09:24:50 +01:00
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Make sure “last cursor” doesn’t point to a row in the just
|
|
|
|
|
|
* free:d snapshot grid.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Note that it will still be erased properly (if hasn’t already),
|
|
|
|
|
|
* since we marked the cell as dirty *before* taking the grid
|
|
|
|
|
|
* snapshot.
|
|
|
|
|
|
*/
|
url-mode: snapshot screen state when entering URL mode
Previously, we automatically exited URL mode whenever we received data
on the PTY. This was done since we don’t know _what_ has changed on
the screen, and we don’t want to display misleading jump labels.
However, this becomes a problem in curses-like applications that
periodically updates part of the screen. For example, a statusbar with
a clock.
This patch changes this behavior; instead of cancelling URL mode when
receiving PTY data, we snapshot the grid when entering URL mode.
When *rendering*, we use the snapshot:ed grid, while PTY updates
modify the “real” grid.
Snapshot:ing the grid means taking a full/deep copy of the current
grid, including sixel images etc.
Finally, it isn’t necessary to “damage” the entire view
when *entering* URL mode, since we’re at that point the renderer is in
sync with the grid. But we *do* need to damage the entire view when
exiting URL mode, since the grid changes on the “real” grid hasn’t
been tracked by the renderer.
2021-02-22 10:22:41 +01:00
|
|
|
|
term->render.last_cursor.row = NULL;
|
2021-01-31 11:39:23 +01:00
|
|
|
|
|
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));
|
url-mode: snapshot screen state when entering URL mode
Previously, we automatically exited URL mode whenever we received data
on the PTY. This was done since we don’t know _what_ has changed on
the screen, and we don’t want to display misleading jump labels.
However, this becomes a problem in curses-like applications that
periodically updates part of the screen. For example, a statusbar with
a clock.
This patch changes this behavior; instead of cancelling URL mode when
receiving PTY data, we snapshot the grid when entering URL mode.
When *rendering*, we use the snapshot:ed grid, while PTY updates
modify the “real” grid.
Snapshot:ing the grid means taking a full/deep copy of the current
grid, including sixel images etc.
Finally, it isn’t necessary to “damage” the entire view
when *entering* URL mode, since we’re at that point the renderer is in
sync with the grid. But we *do* need to damage the entire view when
exiting URL mode, since the grid changes on the “real” grid hasn’t
been tracked by the renderer.
2021-02-22 10:22:41 +01:00
|
|
|
|
|
2021-02-06 11:51:58 +01:00
|
|
|
|
render_refresh(term);
|
2021-01-31 11:12:07 +01:00
|
|
|
|
}
|