diff --git a/csi.c b/csi.c index d6ae1f94..42f7b5e2 100644 --- a/csi.c +++ b/csi.c @@ -866,7 +866,7 @@ csi_dispatch(struct terminal *term, uint8_t final) break; case 12: - /* Ignored */ + term_cursor_blink_enable(term); break; case 25: @@ -992,7 +992,7 @@ csi_dispatch(struct terminal *term, uint8_t final) break; case 12: - /* Ignored */ + term_cursor_blink_disable(term); break; case 25: @@ -1224,9 +1224,10 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } - term->cursor_blinking = param == 0 || param & 1; - if (term->cursor_blinking) - LOG_WARN("unimplemented: blinking cursor"); + if (param == 0 || param & 1) + term_cursor_blink_enable(term); + else + term_cursor_blink_disable(term); break; } diff --git a/render.c b/render.c index b8d8381c..1b574a83 100644 --- a/render.c +++ b/render.c @@ -184,9 +184,13 @@ render_cell(struct terminal *term, pixman_image_t *pix, int x = term->x_margin + col * width; int y = term->y_margin + row * height; - bool block_cursor = has_cursor && term->cursor_style == CURSOR_BLOCK; bool is_selected = coord_is_selected(term, col, row); + bool block_cursor = + has_cursor && + term->cursor_style == CURSOR_BLOCK && + term->cursor_blink.state == CURSOR_BLINK_ON; + uint32_t _fg = 0; uint32_t _bg = 0; @@ -240,7 +244,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, &(pixman_rectangle16_t){x, y, cell_cols * width, height}); /* Non-block cursors */ - if (has_cursor && !block_cursor) { + if (has_cursor && !block_cursor && term->cursor_blink.state == CURSOR_BLINK_ON) { pixman_color_t cursor_color; if (term->cursor_color.text >> 31) { cursor_color = color_hex_to_pixman(term->cursor_color.cursor); @@ -524,6 +528,15 @@ grid_render(struct terminal *term) /* Detect cursor movement - we don't dirty cells touched * by the cursor, since only the final cell matters. */ all_clean = false; + + /* Force cursor blink to ON, to avoid blinking while moving cursor */ + term->render.last_cursor.blink_state = CURSOR_BLINK_ON; + term->cursor_blink.state = CURSOR_BLINK_ON; + } + + if (term->render.last_cursor.blink_state != term->cursor_blink.state) { + /* Need to re-draw cursor */ + all_clean = false; } } @@ -675,6 +688,7 @@ grid_render(struct terminal *term) cell->attrs.clean = 0; term->render.last_cursor.cell = cell; + term->render.last_cursor.blink_state = term->cursor_blink.state; int cols_updated = render_cell( term, pix, cell, term->cursor.point.col, view_aligned_row, true); diff --git a/terminal.c b/terminal.c index f949aa8f..5fe9e072 100644 --- a/terminal.c +++ b/terminal.c @@ -275,6 +275,36 @@ fdm_blink(struct fdm *fdm, int fd, int events, void *data) return true; } +static bool +fdm_cursor_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->cursor_blink.fd, &expiration_count, sizeof(expiration_count)); + + if (ret < 0) { + if (errno == EAGAIN) + return true; + + LOG_ERRNO("failed to read cursor blink timer"); + return false; + } + + LOG_DBG("cursor blink timer expired %llu times", + (unsigned long long)expiration_count); + + /* Invert blink state */ + term->cursor_blink.state = term->cursor_blink.state == CURSOR_BLINK_ON + ? CURSOR_BLINK_OFF : CURSOR_BLINK_ON; + + render_refresh(term); + return true; +} + static bool fdm_delayed_render(struct fdm *fdm, int fd, int events, void *data) { @@ -403,6 +433,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, int ptmx = -1; int flash_fd = -1; int blink_fd = -1; + int cursor_blink_fd = -1; int delay_lower_fd = -1; int delay_upper_fd = -1; @@ -420,6 +451,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, LOG_ERRNO("failed to create blink timer FD"); goto close_fds; } + if ((cursor_blink_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) { + LOG_ERRNO("failed to create cursor blink timer FD"); + goto close_fds; + } if ((delay_lower_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)) == -1 || (delay_upper_fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) { @@ -438,6 +473,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, if (!fdm_add(fdm, ptmx, EPOLLIN, &fdm_ptmx, term) || !fdm_add(fdm, flash_fd, EPOLLIN, &fdm_flash, term) || !fdm_add(fdm, blink_fd, EPOLLIN, &fdm_blink, term) || + !fdm_add(fdm, cursor_blink_fd, EPOLLIN, &fdm_cursor_blink, term) || !fdm_add(fdm, delay_lower_fd, EPOLLIN, &fdm_delayed_render, term) || !fdm_add(fdm, delay_upper_fd, EPOLLIN, &fdm_delayed_render, term)) { @@ -489,6 +525,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, .origin = ORIGIN_ABSOLUTE, .default_cursor_style = conf->cursor.style, .cursor_style = conf->cursor.style, + .cursor_blink = { + .state = CURSOR_BLINK_ON, + .fd = cursor_blink_fd, + }, .default_cursor_color = { .text = conf->cursor.color.text, .cursor = conf->cursor.color.cursor, @@ -572,6 +612,7 @@ close_fds: fdm_del(fdm, ptmx); fdm_del(fdm, flash_fd); fdm_del(fdm, blink_fd); + fdm_del(fdm, cursor_blink_fd); fdm_del(fdm, delay_lower_fd); fdm_del(fdm, delay_upper_fd); @@ -688,6 +729,7 @@ term_destroy(struct terminal *term) fdm_del(term->fdm, term->delayed_render_timer.lower_fd); fdm_del(term->fdm, term->delayed_render_timer.upper_fd); + fdm_del(term->fdm, term->cursor_blink.fd); fdm_del(term->fdm, term->blink.fd); fdm_del(term->fdm, term->flash.fd); fdm_del(term->fdm, term->ptmx); @@ -878,7 +920,7 @@ term_reset(struct terminal *term, bool hard) term->saved_cursor = (struct cursor){.point = {0, 0}}; term->alt_saved_cursor = (struct cursor){.point = {0, 0}}; term->cursor_style = term->default_cursor_style; - term->cursor_blinking = false; + term_cursor_blink_disable(term); term->cursor_color.text = term->default_cursor_color.text; term->cursor_color.cursor = term->default_cursor_color.cursor; selection_cancel(term); @@ -1085,6 +1127,27 @@ term_cursor_down(struct terminal *term, int count) term_cursor_to(term, term->cursor.point.row + move_amount, term->cursor.point.col); } +void +term_cursor_blink_enable(struct terminal *term) +{ + static const struct itimerspec timer = { + .it_value = {.tv_sec = 0, .tv_nsec = 500000000}, + .it_interval = {.tv_sec = 0, .tv_nsec = 500000000}, + }; + + if (timerfd_settime(term->cursor_blink.fd, 0, &timer, NULL) < 0) + LOG_ERRNO("failed to arm cursor blink timer"); + + term->cursor_blink.state = CURSOR_BLINK_ON; +} + +void +term_cursor_blink_disable(struct terminal *term) +{ + term->cursor_blink.state = CURSOR_BLINK_ON; + timerfd_settime(term->cursor_blink.fd, 0, &(struct itimerspec){{0}}, NULL); +} + void term_scroll_partial(struct terminal *term, struct scroll_region region, int rows) { diff --git a/terminal.h b/terminal.h index b73f1c21..e29f5269 100644 --- a/terminal.h +++ b/terminal.h @@ -232,7 +232,10 @@ struct terminal { struct cursor alt_saved_cursor; enum cursor_style default_cursor_style; enum cursor_style cursor_style; - bool cursor_blinking; + struct { + enum { CURSOR_BLINK_ON, CURSOR_BLINK_OFF } state; + int fd; + } cursor_blink; struct { uint32_t text; uint32_t cursor; @@ -292,6 +295,7 @@ struct terminal { struct coord actual; /* Absolute */ struct coord in_view; /* Offset by view */ struct cell *cell; /* For easy access to content */ + int blink_state; } last_cursor; struct buffer *last_buf; /* Buffer we rendered to last time */ @@ -345,6 +349,8 @@ void term_cursor_left(struct terminal *term, int count); void term_cursor_right(struct terminal *term, int count); void term_cursor_up(struct terminal *term, int count); void term_cursor_down(struct terminal *term, int count); +void term_cursor_blink_enable(struct terminal *term); +void term_cursor_blink_disable(struct terminal *term); void term_scroll(struct terminal *term, int rows); void term_scroll_reverse(struct terminal *term, int rows);