Merge branch 'master' into releases/1.8

This commit is contained in:
Daniel Eklöf 2021-07-18 17:57:11 +02:00
commit 4d82e04860
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
19 changed files with 1088 additions and 457 deletions

View file

@ -1,5 +1,6 @@
# Changelog
* [Unreleased](#unreleased)
* [1.8.1](#1-8-1)
* [1.8.0](#1-8-0)
* [1.7.2](#1-7-2)
@ -27,6 +28,58 @@
* [1.2.0](#1-2-0)
## Unreleased
### Added
* `locked-title=no|yes` to `foot.ini`
(https://codeberg.org/dnkl/foot/issues/386).
* `tweak.overflowing-glyphs` option, which can be enabled to fix rendering
issues with glyphs of any width that appear cut-off
(https://codeberg.org/dnkl/foot/issues/592).
### Changed
* Non-empty lines are now considered to have a hard linebreak,
_unless_ an actual word-wrap is inserted.
* Setting `DECSDM` now _disables_ sixel scrolling, while resetting it
_enables_ scrolling (https://codeberg.org/dnkl/foot/issues/631).
### Deprecated
### Removed
* The `tweak.allow-overflowing-double-width-glyphs` and
`tweak.pua-double-width` options (which have been superseded by
`tweak.overflowing-glyphs`).
### Fixed
* FD exhaustion when repeatedly entering/exiting URL mode with many
URLs.
* Double free of URL while removing duplicated and/or overlapping URLs
in URL mode (https://codeberg.org/dnkl/foot/issues/627).
* Crash when an unclosed OSC-8 URL ran into un-allocated scrollback
rows.
* Some box-drawing characters were rendered incorrectly on big-endian
architectures.
* Crash when resizing the window to the smallest possible size while
scrollback search is active.
* Scrollback indicator being incorrectly rendered when window size is
very small.
* Reduced memory usage in URL mode.
* Crash when the `E3` escape (`\E[3J`) was executed, and there was a
selection, or sixel image, in the scrollback
(https://codeberg.org/dnkl/foot/issues/633).
### Security
### Contributors
* [clktmr](https://codeberg.org/clktmr)
## 1.8.1
### Added
@ -43,7 +96,7 @@
* Grapheme cluster width is now limited to two cells by default. This
may cause cursor synchronization issues with many applications. You
can set `[tweak].grapheme-width-method=wcswidth` to revert to the
behavior from foot-1.8.0.
behavior in foot-1.8.0.
### Fixed

View file

@ -1244,6 +1244,16 @@ draw_box_drawings_double_vertical_and_horizontal(struct buf *buf)
vline(hmid + 2 * thick, buf->height, vmid + 2 * thick, thick);
}
static inline void
set_a1_bit(uint8_t *data, size_t ofs, size_t bit_no)
{
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
data[ofs] |= 1 << bit_no;
#else
data[ofs] |= 1 << (7 - bit_no);
#endif
}
static void
draw_box_drawings_light_arc(struct buf *buf, wchar_t wc)
{
@ -1354,7 +1364,7 @@ draw_box_drawings_light_arc(struct buf *buf, wchar_t wc)
if (fmt == PIXMAN_a1) {
size_t idx = c / 8;
size_t bit_no = c % 8;
data[r * stride + idx] |= 1 << bit_no;
set_a1_bit(data, r * stride + idx, bit_no);
} else
data[r * stride + c] = 0xff;
}
@ -1376,7 +1386,7 @@ draw_box_drawings_light_arc(struct buf *buf, wchar_t wc)
if (fmt == PIXMAN_a1) {
size_t ofs = col / 8;
size_t bit_no = col % 8;
data[row * stride + ofs] |= 1 << bit_no;
set_a1_bit(data, row * stride + ofs, bit_no);
} else
data[row * stride + col] = 0xff;
}
@ -1392,7 +1402,7 @@ draw_box_drawings_light_arc(struct buf *buf, wchar_t wc)
if (fmt == PIXMAN_a1) {
size_t ofs = col / 8;
size_t bit_no = col % 8;
data[row * stride + ofs] |= 1 << bit_no;
set_a1_bit(data, row * stride + ofs, bit_no);
} else
data[row * stride + col] = 0xff;
}
@ -1767,7 +1777,7 @@ draw_light_shade(struct buf *buf)
for (size_t col = 0; col < buf->width; col += 2) {
size_t idx = col / 8;
size_t bit_no = col % 8;
buf->data[row * buf->stride + idx] |= 1 << bit_no;
set_a1_bit(buf->data, row * buf->stride + idx, bit_no);
}
}
}
@ -1790,7 +1800,7 @@ draw_medium_shade(struct buf *buf)
for (size_t col = row % 2; col < buf->width; col += 2) {
size_t idx = col / 8;
size_t bit_no = col % 8;
buf->data[row * buf->stride + idx] |= 1 << bit_no;
set_a1_bit(buf->data, row * buf->stride + idx, bit_no);
}
}
}
@ -1813,7 +1823,7 @@ draw_dark_shade(struct buf *buf)
for (size_t col = 0; col < buf->width; col += 1 + row % 2) {
size_t idx = col / 8;
size_t bit_no = col % 8;
buf->data[row * buf->stride + idx] |= 1 << bit_no;
set_a1_bit(buf->data, row * buf->stride + idx, bit_no);
}
}
}

View file

@ -643,6 +643,9 @@ parse_section_main(const char *key, const char *value, struct config *conf,
conf->title = xstrdup(value);
}
else if (strcmp(key, "locked-title") == 0)
conf->locked_title = str_to_bool(value);
else if (strcmp(key, "app-id") == 0) {
free(conf->app_id);
conf->app_id = xstrdup(value);
@ -2210,16 +2213,10 @@ parse_section_tweak(
return false;
}
else if (strcmp(key, "allow-overflowing-double-width-glyphs") == 0) {
conf->tweak.allow_overflowing_double_width_glyphs = str_to_bool(value);
if (!conf->tweak.allow_overflowing_double_width_glyphs)
LOG_WARN("tweak: disabled overflowing double-width glyphs");
}
else if (strcmp(key, "pua-double-width") == 0) {
conf->tweak.pua_double_width = str_to_bool(value);
if (conf->tweak.pua_double_width)
LOG_WARN("tweak: PUA double width glyphs enabled");
else if (strcmp(key, "overflowing-glyphs") == 0) {
conf->tweak.overflowing_glyphs = str_to_bool(value);
if (!conf->tweak.overflowing_glyphs)
LOG_WARN("tweak: disabled overflowing glyphs");
}
else if (strcmp(key, "damage-whole-window") == 0) {
@ -2830,7 +2827,7 @@ config_load(struct config *conf, const char *conf_path,
.tweak = {
.fcft_filter = FCFT_SCALING_FILTER_LANCZOS3,
.allow_overflowing_double_width_glyphs = true,
.overflowing_glyphs = true,
.grapheme_shaping = false,
.grapheme_width_method = GRAPHEME_WIDTH_DOUBLE,
.delayed_render_lower_ns = 500000, /* 0.5ms */
@ -2841,7 +2838,6 @@ config_load(struct config *conf, const char *conf_path,
.damage_whole_window = false,
.box_drawing_base_thickness = 0.04,
.box_drawing_solid_shades = true,
.pua_double_width = false,
},
.notifications = tll_init(),

View file

@ -77,6 +77,7 @@ struct config {
wchar_t *word_delimiters;
bool login_shell;
bool no_wait;
bool locked_title;
struct {
enum conf_size_type type;
@ -244,7 +245,7 @@ struct config {
struct {
enum fcft_scaling_filter fcft_filter;
bool allow_overflowing_double_width_glyphs;
bool overflowing_glyphs;
bool grapheme_shaping;
enum {GRAPHEME_WIDTH_WCSWIDTH, GRAPHEME_WIDTH_DOUBLE} grapheme_width_method;
bool render_timer_osd;
@ -255,7 +256,6 @@ struct config {
off_t max_shm_pool_size;
float box_drawing_base_thickness;
bool box_drawing_solid_shades;
bool pua_double_width;
} tweak;
user_notifications_t notifications;

29
csi.c
View file

@ -402,7 +402,7 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
break;
case 80:
term->sixel.scrolling = enable;
term->sixel.scrolling = !enable;
break;
case 1000:
@ -611,7 +611,7 @@ decrqm(const struct terminal *term, unsigned param, bool *enabled)
case 12: *enabled = term->cursor_blink.decset; return true;
case 25: *enabled = !term->hide_cursor; return true;
case 45: *enabled = term->reverse_wrap; return true;
case 80: *enabled = term->sixel.scrolling; return true;
case 80: *enabled = !term->sixel.scrolling; return true;
case 1000: *enabled = term->mouse_tracking == MOUSE_CLICK; return true;
case 1001: *enabled = false; return true;
case 1002: *enabled = term->mouse_tracking == MOUSE_DRAG; return true;
@ -654,7 +654,7 @@ xtsave(struct terminal *term, unsigned param)
case 25: term->xtsave.show_cursor = !term->hide_cursor; break;
case 45: term->xtsave.reverse_wrap = term->reverse_wrap; break;
case 47: term->xtsave.alt_screen = term->grid == &term->alt; break;
case 80: term->xtsave.sixel_scrolling = term->sixel.scrolling; break;
case 80: term->xtsave.sixel_display_mode = !term->sixel.scrolling; break;
case 1000: term->xtsave.mouse_click = term->mouse_tracking == MOUSE_CLICK; break;
case 1001: break;
case 1002: term->xtsave.mouse_drag = term->mouse_tracking == MOUSE_DRAG; break;
@ -696,7 +696,7 @@ xtrestore(struct terminal *term, unsigned param)
case 25: enable = term->xtsave.show_cursor; break;
case 45: enable = term->xtsave.reverse_wrap; break;
case 47: enable = term->xtsave.alt_screen; break;
case 80: enable = term->xtsave.sixel_scrolling; break;
case 80: enable = term->xtsave.sixel_display_mode; break;
case 1000: enable = term->xtsave.mouse_click; break;
case 1001: return;
case 1002: enable = term->xtsave.mouse_drag; break;
@ -917,26 +917,7 @@ csi_dispatch(struct terminal *term, uint8_t final)
case 3: {
/* Erase scrollback */
int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows;
for (size_t i = 0; i < term->grid->num_rows; i++) {
if (end >= term->grid->offset) {
/* Not wrapped */
if (i >= term->grid->offset && i <= end)
continue;
} else {
/* Wrapped */
if (i >= term->grid->offset || i <= end)
continue;
}
if (term->render.last_cursor.row == term->grid->rows[i])
term->render.last_cursor.row = NULL;
grid_row_free(term->grid->rows[i]);
term->grid->rows[i] = NULL;
}
term->grid->view = term->grid->offset;
term_damage_view(term);
term_erase_scrollback(term);
break;
}

View file

@ -204,7 +204,7 @@ Note that these are just the defaults; they can be changed in
characters are whitespace characters.
*ctrl*+*v*, *ctrl*+*y*
Paste from clipboard into the searh buffer.
Paste from clipboard into the search buffer.
*shift*+*insert*
Paste from primary selection into the search buffer.

View file

@ -188,8 +188,8 @@ in this order:
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.
pause the fiddling for *resize-delay-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
@ -224,6 +224,10 @@ in this order:
*title*
Initial window title. Default: _foot_.
*locked-title*
Boolean. If enabled, applications are not allowed to change the
title at run-time. Default: _no_.
*app-id*
Value to set the *app-id* property on the Wayland window to. The
compositor can use this value to e.g. group multiple windows, or
@ -843,34 +847,24 @@ any of these options.
Default: _lanczos3_.
*allow-overflowing-double-width-glyphs*
Boolean. When enabled, double width glyphs with a character width
of 1 are allowed to overflow into the neighbouring cell.
*overflowing-glyphs*
Boolean. When enabled, glyphs wider than their cell(s) are allowed
to render into one additional neighbouring cell.
One use case for this is fonts "icon" characters in the Unicode
private usage area, e.g. Nerd Fonts, or Powerline Fonts. Without
this option, such glyphs will appear "cut off".
One use case for this are fonts with wide italic characters that
"bend" into the next cell. Without this option, such glyphs will
appear "cut off".
Another use case are legacy emoji characters like *WHITE FROWNING
FACE*.
Another use case are fonts with "icon" characters in the Unicode
private usage area, e.g. Nerd Fonts, or Powerline Fonts and legacy
emoji characters like *WHITE FROWNING FACE*.
Note: this feature uses _heuristics_ to determine *which* glyphs
should be allowed to overflow.
See also: *pua-double-width*
Note: might impact performance depending on the font used.
Especially small font sizes can cause many overflowing glyphs
because of subpixel rendering.
Default: _yes_.
*pua-double-width*
Boolean. When enabled, Unicode code points from the private usage
area (PUA) are always considered to be double width, regardless of
the actual glyph width.
Ignored if *allow-overflowing-double-width-glyphs* has been
disabled.
Default: _no_.
*render-timer*
Enables a frame rendering timer, that prints the time it takes to
render each frame, in microseconds, either on-screen, to stderr,

View file

@ -4,6 +4,10 @@
# term=foot (or xterm-256color if built with -Dterminfo=disabled)
# login-shell=no
# app-id=foot
# title=foot
# locked-title=no
# font=monospace:size=8
# font-bold=<bold variant of regular font>
# font-italic=<italic variant of regular font>
@ -122,7 +126,7 @@
# show-urls-copy=none
[search-bindings]
# cancel=Control+g Escape
# cancel=Control+g Control+c Escape
# commit=Return
# find-prev=Control+r
# find-next=Control+s
@ -142,7 +146,7 @@
# primary-paste=Shift+Insert
[url-bindings]
# cancel=Control+g Control+d Escape
# cancel=Control+g Control+c Control+d Escape
# toggle-url-visible=t
[mouse-bindings]

339
render.c
View file

@ -437,6 +437,20 @@ draw_cursor(const struct terminal *term, const struct cell *cell,
}
}
static inline void
render_cell_prepass(struct terminal *term, struct row *row, int col)
{
for (; col < term->cols - 1; col++) {
if (row->cells[col].attrs.confined ||
(row->cells[col].attrs.clean == row->cells[col + 1].attrs.clean)) {
break;
}
row->cells[col].attrs.clean = 0;
row->cells[col + 1].attrs.clean = 0;
}
}
static int
render_cell(struct terminal *term, pixman_image_t *pix,
struct row *row, int col, int row_no, bool has_cursor)
@ -446,6 +460,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
return 0;
cell->attrs.clean = 1;
cell->attrs.confined = true;
int width = term->cell_width;
int height = term->cell_height;
@ -458,7 +473,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
uint32_t _fg = 0;
uint32_t _bg = 0;
bool apply_alpha = false;
uint16_t alpha = 0xffff;
if (is_selected && term->colors.use_custom_selection) {
_fg = term->colors.selection_fg;
@ -472,14 +487,14 @@ render_cell(struct terminal *term, pixman_image_t *pix,
uint32_t swap = _fg;
_fg = _bg;
_bg = swap;
} else
apply_alpha = !cell->attrs.have_bg;
} else if (!cell->attrs.have_bg)
alpha = term->colors.alpha;
}
if (unlikely(is_selected && _fg == _bg)) {
/* Invert bg when selected/highlighted text has same fg/bg */
_bg = ~_bg;
apply_alpha = false;
alpha = 0xffff;
}
if (cell->attrs.dim)
@ -491,8 +506,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
_fg = color_dim(_fg);
pixman_color_t fg = color_hex_to_pixman(_fg);
pixman_color_t bg = color_hex_to_pixman_with_alpha(
_bg, apply_alpha ? term->colors.alpha : 0xffff);
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha);
if (term->is_searching && !is_selected) {
color_dim_for_search(&fg);
@ -598,51 +612,32 @@ render_cell(struct terminal *term, pixman_image_t *pix,
cell_cols = max(1, min(cell_cols, cols_left));
/*
* Hack!
*
* Deal with double-width glyphs for which wcwidth() returns
* 1. Typically Unicode private usage area characters,
* e.g. powerline, or nerd hack fonts.
*
* Users can enable a tweak option that lets this glyphs
* overflow/bleed into the neighbouring cell.
*
* We only apply this workaround if:
* - the user has explicitly enabled this feature
* - the *character* width is 1
* - the *glyph* width is at least 1.5 cells
* - the *glyph* width is less than 3 cells
* - *this* column isnt the last column
* - *this* cells is followed by an empty cell, or a space
* Determine cells that will bleed into their right neighbor and remember
* them for cleanup in the next frame.
*/
if (term->conf->tweak.allow_overflowing_double_width_glyphs &&
((glyph_count > 0 &&
cell_cols == 1 &&
glyphs[0]->width >= term->cell_width * 15 / 10 &&
glyphs[0]->width < 3 * term->cell_width &&
col < term->cols - 1) ||
(term->conf->tweak.pua_double_width &&
((base >= 0x00e000 && base <= 0x00f8ff) ||
(base >= 0x0f0000 && base <= 0x0ffffd) ||
(base >= 0x100000 && base <= 0x10fffd)))) &&
(row->cells[col + 1].wc == 0 || row->cells[col + 1].wc == L' '))
int render_width = cell_cols * width;
if (term->conf->tweak.overflowing_glyphs &&
glyph_count > 0)
{
cell_cols = 2;
int glyph_width = 0, advance = 0;
for (size_t i = 0; i < glyph_count; i++) {
glyph_width = max(glyph_width,
advance + glyphs[i]->x + glyphs[i]->width);
advance += glyphs[i]->advance.x;
}
/*
* Ensure the cell were overflowing into gets re-rendered, to
* ensure it is erased if *this* cell is erased. Note that we
* do *not* mark the row as dirty - we dont need to re-render
* the cell if nothing else on the row has changed.
*/
row->cells[col].attrs.clean = 0;
row->cells[col + 1].attrs.clean = 0;
if (glyph_width > render_width) {
render_width = min(glyph_width, render_width + width);
for (int i = 0; i < cell_cols; i++)
row->cells[col + i].attrs.confined = false;
}
}
pixman_region32_t clip;
pixman_region32_init_rect(
&clip, x, y,
cell_cols * term->cell_width, term->cell_height);
render_width, term->cell_height);
pixman_image_set_clip_region32(pix, &clip);
/* Background */
@ -772,6 +767,10 @@ static void
render_row(struct terminal *term, pixman_image_t *pix, struct row *row,
int row_no, int cursor_col)
{
if (term->conf->tweak.overflowing_glyphs)
for (int col = term->cols - 1; col >= 0; col--)
render_cell_prepass(term, row, col);
for (int col = term->cols - 1; col >= 0; col--)
render_cell(term, pix, row, col, row_no, cursor_col == col);
}
@ -816,13 +815,10 @@ render_margin(struct terminal *term, struct buffer *buf,
const int line_count = end_line - start_line;
uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
if (term->is_searching)
_bg = color_dim(_bg);
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha);
pixman_color_t bg = color_hex_to_pixman_with_alpha(
_bg,
(_bg == (term->reverse ? term->colors.fg : term->colors.bg)
? term->colors.alpha : 0xffff));
if (term->is_searching)
color_dim_for_search(&bg);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix[0], &bg, 4,
@ -947,7 +943,7 @@ grid_render_scroll(struct terminal *term, struct buffer *buf,
if (try_shm_scroll) {
did_shm_scroll = shm_scroll(
term->wl->shm, buf, dmg->lines * term->cell_height,
buf, dmg->lines * term->cell_height,
term->margins.top, dmg->region.start * term->cell_height,
term->margins.bottom, (term->rows - dmg->region.end) * term->cell_height);
}
@ -958,7 +954,7 @@ grid_render_scroll(struct terminal *term, struct buffer *buf,
term, buf, dmg->region.end - dmg->lines, term->rows, false);
} else {
/* Fallback for when we either cannot do SHM scrolling, or it failed */
uint8_t *raw = buf->mmapped;
uint8_t *raw = buf->data;
memmove(raw + dst_y * buf->stride,
raw + src_y * buf->stride,
height * buf->stride);
@ -1012,7 +1008,7 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf,
if (try_shm_scroll) {
did_shm_scroll = shm_scroll(
term->wl->shm, buf, -dmg->lines * term->cell_height,
buf, -dmg->lines * term->cell_height,
term->margins.top, dmg->region.start * term->cell_height,
term->margins.bottom, (term->rows - dmg->region.end) * term->cell_height);
}
@ -1023,7 +1019,7 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf,
term, buf, dmg->region.start, dmg->region.start + dmg->lines, false);
} else {
/* Fallback for when we either cannot do SHM scrolling, or it failed */
uint8_t *raw = buf->mmapped;
uint8_t *raw = buf->data;
memmove(raw + dst_y * buf->stride,
raw + src_y * buf->stride,
height * buf->stride);
@ -1196,8 +1192,10 @@ render_sixel(struct terminal *term, pixman_image_t *pix,
(last_col_needs_erase && last_col))
{
render_cell(term, pix, row, col, term_row_no, cursor_col == col);
} else
} else {
cell->attrs.clean = 1;
cell->attrs.confined = 1;
}
}
}
}
@ -1577,21 +1575,16 @@ render_csd_part(struct terminal *term,
}
static void
render_csd_title(struct terminal *term)
render_csd_title(struct terminal *term, const struct csd_data *info,
struct buffer *buf)
{
xassert(term->window->csd_mode == CSD_YES);
struct csd_data info = get_csd_data(term, CSD_SURF_TITLE);
struct wl_surface *surf = term->window->csd.surface[CSD_SURF_TITLE].surf;
xassert(info->width > 0 && info->height > 0);
xassert(info.width > 0 && info.height > 0);
xassert(info.width % term->scale == 0);
xassert(info.height % term->scale == 0);
unsigned long cookie = shm_cookie_csd(term, CSD_SURF_TITLE);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie, false, 1);
xassert(info->width % term->scale == 0);
xassert(info->height % term->scale == 0);
uint32_t _color = term->conf->colors.fg;
uint16_t alpha = 0xffff;
@ -1605,31 +1598,27 @@ render_csd_title(struct terminal *term)
_color = color_dim(_color);
pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha);
render_csd_part(term, surf, buf, info.width, info.height, &color);
render_csd_part(term, surf, buf, info->width, info->height, &color);
csd_commit(term, surf, buf);
}
static void
render_csd_border(struct terminal *term, enum csd_surface surf_idx)
render_csd_border(struct terminal *term, enum csd_surface surf_idx,
const struct csd_data *info, struct buffer *buf)
{
xassert(term->window->csd_mode == CSD_YES);
xassert(surf_idx >= CSD_SURF_LEFT && surf_idx <= CSD_SURF_BOTTOM);
struct csd_data info = get_csd_data(term, surf_idx);
struct wl_surface *surf = term->window->csd.surface[surf_idx].surf;
if (info.width == 0 || info.height == 0)
if (info->width == 0 || info->height == 0)
return;
xassert(info.width % term->scale == 0);
xassert(info.height % term->scale == 0);
unsigned long cookie = shm_cookie_csd(term, surf_idx);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie, false, 1);
xassert(info->width % term->scale == 0);
xassert(info->height % term->scale == 0);
pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0);
render_csd_part(term, surf, buf, info.width, info.height, &color);
render_csd_part(term, surf, buf, info->width, info->height, &color);
csd_commit(term, surf, buf);
}
@ -1796,23 +1785,19 @@ render_csd_button_close(struct terminal *term, struct buffer *buf)
}
static void
render_csd_button(struct terminal *term, enum csd_surface surf_idx)
render_csd_button(struct terminal *term, enum csd_surface surf_idx,
const struct csd_data *info, struct buffer *buf)
{
xassert(term->window->csd_mode == CSD_YES);
xassert(surf_idx >= CSD_SURF_MINIMIZE && surf_idx <= CSD_SURF_CLOSE);
struct csd_data info = get_csd_data(term, surf_idx);
struct wl_surface *surf = term->window->csd.surface[surf_idx].surf;
if (info.width == 0 || info.height == 0)
if (info->width == 0 || info->height == 0)
return;
xassert(info.width % term->scale == 0);
xassert(info.height % term->scale == 0);
unsigned long cookie = shm_cookie_csd(term, surf_idx);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie, false, 1);
xassert(info->width % term->scale == 0);
xassert(info->height % term->scale == 0);
uint32_t _color;
uint16_t alpha = 0xffff;
@ -1861,7 +1846,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx)
_color = color_dim(_color);
pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha);
render_csd_part(term, surf, buf, info.width, info.height, &color);
render_csd_part(term, surf, buf, info->width, info->height, &color);
switch (surf_idx) {
case CSD_SURF_MINIMIZE: render_csd_button_minimize(term, buf); break;
@ -1885,12 +1870,16 @@ render_csd(struct terminal *term)
if (term->window->is_fullscreen)
return;
struct csd_data infos[CSD_SURF_COUNT];
int widths[CSD_SURF_COUNT];
int heights[CSD_SURF_COUNT];
for (size_t i = 0; i < CSD_SURF_COUNT; i++) {
struct csd_data info = get_csd_data(term, i);
const int x = info.x;
const int y = info.y;
const int width = info.width;
const int height = info.height;
infos[i] = get_csd_data(term, i);
const int x = infos[i].x;
const int y = infos[i].y;
const int width = infos[i].width;
const int height = infos[i].height;
struct wl_surface *surf = term->window->csd.surface[i].surf;
struct wl_subsurface *sub = term->window->csd.surface[i].sub;
@ -1899,20 +1888,27 @@ render_csd(struct terminal *term)
xassert(sub != NULL);
if (width == 0 || height == 0) {
widths[i] = heights[i] = 0;
wl_subsurface_set_position(sub, 0, 0);
wl_surface_attach(surf, NULL, 0, 0);
wl_surface_commit(surf);
continue;
}
widths[i] = width;
heights[i] = height;
wl_subsurface_set_position(sub, x / term->scale, y / term->scale);
}
struct buffer *bufs[CSD_SURF_COUNT];
shm_get_many(term->render.chains.csd, CSD_SURF_COUNT, widths, heights, bufs);
for (size_t i = CSD_SURF_LEFT; i <= CSD_SURF_BOTTOM; i++)
render_csd_border(term, i);
render_csd_border(term, i, &infos[i], bufs[i]);
for (size_t i = CSD_SURF_MINIMIZE; i <= CSD_SURF_CLOSE; i++)
render_csd_button(term, i);
render_csd_title(term);
render_csd_button(term, i, &infos[i], bufs[i]);
render_csd_title(term, &infos[CSD_SURF_TITLE], bufs[CSD_SURF_TITLE]);
}
static void
@ -2051,10 +2047,6 @@ render_scrollback_position(struct terminal *term)
const int height =
(2 * margin + term->cell_height + scale - 1) / scale * scale;
unsigned long cookie = shm_cookie_scrollback_indicator(term);
struct buffer *buf = shm_get_buffer(
term->wl->shm, width, height, cookie, false, 1);
/* *Where* to render - parent relative coordinates */
int surf_top = 0;
switch (term->conf->scrollback.indicator.position) {
@ -2072,18 +2064,29 @@ render_scrollback_position(struct terminal *term)
/* Make sure we don't collide with the scrollback search box */
lines--;
}
xassert(lines > 0);
int pixels = lines * term->cell_height - height + 2 * margin;
lines = max(lines, 0);
int pixels = max(lines * term->cell_height - height + 2 * margin, 0);
surf_top = term->cell_height - margin + (int)(percent * pixels);
break;
}
}
const int x = (term->width - margin - width) / scale * scale;
const int y = (term->margins.top + surf_top) / scale * scale;
if (y + height > term->height) {
wl_surface_attach(win->scrollback_indicator.surf, NULL, 0, 0);
wl_surface_commit(win->scrollback_indicator.surf);
return;
}
struct buffer_chain *chain = term->render.chains.scrollback_indicator;
struct buffer *buf = shm_get_buffer(chain, width, height);
wl_subsurface_set_position(
win->scrollback_indicator.sub,
(term->width - margin - width) / scale,
(term->margins.top + surf_top) / scale);
win->scrollback_indicator.sub, x / scale, y / scale);
render_osd(
term,
@ -2092,7 +2095,6 @@ render_scrollback_position(struct terminal *term)
buf, text,
term->colors.table[0], term->colors.table[8 + 4],
width - margin - wcslen(text) * term->cell_width, margin);
}
static void
@ -2112,9 +2114,8 @@ render_render_timer(struct terminal *term, struct timeval render_time)
const int height =
(2 * margin + term->cell_height + scale - 1) / scale * scale;
unsigned long cookie = shm_cookie_render_timer(term);
struct buffer *buf = shm_get_buffer(
term->wl->shm, width, height, cookie, false, 1);
struct buffer_chain *chain = term->render.chains.render_timer;
struct buffer *buf = shm_get_buffer(chain, width, height);
wl_subsurface_set_position(
win->render_timer.sub,
@ -2157,7 +2158,7 @@ reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old
}
if (new->age > 1) {
memcpy(new->mmapped, old->mmapped, new->size);
memcpy(new->data, old->data, new->height * new->stride);
return;
}
@ -2287,9 +2288,8 @@ grid_render(struct terminal *term)
xassert(term->width > 0);
xassert(term->height > 0);
unsigned long cookie = shm_cookie_grid(term);
struct buffer *buf = shm_get_buffer(
term->wl->shm, term->width, term->height, cookie, true, 1 + term->render.workers.count);
struct buffer_chain *chain = term->render.chains.grid;
struct buffer *buf = shm_get_buffer(chain, term->width, term->height);
/* Dirty old and current cursor cell, to ensure theyre repainted */
dirty_old_cursor(term);
@ -2306,7 +2306,7 @@ grid_render(struct terminal *term)
}
else if (buf->age > 0) {
LOG_DBG("buffer age: %u", buf->age);
LOG_DBG("buffer age: %u (%p)", buf->age, (void *)buf);
xassert(term->render.last_buf != NULL);
xassert(term->render.last_buf != buf);
@ -2319,17 +2319,18 @@ grid_render(struct terminal *term)
}
if (term->render.last_buf != NULL) {
free(term->render.last_buf->scroll_damage);
term->render.last_buf->scroll_damage = NULL;
shm_unref(term->render.last_buf);
term->render.last_buf = NULL;
}
term->render.last_buf = buf;
term->render.was_flashing = term->flash.active;
term->render.was_searching = term->is_searching;
shm_addref(buf);
buf->age = 0;
xassert(buf->scroll_damage == NULL);
free(term->render.last_buf->scroll_damage);
buf->scroll_damage_count = tll_length(term->grid->scroll_damage);
buf->scroll_damage = xmalloc(
buf->scroll_damage_count * sizeof(buf->scroll_damage[0]));
@ -2620,8 +2621,14 @@ render_search_box(struct terminal *term)
const size_t visible_cells = (visible_width - 2 * margin) / term->cell_width;
size_t glyph_offset = term->render.search_glyph_offset;
unsigned long cookie = shm_cookie_search(term);
struct buffer *buf = shm_get_buffer(term->wl->shm, width, height, cookie, false, 1);
struct buffer_chain *chain = term->render.chains.search;
struct buffer *buf = shm_get_buffer(chain, width, height);
pixman_region32_t clip;
pixman_region32_init_rect(&clip, 0, 0, width, height);
pixman_image_set_clip_region32(buf->pix[0], &clip);
pixman_region32_fini(&clip);
#define WINDOW_X(x) (margin + x)
#define WINDOW_Y(y) (term->height - margin - height + y)
@ -2875,6 +2882,10 @@ render_urls(struct terminal *term)
struct wl_window *win = term->window;
xassert(tll_length(win->urls) > 0);
const int scale = term->scale;
const int x_margin = 2 * scale;
const int y_margin = 1 * scale;
/* Calculate view start, counted from the *current* scrollback start */
const int scrollback_end
= (term->grid->offset + term->rows) & (term->grid->num_rows - 1);
@ -2886,6 +2897,45 @@ render_urls(struct terminal *term)
const bool show_url = term->urls_show_uri_on_jump_label;
/*
* There can potentially be a lot of URLs.
*
* Since each URL is a separate sub-surface, and requires its own
* SHM buffer, we may be allocating a lot of buffers.
*
* SHM buffers normally have their own, private SHM buffer
* pool. Each pool is mmapped, and thus allocates *at least*
* 4K. Since URL labels are typically small, we end up using an
* excessive amount of both virtual and physical memory.
*
* For this reason, we instead use shm_get_many(), which uses a
* single, shared pool for all buffers.
*
* To be able to use it, we need to have all the *all* the buffer
* dimensions up front.
*
* Thus, the first iteration through the URLs do the heavy
* lifting: builds the label contents and calculates both its
* position and size. But instead of rendering the label
* immediately, we store the calculated data, and then do a second
* pass, where we first get all our buffers, and then render to
* them.
*/
/* Positioning data + label contents */
struct {
const struct wl_url *url;
wchar_t *text;
int x;
int y;
} info[tll_length(win->urls)];
/* For shm_get_many() */
int widths[tll_length(win->urls)];
int heights[tll_length(win->urls)];
size_t render_count = 0;
tll_foreach(win->urls, it) {
const struct url *url = it->item.url;
const wchar_t *key = url->key;
@ -2942,6 +2992,7 @@ render_urls(struct terminal *term)
/* Maximum width of label, in pixels */
const int max_width =
term->width - term->margins.left - term->margins.right - x;
const int max_cols = max_width / term->cell_width;
const size_t key_len = wcslen(key);
@ -2979,10 +3030,6 @@ render_urls(struct terminal *term)
* Do it in a way such that we dont cut the label in the
* middle of a double-width character.
*/
const int scale = term->scale;
const int x_margin = 2 * scale;
const int y_margin = 1 * scale;
const int max_cols = max_width / term->cell_width;
int cols = 0;
@ -3010,23 +3057,48 @@ render_urls(struct terminal *term)
const int height =
(2 * y_margin + term->cell_height + scale - 1) / scale * scale;
struct buffer *buf = shm_get_buffer(
term->wl->shm, width, height, shm_cookie_url(url), false, 1);
info[render_count].url = &it->item;
info[render_count].text = xwcsdup(label);
info[render_count].x = x;
info[render_count].y = y;
widths[render_count] = width;
heights[render_count] = height;
render_count++;
}
struct buffer_chain *chain = term->render.chains.url;
struct buffer *bufs[render_count];
shm_get_many(chain, render_count, widths, heights, bufs);
uint32_t fg = term->conf->colors.use_custom.jump_label
? term->conf->colors.jump_label.fg
: term->colors.table[0];
uint32_t bg = term->conf->colors.use_custom.jump_label
? term->conf->colors.jump_label.bg
: term->colors.table[3];
for (size_t i = 0; i < render_count; i++) {
struct wl_surface *surf = info[i].url->surf.surf;
struct wl_subsurface *sub_surf = info[i].url->surf.sub;
const wchar_t *label = info[i].text;
const int x = info[i].x;
const int y = info[i].y;
xassert(surf != NULL);
xassert(sub_surf != NULL);
wl_subsurface_set_position(
sub_surf,
(term->margins.left + x) / term->scale,
(term->margins.top + y) / term->scale);
uint32_t fg = term->conf->colors.use_custom.jump_label
? term->conf->colors.jump_label.fg
: term->colors.table[0];
uint32_t bg = term->conf->colors.use_custom.jump_label
? term->conf->colors.jump_label.bg
: term->colors.table[3];
render_osd(
term, surf, sub_surf, buf, label, fg, bg, x_margin, y_margin);
term, surf, sub_surf, bufs[i], label, fg, bg, x_margin, y_margin);
free(info[i].text);
}
}
@ -3119,8 +3191,10 @@ fdm_tiocswinsz(struct fdm *fdm, int fd, int events, void *data)
if (events & EPOLLIN)
tiocswinsz(term);
fdm_del(fdm, fd);
term->window->resize_timeout_fd = -1;
if (term->window->resize_timeout_fd >= 0) {
fdm_del(fdm, term->window->resize_timeout_fd);
term->window->resize_timeout_fd = -1;
}
return true;
}
@ -3405,6 +3479,7 @@ damage_view:
tll_free(term->normal.scroll_damage);
tll_free(term->alt.scroll_damage);
shm_unref(term->render.last_buf);
term->render.last_buf = NULL;
term_damage_view(term);
render_refresh_csd(term);

