mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
Merge branch 'pipe-grid-to-external-tool'
Closes https://codeberg.org/dnkl/foot/issues/29
This commit is contained in:
commit
ac42ce4303
15 changed files with 732 additions and 302 deletions
|
|
@ -24,6 +24,10 @@
|
|||
applications can override this.
|
||||
* Multi-seat support
|
||||
* Implemented `C0::FF` (form feed)
|
||||
* **pipe-visible** and **pipe-scrollback** key bindings. These let you
|
||||
pipe either the currently visible text, or the entire scrollback to
|
||||
external tools (https://codeberg.org/dnkl/foot/issues/29). Example:
|
||||
`pipe-visible=[sh -c "xurls | bemenu | xargs -r firefox] Control+Print`
|
||||
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
217
config.c
217
config.c
|
|
@ -63,6 +63,8 @@ static const char *binding_action_map[] = {
|
|||
[BIND_ACTION_MINIMIZE] = "minimize",
|
||||
[BIND_ACTION_MAXIMIZE] = "maximize",
|
||||
[BIND_ACTION_FULLSCREEN] = "fullscreen",
|
||||
[BIND_ACTION_PIPE_SCROLLBACK] = "pipe-scrollback",
|
||||
[BIND_ACTION_PIPE_VIEW] = "pipe-visible",
|
||||
};
|
||||
|
||||
static_assert(ALEN(binding_action_map) == BIND_ACTION_COUNT,
|
||||
|
|
@ -483,21 +485,19 @@ parse_section_csd(const char *key, const char *value, struct config *conf,
|
|||
}
|
||||
|
||||
static bool
|
||||
verify_key_combo(const struct config *conf, const char *combo, const char *path,
|
||||
unsigned lineno)
|
||||
verify_key_combo(const struct config *conf, enum bind_action_normal action,
|
||||
const char *combo, const char *path, unsigned lineno)
|
||||
{
|
||||
for (enum bind_action_normal action = 0; action < BIND_ACTION_COUNT; action++) {
|
||||
if (conf->bindings.key[action] == NULL)
|
||||
continue;
|
||||
|
||||
char *copy = strdup(conf->bindings.key[action]);
|
||||
tll_foreach(conf->bindings.key, it) {
|
||||
char *copy = strdup(it->item.key);
|
||||
|
||||
for (char *save = NULL, *collision = strtok_r(copy, " ", &save);
|
||||
collision != NULL;
|
||||
collision = strtok_r(NULL, " ", &save))
|
||||
{
|
||||
if (strcmp(combo, collision) == 0) {
|
||||
LOG_ERR("%s:%d: %s already mapped to %s", path, lineno, combo, binding_action_map[action]);
|
||||
LOG_ERR("%s:%d: %s already mapped to %s", path, lineno, combo,
|
||||
binding_action_map[it->item.action]);
|
||||
free(copy);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -528,7 +528,26 @@ parse_section_key_bindings(
|
|||
const char *key, const char *value, struct config *conf,
|
||||
const char *path, unsigned lineno)
|
||||
{
|
||||
for (enum bind_action_normal action = 0; action < BIND_ACTION_COUNT; action++) {
|
||||
const char *pipe_cmd = NULL;
|
||||
size_t pipe_len = 0;
|
||||
|
||||
if (value[0] == '[') {
|
||||
const char *pipe_cmd_end = strrchr(value, ']');
|
||||
if (pipe_cmd_end == NULL) {
|
||||
LOG_ERR("%s:%d: unclosed '['", path, lineno);
|
||||
return false;
|
||||
}
|
||||
|
||||
pipe_cmd = &value[1];
|
||||
pipe_len = pipe_cmd_end - pipe_cmd;
|
||||
|
||||
value = pipe_cmd_end + 1;
|
||||
}
|
||||
|
||||
for (enum bind_action_normal action = 0;
|
||||
action < BIND_ACTION_COUNT;
|
||||
action++)
|
||||
{
|
||||
if (binding_action_map[action] == NULL)
|
||||
continue;
|
||||
|
||||
|
|
@ -536,17 +555,47 @@ parse_section_key_bindings(
|
|||
continue;
|
||||
|
||||
if (strcasecmp(value, "none") == 0) {
|
||||
free(conf->bindings.key[action]);
|
||||
conf->bindings.key[action] = NULL;
|
||||
tll_foreach(conf->bindings.key, it) {
|
||||
if (it->item.action == action) {
|
||||
free(it->item.key);
|
||||
free(it->item.pipe_cmd);
|
||||
tll_remove(conf->bindings.key, it);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!verify_key_combo(conf, value, path, lineno)) {
|
||||
if (!verify_key_combo(conf, action, value, path, lineno)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
free(conf->bindings.key[action]);
|
||||
conf->bindings.key[action] = strdup(value);
|
||||
bool already_added = false;
|
||||
tll_foreach(conf->bindings.key, it) {
|
||||
if (it->item.action == action &&
|
||||
((it->item.pipe_cmd == NULL && pipe_cmd == NULL) ||
|
||||
(it->item.pipe_cmd != NULL && pipe_cmd != NULL &&
|
||||
strncmp(it->item.pipe_cmd, pipe_cmd, pipe_len) == 0)))
|
||||
{
|
||||
|
||||
free(it->item.key);
|
||||
free(it->item.pipe_cmd);
|
||||
|
||||
it->item.key = strdup(value);
|
||||
it->item.pipe_cmd = pipe_cmd != NULL
|
||||
? strndup(pipe_cmd, pipe_len) : NULL;
|
||||
already_added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!already_added) {
|
||||
struct config_key_binding_normal binding = {
|
||||
.action = action,
|
||||
.key = strdup(value),
|
||||
.pipe_cmd = pipe_cmd != NULL ? strndup(pipe_cmd, pipe_len) : NULL,
|
||||
};
|
||||
tll_push_back(conf->bindings.key, binding);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +617,12 @@ parse_section_mouse_bindings(
|
|||
continue;
|
||||
|
||||
if (strcmp(value, "NONE") == 0) {
|
||||
conf->bindings.mouse[action] = (struct mouse_binding){0, 0, BIND_ACTION_NONE};
|
||||
tll_foreach(conf->bindings.mouse, it) {
|
||||
if (it->item.action == action) {
|
||||
tll_remove(conf->bindings.mouse, it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -590,16 +644,32 @@ parse_section_mouse_bindings(
|
|||
const int count = 1;
|
||||
|
||||
/* Make sure button isn't already mapped to another action */
|
||||
for (enum bind_action_normal j = 0; j < BIND_ACTION_COUNT; j++) {
|
||||
const struct mouse_binding *collision = &conf->bindings.mouse[j];
|
||||
if (collision->button == i && collision->count == count) {
|
||||
tll_foreach(conf->bindings.mouse, it) {
|
||||
if (it->item.button == i && it->item.count == count) {
|
||||
LOG_ERR("%s:%d: %s already mapped to %s", path, lineno,
|
||||
value, binding_action_map[collision->action]);
|
||||
value, binding_action_map[it->item.action]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
conf->bindings.mouse[action] = (struct mouse_binding){i, count, action};
|
||||
bool already_added = false;
|
||||
tll_foreach(conf->bindings.mouse, it) {
|
||||
if (it->item.action == action) {
|
||||
it->item.button = i;
|
||||
it->item.count = count;
|
||||
already_added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!already_added) {
|
||||
struct mouse_binding binding = {
|
||||
.action = action,
|
||||
.button = i,
|
||||
.count = count,
|
||||
};
|
||||
tll_push_back(conf->bindings.mouse, binding);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -883,42 +953,6 @@ config_load(struct config *conf, const char *conf_path)
|
|||
.cursor = 0,
|
||||
},
|
||||
},
|
||||
|
||||
.bindings = {
|
||||
.key = {
|
||||
[BIND_ACTION_SCROLLBACK_UP] = strdup("Shift+Page_Up"),
|
||||
[BIND_ACTION_SCROLLBACK_DOWN] = strdup("Shift+Page_Down"),
|
||||
[BIND_ACTION_CLIPBOARD_COPY] = strdup("Control+Shift+C"),
|
||||
[BIND_ACTION_CLIPBOARD_PASTE] = strdup("Control+Shift+V"),
|
||||
[BIND_ACTION_SEARCH_START] = strdup("Control+Shift+R"),
|
||||
[BIND_ACTION_FONT_SIZE_UP] = strdup("Control+plus Control+equal Control+KP_Add"),
|
||||
[BIND_ACTION_FONT_SIZE_DOWN] = strdup("Control+minus Control+KP_Subtract"),
|
||||
[BIND_ACTION_FONT_SIZE_RESET] = strdup("Control+0 Control+KP_0"),
|
||||
[BIND_ACTION_SPAWN_TERMINAL] = strdup("Control+Shift+N"),
|
||||
},
|
||||
.mouse = {
|
||||
[BIND_ACTION_PRIMARY_PASTE] = {BTN_MIDDLE, 1, BIND_ACTION_PRIMARY_PASTE},
|
||||
},
|
||||
.search = {
|
||||
[BIND_ACTION_SEARCH_CANCEL] = strdup("Control+g Escape"),
|
||||
[BIND_ACTION_SEARCH_COMMIT] = strdup("Return"),
|
||||
[BIND_ACTION_SEARCH_FIND_PREV] = strdup("Control+r"),
|
||||
[BIND_ACTION_SEARCH_FIND_NEXT] = strdup("Control+s"),
|
||||
[BIND_ACTION_SEARCH_EDIT_LEFT] = strdup("Left Control+b"),
|
||||
[BIND_ACTION_SEARCH_EDIT_LEFT_WORD] = strdup("Control+Left Mod1+b"),
|
||||
[BIND_ACTION_SEARCH_EDIT_RIGHT] = strdup("Right Control+f"),
|
||||
[BIND_ACTION_SEARCH_EDIT_RIGHT_WORD] = strdup("Control+Right Mod1+f"),
|
||||
[BIND_ACTION_SEARCH_EDIT_HOME] = strdup("Home Control+a"),
|
||||
[BIND_ACTION_SEARCH_EDIT_END] = strdup("End Control+e"),
|
||||
[BIND_ACTION_SEARCH_DELETE_PREV] = strdup("BackSpace"),
|
||||
[BIND_ACTION_SEARCH_DELETE_PREV_WORD] = strdup("Mod1+BackSpace Control+BackSpace"),
|
||||
[BIND_ACTION_SEARCH_DELETE_NEXT] = strdup("Delete "),
|
||||
[BIND_ACTION_SEARCH_DELETE_NEXT_WORD] = strdup("Mod1+d Control+Delete"),
|
||||
[BIND_ACTION_SEARCH_EXTEND_WORD] = strdup("Control+w"),
|
||||
[BIND_ACTION_SEARCH_EXTEND_WORD_WS] = strdup("Control+Shift+W"),
|
||||
},
|
||||
},
|
||||
|
||||
.csd = {
|
||||
.preferred = CONF_CSD_PREFER_SERVER,
|
||||
.title_height = 26,
|
||||
|
|
@ -938,6 +972,63 @@ config_load(struct config *conf, const char *conf_path)
|
|||
},
|
||||
};
|
||||
|
||||
struct config_key_binding_normal scrollback_up = {BIND_ACTION_SCROLLBACK_UP, strdup("Shift+Page_Up")};
|
||||
struct config_key_binding_normal scrollback_down = {BIND_ACTION_SCROLLBACK_DOWN, strdup("Shift+Page_Down")};
|
||||
struct config_key_binding_normal clipboard_copy = {BIND_ACTION_CLIPBOARD_COPY, strdup("Control+Shift+C")};
|
||||
struct config_key_binding_normal clipboard_paste = {BIND_ACTION_CLIPBOARD_PASTE, strdup("Control+Shift+V")};
|
||||
struct config_key_binding_normal search_start = {BIND_ACTION_SEARCH_START, strdup("Control+Shift+R")};
|
||||
struct config_key_binding_normal font_size_up = {BIND_ACTION_FONT_SIZE_UP, strdup("Control+plus Control+equal Control+KP_Add")};
|
||||
struct config_key_binding_normal font_size_down = {BIND_ACTION_FONT_SIZE_DOWN, strdup("Control+minus Control+KP_Subtract")};
|
||||
struct config_key_binding_normal font_size_reset = {BIND_ACTION_FONT_SIZE_RESET, strdup("Control+0 Control+KP_0")};
|
||||
struct config_key_binding_normal spawn_terminal = {BIND_ACTION_SPAWN_TERMINAL, strdup("Control+Shift+N")};
|
||||
|
||||
tll_push_back(conf->bindings.key, scrollback_up);
|
||||
tll_push_back(conf->bindings.key, scrollback_down);
|
||||
tll_push_back(conf->bindings.key, clipboard_copy);
|
||||
tll_push_back(conf->bindings.key, clipboard_paste);
|
||||
tll_push_back(conf->bindings.key, search_start);
|
||||
tll_push_back(conf->bindings.key, font_size_up);
|
||||
tll_push_back(conf->bindings.key, font_size_down);
|
||||
tll_push_back(conf->bindings.key, font_size_reset);
|
||||
tll_push_back(conf->bindings.key, spawn_terminal);
|
||||
|
||||
struct mouse_binding primary_paste = {BIND_ACTION_PRIMARY_PASTE, BTN_MIDDLE, 1};
|
||||
tll_push_back(conf->bindings.mouse, primary_paste);
|
||||
|
||||
struct config_key_binding_search search_cancel = {BIND_ACTION_SEARCH_CANCEL, strdup("Control+g Escape")};
|
||||
struct config_key_binding_search search_commit = {BIND_ACTION_SEARCH_COMMIT, strdup("Return")};
|
||||
struct config_key_binding_search search_find_prev = {BIND_ACTION_SEARCH_FIND_PREV, strdup("Control+r")};
|
||||
struct config_key_binding_search search_find_next = {BIND_ACTION_SEARCH_FIND_NEXT, strdup("Control+s")};
|
||||
struct config_key_binding_search search_edit_left = {BIND_ACTION_SEARCH_EDIT_LEFT, strdup("Left Control+b")};
|
||||
struct config_key_binding_search search_edit_left_word = {BIND_ACTION_SEARCH_EDIT_LEFT_WORD, strdup("Control+Left Mod1+b")};
|
||||
struct config_key_binding_search search_edit_right = {BIND_ACTION_SEARCH_EDIT_RIGHT, strdup("Right Control+f")};
|
||||
struct config_key_binding_search search_edit_right_word = {BIND_ACTION_SEARCH_EDIT_RIGHT_WORD, strdup("Control+Right Mod1+f")};
|
||||
struct config_key_binding_search search_edit_home = {BIND_ACTION_SEARCH_EDIT_HOME, strdup("Home Control+a")};
|
||||
struct config_key_binding_search search_edit_end = {BIND_ACTION_SEARCH_EDIT_END, strdup("End Control+e")};
|
||||
struct config_key_binding_search search_del_prev = {BIND_ACTION_SEARCH_DELETE_PREV, strdup("BackSpace")};
|
||||
struct config_key_binding_search search_del_prev_word = {BIND_ACTION_SEARCH_DELETE_PREV_WORD, strdup("Mod1+BackSpace Control+BackSpace")};
|
||||
struct config_key_binding_search search_del_next = {BIND_ACTION_SEARCH_DELETE_NEXT, strdup("Delete")};
|
||||
struct config_key_binding_search search_del_next_word = {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, strdup("Mod1+d Control+Delete")};
|
||||
struct config_key_binding_search search_ext_word = {BIND_ACTION_SEARCH_EXTEND_WORD, strdup("Control+w")};
|
||||
struct config_key_binding_search search_ext_word_ws = {BIND_ACTION_SEARCH_EXTEND_WORD_WS, strdup("Control+Shift+W")};
|
||||
|
||||
tll_push_back(conf->bindings.search, search_cancel);
|
||||
tll_push_back(conf->bindings.search, search_commit);
|
||||
tll_push_back(conf->bindings.search, search_find_prev);
|
||||
tll_push_back(conf->bindings.search, search_find_next);
|
||||
tll_push_back(conf->bindings.search, search_edit_left);
|
||||
tll_push_back(conf->bindings.search, search_edit_left_word);
|
||||
tll_push_back(conf->bindings.search, search_edit_right);
|
||||
tll_push_back(conf->bindings.search, search_edit_right_word);
|
||||
tll_push_back(conf->bindings.search, search_edit_home);
|
||||
tll_push_back(conf->bindings.search, search_edit_end);
|
||||
tll_push_back(conf->bindings.search, search_del_prev);
|
||||
tll_push_back(conf->bindings.search, search_del_prev_word);
|
||||
tll_push_back(conf->bindings.search, search_del_next);
|
||||
tll_push_back(conf->bindings.search, search_del_next_word);
|
||||
tll_push_back(conf->bindings.search, search_ext_word);
|
||||
tll_push_back(conf->bindings.search, search_ext_word_ws);
|
||||
|
||||
char *default_path = NULL;
|
||||
if (conf_path == NULL) {
|
||||
if ((default_path = get_config_path()) == NULL) {
|
||||
|
|
@ -982,10 +1073,16 @@ config_free(struct config conf)
|
|||
tll_free(conf.fonts);
|
||||
free(conf.server_socket_path);
|
||||
|
||||
for (enum bind_action_normal i = 0; i < BIND_ACTION_COUNT; i++)
|
||||
free(conf.bindings.key[i]);
|
||||
for (enum bind_action_search i = 0; i < BIND_ACTION_SEARCH_COUNT; i++)
|
||||
free(conf.bindings.search[i]);
|
||||
tll_foreach(conf.bindings.key, it) {
|
||||
free(it->item.key);
|
||||
free(it->item.pipe_cmd);
|
||||
}
|
||||
tll_foreach(conf.bindings.search, it)
|
||||
free(it->item.key);
|
||||
|
||||
tll_free(conf.bindings.key);
|
||||
tll_free(conf.bindings.mouse);
|
||||
tll_free(conf.bindings.search);
|
||||
}
|
||||
|
||||
struct config_font
|
||||
|
|
|
|||
18
config.h
18
config.h
|
|
@ -6,6 +6,7 @@
|
|||
#include <tllist.h>
|
||||
|
||||
#include "terminal.h"
|
||||
#include "wayland.h"
|
||||
|
||||
struct config_font {
|
||||
char *pattern;
|
||||
|
|
@ -13,6 +14,17 @@ struct config_font {
|
|||
int px_size;
|
||||
};
|
||||
|
||||
struct config_key_binding_normal {
|
||||
enum bind_action_normal action;
|
||||
char *key;
|
||||
char *pipe_cmd;
|
||||
};
|
||||
|
||||
struct config_key_binding_search {
|
||||
enum bind_action_search action;
|
||||
char *key;
|
||||
};
|
||||
|
||||
struct config {
|
||||
char *term;
|
||||
char *shell;
|
||||
|
|
@ -48,8 +60,8 @@ struct config {
|
|||
|
||||
struct {
|
||||
/* Bindings for "normal" mode */
|
||||
char *key[BIND_ACTION_COUNT];
|
||||
struct mouse_binding mouse[BIND_ACTION_COUNT];
|
||||
tll(struct config_key_binding_normal) key;
|
||||
tll(struct mouse_binding) mouse;
|
||||
|
||||
/*
|
||||
* Special modes
|
||||
|
|
@ -57,7 +69,7 @@ struct config {
|
|||
|
||||
/* While searching (not - action to *start* a search is in the
|
||||
* 'key' bindings above */
|
||||
char *search[BIND_ACTION_SEARCH_COUNT];
|
||||
tll(struct config_key_binding_search) search;
|
||||
} bindings;
|
||||
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -234,6 +234,23 @@ e.g. *search-start=none*.
|
|||
*fullscreen*
|
||||
Toggles the fullscreen state. Default: _not bound_.
|
||||
|
||||
*pipe-visible*, *pipe-scrollback*
|
||||
Pipes the currently visible text, or the entire scrollback, to an
|
||||
external tool. The syntax for this option is a bit special; the
|
||||
first part of the value is the command to execute enclosed in
|
||||
"[]", followed by the binding(s).
|
||||
|
||||
You can configure multiple pipes as long as the command strings
|
||||
are different and the key bindings are unique.
|
||||
|
||||
Note that the command is *not* automatically run inside a shell;
|
||||
use *sh -c "command line"* if you need that.
|
||||
|
||||
Example:
|
||||
*pipe-visible=[sh -c "xurls | bemenu | xargs -r firefox"] Control+Print*
|
||||
|
||||
Default: _not bound_
|
||||
|
||||
# SECTION: mouse-bindings
|
||||
|
||||
This section lets you override the default mouse bindings.
|
||||
|
|
|
|||
176
extract.c
Normal file
176
extract.c
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
#include "extract.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define LOG_MODULE "extract"
|
||||
#define LOG_ENABLE_DBG 1
|
||||
#include "log.h"
|
||||
|
||||
struct extraction_context {
|
||||
wchar_t *buf;
|
||||
size_t size;
|
||||
size_t idx;
|
||||
size_t empty_count;
|
||||
bool failed;
|
||||
const struct row *last_row;
|
||||
const struct cell *last_cell;
|
||||
enum selection_kind selection_kind;
|
||||
};
|
||||
|
||||
struct extraction_context *
|
||||
extract_begin(enum selection_kind kind)
|
||||
{
|
||||
struct extraction_context *ctx = malloc(sizeof(*ctx));
|
||||
*ctx = (struct extraction_context){
|
||||
.selection_kind = kind,
|
||||
};
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static bool
|
||||
ensure_size(struct extraction_context *ctx, size_t additional_chars)
|
||||
{
|
||||
while (ctx->size < ctx->idx + additional_chars) {
|
||||
size_t new_size = ctx->size == 0 ? 512 : ctx->size * 2;
|
||||
wchar_t *new_buf = realloc(ctx->buf, new_size * sizeof(wchar_t));
|
||||
|
||||
if (new_buf == NULL)
|
||||
return false;
|
||||
|
||||
ctx->buf = new_buf;
|
||||
ctx->size = new_size;
|
||||
}
|
||||
|
||||
assert(ctx->size >= ctx->idx + additional_chars);
|
||||
return true;
|
||||
}
|
||||
bool
|
||||
extract_finish(struct extraction_context *ctx, char **text, size_t *len)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (text == NULL)
|
||||
return false;
|
||||
|
||||
*text = NULL;
|
||||
if (len != NULL)
|
||||
*len = 0;
|
||||
|
||||
if (ctx->failed)
|
||||
goto out;
|
||||
|
||||
if (ctx->idx == 0) {
|
||||
/* Selection of empty cells only */
|
||||
if (!ensure_size(ctx, 1))
|
||||
goto out;
|
||||
ctx->buf[ctx->idx] = L'\0';
|
||||
} else {
|
||||
assert(ctx->idx > 0);
|
||||
assert(ctx->idx < ctx->size);
|
||||
if (ctx->buf[ctx->idx - 1] == L'\n')
|
||||
ctx->buf[ctx->idx - 1] = L'\0';
|
||||
else
|
||||
ctx->buf[ctx->idx] = L'\0';
|
||||
}
|
||||
|
||||
size_t _len = wcstombs(NULL, ctx->buf, 0);
|
||||
if (_len == (size_t)-1) {
|
||||
LOG_ERRNO("failed to convert selection to UTF-8");
|
||||
goto out;
|
||||
}
|
||||
|
||||
*text = malloc(_len + 1);
|
||||
wcstombs(*text, ctx->buf, _len + 1);
|
||||
|
||||
if (len != NULL)
|
||||
*len = _len;
|
||||
|
||||
ret = true;
|
||||
|
||||
out:
|
||||
free(ctx->buf);
|
||||
free(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
extract_one(const struct terminal *term, const struct row *row,
|
||||
const struct cell *cell, int col, void *context)
|
||||
{
|
||||
struct extraction_context *ctx = context;
|
||||
|
||||
if (cell->wc == CELL_MULT_COL_SPACER)
|
||||
return true;
|
||||
|
||||
if (ctx->last_row != NULL && row != ctx->last_row) {
|
||||
/* New row - determine if we should insert a newline or not */
|
||||
|
||||
if (ctx->selection_kind == SELECTION_NONE ||
|
||||
ctx->selection_kind == SELECTION_NORMAL)
|
||||
{
|
||||
if (ctx->last_row->linebreak ||
|
||||
ctx->empty_count > 0 ||
|
||||
cell->wc == 0)
|
||||
{
|
||||
/* Row has a hard linebreak, or either last cell or
|
||||
* current cell is empty */
|
||||
if (!ensure_size(ctx, 1))
|
||||
goto err;
|
||||
|
||||
ctx->buf[ctx->idx++] = L'\n';
|
||||
ctx->empty_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
else if (ctx->selection_kind == SELECTION_BLOCK) {
|
||||
/* Always insert a linebreak */
|
||||
if (!ensure_size(ctx, 1))
|
||||
goto err;
|
||||
|
||||
ctx->buf[ctx->idx++] = L'\n';
|
||||
ctx->empty_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell->wc == 0) {
|
||||
ctx->empty_count++;
|
||||
ctx->last_row = row;
|
||||
ctx->last_cell = cell;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Replace empty cells with spaces when followed by non-empty cell */
|
||||
if (!ensure_size(ctx, ctx->empty_count))
|
||||
goto err;
|
||||
|
||||
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||
ctx->buf[ctx->idx++] = L' ';
|
||||
ctx->empty_count = 0;
|
||||
|
||||
if (cell->wc >= CELL_COMB_CHARS_LO &&
|
||||
cell->wc < (CELL_COMB_CHARS_LO + term->composed_count))
|
||||
{
|
||||
const struct composed *composed
|
||||
= &term->composed[cell->wc - CELL_COMB_CHARS_LO];
|
||||
|
||||
if (!ensure_size(ctx, 1 + composed->count))
|
||||
goto err;
|
||||
|
||||
ctx->buf[ctx->idx++] = composed->base;
|
||||
for (size_t i = 0; i < composed->count; i++)
|
||||
ctx->buf[ctx->idx++] = composed->combining[i];
|
||||
}
|
||||
|
||||
else {
|
||||
if (!ensure_size(ctx, 1))
|
||||
goto err;
|
||||
ctx->buf[ctx->idx++] = cell->wc;
|
||||
}
|
||||
|
||||
ctx->last_row = row;
|
||||
ctx->last_cell = cell;
|
||||
return true;
|
||||
|
||||
err:
|
||||
ctx->failed = true;
|
||||
return false;
|
||||
}
|
||||
17
extract.h
Normal file
17
extract.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "terminal.h"
|
||||
|
||||
struct extraction_context;
|
||||
|
||||
struct extraction_context *extract_begin(enum selection_kind kind);
|
||||
|
||||
bool extract_one(
|
||||
const struct terminal *term, const struct row *row, const struct cell *cell,
|
||||
int col, void *context);
|
||||
|
||||
bool extract_finish(
|
||||
struct extraction_context *context, char **text, size_t *len);
|
||||
2
footrc
2
footrc
|
|
@ -58,6 +58,8 @@
|
|||
# minimize=none
|
||||
# maximize=none
|
||||
# fullscreen=none
|
||||
# pipe-visible=[sh -c "xurls | bemenu | xargs -r firefox"] none
|
||||
# pipe-scrollback=[sh -c "xurls | bemenu | xargs -r firefox"] none
|
||||
|
||||
[mouse-bindings]
|
||||
# primary-paste=BTN_MIDDLE
|
||||
|
|
|
|||
179
input.c
179
input.c
|
|
@ -5,10 +5,12 @@
|
|||
#include <signal.h>
|
||||
#include <threads.h>
|
||||
#include <locale.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <linux/input-event-codes.h>
|
||||
|
||||
|
|
@ -28,13 +30,54 @@
|
|||
#include "render.h"
|
||||
#include "search.h"
|
||||
#include "selection.h"
|
||||
#include "spawn.h"
|
||||
#include "terminal.h"
|
||||
#include "tokenize.h"
|
||||
#include "util.h"
|
||||
#include "vt.h"
|
||||
|
||||
struct pipe_context {
|
||||
char *text;
|
||||
size_t idx;
|
||||
size_t left;
|
||||
};
|
||||
|
||||
static bool
|
||||
fdm_write_pipe(struct fdm *fdm, int fd, int events, void *data)
|
||||
{
|
||||
struct pipe_context *ctx = data;
|
||||
|
||||
if (events & EPOLLHUP)
|
||||
goto pipe_closed;
|
||||
|
||||
assert(events & EPOLLOUT);
|
||||
ssize_t written = write(fd, &ctx->text[ctx->idx], ctx->left);
|
||||
|
||||
if (written < 0) {
|
||||
LOG_WARN("failed to write to pipe: %s", strerror(errno));
|
||||
goto pipe_closed;
|
||||
}
|
||||
|
||||
assert(written <= ctx->left);
|
||||
ctx->idx += written;
|
||||
ctx->left -= written;
|
||||
|
||||
if (ctx->left == 0)
|
||||
goto pipe_closed;
|
||||
|
||||
return true;
|
||||
|
||||
pipe_closed:
|
||||
free(ctx->text);
|
||||
free(ctx);
|
||||
fdm_del(fdm, fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
execute_binding(struct seat *seat, struct terminal *term,
|
||||
enum bind_action_normal action, uint32_t serial)
|
||||
enum bind_action_normal action, const char *pipe_cmd,
|
||||
uint32_t serial)
|
||||
{
|
||||
switch (action) {
|
||||
case BIND_ACTION_NONE:
|
||||
|
|
@ -101,6 +144,104 @@ execute_binding(struct seat *seat, struct terminal *term,
|
|||
xdg_toplevel_set_fullscreen(term->window->xdg_toplevel, NULL);
|
||||
break;
|
||||
|
||||
case BIND_ACTION_PIPE_SCROLLBACK:
|
||||
case BIND_ACTION_PIPE_VIEW: {
|
||||
if (pipe_cmd == NULL)
|
||||
break;
|
||||
|
||||
struct pipe_context *ctx = NULL;
|
||||
|
||||
char *cmd = strdup(pipe_cmd);
|
||||
char **argv = NULL;
|
||||
|
||||
if (!tokenize_cmdline(cmd, &argv))
|
||||
goto pipe_err;
|
||||
|
||||
int pipe_fd[2] = {-1, -1};
|
||||
if (pipe(pipe_fd) < 0) {
|
||||
LOG_ERRNO("failed to create pipe");
|
||||
goto pipe_err;
|
||||
}
|
||||
|
||||
int stdout_fd = open("/dev/null", O_WRONLY);
|
||||
int stderr_fd = open("/dev/null", O_WRONLY);
|
||||
|
||||
if (stdout_fd < 0 || stderr_fd < 0) {
|
||||
LOG_ERRNO("failed to open /dev/null");
|
||||
goto pipe_err;
|
||||
}
|
||||
|
||||
char *text;
|
||||
size_t len;
|
||||
|
||||
bool success = action == BIND_ACTION_PIPE_SCROLLBACK
|
||||
? term_scrollback_to_text(term, &text, &len)
|
||||
: term_view_to_text(term, &text, &len);
|
||||
|
||||
if (!success)
|
||||
goto pipe_err;
|
||||
|
||||
/* Make write-end non-blocking; required by the FDM */
|
||||
{
|
||||
int flags = fcntl(pipe_fd[1], F_GETFL);
|
||||
if (flags < 0 ||
|
||||
fcntl(pipe_fd[1], F_SETFL, flags | O_NONBLOCK) < 0)
|
||||
{
|
||||
LOG_ERRNO("failed to make write-end of pipe non-blocking");
|
||||
goto pipe_err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure write-end is closed on exec() - or the spawned
|
||||
* program may not terminate*/
|
||||
{
|
||||
int flags = fcntl(pipe_fd[1], F_GETFD);
|
||||
if (flags < 0 ||
|
||||
fcntl(pipe_fd[1], F_SETFD, flags | FD_CLOEXEC) < 0)
|
||||
{
|
||||
LOG_ERRNO("failed to set FD_CLOEXEC on writeend of pipe");
|
||||
goto pipe_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!spawn(term->reaper, NULL, argv, pipe_fd[0], stdout_fd, stderr_fd))
|
||||
goto pipe_err;
|
||||
|
||||
/* Not needed anymore */
|
||||
free(argv); argv = NULL;
|
||||
free(cmd); cmd = NULL;
|
||||
|
||||
/* Close read end */
|
||||
close(pipe_fd[0]);
|
||||
|
||||
ctx = malloc(sizeof(*ctx));
|
||||
*ctx = (struct pipe_context){
|
||||
.text = text,
|
||||
.left = len,
|
||||
};
|
||||
|
||||
/* Asynchronously write the output to the pipe */
|
||||
if (!fdm_add(term->fdm, pipe_fd[1], EPOLLOUT, &fdm_write_pipe, ctx))
|
||||
goto pipe_err;
|
||||
|
||||
break;
|
||||
|
||||
pipe_err:
|
||||
if (stdout_fd >= 0)
|
||||
close(stdout_fd);
|
||||
if (stderr_fd >= 0)
|
||||
close(stderr_fd);
|
||||
if (pipe_fd[0] >= 0)
|
||||
close(pipe_fd[0]);
|
||||
if (pipe_fd[1] >= 0)
|
||||
close(pipe_fd[1]);
|
||||
free(text);
|
||||
free(argv);
|
||||
free(cmd);
|
||||
free(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
case BIND_ACTION_COUNT:
|
||||
assert(false);
|
||||
break;
|
||||
|
|
@ -192,7 +333,8 @@ input_parse_key_binding(struct xkb_keymap *keymap, const char *combos,
|
|||
};
|
||||
|
||||
tll_push_back(*bindings, binding);
|
||||
}
|
||||
} else
|
||||
tll_free(key_codes);
|
||||
}
|
||||
|
||||
free(copy);
|
||||
|
|
@ -270,31 +412,33 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
|
|||
munmap(map_str, size);
|
||||
close(fd);
|
||||
|
||||
for (enum bind_action_normal i = 0; i < BIND_ACTION_COUNT; i++) {
|
||||
tll_foreach(wayl->conf->bindings.key, it) {
|
||||
key_binding_list_t bindings = tll_init();
|
||||
input_parse_key_binding(
|
||||
seat->kbd.xkb_keymap, wayl->conf->bindings.key[i], &bindings);
|
||||
seat->kbd.xkb_keymap, it->item.key, &bindings);
|
||||
|
||||
tll_foreach(bindings, it) {
|
||||
tll_foreach(bindings, it2) {
|
||||
tll_push_back(
|
||||
seat->kbd.bindings.key,
|
||||
((struct key_binding_normal){.bind = it->item, .action = i}));
|
||||
((struct key_binding_normal){
|
||||
.bind = it2->item,
|
||||
.action = it->item.action,
|
||||
.pipe_cmd = it->item.pipe_cmd}));
|
||||
}
|
||||
|
||||
tll_free(bindings);
|
||||
}
|
||||
|
||||
for (enum bind_action_search i = 0; i < BIND_ACTION_SEARCH_COUNT; i++) {
|
||||
tll_foreach(wayl->conf->bindings.search, it) {
|
||||
key_binding_list_t bindings = tll_init();
|
||||
input_parse_key_binding(
|
||||
seat->kbd.xkb_keymap, wayl->conf->bindings.search[i], &bindings);
|
||||
seat->kbd.xkb_keymap, it->item.key, &bindings);
|
||||
|
||||
tll_foreach(bindings, it) {
|
||||
tll_foreach(bindings, it2) {
|
||||
tll_push_back(
|
||||
seat->kbd.bindings.search,
|
||||
((struct key_binding_search){.bind = it->item, .action = i}));
|
||||
((struct key_binding_search){
|
||||
.bind = it2->item, .action = it->item.action}));
|
||||
}
|
||||
|
||||
tll_free(bindings);
|
||||
}
|
||||
}
|
||||
|
|
@ -596,14 +740,14 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|||
|
||||
/* Match symbol */
|
||||
if (it->item.bind.sym == sym) {
|
||||
execute_binding(seat, term, it->item.action, serial);
|
||||
execute_binding(seat, term, it->item.action, it->item.pipe_cmd, serial);
|
||||
goto maybe_repeat;
|
||||
}
|
||||
|
||||
/* Match raw key code */
|
||||
tll_foreach(it->item.bind.key_codes, code) {
|
||||
if (code->item == key) {
|
||||
execute_binding(seat, term, it->item.action, serial);
|
||||
execute_binding(seat, term, it->item.action, it->item.pipe_cmd, serial);
|
||||
goto maybe_repeat;
|
||||
}
|
||||
}
|
||||
|
|
@ -1239,9 +1383,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
|||
}
|
||||
|
||||
else {
|
||||
for (size_t i = 0; i < ALEN(wayl->conf->bindings.mouse); i++) {
|
||||
const struct mouse_binding *binding =
|
||||
&wayl->conf->bindings.mouse[i];
|
||||
tll_foreach(wayl->conf->bindings.mouse, it) {
|
||||
const struct mouse_binding *binding = &it->item;
|
||||
|
||||
if (binding->button != button) {
|
||||
/* Wrong button */
|
||||
|
|
@ -1253,7 +1396,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
|||
continue;
|
||||
}
|
||||
|
||||
execute_binding(seat, term, binding->action, serial);
|
||||
execute_binding(seat, term, binding->action, NULL, serial);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ executable(
|
|||
'commands.c', 'commands.h',
|
||||
'csi.c', 'csi.h',
|
||||
'dcs.c', 'dcs.h',
|
||||
'extract.c', 'extract.h',
|
||||
'fdm.c', 'fdm.h',
|
||||
'grid.c', 'grid.h',
|
||||
'input.c', 'input.h',
|
||||
|
|
@ -122,6 +123,7 @@ executable(
|
|||
'sixel.c', 'sixel.h',
|
||||
'sixel-hls.c', 'sixel-hls.h',
|
||||
'slave.c', 'slave.h',
|
||||
'spawn.c', 'spawn.h',
|
||||
'terminal.c', 'terminal.h',
|
||||
'tokenize.c', 'tokenize.h',
|
||||
'vt.c', 'vt.h',
|
||||
|
|
|
|||
194
selection.c
194
selection.c
|
|
@ -14,6 +14,7 @@
|
|||
#include "log.h"
|
||||
|
||||
#include "async.h"
|
||||
#include "extract.h"
|
||||
#include "grid.h"
|
||||
#include "misc.h"
|
||||
#include "render.h"
|
||||
|
|
@ -100,7 +101,7 @@ selection_view_down(struct terminal *term, int new_view)
|
|||
static void
|
||||
foreach_selected_normal(
|
||||
struct terminal *term, struct coord _start, struct coord _end,
|
||||
void (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
||||
bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
||||
void *data)
|
||||
{
|
||||
const struct coord *start = &_start;
|
||||
|
|
@ -134,7 +135,8 @@ foreach_selected_normal(
|
|||
c <= (r == end_row ? end_col : term->cols - 1);
|
||||
c++)
|
||||
{
|
||||
cb(term, row, &row->cells[c], c, data);
|
||||
if (!cb(term, row, &row->cells[c], c, data))
|
||||
return;
|
||||
}
|
||||
|
||||
start_col = 0;
|
||||
|
|
@ -144,7 +146,7 @@ foreach_selected_normal(
|
|||
static void
|
||||
foreach_selected_block(
|
||||
struct terminal *term, struct coord _start, struct coord _end,
|
||||
void (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
||||
bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
||||
void *data)
|
||||
{
|
||||
const struct coord *start = &_start;
|
||||
|
|
@ -166,7 +168,8 @@ foreach_selected_block(
|
|||
assert(row != NULL);
|
||||
|
||||
for (int c = top_left.col; c <= bottom_right.col; c++) {
|
||||
cb(term, row, &row->cells[c], c, data);
|
||||
if (!cb(term, row, &row->cells[c], c, data))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -174,7 +177,7 @@ foreach_selected_block(
|
|||
static void
|
||||
foreach_selected(
|
||||
struct terminal *term, struct coord start, struct coord end,
|
||||
void (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
||||
bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
||||
void *data)
|
||||
{
|
||||
switch (term->selection.kind) {
|
||||
|
|
@ -192,173 +195,27 @@ foreach_selected(
|
|||
assert(false);
|
||||
}
|
||||
|
||||
static size_t
|
||||
min_bufsize_for_extraction(const struct terminal *term)
|
||||
static bool
|
||||
extract_one_const_wrapper(struct terminal *term,
|
||||
struct row *row, struct cell *cell,
|
||||
int col, void *data)
|
||||
{
|
||||
const struct coord *start = &term->selection.start;
|
||||
const struct coord *end = &term->selection.end;
|
||||
const size_t chars_per_cell = 1 + ALEN(term->composed[0].combining);
|
||||
|
||||
switch (term->selection.kind) {
|
||||
case SELECTION_NONE:
|
||||
return 0;
|
||||
|
||||
case SELECTION_NORMAL:
|
||||
if (term->selection.end.row < 0)
|
||||
return 0;
|
||||
|
||||
assert(term->selection.start.row != -1);
|
||||
|
||||
if (start->row > end->row) {
|
||||
const struct coord *tmp = start;
|
||||
start = end;
|
||||
end = tmp;
|
||||
}
|
||||
|
||||
if (start->row == end->row)
|
||||
return (end->col - start->col + 1) * chars_per_cell;
|
||||
else {
|
||||
size_t cells = 0;
|
||||
|
||||
/* Add one extra column on each row, for \n */
|
||||
|
||||
cells += term->cols - start->col + 1;
|
||||
cells += (term->cols + 1) * (end->row - start->row - 1);
|
||||
cells += end->col + 1 + 1;
|
||||
return cells * chars_per_cell;
|
||||
}
|
||||
|
||||
case SELECTION_BLOCK: {
|
||||
struct coord top_left = {
|
||||
.row = min(start->row, end->row),
|
||||
.col = min(start->col, end->col),
|
||||
};
|
||||
|
||||
struct coord bottom_right = {
|
||||
.row = max(start->row, end->row),
|
||||
.col = max(start->col, end->col),
|
||||
};
|
||||
|
||||
/* Add one extra column on each row, for \n */
|
||||
int cols = bottom_right.col - top_left.col + 1 + 1;
|
||||
int rows = bottom_right.row - top_left.row + 1;
|
||||
return rows * cols * chars_per_cell;
|
||||
}
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct extract {
|
||||
wchar_t *buf;
|
||||
size_t size;
|
||||
size_t idx;
|
||||
size_t empty_count;
|
||||
const struct row *last_row;
|
||||
const struct cell *last_cell;
|
||||
};
|
||||
|
||||
static void
|
||||
extract_one(struct terminal *term, struct row *row, struct cell *cell,
|
||||
int col, void *data)
|
||||
{
|
||||
struct extract *ctx = data;
|
||||
|
||||
if (cell->wc == CELL_MULT_COL_SPACER)
|
||||
return;
|
||||
|
||||
if (ctx->last_row != NULL && row != ctx->last_row) {
|
||||
/* New row - determine if we should insert a newline or not */
|
||||
|
||||
if (term->selection.kind == SELECTION_NORMAL) {
|
||||
if (ctx->last_row->linebreak ||
|
||||
ctx->empty_count > 0 ||
|
||||
cell->wc == 0)
|
||||
{
|
||||
/* Row has a hard linebreak, or either last cell or
|
||||
* current cell is empty */
|
||||
ctx->buf[ctx->idx++] = L'\n';
|
||||
ctx->empty_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
else if (term->selection.kind == SELECTION_BLOCK) {
|
||||
/* Always insert a linebreak */
|
||||
ctx->buf[ctx->idx++] = L'\n';
|
||||
ctx->empty_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell->wc == 0) {
|
||||
ctx->empty_count++;
|
||||
ctx->last_row = row;
|
||||
ctx->last_cell = cell;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Replace empty cells with spaces when followed by non-empty cell */
|
||||
assert(ctx->idx + ctx->empty_count <= ctx->size);
|
||||
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||
ctx->buf[ctx->idx++] = L' ';
|
||||
ctx->empty_count = 0;
|
||||
|
||||
assert(ctx->idx + 1 <= ctx->size);
|
||||
|
||||
if (cell->wc >= CELL_COMB_CHARS_LO &&
|
||||
cell->wc < (CELL_COMB_CHARS_LO + term->composed_count)) {
|
||||
const struct composed *composed = &term->composed[cell->wc - CELL_COMB_CHARS_LO];
|
||||
|
||||
ctx->buf[ctx->idx++] = composed->base;
|
||||
|
||||
assert(ctx->idx + composed->count <= ctx->size);
|
||||
for (size_t i = 0; i < composed->count; i++)
|
||||
ctx->buf[ctx->idx++] = composed->combining[i];
|
||||
} else
|
||||
ctx->buf[ctx->idx++] = cell->wc;
|
||||
|
||||
ctx->last_row = row;
|
||||
ctx->last_cell = cell;
|
||||
return extract_one(term, row, cell, col, data);
|
||||
}
|
||||
|
||||
static char *
|
||||
extract_selection(const struct terminal *term)
|
||||
{
|
||||
const size_t max_cells = min_bufsize_for_extraction(term);
|
||||
const size_t buf_size = max_cells + 1;
|
||||
|
||||
struct extract ctx = {
|
||||
.buf = malloc(buf_size * sizeof(wchar_t)),
|
||||
.size = buf_size,
|
||||
};
|
||||
struct extraction_context *ctx = extract_begin(term->selection.kind);
|
||||
if (ctx == NULL)
|
||||
return NULL;
|
||||
|
||||
foreach_selected(
|
||||
(struct terminal *)term, term->selection.start, term->selection.end,
|
||||
&extract_one, &ctx);
|
||||
&extract_one_const_wrapper, ctx);
|
||||
|
||||
if (ctx.idx == 0) {
|
||||
/* Selection of empty cells only */
|
||||
ctx.buf[ctx.idx] = L'\0';
|
||||
} else {
|
||||
assert(ctx.idx > 0);
|
||||
assert(ctx.idx < ctx.size);
|
||||
if (ctx.buf[ctx.idx - 1] == L'\n')
|
||||
ctx.buf[ctx.idx - 1] = L'\0';
|
||||
else
|
||||
ctx.buf[ctx.idx] = L'\0';
|
||||
}
|
||||
|
||||
size_t len = wcstombs(NULL, ctx.buf, 0);
|
||||
if (len == (size_t)-1) {
|
||||
LOG_ERRNO("failed to convert selection to UTF-8");
|
||||
free(ctx.buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *ret = malloc(len + 1);
|
||||
wcstombs(ret, ctx.buf, len + 1);
|
||||
free(ctx.buf);
|
||||
return ret;
|
||||
char *text;
|
||||
return extract_finish(ctx, &text, NULL) ? text : NULL;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -377,41 +234,44 @@ selection_start(struct terminal *term, int col, int row,
|
|||
term->selection.end = (struct coord){-1, -1};
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
unmark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
||||
int col, void *data)
|
||||
{
|
||||
if (cell->attrs.selected == 0 || (cell->attrs.selected & 2)) {
|
||||
/* Ignore if already deselected, or if premarked for updated selection */
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
row->dirty = true;
|
||||
cell->attrs.selected = 0;
|
||||
cell->attrs.clean = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
static bool
|
||||
premark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
||||
int col, void *data)
|
||||
{
|
||||
/* Tell unmark to leave this be */
|
||||
cell->attrs.selected |= 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
mark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
||||
int col, void *data)
|
||||
{
|
||||
if (cell->attrs.selected & 1) {
|
||||
cell->attrs.selected = 1; /* Clear the pre-mark bit */
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
row->dirty = true;
|
||||
cell->attrs.selected = 1;
|
||||
cell->attrs.clean = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
76
spawn.c
Normal file
76
spawn.c
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#include "spawn.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#define LOG_MODULE "spawn"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
|
||||
bool
|
||||
spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
||||
int stdin_fd, int stdout_fd, int stderr_fd)
|
||||
{
|
||||
int pipe_fds[2] = {-1, -1};
|
||||
if (pipe2(pipe_fds, O_CLOEXEC) < 0) {
|
||||
LOG_ERRNO("failed to create pipe");
|
||||
goto err;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
LOG_ERRNO("failed to fork");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
/* Child */
|
||||
close(pipe_fds[0]);
|
||||
|
||||
if ((stdin_fd >= 0 && (dup2(stdin_fd, STDIN_FILENO) < 0 || close(stdin_fd) < 0)) ||
|
||||
(stdout_fd >= 0 && (dup2(stdout_fd, STDOUT_FILENO) < 0 || close(stdout_fd) < 0)) ||
|
||||
(stderr_fd >= 0 && (dup2(stderr_fd, STDERR_FILENO) < 0 || close(stderr_fd) < 0)) ||
|
||||
(cwd != NULL && chdir(cwd) < 0) ||
|
||||
execvp(argv[0], argv) < 0)
|
||||
{
|
||||
(void)!write(pipe_fds[1], &errno, sizeof(errno));
|
||||
_exit(errno);
|
||||
}
|
||||
assert(false);
|
||||
_exit(errno);
|
||||
}
|
||||
|
||||
/* Parent */
|
||||
close(pipe_fds[1]);
|
||||
|
||||
int _errno;
|
||||
static_assert(sizeof(_errno) == sizeof(errno), "errno size mismatch");
|
||||
|
||||
ssize_t ret = read(pipe_fds[0], &_errno, sizeof(_errno));
|
||||
close(pipe_fds[0]);
|
||||
|
||||
if (ret == 0) {
|
||||
reaper_add(reaper, pid);
|
||||
return true;
|
||||
} else if (ret < 0) {
|
||||
LOG_ERRNO("failed to read from pipe");
|
||||
return false;
|
||||
} else {
|
||||
LOG_ERRNO_P("%s: failed to spawn", _errno, argv[0]);
|
||||
errno = _errno;
|
||||
waitpid(pid, NULL, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
err:
|
||||
if (pipe_fds[0] != -1)
|
||||
close(pipe_fds[0]);
|
||||
if (pipe_fds[1] != -1)
|
||||
close(pipe_fds[1]);
|
||||
return false;
|
||||
}
|
||||
7
spawn.h
Normal file
7
spawn.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "reaper.h"
|
||||
|
||||
bool spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
||||
int stdin_fd, int stdout_fd, int stderr_fd);
|
||||
115
terminal.c
115
terminal.c
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "async.h"
|
||||
#include "config.h"
|
||||
#include "extract.h"
|
||||
#include "grid.h"
|
||||
#include "quirks.h"
|
||||
#include "reaper.h"
|
||||
|
|
@ -29,6 +30,7 @@
|
|||
#include "selection.h"
|
||||
#include "sixel.h"
|
||||
#include "slave.h"
|
||||
#include "spawn.h"
|
||||
#include "util.h"
|
||||
#include "vt.h"
|
||||
|
||||
|
|
@ -2245,59 +2247,9 @@ term_flash(struct terminal *term, unsigned duration_ms)
|
|||
bool
|
||||
term_spawn_new(const struct terminal *term)
|
||||
{
|
||||
int pipe_fds[2] = {-1, -1};
|
||||
if (pipe2(pipe_fds, O_CLOEXEC) < 0) {
|
||||
LOG_ERRNO("failed to create pipe");
|
||||
goto err;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
LOG_ERRNO("failed to fork new terminal");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
/* Child */
|
||||
close(pipe_fds[0]);
|
||||
if (chdir(term->cwd) < 0 ||
|
||||
execlp(term->foot_exe, term->foot_exe, NULL) < 0)
|
||||
{
|
||||
(void)!write(pipe_fds[1], &errno, sizeof(errno));
|
||||
_exit(errno);
|
||||
}
|
||||
assert(false);
|
||||
_exit(errno);
|
||||
}
|
||||
|
||||
/* Parent */
|
||||
close(pipe_fds[1]);
|
||||
|
||||
int _errno;
|
||||
static_assert(sizeof(_errno) == sizeof(errno), "errno size mismatch");
|
||||
|
||||
ssize_t ret = read(pipe_fds[0], &_errno, sizeof(_errno));
|
||||
close(pipe_fds[0]);
|
||||
|
||||
if (ret == 0) {
|
||||
reaper_add(term->reaper, pid);
|
||||
return true;
|
||||
} else if (ret < 0) {
|
||||
LOG_ERRNO("failed to read from pipe");
|
||||
return false;
|
||||
} else {
|
||||
LOG_ERRNO_P("%s: failed to spawn new terminal", _errno, term->foot_exe);
|
||||
errno = _errno;
|
||||
waitpid(pid, NULL, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
err:
|
||||
if (pipe_fds[0] != -1)
|
||||
close(pipe_fds[0]);
|
||||
if (pipe_fds[1] != -1)
|
||||
close(pipe_fds[1]);
|
||||
return false;
|
||||
return spawn(
|
||||
term->reaper, term->cwd, (char *const []){term->foot_exe, NULL},
|
||||
-1, -1, -1);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -2474,3 +2426,60 @@ term_surface_kind(const struct terminal *term, const struct wl_surface *surface)
|
|||
else
|
||||
return TERM_SURF_NONE;
|
||||
}
|
||||
|
||||
static bool
|
||||
rows_to_text(const struct terminal *term, int start, int end,
|
||||
char **text, size_t *len)
|
||||
{
|
||||
struct extraction_context *ctx = extract_begin(SELECTION_NONE);
|
||||
if (ctx == NULL)
|
||||
return false;
|
||||
|
||||
for (size_t r = start;
|
||||
r != ((end + 1) & (term->grid->num_rows - 1));
|
||||
r = (r + 1) & (term->grid->num_rows - 1))
|
||||
{
|
||||
const struct row *row = term->grid->rows[r];
|
||||
assert(row != NULL);
|
||||
|
||||
for (int c = 0; c < term->cols; c++)
|
||||
if (!extract_one(term, row, &row->cells[c], c, ctx))
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
return extract_finish(ctx, text, len);
|
||||
}
|
||||
|
||||
bool
|
||||
term_scrollback_to_text(const struct terminal *term, char **text, size_t *len)
|
||||
{
|
||||
int start = term->grid->offset + term->rows;
|
||||
int end = term->grid->offset + term->rows - 1;
|
||||
|
||||
/* If scrollback isn't full yet, this may be NULL, so scan forward
|
||||
* until we find the first non-NULL row */
|
||||
while (term->grid->rows[start] == NULL) {
|
||||
start++;
|
||||
start &= term->grid->num_rows - 1;
|
||||
}
|
||||
|
||||
if (end < 0)
|
||||
end += term->grid->num_rows;
|
||||
|
||||
while (term->grid->rows[end] == NULL) {
|
||||
end--;
|
||||
if (end < 0)
|
||||
end += term->grid->num_rows;
|
||||
}
|
||||
|
||||
return rows_to_text(term, start, end, text, len);
|
||||
}
|
||||
|
||||
bool
|
||||
term_view_to_text(const struct terminal *term, char **text, size_t *len)
|
||||
{
|
||||
int start = grid_row_absolute_in_view(term->grid, 0);
|
||||
int end = grid_row_absolute_in_view(term->grid, term->rows - 1);
|
||||
return rows_to_text(term, start, end, text, len);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -540,3 +540,8 @@ void term_disable_app_sync_updates(struct terminal *term);
|
|||
|
||||
enum term_surface term_surface_kind(
|
||||
const struct terminal *term, const struct wl_surface *surface);
|
||||
|
||||
bool term_scrollback_to_text(
|
||||
const struct terminal *term, char **text, size_t *len);
|
||||
bool term_view_to_text(
|
||||
const struct terminal *term, char **text, size_t *len);
|
||||
|
|
|
|||
|
|
@ -39,18 +39,21 @@ enum bind_action_normal {
|
|||
BIND_ACTION_MINIMIZE,
|
||||
BIND_ACTION_MAXIMIZE,
|
||||
BIND_ACTION_FULLSCREEN,
|
||||
BIND_ACTION_PIPE_SCROLLBACK,
|
||||
BIND_ACTION_PIPE_VIEW,
|
||||
BIND_ACTION_COUNT,
|
||||
};
|
||||
|
||||
struct key_binding_normal {
|
||||
struct key_binding bind;
|
||||
enum bind_action_normal action;
|
||||
const char *pipe_cmd;
|
||||
};
|
||||
|
||||
struct mouse_binding {
|
||||
enum bind_action_normal action;
|
||||
uint32_t button;
|
||||
int count;
|
||||
enum bind_action_normal action;
|
||||
};
|
||||
|
||||
enum bind_action_search {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue