diff --git a/CHANGELOG.md b/CHANGELOG.md index 56507d24..31bd2d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,11 @@ * The scrollback search box no longer accepts non-printable characters. * Non-formatting C0 control characters, `BS`, `HT` and `DEL` are now stripped from pasted text. +* While doing an interactive resize of a foot window, foot now + requires 100ms of idle time (where the window size does not change) + before sending the new dimensions to the client application. The + timing can be tweaked, or completely disabled, by setting + `resize-delay-ms` (https://codeberg.org/dnkl/foot/issues/301). ### Deprecated diff --git a/config.c b/config.c index 81b329cf..e2d9e2cb 100644 --- a/config.c +++ b/config.c @@ -523,6 +523,19 @@ parse_section_main(const char *key, const char *value, struct config *conf, conf->center = center; } + else if (strcmp(key, "resize-delay-ms") == 0) { + unsigned long ms; + if (!str_to_ulong(value, 10, &ms)) { + LOG_AND_NOTIFY_ERR( + "%s:%d: [default]: resize-delay-ms: " + "expected an integer, got '%s'", + path, lineno, value); + return false; + } + + conf->resize_delay_ms = ms; + } + else if (strcmp(key, "bold-text-in-bright") == 0) conf->bold_in_bright = str_to_bool(value); @@ -2080,6 +2093,7 @@ config_load(struct config *conf, const char *conf_path, }, .pad_x = 2, .pad_y = 2, + .resize_delay_ms = 100, .bold_in_bright = false, .bell_action = BELL_ACTION_NONE, .startup_mode = STARTUP_WINDOWED, diff --git a/config.h b/config.h index 1c349f2a..56273053 100644 --- a/config.h +++ b/config.h @@ -77,6 +77,7 @@ struct config { unsigned pad_x; unsigned pad_y; bool center; + uint16_t resize_delay_ms; bool bold_in_bright; enum { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 180ac7c2..8684850b 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -131,6 +131,25 @@ in this order: Default: _2x2_. +*resize-delay-ms* + Time, in milliseconds, of "idle time" "before foot sends the new + window dimensions to the client application while doing an + interactive resize of a foot window. Idle time in this context is + a period of time where the window size is not changing. + + In other words, while you are fiddling with the window size, foot + does not send the updated dimensions to the client. Only when you + pause the fiddling for *relay-size-ms* milliseconds is the client + updated. + + Emphasis is on _while_ here; as soon as the interactive resize + ends (i.e. when you let go of the window border), the final + dimensions is sent to the client, without any delays. + + Setting it to 0 disables the delay completely. + + Default: _100_. + *initial-window-size-pixels* Initial window width and height in _pixels_ (subject to output scaling), on the form _WIDTHxHEIGHT_. The height _includes_ the diff --git a/foot.ini b/foot.ini index 19a82061..a9c0108f 100644 --- a/foot.ini +++ b/foot.ini @@ -18,6 +18,7 @@ # initial-window-size-chars= # initial-window-mode=windowed # pad=2x2 # optionally append 'center' +# resize-delay-ms=100 # bold-text-in-bright=no # bell=none diff --git a/render.c b/render.c index e45140e5..6d7a54fa 100644 --- a/render.c +++ b/render.c @@ -1,10 +1,12 @@ #include "render.h" #include +#include #include #include #include +#include #include #if __has_include() #include @@ -2545,6 +2547,89 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da grid_render(term); } +static void +tiocswinsz(struct terminal *term) +{ + if (term->ptmx >= 0) { + if (ioctl(term->ptmx, (unsigned int)TIOCSWINSZ, + &(struct winsize){ + .ws_row = term->rows, + .ws_col = term->cols, + .ws_xpixel = term->cols * term->cell_width, + .ws_ypixel = term->rows * term->cell_height}) < 0) + { + LOG_ERRNO("TIOCSWINSZ"); + } + } +} + +static bool +fdm_tiocswinsz(struct fdm *fdm, int fd, int events, void *data) +{ + struct terminal *term = data; + + if (events & EPOLLIN) + tiocswinsz(term); + + fdm_del(fdm, fd); + term->window->resize_timeout_fd = -1; + return true; +} + +static void +send_dimensions_to_client(struct terminal *term) +{ + struct wl_window *win = term->window; + + if (!win->is_resizing || term->conf->resize_delay_ms == 0) { + /* Send new dimensions to client immediately */ + tiocswinsz(term); + + /* And make sure to reset and deallocate a lingering timer */ + if (win->resize_timeout_fd >= 0) { + fdm_del(term->fdm, win->resize_timeout_fd); + win->resize_timeout_fd = -1; + } + } else { + /* Send new dimensions to client “in a while” */ + assert(win->is_resizing && term->conf->resize_delay_ms > 0); + + int fd = win->resize_timeout_fd; + uint16_t delay_ms = term->conf->resize_delay_ms; + bool successfully_scheduled = false; + + if (fd < 0) { + /* Lazy create timer fd */ + fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); + if (fd < 0) + LOG_ERRNO("failed to create TIOCSWINSZ timer"); + else if (!fdm_add(term->fdm, fd, EPOLLIN, &fdm_tiocswinsz, term)) { + close(fd); + fd = -1; + } + + win->resize_timeout_fd = fd; + } + + if (fd >= 0) { + /* Reset timeout */ + const struct itimerspec timeout = { + .it_value = {.tv_sec = 0, .tv_nsec = delay_ms * 1000000}, + }; + + if (timerfd_settime(fd, 0, &timeout, NULL) < 0) { + LOG_ERRNO("failed to arm TIOCSWINSZ timer"); + fdm_del(term->fdm, fd); + win->resize_timeout_fd = -1; + } else + successfully_scheduled = true; + } + + if (!successfully_scheduled) + tiocswinsz(term); + } +} + /* Move to terminal.c? */ static bool maybe_resize(struct terminal *term, int width, int height, bool force) @@ -2671,7 +2756,7 @@ maybe_resize(struct terminal *term, int width, int height, bool force) const int total_x_pad = term->width - grid_width; const int total_y_pad = term->height - grid_height; - if (term->conf->center) { + if (term->conf->center && !term->window->is_resizing) { term->margins.left = total_x_pad / 2; term->margins.top = total_y_pad / 2; } else { @@ -2723,17 +2808,6 @@ maybe_resize(struct terminal *term, int width, int height, bool force) term->width, term->height, term->cols, term->rows, term->margins.left, term->margins.right, term->margins.top, term->margins.bottom); - /* Signal TIOCSWINSZ */ - if (term->ptmx >= 0 && ioctl(term->ptmx, (unsigned int)TIOCSWINSZ, - &(struct winsize){ - .ws_row = term->rows, - .ws_col = term->cols, - .ws_xpixel = term->cols * term->cell_width, - .ws_ypixel = term->rows * term->cell_height}) == -1) - { - LOG_ERRNO("TIOCSWINSZ"); - } - if (term->scroll_region.start >= term->rows) term->scroll_region.start = 0; @@ -2743,6 +2817,9 @@ maybe_resize(struct terminal *term, int width, int height, bool force) term->render.last_cursor.row = NULL; damage_view: + /* Signal TIOCSWINSZ */ + send_dimensions_to_client(term); + if (!term->window->is_maximized && !term->window->is_fullscreen && !term->window->is_tiled) diff --git a/wayland.c b/wayland.c index 7985a90c..d3c560b7 100644 --- a/wayland.c +++ b/wayland.c @@ -535,6 +535,7 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, bool is_activated = false; bool is_fullscreen = false; bool is_maximized = false; + bool is_resizing = false; bool is_tiled_top = false; bool is_tiled_bottom = false; bool is_tiled_left = false; @@ -566,11 +567,7 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, case XDG_TOPLEVEL_STATE_TILED_RIGHT: is_tiled_right = true; break; case XDG_TOPLEVEL_STATE_TILED_TOP: is_tiled_top = true; break; case XDG_TOPLEVEL_STATE_TILED_BOTTOM: is_tiled_bottom = true; break; - - case XDG_TOPLEVEL_STATE_RESIZING: - /* Ignored */ - /* TODO: throttle? */ - break; + case XDG_TOPLEVEL_STATE_RESIZING: is_resizing = true; break; } #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG @@ -614,6 +611,7 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, win->configure.is_activated = is_activated; win->configure.is_fullscreen = is_fullscreen; win->configure.is_maximized = is_maximized; + win->configure.is_resizing = is_resizing; win->configure.is_tiled_top = is_tiled_top; win->configure.is_tiled_bottom = is_tiled_bottom; win->configure.is_tiled_left = is_tiled_left; @@ -646,8 +644,11 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, struct terminal *term = win->term; bool wasnt_configured = !win->is_configured; + bool was_resizing = win->is_resizing; + win->is_configured = true; win->is_maximized = win->configure.is_maximized; + win->is_resizing = win->configure.is_resizing; win->is_tiled_top = win->configure.is_tiled_top; win->is_tiled_bottom = win->configure.is_tiled_bottom; win->is_tiled_left = win->configure.is_tiled_left; @@ -676,8 +677,25 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, term->window->frame_callback = NULL; } - bool resized = render_resize( - term, win->configure.width, win->configure.height); +#if 1 + /* + * TODO: decide if we should to the last “forced” call when ending + * an interactive resize. + * + * Without it, the last TIOCSWINSZ sent to the client will be a + * scheduled one. I.e. there will be a small delay after the user + * has *stopped* resizing, and the client application receives the + * final size. + * + * Note: if we also disable content centering while resizing, then + * the last, forced, resize *is* necessary. + */ + bool resized = was_resizing && !win->is_resizing + ? render_resize_force(term, win->configure.width, win->configure.height) + : render_resize(term, win->configure.width, win->configure.height); +#else + bool resized = render_resize(term, win->configure.width, win->configure.height); +#endif if (win->configure.is_activated) term_visual_focus_in(term); @@ -1276,6 +1294,7 @@ wayl_win_init(struct terminal *term) win->term = term; win->use_csd = CSD_UNKNOWN; win->csd.move_timeout_fd = -1; + win->resize_timeout_fd = -1; win->surface = wl_compositor_create_surface(wayl->compositor); if (win->surface == NULL) { @@ -1428,6 +1447,9 @@ wayl_win_destroy(struct wl_window *win) wl_surface_destroy(win->surface); wayl_roundtrip(win->term->wl); + + if (win->resize_timeout_fd >= 0) + fdm_del(win->term->wl->fdm, win->resize_timeout_fd); free(win); } diff --git a/wayland.h b/wayland.h index 5ebc8a7e..b849249e 100644 --- a/wayland.h +++ b/wayland.h @@ -372,22 +372,26 @@ struct wl_window { bool is_configured; bool is_fullscreen; bool is_maximized; + bool is_resizing; bool is_tiled_top; bool is_tiled_bottom; bool is_tiled_left; bool is_tiled_right; bool is_tiled; /* At least one of is_tiled_{top,bottom,left,right} is true */ struct { - bool is_activated; - bool is_fullscreen; - bool is_maximized; - bool is_tiled_top; - bool is_tiled_bottom; - bool is_tiled_left; - bool is_tiled_right; int width; int height; + bool is_activated:1; + bool is_fullscreen:1; + bool is_maximized:1; + bool is_resizing:1; + bool is_tiled_top:1; + bool is_tiled_bottom:1; + bool is_tiled_left:1; + bool is_tiled_right:1; } configure; + + int resize_timeout_fd; }; struct config;