diff --git a/csi.c b/csi.c index a7892e9a..a5e19290 100644 --- a/csi.c +++ b/csi.c @@ -325,7 +325,7 @@ csi_dispatch(struct terminal *term, uint8_t final) case 0: { switch (final) { case 'c': - vt_to_slave(term, "\033[?6c", 5); + term_to_slave(term, "\033[?6c", 5); break; case 'd': { @@ -690,7 +690,7 @@ csi_dispatch(struct terminal *term, uint8_t final) snprintf(reply, sizeof(reply), "\x1b[%d;%dR", term->cursor.row + 1, term->cursor.col + 1); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); break; } @@ -910,7 +910,7 @@ csi_dispatch(struct terminal *term, uint8_t final) */ char reply[32]; snprintf(reply, sizeof(reply), "\033[?%u;2$y", param); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); break; } @@ -965,7 +965,7 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } - vt_to_slave(term, "\033[>41;347;0c", 12); + term_to_slave(term, "\033[>41;347;0c", 12); break; } diff --git a/fdm.c b/fdm.c index e626b47f..72e32328 100644 --- a/fdm.c +++ b/fdm.c @@ -14,6 +14,7 @@ struct handler { int fd; + int events; fdm_handler_t callback; void *callback_data; bool deleted; @@ -78,6 +79,7 @@ fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data) struct handler *fd_data = malloc(sizeof(*fd_data)); *fd_data = (struct handler) { .fd = fd, + .events = events, .callback = handler, .callback_data = data, .deleted = false, @@ -91,7 +93,7 @@ fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data) }; if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { - LOG_ERRNO("failed to register FD with epoll"); + LOG_ERRNO("failed to register FD=%d with epoll", fd); free(fd_data); tll_pop_back(fdm->fds); return false; @@ -107,22 +109,23 @@ fdm_del_internal(struct fdm *fdm, int fd, bool close_fd) return true; tll_foreach(fdm->fds, it) { - if (it->item->fd == fd) { - if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0) - LOG_ERRNO("failed to unregister FD=%d from epoll", fd); + if (it->item->fd != fd) + continue; - if (close_fd) - close(it->item->fd); + if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0) + LOG_ERRNO("failed to unregister FD=%d from epoll", fd); - it->item->deleted = true; - if (fdm->is_polling) - tll_push_back(fdm->deferred_delete, it->item); - else - free(it->item); + if (close_fd) + close(it->item->fd); - tll_remove(fdm->fds, it); - return true; - } + it->item->deleted = true; + if (fdm->is_polling) + tll_push_back(fdm->deferred_delete, it->item); + else + free(it->item); + + tll_remove(fdm->fds, it); + return true; } LOG_ERR("no such FD: %d", fd); @@ -141,6 +144,55 @@ fdm_del_no_close(struct fdm *fdm, int fd) return fdm_del_internal(fdm, fd, false); } +static bool +event_modify(struct fdm *fdm, struct handler *fd, int new_events) +{ + if (new_events == fd->events) + return true; + + struct epoll_event ev = { + .events = new_events, + .data = {.ptr = fd}, + }; + + if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_MOD, fd->fd, &ev) < 0) { + LOG_ERRNO("failed to modify FD=%d with epoll (events 0x%08x -> 0x%08x)", + fd->fd, fd->events, new_events); + return false; + } + + fd->events = new_events; + return true; +} + +bool +fdm_event_add(struct fdm *fdm, int fd, int events) +{ + tll_foreach(fdm->fds, it) { + if (it->item->fd != fd) + continue; + + return event_modify(fdm, it->item, it->item->events | events); + } + + LOG_ERR("FD=%d not registered with the FDM", fd); + return false; +} + +bool +fdm_event_del(struct fdm *fdm, int fd, int events) +{ + tll_foreach(fdm->fds, it) { + if (it->item->fd != fd) + continue; + + return event_modify(fdm, it->item, it->item->events & ~events); + } + + LOG_ERR("FD=%d not registered with the FDM", fd); + return false; +} + bool fdm_poll(struct fdm *fdm) { diff --git a/fdm.h b/fdm.h index fb12bffb..62d73ded 100644 --- a/fdm.h +++ b/fdm.h @@ -13,4 +13,7 @@ bool fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *d bool fdm_del(struct fdm *fdm, int fd); bool fdm_del_no_close(struct fdm *fdm, int fd); +bool fdm_event_add(struct fdm *fdm, int fd, int events); +bool fdm_event_del(struct fdm *fdm, int fd, int events); + bool fdm_poll(struct fdm *fdm); diff --git a/input.c b/input.c index bee51244..40cfa613 100644 --- a/input.c +++ b/input.c @@ -258,7 +258,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, info->keypad_keys_mode != term->keypad_keys_mode) continue; - vt_to_slave(term, info->seq, strlen(info->seq)); + term_to_slave(term, info->seq, strlen(info->seq)); found_map = true; term_reset_view(term); @@ -314,14 +314,14 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, char reply[1024]; snprintf(reply, sizeof(reply), "\x1b[27;%d;%d~", modify_param, sym); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); } else { if (effective_mods & alt) - vt_to_slave(term, "\x1b", 1); + term_to_slave(term, "\x1b", 1); - vt_to_slave(term, buf, count); + term_to_slave(term, buf, count); } term_reset_view(term); diff --git a/osc.c b/osc.c index e860edcd..6b86be03 100644 --- a/osc.c +++ b/osc.c @@ -78,7 +78,7 @@ from_clipboard_cb(const char *text, size_t size, void *user) assert(chunk != NULL); assert(strlen(chunk) == 4); - vt_to_slave(term, chunk, 4); + term_to_slave(term, chunk, 4); free(chunk); ctx->idx = 0; @@ -98,7 +98,7 @@ from_clipboard_cb(const char *text, size_t size, void *user) char *chunk = base64_encode((const uint8_t *)t, left / 3 * 3); assert(chunk != NULL); assert(strlen(chunk) % 4 == 0); - vt_to_slave(term, chunk, strlen(chunk)); + term_to_slave(term, chunk, strlen(chunk)); free(chunk); } @@ -120,9 +120,9 @@ osc_from_clipboard(struct terminal *term, const char *source) if (src == 0) return; - vt_to_slave(term, "\033]52;", 5); - vt_to_slave(term, &src, 1); - vt_to_slave(term, ";", 1); + term_to_slave(term, "\033]52;", 5); + term_to_slave(term, &src, 1); + term_to_slave(term, ";", 1); struct clip_context ctx = { .term = term, @@ -141,10 +141,10 @@ osc_from_clipboard(struct terminal *term, const char *source) if (ctx.idx > 0) { char res[4]; base64_encode_final(ctx.buf, ctx.idx, res); - vt_to_slave(term, res, 4); + term_to_slave(term, res, 4); } - vt_to_slave(term, "\033\\", 2); + term_to_slave(term, "\033\\", 2); } static void @@ -343,7 +343,7 @@ osc_dispatch(struct terminal *term) char reply[32]; snprintf(reply, sizeof(reply), "\033]4;%u;rgb:%02x/%02x/%02x\033\\", idx, r, g, b); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); break; } @@ -377,7 +377,7 @@ osc_dispatch(struct terminal *term) reply, sizeof(reply), "\033]%u;rgb:%02x/%02x/%02x\033\\", param, r, g, b); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); break; } diff --git a/selection.c b/selection.c index d2ed7354..6490fb06 100644 --- a/selection.c +++ b/selection.c @@ -533,7 +533,7 @@ static void from_clipboard_cb(const char *data, size_t size, void *user) { struct terminal *term = user; - vt_to_slave(term, data, size); + term_to_slave(term, data, size); } void @@ -544,12 +544,12 @@ selection_from_clipboard(struct terminal *term, uint32_t serial) return; if (term->bracketed_paste) - vt_to_slave(term, "\033[200~", 6); + term_to_slave(term, "\033[200~", 6); text_from_clipboard(term, serial, &from_clipboard_cb, term); if (term->bracketed_paste) - vt_to_slave(term, "\033[201~", 6); + term_to_slave(term, "\033[201~", 6); } bool @@ -675,12 +675,12 @@ selection_from_primary(struct terminal *term) return; if (term->bracketed_paste) - vt_to_slave(term, "\033[200~", 6); + term_to_slave(term, "\033[200~", 6); text_from_primary(term, &from_clipboard_cb, term); if (term->bracketed_paste) - vt_to_slave(term, "\033[201~", 6); + term_to_slave(term, "\033[201~", 6); } #if 0 diff --git a/server.c b/server.c index a0d6cc94..217460a9 100644 --- a/server.c +++ b/server.c @@ -196,7 +196,6 @@ fdm_server(struct fdm *fdm, int fd, int events, void *data) }; if (!fdm_add(server->fdm, client_fd, EPOLLIN, &fdm_client, client)) { - LOG_ERR("client FD=%d: failed to add client to FDM", client_fd); close(client_fd); free(client); return false; @@ -321,10 +320,8 @@ server_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl) .clients = tll_init(), }; - if (!fdm_add(fdm, fd, EPOLLIN, &fdm_server, server)) { - LOG_ERR("failed to add server FD to the FDM"); + if (!fdm_add(fdm, fd, EPOLLIN, &fdm_server, server)) goto err; - } return server; diff --git a/terminal.c b/terminal.c index ccc6ec41..a34df466 100644 --- a/terminal.c +++ b/terminal.c @@ -26,15 +26,103 @@ #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) +enum ptmx_write_status {PTMX_WRITE_DONE, PTMX_WRITE_REMAIN, PTMX_WRITE_ERR}; + +static enum ptmx_write_status +to_slave(struct terminal *term, const void *_data, size_t len, size_t *idx) +{ + const uint8_t *const data = _data; + size_t left = len - *idx; + + while (left > 0) { + ssize_t ret = write(term->ptmx, &data[*idx], left); + + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return PTMX_WRITE_REMAIN; + + LOG_ERRNO("failed to write to client"); + return PTMX_WRITE_ERR; + } + + *idx += ret; + left -= left; + } + + return PTMX_WRITE_DONE; +} + +bool +term_to_slave(struct terminal *term, const void *_data, size_t len) +{ + if (tll_length(term->ptmx_buffer) > 0) { + /* With a non-empty queue, EPOLLOUT has already been enabled */ + goto enqueue_data; + } + + switch (to_slave(term, _data, len, &(size_t){0})) { + case PTMX_WRITE_REMAIN: + if (!fdm_event_add(term->fdm, term->ptmx, EPOLLOUT)) + return false; + goto enqueue_data; + + case PTMX_WRITE_DONE: return true; + case PTMX_WRITE_ERR: return false; + } + +enqueue_data: + { + void *copy = malloc(len); + memcpy(copy, _data, len); + + struct ptmx_buffer queued = { + .data = copy, + .len = len, + .idx = 0, + }; + tll_push_back(term->ptmx_buffer, queued); + } + + return true; +} + +static bool +fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data) +{ + struct terminal *term = data; + assert(tll_length(term->ptmx_buffer) > 0); + + tll_foreach(term->ptmx_buffer, it) { + switch (to_slave(term, it->item.data, it->item.len, &it->item.idx)) { + case PTMX_WRITE_DONE: + free(it->item.data); + tll_remove(term->ptmx_buffer, it); + break; + + case PTMX_WRITE_REMAIN: return true; /* to_slave() updated it->item.idx */ + case PTMX_WRITE_ERR: return false; + } + } + + fdm_event_del(term->fdm, term->ptmx, EPOLLOUT); + return true; +} + static bool fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) { struct terminal *term = data; + if (events & EPOLLOUT) { + if (!fdm_ptmx_out(fdm, fd, events, data)) + return false; + } + if ((events & EPOLLHUP) && !(events & EPOLLIN)) return term_shutdown(term); - assert(events & EPOLLIN); + if (!(events & EPOLLIN)) + return true; uint8_t buf[24 * 1024]; ssize_t count = read(term->ptmx, buf, sizeof(buf)); @@ -328,25 +416,20 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, goto close_fds; } - if (!fdm_add(fdm, ptmx, EPOLLIN, &fdm_ptmx, term)) { - LOG_ERR("failed to add ptmx to FDM"); + int ptmx_flags; + if ((ptmx_flags = fcntl(ptmx, F_GETFL)) < 0 || + fcntl(ptmx, F_SETFL, ptmx_flags | O_NONBLOCK) < 0) + { + LOG_ERRNO("failed to configure ptmx as non-blocking"); goto err; } - if (!fdm_add(fdm, flash_fd, EPOLLIN, &fdm_flash, term)) { - LOG_ERR("failed to add flash timer FD to FDM"); - goto err; - } - - if (!fdm_add(fdm, blink_fd, EPOLLIN, &fdm_blink, term)) { - LOG_ERR("failed to add blink tiemr FD to FDM"); - goto err; - } - - if (!fdm_add(fdm, delay_lower_fd, EPOLLIN, &fdm_delayed_render, term) || + 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, delay_lower_fd, EPOLLIN, &fdm_delayed_render, term) || !fdm_add(fdm, delay_upper_fd, EPOLLIN, &fdm_delayed_render, term)) { - LOG_ERR("failed to add delayed rendering timer FDs to FDM"); goto err; } @@ -355,6 +438,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, .fdm = fdm, .quit = false, .ptmx = ptmx, + .ptmx_buffer = tll_init(), .cursor_keys_mode = CURSOR_KEYS_NORMAL, .keypad_keys_mode = KEYPAD_NUMERICAL, .auto_margin = true, @@ -538,7 +622,6 @@ term_shutdown(struct terminal *term) } if (!fdm_add(term->fdm, event_fd, EPOLLIN, &fdm_shutdown, term)) { - LOG_ERR("failed to add terminal shutdown event FD to the FDM"); close(event_fd); return false; } @@ -629,6 +712,10 @@ term_destroy(struct terminal *term) assert(tll_length(term->render.workers.queue) == 0); tll_free(term->render.workers.queue); + tll_foreach(term->ptmx_buffer, it) + free(it->item.data); + tll_free(term->ptmx_buffer); + int ret = EXIT_SUCCESS; if (term->slave > 0) { @@ -1070,7 +1157,7 @@ term_focus_in(struct terminal *term) { if (!term->focus_events) return; - vt_to_slave(term, "\033[I", 3); + term_to_slave(term, "\033[I", 3); } void @@ -1078,7 +1165,7 @@ term_focus_out(struct terminal *term) { if (!term->focus_events) return; - vt_to_slave(term, "\033[O", 3); + term_to_slave(term, "\033[O", 3); } static int @@ -1152,7 +1239,7 @@ report_mouse_click(struct terminal *term, int encoded_button, int row, int col, return; } - vt_to_slave(term, response, strlen(response)); + term_to_slave(term, response, strlen(response)); } static void diff --git a/terminal.h b/terminal.h index 11571519..0ed83877 100644 --- a/terminal.h +++ b/terminal.h @@ -145,6 +145,12 @@ enum mouse_reporting { enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR }; +struct ptmx_buffer { + void *data; + size_t len; + size_t idx; +}; + struct terminal { struct fdm *fdm; @@ -152,6 +158,8 @@ struct terminal { int ptmx; bool quit; + tll(struct ptmx_buffer) ptmx_buffer; + enum cursor_keys cursor_keys_mode; enum keypad_keys keypad_keys_mode; bool reverse; @@ -303,6 +311,7 @@ bool term_shutdown(struct terminal *term); int term_destroy(struct terminal *term); void term_reset(struct terminal *term, bool hard); +bool term_to_slave(struct terminal *term, const void *data, size_t len); void term_damage_rows(struct terminal *term, int start, int end); void term_damage_rows_in_view(struct terminal *term, int start, int end); diff --git a/vt.c b/vt.c index a2ac1faf..41524857 100644 --- a/vt.c +++ b/vt.c @@ -1015,23 +1015,3 @@ vt_from_slave(struct terminal *term, const uint8_t *data, size_t len) } } } - -bool -vt_to_slave(struct terminal *term, const void *_data, size_t len) -{ - const uint8_t *data = _data; - size_t left = len; - - while (left > 0) { - ssize_t ret = write(term->ptmx, data, left); - if (ret < 0) { - LOG_ERRNO("failed to write to client"); - return false; - } - - data += ret; - left -= ret; - } - - return true; -} diff --git a/vt.h b/vt.h index 302dc6dc..0e85a75b 100644 --- a/vt.h +++ b/vt.h @@ -6,7 +6,6 @@ #include "terminal.h" void vt_from_slave(struct terminal *term, const uint8_t *data, size_t len); -bool vt_to_slave(struct terminal *term, const void *data, size_t len); static inline int vt_param_get(const struct terminal *term, size_t idx, int default_value) diff --git a/wayland.c b/wayland.c index 2bbf8c2e..f4cba71c 100644 --- a/wayland.c +++ b/wayland.c @@ -522,14 +522,9 @@ wayl_init(struct fdm *fdm) goto out; } - int wl_fd = wl_display_get_fd(wayl->display); - if (!fdm_add(fdm, wl_fd, EPOLLIN, &fdm_wayl, wayl)) { - LOG_ERR("failed to register Wayland connection with the FDM"); - goto out; - } - - if (!fdm_add(fdm, wayl->kbd.repeat.fd, EPOLLIN, &fdm_repeat, wayl)) { - LOG_ERR("failed to register keyboard repeat timer with the FDM"); + if (!fdm_add(fdm, wl_display_get_fd(wayl->display), EPOLLIN, &fdm_wayl, wayl) || + !fdm_add(fdm, wayl->kbd.repeat.fd, EPOLLIN, &fdm_repeat, wayl)) + { goto out; }