Merge branch 'block-input-events-while-pasting' into master

Closes #101
This commit is contained in:
Daniel Eklöf 2020-08-27 19:51:37 +02:00
commit 0e89302da2
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
4 changed files with 220 additions and 66 deletions

View file

@ -86,6 +86,8 @@
(https://codeberg.org/dnkl/foot/issues/97). (https://codeberg.org/dnkl/foot/issues/97).
* Mouse events from being sent to client application when a mouse * Mouse events from being sent to client application when a mouse
binding has consumed it. binding has consumed it.
* Input events from getting mixed with paste data
(https://codeberg.org/dnkl/foot/issues/101).
### Security ### Security

View file

@ -8,6 +8,7 @@
#include <wctype.h> #include <wctype.h>
#include <sys/epoll.h> #include <sys/epoll.h>
#include <sys/timerfd.h>
#define LOG_MODULE "selection" #define LOG_MODULE "selection"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
@ -993,12 +994,50 @@ selection_to_clipboard(struct seat *seat, struct terminal *term, uint32_t serial
} }
struct clipboard_receive { struct clipboard_receive {
int read_fd;
int timeout_fd;
struct itimerspec timeout;
/* Callback data */ /* Callback data */
void (*cb)(const char *data, size_t size, void *user); void (*cb)(const char *data, size_t size, void *user);
void (*done)(void *user); void (*done)(void *user);
void *user; void *user;
}; };
static void
clipboard_receive_done(struct fdm *fdm, struct clipboard_receive *ctx)
{
fdm_del(fdm, ctx->timeout_fd);
fdm_del(fdm, ctx->read_fd);
ctx->done(ctx->user);
free(ctx);
}
static bool
fdm_receive_timeout(struct fdm *fdm, int fd, int events, void *data)
{
struct clipboard_receive *ctx = data;
if (events & EPOLLHUP)
return false;
assert(events & EPOLLIN);
uint64_t expire_count;
ssize_t ret = read(fd, &expire_count, sizeof(expire_count));
if (ret < 0) {
if (errno == EAGAIN)
return true;
LOG_ERRNO("failed to read clipboard timeout timer");
return false;
}
LOG_WARN("no data received from clipboard in %llu seconds, aborting",
(unsigned long long)ctx->timeout.it_value.tv_sec);
clipboard_receive_done(fdm, ctx);
return true;
}
static bool static bool
fdm_receive(struct fdm *fdm, int fd, int events, void *data) fdm_receive(struct fdm *fdm, int fd, int events, void *data)
@ -1008,6 +1047,12 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data)
if ((events & EPOLLHUP) && !(events & EPOLLIN)) if ((events & EPOLLHUP) && !(events & EPOLLIN))
goto done; goto done;
/* Reset timeout timer */
if (timerfd_settime(ctx->timeout_fd, 0, &ctx->timeout, NULL) < 0) {
LOG_ERRNO("failed to re-arm clipboard timeout timer");
return false;
}
/* Read until EOF */ /* Read until EOF */
while (true) { while (true) {
char text[256]; char text[256];
@ -1044,9 +1089,7 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data)
} }
done: done:
fdm_del(fdm, fd); clipboard_receive_done(fdm, ctx);
ctx->done(ctx->user);
free(ctx);
return true; return true;
} }
@ -1055,28 +1098,52 @@ begin_receive_clipboard(struct terminal *term, int read_fd,
void (*cb)(const char *data, size_t size, void *user), void (*cb)(const char *data, size_t size, void *user),
void (*done)(void *user), void *user) void (*done)(void *user), void *user)
{ {
int timeout_fd = -1;
struct clipboard_receive *ctx = NULL;
int flags; int flags;
if ((flags = fcntl(read_fd, F_GETFL)) < 0 || if ((flags = fcntl(read_fd, F_GETFL)) < 0 ||
fcntl(read_fd, F_SETFL, flags | O_NONBLOCK) < 0) fcntl(read_fd, F_SETFL, flags | O_NONBLOCK) < 0)
{ {
LOG_ERRNO("failed to set O_NONBLOCK"); LOG_ERRNO("failed to set O_NONBLOCK");
close(read_fd); goto err;
done(user);
return;
} }
struct clipboard_receive *ctx = xmalloc(sizeof(*ctx)); timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if (timeout_fd < 0) {
LOG_ERRNO("failed to create clipboard timeout timer FD");
goto err;
}
const struct itimerspec timeout = {.it_value = {.tv_sec = 2}};
if (timerfd_settime(timeout_fd, 0, &timeout, NULL) < 0) {
LOG_ERRNO("faild to arm clipboard timeout timer");
goto err;
}
ctx = xmalloc(sizeof(*ctx));
*ctx = (struct clipboard_receive) { *ctx = (struct clipboard_receive) {
.read_fd = read_fd,
.timeout_fd = timeout_fd,
.timeout = timeout,
.cb = cb, .cb = cb,
.done = done, .done = done,
.user = user, .user = user,
}; };
if (!fdm_add(term->fdm, read_fd, EPOLLIN, &fdm_receive, ctx)) { if (!fdm_add(term->fdm, read_fd, EPOLLIN, &fdm_receive, ctx) ||
close(read_fd); !fdm_add(term->fdm, timeout_fd, EPOLLIN, &fdm_receive_timeout, ctx))
free(ctx); {
done(user); goto err;
} }
return;
err:
free(ctx);
fdm_del(term->fdm, timeout_fd);
fdm_del(term->fdm, read_fd);
done(user);
} }
void void
@ -1115,7 +1182,8 @@ static void
from_clipboard_cb(const char *data, size_t size, void *user) from_clipboard_cb(const char *data, size_t size, void *user)
{ {
struct terminal *term = user; struct terminal *term = user;
term_to_slave(term, data, size); assert(term->is_sending_paste_data);
term_paste_data_to_slave(term, data, size);
} }
static void static void
@ -1124,18 +1192,31 @@ from_clipboard_done(void *user)
struct terminal *term = user; struct terminal *term = user;
if (term->bracketed_paste) if (term->bracketed_paste)
term_to_slave(term, "\033[201~", 6); term_paste_data_to_slave(term, "\033[201~", 6);
term->is_sending_paste_data = false;
/* Make sure we send any queued up non-paste data */
if (tll_length(term->ptmx_buffers) > 0)
fdm_event_add(term->fdm, term->ptmx, EPOLLOUT);
} }
void void
selection_from_clipboard(struct seat *seat, struct terminal *term, uint32_t serial) selection_from_clipboard(struct seat *seat, struct terminal *term, uint32_t serial)
{ {
if (term->is_sending_paste_data) {
/* We're already pasting... */
return;
}
struct wl_clipboard *clipboard = &seat->clipboard; struct wl_clipboard *clipboard = &seat->clipboard;
if (clipboard->data_offer == NULL) if (clipboard->data_offer == NULL)
return; return;
term->is_sending_paste_data = true;
if (term->bracketed_paste) if (term->bracketed_paste)
term_to_slave(term, "\033[200~", 6); term_paste_data_to_slave(term, "\033[200~", 6);
text_from_clipboard( text_from_clipboard(
seat, term, &from_clipboard_cb, &from_clipboard_done, term); seat, term, &from_clipboard_cb, &from_clipboard_done, term);
@ -1241,12 +1322,18 @@ selection_from_primary(struct seat *seat, struct terminal *term)
if (term->wl->primary_selection_device_manager == NULL) if (term->wl->primary_selection_device_manager == NULL)
return; return;
if (term->is_sending_paste_data) {
/* We're already pasting... */
return;
}
struct wl_clipboard *clipboard = &seat->clipboard; struct wl_clipboard *clipboard = &seat->clipboard;
if (clipboard->data_offer == NULL) if (clipboard->data_offer == NULL)
return; return;
term->is_sending_paste_data = true;
if (term->bracketed_paste) if (term->bracketed_paste)
term_to_slave(term, "\033[200~", 6); term_paste_data_to_slave(term, "\033[200~", 6);
text_from_primary(seat, term, &from_clipboard_cb, &from_clipboard_done, term); text_from_primary(seat, term, &from_clipboard_cb, &from_clipboard_done, term);
} }

