Merge branch 'pipe-grid-to-external-tool'

Closes https://codeberg.org/dnkl/foot/issues/29
This commit is contained in:
Daniel Eklöf 2020-07-17 09:34:51 +02:00
commit ac42ce4303
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
15 changed files with 732 additions and 302 deletions

View file

@ -24,6 +24,10 @@
applications can override this. applications can override this.
* Multi-seat support * Multi-seat support
* Implemented `C0::FF` (form feed) * 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 ### Changed

217
config.c
View file

@ -63,6 +63,8 @@ static const char *binding_action_map[] = {
[BIND_ACTION_MINIMIZE] = "minimize", [BIND_ACTION_MINIMIZE] = "minimize",
[BIND_ACTION_MAXIMIZE] = "maximize", [BIND_ACTION_MAXIMIZE] = "maximize",
[BIND_ACTION_FULLSCREEN] = "fullscreen", [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, 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 static bool
verify_key_combo(const struct config *conf, const char *combo, const char *path, verify_key_combo(const struct config *conf, enum bind_action_normal action,
unsigned lineno) const char *combo, const char *path, unsigned lineno)
{ {
for (enum bind_action_normal action = 0; action < BIND_ACTION_COUNT; action++) { tll_foreach(conf->bindings.key, it) {
if (conf->bindings.key[action] == NULL) char *copy = strdup(it->item.key);
continue;
char *copy = strdup(conf->bindings.key[action]);
for (char *save = NULL, *collision = strtok_r(copy, " ", &save); for (char *save = NULL, *collision = strtok_r(copy, " ", &save);
collision != NULL; collision != NULL;
collision = strtok_r(NULL, " ", &save)) collision = strtok_r(NULL, " ", &save))
{ {
if (strcmp(combo, collision) == 0) { 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); free(copy);
return false; return false;
} }
@ -528,7 +528,26 @@ parse_section_key_bindings(
const char *key, const char *value, struct config *conf, const char *key, const char *value, struct config *conf,
const char *path, unsigned lineno) 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) if (binding_action_map[action] == NULL)
continue; continue;
@ -536,17 +555,47 @@ parse_section_key_bindings(
continue; continue;
if (strcasecmp(value, "none") == 0) { if (strcasecmp(value, "none") == 0) {
free(conf->bindings.key[action]); tll_foreach(conf->bindings.key, it) {
conf->bindings.key[action] = NULL; if (it->item.action == action) {
free(it->item.key);
free(it->item.pipe_cmd);
tll_remove(conf->bindings.key, it);
}
}
return true; return true;
} }
if (!verify_key_combo(conf, value, path, lineno)) { if (!verify_key_combo(conf, action, value, path, lineno)) {
return false; return false;
} }
free(conf->bindings.key[action]); bool already_added = false;
conf->bindings.key[action] = strdup(value); 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; return true;
} }
@ -568,7 +617,12 @@ parse_section_mouse_bindings(
continue; continue;
if (strcmp(value, "NONE") == 0) { 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; return true;
} }
@ -590,16 +644,32 @@ parse_section_mouse_bindings(
const int count = 1; const int count = 1;
/* Make sure button isn't already mapped to another action */ /* Make sure button isn't already mapped to another action */
for (enum bind_action_normal j = 0; j < BIND_ACTION_COUNT; j++) { tll_foreach(conf->bindings.mouse, it) {
const struct mouse_binding *collision = &conf->bindings.mouse[j]; if (it->item.button == i && it->item.count == count) {
if (collision->button == i && collision->count == count) {
LOG_ERR("%s:%d: %s already mapped to %s", path, lineno, 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; 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; return true;
} }
@ -883,42 +953,6 @@ config_load(struct config *conf, const char *conf_path)
.cursor = 0, .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 = { .csd = {
.preferred = CONF_CSD_PREFER_SERVER, .preferred = CONF_CSD_PREFER_SERVER,
.title_height = 26, .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; char *default_path = NULL;
if (conf_path == NULL) { if (conf_path == NULL) {
if ((default_path = get_config_path()) == NULL) { if ((default_path = get_config_path()) == NULL) {
@ -982,10 +1073,16 @@ config_free(struct config conf)
tll_free(conf.fonts); tll_free(conf.fonts);
free(conf.server_socket_path); free(conf.server_socket_path);
for (enum bind_action_normal i = 0; i < BIND_ACTION_COUNT; i++) tll_foreach(conf.bindings.key, it) {
free(conf.bindings.key[i]); free(it->item.key);
for (enum bind_action_search i = 0; i < BIND_ACTION_SEARCH_COUNT; i++) free(it->item.pipe_cmd);
free(conf.bindings.search[i]); }
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 struct config_font

View file

@ -6,6 +6,7 @@
#include <tllist.h> #include <tllist.h>
#include "terminal.h" #include "terminal.h"
#include "wayland.h"
struct config_font { struct config_font {
char *pattern; char *pattern;
@ -13,6 +14,17 @@ struct config_font {
int px_size; 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 { struct config {
char *term; char *term;
char *shell; char *shell;
@ -48,8 +60,8 @@ struct config {
struct { struct {
/* Bindings for "normal" mode */ /* Bindings for "normal" mode */
char *key[BIND_ACTION_COUNT]; tll(struct config_key_binding_normal) key;
struct mouse_binding mouse[BIND_ACTION_COUNT]; tll(struct mouse_binding) mouse;
/* /*
* Special modes * Special modes
@ -57,7 +69,7 @@ struct config {
/* While searching (not - action to *start* a search is in the /* While searching (not - action to *start* a search is in the
* 'key' bindings above */ * 'key' bindings above */
char *search[BIND_ACTION_SEARCH_COUNT]; tll(struct config_key_binding_search) search;
} bindings; } bindings;
struct { struct {

View file

@ -234,6 +234,23 @@ e.g. *search-start=none*.
*fullscreen* *fullscreen*
Toggles the fullscreen state. Default: _not bound_. 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 # SECTION: mouse-bindings
This section lets you override the default mouse bindings. This section lets you override the default mouse bindings.

176
extract.c Normal file
View 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
View 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
View file

@ -58,6 +58,8 @@
# minimize=none # minimize=none
# maximize=none # maximize=none
# fullscreen=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] [mouse-bindings]
# primary-paste=BTN_MIDDLE # primary-paste=BTN_MIDDLE

179
input.c
View file

@ -5,10 +5,12 @@
#include <signal.h> #include <signal.h>
#include <threads.h> #include <threads.h>
#include <locale.h> #include <locale.h>
#include <errno.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/timerfd.h> #include <sys/timerfd.h>
#include <sys/epoll.h> #include <sys/epoll.h>
#include <fcntl.h>
#include <linux/input-event-codes.h> #include <linux/input-event-codes.h>
@ -28,13 +30,54 @@
#include "render.h" #include "render.h"
#include "search.h" #include "search.h"
#include "selection.h" #include "selection.h"
#include "spawn.h"
#include "terminal.h" #include "terminal.h"
#include "tokenize.h"
#include "util.h" #include "util.h"
#include "vt.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 static void
execute_binding(struct seat *seat, struct terminal *term, 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) { switch (action) {
case BIND_ACTION_NONE: 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); xdg_toplevel_set_fullscreen(term->window->xdg_toplevel, NULL);
break; 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: case BIND_ACTION_COUNT:
assert(false); assert(false);
break; break;
@ -192,7 +333,8 @@ input_parse_key_binding(struct xkb_keymap *keymap, const char *combos,
}; };
tll_push_back(*bindings, binding); tll_push_back(*bindings, binding);
} } else
tll_free(key_codes);
} }
free(copy); free(copy);
@ -270,31 +412,33 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
munmap(map_str, size); munmap(map_str, size);
close(fd); 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(); key_binding_list_t bindings = tll_init();
input_parse_key_binding( 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( tll_push_back(
seat->kbd.bindings.key, 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); 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(); key_binding_list_t bindings = tll_init();
input_parse_key_binding( 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( tll_push_back(
seat->kbd.bindings.search, 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); tll_free(bindings);
} }
} }
@ -596,14 +740,14 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
/* Match symbol */ /* Match symbol */
if (it->item.bind.sym == sym) { 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; goto maybe_repeat;
} }
/* Match raw key code */ /* Match raw key code */
tll_foreach(it->item.bind.key_codes, code) { tll_foreach(it->item.bind.key_codes, code) {
if (code->item == key) { 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; goto maybe_repeat;
} }
} }
@ -1239,9 +1383,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
} }
else { else {
for (size_t i = 0; i < ALEN(wayl->conf->bindings.mouse); i++) { tll_foreach(wayl->conf->bindings.mouse, it) {
const struct mouse_binding *binding = const struct mouse_binding *binding = &it->item;
&wayl->conf->bindings.mouse[i];
if (binding->button != button) { if (binding->button != button) {
/* Wrong button */ /* Wrong button */
@ -1253,7 +1396,7 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
continue; continue;
} }
execute_binding(seat, term, binding->action, serial); execute_binding(seat, term, binding->action, NULL, serial);
break; break;
} }
} }

View file

@ -105,6 +105,7 @@ executable(
'commands.c', 'commands.h', 'commands.c', 'commands.h',
'csi.c', 'csi.h', 'csi.c', 'csi.h',
'dcs.c', 'dcs.h', 'dcs.c', 'dcs.h',
'extract.c', 'extract.h',
'fdm.c', 'fdm.h', 'fdm.c', 'fdm.h',
'grid.c', 'grid.h', 'grid.c', 'grid.h',
'input.c', 'input.h', 'input.c', 'input.h',
@ -122,6 +123,7 @@ executable(
'sixel.c', 'sixel.h', 'sixel.c', 'sixel.h',
'sixel-hls.c', 'sixel-hls.h', 'sixel-hls.c', 'sixel-hls.h',
'slave.c', 'slave.h', 'slave.c', 'slave.h',
'spawn.c', 'spawn.h',
'terminal.c', 'terminal.h', 'terminal.c', 'terminal.h',
'tokenize.c', 'tokenize.h', 'tokenize.c', 'tokenize.h',
'vt.c', 'vt.h', 'vt.c', 'vt.h',

View file

@ -14,6 +14,7 @@
#include "log.h" #include "log.h"
#include "async.h" #include "async.h"
#include "extract.h"
#include "grid.h" #include "grid.h"
#include "misc.h" #include "misc.h"
#include "render.h" #include "render.h"
@ -100,7 +101,7 @@ selection_view_down(struct terminal *term, int new_view)
static void static void
foreach_selected_normal( foreach_selected_normal(
struct terminal *term, struct coord _start, struct coord _end, 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) void *data)
{ {
const struct coord *start = &_start; const struct coord *start = &_start;
@ -134,7 +135,8 @@ foreach_selected_normal(
c <= (r == end_row ? end_col : term->cols - 1); c <= (r == end_row ? end_col : term->cols - 1);
c++) c++)
{ {
cb(term, row, &row->cells[c], c, data); if (!cb(term, row, &row->cells[c], c, data))
return;
} }
start_col = 0; start_col = 0;
@ -144,7 +146,7 @@ foreach_selected_normal(
static void static void
foreach_selected_block( foreach_selected_block(
struct terminal *term, struct coord _start, struct coord _end, 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) void *data)
{ {
const struct coord *start = &_start; const struct coord *start = &_start;
@ -166,7 +168,8 @@ foreach_selected_block(
assert(row != NULL); assert(row != NULL);
for (int c = top_left.col; c <= bottom_right.col; c++) { 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 static void
foreach_selected( foreach_selected(
struct terminal *term, struct coord start, struct coord end, 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) void *data)
{ {
switch (term->selection.kind) { switch (term->selection.kind) {
@ -192,173 +195,27 @@ foreach_selected(
assert(false); assert(false);
} }
static size_t static bool
min_bufsize_for_extraction(const struct terminal *term) extract_one_const_wrapper(struct terminal *term,
struct row *row, struct cell *cell,
int col, void *data)
{ {
const struct coord *start = &term->selection.start; return extract_one(term, row, cell, col, data);
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;
} }
static char * static char *
extract_selection(const struct terminal *term) extract_selection(const struct terminal *term)
{ {
const size_t max_cells = min_bufsize_for_extraction(term); struct extraction_context *ctx = extract_begin(term->selection.kind);
const size_t buf_size = max_cells + 1; if (ctx == NULL)
return NULL;
struct extract ctx = {
.buf = malloc(buf_size * sizeof(wchar_t)),
.size = buf_size,
};
foreach_selected( foreach_selected(
(struct terminal *)term, term->selection.start, term->selection.end, (struct terminal *)term, term->selection.start, term->selection.end,
&extract_one, &ctx); &extract_one_const_wrapper, ctx);
if (ctx.idx == 0) { char *text;
/* Selection of empty cells only */ return extract_finish(ctx, &text, NULL) ? text : NULL;
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;
} }
void void
@ -377,41 +234,44 @@ selection_start(struct terminal *term, int col, int row,
term->selection.end = (struct coord){-1, -1}; term->selection.end = (struct coord){-1, -1};
} }
static void static bool
unmark_selected(struct terminal *term, struct row *row, struct cell *cell, unmark_selected(struct terminal *term, struct row *row, struct cell *cell,
int col, void *data) int col, void *data)
{ {
if (cell->attrs.selected == 0 || (cell->attrs.selected & 2)) { if (cell->attrs.selected == 0 || (cell->attrs.selected & 2)) {
/* Ignore if already deselected, or if premarked for updated selection */ /* Ignore if already deselected, or if premarked for updated selection */
return; return true;
} }
row->dirty = true; row->dirty = true;
cell->attrs.selected = 0; cell->attrs.selected = 0;
cell->attrs.clean = 0; cell->attrs.clean = 0;
return true;
} }
static void static bool
premark_selected(struct terminal *term, struct row *row, struct cell *cell, premark_selected(struct terminal *term, struct row *row, struct cell *cell,
int col, void *data) int col, void *data)
{ {
/* Tell unmark to leave this be */ /* Tell unmark to leave this be */
cell->attrs.selected |= 2; cell->attrs.selected |= 2;
return true;
} }
static void static bool
mark_selected(struct terminal *term, struct row *row, struct cell *cell, mark_selected(struct terminal *term, struct row *row, struct cell *cell,
int col, void *data) int col, void *data)
{ {
if (cell->attrs.selected & 1) { if (cell->attrs.selected & 1) {
cell->attrs.selected = 1; /* Clear the pre-mark bit */ cell->attrs.selected = 1; /* Clear the pre-mark bit */
return; return true;
} }
row->dirty = true; row->dirty = true;
cell->attrs.selected = 1; cell->attrs.selected = 1;
cell->attrs.clean = 0; cell->attrs.clean = 0;
return true;
} }
static void static void

76
spawn.c Normal file
View 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
View 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);

View file

@ -22,6 +22,7 @@
#include "async.h" #include "async.h"
#include "config.h" #include "config.h"
#include "extract.h"
#include "grid.h" #include "grid.h"
#include "quirks.h" #include "quirks.h"
#include "reaper.h" #include "reaper.h"
@ -29,6 +30,7 @@
#include "selection.h" #include "selection.h"
#include "sixel.h" #include "sixel.h"
#include "slave.h" #include "slave.h"
#include "spawn.h"
#include "util.h" #include "util.h"
#include "vt.h" #include "vt.h"
@ -2245,59 +2247,9 @@ term_flash(struct terminal *term, unsigned duration_ms)
bool bool
term_spawn_new(const struct terminal *term) term_spawn_new(const struct terminal *term)
{ {
int pipe_fds[2] = {-1, -1}; return spawn(
if (pipe2(pipe_fds, O_CLOEXEC) < 0) { term->reaper, term->cwd, (char *const []){term->foot_exe, NULL},
LOG_ERRNO("failed to create pipe"); -1, -1, -1);
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;
} }
void void
@ -2474,3 +2426,60 @@ term_surface_kind(const struct terminal *term, const struct wl_surface *surface)
else else
return TERM_SURF_NONE; 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);
}

View file

@ -540,3 +540,8 @@ void term_disable_app_sync_updates(struct terminal *term);
enum term_surface term_surface_kind( enum term_surface term_surface_kind(
const struct terminal *term, const struct wl_surface *surface); 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);

View file

@ -39,18 +39,21 @@ enum bind_action_normal {
BIND_ACTION_MINIMIZE, BIND_ACTION_MINIMIZE,
BIND_ACTION_MAXIMIZE, BIND_ACTION_MAXIMIZE,
BIND_ACTION_FULLSCREEN, BIND_ACTION_FULLSCREEN,
BIND_ACTION_PIPE_SCROLLBACK,
BIND_ACTION_PIPE_VIEW,
BIND_ACTION_COUNT, BIND_ACTION_COUNT,
}; };
struct key_binding_normal { struct key_binding_normal {
struct key_binding bind; struct key_binding bind;
enum bind_action_normal action; enum bind_action_normal action;
const char *pipe_cmd;
}; };
struct mouse_binding { struct mouse_binding {
enum bind_action_normal action;
uint32_t button; uint32_t button;
int count; int count;
enum bind_action_normal action;
}; };
enum bind_action_search { enum bind_action_search {