View file

@ -69,10 +69,8 @@ selection_on_rows(const struct terminal *term, int row_start, int row_end)
end = tmp;
}
if (row_start >= start->row && row_end <= end->row) {
LOG_INFO("ON ROWS");
if (row_start >= start->row && row_end <= end->row)
return true;
}
return false;
}

View file

@ -132,13 +132,6 @@ term_shutdown_handler(void *data, int exit_code)
{
struct terminal_instance *instance = data;
struct wl_shm *shm = instance->server->wayl->shm;
shm_purge(shm, shm_cookie_grid(instance->terminal));
shm_purge(shm, shm_cookie_search(instance->terminal));
for (enum csd_surface surf = 0; surf < CSD_SURF_COUNT; surf++)
shm_purge(shm, shm_cookie_csd(instance->terminal, surf));
instance->terminal = NULL;
instance_destroy(instance, exit_code);
}

622
shm.c
View file

@ -55,11 +55,43 @@
*/
static off_t max_pool_size = 512 * 1024 * 1024;
static tll(struct buffer) buffers;
static bool can_punch_hole = false;
static bool can_punch_hole_initialized = false;
struct buffer_pool {
int fd; /* memfd */
struct wl_shm_pool *wl_pool;
void *real_mmapped; /* Address returned from mmap */
size_t mmap_size; /* Size of mmap (>= size) */
size_t ref_count;
};
struct buffer_chain;
struct buffer_private {
struct buffer public;
struct buffer_chain *chain;
size_t ref_count;
bool busy; /* Owned by compositor */
struct buffer_pool *pool;
off_t offset; /* Offset into memfd where data begins */
size_t size;
bool scrollable;
};
struct buffer_chain {
tll(struct buffer_private *) bufs;
struct wl_shm *shm;
size_t pix_instances;
bool scrollable;
};
static tll(struct buffer_private *) deferred;
#undef MEASURE_SHM_ALLOCS
#if defined(MEASURE_SHM_ALLOCS)
static size_t max_alloced = 0;
@ -85,34 +117,70 @@ buffer_destroy_dont_close(struct buffer *buf)
free(buf->pix);
buf->pix = NULL;
buf->wl_buf = NULL;
buf->mmapped = NULL;
buf->data = NULL;
}
static void
buffer_destroy(struct buffer *buf)
pool_unref(struct buffer_pool *pool)
{
buffer_destroy_dont_close(buf);
if (buf->real_mmapped != MAP_FAILED)
munmap(buf->real_mmapped, buf->mmap_size);
if (buf->pool != NULL)
wl_shm_pool_destroy(buf->pool);
if (buf->fd >= 0)
close(buf->fd);
if (pool == NULL)
return;
buf->real_mmapped = MAP_FAILED;
xassert(pool->ref_count > 0);
pool->ref_count--;
if (pool->ref_count > 0)
return;
if (pool->real_mmapped != MAP_FAILED)
munmap(pool->real_mmapped, pool->mmap_size);
if (pool->wl_pool != NULL)
wl_shm_pool_destroy(pool->wl_pool);
if (pool->fd >= 0)
close(pool->fd);
pool->real_mmapped = MAP_FAILED;
pool->wl_pool = NULL;
pool->fd = -1;
free(pool);
}
static void
buffer_destroy(struct buffer_private *buf)
{
buffer_destroy_dont_close(&buf->public);
pool_unref(buf->pool);
buf->pool = NULL;
buf->fd = -1;
free(buf->scroll_damage);
pixman_region32_fini(&buf->dirty);
free(buf->public.scroll_damage);
pixman_region32_fini(&buf->public.dirty);
free(buf);
}
static bool
buffer_unref_no_remove_from_chain(struct buffer_private *buf)
{
xassert(buf->ref_count > 0);
buf->ref_count--;
if (buf->ref_count > 0)
return false;
if (buf->busy)
tll_push_back(deferred, buf);
else
buffer_destroy(buf);
return true;
}
void
shm_fini(void)
{
tll_foreach(buffers, it) {
buffer_destroy(&it->item);
tll_remove(buffers, it);
LOG_DBG("deferred buffers: %zu", tll_length(deferred));
tll_foreach(deferred, it) {
buffer_destroy(it->item);
tll_remove(deferred, it);
}
#if defined(MEASURE_SHM_ALLOCS) && MEASURE_SHM_ALLOCS
@ -123,11 +191,28 @@ shm_fini(void)
static void
buffer_release(void *data, struct wl_buffer *wl_buffer)
{
struct buffer *buffer = data;
LOG_DBG("release: cookie=%lx (buf=%p)", buffer->cookie, (void *)buffer);
xassert(buffer->wl_buf == wl_buffer);
struct buffer_private *buffer = data;
xassert(buffer->public.wl_buf == wl_buffer);
xassert(buffer->busy);
buffer->busy = false;
if (buffer->ref_count == 0) {
bool found = false;
tll_foreach(deferred, it) {
if (it->item == buffer) {
found = true;
tll_remove(deferred, it);
break;
}
}
buffer_destroy(buffer);
xassert(found);
if (!found)
LOG_WARN("deferred delete: buffer not on the 'deferred' list");
}
}
static const struct wl_buffer_listener buffer_listener = {
@ -154,47 +239,53 @@ page_size(void)
#endif
static bool
instantiate_offset(struct wl_shm *shm, struct buffer *buf, off_t new_offset)
instantiate_offset(struct buffer_private *buf, off_t new_offset)
{
xassert(buf->fd >= 0);
xassert(buf->mmapped == NULL);
xassert(buf->wl_buf == NULL);
xassert(buf->pix == NULL);
xassert(buf->public.data == NULL);
xassert(buf->public.pix == NULL);
xassert(buf->public.wl_buf == NULL);
xassert(buf->pool != NULL);
const struct buffer_pool *pool = buf->pool;
void *mmapped = MAP_FAILED;
struct wl_buffer *wl_buf = NULL;
pixman_image_t **pix = xcalloc(buf->pix_instances, sizeof(*pix));
pixman_image_t **pix = xcalloc(buf->public.pix_instances, sizeof(*pix));
mmapped = (uint8_t *)buf->real_mmapped + new_offset;
mmapped = (uint8_t *)pool->real_mmapped + new_offset;
wl_buf = wl_shm_pool_create_buffer(
buf->pool, new_offset, buf->width, buf->height, buf->stride, WL_SHM_FORMAT_ARGB8888);
pool->wl_pool, new_offset,
buf->public.width, buf->public.height, buf->public.stride,
WL_SHM_FORMAT_ARGB8888);
if (wl_buf == NULL) {
LOG_ERR("failed to create SHM buffer");
goto err;
}
/* One pixman image for each worker thread (do we really need multiple?) */
for (size_t i = 0; i < buf->pix_instances; i++) {
for (size_t i = 0; i < buf->public.pix_instances; i++) {
pix[i] = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, buf->width, buf->height, (uint32_t *)mmapped, buf->stride);
PIXMAN_a8r8g8b8, buf->public.width, buf->public.height,
(uint32_t *)mmapped, buf->public.stride);
if (pix[i] == NULL) {
LOG_ERR("failed to create pixman image");
goto err;
}
}
buf->public.data = mmapped;
buf->public.wl_buf = wl_buf;
buf->public.pix = pix;
buf->offset = new_offset;
buf->mmapped = mmapped;
buf->wl_buf = wl_buf;
buf->pix = pix;
wl_buffer_add_listener(wl_buf, &buffer_listener, buf);
return true;
err:
if (pix != NULL) {
for (size_t i = 0; i < buf->pix_instances; i++)
for (size_t i = 0; i < buf->public.pix_instances; i++)
if (pix[i] != NULL)
pixman_image_unref(pix[i]);
}
@ -206,76 +297,12 @@ err:
return false;
}
struct buffer *
shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, bool scrollable, size_t pix_instances)
static void NOINLINE
get_new_buffers(struct buffer_chain *chain, size_t count,
int widths[static count], int heights[static count],
struct buffer *bufs[static count], bool immediate_purge)
{
/* Purge buffers marked for purging */
tll_foreach(buffers, it) {
if (it->item.cookie != cookie)
continue;
if (!it->item.purge)
continue;
xassert(!it->item.busy);
LOG_DBG("cookie=%lx: purging buffer %p (width=%d, height=%d): %zu KB",
cookie, (void *)&it->item, it->item.width, it->item.height,
it->item.size / 1024);
buffer_destroy(&it->item);
tll_remove(buffers, it);
}
struct buffer *cached = NULL;
tll_foreach(buffers, it) {
if (it->item.width != width)
continue;
if (it->item.height != height)
continue;
if (it->item.cookie != cookie)
continue;
if (it->item.busy)
it->item.age++;
else
#if FORCED_DOUBLE_BUFFERING
if (it->item.age == 0)
it->item.age++;
else
#endif
{
LOG_DBG("cookie=%lx: re-using buffer from cache (buf=%p)",
cookie, (void *)&it->item);
it->item.busy = true;
it->item.purge = false;
pixman_region32_clear(&it->item.dirty);
free(it->item.scroll_damage);
it->item.scroll_damage = NULL;
xassert(it->item.pix_instances == pix_instances);
cached = &it->item;
}
}
if (cached != NULL)
return cached;
/* Purge old buffers associated with this cookie */
tll_foreach(buffers, it) {
if (it->item.cookie != cookie)
continue;
if (it->item.busy)
continue;
if (it->item.width == width && it->item.height == height)
continue;
LOG_DBG("cookie=%lx: marking buffer %p for purging", cookie, (void *)&it->item);
it->item.purge = true;
}
xassert(count == 1 || !chain->scrollable);
/*
* No existing buffer available. Create a new one by:
*
@ -286,14 +313,21 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
* The pixman image and the wayland buffer are now sharing memory.
*/
int stride[count];
int sizes[count];
size_t total_size = 0;
for (size_t i = 0; i < count; i++) {
stride[i] = stride_for_format_and_width(PIXMAN_a8r8g8b8, widths[i]);
sizes[i] = stride[i] * heights[i];
total_size += sizes[i];
}
int pool_fd = -1;
const int stride = stride_for_format_and_width(PIXMAN_a8r8g8b8, width);
const size_t size = stride * height;
void *real_mmapped = MAP_FAILED;
struct wl_shm_pool *pool = NULL;
LOG_DBG("cookie=%lx: allocating new buffer: %zu KB", cookie, size / 1024);
struct wl_shm_pool *wl_pool = NULL;
struct buffer_pool *pool = NULL;
/* Backing memory for SHM */
#if defined(MEMFD_CREATE)
@ -312,14 +346,20 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
}
#if __SIZEOF_POINTER__ == 8
off_t initial_offset = scrollable && max_pool_size > 0 ? (max_pool_size / 4) & ~(page_size() - 1) : 0;
off_t memfd_size = scrollable && max_pool_size > 0 ? max_pool_size : size;
off_t offset = chain->scrollable && max_pool_size > 0
? (max_pool_size / 4) & ~(page_size() - 1)
: 0;
off_t memfd_size = chain->scrollable && max_pool_size > 0
? max_pool_size
: total_size;
#else
off_t initial_offset = 0;
off_t memfd_size = size;
off_t offset = 0;
off_t memfd_size = total_size;
#endif
LOG_DBG("memfd-size: %lu, initial offset: %lu", memfd_size, initial_offset);
xassert(chain->scrollable || (offset == 0 && memfd_size == total_size));
LOG_DBG("memfd-size: %lu, initial offset: %lu", memfd_size, offset);
if (ftruncate(pool_fd, memfd_size) == -1) {
LOG_ERRNO("failed to set size of SHM backing memory file");
@ -344,10 +384,10 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
#endif
}
if (scrollable && !can_punch_hole) {
initial_offset = 0;
memfd_size = size;
scrollable = false;
if (chain->scrollable && !can_punch_hole) {
offset = 0;
memfd_size = total_size;
chain->scrollable = false;
if (ftruncate(pool_fd, memfd_size) < 0) {
LOG_ERRNO("failed to set size of SHM backing memory file");
@ -375,37 +415,66 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
}
#endif
pool = wl_shm_create_pool(shm, pool_fd, memfd_size);
if (pool == NULL) {
wl_pool = wl_shm_create_pool(chain->shm, pool_fd, memfd_size);
if (wl_pool == NULL) {
LOG_ERR("failed to create SHM pool");
goto err;
}
/* Push to list of available buffers, but marked as 'busy' */
tll_push_back(
buffers,
((struct buffer){
.cookie = cookie,
.width = width,
.height = height,
.stride = stride,
.busy = true,
.size = size,
.pix_instances = pix_instances,
.fd = pool_fd,
.pool = pool,
.scrollable = scrollable,
.real_mmapped = real_mmapped,
.mmap_size = memfd_size,
.offset = 0,
.age = 1234, /* Force a full repaint */
}));
struct buffer *ret = &tll_back(buffers);
if (!instantiate_offset(shm, ret, initial_offset))
pool = xmalloc(sizeof(*pool));
if (pool == NULL) {
LOG_ERRNO("failed to allocate buffer pool");
goto err;
}
pixman_region32_init(&ret->dirty);
*pool = (struct buffer_pool){
.fd = pool_fd,
.wl_pool = wl_pool,
.real_mmapped = real_mmapped,
.mmap_size = memfd_size,
.ref_count = 0,
};
for (size_t i = 0; i < count; i++) {
if (sizes[i] == 0) {
bufs[i] = NULL;
continue;
}
/* Push to list of available buffers, but marked as 'busy' */
struct buffer_private *buf = xmalloc(sizeof(*buf));
*buf = (struct buffer_private){
.public = {
.width = widths[i],
.height = heights[i],
.stride = stride[i],
.pix_instances = chain->pix_instances,
.age = 1234, /* Force a full repaint */
},
.chain = chain,
.ref_count = immediate_purge ? 0 : 1,
.busy = true,
.pool = pool,
.offset = 0,
.size = sizes[i],
.scrollable = chain->scrollable,
};
if (!instantiate_offset(buf, offset)) {
free(buf);
goto err;
}
if (immediate_purge)
tll_push_front(deferred, buf);
else
tll_push_front(chain->bufs, buf);
pixman_region32_init(&buf->public.dirty);
pool->ref_count++;
offset += buf->size;
bufs[i] = &buf->public;
}
#if defined(MEASURE_SHM_ALLOCS) && MEASURE_SHM_ALLOCS
{
@ -417,11 +486,19 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
}
#endif
return ret;
if (!shm_can_scroll(bufs[0])) {
/* We only need to keep the pool FD open if were going to SHM
* scroll it */
close(pool_fd);
pool->fd = -1;
}
return;
err:
if (pool != NULL)
wl_shm_pool_destroy(pool);
pool_unref(pool);
if (wl_pool != NULL)
wl_shm_pool_destroy(wl_pool);
if (real_mmapped != MAP_FAILED)
munmap(real_mmapped, memfd_size);
if (pool_fd != -1)
@ -429,13 +506,80 @@ err:
/* We don't handle this */
abort();
return NULL;
}
void
shm_get_many(struct buffer_chain *chain, size_t count,
int widths[static count], int heights[static count],
struct buffer *bufs[static count])
{
get_new_buffers(chain, count, widths, heights, bufs, true);
}
struct buffer *
shm_get_buffer(struct buffer_chain *chain, int width, int height)
{
LOG_DBG(
"chain=%p: looking for a re-usable %dx%d buffer "
"among %zu potential buffers",
(void *)chain, width, height, tll_length(chain->bufs));
struct buffer_private *cached = NULL;
tll_foreach(chain->bufs, it) {
struct buffer_private *buf = it->item;
if (buf->public.width != width || buf->public.height != height) {
LOG_DBG("purging mismatching buffer %p", (void *)buf);
if (buffer_unref_no_remove_from_chain(buf))
tll_remove(chain->bufs, it);
continue;
}
if (buf->busy)
buf->public.age++;
else
#if FORCED_DOUBLE_BUFFERING
if (buf->age == 0)
buf->age++;
else
#endif
{
if (cached == NULL) {
LOG_DBG("re-using buffer %p from cache", (void *)buf);
buf->busy = true;
pixman_region32_clear(&buf->public.dirty);
free(buf->public.scroll_damage);
buf->public.scroll_damage = NULL;
xassert(buf->public.pix_instances == chain->pix_instances);
cached = it->item;
} else {
/* We have multiple buffers eligible for
* re-use. Pick the youngest one, and mark the
* other one for purging */
if (buf->public.age < cached->public.age) {
shm_unref(&cached->public);
cached = buf;
} else {
if (buffer_unref_no_remove_from_chain(buf))
tll_remove(chain->bufs, it);
}
}
}
}
if (cached != NULL)
return &cached->public;
struct buffer *ret;
get_new_buffers(chain, 1, &width, &height, &ret, false);
return ret;
}
bool
shm_can_scroll(const struct buffer *buf)
shm_can_scroll(const struct buffer *_buf)
{
#if __SIZEOF_POINTER__ == 8
const struct buffer_private *buf = (const struct buffer_private *)_buf;
return can_punch_hole && max_pool_size > 0 && buf->scrollable;
#else
/* Not enough virtual address space in 32-bit */
@ -445,14 +589,20 @@ shm_can_scroll(const struct buffer *buf)
#if __SIZEOF_POINTER__ == 8 && defined(FALLOC_FL_PUNCH_HOLE)
static bool
wrap_buffer(struct wl_shm *shm, struct buffer *buf, off_t new_offset)
wrap_buffer(struct buffer_private *buf, off_t new_offset)
{
struct buffer_pool *pool = buf->pool;
xassert(pool->ref_count == 1);
/* We don't allow overlapping offsets */
off_t UNUSED diff =
new_offset < buf->offset ? buf->offset - new_offset : new_offset - buf->offset;
off_t UNUSED diff = new_offset < buf->offset
? buf->offset - new_offset
: new_offset - buf->offset;
xassert(diff > buf->size);
memcpy((uint8_t *)buf->real_mmapped + new_offset, buf->mmapped, buf->size);
memcpy((uint8_t *)pool->real_mmapped + new_offset,
buf->public.data,
buf->size);
off_t trim_ofs, trim_len;
if (new_offset > buf->offset) {
@ -462,11 +612,11 @@ wrap_buffer(struct wl_shm *shm, struct buffer *buf, off_t new_offset)
} else {
/* Trim everything *after* the new buffer location */
trim_ofs = new_offset + buf->size;
trim_len = buf->mmap_size - trim_ofs;
trim_len = pool->mmap_size - trim_ofs;
}
if (fallocate(
buf->fd,
pool->fd,
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
trim_ofs, trim_len) < 0)
{
@ -475,30 +625,34 @@ wrap_buffer(struct wl_shm *shm, struct buffer *buf, off_t new_offset)
}
/* Re-instantiate pixman+wl_buffer+raw pointersw */
buffer_destroy_dont_close(buf);
return instantiate_offset(shm, buf, new_offset);
buffer_destroy_dont_close(&buf->public);
return instantiate_offset(buf, new_offset);
}
static bool
shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows,
shm_scroll_forward(struct buffer_private *buf, int rows,
int top_margin, int top_keep_rows,
int bottom_margin, int bottom_keep_rows)
{
struct buffer_pool *pool = buf->pool;
xassert(can_punch_hole);
xassert(buf->busy);
xassert(buf->pix);
xassert(buf->wl_buf);
xassert(buf->fd >= 0);
xassert(buf->public.pix != NULL);
xassert(buf->public.wl_buf != NULL);
xassert(pool != NULL);
xassert(pool->ref_count == 1);
xassert(pool->fd >= 0);
LOG_DBG("scrolling %d rows (%d bytes)", rows, rows * buf->stride);
LOG_DBG("scrolling %d rows (%d bytes)", rows, rows * buf->public.stride);
const off_t diff = rows * buf->stride;
const off_t diff = rows * buf->public.stride;
xassert(rows > 0);
xassert(diff < buf->size);
if (buf->offset + diff + buf->size > max_pool_size) {
LOG_DBG("memfd offset wrap around");
if (!wrap_buffer(shm, buf, 0))
if (!wrap_buffer(buf, 0))
goto err;
}
@ -507,6 +661,7 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows,
xassert(new_offset + buf->size <= max_pool_size);
#if TIME_SCROLL
struct timeval tot;
struct timeval time1;
gettimeofday(&time1, NULL);
@ -515,10 +670,13 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows,
if (top_keep_rows > 0) {
/* Copy current 'top' region to its new location */
const int stride = buf->public.stride;
uint8_t *base = buf->public.data;
memmove(
(uint8_t *)buf->mmapped + (top_margin + rows) * buf->stride,
(uint8_t *)buf->mmapped + (top_margin + 0) * buf->stride,
top_keep_rows * buf->stride);
base + (top_margin + rows) * stride,
base + (top_margin + 0) * stride,
top_keep_rows * stride);
#if TIME_SCROLL
gettimeofday(&time2, NULL);
@ -528,14 +686,14 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows,
}
/* Destroy old objects (they point to the old offset) */
buffer_destroy_dont_close(buf);
buffer_destroy_dont_close(&buf->public);
/* Free unused memory - everything up until the new offset */
const off_t trim_ofs = 0;
const off_t trim_len = new_offset;
if (fallocate(
buf->fd,
pool->fd,
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
trim_ofs, trim_len) < 0)
{
@ -551,7 +709,7 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows,
#endif
/* Re-instantiate pixman+wl_buffer+raw pointersw */
bool ret = instantiate_offset(shm, buf, new_offset);
bool ret = instantiate_offset(buf, new_offset);
#if TIME_SCROLL
struct timeval time4;
@ -562,10 +720,14 @@ shm_scroll_forward(struct wl_shm *shm, struct buffer *buf, int rows,
if (ret && bottom_keep_rows > 0) {
/* Copy 'bottom' region to its new location */
const size_t size = buf->size;
const int stride = buf->public.stride;
uint8_t *base = buf->public.data;
memmove(
(uint8_t *)buf->mmapped + buf->size - (bottom_margin + bottom_keep_rows) * buf->stride,
(uint8_t *)buf->mmapped + buf->size - (bottom_margin + rows + bottom_keep_rows) * buf->stride,
bottom_keep_rows * buf->stride);
base + size - (bottom_margin + bottom_keep_rows) * stride,
base + size - (bottom_margin + rows + bottom_keep_rows) * stride,
bottom_keep_rows * stride);
#if TIME_SCROLL
struct timeval time5;
@ -584,16 +746,19 @@ err:
}
static bool
shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows,
shm_scroll_reverse(struct buffer_private *buf, int rows,
int top_margin, int top_keep_rows,
int bottom_margin, int bottom_keep_rows)
{
xassert(rows > 0);
const off_t diff = rows * buf->stride;
struct buffer_pool *pool = buf->pool;
xassert(pool->ref_count == 1);
const off_t diff = rows * buf->public.stride;
if (diff > buf->offset) {
LOG_DBG("memfd offset reverse wrap-around");
if (!wrap_buffer(shm, buf, (max_pool_size - buf->size) & ~(page_size() - 1)))
if (!wrap_buffer(buf, (max_pool_size - buf->size) & ~(page_size() - 1)))
goto err;
}
@ -611,10 +776,14 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows,
if (bottom_keep_rows > 0) {
/* Copy 'bottom' region to its new location */
const size_t size = buf->size;
const int stride = buf->public.stride;
uint8_t *base = buf->public.data;
memmove(
(uint8_t *)buf->mmapped + buf->size - (bottom_margin + rows + bottom_keep_rows) * buf->stride,
(uint8_t *)buf->mmapped + buf->size - (bottom_margin + bottom_keep_rows) * buf->stride,
bottom_keep_rows * buf->stride);
base + size - (bottom_margin + rows + bottom_keep_rows) * stride,
base + size - (bottom_margin + bottom_keep_rows) * stride,
bottom_keep_rows * stride);
#if TIME_SCROLL
gettimeofday(&time1, NULL);
@ -624,14 +793,14 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows,
}
/* Destroy old objects (they point to the old offset) */
buffer_destroy_dont_close(buf);
buffer_destroy_dont_close(&buf->public);
/* Free unused memory - everything after the relocated buffer */
const off_t trim_ofs = new_offset + buf->size;
const off_t trim_len = buf->mmap_size - trim_ofs;
const off_t trim_len = pool->mmap_size - trim_ofs;
if (fallocate(
buf->fd,
pool->fd,
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
trim_ofs, trim_len) < 0)
{
@ -646,7 +815,7 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows,
#endif
/* Re-instantiate pixman+wl_buffer+raw pointers */
bool ret = instantiate_offset(shm, buf, new_offset);
bool ret = instantiate_offset(buf, new_offset);
#if TIME_SCROLL
struct timeval time3;
@ -657,10 +826,13 @@ shm_scroll_reverse(struct wl_shm *shm, struct buffer *buf, int rows,
if (ret && top_keep_rows > 0) {
/* Copy current 'top' region to its new location */
const int stride = buf->public.stride;
uint8_t *base = buf->public.data;
memmove(
(uint8_t *)buf->mmapped + (top_margin + 0) * buf->stride,
(uint8_t *)buf->mmapped + (top_margin + rows) * buf->stride,
top_keep_rows * buf->stride);
base + (top_margin + 0) * stride,
base + (top_margin + rows) * stride,
top_keep_rows * stride);
#if TIME_SCROLL
struct timeval time4;
@ -679,36 +851,88 @@ err:
#endif /* FALLOC_FL_PUNCH_HOLE */
bool
shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows,
shm_scroll(struct buffer *_buf, int rows,
int top_margin, int top_keep_rows,
int bottom_margin, int bottom_keep_rows)
{
#if __SIZEOF_POINTER__ == 8 && defined(FALLOC_FL_PUNCH_HOLE)
if (!shm_can_scroll(buf))
if (!shm_can_scroll(_buf))
return false;
struct buffer_private *buf = (struct buffer_private *)_buf;
xassert(rows != 0);
return rows > 0
? shm_scroll_forward(shm, buf, rows, top_margin, top_keep_rows, bottom_margin, bottom_keep_rows)
: shm_scroll_reverse(shm, buf, -rows, top_margin, top_keep_rows, bottom_margin, bottom_keep_rows);
? shm_scroll_forward(buf, rows, top_margin, top_keep_rows, bottom_margin, bottom_keep_rows)
: shm_scroll_reverse(buf, -rows, top_margin, top_keep_rows, bottom_margin, bottom_keep_rows);
#else
return false;
#endif
}
void
shm_purge(struct wl_shm *shm, unsigned long cookie)
shm_purge(struct buffer_chain *chain)
{
LOG_DBG("cookie=%lx: purging all buffers", cookie);
LOG_DBG("chain: %p: purging all buffers", (void *)chain);
/* Purge old buffers associated with this cookie */
tll_foreach(buffers, it) {
if (it->item.cookie != cookie)
continue;
xassert(!it->item.busy);
buffer_destroy(&it->item);
tll_remove(buffers, it);
tll_foreach(chain->bufs, it) {
if (buffer_unref_no_remove_from_chain(it->item))
tll_remove(chain->bufs, it);
}
}
void
shm_addref(struct buffer *_buf)
{
struct buffer_private *buf = (struct buffer_private *)_buf;
buf->ref_count++;
}
void
shm_unref(struct buffer *_buf)
{
if (_buf == NULL)
return;
struct buffer_private *buf = (struct buffer_private *)_buf;
struct buffer_chain *chain = buf->chain;
tll_foreach(chain->bufs, it) {
if (it->item != buf)
continue;
if (buffer_unref_no_remove_from_chain(buf))
tll_remove(chain->bufs, it);
break;
}
}
struct buffer_chain *
shm_chain_new(struct wl_shm *shm, bool scrollable, size_t pix_instances)
{
struct buffer_chain *chain = xmalloc(sizeof(*chain));
*chain = (struct buffer_chain){
.bufs = tll_init(),
.shm = shm,
.pix_instances = pix_instances,
.scrollable = scrollable,
};
return chain;
}
void
shm_chain_free(struct buffer_chain *chain)
{
if (chain == NULL)
return;
shm_purge(chain);
if (tll_length(chain->bufs) > 0) {
BUG("chain=%p: there are buffers remaining; "
"is there a missing call to shm_unref()?", (void *)chain);
}
free(chain);
}

75
shm.h
View file

@ -7,58 +7,71 @@
#include <pixman.h>
#include <wayland-client.h>
#include "terminal.h"
#include <tllist.h>
struct damage;
struct buffer {
unsigned long cookie;
int width;
int height;
int stride;
bool busy;
size_t size; /* Buffer size */
void *mmapped; /* Raw data (TODO: rename) */
void *data;
struct wl_buffer *wl_buf;
pixman_image_t **pix;
size_t pix_instances;
/* Internal */
int fd; /* memfd */
struct wl_shm_pool *pool;
void *real_mmapped; /* Address returned from mmap */
size_t mmap_size; /* Size of mmap (>= size) */
off_t offset; /* Offset into memfd where data begins */
bool scrollable;
bool purge; /* True if this buffer should be destroyed */
unsigned age;
struct damage *scroll_damage;
size_t scroll_damage_count;
pixman_region32_t dirty;
};
struct buffer *shm_get_buffer(
struct wl_shm *shm, int width, int height, unsigned long cookie, bool scrollable, size_t pix_instances);
void shm_fini(void);
void shm_set_max_pool_size(off_t max_pool_size);
struct buffer_chain;
struct buffer_chain *shm_chain_new(
struct wl_shm *shm, bool scrollable, size_t pix_instances);
void shm_chain_free(struct buffer_chain *chain);
/*
* Returns a single buffer.
*
* May returned a cached buffer. If so, the buffers age indicates how
* many shm_get_buffer() calls have been made for the same
* width/height while the buffer was still busy.
*
* A newly allocated buffer has an age of 1234.
*/
struct buffer *shm_get_buffer(struct buffer_chain *chain, int width, int height);
/*
* Returns many buffers, described by info, all sharing the same SHM
* buffer pool.
*
* Never returns cached buffers. However, the newly created buffers
* are all inserted into the regular buffer cache, and are treated
* just like buffers created by shm_get_buffer().
*
* This function is useful when allocating many small buffers, with
* (roughly) the same life time.
*
* Buffers are tagged for immediate purging, and will be destroyed as
* soon as the compositor releases them.
*/
void shm_get_many(
struct buffer_chain *chain, size_t count,
int widths[static count], int heights[static count],
struct buffer *bufs[static count]);
bool shm_can_scroll(const struct buffer *buf);
bool shm_scroll(struct wl_shm *shm, struct buffer *buf, int rows,
bool shm_scroll(struct buffer *buf, int rows,
int top_margin, int top_keep_rows,
int bottom_margin, int bottom_keep_rows);
void shm_purge(struct wl_shm *shm, unsigned long cookie);
void shm_addref(struct buffer *buf);
void shm_unref(struct buffer *buf);
struct terminal;
static inline unsigned long shm_cookie_grid(const struct terminal *term) { return (unsigned long)((uintptr_t)term + 0); }
static inline unsigned long shm_cookie_search(const struct terminal *term) { return (unsigned long)((uintptr_t)term + 1); }
static inline unsigned long shm_cookie_scrollback_indicator(const struct terminal *term) { return (unsigned long)(uintptr_t)term + 2; }
static inline unsigned long shm_cookie_render_timer(const struct terminal *term) { return (unsigned long)(uintptr_t)term + 3; }
static inline unsigned long shm_cookie_csd(const struct terminal *term, int n) { return (unsigned long)((uintptr_t)term + 4 + (n)); }
struct url;
static inline unsigned long shm_cookie_url(const struct url *url) { return (unsigned long)(uintptr_t)url; }
void shm_purge(struct buffer_chain *chain);

View file

@ -88,9 +88,10 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
void
sixel_destroy(struct sixel *sixel)
{
pixman_image_unref(sixel->pix);
free(sixel->data);
if (sixel->pix != NULL)
pixman_image_unref(sixel->pix);
free(sixel->data);
sixel->pix = NULL;
sixel->data = NULL;
}

View file

@ -35,6 +35,7 @@
#include "selection.h"
#include "sixel.h"
#include "slave.h"
#include "shm.h"
#include "spawn.h"
#include "url-mode.h"
#include "util.h"
@ -1143,6 +1144,14 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.tab_stops = tll_init(),
.wl = wayl,
.render = {
.chains = {
.grid = shm_chain_new(wayl->shm, true, 1 + conf->render_worker_count),
.search = shm_chain_new(wayl->shm, false, 1),
.scrollback_indicator = shm_chain_new(wayl->shm, false, 1),
.render_timer = shm_chain_new(wayl->shm, false, 1),
.url = shm_chain_new(wayl->shm, false, 1),
.csd = shm_chain_new(wayl->shm, false, 1),
},
.scrollback_lines = conf->scrollback.lines,
.app_sync_updates.timer_fd = app_sync_updates_fd,
.title = {
@ -1458,6 +1467,14 @@ term_destroy(struct terminal *term)
xassert(tll_length(term->render.workers.queue) == 0);
tll_free(term->render.workers.queue);
shm_unref(term->render.last_buf);
shm_chain_free(term->render.chains.grid);
shm_chain_free(term->render.chains.search);
shm_chain_free(term->render.chains.scrollback_indicator);
shm_chain_free(term->render.chains.render_timer);
shm_chain_free(term->render.chains.url);
shm_chain_free(term->render.chains.csd);
tll_free(term->tab_stops);
tll_foreach(term->ptmx_buffers, it) {
@ -1984,6 +2001,208 @@ term_erase(struct terminal *term, const struct coord *start, const struct coord
sixel_overwrite_by_row(term, end->row, 0, end->col + 1);
}
void
term_erase_scrollback(struct terminal *term)
{
const int num_rows = term->grid->num_rows;
const int mask = num_rows - 1;
const int start = (term->grid->offset + term->rows) & mask;
const int end = (term->grid->offset - 1) & mask;
const int scrollback_start = term->grid->offset + term->rows;
const int rel_start = (start - scrollback_start + num_rows) & mask;
const int rel_end = (end - scrollback_start + num_rows) & mask;
const int sel_start = term->selection.start.row;
const int sel_end = term->selection.end.row;
if (sel_end >= 0) {
/*
* Cancel selection if it touches any of the rows in the
* scrollback, since we cant have the selection reference
* soon-to-be deleted rows.
*
* This is done by range checking the selection range against
* the scrollback range.
*
* To make this comparison simpler, the start/end absolute row
* numbers are rebased against the scrollback start, where
* row 0 is the *first* row in the scrollback. A high number
* thus means the row is further *down* in the scrollback,
* closer to the screen bottom.
*/
const int rel_sel_start = (sel_start - scrollback_start + num_rows) & mask;
const int rel_sel_end = (sel_end - scrollback_start + num_rows) & mask;
if ((rel_sel_start <= rel_start && rel_sel_end >= rel_start) ||
(rel_sel_start <= rel_end && rel_sel_end >= rel_end) ||
(rel_sel_start >= rel_start && rel_sel_end <= rel_end))
{
selection_cancel(term);
}
}
tll_foreach(term->grid->sixel_images, it) {
struct sixel *six = &it->item;
const int six_start = (six->pos.row - scrollback_start + num_rows) & mask;
const int six_end = (six->pos.row + six->rows - 1 - scrollback_start + num_rows) & mask;
if ((six_start <= rel_start && six_end >= rel_start) ||
(six_start <= rel_end && six_end >= rel_end) ||
(six_start >= rel_start && six_end <= rel_end))
{
sixel_destroy(six);
tll_remove(term->grid->sixel_images, it);
}
}
for (int i = start;; i = (i + 1) & mask) {
struct row *row = term->grid->rows[i];
if (row != NULL) {
if (term->render.last_cursor.row == row)
term->render.last_cursor.row = NULL;
grid_row_free(row);
term->grid->rows[i] = NULL;
}
if (i == end)
break;
}
term->grid->view = term->grid->offset;
term_damage_view(term);
}
UNITTEST
{
const int scrollback_rows = 16;
const int term_rows = 5;
const int cols = 5;
struct fdm *fdm = fdm_init();
xassert(fdm != NULL);
struct terminal term = {
.fdm = fdm,
.rows = term_rows,
.cols = cols,
.normal = {
.rows = xcalloc(scrollback_rows, sizeof(term.normal.rows[0])),
.num_rows = scrollback_rows,
.num_cols = cols,
},
.grid = &term.normal,
.selection = {
.start = {-1, -1},
.end = {-1, -1},
.kind = SELECTION_NONE,
.auto_scroll = {
.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK),
},
},
};
xassert(term.selection.auto_scroll.fd >= 0);
#define populate_scrollback() do { \
for (int i = 0; i < scrollback_rows; i++) { \
if (term.normal.rows[i] == NULL) { \
struct row *r = xcalloc(1, sizeof(*term.normal.rows[i])); \
r->cells = xcalloc(cols, sizeof(r->cells[0])); \
term.normal.rows[i] = r; \
} \
} \
} while (0)
/*
* Test case 1 - no selection, just verify all rows except those
* on screen have been deleted.
*/
populate_scrollback();
term.normal.offset = 11;
term_erase_scrollback(&term);
for (int i = 0; i < scrollback_rows; i++) {
if (i >= term.normal.offset && i < term.normal.offset + term_rows)
xassert(term.normal.rows[i] != NULL);
else
xassert(term.normal.rows[i] == NULL);
}
/*
* Test case 2 - selection that touches the scrollback. Verify the
* selection is cancelled.
*/
term.normal.offset = 14; /* Screen covers rows 14,15,0,1,2 */
/* Selection covers rows 15,0,1,2,3 */
term.selection.start = (struct coord){.row = 15};
term.selection.end = (struct coord){.row = 19};
term.selection.kind = SELECTION_CHAR_WISE;
populate_scrollback();
term_erase_scrollback(&term);
xassert(term.selection.start.row < 0);
xassert(term.selection.end.row < 0);
xassert(term.selection.kind == SELECTION_NONE);
/*
* Test case 3 - selection that does *not* touch the
* scrollback. Verify the selection is *not* cancelled.
*/
/* Selection covers rows 15,0 */
term.selection.start = (struct coord){.row = 15};
term.selection.end = (struct coord){.row = 16};
term.selection.kind = SELECTION_CHAR_WISE;
populate_scrollback();
term_erase_scrollback(&term);
xassert(term.selection.start.row == 15);
xassert(term.selection.end.row == 16);
xassert(term.selection.kind == SELECTION_CHAR_WISE);
term.selection.start = (struct coord){-1, -1};
term.selection.end = (struct coord){-1, -1};
term.selection.kind = SELECTION_NONE;
/*
* Test case 4 - sixel that touch the scrollback
*/
struct sixel six = {
.rows = 5,
.pos = {
.row = 15,
},
};
tll_push_back(term.normal.sixel_images, six);
populate_scrollback();
term_erase_scrollback(&term);
xassert(tll_length(term.normal.sixel_images) == 0);
/*
* Test case 5 - sixel that does *not* touch the scrollback
*/
six.rows = 3;
tll_push_back(term.normal.sixel_images, six);
populate_scrollback();
term_erase_scrollback(&term);
xassert(tll_length(term.normal.sixel_images) == 1);
/* Cleanup */
tll_free(term.normal.sixel_images);
close(term.selection.auto_scroll.fd);
for (int i = 0; i < scrollback_rows; i++)
grid_row_free(term.normal.rows[i]);
free(term.normal.rows);
fdm_destroy(fdm);
}
int
term_row_rel_to_abs(const struct terminal *term, int row)
{
@ -2634,9 +2853,13 @@ term_xcursor_update(struct terminal *term)
void
term_set_window_title(struct terminal *term, const char *title)
{
if (term->conf->locked_title && term->window_title_has_been_set)
return;
free(term->window_title);
term->window_title = xstrdup(title);
render_refresh_title(term);
term->window_title_has_been_set = true;
}
void
@ -2756,6 +2979,7 @@ print_linewrap(struct terminal *term)
return;
}
term->grid->cur_row->linebreak = false;
term->grid->cursor.lcf = false;
const int row = term->grid->cursor.point.row;
@ -2848,7 +3072,7 @@ term_print(struct terminal *term, wchar_t wc, int width)
cell->attrs = term->vt.attrs;
row->dirty = true;
row->linebreak = false;
row->linebreak = true;
/* Advance cursor the 'additional' columns while dirty:ing the cells */
for (int i = 1; i < width && term->grid->cursor.point.col < term->cols - 1; i++) {
@ -2887,7 +3111,7 @@ ascii_printer_fast(struct terminal *term, wchar_t wc)
cell->attrs = term->vt.attrs;
row->dirty = true;
row->linebreak = false;
row->linebreak = true;
/* Advance cursor */
if (unlikely(++term->grid->cursor.point.col >= term->cols)) {

View file

@ -21,6 +21,7 @@
#include "fdm.h"
#include "macros.h"
#include "reaper.h"
#include "shm.h"
#include "wayland.h"
/*
@ -42,11 +43,12 @@ struct attributes {
uint32_t fg:24;
bool clean:1;
bool confined:1;
bool have_fg:1;
bool have_bg:1;
uint32_t selected:2;
bool url:1;
uint32_t reserved:2;
uint32_t reserved:1;
uint32_t bg:24;
};
static_assert(sizeof(struct attributes) == 8, "VT attribute struct too large");
@ -264,6 +266,7 @@ struct url {
enum url_action action;
bool url_mode_dont_change_url_attr; /* Entering/exiting URL mode doesnt touch the cells attr.url */
bool osc8;
bool duplicate;
};
typedef tll(struct url) url_list_t;
@ -377,11 +380,12 @@ struct terminal {
bool ime:1;
bool app_sync_updates:1;
bool sixel_scrolling:1;
bool sixel_display_mode:1;
bool sixel_private_palette:1;
bool sixel_cursor_right_of_graphics:1;
} xtsave;
bool window_title_has_been_set;
char *window_title;
tll(char *) window_title_stack;
@ -472,6 +476,15 @@ struct terminal {
enum term_surface active_surface;
struct {
struct {
struct buffer_chain *grid;
struct buffer_chain *search;
struct buffer_chain *scrollback_indicator;
struct buffer_chain *render_timer;
struct buffer_chain *url;
struct buffer_chain *csd;
} chains;
/* Scheduled for rendering, as soon-as-possible */
struct {
bool grid;
@ -649,6 +662,7 @@ void term_damage_scroll(
void term_erase(
struct terminal *term, const struct coord *start, const struct coord *end);
void term_erase_scrollback(struct terminal *term);
int term_row_rel_to_abs(const struct terminal *term, int row);
void term_cursor_home(struct terminal *term);

View file

@ -469,16 +469,20 @@ remove_overlapping(url_list_t *urls, int cols)
*/
xassert(in->osc8 || out->osc8);
if (in->osc8) {
url_destroy(&outer->item);
tll_remove(*urls, outer);
} else {
url_destroy(&inner->item);
tll_remove(*urls, inner);
}
if (in->osc8)
outer->item.duplicate = true;
else
inner->item.duplicate = true;
}
}
}
tll_foreach(*urls, it) {
if (it->item.duplicate) {
url_destroy(&it->item);
tll_remove(*urls, it);
}
}
}
void
@ -649,6 +653,11 @@ tag_cells_for_url(struct terminal *term, const struct url *url, bool value)
c = 0;
row = term->grid->rows[r];
if (row == NULL) {
/* Un-allocated scrollback. This most likely means a
* runaway OSC-8 URL. */
break;
}
row->dirty = true;
}
}

23
vt.c
View file

@ -412,6 +412,29 @@ action_collect(struct terminal *term, uint8_t c)
LOG_WARN("only four private/intermediate characters supported");
}
UNITTEST
{
struct terminal term = {.vt = {.private = 0}};
uint32_t expected = ' ';
action_collect(&term, ' ');
xassert(term.vt.private == expected);
expected |= '/' << 8;
action_collect(&term, '/');
xassert(term.vt.private == expected);
expected |= '<' << 16;
action_collect(&term, '<');
xassert(term.vt.private == expected);
expected |= '?' << 24;
action_collect(&term, '?');
xassert(term.vt.private == expected);
action_collect(&term, '?');
xassert(term.vt.private == expected);
}
static void
action_esc_dispatch(struct terminal *term, uint8_t final)
{

View file

@ -26,6 +26,7 @@
#include "input.h"
#include "render.h"
#include "selection.h"
#include "shm.h"
#include "util.h"
#include "xmalloc.h"
@ -50,8 +51,11 @@ csd_instantiate(struct wl_window *win)
static void
csd_destroy(struct wl_window *win)
{
struct terminal *term = win->term;
for (size_t i = 0; i < ALEN(win->csd.surface); i++)
wayl_win_subsurface_destroy(&win->csd.surface[i]);
shm_purge(term->render.chains.csd);
}
static void
@ -1410,6 +1414,8 @@ wayl_win_destroy(struct wl_window *win)
if (win == NULL)
return;
struct terminal *term = win->term;
if (win->csd.move_timeout_fd != -1)
close(win->csd.move_timeout_fd);
@ -1438,6 +1444,12 @@ wayl_win_destroy(struct wl_window *win)
wl_surface_commit(win->search.surf);
}
/* URLs */
tll_foreach(win->urls, it) {
wl_surface_attach(it->item.surf.surf, NULL, 0, 0);
wl_surface_commit(it->item.surf.surf);
}
/* CSD */
for (size_t i = 0; i < ALEN(win->csd.surface); i++) {
if (win->csd.surface[i].surf != NULL) {
@ -1465,6 +1477,13 @@ wayl_win_destroy(struct wl_window *win)
wayl_win_subsurface_destroy(&win->scrollback_indicator);
wayl_win_subsurface_destroy(&win->render_timer);
shm_purge(term->render.chains.search);
shm_purge(term->render.chains.scrollback_indicator);
shm_purge(term->render.chains.render_timer);
shm_purge(term->render.chains.grid);
shm_purge(term->render.chains.url);
shm_purge(term->render.chains.csd);
#if defined(HAVE_XDG_ACTIVATION)
if (win->xdg_activation_token != NULL)
xdg_activation_token_v1_destroy(win->xdg_activation_token);