View file

@ -50,31 +50,38 @@ const char *const XCURSOR_RIGHT_SIDE = "right_side";
const char *const XCURSOR_TOP_SIDE = "top_side"; const char *const XCURSOR_TOP_SIDE = "top_side";
const char *const XCURSOR_BOTTOM_SIDE = "bottom_side"; const char *const XCURSOR_BOTTOM_SIDE = "bottom_side";
bool static void
term_to_slave(struct terminal *term, const void *_data, size_t len) enqueue_data_for_slave(const void *data, size_t len, size_t offset,
ptmx_buffer_list_t *buffer_list)
{ {
if (term->ptmx < 0) { void *copy = xmalloc(len);
/* We're probably in "hold" */ memcpy(copy, data, len);
return false;
}
size_t async_idx = 0; struct ptmx_buffer queued = {
if (tll_length(term->ptmx_buffer) > 0) { .data = copy,
/* With a non-empty queue, EPOLLOUT has already been enabled */ .len = len,
goto enqueue_data; .idx = offset,
} };
tll_push_back(*buffer_list, queued);
}
static bool
data_to_slave(struct terminal *term, const void *data, size_t len,
ptmx_buffer_list_t *buffer_list)
{
/* /*
* Try a synchronous write first. If we fail to write everything, * Try a synchronous write first. If we fail to write everything,
* switch to asynchronous. * switch to asynchronous.
*/ */
switch (async_write(term->ptmx, _data, len, &async_idx)) { size_t async_idx = 0;
switch (async_write(term->ptmx, data, len, &async_idx)) {
case ASYNC_WRITE_REMAIN: case ASYNC_WRITE_REMAIN:
/* Switch to asynchronous mode; let FDM write the remaining data */ /* Switch to asynchronous mode; let FDM write the remaining data */
if (!fdm_event_add(term->fdm, term->ptmx, EPOLLOUT)) if (!fdm_event_add(term->fdm, term->ptmx, EPOLLOUT))
return false; return false;
goto enqueue_data; enqueue_data_for_slave(data, len, async_idx, buffer_list);
return true;
case ASYNC_WRITE_DONE: case ASYNC_WRITE_DONE:
return true; return true;
@ -87,24 +94,52 @@ term_to_slave(struct terminal *term, const void *_data, size_t len)
/* Shouldn't get here */ /* Shouldn't get here */
assert(false); assert(false);
return false; return false;
}
enqueue_data: bool
/* term_paste_data_to_slave(struct terminal *term, const void *data, size_t len)
* We're in asynchronous mode - push data to queue and let the FDM {
* handler take care of it assert(term->is_sending_paste_data);
*/
{
void *copy = xmalloc(len);
memcpy(copy, _data, len);
struct ptmx_buffer queued = { if (term->ptmx < 0) {
.data = copy, /* We're probably in "hold" */
.len = len, return false;
.idx = async_idx,
};
tll_push_back(term->ptmx_buffer, queued);
} }
return true;
if (tll_length(term->ptmx_paste_buffers) > 0) {
/* Don't even try to send data *now* if there's queued up
* data, since that would result in events arriving out of
* order. */
enqueue_data_for_slave(data, len, 0, &term->ptmx_paste_buffers);
return true;
}
return data_to_slave(term, data, len, &term->ptmx_paste_buffers);
}
bool
term_to_slave(struct terminal *term, const void *data, size_t len)
{
if (term->ptmx < 0) {
/* We're probably in "hold" */
return false;
}
if (tll_length(term->ptmx_buffers) > 0 || term->is_sending_paste_data) {
/*
* Don't even try to send data *now* if there's queued up
* data, since that would result in events arriving out of
* order.
*
* Furthermore, if we're currently sending paste data to the
* client, do *not* mix that stream with other events
* (https://codeberg.org/dnkl/foot/issues/101).
*/
enqueue_data_for_slave(data, len, 0, &term->ptmx_buffers);
return true;
}
return data_to_slave(term, data, len, &term->ptmx_buffers);
} }
static bool static bool
@ -113,28 +148,48 @@ fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data)
struct terminal *term = data; struct terminal *term = data;
/* If there is no queued data, then we shouldn't be in asynchronous mode */ /* If there is no queued data, then we shouldn't be in asynchronous mode */
assert(tll_length(term->ptmx_buffer) > 0); assert(tll_length(term->ptmx_buffers) > 0 ||
tll_length(term->ptmx_paste_buffers) > 0);
/* Don't use pop() since we may not be able to write the entire buffer */ /* Writes a single buffer, returns if not all of it could be written */
tll_foreach(term->ptmx_buffer, it) { #define write_one_buffer(buffer_list) \
switch (async_write(term->ptmx, it->item.data, it->item.len, &it->item.idx)) { { \
case ASYNC_WRITE_DONE: switch (async_write(term->ptmx, it->item.data, it->item.len, &it->item.idx)) { \
free(it->item.data); case ASYNC_WRITE_DONE: \
tll_remove(term->ptmx_buffer, it); free(it->item.data); \
break; tll_remove(buffer_list, it); \
break; \
case ASYNC_WRITE_REMAIN: case ASYNC_WRITE_REMAIN: \
/* to_slave() updated it->item.idx */ /* to_slave() updated it->item.idx */ \
return true; return true; \
case ASYNC_WRITE_ERR: \
case ASYNC_WRITE_ERR: LOG_ERRNO("failed to asynchronously write %zu bytes to slave", \
LOG_ERRNO("failed to asynchronously write %zu bytes to slave", it->item.len - it->item.idx); \
it->item.len - it->item.idx); return false; \
return false; } \
}
} }
/* No more queued data, switch back to synchronous mode */ tll_foreach(term->ptmx_paste_buffers, it)
write_one_buffer(term->ptmx_paste_buffers);
/* If we get here, *all* paste data buffers were successfully
* flushed */
if (!term->is_sending_paste_data) {
tll_foreach(term->ptmx_buffers, it)
write_one_buffer(term->ptmx_buffers);
}
/*
* If we get here, *all* buffers were successfully flushed.
*
* Or, we're still sending paste data, in which case we do *not*
* want to send the "normal" queued up data
*
* In both cases, we want to *disable* the FDM callback since
* otherwise we'd just be called right away again, with nothing to
* write.
*/
fdm_event_del(term->fdm, term->ptmx, EPOLLOUT); fdm_event_del(term->fdm, term->ptmx, EPOLLOUT);
return true; return true;
} }
@ -840,7 +895,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.conf = conf, .conf = conf,
.quit = false, .quit = false,
.ptmx = ptmx, .ptmx = ptmx,
.ptmx_buffer = tll_init(), .ptmx_buffers = tll_init(),
.ptmx_paste_buffers = tll_init(),
.font_sizes = xmalloc(sizeof(term->font_sizes[0]) * tll_length(conf->fonts)), .font_sizes = xmalloc(sizeof(term->font_sizes[0]) * tll_length(conf->fonts)),
.font_dpi = 0., .font_dpi = 0.,
.font_subpixel = (conf->colors.alpha == 0xffff /* Can't do subpixel rendering on transparent background */ .font_subpixel = (conf->colors.alpha == 0xffff /* Can't do subpixel rendering on transparent background */
@ -1206,9 +1262,12 @@ term_destroy(struct terminal *term)
assert(tll_length(term->render.workers.queue) == 0); assert(tll_length(term->render.workers.queue) == 0);
tll_free(term->render.workers.queue); tll_free(term->render.workers.queue);
tll_foreach(term->ptmx_buffer, it) tll_foreach(term->ptmx_buffers, it)
free(it->item.data); free(it->item.data);
tll_free(term->ptmx_buffer); tll_free(term->ptmx_buffers);
tll_foreach(term->ptmx_paste_buffers, it)
free(it->item.data);
tll_free(term->ptmx_paste_buffers);
tll_free(term->tab_stops); tll_free(term->tab_stops);
tll_foreach(term->normal.sixel_images, it) tll_foreach(term->normal.sixel_images, it)

View file

@ -205,6 +205,8 @@ enum term_surface {
TERM_SURF_BUTTON_CLOSE, TERM_SURF_BUTTON_CLOSE,
}; };
typedef tll(struct ptmx_buffer) ptmx_buffer_list_t;
struct terminal { struct terminal {
struct fdm *fdm; struct fdm *fdm;
struct reaper *reaper; struct reaper *reaper;
@ -226,7 +228,9 @@ struct terminal {
float font_dpi; float font_dpi;
enum fcft_subpixel font_subpixel; enum fcft_subpixel font_subpixel;
tll(struct ptmx_buffer) ptmx_buffer; bool is_sending_paste_data;
ptmx_buffer_list_t ptmx_buffers;
ptmx_buffer_list_t ptmx_paste_buffers;
enum cursor_origin origin; enum cursor_origin origin;
enum cursor_keys cursor_keys_mode; enum cursor_keys cursor_keys_mode;
@ -489,6 +493,8 @@ int term_destroy(struct terminal *term);
void term_reset(struct terminal *term, bool hard); void term_reset(struct terminal *term, bool hard);
bool term_to_slave(struct terminal *term, const void *data, size_t len); bool term_to_slave(struct terminal *term, const void *data, size_t len);
bool term_paste_data_to_slave(
struct terminal *term, const void *data, size_t len);
bool term_font_size_increase(struct terminal *term); bool term_font_size_increase(struct terminal *term);
bool term_font_size_decrease(struct terminal *term); bool term_font_size_decrease(struct terminal *term);