From fe974956b045de20df3f39f6804c5ae934ea8e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 28 Oct 2019 18:35:16 +0100 Subject: [PATCH] term: integrate directly with FDM --- main.c | 218 +------------------------------------------------- terminal.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 224 insertions(+), 223 deletions(-) diff --git a/main.c b/main.c index 3d951b67..64dea222 100644 --- a/main.c +++ b/main.c @@ -3,227 +3,26 @@ #include #include #include -#include -#include -#include #include #include -#include #include -#include #include -#include -#include - -#include -#include -#include - -#include -#include #define LOG_MODULE "main" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "log.h" #include "config.h" #include "fdm.h" #include "font.h" -#include "grid.h" -#include "render.h" #include "shm.h" -#include "slave.h" #include "terminal.h" -#include "tokenize.h" #include "version.h" -#include "vt.h" #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) -static bool -fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) -{ - struct terminal *term = data; - - if (events & EPOLLHUP) { - term->quit = true; - - if (!(events & EPOLLIN)) - return false; - } - - assert(events & EPOLLIN); - - uint8_t buf[24 * 1024]; - ssize_t count = read(term->ptmx, buf, sizeof(buf)); - - if (count < 0) { - if (errno == EAGAIN) - return true; - - LOG_ERRNO("failed to read from pseudo terminal"); - return false; - } - - vt_from_slave(term, buf, count); - - /* - * We likely need to re-render. But, we don't want to - * do it immediately. Often, a single client operation - * is done through multiple writes. Many times, we're - * so fast that we render mid-operation frames. - * - * For example, we might end up rendering a frame - * where the client just erased a line, while in the - * next frame, the client wrote to the same line. This - * causes screen "flashes". - * - * Mitigate by always incuring a small delay before - * rendering the next frame. This gives the client - * some time to finish the operation (and thus gives - * us time to receive the last writes before doing any - * actual rendering). - * - * We incur this delay *every* time we receive - * input. To ensure we don't delay rendering - * indefinitely, we start a second timer that is only - * reset when we render. - * - * Note that when the client is producing data at a - * very high pace, we're rate limited by the wayland - * compositor anyway. The delay we introduce here only - * has any effect when the renderer is idle. - * - * TODO: this adds input latency. Can we somehow hint - * ourselves we just received keyboard input, and in - * this case *not* delay rendering? - */ - if (term->window->frame_callback == NULL) { - /* First timeout - reset each time we receive input. */ - timerfd_settime( - term->delayed_render_timer.lower_fd, 0, - &(struct itimerspec){.it_value = {.tv_nsec = 1000000}}, - NULL); - - /* Second timeout - only reset when we render. Set to one - * frame (assuming 60Hz) */ - if (!term->delayed_render_timer.is_armed) { - timerfd_settime( - term->delayed_render_timer.upper_fd, 0, - &(struct itimerspec){.it_value = {.tv_nsec = 16666666}}, - NULL); - term->delayed_render_timer.is_armed = true; - } - } - - return !(events & EPOLLHUP); -} - -static bool -fdm_flash(struct fdm *fdm, int fd, int events, void *data) -{ - if (events & EPOLLHUP) - return false; - - struct terminal *term = data; - uint64_t expiration_count; - ssize_t ret = read( - term->flash.fd, &expiration_count, sizeof(expiration_count)); - - if (ret < 0) { - if (errno == EAGAIN) - return true; - - LOG_ERRNO("failed to read flash timer"); - return false; - } - - LOG_DBG("flash timer expired %llu times", - (unsigned long long)expiration_count); - - term->flash.active = false; - term_damage_view(term); - render_refresh(term); - return true; -} - -static bool -fdm_blink(struct fdm *fdm, int fd, int events, void *data) -{ - if (events & EPOLLHUP) - return false; - - struct terminal *term = data; - uint64_t expiration_count; - ssize_t ret = read( - term->blink.fd, &expiration_count, sizeof(expiration_count)); - - if (ret < 0) { - if (errno == EAGAIN) - return true; - - LOG_ERRNO("failed to read blink timer"); - return false; - } - - LOG_DBG("blink timer expired %llu times", - (unsigned long long)expiration_count); - - term->blink.state = term->blink.state == BLINK_ON - ? BLINK_OFF : BLINK_ON; - - /* Scan all visible cells and mark rows with blinking cells dirty */ - for (int r = 0; r < term->rows; r++) { - struct row *row = grid_row_in_view(term->grid, r); - for (int col = 0; col < term->cols; col++) { - struct cell *cell = &row->cells[col]; - - if (cell->attrs.blink) { - cell->attrs.clean = 0; - row->dirty = true; - } - } - } - - render_refresh(term); - return true; -} - -static bool -fdm_delayed_render(struct fdm *fdm, int fd, int events, void *data) -{ - if (events & EPOLLHUP) - return false; - - struct terminal *term = data; - assert(term->delayed_render_timer.is_armed); - - uint64_t unused; - ssize_t ret1 = 0; - ssize_t ret2 = 0; - - if (fd == term->delayed_render_timer.lower_fd) - ret1 = read(term->delayed_render_timer.lower_fd, &unused, sizeof(unused)); - if (fd == term->delayed_render_timer.upper_fd) - ret2 = read(term->delayed_render_timer.upper_fd, &unused, sizeof(unused)); - - if ((ret1 < 0 || ret2 < 0) && errno != EAGAIN) - LOG_ERRNO("failed to read timeout timer"); - else if (ret1 > 0 || ret2 > 0) { - render_refresh(term); - - /* Reset timers */ - term->delayed_render_timer.is_armed = false; - timerfd_settime(term->delayed_render_timer.lower_fd, 0, &(struct itimerspec){.it_value = {0}}, NULL); - timerfd_settime(term->delayed_render_timer.upper_fd, 0, &(struct itimerspec){.it_value = {0}}, NULL); - } else - assert(false); - - return true; -} - static void print_usage(const char *prog_name) { @@ -330,7 +129,6 @@ main(int argc, char *const *argv) setlocale(LC_ALL, ""); setenv("TERM", conf.term, 1); - struct fdm *fdm = NULL; struct wayland *wayl = NULL; struct terminal *term = NULL; @@ -344,12 +142,6 @@ main(int argc, char *const *argv) if ((term = term_init(&conf, fdm, wayl, argc, argv)) == NULL) goto out; - fdm_add(fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term); - fdm_add(fdm, term->flash.fd, EPOLLIN, &fdm_flash, term); - fdm_add(fdm, term->blink.fd, EPOLLIN, &fdm_blink, term); - fdm_add(fdm, term->delayed_render_timer.lower_fd, EPOLLIN, &fdm_delayed_render, term); - fdm_add(fdm, term->delayed_render_timer.upper_fd, EPOLLIN, &fdm_delayed_render, term); - while (true) { wl_display_flush(term->wl->display); /* TODO: figure out how to get rid of this */ @@ -361,14 +153,6 @@ main(int argc, char *const *argv) ret = EXIT_SUCCESS; out: - if (fdm != NULL) { - fdm_del(fdm, term->ptmx); - fdm_del(fdm, term->flash.fd); - fdm_del(fdm, term->blink.fd); - fdm_del(fdm, term->delayed_render_timer.lower_fd); - fdm_del(fdm, term->delayed_render_timer.upper_fd); - } - shm_fini(); int child_ret = term_destroy(term); diff --git a/terminal.c b/terminal.c index 023390d9..2cf87fa6 100644 --- a/terminal.c +++ b/terminal.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,188 @@ #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) +static bool +fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) +{ + struct terminal *term = data; + + if (events & EPOLLHUP) { + term->quit = true; + + if (!(events & EPOLLIN)) + return false; + } + + assert(events & EPOLLIN); + + uint8_t buf[24 * 1024]; + ssize_t count = read(term->ptmx, buf, sizeof(buf)); + + if (count < 0) { + if (errno == EAGAIN) + return true; + + LOG_ERRNO("failed to read from pseudo terminal"); + return false; + } + + vt_from_slave(term, buf, count); + + /* + * We likely need to re-render. But, we don't want to + * do it immediately. Often, a single client operation + * is done through multiple writes. Many times, we're + * so fast that we render mid-operation frames. + * + * For example, we might end up rendering a frame + * where the client just erased a line, while in the + * next frame, the client wrote to the same line. This + * causes screen "flashes". + * + * Mitigate by always incuring a small delay before + * rendering the next frame. This gives the client + * some time to finish the operation (and thus gives + * us time to receive the last writes before doing any + * actual rendering). + * + * We incur this delay *every* time we receive + * input. To ensure we don't delay rendering + * indefinitely, we start a second timer that is only + * reset when we render. + * + * Note that when the client is producing data at a + * very high pace, we're rate limited by the wayland + * compositor anyway. The delay we introduce here only + * has any effect when the renderer is idle. + * + * TODO: this adds input latency. Can we somehow hint + * ourselves we just received keyboard input, and in + * this case *not* delay rendering? + */ + if (term->window->frame_callback == NULL) { + /* First timeout - reset each time we receive input. */ + timerfd_settime( + term->delayed_render_timer.lower_fd, 0, + &(struct itimerspec){.it_value = {.tv_nsec = 1000000}}, + NULL); + + /* Second timeout - only reset when we render. Set to one + * frame (assuming 60Hz) */ + if (!term->delayed_render_timer.is_armed) { + timerfd_settime( + term->delayed_render_timer.upper_fd, 0, + &(struct itimerspec){.it_value = {.tv_nsec = 16666666}}, + NULL); + term->delayed_render_timer.is_armed = true; + } + } + + return !(events & EPOLLHUP); +} + +static bool +fdm_flash(struct fdm *fdm, int fd, int events, void *data) +{ + if (events & EPOLLHUP) + return false; + + struct terminal *term = data; + uint64_t expiration_count; + ssize_t ret = read( + term->flash.fd, &expiration_count, sizeof(expiration_count)); + + if (ret < 0) { + if (errno == EAGAIN) + return true; + + LOG_ERRNO("failed to read flash timer"); + return false; + } + + LOG_DBG("flash timer expired %llu times", + (unsigned long long)expiration_count); + + term->flash.active = false; + term_damage_view(term); + render_refresh(term); + return true; +} + +static bool +fdm_blink(struct fdm *fdm, int fd, int events, void *data) +{ + if (events & EPOLLHUP) + return false; + + struct terminal *term = data; + uint64_t expiration_count; + ssize_t ret = read( + term->blink.fd, &expiration_count, sizeof(expiration_count)); + + if (ret < 0) { + if (errno == EAGAIN) + return true; + + LOG_ERRNO("failed to read blink timer"); + return false; + } + + LOG_DBG("blink timer expired %llu times", + (unsigned long long)expiration_count); + + term->blink.state = term->blink.state == BLINK_ON + ? BLINK_OFF : BLINK_ON; + + /* Scan all visible cells and mark rows with blinking cells dirty */ + for (int r = 0; r < term->rows; r++) { + struct row *row = grid_row_in_view(term->grid, r); + for (int col = 0; col < term->cols; col++) { + struct cell *cell = &row->cells[col]; + + if (cell->attrs.blink) { + cell->attrs.clean = 0; + row->dirty = true; + } + } + } + + render_refresh(term); + return true; +} + +static bool +fdm_delayed_render(struct fdm *fdm, int fd, int events, void *data) +{ + if (events & EPOLLHUP) + return false; + + struct terminal *term = data; + assert(term->delayed_render_timer.is_armed); + + uint64_t unused; + ssize_t ret1 = 0; + ssize_t ret2 = 0; + + if (fd == term->delayed_render_timer.lower_fd) + ret1 = read(term->delayed_render_timer.lower_fd, &unused, sizeof(unused)); + if (fd == term->delayed_render_timer.upper_fd) + ret2 = read(term->delayed_render_timer.upper_fd, &unused, sizeof(unused)); + + if ((ret1 < 0 || ret2 < 0) && errno != EAGAIN) + LOG_ERRNO("failed to read timeout timer"); + else if (ret1 > 0 || ret2 > 0) { + render_refresh(term); + + /* Reset timers */ + term->delayed_render_timer.is_armed = false; + timerfd_settime(term->delayed_render_timer.lower_fd, 0, &(struct itimerspec){.it_value = {0}}, NULL); + timerfd_settime(term->delayed_render_timer.upper_fd, 0, &(struct itimerspec){.it_value = {0}}, NULL); + } else + assert(false); + + return true; +} + struct terminal * term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, int argc, char *const *argv) @@ -278,6 +461,28 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, } } + if (!fdm_add(fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term)) { + LOG_ERR("failed to add ptmx to FDM"); + goto out; + } + + if (!fdm_add(fdm, term->flash.fd, EPOLLIN, &fdm_flash, term)) { + LOG_ERR("failed to add flash timer FD to FDM"); + goto out; + } + + if (!fdm_add(fdm, term->blink.fd, EPOLLIN, &fdm_blink, term)) { + LOG_ERR("failed to add blink tiemr FD to FDM"); + goto out; + } + + if (!fdm_add(fdm, term->delayed_render_timer.lower_fd, EPOLLIN, &fdm_delayed_render, term) || + !fdm_add(fdm, term->delayed_render_timer.upper_fd, EPOLLIN, &fdm_delayed_render, term)) + { + LOG_ERR("failed to add delayed rendering timer FDs to FDM"); + goto out; + } + wayl->term = term; return term; @@ -294,10 +499,15 @@ term_destroy(struct terminal *term) wayl_win_destroy(term->window); - if (term->delayed_render_timer.lower_fd != -1) + if (term->delayed_render_timer.lower_fd != -1) { + fdm_del(term->fdm, term->delayed_render_timer.lower_fd); close(term->delayed_render_timer.lower_fd); - if (term->delayed_render_timer.upper_fd != -1) + } + + if (term->delayed_render_timer.upper_fd != -1) { + fdm_del(term->fdm, term->delayed_render_timer.upper_fd); close(term->delayed_render_timer.upper_fd); + } mtx_lock(&term->render.workers.lock); assert(tll_length(term->render.workers.queue) == 0); @@ -323,13 +533,20 @@ term_destroy(struct terminal *term) free(term->search.buf); - if (term->flash.fd != -1) + if (term->flash.fd != -1) { + fdm_del(term->fdm, term->flash.fd); close(term->flash.fd); - if (term->blink.fd != -1) - close(term->blink.fd); + } - if (term->ptmx != -1) + if (term->blink.fd != -1) { + fdm_del(term->fdm, term->blink.fd); + close(term->blink.fd); + } + + if (term->ptmx != -1) { + fdm_del(term->fdm, term->ptmx); close(term->ptmx); + } for (size_t i = 0; i < term->render.workers.count; i++) thrd_join(term->render.workers.threads[i], NULL);