mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-04-05 07:15:30 -04:00
commit
2c881b9f06
15 changed files with 658 additions and 98 deletions
|
|
@ -37,8 +37,8 @@
|
||||||
* Key/mouse binding `select-extend-character-wise`, which forces the
|
* Key/mouse binding `select-extend-character-wise`, which forces the
|
||||||
selection mode to 'character-wise' when extending a selection.
|
selection mode to 'character-wise' when extending a selection.
|
||||||
* `DECSET` `47`, `1047` and `1048`.
|
* `DECSET` `47`, `1047` and `1048`.
|
||||||
* URL detection. URLs are highlighted and activated using the keyboard
|
* URL detection and OSC-8 support. URLs are highlighted and activated
|
||||||
(**no** mouse support). See **foot**(1)::URLs, or
|
using the keyboard (**no** mouse support). See **foot**(1)::URLs, or
|
||||||
[README.md](README.md#urls) for details
|
[README.md](README.md#urls) for details
|
||||||
(https://codeberg.org/dnkl/foot/issues/14).
|
(https://codeberg.org/dnkl/foot/issues/14).
|
||||||
* `-d,--log-level={info|warning|error}` to both `foot` and
|
* `-d,--log-level={info|warning|error}` to both `foot` and
|
||||||
|
|
|
||||||
17
config.c
17
config.c
|
|
@ -119,6 +119,7 @@ static_assert(ALEN(search_binding_action_map) == BIND_ACTION_SEARCH_COUNT,
|
||||||
static const char *const url_binding_action_map[] = {
|
static const char *const url_binding_action_map[] = {
|
||||||
[BIND_ACTION_URL_NONE] = NULL,
|
[BIND_ACTION_URL_NONE] = NULL,
|
||||||
[BIND_ACTION_URL_CANCEL] = "cancel",
|
[BIND_ACTION_URL_CANCEL] = "cancel",
|
||||||
|
[BIND_ACTION_URL_TOGGLE_URL_ON_JUMP_LABEL] = "toggle-url-visible",
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(ALEN(url_binding_action_map) == BIND_ACTION_URL_COUNT,
|
static_assert(ALEN(url_binding_action_map) == BIND_ACTION_URL_COUNT,
|
||||||
|
|
@ -790,6 +791,19 @@ parse_section_main(const char *key, const char *value, struct config *conf,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (strcmp(key, "osc8-underline") == 0) {
|
||||||
|
if (strcmp(value, "url-mode") == 0)
|
||||||
|
conf->osc8_underline = OSC8_UNDERLINE_URL_MODE;
|
||||||
|
else if (strcmp(value, "always") == 0)
|
||||||
|
conf->osc8_underline = OSC8_UNDERLINE_ALWAYS;
|
||||||
|
else {
|
||||||
|
LOG_AND_NOTIFY_ERR(
|
||||||
|
"%s:%u: [default]: %s: invalid 'osc8-underline'; "
|
||||||
|
"must be one of 'url-mode', or 'always'", path, lineno, value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
LOG_AND_NOTIFY_ERR("%s:%u: [default]: %s: invalid key", path, lineno, key);
|
LOG_AND_NOTIFY_ERR("%s:%u: [default]: %s: invalid key", path, lineno, key);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -2107,6 +2121,7 @@ add_default_url_bindings(struct config *conf)
|
||||||
add_binding(BIND_ACTION_URL_CANCEL, ctrl, XKB_KEY_g);
|
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, ctrl, XKB_KEY_d);
|
||||||
add_binding(BIND_ACTION_URL_CANCEL, none, XKB_KEY_Escape);
|
add_binding(BIND_ACTION_URL_CANCEL, none, XKB_KEY_Escape);
|
||||||
|
add_binding(BIND_ACTION_URL_TOGGLE_URL_ON_JUMP_LABEL, none, XKB_KEY_t);
|
||||||
|
|
||||||
#undef add_binding
|
#undef add_binding
|
||||||
}
|
}
|
||||||
|
|
@ -2241,6 +2256,8 @@ config_load(struct config *conf, const char *conf_path,
|
||||||
.argv = NULL,
|
.argv = NULL,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.osc8_underline = OSC8_UNDERLINE_URL_MODE,
|
||||||
|
|
||||||
.tweak = {
|
.tweak = {
|
||||||
.fcft_filter = FCFT_SCALING_FILTER_LANCZOS3,
|
.fcft_filter = FCFT_SCALING_FILTER_LANCZOS3,
|
||||||
.allow_overflowing_double_width_glyphs = true,
|
.allow_overflowing_double_width_glyphs = true,
|
||||||
|
|
|
||||||
5
config.h
5
config.h
|
|
@ -205,6 +205,11 @@ struct config {
|
||||||
struct config_spawn_template notify;
|
struct config_spawn_template notify;
|
||||||
struct config_spawn_template url_launch;
|
struct config_spawn_template url_launch;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
OSC8_UNDERLINE_URL_MODE,
|
||||||
|
OSC8_UNDERLINE_ALWAYS,
|
||||||
|
} osc8_underline;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
enum fcft_scaling_filter fcft_filter;
|
enum fcft_scaling_filter fcft_filter;
|
||||||
bool allow_overflowing_double_width_glyphs;
|
bool allow_overflowing_double_width_glyphs;
|
||||||
|
|
|
||||||
|
|
@ -241,13 +241,25 @@ in this order:
|
||||||
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_.
|
||||||
|
|
||||||
|
|
||||||
*workers*
|
*workers*
|
||||||
Number of threads to use for rendering. Set to 0 to disable
|
Number of threads to use for rendering. Set to 0 to disable
|
||||||
multithreading. Default: the number of available logical CPUs
|
multithreading. Default: the number of available logical CPUs
|
||||||
(including SMT). Note that this is not always the best value. In
|
(including SMT). Note that this is not always the best value. In
|
||||||
some cases, the number of physical _cores_ is better.
|
some cases, the number of physical _cores_ is better.
|
||||||
|
|
||||||
|
*osc8-underline*
|
||||||
|
When to underline OSC-8 URLs. Possible values are *url-mode* and
|
||||||
|
*always*.
|
||||||
|
|
||||||
|
When set to *url-mode*, OSC-8 URLs are only highlighted in URL
|
||||||
|
mode, just like auto-detected URLs.
|
||||||
|
|
||||||
|
When set to *always*, OSC-8 URLs are always highlighted,
|
||||||
|
regardless of their other attributes (bold, italic etc). Note that
|
||||||
|
this does _not_ make them clickable.
|
||||||
|
|
||||||
|
Default: _url-mode_
|
||||||
|
|
||||||
|
|
||||||
# SECTION: scrollback
|
# SECTION: scrollback
|
||||||
|
|
||||||
|
|
@ -610,6 +622,20 @@ mode. The syntax is exactly the same as the regular **key-bindings**.
|
||||||
Exits URL mode without opening an URL. Default: _Control+g
|
Exits URL mode without opening an URL. Default: _Control+g
|
||||||
Control+d Escape_.
|
Control+d Escape_.
|
||||||
|
|
||||||
|
*toggle-url-visible*
|
||||||
|
By default, the jump label only shows the key sequence required to
|
||||||
|
activate it. This is fine as long as the URL is visible in the
|
||||||
|
original text.
|
||||||
|
|
||||||
|
But with e.g. OSC-8 URLs (the terminal version of HTML anchors,
|
||||||
|
i.e. "links"), the text on the screen can be something completey
|
||||||
|
different than the URL.
|
||||||
|
|
||||||
|
This action toggles between showing and hiding the URL on the jump
|
||||||
|
label.
|
||||||
|
|
||||||
|
Default: _t_.
|
||||||
|
|
||||||
|
|
||||||
# SECTION: mouse-bindings
|
# SECTION: mouse-bindings
|
||||||
|
|
||||||
|
|
|
||||||
2
foot.ini
2
foot.ini
|
|
@ -29,6 +29,7 @@
|
||||||
# jump-label-letters=sadfjklewcmpgh
|
# jump-label-letters=sadfjklewcmpgh
|
||||||
# selection-target=primary
|
# selection-target=primary
|
||||||
# workers=<number of logical CPUs>
|
# workers=<number of logical CPUs>
|
||||||
|
# osc8-underline=url-mode
|
||||||
|
|
||||||
[scrollback]
|
[scrollback]
|
||||||
# lines=1000
|
# lines=1000
|
||||||
|
|
@ -125,6 +126,7 @@
|
||||||
|
|
||||||
[url-bindings]
|
[url-bindings]
|
||||||
# cancel=Control+g Control+d Escape
|
# cancel=Control+g Control+d Escape
|
||||||
|
# toggle-url-visible=t
|
||||||
|
|
||||||
[mouse-bindings]
|
[mouse-bindings]
|
||||||
# primary-paste=BTN_MIDDLE
|
# primary-paste=BTN_MIDDLE
|
||||||
|
|
|
||||||
190
grid.c
190
grid.c
|
|
@ -33,6 +33,7 @@ grid_row_alloc(int cols, bool initialize)
|
||||||
struct row *row = xmalloc(sizeof(*row));
|
struct row *row = xmalloc(sizeof(*row));
|
||||||
row->dirty = false;
|
row->dirty = false;
|
||||||
row->linebreak = false;
|
row->linebreak = false;
|
||||||
|
row->extra = NULL;
|
||||||
|
|
||||||
if (initialize) {
|
if (initialize) {
|
||||||
row->cells = xcalloc(cols, sizeof(row->cells[0]));
|
row->cells = xcalloc(cols, sizeof(row->cells[0]));
|
||||||
|
|
@ -50,6 +51,8 @@ grid_row_free(struct row *row)
|
||||||
if (row == NULL)
|
if (row == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
grid_row_reset_extra(row);
|
||||||
|
free(row->extra);
|
||||||
free(row->cells);
|
free(row->cells);
|
||||||
free(row);
|
free(row);
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +114,25 @@ grid_resize_without_reflow(
|
||||||
sixel_destroy(&it->item);
|
sixel_destroy(&it->item);
|
||||||
tll_remove(untranslated_sixels, it);
|
tll_remove(untranslated_sixels, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Copy URI ranges, truncating them if necessary */
|
||||||
|
if (old_row->extra == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tll_foreach(old_row->extra->uri_ranges, it) {
|
||||||
|
if (it->item.start >= new_rows) {
|
||||||
|
/* The whole range is truncated */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct row_uri_range range = {
|
||||||
|
.start = it->item.start,
|
||||||
|
.end = min(it->item.end, new_cols - 1),
|
||||||
|
.id = it->item.id,
|
||||||
|
.uri = xstrdup(it->item.uri),
|
||||||
|
};
|
||||||
|
grid_row_add_uri_range(new_row, range);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear "new" lines */
|
/* Clear "new" lines */
|
||||||
|
|
@ -119,7 +141,6 @@ grid_resize_without_reflow(
|
||||||
new_grid[(new_offset + r) & (new_rows - 1)] = new_row;
|
new_grid[(new_offset + r) & (new_rows - 1)] = new_row;
|
||||||
|
|
||||||
memset(new_row->cells, 0, sizeof(struct cell) * new_cols);
|
memset(new_row->cells, 0, sizeof(struct cell) * new_cols);
|
||||||
new_row->linebreak = false;
|
|
||||||
new_row->dirty = true;
|
new_row->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,6 +187,103 @@ grid_resize_without_reflow(
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
reflow_uri_ranges(const struct row *old_row, struct row *new_row,
|
||||||
|
int old_col_idx, int new_col_idx)
|
||||||
|
{
|
||||||
|
if (old_row->extra == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for URI range start/end points on the “old” row, and
|
||||||
|
* open/close a corresponding URI range on the “new” row.
|
||||||
|
*/
|
||||||
|
|
||||||
|
tll_foreach(old_row->extra->uri_ranges, it) {
|
||||||
|
if (it->item.start == old_col_idx) {
|
||||||
|
struct row_uri_range new_range = {
|
||||||
|
.start = new_col_idx,
|
||||||
|
.end = -1,
|
||||||
|
.id = it->item.id,
|
||||||
|
.uri = xstrdup(it->item.uri),
|
||||||
|
};
|
||||||
|
grid_row_add_uri_range(new_row, new_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (it->item.end == old_col_idx) {
|
||||||
|
xassert(new_row->extra != NULL);
|
||||||
|
|
||||||
|
bool found_it = false;
|
||||||
|
tll_foreach(new_row->extra->uri_ranges, it2) {
|
||||||
|
if (it2->item.id != it->item.id)
|
||||||
|
continue;
|
||||||
|
if (it2->item.end >= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
it2->item.end = new_col_idx;
|
||||||
|
found_it = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
xassert(found_it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct row *
|
||||||
|
_line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row,
|
||||||
|
int *row_idx, int *col_idx, int row_count, int col_count)
|
||||||
|
{
|
||||||
|
*col_idx = 0;
|
||||||
|
*row_idx = (*row_idx + 1) & (row_count - 1);
|
||||||
|
|
||||||
|
struct row *new_row = new_grid[*row_idx];
|
||||||
|
|
||||||
|
if (new_row == NULL) {
|
||||||
|
/* Scrollback not yet full, allocate a completely new row */
|
||||||
|
new_row = grid_row_alloc(col_count, true);
|
||||||
|
new_grid[*row_idx] = new_row;
|
||||||
|
} else {
|
||||||
|
/* Scrollback is full, need to re-use a row */
|
||||||
|
memset(new_row->cells, 0, col_count * sizeof(new_row->cells[0]));
|
||||||
|
grid_row_reset_extra(new_row);
|
||||||
|
new_row->linebreak = false;
|
||||||
|
|
||||||
|
tll_foreach(old_grid->sixel_images, it) {
|
||||||
|
if (it->item.pos.row == *row_idx) {
|
||||||
|
sixel_destroy(&it->item);
|
||||||
|
tll_remove(old_grid->sixel_images, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row->extra == NULL)
|
||||||
|
return new_row;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* URI ranges are per row. Thus, we need to ‘close’ the still-open
|
||||||
|
* ranges on the previous row, and re-open them on the
|
||||||
|
* next/current row.
|
||||||
|
*/
|
||||||
|
tll_foreach(row->extra->uri_ranges, it) {
|
||||||
|
if (it->item.end >= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Terminate URI range on the previous row */
|
||||||
|
it->item.end = col_count - 1;
|
||||||
|
|
||||||
|
/* Open a new range on the new/current row */
|
||||||
|
struct row_uri_range new_range = {
|
||||||
|
.start = 0,
|
||||||
|
.end = -1,
|
||||||
|
.id = it->item.id,
|
||||||
|
.uri = xstrdup(it->item.uri),
|
||||||
|
};
|
||||||
|
grid_row_add_uri_range(new_row, new_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_row;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
grid_resize_and_reflow(
|
grid_resize_and_reflow(
|
||||||
struct grid *grid, int new_rows, int new_cols,
|
struct grid *grid, int new_rows, int new_cols,
|
||||||
|
|
@ -245,29 +363,13 @@ grid_resize_and_reflow(
|
||||||
tll_remove(untranslated_sixels, it);
|
tll_remove(untranslated_sixels, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define line_wrap() \
|
#define line_wrap() \
|
||||||
do { \
|
new_row = _line_wrap( \
|
||||||
new_col_idx = 0; \
|
grid, new_grid, new_row, &new_row_idx, &new_col_idx, \
|
||||||
new_row_idx = (new_row_idx + 1) & (new_rows - 1); \
|
new_rows, new_cols)
|
||||||
\
|
|
||||||
new_row = new_grid[new_row_idx]; \
|
|
||||||
if (new_row == NULL) { \
|
|
||||||
new_row = grid_row_alloc(new_cols, true); \
|
|
||||||
new_grid[new_row_idx] = new_row; \
|
|
||||||
} else { \
|
|
||||||
memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); \
|
|
||||||
new_row->linebreak = false; \
|
|
||||||
tll_foreach(grid->sixel_images, it) { \
|
|
||||||
if (it->item.pos.row == new_row_idx) { \
|
|
||||||
sixel_destroy(&it->item); \
|
|
||||||
tll_remove(grid->sixel_images, it); \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
#define print_spacer() \
|
#define print_spacer() \
|
||||||
do { \
|
do { \
|
||||||
new_row->cells[new_col_idx].wc = CELL_MULT_COL_SPACER; \
|
new_row->cells[new_col_idx].wc = CELL_MULT_COL_SPACER; \
|
||||||
new_row->cells[new_col_idx].attrs = old_cell->attrs; \
|
new_row->cells[new_col_idx].attrs = old_cell->attrs; \
|
||||||
new_row->cells[new_col_idx].attrs.clean = 1; \
|
new_row->cells[new_col_idx].attrs.clean = 1; \
|
||||||
|
|
@ -294,6 +396,17 @@ grid_resize_and_reflow(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If there’s an URI start/end point here, we need to make
|
||||||
|
* sure we handle it */
|
||||||
|
if (old_row->extra != NULL) {
|
||||||
|
tll_foreach(old_row->extra->uri_ranges, it) {
|
||||||
|
if (it->item.start == c || it->item.end == c) {
|
||||||
|
is_tracking_point = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (old_row->cells[c].wc == 0 && !is_tracking_point) {
|
if (old_row->cells[c].wc == 0 && !is_tracking_point) {
|
||||||
empty_count++;
|
empty_count++;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -356,6 +469,8 @@ grid_resize_and_reflow(
|
||||||
tll_remove(tracking_points, it);
|
tll_remove(tracking_points, it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reflow_uri_ranges(old_row, new_row, c, new_col_idx);
|
||||||
}
|
}
|
||||||
new_col_idx++;
|
new_col_idx++;
|
||||||
}
|
}
|
||||||
|
|
@ -382,6 +497,21 @@ grid_resize_and_reflow(
|
||||||
#undef line_wrap
|
#undef line_wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(_DEBUG)
|
||||||
|
/* Verify all URI ranges have been “closed” */
|
||||||
|
for (int r = 0; r < new_rows; r++) {
|
||||||
|
const struct row *row = new_grid[r];
|
||||||
|
|
||||||
|
if (row == NULL)
|
||||||
|
continue;
|
||||||
|
if (row->extra == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tll_foreach(row->extra->uri_ranges, it)
|
||||||
|
xassert(it->item.end >= 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Set offset such that the last reflowed row is at the bottom */
|
/* Set offset such that the last reflowed row is at the bottom */
|
||||||
grid->offset = new_row_idx - new_screen_rows + 1;
|
grid->offset = new_row_idx - new_screen_rows + 1;
|
||||||
while (grid->offset < 0)
|
while (grid->offset < 0)
|
||||||
|
|
@ -449,3 +579,17 @@ grid_resize_and_reflow(
|
||||||
|
|
||||||
tll_free(tracking_points);
|
tll_free(tracking_points);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ensure_row_has_extra_data(struct row *row)
|
||||||
|
{
|
||||||
|
if (row->extra == NULL)
|
||||||
|
row->extra = xcalloc(1, sizeof(*row->extra));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
grid_row_add_uri_range(struct row *row, struct row_uri_range range)
|
||||||
|
{
|
||||||
|
ensure_row_has_extra_data(row);
|
||||||
|
tll_push_back(row->extra->uri_ranges, range);
|
||||||
|
}
|
||||||
|
|
|
||||||
17
grid.h
17
grid.h
|
|
@ -72,3 +72,20 @@ grid_row_in_view(struct grid *grid, int row_no)
|
||||||
xassert(row != NULL);
|
xassert(row != NULL);
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void grid_row_add_uri_range(struct row *row, struct row_uri_range range);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
grid_row_reset_extra(struct row *row)
|
||||||
|
{
|
||||||
|
if (likely(row->extra == NULL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
tll_foreach(row->extra->uri_ranges, it) {
|
||||||
|
free(it->item.uri);
|
||||||
|
tll_remove(row->extra->uri_ranges, it);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(row->extra);
|
||||||
|
row->extra = NULL;
|
||||||
|
}
|
||||||
|
|
|
||||||
2
main.c
2
main.c
|
|
@ -383,6 +383,8 @@ main(int argc, char *const *argv)
|
||||||
name.sysname, name.machine, sizeof(void *) * 8);
|
name.sysname, name.machine, sizeof(void *) * 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srand(time(NULL));
|
||||||
|
|
||||||
setlocale(LC_CTYPE, "");
|
setlocale(LC_CTYPE, "");
|
||||||
LOG_INFO("locale: %s", setlocale(LC_CTYPE, NULL));
|
LOG_INFO("locale: %s", setlocale(LC_CTYPE, NULL));
|
||||||
if (!locale_is_utf8()) {
|
if (!locale_is_utf8()) {
|
||||||
|
|
|
||||||
64
osc.c
64
osc.c
|
|
@ -1,5 +1,6 @@
|
||||||
#include "osc.h"
|
#include "osc.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
@ -15,6 +16,7 @@
|
||||||
#include "selection.h"
|
#include "selection.h"
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
#include "uri.h"
|
#include "uri.h"
|
||||||
|
#include "util.h"
|
||||||
#include "vt.h"
|
#include "vt.h"
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
#include "xsnprintf.h"
|
#include "xsnprintf.h"
|
||||||
|
|
@ -376,6 +378,64 @@ osc_set_pwd(struct terminal *term, char *string)
|
||||||
free(host);
|
free(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
osc_uri(struct terminal *term, char *string)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* \E]8;<params>;URI\e\\
|
||||||
|
*
|
||||||
|
* Params are key=value pairs, separated by ‘:’.
|
||||||
|
*
|
||||||
|
* The only defined key (as of 2020-05-31) is ‘id’, which is used
|
||||||
|
* to group split-up URIs:
|
||||||
|
*
|
||||||
|
* ╔═ file1 ════╗
|
||||||
|
* ║ ╔═ file2 ═══╗
|
||||||
|
* ║http://exa║Lorem ipsum║
|
||||||
|
* ║le.com ║ dolor sit ║
|
||||||
|
* ║ ║amet, conse║
|
||||||
|
* ╚══════════║ctetur adip║
|
||||||
|
* ╚═══════════╝
|
||||||
|
*
|
||||||
|
* This lets a terminal emulator highlight both parts at the same
|
||||||
|
* time (e.g. when hovering over one of the parts with the mouse).
|
||||||
|
*/
|
||||||
|
|
||||||
|
char *params = string;
|
||||||
|
char *params_end = strchr(params, ';');
|
||||||
|
if (params_end == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
*params_end = '\0';
|
||||||
|
const char *uri = params_end + 1;
|
||||||
|
uint64_t id = (uint64_t)rand() << 32 | rand();
|
||||||
|
|
||||||
|
char *ctx = NULL;
|
||||||
|
for (const char *key_value = strtok_r(params, ":", &ctx);
|
||||||
|
key_value != NULL;
|
||||||
|
key_value = strtok_r(NULL, ":", &ctx))
|
||||||
|
{
|
||||||
|
const char *key = key_value;
|
||||||
|
char *operator = strchr(key_value, '=');
|
||||||
|
|
||||||
|
if (operator == NULL)
|
||||||
|
continue;
|
||||||
|
*operator = '\0';
|
||||||
|
|
||||||
|
const char *value = operator + 1;
|
||||||
|
|
||||||
|
if (strcmp(key, "id") == 0)
|
||||||
|
id = sdbm_hash(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("OSC-8: URL=%s, id=%" PRIu64, uri, id);
|
||||||
|
|
||||||
|
if (uri[0] == '\0')
|
||||||
|
term_osc8_close(term);
|
||||||
|
else
|
||||||
|
term_osc8_open(term, id, uri);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
osc_notify(struct terminal *term, char *string)
|
osc_notify(struct terminal *term, char *string)
|
||||||
{
|
{
|
||||||
|
|
@ -553,6 +613,10 @@ osc_dispatch(struct terminal *term)
|
||||||
osc_set_pwd(term, string);
|
osc_set_pwd(term, string);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
osc_uri(term, string);
|
||||||
|
break;
|
||||||
|
|
||||||
case 10:
|
case 10:
|
||||||
case 11: {
|
case 11: {
|
||||||
/* Set default foreground/background color */
|
/* Set default foreground/background color */
|
||||||
|
|
|
||||||
37
render.c
37
render.c
|
|
@ -2525,12 +2525,19 @@ render_urls(struct terminal *term)
|
||||||
+ term->grid->num_rows) & (term->grid->num_rows - 1);
|
+ term->grid->num_rows) & (term->grid->num_rows - 1);
|
||||||
const int view_end = view_start + term->rows - 1;
|
const int view_end = view_start + term->rows - 1;
|
||||||
|
|
||||||
|
const bool show_url = term->urls_show_uri_on_jump_label;
|
||||||
|
|
||||||
tll_foreach(win->urls, it) {
|
tll_foreach(win->urls, it) {
|
||||||
const struct url *url = it->item.url;
|
const struct url *url = it->item.url;
|
||||||
const wchar_t *text = url->text;
|
|
||||||
const wchar_t *key = url->key;
|
const wchar_t *key = url->key;
|
||||||
const size_t entered_key_len = wcslen(term->url_keys);
|
const size_t entered_key_len = wcslen(term->url_keys);
|
||||||
|
|
||||||
|
if (key == NULL) {
|
||||||
|
/* TODO: if we decide to use the .text field, we cannot
|
||||||
|
* just skip the entire jump label like this */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
struct wl_surface *surf = it->item.surf.surf;
|
struct wl_surface *surf = it->item.surf.surf;
|
||||||
struct wl_subsurface *sub_surf = it->item.surf.sub;
|
struct wl_subsurface *sub_surf = it->item.surf.sub;
|
||||||
|
|
||||||
|
|
@ -2557,22 +2564,28 @@ render_urls(struct terminal *term)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t text_len = wcslen(text);
|
const size_t key_len = wcslen(key);
|
||||||
size_t chars = wcslen(key) + (text_len > 0 ? 3 + text_len : 0);
|
|
||||||
|
size_t url_len = mbstowcs(NULL, url->url, 0);
|
||||||
|
if (url_len == (size_t)-1)
|
||||||
|
url_len = 0;
|
||||||
|
|
||||||
|
wchar_t url_wchars[url_len + 1];
|
||||||
|
mbstowcs(url_wchars, url->url, url_len + 1);
|
||||||
|
|
||||||
|
size_t chars = key_len + (show_url ? (2 + url_len) : 0);
|
||||||
|
|
||||||
const size_t max_chars = 50;
|
const size_t max_chars = 50;
|
||||||
chars = min(chars, max_chars);
|
chars = min(chars, max_chars);
|
||||||
|
|
||||||
wchar_t label[chars + 2];
|
wchar_t label[chars + 2];
|
||||||
if (text_len == 0)
|
label[chars] = L'…';
|
||||||
wcscpy(label, key);
|
label[chars + 1] = L'\0';
|
||||||
else {
|
|
||||||
int count = swprintf(label, chars + 1, L"%ls - %ls", key, text);
|
if (show_url)
|
||||||
if (count >= max_chars) {
|
swprintf(label, chars + 1, L"%ls: %ls", key, url_wchars);
|
||||||
label[max_chars] = L'…';
|
else
|
||||||
label[max_chars + 1] = L'\0';
|
wcsncpy(label, key, chars + 1);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < wcslen(key); i++)
|
for (size_t i = 0; i < wcslen(key); i++)
|
||||||
label[i] = towupper(label[i]);
|
label[i] = towupper(label[i]);
|
||||||
|
|
|
||||||
96
terminal.c
96
terminal.c
|
|
@ -1102,6 +1102,9 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
|
||||||
.blink = {.fd = -1},
|
.blink = {.fd = -1},
|
||||||
.vt = {
|
.vt = {
|
||||||
.state = 0, /* STATE_GROUND */
|
.state = 0, /* STATE_GROUND */
|
||||||
|
.osc8 = {
|
||||||
|
.begin = {-1, -1},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
.colors = {
|
.colors = {
|
||||||
.fg = conf->colors.fg,
|
.fg = conf->colors.fg,
|
||||||
|
|
@ -1645,6 +1648,9 @@ term_reset(struct terminal *term, bool hard)
|
||||||
free(term->vt.osc.data);
|
free(term->vt.osc.data);
|
||||||
memset(&term->vt, 0, sizeof(term->vt));
|
memset(&term->vt, 0, sizeof(term->vt));
|
||||||
term->vt.state = 0; /* GROUND */
|
term->vt.state = 0; /* GROUND */
|
||||||
|
term->vt.osc8.begin = (struct coord){-1, -1};
|
||||||
|
free(term->vt.osc8.uri);
|
||||||
|
term->vt.osc8.uri = NULL;
|
||||||
|
|
||||||
if (term->grid == &term->alt) {
|
if (term->grid == &term->alt) {
|
||||||
term->grid = &term->normal;
|
term->grid = &term->normal;
|
||||||
|
|
@ -2154,8 +2160,11 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows
|
||||||
grid_swap_row(term->grid, i - rows, i);
|
grid_swap_row(term->grid, i - rows, i);
|
||||||
|
|
||||||
/* Erase scrolled in lines */
|
/* Erase scrolled in lines */
|
||||||
for (int r = region.end - rows; r < region.end; r++)
|
for (int r = region.end - rows; r < region.end; r++) {
|
||||||
erase_line(term, grid_row_and_alloc(term->grid, r));
|
struct row *row = grid_row_and_alloc(term->grid, r);
|
||||||
|
grid_row_reset_extra(row);
|
||||||
|
erase_line(term, row);
|
||||||
|
}
|
||||||
|
|
||||||
term_damage_scroll(term, DAMAGE_SCROLL, region, rows);
|
term_damage_scroll(term, DAMAGE_SCROLL, region, rows);
|
||||||
term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
|
term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
|
||||||
|
|
@ -2222,8 +2231,11 @@ term_scroll_reverse_partial(struct terminal *term,
|
||||||
grid_swap_row(term->grid, i, i - rows);
|
grid_swap_row(term->grid, i, i - rows);
|
||||||
|
|
||||||
/* Erase scrolled in lines */
|
/* Erase scrolled in lines */
|
||||||
for (int r = region.start; r < region.start + rows; r++)
|
for (int r = region.start; r < region.start + rows; r++) {
|
||||||
erase_line(term, grid_row_and_alloc(term->grid, r));
|
struct row *row = grid_row_and_alloc(term->grid, r);
|
||||||
|
grid_row_reset_extra(row);
|
||||||
|
erase_line(term, row);
|
||||||
|
}
|
||||||
|
|
||||||
term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows);
|
term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows);
|
||||||
term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
|
term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
|
||||||
|
|
@ -2989,3 +3001,79 @@ term_ime_set_cursor_rect(struct terminal *term, int x, int y, int width,
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
term_osc8_open(struct terminal *term, uint64_t id, const char *uri)
|
||||||
|
{
|
||||||
|
if (unlikely(term->vt.osc8.begin.row >= 0)) {
|
||||||
|
/* It’s valid to switch from one URI to another without
|
||||||
|
* closing the first one */
|
||||||
|
term_osc8_close(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
term->vt.osc8.begin = (struct coord){
|
||||||
|
.col = term->grid->cursor.point.col,
|
||||||
|
.row = grid_row_absolute(term->grid, term->grid->cursor.point.row),
|
||||||
|
};
|
||||||
|
term->vt.osc8.id = id;
|
||||||
|
term->vt.osc8.uri = xstrdup(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
term_osc8_close(struct terminal *term)
|
||||||
|
{
|
||||||
|
if (term->vt.osc8.begin.row < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (term->vt.osc8.uri[0] == '\0')
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
struct coord start = term->vt.osc8.begin;
|
||||||
|
struct coord end = (struct coord){
|
||||||
|
.col = term->grid->cursor.point.col,
|
||||||
|
.row = grid_row_absolute(term->grid, term->grid->cursor.point.row),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (start.row == end.row && start.col == end.col) {
|
||||||
|
/* Zero-length URL, e.g: \E]8;;http://foo\E\\\E]8;;\E\\ */
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end is *inclusive */
|
||||||
|
if (--end.col < 0) {
|
||||||
|
end.row--;
|
||||||
|
end.col = term->cols - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int r = start.row;
|
||||||
|
int start_col = start.col;
|
||||||
|
do {
|
||||||
|
int end_col = r == end.row ? end.col : term->cols - 1;
|
||||||
|
|
||||||
|
struct row *row = term->grid->rows[r];
|
||||||
|
|
||||||
|
switch (term->conf->osc8_underline) {
|
||||||
|
case OSC8_UNDERLINE_ALWAYS:
|
||||||
|
for (int c = start_col; c <= end_col; c++)
|
||||||
|
row->cells[c].attrs.url = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OSC8_UNDERLINE_URL_MODE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct row_uri_range range = {
|
||||||
|
.start = start_col,
|
||||||
|
.end = end_col,
|
||||||
|
.id = term->vt.osc8.id,
|
||||||
|
.uri = xstrdup(term->vt.osc8.uri),
|
||||||
|
};
|
||||||
|
grid_row_add_uri_range(row, range);
|
||||||
|
start_col = 0;
|
||||||
|
} while (r++ != end.row);
|
||||||
|
|
||||||
|
done:
|
||||||
|
free(term->vt.osc8.uri);
|
||||||
|
term->vt.osc8.id = 0;
|
||||||
|
term->vt.osc8.uri = NULL;
|
||||||
|
term->vt.osc8.begin = (struct coord){-1, -1};
|
||||||
|
}
|
||||||
|
|
|
||||||
36
terminal.h
36
terminal.h
|
|
@ -86,10 +86,22 @@ struct composed {
|
||||||
uint8_t count;
|
uint8_t count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct row_uri_range {
|
||||||
|
int start;
|
||||||
|
int end;
|
||||||
|
uint64_t id;
|
||||||
|
char *uri;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct row_data {
|
||||||
|
tll(struct row_uri_range) uri_ranges;
|
||||||
|
};
|
||||||
|
|
||||||
struct row {
|
struct row {
|
||||||
struct cell *cells;
|
struct cell *cells;
|
||||||
bool dirty;
|
bool dirty;
|
||||||
bool linebreak;
|
bool linebreak;
|
||||||
|
struct row_data *extra;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sixel {
|
struct sixel {
|
||||||
|
|
@ -144,12 +156,25 @@ struct vt {
|
||||||
struct vt_param v[16];
|
struct vt_param v[16];
|
||||||
uint8_t idx;
|
uint8_t idx;
|
||||||
} params;
|
} params;
|
||||||
|
|
||||||
uint32_t private; /* LSB=priv0, MSB=priv3 */
|
uint32_t private; /* LSB=priv0, MSB=priv3 */
|
||||||
|
|
||||||
|
struct attributes attrs;
|
||||||
|
struct attributes saved_attrs;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint8_t *data;
|
uint8_t *data;
|
||||||
size_t size;
|
size_t size;
|
||||||
size_t idx;
|
size_t idx;
|
||||||
} osc;
|
} osc;
|
||||||
|
|
||||||
|
/* Start coordinate for current OSC-8 URI */
|
||||||
|
struct {
|
||||||
|
uint64_t id;
|
||||||
|
char *uri;
|
||||||
|
struct coord begin;
|
||||||
|
} osc8;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint8_t *data;
|
uint8_t *data;
|
||||||
size_t size;
|
size_t size;
|
||||||
|
|
@ -157,8 +182,6 @@ struct vt {
|
||||||
void (*put_handler)(struct terminal *term, uint8_t c);
|
void (*put_handler)(struct terminal *term, uint8_t c);
|
||||||
void (*unhook_handler)(struct terminal *term);
|
void (*unhook_handler)(struct terminal *term);
|
||||||
} dcs;
|
} dcs;
|
||||||
struct attributes attrs;
|
|
||||||
struct attributes saved_attrs;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum cursor_origin { ORIGIN_ABSOLUTE, ORIGIN_RELATIVE };
|
enum cursor_origin { ORIGIN_ABSOLUTE, ORIGIN_RELATIVE };
|
||||||
|
|
@ -227,12 +250,13 @@ typedef tll(struct ptmx_buffer) ptmx_buffer_list_t;
|
||||||
|
|
||||||
enum url_action { URL_ACTION_COPY, URL_ACTION_LAUNCH };
|
enum url_action { URL_ACTION_COPY, URL_ACTION_LAUNCH };
|
||||||
struct url {
|
struct url {
|
||||||
wchar_t *url;
|
uint64_t id;
|
||||||
wchar_t *text;
|
char *url;
|
||||||
wchar_t *key;
|
wchar_t *key;
|
||||||
struct coord start;
|
struct coord start;
|
||||||
struct coord end;
|
struct coord end;
|
||||||
enum url_action action;
|
enum url_action action;
|
||||||
|
bool url_mode_dont_change_url_attr; /* Entering/exiting URL mode doesn’t touch the cells’ attr.url */
|
||||||
};
|
};
|
||||||
typedef tll(struct url) url_list_t;
|
typedef tll(struct url) url_list_t;
|
||||||
|
|
||||||
|
|
@ -516,6 +540,7 @@ struct terminal {
|
||||||
|
|
||||||
url_list_t urls;
|
url_list_t urls;
|
||||||
wchar_t url_keys[5];
|
wchar_t url_keys[5];
|
||||||
|
bool urls_show_uri_on_jump_label;
|
||||||
|
|
||||||
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -668,3 +693,6 @@ void term_ime_set_cursor_rect(
|
||||||
|
|
||||||
void term_urls_reset(struct terminal *term);
|
void term_urls_reset(struct terminal *term);
|
||||||
void term_collect_urls(struct terminal *term);
|
void term_collect_urls(struct terminal *term);
|
||||||
|
|
||||||
|
void term_osc8_open(struct terminal *term, uint64_t id, const char *uri);
|
||||||
|
void term_osc8_close(struct terminal *term);
|
||||||
|
|
|
||||||
243
url-mode.c
243
url-mode.c
|
|
@ -1,5 +1,6 @@
|
||||||
#include "url-mode.h"
|
#include "url-mode.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <wctype.h>
|
#include <wctype.h>
|
||||||
|
|
||||||
|
|
@ -11,9 +12,12 @@
|
||||||
#include "selection.h"
|
#include "selection.h"
|
||||||
#include "spawn.h"
|
#include "spawn.h"
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
|
#include "uri.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
|
|
||||||
|
static void url_destroy(struct url *url);
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
execute_binding(struct seat *seat, struct terminal *term,
|
execute_binding(struct seat *seat, struct terminal *term,
|
||||||
enum bind_action_url action, uint32_t serial)
|
enum bind_action_url action, uint32_t serial)
|
||||||
|
|
@ -26,6 +30,11 @@ execute_binding(struct seat *seat, struct terminal *term,
|
||||||
urls_reset(term);
|
urls_reset(term);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case BIND_ACTION_URL_TOGGLE_URL_ON_JUMP_LABEL:
|
||||||
|
term->urls_show_uri_on_jump_label = !term->urls_show_uri_on_jump_label;
|
||||||
|
render_refresh_urls(term);
|
||||||
|
return true;
|
||||||
|
|
||||||
case BIND_ACTION_URL_COUNT:
|
case BIND_ACTION_URL_COUNT:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -36,42 +45,59 @@ execute_binding(struct seat *seat, struct terminal *term,
|
||||||
static void
|
static void
|
||||||
activate_url(struct seat *seat, struct terminal *term, const struct url *url)
|
activate_url(struct seat *seat, struct terminal *term, const struct url *url)
|
||||||
{
|
{
|
||||||
size_t chars = wcstombs(NULL, url->url, 0);
|
char *url_string = NULL;
|
||||||
|
|
||||||
if (chars != (size_t)-1) {
|
char *scheme, *host, *path;
|
||||||
char *url_utf8 = xmalloc(chars + 1);
|
if (uri_parse(url->url, strlen(url->url), &scheme, NULL, NULL,
|
||||||
wcstombs(url_utf8, url->url, chars + 1);
|
&host, NULL, &path, NULL, NULL))
|
||||||
|
{
|
||||||
|
if (strcmp(scheme, "file") == 0 && hostname_is_localhost(host)) {
|
||||||
|
/*
|
||||||
|
* This is a file in *this* computer. Pass only the
|
||||||
|
* filename to the URL-launcher.
|
||||||
|
*
|
||||||
|
* I.e. strip the ‘file://user@host/’ prefix.
|
||||||
|
*/
|
||||||
|
url_string = path;
|
||||||
|
} else
|
||||||
|
free(path);
|
||||||
|
|
||||||
switch (url->action) {
|
free(scheme);
|
||||||
case URL_ACTION_COPY:
|
free(host);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (url_string == NULL)
|
||||||
|
url_string = xstrdup(url->url);
|
||||||
|
|
||||||
|
switch (url->action) {
|
||||||
|
case URL_ACTION_COPY:
|
||||||
|
if (text_to_clipboard(seat, term, url_string, seat->kbd.serial)) {
|
||||||
|
/* Now owned by our clipboard “manager” */
|
||||||
|
url_string = NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case URL_ACTION_LAUNCH: {
|
||||||
|
size_t argc;
|
||||||
|
char **argv;
|
||||||
|
|
||||||
|
if (spawn_expand_template(
|
||||||
|
&term->conf->url_launch, 1,
|
||||||
|
(const char *[]){"url"},
|
||||||
|
(const char *[]){url_string},
|
||||||
|
&argc, &argv))
|
||||||
|
{
|
||||||
|
spawn(term->reaper, term->cwd, argv, -1, -1, -1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
free(argv[i]);
|
||||||
|
free(argv);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(url_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -121,6 +147,9 @@ urls_input(struct seat *seat, struct terminal *term, uint32_t key,
|
||||||
const struct url *match = NULL;
|
const struct url *match = NULL;
|
||||||
|
|
||||||
tll_foreach(term->urls, it) {
|
tll_foreach(term->urls, it) {
|
||||||
|
if (it->item.key == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
const struct url *url = &it->item;
|
const struct url *url = &it->item;
|
||||||
const size_t key_len = wcslen(it->item.key);
|
const size_t key_len = wcslen(it->item.key);
|
||||||
|
|
||||||
|
|
@ -151,7 +180,8 @@ urls_input(struct seat *seat, struct terminal *term, uint32_t key,
|
||||||
IGNORE_WARNING("-Wpedantic")
|
IGNORE_WARNING("-Wpedantic")
|
||||||
|
|
||||||
static void
|
static void
|
||||||
auto_detected(const struct terminal *term, enum url_action action, url_list_t *urls)
|
auto_detected(const struct terminal *term, enum url_action action,
|
||||||
|
url_list_t *urls)
|
||||||
{
|
{
|
||||||
static const wchar_t *const prots[] = {
|
static const wchar_t *const prots[] = {
|
||||||
L"http://",
|
L"http://",
|
||||||
|
|
@ -313,14 +343,20 @@ auto_detected(const struct terminal *term, enum url_action action, url_list_t *u
|
||||||
start.row += term->grid->view;
|
start.row += term->grid->view;
|
||||||
end.row += term->grid->view;
|
end.row += term->grid->view;
|
||||||
|
|
||||||
tll_push_back(
|
size_t chars = wcstombs(NULL, url, 0);
|
||||||
*urls,
|
if (chars != (size_t)-1) {
|
||||||
((struct url){
|
char *url_utf8 = xmalloc((chars + 1) * sizeof(wchar_t));
|
||||||
.url = xwcsdup(url),
|
wcstombs(url_utf8, url, chars + 1);
|
||||||
.text = xwcsdup(L""),
|
|
||||||
.start = start,
|
tll_push_back(
|
||||||
.end = end,
|
*urls,
|
||||||
.action = action}));
|
((struct url){
|
||||||
|
.id = (uint64_t)rand() << 32 | rand(),
|
||||||
|
.url = url_utf8,
|
||||||
|
.start = start,
|
||||||
|
.end = end,
|
||||||
|
.action = action}));
|
||||||
|
}
|
||||||
|
|
||||||
state = STATE_PROTOCOL;
|
state = STATE_PROTOCOL;
|
||||||
len = 0;
|
len = 0;
|
||||||
|
|
@ -335,15 +371,78 @@ auto_detected(const struct terminal *term, enum url_action action, url_list_t *u
|
||||||
|
|
||||||
UNIGNORE_WARNINGS
|
UNIGNORE_WARNINGS
|
||||||
|
|
||||||
|
static void
|
||||||
|
osc8_uris(const struct terminal *term, enum url_action action, url_list_t *urls)
|
||||||
|
{
|
||||||
|
bool dont_touch_url_attr = false;
|
||||||
|
|
||||||
|
switch (term->conf->osc8_underline) {
|
||||||
|
case OSC8_UNDERLINE_URL_MODE:
|
||||||
|
dont_touch_url_attr = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OSC8_UNDERLINE_ALWAYS:
|
||||||
|
dont_touch_url_attr = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int r = 0; r < term->rows; r++) {
|
||||||
|
const struct row *row = grid_row_in_view(term->grid, r);
|
||||||
|
|
||||||
|
if (row->extra == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tll_foreach(row->extra->uri_ranges, it) {
|
||||||
|
struct coord start = {
|
||||||
|
.col = it->item.start,
|
||||||
|
.row = r + term->grid->view,
|
||||||
|
};
|
||||||
|
struct coord end = {
|
||||||
|
.col = it->item.end,
|
||||||
|
.row = r + term->grid->view,
|
||||||
|
};
|
||||||
|
tll_push_back(
|
||||||
|
*urls,
|
||||||
|
((struct url){
|
||||||
|
.id = it->item.id,
|
||||||
|
.url = xstrdup(it->item.uri),
|
||||||
|
.start = start,
|
||||||
|
.end = end,
|
||||||
|
.action = action,
|
||||||
|
.url_mode_dont_change_url_attr = dont_touch_url_attr}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
remove_duplicates(url_list_t *urls)
|
||||||
|
{
|
||||||
|
tll_foreach(*urls, outer) {
|
||||||
|
tll_foreach(*urls, inner) {
|
||||||
|
if (outer == inner)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (outer->item.start.row == inner->item.start.row &&
|
||||||
|
outer->item.start.col == inner->item.start.col &&
|
||||||
|
outer->item.end.row == inner->item.end.row &&
|
||||||
|
outer->item.end.col == inner->item.end.col)
|
||||||
|
{
|
||||||
|
url_destroy(&inner->item);
|
||||||
|
tll_remove(*urls, inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
urls_collect(const struct terminal *term, enum url_action action, url_list_t *urls)
|
urls_collect(const struct terminal *term, enum url_action action, url_list_t *urls)
|
||||||
{
|
{
|
||||||
xassert(tll_length(term->urls) == 0);
|
xassert(tll_length(term->urls) == 0);
|
||||||
|
osc8_uris(term, action, urls);
|
||||||
auto_detected(term, action, urls);
|
auto_detected(term, action, urls);
|
||||||
|
remove_duplicates(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void url_destroy(struct url *url);
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
wcscmp_qsort_wrapper(const void *_a, const void *_b)
|
wcscmp_qsort_wrapper(const void *_a, const void *_b)
|
||||||
{
|
{
|
||||||
|
|
@ -416,22 +515,59 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls)
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
uint64_t seen_ids[count];
|
||||||
wchar_t *combos[count];
|
wchar_t *combos[count];
|
||||||
generate_key_combos(conf, count, combos);
|
generate_key_combos(conf, count, combos);
|
||||||
|
|
||||||
size_t idx = 0;
|
size_t combo_idx = 0;
|
||||||
tll_foreach(*urls, it)
|
size_t id_idx = 0;
|
||||||
it->item.key = combos[idx++];
|
|
||||||
|
tll_foreach(*urls, it) {
|
||||||
|
bool id_already_seen = false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < id_idx; i++) {
|
||||||
|
if (it->item.id == seen_ids[i]) {
|
||||||
|
id_already_seen = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id_already_seen)
|
||||||
|
continue;
|
||||||
|
seen_ids[id_idx++] = it->item.id;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scan previous URLs, and check if *this* URL matches any of
|
||||||
|
* them; if so, re-use the *same* key combo.
|
||||||
|
*/
|
||||||
|
bool url_already_seen = false;
|
||||||
|
tll_foreach(*urls, it2) {
|
||||||
|
if (&it->item == &it2->item)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (strcmp(it->item.url, it2->item.url) == 0) {
|
||||||
|
it->item.key = xwcsdup(it2->item.key);
|
||||||
|
url_already_seen = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url_already_seen)
|
||||||
|
it->item.key = combos[combo_idx++];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free combos we didn’t use up */
|
||||||
|
for (size_t i = combo_idx; i < count; i++)
|
||||||
|
free(combos[i]);
|
||||||
|
|
||||||
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
||||||
tll_foreach(*urls, it) {
|
tll_foreach(*urls, it) {
|
||||||
char url[1024];
|
if (it->item.key == NULL)
|
||||||
wcstombs(url, it->item.url, sizeof(url) - 1);
|
continue;
|
||||||
|
|
||||||
char key[32];
|
char key[32];
|
||||||
wcstombs(key, it->item.key, sizeof(key) - 1);
|
wcstombs(key, it->item.key, sizeof(key) - 1);
|
||||||
|
LOG_DBG("URL: %s (%s)", it->item.url, key);
|
||||||
LOG_DBG("URL: %s (%s)", url, key);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -439,6 +575,9 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls)
|
||||||
static void
|
static void
|
||||||
tag_cells_for_url(struct terminal *term, const struct url *url, bool value)
|
tag_cells_for_url(struct terminal *term, const struct url *url, bool value)
|
||||||
{
|
{
|
||||||
|
if (url->url_mode_dont_change_url_attr)
|
||||||
|
return;
|
||||||
|
|
||||||
const struct coord *start = &url->start;
|
const struct coord *start = &url->start;
|
||||||
const struct coord *end = &url->end;
|
const struct coord *end = &url->end;
|
||||||
|
|
||||||
|
|
@ -493,7 +632,6 @@ static void
|
||||||
url_destroy(struct url *url)
|
url_destroy(struct url *url)
|
||||||
{
|
{
|
||||||
free(url->url);
|
free(url->url);
|
||||||
free(url->text);
|
|
||||||
free(url->key);
|
free(url->key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -516,6 +654,7 @@ urls_reset(struct terminal *term)
|
||||||
tll_remove(term->urls, it);
|
tll_remove(term->urls, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
term->urls_show_uri_on_jump_label = false;
|
||||||
memset(term->url_keys, 0, sizeof(term->url_keys));
|
memset(term->url_keys, 0, sizeof(term->url_keys));
|
||||||
render_refresh(term);
|
render_refresh(term);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
util.h
14
util.h
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
#include <threads.h>
|
#include <threads.h>
|
||||||
|
|
||||||
#define ALEN(v) (sizeof(v) / sizeof((v)[0]))
|
#define ALEN(v) (sizeof(v) / sizeof((v)[0]))
|
||||||
|
|
@ -21,3 +22,16 @@ thrd_err_as_string(int thrd_err)
|
||||||
|
|
||||||
return "unknown error";
|
return "unknown error";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline uint64_t
|
||||||
|
sdbm_hash(const char *s)
|
||||||
|
{
|
||||||
|
uint64_t hash = 0;
|
||||||
|
|
||||||
|
for (; *s != '\0'; s++) {
|
||||||
|
int c = *s;
|
||||||
|
hash = c + (hash << 6) + (hash << 16) - hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ enum bind_action_search {
|
||||||
enum bind_action_url {
|
enum bind_action_url {
|
||||||
BIND_ACTION_URL_NONE,
|
BIND_ACTION_URL_NONE,
|
||||||
BIND_ACTION_URL_CANCEL,
|
BIND_ACTION_URL_CANCEL,
|
||||||
|
BIND_ACTION_URL_TOGGLE_URL_ON_JUMP_LABEL,
|
||||||
BIND_ACTION_URL_COUNT,
|
BIND_ACTION_URL_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue