mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-03-17 05:33:52 -04:00
Merge branch 'url-detection'
This commit is contained in:
commit
63a50afc8e
23 changed files with 1340 additions and 138 deletions
|
|
@ -41,6 +41,10 @@
|
||||||
copied to. The default is `primary`, which corresponds to the
|
copied to. The default is `primary`, which corresponds to the
|
||||||
behavior in older foot releases
|
behavior in older foot releases
|
||||||
(https://codeberg.org/dnkl/foot/issues/288).
|
(https://codeberg.org/dnkl/foot/issues/288).
|
||||||
|
* URL detection. URLs are highlighted and activated using the keyboard
|
||||||
|
(**no** mouse support). See **foot**(1)::URLs, or
|
||||||
|
[README.md](README.md#urls) for details
|
||||||
|
(https://codeberg.org/dnkl/foot/issues/14).
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
||||||
34
README.md
34
README.md
|
|
@ -22,6 +22,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
|
||||||
1. [Scrollback search](#scrollback-search)
|
1. [Scrollback search](#scrollback-search)
|
||||||
1. [Mouse](#mouse)
|
1. [Mouse](#mouse)
|
||||||
1. [Server (daemon) mode](#server-daemon-mode)
|
1. [Server (daemon) mode](#server-daemon-mode)
|
||||||
|
1. [URLs](#urls)
|
||||||
1. [Alt/meta](#alt-meta)
|
1. [Alt/meta](#alt-meta)
|
||||||
1. [Backspace](#backspace)
|
1. [Backspace](#backspace)
|
||||||
1. [Keypad](#keypad)
|
1. [Keypad](#keypad)
|
||||||
|
|
@ -43,12 +44,13 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
|
||||||
* Lightweight, in dependencies, on-disk and in-memory
|
* Lightweight, in dependencies, on-disk and in-memory
|
||||||
* Wayland native
|
* Wayland native
|
||||||
* DE agnostic
|
* DE agnostic
|
||||||
|
* Server/daemon mode
|
||||||
* User configurable font fallback
|
* User configurable font fallback
|
||||||
* On-the-fly font resize
|
* On-the-fly font resize
|
||||||
* On-the-fly DPI font size adjustment
|
* On-the-fly DPI font size adjustment
|
||||||
* Scrollback search
|
* Scrollback search
|
||||||
|
* Keyboard driven URL detection
|
||||||
* Color emoji support
|
* Color emoji support
|
||||||
* Server/daemon mode
|
|
||||||
* IME (via `text-input-v3`)
|
* IME (via `text-input-v3`)
|
||||||
* Multi-seat
|
* Multi-seat
|
||||||
* [Synchronized Updates](https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/2) support
|
* [Synchronized Updates](https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/2) support
|
||||||
|
|
@ -154,6 +156,10 @@ These are the default shortcuts. See `man foot.ini` and the example
|
||||||
sequence](https://codeberg.org/dnkl/foot/wiki#user-content-how-to-configure-my-shell-to-emit-the-osc-7-escape-sequence),
|
sequence](https://codeberg.org/dnkl/foot/wiki#user-content-how-to-configure-my-shell-to-emit-the-osc-7-escape-sequence),
|
||||||
the new terminal will start in the current working directory.
|
the new terminal will start in the current working directory.
|
||||||
|
|
||||||
|
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>
|
||||||
|
: Enter URL mode, where all currently visible URLs are tagged with a
|
||||||
|
jump label with a key sequence that will open the URL.
|
||||||
|
|
||||||
|
|
||||||
#### Scrollback search
|
#### Scrollback search
|
||||||
|
|
||||||
|
|
@ -247,6 +253,32 @@ desktop), and then run `footclient` instead of `foot` whenever you
|
||||||
want to launch a new terminal.
|
want to launch a new terminal.
|
||||||
|
|
||||||
|
|
||||||
|
## URLs
|
||||||
|
|
||||||
|
Foot supports URL detection. But, unlike many other terminal
|
||||||
|
emulators, where URLs are highlighted when they are hovered and opened
|
||||||
|
by clicking on them, foot uses a keyboard driven approach.
|
||||||
|
|
||||||
|
Pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd> enters _“URL
|
||||||
|
mode”_, where all currently visible URLs are underlined, and is
|
||||||
|
associated with a _“jump-label”_. The jump-label indicates the _key
|
||||||
|
sequence_ (e.g. **”AF”**) to use to activate the URL.
|
||||||
|
|
||||||
|
The key binding can, of course, be customized, like all other key
|
||||||
|
bindings in foot. See `show-urls-launch` and `show-urls-copy` in the
|
||||||
|
`foot.ini` man page.
|
||||||
|
|
||||||
|
`show-urls-launch` by default opens the URL with `xdg-open`. This can
|
||||||
|
be changed with the `url-launch` option.
|
||||||
|
|
||||||
|
`show-urls-copy` is an alternative to `show-urls-launch`, that changes
|
||||||
|
what activating an URL _does_; instead of opening it, it copies it to
|
||||||
|
the clipboard. It is unbound by default.
|
||||||
|
|
||||||
|
Both the jump label colors, and the URL underline color can be
|
||||||
|
configured, independently.
|
||||||
|
|
||||||
|
|
||||||
## Alt/meta
|
## Alt/meta
|
||||||
|
|
||||||
By default, foot prefixes _Meta characters_ with ESC. This corresponds
|
By default, foot prefixes _Meta characters_ with ESC. This corresponds
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ cmd_scrollback_up(struct terminal *term, int rows)
|
||||||
} else
|
} else
|
||||||
term_damage_view(term);
|
term_damage_view(term);
|
||||||
|
|
||||||
|
render_refresh_urls(term);
|
||||||
render_refresh(term);
|
render_refresh(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,5 +158,6 @@ cmd_scrollback_down(struct terminal *term, int rows)
|
||||||
} else
|
} else
|
||||||
term_damage_view(term);
|
term_damage_view(term);
|
||||||
|
|
||||||
|
render_refresh_urls(term);
|
||||||
render_refresh(term);
|
render_refresh(term);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
263
config.c
263
config.c
|
|
@ -75,6 +75,8 @@ static const char *const binding_action_map[] = {
|
||||||
[BIND_ACTION_PIPE_SCROLLBACK] = "pipe-scrollback",
|
[BIND_ACTION_PIPE_SCROLLBACK] = "pipe-scrollback",
|
||||||
[BIND_ACTION_PIPE_VIEW] = "pipe-visible",
|
[BIND_ACTION_PIPE_VIEW] = "pipe-visible",
|
||||||
[BIND_ACTION_PIPE_SELECTED] = "pipe-selected",
|
[BIND_ACTION_PIPE_SELECTED] = "pipe-selected",
|
||||||
|
[BIND_ACTION_SHOW_URLS_COPY] = "show-urls-copy",
|
||||||
|
[BIND_ACTION_SHOW_URLS_LAUNCH] = "show-urls-launch",
|
||||||
|
|
||||||
/* Mouse-specific actions */
|
/* Mouse-specific actions */
|
||||||
[BIND_ACTION_SELECT_BEGIN] = "select-begin",
|
[BIND_ACTION_SELECT_BEGIN] = "select-begin",
|
||||||
|
|
@ -114,6 +116,14 @@ static const char *const search_binding_action_map[] = {
|
||||||
static_assert(ALEN(search_binding_action_map) == BIND_ACTION_SEARCH_COUNT,
|
static_assert(ALEN(search_binding_action_map) == BIND_ACTION_SEARCH_COUNT,
|
||||||
"search binding action map size mismatch");
|
"search binding action map size mismatch");
|
||||||
|
|
||||||
|
static const char *const url_binding_action_map[] = {
|
||||||
|
[BIND_ACTION_URL_NONE] = NULL,
|
||||||
|
[BIND_ACTION_URL_CANCEL] = "cancel",
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(ALEN(url_binding_action_map) == BIND_ACTION_URL_COUNT,
|
||||||
|
"URL binding action map size mismatch");
|
||||||
|
|
||||||
#define LOG_AND_NOTIFY_ERR(...) \
|
#define LOG_AND_NOTIFY_ERR(...) \
|
||||||
do { \
|
do { \
|
||||||
LOG_ERR(__VA_ARGS__); \
|
LOG_ERR(__VA_ARGS__); \
|
||||||
|
|
@ -410,6 +420,30 @@ str_to_color(const char *s, uint32_t *color, bool allow_alpha,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
str_to_two_colors(const char *s, uint32_t *first, uint32_t *second,
|
||||||
|
bool allow_alpha, struct config *conf, const char *path,
|
||||||
|
int lineno, const char *section, const char *key)
|
||||||
|
{
|
||||||
|
/* TODO: do this without strdup() */
|
||||||
|
char *value_copy = xstrdup(s);
|
||||||
|
const char *first_as_str = strtok(value_copy, " ");
|
||||||
|
const char *second_as_str = strtok(NULL, " ");
|
||||||
|
|
||||||
|
if (first_as_str == NULL || second_as_str == NULL ||
|
||||||
|
!str_to_color(first_as_str, first, allow_alpha, conf, path, lineno, section, key) ||
|
||||||
|
!str_to_color(second_as_str, second, allow_alpha, conf, path, lineno, section, key))
|
||||||
|
{
|
||||||
|
LOG_AND_NOTIFY_ERR("%s:%d: [%s]: %s: invalid colors: %s",
|
||||||
|
path, lineno, section, key, s);
|
||||||
|
free(value_copy);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(value_copy);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
str_to_pt_or_px(const char *s, struct pt_or_px *res, struct config *conf,
|
str_to_pt_or_px(const char *s, struct pt_or_px *res, struct config *conf,
|
||||||
const char *path, int lineno, const char *section, const char *key)
|
const char *path, int lineno, const char *section, const char *key)
|
||||||
|
|
@ -444,6 +478,33 @@ str_to_pt_or_px(const char *s, struct pt_or_px *res, struct config *conf,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
str_to_spawn_template(struct config *conf,
|
||||||
|
const char *s, struct config_spawn_template *template,
|
||||||
|
const char *path, int lineno, const char *section,
|
||||||
|
const char *key)
|
||||||
|
{
|
||||||
|
free(template->raw_cmd);
|
||||||
|
free(template->argv);
|
||||||
|
|
||||||
|
template->raw_cmd = NULL;
|
||||||
|
template->argv = NULL;
|
||||||
|
|
||||||
|
char *raw_cmd = xstrdup(s);
|
||||||
|
char **argv = NULL;
|
||||||
|
|
||||||
|
if (!tokenize_cmdline(raw_cmd, &argv)) {
|
||||||
|
LOG_AND_NOTIFY_ERR(
|
||||||
|
"%s:%d: [%s]: %s: syntax error in command line",
|
||||||
|
path, lineno, section, key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template->raw_cmd = raw_cmd;
|
||||||
|
template->argv = argv;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_section_main(const char *key, const char *value, struct config *conf,
|
parse_section_main(const char *key, const char *value, struct config *conf,
|
||||||
const char *path, unsigned lineno)
|
const char *path, unsigned lineno)
|
||||||
|
|
@ -666,24 +727,19 @@ parse_section_main(const char *key, const char *value, struct config *conf,
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (strcmp(key, "notify") == 0) {
|
else if (strcmp(key, "notify") == 0) {
|
||||||
free(conf->notify.raw_cmd);
|
if (!str_to_spawn_template(conf, value, &conf->notify, path, lineno,
|
||||||
free(conf->notify.argv);
|
"default", "notify"))
|
||||||
|
{
|
||||||
conf->notify.raw_cmd = NULL;
|
|
||||||
conf->notify.argv = NULL;
|
|
||||||
|
|
||||||
char *raw_cmd = xstrdup(value);
|
|
||||||
char **argv = NULL;
|
|
||||||
|
|
||||||
if (!tokenize_cmdline(raw_cmd, &argv)) {
|
|
||||||
LOG_AND_NOTIFY_ERR(
|
|
||||||
"%s:%d: [default]: notify: syntax error in command line",
|
|
||||||
path, lineno);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
conf->notify.raw_cmd = raw_cmd;
|
else if (strcmp(key, "url-launch") == 0) {
|
||||||
conf->notify.argv = argv;
|
if (!str_to_spawn_template(conf, value, &conf->url_launch, path, lineno,
|
||||||
|
"default", "url-launch"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (strcmp(key, "selection-target") == 0) {
|
else if (strcmp(key, "selection-target") == 0) {
|
||||||
|
|
@ -813,6 +869,30 @@ parse_section_colors(const char *key, const char *value, struct config *conf,
|
||||||
else if (strcmp(key, "bright7") == 0) color = &conf->colors.bright[7];
|
else if (strcmp(key, "bright7") == 0) color = &conf->colors.bright[7];
|
||||||
else if (strcmp(key, "selection-foreground") == 0) color = &conf->colors.selection_fg;
|
else if (strcmp(key, "selection-foreground") == 0) color = &conf->colors.selection_fg;
|
||||||
else if (strcmp(key, "selection-background") == 0) color = &conf->colors.selection_bg;
|
else if (strcmp(key, "selection-background") == 0) color = &conf->colors.selection_bg;
|
||||||
|
|
||||||
|
else if (strcmp(key, "jump-labels") == 0) {
|
||||||
|
if (!str_to_two_colors(
|
||||||
|
value, &conf->colors.jump_label.fg, &conf->colors.jump_label.bg,
|
||||||
|
false, conf, path, lineno, "colors", "jump-labels"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
conf->colors.use_custom.jump_label = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (strcmp(key, "urls") == 0) {
|
||||||
|
if (!str_to_color(value, &conf->colors.url, false,
|
||||||
|
conf, path, lineno, "colors", "urls"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
conf->colors.use_custom.url = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
else if (strcmp(key, "alpha") == 0) {
|
else if (strcmp(key, "alpha") == 0) {
|
||||||
double alpha;
|
double alpha;
|
||||||
if (!str_to_double(value, &alpha) || alpha < 0. || alpha > 1.) {
|
if (!str_to_double(value, &alpha) || alpha < 0. || alpha > 1.) {
|
||||||
|
|
@ -860,23 +940,15 @@ parse_section_cursor(const char *key, const char *value, struct config *conf,
|
||||||
conf->cursor.blink = str_to_bool(value);
|
conf->cursor.blink = str_to_bool(value);
|
||||||
|
|
||||||
else if (strcmp(key, "color") == 0) {
|
else if (strcmp(key, "color") == 0) {
|
||||||
char *value_copy = xstrdup(value);
|
if (!str_to_two_colors(
|
||||||
const char *text = strtok(value_copy, " ");
|
value, &conf->cursor.color.text, &conf->cursor.color.cursor,
|
||||||
const char *cursor = strtok(NULL, " ");
|
false, conf, path, lineno, "cursor", "color"))
|
||||||
|
|
||||||
uint32_t text_color, cursor_color;
|
|
||||||
if (text == NULL || cursor == NULL ||
|
|
||||||
!str_to_color(text, &text_color, false, conf, path, lineno, "cursor", "color") ||
|
|
||||||
!str_to_color(cursor, &cursor_color, false, conf, path, lineno, "cursor", "color"))
|
|
||||||
{
|
{
|
||||||
LOG_AND_NOTIFY_ERR("%s:%d: invalid cursor colors: %s", path, lineno, value);
|
|
||||||
free(value_copy);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
conf->cursor.color.text = 1u << 31 | text_color;
|
conf->cursor.color.text |= 1u << 31;
|
||||||
conf->cursor.color.cursor = 1u << 31 | cursor_color;
|
conf->cursor.color.cursor |= 1u << 31;
|
||||||
free(value_copy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
|
@ -1168,6 +1240,37 @@ has_search_binding_collisions(struct config *conf, enum bind_action_search actio
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
has_url_binding_collisions(struct config *conf, enum bind_action_url action,
|
||||||
|
const key_combo_list_t *key_combos,
|
||||||
|
const char *path, unsigned lineno)
|
||||||
|
{
|
||||||
|
tll_foreach(conf->bindings.url, it) {
|
||||||
|
if (it->item.action == action)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tll_foreach(*key_combos, it2) {
|
||||||
|
const struct config_key_modifiers *mods1 = &it->item.modifiers;
|
||||||
|
const struct config_key_modifiers *mods2 = &it2->item.modifiers;
|
||||||
|
|
||||||
|
bool shift = mods1->shift == mods2->shift;
|
||||||
|
bool alt = mods1->alt == mods2->alt;
|
||||||
|
bool ctrl = mods1->ctrl == mods2->ctrl;
|
||||||
|
bool meta = mods1->meta == mods2->meta;
|
||||||
|
bool sym = it->item.sym == it2->item.sym;
|
||||||
|
|
||||||
|
if (shift && alt && ctrl && meta && sym) {
|
||||||
|
LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s'",
|
||||||
|
path, lineno, it2->item.text,
|
||||||
|
url_binding_action_map[it->item.action]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
argv_compare(char *const *argv1, char *const *argv2)
|
argv_compare(char *const *argv1, char *const *argv2)
|
||||||
{
|
{
|
||||||
|
|
@ -1403,6 +1506,63 @@ parse_section_search_bindings(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_section_url_bindings(
|
||||||
|
const char *key, const char *value, struct config *conf,
|
||||||
|
const char *path, unsigned lineno)
|
||||||
|
{
|
||||||
|
for (enum bind_action_url action = 0;
|
||||||
|
action < BIND_ACTION_URL_COUNT;
|
||||||
|
action++)
|
||||||
|
{
|
||||||
|
if (url_binding_action_map[action] == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strcmp(key, url_binding_action_map[action]) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Unset binding */
|
||||||
|
if (strcasecmp(value, "none") == 0) {
|
||||||
|
tll_foreach(conf->bindings.url, it) {
|
||||||
|
if (it->item.action == action)
|
||||||
|
tll_remove(conf->bindings.url, it);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_combo_list_t key_combos = tll_init();
|
||||||
|
if (!parse_key_combos(conf, value, &key_combos, path, lineno) ||
|
||||||
|
has_url_binding_collisions(conf, action, &key_combos, path, lineno))
|
||||||
|
{
|
||||||
|
free_key_combo_list(&key_combos);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove existing bindings for this action */
|
||||||
|
tll_foreach(conf->bindings.url, it) {
|
||||||
|
if (it->item.action == action)
|
||||||
|
tll_remove(conf->bindings.url, it);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Emit key bindings */
|
||||||
|
tll_foreach(key_combos, it) {
|
||||||
|
struct config_key_binding_url binding = {
|
||||||
|
.action = action,
|
||||||
|
.modifiers = it->item.modifiers,
|
||||||
|
.sym = it->item.sym,
|
||||||
|
};
|
||||||
|
tll_push_back(conf->bindings.url, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
free_key_combo_list(&key_combos);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_AND_NOTIFY_ERR("%s:%u: [url-bindings]: %s: invalid key", path, lineno, key);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_mouse_combos(struct config *conf, const char *combos, key_combo_list_t *key_combos,
|
parse_mouse_combos(struct config *conf, const char *combos, key_combo_list_t *key_combos,
|
||||||
const char *path, unsigned lineno)
|
const char *path, unsigned lineno)
|
||||||
|
|
@ -1778,6 +1938,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar
|
||||||
SECTION_CSD,
|
SECTION_CSD,
|
||||||
SECTION_KEY_BINDINGS,
|
SECTION_KEY_BINDINGS,
|
||||||
SECTION_SEARCH_BINDINGS,
|
SECTION_SEARCH_BINDINGS,
|
||||||
|
SECTION_URL_BINDINGS,
|
||||||
SECTION_MOUSE_BINDINGS,
|
SECTION_MOUSE_BINDINGS,
|
||||||
SECTION_TWEAK,
|
SECTION_TWEAK,
|
||||||
SECTION_COUNT,
|
SECTION_COUNT,
|
||||||
|
|
@ -1800,6 +1961,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar
|
||||||
[SECTION_CSD] = {&parse_section_csd, "csd"},
|
[SECTION_CSD] = {&parse_section_csd, "csd"},
|
||||||
[SECTION_KEY_BINDINGS] = {&parse_section_key_bindings, "key-bindings"},
|
[SECTION_KEY_BINDINGS] = {&parse_section_key_bindings, "key-bindings"},
|
||||||
[SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"},
|
[SECTION_SEARCH_BINDINGS] = {&parse_section_search_bindings, "search-bindings"},
|
||||||
|
[SECTION_URL_BINDINGS] = {&parse_section_url_bindings, "url-bindings"},
|
||||||
[SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"},
|
[SECTION_MOUSE_BINDINGS] = {&parse_section_mouse_bindings, "mouse-bindings"},
|
||||||
[SECTION_TWEAK] = {&parse_section_tweak, "tweak"},
|
[SECTION_TWEAK] = {&parse_section_tweak, "tweak"},
|
||||||
};
|
};
|
||||||
|
|
@ -1993,6 +2155,7 @@ add_default_key_bindings(struct config *conf)
|
||||||
add_binding(BIND_ACTION_FONT_SIZE_RESET, ctrl, XKB_KEY_0);
|
add_binding(BIND_ACTION_FONT_SIZE_RESET, ctrl, XKB_KEY_0);
|
||||||
add_binding(BIND_ACTION_FONT_SIZE_RESET, ctrl, XKB_KEY_KP_0);
|
add_binding(BIND_ACTION_FONT_SIZE_RESET, ctrl, XKB_KEY_KP_0);
|
||||||
add_binding(BIND_ACTION_SPAWN_TERMINAL, ctrl_shift, XKB_KEY_N);
|
add_binding(BIND_ACTION_SPAWN_TERMINAL, ctrl_shift, XKB_KEY_N);
|
||||||
|
add_binding(BIND_ACTION_SHOW_URLS_LAUNCH, ctrl_shift, XKB_KEY_U);
|
||||||
|
|
||||||
#undef add_binding
|
#undef add_binding
|
||||||
}
|
}
|
||||||
|
|
@ -2045,6 +2208,26 @@ add_default_search_bindings(struct config *conf)
|
||||||
#undef add_binding
|
#undef add_binding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_default_url_bindings(struct config *conf)
|
||||||
|
{
|
||||||
|
#define add_binding(action, mods, sym) \
|
||||||
|
do { \
|
||||||
|
tll_push_back( \
|
||||||
|
conf->bindings.url, \
|
||||||
|
((struct config_key_binding_url){action, mods, sym})); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
const struct config_key_modifiers none = {0};
|
||||||
|
const struct config_key_modifiers ctrl = {.ctrl = true};
|
||||||
|
|
||||||
|
add_binding(BIND_ACTION_URL_CANCEL, ctrl, XKB_KEY_g);
|
||||||
|
add_binding(BIND_ACTION_URL_CANCEL, ctrl, XKB_KEY_d);
|
||||||
|
add_binding(BIND_ACTION_URL_CANCEL, none, XKB_KEY_Escape);
|
||||||
|
|
||||||
|
#undef add_binding
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
add_default_mouse_bindings(struct config *conf)
|
add_default_mouse_bindings(struct config *conf)
|
||||||
{
|
{
|
||||||
|
|
@ -2138,7 +2321,11 @@ config_load(struct config *conf, const char *conf_path,
|
||||||
.alpha = 0xffff,
|
.alpha = 0xffff,
|
||||||
.selection_fg = 0x80000000, /* Use default bg */
|
.selection_fg = 0x80000000, /* Use default bg */
|
||||||
.selection_bg = 0x80000000, /* Use default fg */
|
.selection_bg = 0x80000000, /* Use default fg */
|
||||||
.selection_uses_custom_colors = false,
|
.use_custom = {
|
||||||
|
.selection = false,
|
||||||
|
.jump_label = false,
|
||||||
|
.url = false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
.cursor = {
|
.cursor = {
|
||||||
|
|
@ -2189,12 +2376,16 @@ config_load(struct config *conf, const char *conf_path,
|
||||||
"notify-send -a foot -i foot ${title} ${body}");
|
"notify-send -a foot -i foot ${title} ${body}");
|
||||||
tokenize_cmdline(conf->notify.raw_cmd, &conf->notify.argv);
|
tokenize_cmdline(conf->notify.raw_cmd, &conf->notify.argv);
|
||||||
|
|
||||||
|
conf->url_launch.raw_cmd = xstrdup("xdg-open ${url}");
|
||||||
|
tokenize_cmdline(conf->url_launch.raw_cmd, &conf->url_launch.argv);
|
||||||
|
|
||||||
tll_foreach(*initial_user_notifications, it)
|
tll_foreach(*initial_user_notifications, it)
|
||||||
tll_push_back(conf->notifications, it->item);
|
tll_push_back(conf->notifications, it->item);
|
||||||
tll_free(*initial_user_notifications);
|
tll_free(*initial_user_notifications);
|
||||||
|
|
||||||
add_default_key_bindings(conf);
|
add_default_key_bindings(conf);
|
||||||
add_default_search_bindings(conf);
|
add_default_search_bindings(conf);
|
||||||
|
add_default_url_bindings(conf);
|
||||||
add_default_mouse_bindings(conf);
|
add_default_mouse_bindings(conf);
|
||||||
|
|
||||||
struct config_file conf_file = {.path = NULL, .fd = -1};
|
struct config_file conf_file = {.path = NULL, .fd = -1};
|
||||||
|
|
@ -2231,7 +2422,7 @@ config_load(struct config *conf, const char *conf_path,
|
||||||
ret = parse_config_file(f, conf, conf_file.path, errors_are_fatal);
|
ret = parse_config_file(f, conf, conf_file.path, errors_are_fatal);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
conf->colors.selection_uses_custom_colors =
|
conf->colors.use_custom.selection =
|
||||||
conf->colors.selection_fg >> 24 == 0 &&
|
conf->colors.selection_fg >> 24 == 0 &&
|
||||||
conf->colors.selection_bg >> 24 == 0;
|
conf->colors.selection_bg >> 24 == 0;
|
||||||
|
|
||||||
|
|
@ -2252,6 +2443,13 @@ out:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
free_spawn_template(struct config_spawn_template *template)
|
||||||
|
{
|
||||||
|
free(template->raw_cmd);
|
||||||
|
free(template->argv);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
config_free(struct config conf)
|
config_free(struct config conf)
|
||||||
{
|
{
|
||||||
|
|
@ -2261,8 +2459,8 @@ config_free(struct config conf)
|
||||||
free(conf.app_id);
|
free(conf.app_id);
|
||||||
free(conf.word_delimiters);
|
free(conf.word_delimiters);
|
||||||
free(conf.scrollback.indicator.text);
|
free(conf.scrollback.indicator.text);
|
||||||
free(conf.notify.raw_cmd);
|
free_spawn_template(&conf.notify);
|
||||||
free(conf.notify.argv);
|
free_spawn_template(&conf.url_launch);
|
||||||
for (size_t i = 0; i < ALEN(conf.fonts); i++) {
|
for (size_t i = 0; i < ALEN(conf.fonts); i++) {
|
||||||
tll_foreach(conf.fonts[i], it)
|
tll_foreach(conf.fonts[i], it)
|
||||||
config_font_destroy(&it->item);
|
config_font_destroy(&it->item);
|
||||||
|
|
@ -2286,6 +2484,7 @@ config_free(struct config conf)
|
||||||
tll_free(conf.bindings.key);
|
tll_free(conf.bindings.key);
|
||||||
tll_free(conf.bindings.mouse);
|
tll_free(conf.bindings.mouse);
|
||||||
tll_free(conf.bindings.search);
|
tll_free(conf.bindings.search);
|
||||||
|
tll_free(conf.bindings.url);
|
||||||
|
|
||||||
user_notifications_free(&conf.notifications);
|
user_notifications_free(&conf.notifications);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
config.h
41
config.h
|
|
@ -42,6 +42,12 @@ struct config_key_binding_search {
|
||||||
xkb_keysym_t sym;
|
xkb_keysym_t sym;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct config_key_binding_url {
|
||||||
|
enum bind_action_url action;
|
||||||
|
struct config_key_modifiers modifiers;
|
||||||
|
xkb_keysym_t sym;
|
||||||
|
};
|
||||||
|
|
||||||
struct config_mouse_binding {
|
struct config_mouse_binding {
|
||||||
enum bind_action_normal action;
|
enum bind_action_normal action;
|
||||||
struct config_key_modifiers modifiers;
|
struct config_key_modifiers modifiers;
|
||||||
|
|
@ -60,6 +66,11 @@ struct pt_or_px {
|
||||||
float pt;
|
float pt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct config_spawn_template {
|
||||||
|
char *raw_cmd;
|
||||||
|
char **argv;
|
||||||
|
};
|
||||||
|
|
||||||
struct config {
|
struct config {
|
||||||
char *term;
|
char *term;
|
||||||
char *shell;
|
char *shell;
|
||||||
|
|
@ -128,7 +139,18 @@ struct config {
|
||||||
uint16_t alpha;
|
uint16_t alpha;
|
||||||
uint32_t selection_fg;
|
uint32_t selection_fg;
|
||||||
uint32_t selection_bg;
|
uint32_t selection_bg;
|
||||||
bool selection_uses_custom_colors;
|
uint32_t url;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t bg;
|
||||||
|
} jump_label;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool selection:1;
|
||||||
|
bool jump_label:1;
|
||||||
|
bool url:1;
|
||||||
|
} use_custom;
|
||||||
} colors;
|
} colors;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -157,6 +179,9 @@ struct config {
|
||||||
/* While searching (not - action to *start* a search is in the
|
/* While searching (not - action to *start* a search is in the
|
||||||
* 'key' bindings above */
|
* 'key' bindings above */
|
||||||
tll(struct config_key_binding_search) search;
|
tll(struct config_key_binding_search) search;
|
||||||
|
|
||||||
|
/* While showing URL jump labels */
|
||||||
|
tll(struct config_key_binding_url) url;
|
||||||
} bindings;
|
} bindings;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -167,10 +192,10 @@ struct config {
|
||||||
int button_width;
|
int button_width;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool title_set;
|
bool title_set:1;
|
||||||
bool minimize_set;
|
bool minimize_set:1;
|
||||||
bool maximize_set;
|
bool maximize_set:1;
|
||||||
bool close_set;
|
bool close_set:1;
|
||||||
uint32_t title;
|
uint32_t title;
|
||||||
uint32_t minimize;
|
uint32_t minimize;
|
||||||
uint32_t maximize;
|
uint32_t maximize;
|
||||||
|
|
@ -189,10 +214,8 @@ struct config {
|
||||||
SELECTION_TARGET_BOTH
|
SELECTION_TARGET_BOTH
|
||||||
} selection_target;
|
} selection_target;
|
||||||
|
|
||||||
struct {
|
struct config_spawn_template notify;
|
||||||
char *raw_cmd;
|
struct config_spawn_template url_launch;
|
||||||
char **argv;
|
|
||||||
} notify;
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
enum fcft_scaling_filter fcft_filter;
|
enum fcft_scaling_filter fcft_filter;
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,31 @@ _Examples_:
|
||||||
- Dina:weight=bold:slant=italic
|
- Dina:weight=bold:slant=italic
|
||||||
- Courier New:size=12
|
- Courier New:size=12
|
||||||
|
|
||||||
|
# URLs
|
||||||
|
|
||||||
|
Foot supports URL detection. But, unlike many other terminal
|
||||||
|
emulators, where URLs are highlighted when they are hovered and opened
|
||||||
|
by clicking on them, foot uses a keyboard driven approach.
|
||||||
|
|
||||||
|
Pressing *ctrl*+*shift*+*u* enters _“URL mode”_, where all currently
|
||||||
|
visible URLs are underlined, and is associated with a
|
||||||
|
_“jump-label”_. The jump-label indicates the _key sequence_
|
||||||
|
(e.g. *”AF”*) to use to activate the URL.
|
||||||
|
|
||||||
|
The key binding can, of course, be customized, like all other key
|
||||||
|
bindings in foot. See *show-urls-launch* and *show-urls-copy* in
|
||||||
|
*foot.ini*(5).
|
||||||
|
|
||||||
|
*show-urls-launch* by default opens the URL with *xdg-open*. This can
|
||||||
|
be changed with the *url-launch* option.
|
||||||
|
|
||||||
|
*show-urls-copy* is an alternative to *show-urls-launch*, that changes
|
||||||
|
what activating an URL _does_; instead of opening it, it copies it to
|
||||||
|
the clipboard. It is unbound by default.
|
||||||
|
|
||||||
|
Both the jump label colors, and the URL underline color can be
|
||||||
|
configured, independently.
|
||||||
|
|
||||||
# ALT/META CHARACTERS
|
# ALT/META CHARACTERS
|
||||||
|
|
||||||
By default, foot prefixes meta characters with *ESC*. This corresponds
|
By default, foot prefixes meta characters with *ESC*. This corresponds
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,10 @@ in this order:
|
||||||
|
|
||||||
Default: _notify-send -a foot -i foot ${title} ${body}_.
|
Default: _notify-send -a foot -i foot ${title} ${body}_.
|
||||||
|
|
||||||
|
*url-launch*
|
||||||
|
Command to execute when opening URLs. _${url}_ will be replaced
|
||||||
|
with the actual URL. Default: _xdg-open ${url}_.
|
||||||
|
|
||||||
*selection-target*
|
*selection-target*
|
||||||
Clipboard target to automatically copy selected text to. One of
|
Clipboard target to automatically copy selected text to. One of
|
||||||
*none*, *primary*, *clipboard* or *both*. Default: _primary_.
|
*none*, *primary*, *clipboard* or *both*. Default: _primary_.
|
||||||
|
|
@ -347,6 +351,15 @@ _alpha_ option.
|
||||||
text. Note that *both* options must be set, or the default will be
|
text. Note that *both* options must be set, or the default will be
|
||||||
used. Default: _inverse foreground/background_.
|
used. Default: _inverse foreground/background_.
|
||||||
|
|
||||||
|
*jump-labels*
|
||||||
|
To RRGGBB values specifying the foreground (text) and background
|
||||||
|
colors to use when rendering jump labels in URL mode. Default:
|
||||||
|
_regular0 regular3_.
|
||||||
|
|
||||||
|
*urls*
|
||||||
|
Color to use for the underline used to highlight URLs in URL
|
||||||
|
mode. Default: _regular3_.
|
||||||
|
|
||||||
|
|
||||||
# SECTION: csd
|
# SECTION: csd
|
||||||
|
|
||||||
|
|
@ -491,6 +504,16 @@ e.g. *search-start=none*.
|
||||||
|
|
||||||
Default: _not bound_
|
Default: _not bound_
|
||||||
|
|
||||||
|
*show-urls-launch*
|
||||||
|
Enter URL mode, where all currently visible URLs are tagged with a
|
||||||
|
jump label with a key sequence that will open the URL. Default:
|
||||||
|
_Control+Shift+U_.
|
||||||
|
|
||||||
|
*show-urls-copy*
|
||||||
|
Enter URL mode, where all currently visible URLs are tagged with a
|
||||||
|
jump label with a key sequence that will place the URL in the
|
||||||
|
clipboard. Default: _none_.
|
||||||
|
|
||||||
|
|
||||||
# SECTION: search-bindings
|
# SECTION: search-bindings
|
||||||
|
|
||||||
|
|
@ -569,6 +592,17 @@ scrollback search mode. The syntax is exactly the same as the regular
|
||||||
Paste from the _primary selection_ into the search
|
Paste from the _primary selection_ into the search
|
||||||
buffer. Default: _Shift+Insert_.
|
buffer. Default: _Shift+Insert_.
|
||||||
|
|
||||||
|
|
||||||
|
# SECTION: url-bindings
|
||||||
|
|
||||||
|
This section lets you override the default key bindings used in URL
|
||||||
|
mode. The syntax is exactly the same as the regular **key-bindings**.
|
||||||
|
|
||||||
|
*cancel*
|
||||||
|
Exits URL mode without opening an URL. Default: _Control+g
|
||||||
|
Control+d Escape_.
|
||||||
|
|
||||||
|
|
||||||
# SECTION: mouse-bindings
|
# SECTION: mouse-bindings
|
||||||
|
|
||||||
This section lets you override the default mouse bindings.
|
This section lets you override the default mouse bindings.
|
||||||
|
|
|
||||||
17
foot.ini
17
foot.ini
|
|
@ -20,10 +20,12 @@
|
||||||
# pad=2x2 # optionally append 'center'
|
# pad=2x2 # optionally append 'center'
|
||||||
# resize-delay-ms=100
|
# resize-delay-ms=100
|
||||||
|
|
||||||
|
# notify=notify-send -a foot -i foot ${title} ${body}
|
||||||
|
# url-launch=xdg-open ${url}
|
||||||
|
|
||||||
# bold-text-in-bright=no
|
# bold-text-in-bright=no
|
||||||
# bell=none
|
# bell=none
|
||||||
# word-delimiters=,│`|:"'()[]{}<>
|
# word-delimiters=,│`|:"'()[]{}<>
|
||||||
# notify=notify-send -a foot -i foot ${title} ${body}
|
|
||||||
# selection-target=primary
|
# selection-target=primary
|
||||||
# workers=<number of logical CPUs>
|
# workers=<number of logical CPUs>
|
||||||
|
|
||||||
|
|
@ -64,15 +66,17 @@
|
||||||
# bright7=ffffff # bright white
|
# bright7=ffffff # bright white
|
||||||
# selection-foreground=<inverse foreground/background>
|
# selection-foreground=<inverse foreground/background>
|
||||||
# selection-background=<inverse foreground/background>
|
# selection-background=<inverse foreground/background>
|
||||||
|
# jump-labels=<regular0> <regular3>
|
||||||
|
# urls=<regular3>
|
||||||
|
|
||||||
[csd]
|
[csd]
|
||||||
# preferred=server
|
# preferred=server
|
||||||
# size=26
|
# size=26
|
||||||
# color=<foreground color>
|
# color=<foreground color>
|
||||||
# button-width=26
|
# button-width=26
|
||||||
# button-minimize-color=ff0000ff
|
# button-minimize-color=<regular4>
|
||||||
# button-maximize-color=ff00ff00
|
# button-maximize-color=<regular2>
|
||||||
# button-close-color=ffff0000
|
# button-close-color=<regular1>
|
||||||
|
|
||||||
[key-bindings]
|
[key-bindings]
|
||||||
# scrollback-up-page=Shift+Page_Up
|
# scrollback-up-page=Shift+Page_Up
|
||||||
|
|
@ -95,6 +99,8 @@
|
||||||
# pipe-visible=[sh -c "xurls | bemenu | xargs -r firefox"] none
|
# pipe-visible=[sh -c "xurls | bemenu | xargs -r firefox"] none
|
||||||
# pipe-scrollback=[sh -c "xurls | bemenu | xargs -r firefox"] none
|
# pipe-scrollback=[sh -c "xurls | bemenu | xargs -r firefox"] none
|
||||||
# pipe-selected=[xargs -r firefox] none
|
# pipe-selected=[xargs -r firefox] none
|
||||||
|
# show-urls-launch=Control+Shift+U
|
||||||
|
# show-urls-copy=none
|
||||||
|
|
||||||
[search-bindings]
|
[search-bindings]
|
||||||
# cancel=Control+g Escape
|
# cancel=Control+g Escape
|
||||||
|
|
@ -116,6 +122,9 @@
|
||||||
# clipboard-paste=Control+v Control+y
|
# clipboard-paste=Control+v Control+y
|
||||||
# primary-paste=Shift+Insert
|
# primary-paste=Shift+Insert
|
||||||
|
|
||||||
|
[url-bindings]
|
||||||
|
# cancel=Control+g Control+d Escape
|
||||||
|
|
||||||
[mouse-bindings]
|
[mouse-bindings]
|
||||||
# primary-paste=BTN_MIDDLE
|
# primary-paste=BTN_MIDDLE
|
||||||
# select-begin=BTN_LEFT
|
# select-begin=BTN_LEFT
|
||||||
|
|
|
||||||
48
input.c
48
input.c
|
|
@ -34,6 +34,7 @@
|
||||||
#include "spawn.h"
|
#include "spawn.h"
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
#include "tokenize.h"
|
#include "tokenize.h"
|
||||||
|
#include "url-mode.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "vt.h"
|
#include "vt.h"
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
|
|
@ -271,6 +272,20 @@ execute_binding(struct seat *seat, struct terminal *term,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case BIND_ACTION_SHOW_URLS_COPY:
|
||||||
|
case BIND_ACTION_SHOW_URLS_LAUNCH: {
|
||||||
|
xassert(!urls_mode_is_active(term));
|
||||||
|
|
||||||
|
enum url_action url_action = action == BIND_ACTION_SHOW_URLS_COPY
|
||||||
|
? URL_ACTION_COPY
|
||||||
|
: URL_ACTION_LAUNCH;
|
||||||
|
|
||||||
|
urls_collect(term, url_action, &term->urls);
|
||||||
|
urls_assign_key_combos(&term->urls);
|
||||||
|
urls_render(term);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
case BIND_ACTION_SELECT_BEGIN:
|
case BIND_ACTION_SELECT_BEGIN:
|
||||||
selection_start(
|
selection_start(
|
||||||
term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false);
|
term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false);
|
||||||
|
|
@ -403,6 +418,29 @@ convert_search_bindings(const struct config *conf, struct seat *seat)
|
||||||
convert_search_binding(seat, &it->item);
|
convert_search_binding(seat, &it->item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_url_binding(struct seat *seat,
|
||||||
|
const struct config_key_binding_url *conf_binding)
|
||||||
|
{
|
||||||
|
struct key_binding_url binding = {
|
||||||
|
.action = conf_binding->action,
|
||||||
|
.bind = {
|
||||||
|
.mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers),
|
||||||
|
.sym = conf_binding->sym,
|
||||||
|
.key_codes = key_codes_for_xkb_sym(
|
||||||
|
seat->kbd.xkb_keymap, conf_binding->sym),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
tll_push_back(seat->kbd.bindings.url, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_url_bindings(const struct config *conf, struct seat *seat)
|
||||||
|
{
|
||||||
|
tll_foreach(conf->bindings.url, it)
|
||||||
|
convert_url_binding(seat, &it->item);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
convert_mouse_binding(struct seat *seat,
|
convert_mouse_binding(struct seat *seat,
|
||||||
const struct config_mouse_binding *conf_binding)
|
const struct config_mouse_binding *conf_binding)
|
||||||
|
|
@ -528,6 +566,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
|
||||||
|
|
||||||
convert_key_bindings(wayl->conf, seat);
|
convert_key_bindings(wayl->conf, seat);
|
||||||
convert_search_bindings(wayl->conf, seat);
|
convert_search_bindings(wayl->conf, seat);
|
||||||
|
convert_url_bindings(wayl->conf, seat);
|
||||||
convert_mouse_bindings(wayl->conf, seat);
|
convert_mouse_bindings(wayl->conf, seat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -826,6 +865,11 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial,
|
||||||
start_repeater(seat, key);
|
start_repeater(seat, key);
|
||||||
search_input(seat, term, key, sym, effective_mods, serial);
|
search_input(seat, term, key, sym, effective_mods, serial);
|
||||||
return;
|
return;
|
||||||
|
} else if (urls_mode_is_active(term)) {
|
||||||
|
if (should_repeat)
|
||||||
|
start_repeater(seat, key);
|
||||||
|
urls_input(seat, term, key, sym, effective_mods, serial);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
|
|
@ -1194,6 +1238,7 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
|
||||||
case TERM_SURF_SEARCH:
|
case TERM_SURF_SEARCH:
|
||||||
case TERM_SURF_SCROLLBACK_INDICATOR:
|
case TERM_SURF_SCROLLBACK_INDICATOR:
|
||||||
case TERM_SURF_RENDER_TIMER:
|
case TERM_SURF_RENDER_TIMER:
|
||||||
|
case TERM_SURF_JUMP_LABEL:
|
||||||
case TERM_SURF_TITLE:
|
case TERM_SURF_TITLE:
|
||||||
render_xcursor_set(seat, term, XCURSOR_LEFT_PTR);
|
render_xcursor_set(seat, term, XCURSOR_LEFT_PTR);
|
||||||
break;
|
break;
|
||||||
|
|
@ -1282,6 +1327,7 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
|
||||||
case TERM_SURF_SEARCH:
|
case TERM_SURF_SEARCH:
|
||||||
case TERM_SURF_SCROLLBACK_INDICATOR:
|
case TERM_SURF_SCROLLBACK_INDICATOR:
|
||||||
case TERM_SURF_RENDER_TIMER:
|
case TERM_SURF_RENDER_TIMER:
|
||||||
|
case TERM_SURF_JUMP_LABEL:
|
||||||
case TERM_SURF_TITLE:
|
case TERM_SURF_TITLE:
|
||||||
case TERM_SURF_BORDER_LEFT:
|
case TERM_SURF_BORDER_LEFT:
|
||||||
case TERM_SURF_BORDER_RIGHT:
|
case TERM_SURF_BORDER_RIGHT:
|
||||||
|
|
@ -1330,6 +1376,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
|
||||||
case TERM_SURF_SEARCH:
|
case TERM_SURF_SEARCH:
|
||||||
case TERM_SURF_SCROLLBACK_INDICATOR:
|
case TERM_SURF_SCROLLBACK_INDICATOR:
|
||||||
case TERM_SURF_RENDER_TIMER:
|
case TERM_SURF_RENDER_TIMER:
|
||||||
|
case TERM_SURF_JUMP_LABEL:
|
||||||
case TERM_SURF_BUTTON_MINIMIZE:
|
case TERM_SURF_BUTTON_MINIMIZE:
|
||||||
case TERM_SURF_BUTTON_MAXIMIZE:
|
case TERM_SURF_BUTTON_MAXIMIZE:
|
||||||
case TERM_SURF_BUTTON_CLOSE:
|
case TERM_SURF_BUTTON_CLOSE:
|
||||||
|
|
@ -1674,6 +1721,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
||||||
case TERM_SURF_SEARCH:
|
case TERM_SURF_SEARCH:
|
||||||
case TERM_SURF_SCROLLBACK_INDICATOR:
|
case TERM_SURF_SCROLLBACK_INDICATOR:
|
||||||
case TERM_SURF_RENDER_TIMER:
|
case TERM_SURF_RENDER_TIMER:
|
||||||
|
case TERM_SURF_JUMP_LABEL:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TERM_SURF_GRID: {
|
case TERM_SURF_GRID: {
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,7 @@ executable(
|
||||||
'slave.c', 'slave.h',
|
'slave.c', 'slave.h',
|
||||||
'spawn.c', 'spawn.h',
|
'spawn.c', 'spawn.h',
|
||||||
'tokenize.c', 'tokenize.h',
|
'tokenize.c', 'tokenize.h',
|
||||||
|
'url-mode.c', 'url-mode.h',
|
||||||
'user-notification.h',
|
'user-notification.h',
|
||||||
'wayland.c', 'wayland.h',
|
'wayland.c', 'wayland.h',
|
||||||
wl_proto_src + wl_proto_headers, version,
|
wl_proto_src + wl_proto_headers, version,
|
||||||
|
|
|
||||||
71
notify.c
71
notify.c
|
|
@ -30,69 +30,20 @@ notify_notify(const struct terminal *term, const char *title, const char *body)
|
||||||
if (term->conf->notify.argv == NULL)
|
if (term->conf->notify.argv == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
size_t argv_size = 0;
|
char **argv = NULL;
|
||||||
for (; term->conf->notify.argv[argv_size] != NULL; argv_size++)
|
size_t argc = 0;
|
||||||
;
|
|
||||||
|
|
||||||
#define append(s, n) \
|
if (!spawn_expand_template(
|
||||||
do { \
|
&term->conf->notify, 2,
|
||||||
expanded = xrealloc(expanded, len + (n) + 1); \
|
(const char *[]){"title", "body"},
|
||||||
memcpy(&expanded[len], s, n); \
|
(const char *[]){title, body},
|
||||||
len += n; \
|
&argc, &argv))
|
||||||
expanded[len] = '\0'; \
|
{
|
||||||
} while (0)
|
return;
|
||||||
|
|
||||||
char **argv = malloc((argv_size + 1) * sizeof(argv[0]));
|
|
||||||
|
|
||||||
/* Expand ${title} and ${body} */
|
|
||||||
for (size_t i = 0; i < argv_size; i++) {
|
|
||||||
size_t len = 0;
|
|
||||||
char *expanded = NULL;
|
|
||||||
|
|
||||||
char *start = NULL;
|
|
||||||
char *last_end = term->conf->notify.argv[i];
|
|
||||||
|
|
||||||
while ((start = strstr(last_end, "${")) != NULL) {
|
|
||||||
/* Append everything from the last template's end to this
|
|
||||||
* one's beginning */
|
|
||||||
append(last_end, start - last_end);
|
|
||||||
|
|
||||||
/* Find end of template */
|
|
||||||
start += 2;
|
|
||||||
char *end = strstr(start, "}");
|
|
||||||
|
|
||||||
if (end == NULL) {
|
|
||||||
/* Ensure final append() copies the unclosed '${' */
|
|
||||||
last_end = start - 2;
|
|
||||||
LOG_WARN("notify: unclosed template: %s", last_end);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Expand template */
|
|
||||||
if (strncmp(start, "title", end - start) == 0)
|
|
||||||
append(title, strlen(title));
|
|
||||||
else if (strncmp(start, "body", end - start) == 0)
|
|
||||||
append(body, strlen(body));
|
|
||||||
else {
|
|
||||||
/* Unrecognized template - append it as-is */
|
|
||||||
start -= 2;
|
|
||||||
append(start, end + 1 - start);
|
|
||||||
LOG_WARN("notify: unrecognized template: %.*s",
|
|
||||||
(int)(end + 1 - start), start);
|
|
||||||
}
|
|
||||||
|
|
||||||
last_end = end + 1;;
|
|
||||||
}
|
|
||||||
|
|
||||||
append(last_end, term->conf->notify.argv[i] + strlen(term->conf->notify.argv[i]) - last_end);
|
|
||||||
argv[i] = expanded;
|
|
||||||
}
|
}
|
||||||
argv[argv_size] = NULL;
|
|
||||||
|
|
||||||
#undef append
|
|
||||||
|
|
||||||
LOG_DBG("notify command:");
|
LOG_DBG("notify command:");
|
||||||
for (size_t i = 0; i < argv_size; i++)
|
for (size_t i = 0; i < argc; i++)
|
||||||
LOG_DBG(" argv[%zu] = \"%s\"", i, argv[i]);
|
LOG_DBG(" argv[%zu] = \"%s\"", i, argv[i]);
|
||||||
|
|
||||||
/* Redirect stdin to /dev/null, but ignore failure to open */
|
/* Redirect stdin to /dev/null, but ignore failure to open */
|
||||||
|
|
@ -102,7 +53,7 @@ notify_notify(const struct terminal *term, const char *title, const char *body)
|
||||||
if (devnull >= 0)
|
if (devnull >= 0)
|
||||||
close(devnull);
|
close(devnull);
|
||||||
|
|
||||||
for (size_t i = 0; i < argv_size; i++)
|
for (size_t i = 0; i < argc; i++)
|
||||||
free(argv[i]);
|
free(argv[i]);
|
||||||
free(argv);
|
free(argv);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ notify_notify(const struct terminal *term, const char *title, const char *body)
|
||||||
void reaper_add(struct reaper *reaper, pid_t pid, reaper_cb cb, void *cb_data) {}
|
void reaper_add(struct reaper *reaper, pid_t pid, reaper_cb cb, void *cb_data) {}
|
||||||
void reaper_del(struct reaper *reaper, pid_t pid) {}
|
void reaper_del(struct reaper *reaper, pid_t pid) {}
|
||||||
|
|
||||||
|
void urls_reset(struct terminal *term) {}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, const char *const *argv)
|
main(int argc, const char *const *argv)
|
||||||
|
|
|
||||||
196
render.c
196
render.c
|
|
@ -1,6 +1,7 @@
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <wctype.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
|
@ -28,13 +29,14 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "grid.h"
|
#include "grid.h"
|
||||||
#include "hsl.h"
|
#include "hsl.h"
|
||||||
|
#include "ime.h"
|
||||||
#include "quirks.h"
|
#include "quirks.h"
|
||||||
#include "selection.h"
|
#include "selection.h"
|
||||||
#include "sixel.h"
|
|
||||||
#include "shm.h"
|
#include "shm.h"
|
||||||
|
#include "sixel.h"
|
||||||
|
#include "url-mode.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
#include "ime.h"
|
|
||||||
|
|
||||||
#define TIME_SCROLL_DAMAGE 0
|
#define TIME_SCROLL_DAMAGE 0
|
||||||
|
|
||||||
|
|
@ -411,7 +413,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
|
||||||
uint32_t _fg = 0;
|
uint32_t _fg = 0;
|
||||||
uint32_t _bg = 0;
|
uint32_t _bg = 0;
|
||||||
|
|
||||||
if (is_selected && term->conf->colors.selection_uses_custom_colors) {
|
if (is_selected && term->conf->colors.use_custom.selection) {
|
||||||
_fg = term->conf->colors.selection_fg;
|
_fg = term->conf->colors.selection_fg;
|
||||||
_bg = term->conf->colors.selection_bg;
|
_bg = term->conf->colors.selection_bg;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -615,6 +617,15 @@ render_cell(struct terminal *term, pixman_image_t *pix,
|
||||||
if (cell->attrs.strikethrough)
|
if (cell->attrs.strikethrough)
|
||||||
draw_strikeout(term, pix, font, &fg, x, y, cell_cols);
|
draw_strikeout(term, pix, font, &fg, x, y, cell_cols);
|
||||||
|
|
||||||
|
if (unlikely(cell->attrs.url)) {
|
||||||
|
pixman_color_t url_color = color_hex_to_pixman(
|
||||||
|
term->conf->colors.use_custom.url
|
||||||
|
? term->conf->colors.url
|
||||||
|
: term->colors.table[3]
|
||||||
|
);
|
||||||
|
draw_underline(term, pix, font, &url_color, x, y, cell_cols);
|
||||||
|
}
|
||||||
|
|
||||||
draw_cursor:
|
draw_cursor:
|
||||||
if (has_cursor && (term->cursor_style != CURSOR_BLOCK || !term->kbd_focus))
|
if (has_cursor && (term->cursor_style != CURSOR_BLOCK || !term->kbd_focus))
|
||||||
draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols);
|
draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols);
|
||||||
|
|
@ -1604,27 +1615,27 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx)
|
||||||
uint32_t _color;
|
uint32_t _color;
|
||||||
uint16_t alpha = 0xffff;
|
uint16_t alpha = 0xffff;
|
||||||
bool is_active = false;
|
bool is_active = false;
|
||||||
const bool *is_set = NULL;
|
bool is_set = false;
|
||||||
const uint32_t *conf_color = NULL;
|
const uint32_t *conf_color = NULL;
|
||||||
|
|
||||||
switch (surf_idx) {
|
switch (surf_idx) {
|
||||||
case CSD_SURF_MINIMIZE:
|
case CSD_SURF_MINIMIZE:
|
||||||
_color = term->colors.default_table[4]; /* blue */
|
_color = term->colors.default_table[4]; /* blue */
|
||||||
is_set = &term->conf->csd.color.minimize_set;
|
is_set = term->conf->csd.color.minimize_set;
|
||||||
conf_color = &term->conf->csd.color.minimize;
|
conf_color = &term->conf->csd.color.minimize;
|
||||||
is_active = term->active_surface == TERM_SURF_BUTTON_MINIMIZE;
|
is_active = term->active_surface == TERM_SURF_BUTTON_MINIMIZE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CSD_SURF_MAXIMIZE:
|
case CSD_SURF_MAXIMIZE:
|
||||||
_color = term->colors.default_table[2]; /* green */
|
_color = term->colors.default_table[2]; /* green */
|
||||||
is_set = &term->conf->csd.color.maximize_set;
|
is_set = term->conf->csd.color.maximize_set;
|
||||||
conf_color = &term->conf->csd.color.maximize;
|
conf_color = &term->conf->csd.color.maximize;
|
||||||
is_active = term->active_surface == TERM_SURF_BUTTON_MAXIMIZE;
|
is_active = term->active_surface == TERM_SURF_BUTTON_MAXIMIZE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CSD_SURF_CLOSE:
|
case CSD_SURF_CLOSE:
|
||||||
_color = term->colors.default_table[1]; /* red */
|
_color = term->colors.default_table[1]; /* red */
|
||||||
is_set = &term->conf->csd.color.close_set;
|
is_set = term->conf->csd.color.close_set;
|
||||||
conf_color = &term->conf->csd.color.close;
|
conf_color = &term->conf->csd.color.close;
|
||||||
is_active = term->active_surface == TERM_SURF_BUTTON_CLOSE;
|
is_active = term->active_surface == TERM_SURF_BUTTON_CLOSE;
|
||||||
break;
|
break;
|
||||||
|
|
@ -1635,7 +1646,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_active) {
|
if (is_active) {
|
||||||
if (*is_set) {
|
if (is_set) {
|
||||||
_color = *conf_color;
|
_color = *conf_color;
|
||||||
alpha = _color >> 24 | (_color >> 24 << 8);
|
alpha = _color >> 24 | (_color >> 24 << 8);
|
||||||
}
|
}
|
||||||
|
|
@ -2517,6 +2528,120 @@ render_search_box(struct terminal *term)
|
||||||
#undef WINDOW_Y
|
#undef WINDOW_Y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
render_urls(struct terminal *term)
|
||||||
|
{
|
||||||
|
struct wl_window *win = term->window;
|
||||||
|
xassert(tll_length(win->urls) > 0);
|
||||||
|
|
||||||
|
/* Calculate view start, counted from the *current* scrollback start */
|
||||||
|
const int scrollback_end
|
||||||
|
= (term->grid->offset + term->rows) & (term->grid->num_rows - 1);
|
||||||
|
const int view_start
|
||||||
|
= (term->grid->view
|
||||||
|
- scrollback_end
|
||||||
|
+ term->grid->num_rows) & (term->grid->num_rows - 1);
|
||||||
|
const int view_end = view_start + term->rows - 1;
|
||||||
|
|
||||||
|
tll_foreach(win->urls, it) {
|
||||||
|
const struct url *url = it->item.url;
|
||||||
|
const wchar_t *text = url->text;
|
||||||
|
const wchar_t *key = url->key;
|
||||||
|
const size_t entered_key_len = wcslen(term->url_keys);
|
||||||
|
|
||||||
|
struct wl_surface *surf = it->item.surf;
|
||||||
|
struct wl_subsurface *sub_surf = it->item.sub_surf;
|
||||||
|
|
||||||
|
if (surf == NULL || sub_surf == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool hide = false;
|
||||||
|
const struct coord *pos = &url->start;
|
||||||
|
const int _row
|
||||||
|
= (pos->row
|
||||||
|
- scrollback_end
|
||||||
|
+ term->grid->num_rows) & (term->grid->num_rows - 1);
|
||||||
|
|
||||||
|
if (_row < view_start || _row > view_end)
|
||||||
|
hide = true;
|
||||||
|
if (wcslen(key) <= entered_key_len)
|
||||||
|
hide = true;
|
||||||
|
if (wcsncmp(term->url_keys, key, entered_key_len) != 0)
|
||||||
|
hide = true;
|
||||||
|
|
||||||
|
if (hide) {
|
||||||
|
wl_surface_attach(surf, NULL, 0, 0);
|
||||||
|
wl_surface_commit(surf);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t text_len = wcslen(text);
|
||||||
|
size_t chars = wcslen(key) + (text_len > 0 ? 3 + text_len : 0);
|
||||||
|
|
||||||
|
const size_t max_chars = 50;
|
||||||
|
chars = min(chars, max_chars);
|
||||||
|
|
||||||
|
wchar_t label[chars + 2];
|
||||||
|
if (text_len == 0)
|
||||||
|
wcscpy(label, key);
|
||||||
|
else {
|
||||||
|
int count = swprintf(label, chars + 1, L"%ls - %ls", key, text);
|
||||||
|
if (count >= max_chars) {
|
||||||
|
label[max_chars] = L'…';
|
||||||
|
label[max_chars + 1] = L'\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < wcslen(key); i++)
|
||||||
|
label[i] = towupper(label[i]);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < entered_key_len; i++)
|
||||||
|
label[i] = L' ';
|
||||||
|
|
||||||
|
size_t len = wcslen(label);
|
||||||
|
int cols = wcswidth(label, len);
|
||||||
|
|
||||||
|
const int x_margin = 2 * term->scale;
|
||||||
|
const int y_margin = 1 * term->scale;
|
||||||
|
int width = 2 * x_margin + cols * term->cell_width;
|
||||||
|
int height = 2 * y_margin + term->cell_height;
|
||||||
|
|
||||||
|
struct buffer *buf = shm_get_buffer(
|
||||||
|
term->wl->shm, width, height, shm_cookie_url(url), false, 1);
|
||||||
|
|
||||||
|
int col = pos->col;
|
||||||
|
int row = pos->row - term->grid->view;
|
||||||
|
while (row < 0)
|
||||||
|
row += term->grid->num_rows;
|
||||||
|
row &= (term->grid->num_rows - 1);
|
||||||
|
|
||||||
|
int x = col * term->cell_width - 15 * term->cell_width / 10;
|
||||||
|
int y = row * term->cell_height - 5 * term->cell_height / 10;
|
||||||
|
|
||||||
|
if (x < 0)
|
||||||
|
x = 0;
|
||||||
|
#if 0
|
||||||
|
if (y < 0)
|
||||||
|
y += 15 * term->cell_height / 10;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
wl_subsurface_set_position(
|
||||||
|
sub_surf,
|
||||||
|
(term->margins.left + x) / term->scale,
|
||||||
|
(term->margins.top + y) / term->scale);
|
||||||
|
|
||||||
|
uint32_t fg = term->conf->colors.use_custom.jump_label
|
||||||
|
? term->conf->colors.jump_label.fg
|
||||||
|
: term->colors.table[0];
|
||||||
|
uint32_t bg = term->conf->colors.use_custom.jump_label
|
||||||
|
? term->conf->colors.jump_label.bg
|
||||||
|
: term->colors.table[3];
|
||||||
|
|
||||||
|
render_osd(term, surf, sub_surf, buf, label,
|
||||||
|
fg, bg, width, height, x_margin, y_margin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
render_update_title(struct terminal *term)
|
render_update_title(struct terminal *term)
|
||||||
{
|
{
|
||||||
|
|
@ -2545,13 +2670,15 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
|
||||||
|
|
||||||
bool grid = term->render.pending.grid;
|
bool grid = term->render.pending.grid;
|
||||||
bool csd = term->render.pending.csd;
|
bool csd = term->render.pending.csd;
|
||||||
bool search = term->render.pending.search;
|
bool search = term->is_searching && term->render.pending.search;
|
||||||
bool title = term->render.pending.title;
|
bool title = term->render.pending.title;
|
||||||
|
bool urls = urls_mode_is_active(term) > 0 && term->render.pending.urls;
|
||||||
|
|
||||||
term->render.pending.grid = false;
|
term->render.pending.grid = false;
|
||||||
term->render.pending.csd = false;
|
term->render.pending.csd = false;
|
||||||
term->render.pending.search = false;
|
term->render.pending.search = false;
|
||||||
term->render.pending.title = false;
|
term->render.pending.title = false;
|
||||||
|
term->render.pending.urls = false;
|
||||||
|
|
||||||
if (csd && term->window->use_csd == CSD_YES) {
|
if (csd && term->window->use_csd == CSD_YES) {
|
||||||
quirk_weston_csd_on(term);
|
quirk_weston_csd_on(term);
|
||||||
|
|
@ -2562,15 +2689,19 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
|
||||||
if (title)
|
if (title)
|
||||||
render_update_title(term);
|
render_update_title(term);
|
||||||
|
|
||||||
if (search && term->is_searching)
|
if (search)
|
||||||
render_search_box(term);
|
render_search_box(term);
|
||||||
|
|
||||||
tll_foreach(term->wl->seats, it)
|
if (urls)
|
||||||
|
render_urls(term);
|
||||||
|
|
||||||
|
if (grid && (!term->delayed_render_timer.is_armed | csd | search | urls))
|
||||||
|
grid_render(term);
|
||||||
|
|
||||||
|
tll_foreach(term->wl->seats, it) {
|
||||||
if (it->item.kbd_focus == term)
|
if (it->item.kbd_focus == term)
|
||||||
ime_update_cursor_rect(&it->item, term);
|
ime_update_cursor_rect(&it->item, term);
|
||||||
|
}
|
||||||
if (grid && (!term->delayed_render_timer.is_armed || csd || search))
|
|
||||||
grid_render(term);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
@ -2755,6 +2886,9 @@ maybe_resize(struct terminal *term, int width, int height, bool force)
|
||||||
/* Cancel an application initiated "Synchronized Update" */
|
/* Cancel an application initiated "Synchronized Update" */
|
||||||
term_disable_app_sync_updates(term);
|
term_disable_app_sync_updates(term);
|
||||||
|
|
||||||
|
/* Drop out of URL mode */
|
||||||
|
urls_reset(term);
|
||||||
|
|
||||||
term->width = width;
|
term->width = width;
|
||||||
term->height = height;
|
term->height = height;
|
||||||
term->scale = scale;
|
term->scale = scale;
|
||||||
|
|
@ -2981,27 +3115,21 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data)
|
||||||
|
|
||||||
bool grid = term->render.refresh.grid;
|
bool grid = term->render.refresh.grid;
|
||||||
bool csd = term->render.refresh.csd;
|
bool csd = term->render.refresh.csd;
|
||||||
bool search = term->render.refresh.search;
|
bool search = term->is_searching && term->render.refresh.search;
|
||||||
bool title = term->render.refresh.title;
|
bool title = term->render.refresh.title;
|
||||||
|
bool urls = urls_mode_is_active(term) && term->render.refresh.urls;
|
||||||
|
|
||||||
if (!term->is_searching)
|
if (!(grid | csd | search | title | urls))
|
||||||
search = false;
|
|
||||||
|
|
||||||
if (!(grid | csd | search | title))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (term->render.app_sync_updates.enabled && !(csd | search | title))
|
if (term->render.app_sync_updates.enabled && !(csd | search | title | urls))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (csd | search) {
|
|
||||||
/* Force update of parent surface */
|
|
||||||
grid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
term->render.refresh.grid = false;
|
term->render.refresh.grid = false;
|
||||||
term->render.refresh.csd = false;
|
term->render.refresh.csd = false;
|
||||||
term->render.refresh.search = false;
|
term->render.refresh.search = false;
|
||||||
term->render.refresh.title = false;
|
term->render.refresh.title = false;
|
||||||
|
term->render.refresh.urls = false;
|
||||||
|
|
||||||
if (term->window->frame_callback == NULL) {
|
if (term->window->frame_callback == NULL) {
|
||||||
if (csd && term->window->use_csd == CSD_YES) {
|
if (csd && term->window->use_csd == CSD_YES) {
|
||||||
|
|
@ -3013,17 +3141,22 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data)
|
||||||
render_update_title(term);
|
render_update_title(term);
|
||||||
if (search)
|
if (search)
|
||||||
render_search_box(term);
|
render_search_box(term);
|
||||||
tll_foreach(term->wl->seats, it)
|
if (urls)
|
||||||
|
render_urls(term);
|
||||||
|
if (grid | csd | search | urls)
|
||||||
|
grid_render(term);
|
||||||
|
|
||||||
|
tll_foreach(term->wl->seats, it) {
|
||||||
if (it->item.kbd_focus == term)
|
if (it->item.kbd_focus == term)
|
||||||
ime_update_cursor_rect(&it->item, term);
|
ime_update_cursor_rect(&it->item, term);
|
||||||
if (grid)
|
}
|
||||||
grid_render(term);
|
|
||||||
} else {
|
} else {
|
||||||
/* Tells the frame callback to render again */
|
/* Tells the frame callback to render again */
|
||||||
term->render.pending.grid |= grid;
|
term->render.pending.grid |= grid;
|
||||||
term->render.pending.csd |= csd;
|
term->render.pending.csd |= csd;
|
||||||
term->render.pending.search |= search;
|
term->render.pending.search |= search;
|
||||||
term->render.pending.title |= title;
|
term->render.pending.title |= title;
|
||||||
|
term->render.pending.urls |= urls;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3065,6 +3198,13 @@ render_refresh_search(struct terminal *term)
|
||||||
term->render.refresh.search = true;
|
term->render.refresh.search = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
render_refresh_urls(struct terminal *term)
|
||||||
|
{
|
||||||
|
if (urls_mode_is_active(term))
|
||||||
|
term->render.refresh.urls = true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor)
|
render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
1
render.h
1
render.h
|
|
@ -16,6 +16,7 @@ void render_refresh(struct terminal *term);
|
||||||
void render_refresh_csd(struct terminal *term);
|
void render_refresh_csd(struct terminal *term);
|
||||||
void render_refresh_search(struct terminal *term);
|
void render_refresh_search(struct terminal *term);
|
||||||
void render_refresh_title(struct terminal *term);
|
void render_refresh_title(struct terminal *term);
|
||||||
|
void render_refresh_urls(struct terminal *term);
|
||||||
bool render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor);
|
bool render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor);
|
||||||
|
|
||||||
struct render_worker_context {
|
struct render_worker_context {
|
||||||
|
|
|
||||||
3
shm.h
3
shm.h
|
|
@ -52,3 +52,6 @@ static inline unsigned long shm_cookie_search(const struct terminal *term) { ret
|
||||||
static inline unsigned long shm_cookie_scrollback_indicator(const struct terminal *term) { return (unsigned long)(uintptr_t)term + 2; }
|
static inline unsigned long shm_cookie_scrollback_indicator(const struct terminal *term) { return (unsigned long)(uintptr_t)term + 2; }
|
||||||
static inline unsigned long shm_cookie_render_timer(const struct terminal *term) { return (unsigned long)(uintptr_t)term + 3; }
|
static inline unsigned long shm_cookie_render_timer(const struct terminal *term) { return (unsigned long)(uintptr_t)term + 3; }
|
||||||
static inline unsigned long shm_cookie_csd(const struct terminal *term, int n) { return (unsigned long)((uintptr_t)term + 4 + (n)); }
|
static inline unsigned long shm_cookie_csd(const struct terminal *term, int n) { return (unsigned long)((uintptr_t)term + 4 + (n)); }
|
||||||
|
|
||||||
|
struct url;
|
||||||
|
static inline unsigned long shm_cookie_url(const struct url *url) { return (unsigned long)(uintptr_t)url; }
|
||||||
|
|
|
||||||
80
spawn.c
80
spawn.c
|
|
@ -1,5 +1,6 @@
|
||||||
#include "spawn.h"
|
#include "spawn.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
|
@ -11,6 +12,7 @@
|
||||||
#define LOG_ENABLE_DBG 0
|
#define LOG_ENABLE_DBG 0
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
||||||
|
|
@ -74,3 +76,81 @@ err:
|
||||||
close(pipe_fds[1]);
|
close(pipe_fds[1]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
spawn_expand_template(const struct config_spawn_template *template,
|
||||||
|
size_t key_count,
|
||||||
|
const char *key_names[static key_count],
|
||||||
|
const char *key_values[static key_count],
|
||||||
|
size_t *argc, char ***argv)
|
||||||
|
{
|
||||||
|
*argc = 0;
|
||||||
|
*argv = NULL;
|
||||||
|
|
||||||
|
for (; template->argv[*argc] != NULL; (*argc)++)
|
||||||
|
;
|
||||||
|
|
||||||
|
#define append(s, n) \
|
||||||
|
do { \
|
||||||
|
expanded = xrealloc(expanded, len + (n) + 1); \
|
||||||
|
memcpy(&expanded[len], s, n); \
|
||||||
|
len += n; \
|
||||||
|
expanded[len] = '\0'; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
*argv = malloc((*argc + 1) * sizeof((*argv)[0]));
|
||||||
|
|
||||||
|
/* Expand the provided keys */
|
||||||
|
for (size_t i = 0; i < *argc; i++) {
|
||||||
|
size_t len = 0;
|
||||||
|
char *expanded = NULL;
|
||||||
|
|
||||||
|
char *start = NULL;
|
||||||
|
char *last_end = template->argv[i];
|
||||||
|
|
||||||
|
while ((start = strstr(last_end, "${")) != NULL) {
|
||||||
|
/* Append everything from the last template's end to this
|
||||||
|
* one's beginning */
|
||||||
|
append(last_end, start - last_end);
|
||||||
|
|
||||||
|
/* Find end of template */
|
||||||
|
start += 2;
|
||||||
|
char *end = strstr(start, "}");
|
||||||
|
|
||||||
|
if (end == NULL) {
|
||||||
|
/* Ensure final append() copies the unclosed '${' */
|
||||||
|
last_end = start - 2;
|
||||||
|
LOG_WARN("notify: unclosed template: %s", last_end);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expand template */
|
||||||
|
bool valid_key = false;
|
||||||
|
for (size_t j = 0; j < key_count; j++) {
|
||||||
|
if (strncmp(start, key_names[j], end - start) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
append(key_values[j], strlen(key_values[j]));
|
||||||
|
valid_key = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid_key) {
|
||||||
|
/* Unrecognized template - append it as-is */
|
||||||
|
start -= 2;
|
||||||
|
append(start, end + 1 - start);
|
||||||
|
LOG_WARN("notify: unrecognized template: %.*s",
|
||||||
|
(int)(end + 1 - start), start);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_end = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
append(last_end, template->argv[i] + strlen(template->argv[i]) - last_end);
|
||||||
|
(*argv)[i] = expanded;
|
||||||
|
}
|
||||||
|
(*argv)[*argc] = NULL;
|
||||||
|
|
||||||
|
#undef append
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
||||||
6
spawn.h
6
spawn.h
|
|
@ -1,7 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include "config.h"
|
||||||
#include "reaper.h"
|
#include "reaper.h"
|
||||||
|
|
||||||
bool spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
bool spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
||||||
int stdin_fd, int stdout_fd, int stderr_fd);
|
int stdin_fd, int stdout_fd, int stderr_fd);
|
||||||
|
|
||||||
|
bool spawn_expand_template(
|
||||||
|
const struct config_spawn_template *template,
|
||||||
|
size_t key_count, const char *key_names[static key_count],
|
||||||
|
const char *key_values[static key_count], size_t *argc, char ***argv);
|
||||||
|
|
|
||||||
16
terminal.c
16
terminal.c
|
|
@ -36,6 +36,7 @@
|
||||||
#include "sixel.h"
|
#include "sixel.h"
|
||||||
#include "slave.h"
|
#include "slave.h"
|
||||||
#include "spawn.h"
|
#include "spawn.h"
|
||||||
|
#include "url-mode.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "vt.h"
|
#include "vt.h"
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
|
|
@ -227,6 +228,8 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
|
||||||
cursor_blink_rearm_timer(term);
|
cursor_blink_rearm_timer(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
urls_reset(term);
|
||||||
|
|
||||||
uint8_t buf[24 * 1024];
|
uint8_t buf[24 * 1024];
|
||||||
ssize_t count = sizeof(buf);
|
ssize_t count = sizeof(buf);
|
||||||
|
|
||||||
|
|
@ -1404,8 +1407,10 @@ term_destroy(struct terminal *term)
|
||||||
fdm_del(term->fdm, term->flash.fd);
|
fdm_del(term->fdm, term->flash.fd);
|
||||||
fdm_del(term->fdm, term->ptmx);
|
fdm_del(term->fdm, term->ptmx);
|
||||||
|
|
||||||
if (term->window != NULL)
|
if (term->window != NULL) {
|
||||||
wayl_win_destroy(term->window);
|
wayl_win_destroy(term->window);
|
||||||
|
term->window = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
mtx_lock(&term->render.workers.lock);
|
mtx_lock(&term->render.workers.lock);
|
||||||
xassert(tll_length(term->render.workers.queue) == 0);
|
xassert(tll_length(term->render.workers.queue) == 0);
|
||||||
|
|
@ -1485,6 +1490,7 @@ term_destroy(struct terminal *term)
|
||||||
tll_free(term->alt.sixel_images);
|
tll_free(term->alt.sixel_images);
|
||||||
sixel_fini(term);
|
sixel_fini(term);
|
||||||
|
|
||||||
|
urls_reset(term);
|
||||||
term_ime_reset(term);
|
term_ime_reset(term);
|
||||||
|
|
||||||
free(term->foot_exe);
|
free(term->foot_exe);
|
||||||
|
|
@ -2823,8 +2829,13 @@ term_surface_kind(const struct terminal *term, const struct wl_surface *surface)
|
||||||
return TERM_SURF_BUTTON_MAXIMIZE;
|
return TERM_SURF_BUTTON_MAXIMIZE;
|
||||||
else if (surface == term->window->csd.surface[CSD_SURF_CLOSE])
|
else if (surface == term->window->csd.surface[CSD_SURF_CLOSE])
|
||||||
return TERM_SURF_BUTTON_CLOSE;
|
return TERM_SURF_BUTTON_CLOSE;
|
||||||
else
|
else {
|
||||||
|
tll_foreach(term->window->urls, it) {
|
||||||
|
if (surface == it->item.surf)
|
||||||
|
return TERM_SURF_JUMP_LABEL;
|
||||||
|
}
|
||||||
return TERM_SURF_NONE;
|
return TERM_SURF_NONE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
|
@ -2963,3 +2974,4 @@ term_ime_set_cursor_rect(struct terminal *term, int x, int y, int width,
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
23
terminal.h
23
terminal.h
|
|
@ -40,7 +40,8 @@ struct attributes {
|
||||||
uint32_t have_fg:1;
|
uint32_t have_fg:1;
|
||||||
uint32_t have_bg:1;
|
uint32_t have_bg:1;
|
||||||
uint32_t selected:2;
|
uint32_t selected:2;
|
||||||
uint32_t reserved:3;
|
uint32_t url:1;
|
||||||
|
uint32_t reserved:2;
|
||||||
uint32_t bg:24;
|
uint32_t bg:24;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(struct attributes) == 8, "bad size");
|
static_assert(sizeof(struct attributes) == 8, "bad size");
|
||||||
|
|
@ -211,6 +212,7 @@ enum term_surface {
|
||||||
TERM_SURF_SEARCH,
|
TERM_SURF_SEARCH,
|
||||||
TERM_SURF_SCROLLBACK_INDICATOR,
|
TERM_SURF_SCROLLBACK_INDICATOR,
|
||||||
TERM_SURF_RENDER_TIMER,
|
TERM_SURF_RENDER_TIMER,
|
||||||
|
TERM_SURF_JUMP_LABEL,
|
||||||
TERM_SURF_TITLE,
|
TERM_SURF_TITLE,
|
||||||
TERM_SURF_BORDER_LEFT,
|
TERM_SURF_BORDER_LEFT,
|
||||||
TERM_SURF_BORDER_RIGHT,
|
TERM_SURF_BORDER_RIGHT,
|
||||||
|
|
@ -223,6 +225,17 @@ enum term_surface {
|
||||||
|
|
||||||
typedef tll(struct ptmx_buffer) ptmx_buffer_list_t;
|
typedef tll(struct ptmx_buffer) ptmx_buffer_list_t;
|
||||||
|
|
||||||
|
enum url_action { URL_ACTION_COPY, URL_ACTION_LAUNCH };
|
||||||
|
struct url {
|
||||||
|
wchar_t *url;
|
||||||
|
wchar_t *text;
|
||||||
|
wchar_t *key;
|
||||||
|
struct coord start;
|
||||||
|
struct coord end;
|
||||||
|
enum url_action action;
|
||||||
|
};
|
||||||
|
typedef tll(struct url) url_list_t;
|
||||||
|
|
||||||
struct terminal {
|
struct terminal {
|
||||||
struct fdm *fdm;
|
struct fdm *fdm;
|
||||||
struct reaper *reaper;
|
struct reaper *reaper;
|
||||||
|
|
@ -421,6 +434,7 @@ struct terminal {
|
||||||
bool csd;
|
bool csd;
|
||||||
bool search;
|
bool search;
|
||||||
bool title;
|
bool title;
|
||||||
|
bool urls;
|
||||||
} refresh;
|
} refresh;
|
||||||
|
|
||||||
/* Scheduled for rendering, in the next frame callback */
|
/* Scheduled for rendering, in the next frame callback */
|
||||||
|
|
@ -429,6 +443,7 @@ struct terminal {
|
||||||
bool csd;
|
bool csd;
|
||||||
bool search;
|
bool search;
|
||||||
bool title;
|
bool title;
|
||||||
|
bool urls;
|
||||||
} pending;
|
} pending;
|
||||||
|
|
||||||
bool margins; /* Someone explicitly requested a refresh of the margins */
|
bool margins; /* Someone explicitly requested a refresh of the margins */
|
||||||
|
|
@ -499,6 +514,9 @@ struct terminal {
|
||||||
unsigned max_height; /* Maximum image height, in pixels */
|
unsigned max_height; /* Maximum image height, in pixels */
|
||||||
} sixel;
|
} sixel;
|
||||||
|
|
||||||
|
url_list_t urls;
|
||||||
|
wchar_t url_keys[5];
|
||||||
|
|
||||||
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||||
struct {
|
struct {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
|
@ -646,3 +664,6 @@ void term_ime_disable(struct terminal *term);
|
||||||
void term_ime_reset(struct terminal *term);
|
void term_ime_reset(struct terminal *term);
|
||||||
void term_ime_set_cursor_rect(
|
void term_ime_set_cursor_rect(
|
||||||
struct terminal *term, int x, int y, int width, int height);
|
struct terminal *term, int x, int y, int width, int height);
|
||||||
|
|
||||||
|
void term_urls_reset(struct terminal *term);
|
||||||
|
void term_collect_urls(struct terminal *term);
|
||||||
|
|
|
||||||
555
url-mode.c
Normal file
555
url-mode.c
Normal file
|
|
@ -0,0 +1,555 @@
|
||||||
|
#include "url-mode.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <wctype.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "url-mode"
|
||||||
|
#define LOG_ENABLE_DBG 1
|
||||||
|
#include "log.h"
|
||||||
|
#include "grid.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "selection.h"
|
||||||
|
#include "spawn.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
case BIND_ACTION_URL_COUNT:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
activate_url(struct seat *seat, struct terminal *term, const struct url *url)
|
||||||
|
{
|
||||||
|
size_t chars = wcstombs(NULL, url->url, 0);
|
||||||
|
|
||||||
|
if (chars != (size_t)-1) {
|
||||||
|
char *url_utf8 = xmalloc(chars + 1);
|
||||||
|
wcstombs(url_utf8, url->url, chars + 1);
|
||||||
|
|
||||||
|
switch (url->action) {
|
||||||
|
case URL_ACTION_COPY:
|
||||||
|
if (text_to_clipboard(seat, term, url_utf8, seat->kbd.serial)) {
|
||||||
|
/* Now owned by our clipboard “manager” */
|
||||||
|
url_utf8 = NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case URL_ACTION_LAUNCH: {
|
||||||
|
size_t argc;
|
||||||
|
char **argv;
|
||||||
|
|
||||||
|
if (spawn_expand_template(
|
||||||
|
&term->conf->url_launch, 1,
|
||||||
|
(const char *[]){"url"},
|
||||||
|
(const char *[]){url_utf8},
|
||||||
|
&argc, &argv))
|
||||||
|
{
|
||||||
|
spawn(term->reaper, term->cwd, argv, -1, -1, -1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
free(argv[i]);
|
||||||
|
free(argv);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(url_utf8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (it->item.bind.mods != mods)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Match symbol */
|
||||||
|
if (it->item.bind.sym == sym) {
|
||||||
|
execute_binding(seat, term, it->item.action, serial);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match raw key code */
|
||||||
|
tll_foreach(it->item.bind.key_codes, code) {
|
||||||
|
if (code->item == key) {
|
||||||
|
execute_binding(seat, term, it->item.action, serial);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const struct url *url = &it->item;
|
||||||
|
const size_t key_len = wcslen(it->item.key);
|
||||||
|
|
||||||
|
if (key_len >= seq_len + 1 &&
|
||||||
|
wcsncmp(url->key, term->url_keys, seq_len) == 0 &&
|
||||||
|
url->key[seq_len] == wc)
|
||||||
|
{
|
||||||
|
is_valid = true;
|
||||||
|
if (key_len == seq_len + 1) {
|
||||||
|
match = url;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
activate_url(seat, term, match);
|
||||||
|
urls_reset(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (is_valid) {
|
||||||
|
xassert(seq_len + 1 <= ALEN(term->url_keys));
|
||||||
|
term->url_keys[seq_len] = wc;
|
||||||
|
render_refresh_urls(term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IGNORE_WARNING("-Wpedantic")
|
||||||
|
|
||||||
|
static void
|
||||||
|
auto_detected(const struct terminal *term, enum url_action action, url_list_t *urls)
|
||||||
|
{
|
||||||
|
static const wchar_t *const prots[] = {
|
||||||
|
L"http://",
|
||||||
|
L"https://",
|
||||||
|
L"ftp://",
|
||||||
|
L"ftps://",
|
||||||
|
L"file://",
|
||||||
|
L"gemini://",
|
||||||
|
L"gopher://",
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
ssize_t parenthesis = 0;
|
||||||
|
ssize_t brackets = 0;
|
||||||
|
|
||||||
|
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];
|
||||||
|
wchar_t wc = cell->wc;
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
if (wcsncasecmp(prots[i], proto, prot_len) == 0) {
|
||||||
|
state = STATE_URL;
|
||||||
|
start = proto_start[max_prot_len - prot_len];
|
||||||
|
|
||||||
|
wcsncpy(url, proto, prot_len);
|
||||||
|
len = prot_len;
|
||||||
|
|
||||||
|
parenthesis = brackets = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATE_URL: {
|
||||||
|
// static const wchar_t allowed[] =
|
||||||
|
// L"abcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=";
|
||||||
|
// static const wchar_t unwise[] = L"{}|\\^[]`";
|
||||||
|
// static const wchar_t reserved[] = L";/?:@&=+$,";
|
||||||
|
|
||||||
|
bool emit_url = false;
|
||||||
|
switch (wc) {
|
||||||
|
case L'a'...L'z':
|
||||||
|
case L'A'...L'Z':
|
||||||
|
case L'0'...L'9':
|
||||||
|
case L'-': case L'.': case L'_': case L'~': case L':':
|
||||||
|
case L'/': case L'?': case L'#': case L'@': case L'!':
|
||||||
|
case L'$': case L'&': case L'\'': case L'*': case L'+':
|
||||||
|
case L',': case L';': case L'=': case L'"':
|
||||||
|
url[len++] = wc;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L'(':
|
||||||
|
parenthesis++;
|
||||||
|
url[len++] = wc;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L'[':
|
||||||
|
brackets++;
|
||||||
|
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;
|
||||||
|
|
||||||
|
default:
|
||||||
|
emit_url = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c >= term->cols - 1 && row->linebreak)
|
||||||
|
emit_url = true;
|
||||||
|
|
||||||
|
if (emit_url) {
|
||||||
|
/* 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};
|
||||||
|
|
||||||
|
if (--end.col < 0) {
|
||||||
|
end.row--;
|
||||||
|
end.col = term->cols - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
switch (url[len - 1]) {
|
||||||
|
case L'.': case L',': case L':': case L';': case L'?':
|
||||||
|
case L'!': case L'"': case L'\'':
|
||||||
|
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';
|
||||||
|
|
||||||
|
start.row += term->grid->view;
|
||||||
|
end.row += term->grid->view;
|
||||||
|
|
||||||
|
tll_push_back(
|
||||||
|
*urls,
|
||||||
|
((struct url){
|
||||||
|
.url = xwcsdup(url),
|
||||||
|
.text = xwcsdup(L""),
|
||||||
|
.start = start,
|
||||||
|
.end = end,
|
||||||
|
.action = action}));
|
||||||
|
|
||||||
|
state = STATE_PROTOCOL;
|
||||||
|
len = 0;
|
||||||
|
parenthesis = brackets = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UNIGNORE_WARNINGS
|
||||||
|
|
||||||
|
void
|
||||||
|
urls_collect(const struct terminal *term, enum url_action action, url_list_t *urls)
|
||||||
|
{
|
||||||
|
xassert(tll_length(term->urls) == 0);
|
||||||
|
auto_detected(term, action, urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void url_destroy(struct url *url);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
generate_key_combos(size_t count, wchar_t *combos[static count])
|
||||||
|
{
|
||||||
|
/* vimium default */
|
||||||
|
static const wchar_t alphabet[] = L"sadfjklewcmpgh";
|
||||||
|
static const size_t alphabet_len = ALEN(alphabet) - 1;
|
||||||
|
|
||||||
|
size_t hints_count = 1;
|
||||||
|
wchar_t **hints = xmalloc(hints_count * sizeof(hints[0]));
|
||||||
|
|
||||||
|
hints[0] = xwcsdup(L"");
|
||||||
|
|
||||||
|
size_t offset = 0;
|
||||||
|
do {
|
||||||
|
const wchar_t *prefix = hints[offset++];
|
||||||
|
const size_t prefix_len = wcslen(prefix);
|
||||||
|
|
||||||
|
hints = xrealloc(hints, (hints_count + alphabet_len) * sizeof(hints[0]));
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/* Will be reversed later */
|
||||||
|
hint[0] = *wc;
|
||||||
|
wcscpy(&hint[1], prefix);
|
||||||
|
}
|
||||||
|
hints_count += alphabet_len;
|
||||||
|
} while (hints_count - offset < count);
|
||||||
|
|
||||||
|
xassert(hints_count - offset >= count);
|
||||||
|
|
||||||
|
/* 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];
|
||||||
|
else
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
urls_assign_key_combos(url_list_t *urls)
|
||||||
|
{
|
||||||
|
const size_t count = tll_length(*urls);
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
wchar_t *combos[count];
|
||||||
|
generate_key_combos(count, combos);
|
||||||
|
|
||||||
|
size_t idx = 0;
|
||||||
|
tll_foreach(*urls, it)
|
||||||
|
it->item.key = combos[idx++];
|
||||||
|
|
||||||
|
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
||||||
|
tll_foreach(*urls, it) {
|
||||||
|
char url[1024];
|
||||||
|
wcstombs(url, it->item.url, sizeof(url) - 1);
|
||||||
|
|
||||||
|
char key[32];
|
||||||
|
wcstombs(key, it->item.key, sizeof(key) - 1);
|
||||||
|
|
||||||
|
LOG_DBG("URL: %s (%s)", url, key);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tag_cells_for_url(struct terminal *term, const struct url *url, bool value)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
struct wl_window *win = term->window;
|
||||||
|
struct wayland *wayl = term->wl;
|
||||||
|
|
||||||
|
if (tll_length(win->term->urls) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
xassert(tll_length(win->urls) == 0);
|
||||||
|
tll_foreach(win->term->urls, it) {
|
||||||
|
struct wl_surface *surf = wl_compositor_create_surface(wayl->compositor);
|
||||||
|
wl_surface_set_user_data(surf, win);
|
||||||
|
|
||||||
|
struct wl_subsurface *sub_surf = NULL;
|
||||||
|
|
||||||
|
if (surf != NULL) {
|
||||||
|
sub_surf = wl_subcompositor_get_subsurface(
|
||||||
|
wayl->sub_compositor, surf, win->surface);
|
||||||
|
|
||||||
|
if (sub_surf != NULL)
|
||||||
|
wl_subsurface_set_sync(sub_surf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surf == NULL || sub_surf == NULL) {
|
||||||
|
LOG_WARN("failed to create URL (sub)-surface");
|
||||||
|
|
||||||
|
if (surf != NULL) {
|
||||||
|
wl_surface_destroy(surf);
|
||||||
|
surf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sub_surf != NULL) {
|
||||||
|
wl_subsurface_destroy(sub_surf);
|
||||||
|
sub_surf = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wl_url url = {
|
||||||
|
.url = &it->item,
|
||||||
|
.surf = surf,
|
||||||
|
.sub_surf = sub_surf,
|
||||||
|
};
|
||||||
|
|
||||||
|
tll_push_back(win->urls, url);
|
||||||
|
tag_cells_for_url(term, &it->item, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
render_refresh_urls(term);
|
||||||
|
render_refresh(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
url_destroy(struct url *url)
|
||||||
|
{
|
||||||
|
free(url->url);
|
||||||
|
free(url->text);
|
||||||
|
free(url->key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
urls_reset(struct terminal *term)
|
||||||
|
{
|
||||||
|
if (likely(tll_length(term->urls) == 0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (term->window != NULL) {
|
||||||
|
tll_foreach(term->window->urls, it) {
|
||||||
|
if (it->item.sub_surf != NULL)
|
||||||
|
wl_subsurface_destroy(it->item.sub_surf);
|
||||||
|
if (it->item.surf != NULL)
|
||||||
|
wl_surface_destroy(it->item.surf);
|
||||||
|
}
|
||||||
|
tll_free(term->window->urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
tll_foreach(term->urls, it) {
|
||||||
|
tag_cells_for_url(term, &it->item, false);
|
||||||
|
url_destroy(&it->item);
|
||||||
|
}
|
||||||
|
tll_free(term->urls);
|
||||||
|
|
||||||
|
memset(term->url_keys, 0, sizeof(term->url_keys));
|
||||||
|
render_refresh(term);
|
||||||
|
}
|
||||||
22
url-mode.h
Normal file
22
url-mode.h
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
#include <tllist.h>
|
||||||
|
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
static inline bool urls_mode_is_active(const struct terminal *term)
|
||||||
|
{
|
||||||
|
return tll_length(term->urls) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void urls_collect(
|
||||||
|
const struct terminal *term, enum url_action action, url_list_t *urls);
|
||||||
|
void urls_assign_key_combos(url_list_t *urls);
|
||||||
|
|
||||||
|
void urls_render(struct terminal *term);
|
||||||
|
void urls_reset(struct terminal *term);
|
||||||
|
|
||||||
|
void urls_input(struct seat *seat, struct terminal *term, uint32_t key,
|
||||||
|
xkb_keysym_t sym, xkb_mod_mask_t mods, uint32_t serial);
|
||||||
12
wayland.c
12
wayland.c
|
|
@ -148,6 +148,10 @@ seat_destroy(struct seat *seat)
|
||||||
tll_free(it->item.bind.key_codes);
|
tll_free(it->item.bind.key_codes);
|
||||||
tll_free(seat->kbd.bindings.search);
|
tll_free(seat->kbd.bindings.search);
|
||||||
|
|
||||||
|
tll_foreach(seat->kbd.bindings.url, it)
|
||||||
|
tll_free(it->item.bind.key_codes);
|
||||||
|
tll_free(seat->kbd.bindings.url);
|
||||||
|
|
||||||
tll_free(seat->mouse.bindings);
|
tll_free(seat->mouse.bindings);
|
||||||
|
|
||||||
if (seat->kbd.xkb_compose_state != NULL)
|
if (seat->kbd.xkb_compose_state != NULL)
|
||||||
|
|
@ -1422,6 +1426,14 @@ wayl_win_destroy(struct wl_window *win)
|
||||||
|
|
||||||
tll_free(win->on_outputs);
|
tll_free(win->on_outputs);
|
||||||
|
|
||||||
|
tll_foreach(win->urls, it) {
|
||||||
|
if (it->item.sub_surf != NULL)
|
||||||
|
wl_subsurface_destroy(it->item.sub_surf);
|
||||||
|
if (it->item.surf != NULL)
|
||||||
|
wl_surface_destroy(it->item.surf);
|
||||||
|
}
|
||||||
|
tll_free(win->urls);
|
||||||
|
|
||||||
csd_destroy(win);
|
csd_destroy(win);
|
||||||
if (win->render_timer_sub_surface != NULL)
|
if (win->render_timer_sub_surface != NULL)
|
||||||
wl_subsurface_destroy(win->render_timer_sub_surface);
|
wl_subsurface_destroy(win->render_timer_sub_surface);
|
||||||
|
|
|
||||||
23
wayland.h
23
wayland.h
|
|
@ -49,6 +49,8 @@ enum bind_action_normal {
|
||||||
BIND_ACTION_PIPE_SCROLLBACK,
|
BIND_ACTION_PIPE_SCROLLBACK,
|
||||||
BIND_ACTION_PIPE_VIEW,
|
BIND_ACTION_PIPE_VIEW,
|
||||||
BIND_ACTION_PIPE_SELECTED,
|
BIND_ACTION_PIPE_SELECTED,
|
||||||
|
BIND_ACTION_SHOW_URLS_COPY,
|
||||||
|
BIND_ACTION_SHOW_URLS_LAUNCH,
|
||||||
|
|
||||||
/* Mouse specific actions - i.e. they require a mouse coordinate */
|
/* Mouse specific actions - i.e. they require a mouse coordinate */
|
||||||
BIND_ACTION_SELECT_BEGIN,
|
BIND_ACTION_SELECT_BEGIN,
|
||||||
|
|
@ -59,7 +61,7 @@ enum bind_action_normal {
|
||||||
BIND_ACTION_SELECT_WORD_WS,
|
BIND_ACTION_SELECT_WORD_WS,
|
||||||
BIND_ACTION_SELECT_ROW,
|
BIND_ACTION_SELECT_ROW,
|
||||||
|
|
||||||
BIND_ACTION_KEY_COUNT = BIND_ACTION_PIPE_SELECTED + 1,
|
BIND_ACTION_KEY_COUNT = BIND_ACTION_SHOW_URLS_LAUNCH + 1,
|
||||||
BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1,
|
BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -106,6 +108,17 @@ struct key_binding_search {
|
||||||
enum bind_action_search action;
|
enum bind_action_search action;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum bind_action_url {
|
||||||
|
BIND_ACTION_URL_NONE,
|
||||||
|
BIND_ACTION_URL_CANCEL,
|
||||||
|
BIND_ACTION_URL_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct key_binding_url {
|
||||||
|
struct key_binding bind;
|
||||||
|
enum bind_action_url action;
|
||||||
|
};
|
||||||
|
|
||||||
/* Mime-types we support when dealing with data offers (e.g. copy-paste, or DnD) */
|
/* Mime-types we support when dealing with data offers (e.g. copy-paste, or DnD) */
|
||||||
enum data_offer_mime_type {
|
enum data_offer_mime_type {
|
||||||
DATA_OFFER_MIME_UNSET,
|
DATA_OFFER_MIME_UNSET,
|
||||||
|
|
@ -192,6 +205,7 @@ struct seat {
|
||||||
struct {
|
struct {
|
||||||
tll(struct key_binding_normal) key;
|
tll(struct key_binding_normal) key;
|
||||||
tll(struct key_binding_search) search;
|
tll(struct key_binding_search) search;
|
||||||
|
tll(struct key_binding_url) url;
|
||||||
} bindings;
|
} bindings;
|
||||||
} kbd;
|
} kbd;
|
||||||
|
|
||||||
|
|
@ -349,6 +363,12 @@ struct monitor {
|
||||||
bool use_output_release;
|
bool use_output_release;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct wl_url {
|
||||||
|
const struct url *url;
|
||||||
|
struct wl_surface *surf;
|
||||||
|
struct wl_subsurface *sub_surf;
|
||||||
|
};
|
||||||
|
|
||||||
struct wayland;
|
struct wayland;
|
||||||
struct wl_window {
|
struct wl_window {
|
||||||
struct terminal *term;
|
struct terminal *term;
|
||||||
|
|
@ -380,6 +400,7 @@ struct wl_window {
|
||||||
struct wl_callback *frame_callback;
|
struct wl_callback *frame_callback;
|
||||||
|
|
||||||
tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */
|
tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */
|
||||||
|
tll(struct wl_url) urls;
|
||||||
|
|
||||||
bool is_configured;
|
bool is_configured;
|
||||||
bool is_fullscreen;
|
bool is_fullscreen;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue