2019-07-05 10:16:56 +02:00
|
|
|
#include "render.h"
|
|
|
|
|
|
|
|
|
|
#include <string.h>
|
2019-08-01 20:09:16 +02:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
#include <sys/ioctl.h>
|
2019-07-18 09:33:49 +02:00
|
|
|
#include <sys/time.h>
|
2019-07-21 20:11:20 +02:00
|
|
|
#include <sys/timerfd.h>
|
2019-08-01 20:09:16 +02:00
|
|
|
#include <sys/prctl.h>
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-07-05 10:44:09 +02:00
|
|
|
#include <wayland-cursor.h>
|
2019-07-05 10:16:56 +02:00
|
|
|
#include <xdg-shell.h>
|
|
|
|
|
|
|
|
|
|
#define LOG_MODULE "render"
|
|
|
|
|
#define LOG_ENABLE_DBG 0
|
|
|
|
|
#include "log.h"
|
|
|
|
|
#include "shm.h"
|
2019-07-08 13:57:31 +02:00
|
|
|
#include "grid.h"
|
2019-07-28 20:37:59 +02:00
|
|
|
#include "font.h"
|
2019-07-05 10:16:56 +02:00
|
|
|
|
|
|
|
|
#define min(x, y) ((x) < (y) ? (x) : (y))
|
|
|
|
|
#define max(x, y) ((x) > (y) ? (x) : (y))
|
|
|
|
|
|
2019-07-18 10:33:58 +02:00
|
|
|
struct font *
|
2019-07-05 10:16:56 +02:00
|
|
|
attrs_to_font(struct terminal *term, const struct attributes *attrs)
|
|
|
|
|
{
|
|
|
|
|
int idx = attrs->italic << 1 | attrs->bold;
|
2019-10-16 21:52:12 +02:00
|
|
|
return term->fonts[idx];
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-18 10:04:13 +02:00
|
|
|
static inline struct rgb
|
|
|
|
|
color_hex_to_rgb(uint32_t color)
|
|
|
|
|
{
|
|
|
|
|
return (struct rgb){
|
|
|
|
|
((color >> 16) & 0xff) / 255.,
|
|
|
|
|
((color >> 8) & 0xff) / 255.,
|
|
|
|
|
((color >> 0) & 0xff) / 255.,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-16 20:40:32 +02:00
|
|
|
static inline pixman_color_t
|
2019-08-16 22:06:06 +02:00
|
|
|
color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha)
|
2019-08-16 20:40:32 +02:00
|
|
|
{
|
2019-08-16 22:06:06 +02:00
|
|
|
int alpha_div = 0xffff / alpha;
|
2019-08-16 20:40:32 +02:00
|
|
|
return (pixman_color_t){
|
2019-08-16 22:06:06 +02:00
|
|
|
.red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)) / alpha_div,
|
|
|
|
|
.green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)) / alpha_div,
|
|
|
|
|
.blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)) / alpha_div,
|
|
|
|
|
.alpha = alpha,
|
2019-08-16 20:40:32 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-16 22:06:06 +02:00
|
|
|
static inline pixman_color_t
|
|
|
|
|
color_hex_to_pixman(uint32_t color)
|
|
|
|
|
{
|
|
|
|
|
/* Count on the compiler optimizing this */
|
|
|
|
|
return color_hex_to_pixman_with_alpha(color, 0xffff);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-18 10:04:13 +02:00
|
|
|
static inline void
|
|
|
|
|
color_dim(struct rgb *rgb)
|
|
|
|
|
{
|
|
|
|
|
rgb->r /= 2.;
|
|
|
|
|
rgb->g /= 2.;
|
|
|
|
|
rgb->b /= 2.;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-16 20:40:32 +02:00
|
|
|
static inline void
|
|
|
|
|
pixman_color_dim(pixman_color_t *color)
|
|
|
|
|
{
|
|
|
|
|
color->red /= 2;
|
|
|
|
|
color->green /= 2;
|
|
|
|
|
color->blue /= 2;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 19:33:25 +02:00
|
|
|
static inline void
|
|
|
|
|
pixman_color_dim_for_search(pixman_color_t *color)
|
|
|
|
|
{
|
|
|
|
|
color->red /= 3;
|
|
|
|
|
color->green /= 3;
|
|
|
|
|
color->blue /= 3;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 20:39:22 +02:00
|
|
|
static inline int
|
|
|
|
|
font_baseline(const struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
assert(term->fextents.ascent >= 0);
|
|
|
|
|
assert(term->fextents.descent >= 0);
|
|
|
|
|
|
|
|
|
|
int diff = term->fextents.height - (term->fextents.ascent + term->fextents.descent);
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
LOG_INFO("height=%d, ascent=%d, descent=%d, diff=%d",
|
|
|
|
|
term->fextents.height,
|
|
|
|
|
term->fextents.ascent, term->fextents.descent,
|
|
|
|
|
diff);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
return term->fextents.height - diff / 2 - term->fextents.descent;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-08 13:57:31 +02:00
|
|
|
static void
|
2019-08-16 22:06:06 +02:00
|
|
|
draw_bar(const struct terminal *term, pixman_image_t *pix,
|
2019-08-30 19:42:33 +02:00
|
|
|
const struct font *font,
|
2019-08-16 22:06:06 +02:00
|
|
|
const pixman_color_t *color, int x, int y)
|
2019-07-08 13:57:31 +02:00
|
|
|
{
|
2019-08-29 20:41:40 +02:00
|
|
|
int baseline = y + font_baseline(term) - term->fextents.ascent;
|
2019-08-16 22:06:06 +02:00
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, color,
|
2019-08-29 20:41:40 +02:00
|
|
|
1, &(pixman_rectangle16_t){
|
|
|
|
|
x, baseline,
|
2019-08-30 19:42:33 +02:00
|
|
|
font->underline.thickness, term->fextents.ascent + term->fextents.descent});
|
2019-07-22 20:07:34 +02:00
|
|
|
}
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-07-22 20:07:34 +02:00
|
|
|
static void
|
2019-08-16 22:06:06 +02:00
|
|
|
draw_underline(const struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
const struct font *font,
|
|
|
|
|
const pixman_color_t *color, int x, int y, int cols)
|
2019-07-22 20:07:34 +02:00
|
|
|
{
|
2019-08-29 20:42:45 +02:00
|
|
|
int baseline = y + font_baseline(term);
|
2019-08-16 22:06:06 +02:00
|
|
|
int width = font->underline.thickness;
|
|
|
|
|
int y_under = baseline - font->underline.position - width / 2;
|
|
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, color,
|
|
|
|
|
1, &(pixman_rectangle16_t){x, y_under, cols * term->cell_width, width});
|
2019-07-22 20:07:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2019-08-16 22:06:06 +02:00
|
|
|
draw_strikeout(const struct terminal *term, pixman_image_t *pix,
|
|
|
|
|
const struct font *font,
|
|
|
|
|
const pixman_color_t *color, int x, int y, int cols)
|
2019-07-22 20:07:34 +02:00
|
|
|
{
|
2019-08-29 20:42:57 +02:00
|
|
|
int baseline = y + font_baseline(term);
|
2019-08-16 22:06:06 +02:00
|
|
|
int width = font->strikeout.thickness;
|
|
|
|
|
int y_strike = baseline - font->strikeout.position - width / 2;
|
|
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, color,
|
|
|
|
|
1, &(pixman_rectangle16_t){x, y_strike, cols * term->cell_width, width});
|
2019-07-22 20:07:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
coord_is_selected(const struct terminal *term, int col, int row)
|
|
|
|
|
{
|
2019-08-05 20:15:18 +02:00
|
|
|
if (term->selection.start.col == -1 || term->selection.end.col == -1)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const struct coord *start = &term->selection.start;
|
|
|
|
|
const struct coord *end = &term->selection.end;
|
|
|
|
|
|
2019-08-08 17:58:06 +02:00
|
|
|
if (start->row > end->row || (start->row == end->row && start->col > end->col)) {
|
|
|
|
|
const struct coord *tmp = start;
|
|
|
|
|
start = end;
|
|
|
|
|
end = tmp;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-05 20:15:18 +02:00
|
|
|
assert(start->row <= end->row);
|
2019-07-10 20:57:09 +02:00
|
|
|
|
2019-08-05 20:15:18 +02:00
|
|
|
row += term->grid->view;
|
|
|
|
|
|
|
|
|
|
if (start->row == end->row) {
|
|
|
|
|
return row == start->row && col >= start->col && col <= end->col;
|
|
|
|
|
} else {
|
|
|
|
|
if (row == start->row)
|
|
|
|
|
return col >= start->col;
|
|
|
|
|
else if (row == end->row)
|
|
|
|
|
return col <= end->col;
|
|
|
|
|
else
|
|
|
|
|
return row >= start->row && row <= end->row;
|
|
|
|
|
}
|
2019-07-22 20:07:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
arm_blink_timer(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
LOG_DBG("arming blink timer");
|
|
|
|
|
struct itimerspec alarm = {
|
|
|
|
|
.it_value = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
|
|
|
|
|
.it_interval = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (timerfd_settime(term->blink.fd, 0, &alarm, NULL) < 0)
|
|
|
|
|
LOG_ERRNO("failed to arm blink timer");
|
|
|
|
|
else
|
|
|
|
|
term->blink.active = true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
static int
|
2019-08-16 22:06:06 +02:00
|
|
|
render_cell(struct terminal *term, pixman_image_t *pix,
|
2019-07-30 18:03:03 +02:00
|
|
|
struct cell *cell, int col, int row, bool has_cursor)
|
2019-07-22 20:07:34 +02:00
|
|
|
{
|
2019-07-30 18:03:03 +02:00
|
|
|
if (cell->attrs.clean)
|
2019-08-01 20:09:39 +02:00
|
|
|
return 0;
|
2019-07-30 18:03:03 +02:00
|
|
|
|
|
|
|
|
cell->attrs.clean = 1;
|
|
|
|
|
|
2019-08-16 22:06:06 +02:00
|
|
|
int width = term->cell_width;
|
|
|
|
|
int height = term->cell_height;
|
2019-08-27 15:25:35 +02:00
|
|
|
int x = term->x_margin + col * width;
|
|
|
|
|
int y = term->y_margin + row * height;
|
2019-07-22 20:07:34 +02:00
|
|
|
|
|
|
|
|
bool block_cursor = has_cursor && term->cursor_style == CURSOR_BLOCK;
|
|
|
|
|
bool is_selected = coord_is_selected(term, col, row);
|
|
|
|
|
|
2019-08-02 18:19:07 +02:00
|
|
|
uint32_t _fg = cell->attrs.have_fg
|
|
|
|
|
? cell->attrs.fg
|
2019-07-21 10:58:09 +02:00
|
|
|
: !term->reverse ? term->colors.fg : term->colors.bg;
|
2019-08-02 18:19:07 +02:00
|
|
|
uint32_t _bg = cell->attrs.have_bg
|
|
|
|
|
? cell->attrs.bg
|
2019-07-21 10:58:09 +02:00
|
|
|
: !term->reverse ? term->colors.bg : term->colors.fg;
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-07-08 15:56:15 +02:00
|
|
|
/* If *one* is set, we reverse */
|
2019-07-22 20:07:34 +02:00
|
|
|
if (block_cursor ^ cell->attrs.reverse ^ is_selected) {
|
2019-07-16 19:52:45 +02:00
|
|
|
uint32_t swap = _fg;
|
|
|
|
|
_fg = _bg;
|
|
|
|
|
_bg = swap;
|
2019-07-08 13:57:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-22 19:17:57 +02:00
|
|
|
if (cell->attrs.blink && term->blink.state == BLINK_OFF)
|
2019-07-21 20:11:20 +02:00
|
|
|
_fg = _bg;
|
|
|
|
|
|
2019-08-16 20:40:32 +02:00
|
|
|
pixman_color_t fg = color_hex_to_pixman(_fg);
|
2019-08-16 22:06:06 +02:00
|
|
|
pixman_color_t bg = color_hex_to_pixman_with_alpha(
|
|
|
|
|
_bg, block_cursor ? 0xffff : term->colors.alpha);
|
2019-07-16 13:17:51 +02:00
|
|
|
|
2019-07-18 10:04:13 +02:00
|
|
|
if (cell->attrs.dim)
|
2019-08-16 20:40:32 +02:00
|
|
|
pixman_color_dim(&fg);
|
2019-07-16 14:20:39 +02:00
|
|
|
|
2019-07-23 18:54:58 +02:00
|
|
|
if (block_cursor && term->cursor_color.text >> 31) {
|
|
|
|
|
/* User configured cursor color overrides all attributes */
|
|
|
|
|
assert(term->cursor_color.cursor >> 31);
|
2019-08-16 20:40:32 +02:00
|
|
|
fg = color_hex_to_pixman(term->cursor_color.text);
|
|
|
|
|
bg = color_hex_to_pixman(term->cursor_color.cursor);
|
2019-07-23 18:54:58 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-27 19:40:07 +02:00
|
|
|
if (term->is_searching && !is_selected) {
|
2019-08-29 19:33:25 +02:00
|
|
|
pixman_color_dim_for_search(&fg);
|
|
|
|
|
pixman_color_dim_for_search(&bg);
|
2019-08-27 17:23:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-31 21:15:40 +02:00
|
|
|
struct font *font = attrs_to_font(term, &cell->attrs);
|
2019-08-02 18:19:07 +02:00
|
|
|
const struct glyph *glyph = font_glyph_for_wc(font, cell->wc);
|
2019-07-31 21:15:40 +02:00
|
|
|
|
2019-08-16 20:40:32 +02:00
|
|
|
int cell_cols = glyph != NULL ? max(1, glyph->cols) : 1;
|
2019-07-31 21:15:40 +02:00
|
|
|
|
2019-07-08 13:57:31 +02:00
|
|
|
/* Background */
|
2019-08-16 20:40:32 +02:00
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, &bg, 1,
|
|
|
|
|
&(pixman_rectangle16_t){x, y, cell_cols * width, height});
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-07-22 20:07:34 +02:00
|
|
|
/* Non-block cursors */
|
2019-08-17 17:43:47 +02:00
|
|
|
if (has_cursor && !block_cursor) {
|
2019-08-27 17:23:28 +02:00
|
|
|
pixman_color_t cursor_color;
|
|
|
|
|
if (term->cursor_color.text >> 31) {
|
|
|
|
|
cursor_color = color_hex_to_pixman(term->cursor_color.cursor);
|
|
|
|
|
if (term->is_searching)
|
|
|
|
|
pixman_color_dim(&cursor_color);
|
|
|
|
|
} else
|
|
|
|
|
cursor_color = fg;
|
2019-08-12 20:00:28 +02:00
|
|
|
|
2019-07-22 20:07:34 +02:00
|
|
|
if (term->cursor_style == CURSOR_BAR)
|
2019-08-30 19:42:33 +02:00
|
|
|
draw_bar(term, pix, font, &cursor_color, x, y);
|
2019-07-22 20:07:34 +02:00
|
|
|
else if (term->cursor_style == CURSOR_UNDERLINE)
|
|
|
|
|
draw_underline(
|
2019-08-16 22:06:06 +02:00
|
|
|
term, pix, attrs_to_font(term, &cell->attrs), &cursor_color,
|
|
|
|
|
x, y, cell_cols);
|
2019-07-22 20:07:34 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-22 19:17:57 +02:00
|
|
|
if (cell->attrs.blink && !term->blink.active) {
|
2019-07-21 20:11:20 +02:00
|
|
|
/* First cell we see that has blink set - arm blink timer */
|
2019-07-22 20:07:34 +02:00
|
|
|
arm_blink_timer(term);
|
2019-07-21 20:11:20 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-02 18:19:07 +02:00
|
|
|
if (cell->wc == 0 || cell->attrs.conceal)
|
2019-08-01 20:09:39 +02:00
|
|
|
return cell_cols;
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-07-31 21:15:40 +02:00
|
|
|
if (glyph != NULL) {
|
2019-08-18 18:11:38 +02:00
|
|
|
if (unlikely(pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)) {
|
2019-07-31 21:15:40 +02:00
|
|
|
/* Glyph surface is a pre-rendered image (typically a color emoji...) */
|
|
|
|
|
if (!(cell->attrs.blink && term->blink.state == BLINK_OFF)) {
|
2019-08-17 17:36:27 +02:00
|
|
|
pixman_image_composite32(
|
2019-08-16 20:40:32 +02:00
|
|
|
PIXMAN_OP_OVER, glyph->pix, NULL, pix, 0, 0, 0, 0,
|
|
|
|
|
x + glyph->x, y + term->fextents.ascent - glyph->y,
|
|
|
|
|
glyph->width, glyph->height);
|
2019-07-31 21:15:40 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* Glyph surface is an alpha mask */
|
2019-08-16 20:40:32 +02:00
|
|
|
/* TODO: cache */
|
|
|
|
|
pixman_image_t *src = pixman_image_create_solid_fill(&fg);
|
2019-08-17 17:36:27 +02:00
|
|
|
pixman_image_composite32(
|
2019-08-16 20:40:32 +02:00
|
|
|
PIXMAN_OP_OVER, src, glyph->pix, pix, 0, 0, 0, 0,
|
|
|
|
|
x + glyph->x, y + term->fextents.ascent - glyph->y,
|
|
|
|
|
glyph->width, glyph->height);
|
|
|
|
|
pixman_image_unref(src);
|
2019-07-31 21:15:40 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-16 14:20:39 +02:00
|
|
|
/* Underline */
|
2019-08-16 22:06:06 +02:00
|
|
|
if (cell->attrs.underline) {
|
|
|
|
|
draw_underline(term, pix, attrs_to_font(term, &cell->attrs),
|
2019-08-18 18:11:38 +02:00
|
|
|
&fg, x, y, cell_cols);
|
2019-08-16 22:06:06 +02:00
|
|
|
}
|
2019-07-16 15:08:02 +02:00
|
|
|
|
2019-08-16 22:06:06 +02:00
|
|
|
if (cell->attrs.strikethrough) {
|
|
|
|
|
draw_strikeout(term, pix, attrs_to_font(term, &cell->attrs),
|
2019-08-18 18:11:38 +02:00
|
|
|
&fg, x, y, cell_cols);
|
2019-08-16 22:06:06 +02:00
|
|
|
}
|
2019-08-01 20:09:39 +02:00
|
|
|
|
|
|
|
|
return cell_cols;
|
2019-07-08 13:57:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
static void
|
|
|
|
|
grid_render_scroll(struct terminal *term, struct buffer *buf,
|
|
|
|
|
const struct damage *dmg)
|
|
|
|
|
{
|
2019-08-27 15:25:35 +02:00
|
|
|
int dst_y = term->y_margin + (dmg->scroll.region.start + 0) * term->cell_height;
|
|
|
|
|
int src_y = term->y_margin + (dmg->scroll.region.start + dmg->scroll.lines) * term->cell_height;
|
2019-07-05 10:16:56 +02:00
|
|
|
int height = (dmg->scroll.region.end - dmg->scroll.region.start - dmg->scroll.lines) * term->cell_height;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("damage: SCROLL: %d-%d by %d lines (dst-y: %d, src-y: %d, "
|
|
|
|
|
"height: %d, stride: %d, mmap-size: %zu)",
|
|
|
|
|
dmg->scroll.region.start, dmg->scroll.region.end,
|
|
|
|
|
dmg->scroll.lines,
|
2019-08-16 20:40:32 +02:00
|
|
|
dst_y, src_y, height, buf->stride,
|
2019-07-05 10:16:56 +02:00
|
|
|
buf->size);
|
|
|
|
|
|
|
|
|
|
if (height > 0) {
|
2019-08-16 20:40:32 +02:00
|
|
|
uint8_t *raw = buf->mmapped;
|
|
|
|
|
memmove(raw + dst_y * buf->stride,
|
|
|
|
|
raw + src_y * buf->stride,
|
|
|
|
|
height * buf->stride);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-10-27 12:57:37 +01:00
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface, term->x_margin, dst_y, term->width - term->x_margin, height);
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
grid_render_scroll_reverse(struct terminal *term, struct buffer *buf,
|
|
|
|
|
const struct damage *dmg)
|
|
|
|
|
{
|
2019-08-27 15:25:35 +02:00
|
|
|
int src_y = term->y_margin + (dmg->scroll.region.start + 0) * term->cell_height;
|
|
|
|
|
int dst_y = term->y_margin + (dmg->scroll.region.start + dmg->scroll.lines) * term->cell_height;
|
2019-07-05 10:16:56 +02:00
|
|
|
int height = (dmg->scroll.region.end - dmg->scroll.region.start - dmg->scroll.lines) * term->cell_height;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("damage: SCROLL REVERSE: %d-%d by %d lines (dst-y: %d, src-y: %d, "
|
|
|
|
|
"height: %d, stride: %d, mmap-size: %zu)",
|
|
|
|
|
dmg->scroll.region.start, dmg->scroll.region.end,
|
|
|
|
|
dmg->scroll.lines,
|
2019-08-16 20:40:32 +02:00
|
|
|
dst_y, src_y, height, buf->stride,
|
2019-07-05 10:16:56 +02:00
|
|
|
buf->size);
|
|
|
|
|
|
|
|
|
|
if (height > 0) {
|
2019-08-16 20:40:32 +02:00
|
|
|
uint8_t *raw = buf->mmapped;
|
|
|
|
|
memmove(raw + dst_y * buf->stride,
|
|
|
|
|
raw + src_y * buf->stride,
|
|
|
|
|
height * buf->stride);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-10-27 12:57:37 +01:00
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface, term->x_margin, dst_y, term->width - term->x_margin, height);
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 20:31:21 +02:00
|
|
|
static void
|
2019-08-16 22:06:06 +02:00
|
|
|
render_row(struct terminal *term, pixman_image_t *pix, struct row *row, int row_no)
|
2019-07-24 20:31:21 +02:00
|
|
|
{
|
font: initial support for double-width *and* color emoji glyphs
Fonts are now loaded with FT_LOAD_COLOR and we recognize and support
the FT_PIXEL_MODE_BGRA pixel mode.
This is mapped to a CAIRO_FORMAT_ARGB32 surface, that is blitted
as-is (instead of used as a mask like we do for gray and mono glyphs).
Furthermore, since many emojis are double-width, we add initial
support for double-width glyphs.
These are assumed to always be utf8. When PRINT:ing an utf8 character,
we check its width, and add empty "spacer" cells after the cell with
the multi-column glyph.
When rendering, we render the columns in each row backwards. This
ensures the spacer cells get cleared *before* we render the glyph (so
that we don't end up erasing part of the glyph).
Finally, emoji fonts are usually bitmap fonts with *large*
glyphs. These aren't automatically scaled down. I.e. even if we
request a glyph of 13 pixels, we might end up getting a 100px glyph.
To handle this, fontconfig must be configured to scale bitmap
fonts. When it is, we can look at the 'scalable' and 'pixelsizefixup'
properties, and use these to scale the rendered glyph.
2019-07-31 18:03:35 +02:00
|
|
|
for (int col = term->cols - 1; col >= 0; col--)
|
2019-08-16 22:06:06 +02:00
|
|
|
render_cell(term, pix, &row->cells[col], col, row_no, false);
|
2019-07-29 20:13:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
render_worker_thread(void *_ctx)
|
|
|
|
|
{
|
|
|
|
|
struct render_worker_context *ctx = _ctx;
|
|
|
|
|
struct terminal *term = ctx->term;
|
|
|
|
|
const int my_id = ctx->my_id;
|
2019-10-28 18:48:43 +01:00
|
|
|
free(ctx);
|
2019-07-29 20:13:26 +02:00
|
|
|
|
2019-08-01 20:09:16 +02:00
|
|
|
char proc_title[16];
|
|
|
|
|
snprintf(proc_title, sizeof(proc_title), "foot:render:%d", my_id);
|
|
|
|
|
|
|
|
|
|
if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
|
|
|
|
|
LOG_ERRNO("render worker %d: failed to set process title", my_id);
|
|
|
|
|
|
2019-07-29 20:13:26 +02:00
|
|
|
sem_t *start = &term->render.workers.start;
|
|
|
|
|
sem_t *done = &term->render.workers.done;
|
|
|
|
|
mtx_t *lock = &term->render.workers.lock;
|
|
|
|
|
cnd_t *cond = &term->render.workers.cond;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
sem_wait(start);
|
|
|
|
|
|
|
|
|
|
struct buffer *buf = term->render.workers.buf;
|
|
|
|
|
bool frame_done = false;
|
|
|
|
|
|
|
|
|
|
while (!frame_done) {
|
|
|
|
|
mtx_lock(lock);
|
|
|
|
|
while (tll_length(term->render.workers.queue) == 0)
|
|
|
|
|
cnd_wait(cond, lock);
|
|
|
|
|
|
|
|
|
|
int row_no = tll_pop_front(term->render.workers.queue);
|
|
|
|
|
mtx_unlock(lock);
|
|
|
|
|
|
|
|
|
|
switch (row_no) {
|
|
|
|
|
default:
|
|
|
|
|
assert(buf != NULL);
|
2019-08-16 22:54:05 +02:00
|
|
|
render_row(term, buf->pix, grid_row_in_view(term->grid, row_no), row_no);
|
2019-07-29 20:13:26 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case -1:
|
|
|
|
|
frame_done = true;
|
|
|
|
|
sem_post(done);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case -2:
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return -1;
|
2019-07-24 20:31:21 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
static void frame_callback(
|
|
|
|
|
void *data, struct wl_callback *wl_callback, uint32_t callback_data);
|
|
|
|
|
|
|
|
|
|
static const struct wl_callback_listener frame_listener = {
|
|
|
|
|
.done = &frame_callback,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
grid_render(struct terminal *term)
|
|
|
|
|
{
|
2019-07-18 10:35:27 +02:00
|
|
|
#define TIME_FRAME_RENDERING 0
|
|
|
|
|
|
|
|
|
|
#if TIME_FRAME_RENDERING
|
2019-07-18 09:33:49 +02:00
|
|
|
struct timeval start_time;
|
|
|
|
|
gettimeofday(&start_time, NULL);
|
2019-07-18 10:35:27 +02:00
|
|
|
#endif
|
2019-07-18 09:33:49 +02:00
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
assert(term->width > 0);
|
|
|
|
|
assert(term->height > 0);
|
|
|
|
|
|
2019-11-02 00:33:37 +01:00
|
|
|
unsigned long cookie = (uintptr_t)term;
|
|
|
|
|
struct buffer *buf = shm_get_buffer(
|
|
|
|
|
term->wl->shm, term->width, term->height, cookie);
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-11-02 00:33:37 +01:00
|
|
|
pixman_image_t *pix = buf->pix;
|
2019-07-24 18:15:24 +02:00
|
|
|
bool all_clean = tll_length(term->grid->scroll_damage) == 0;
|
|
|
|
|
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
/* If we resized the window, or is flashing, or just stopped flashing */
|
2019-11-02 01:28:29 +01:00
|
|
|
if (term->render.last_buf != buf ||
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
term->flash.active || term->render.was_flashing ||
|
|
|
|
|
term->is_searching != term->render.was_searching)
|
|
|
|
|
{
|
2019-11-02 01:28:29 +01:00
|
|
|
if (term->render.last_buf != NULL &&
|
|
|
|
|
term->render.last_buf->width == buf->width &&
|
|
|
|
|
term->render.last_buf->height == buf->height &&
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
!term->flash.active &&
|
|
|
|
|
!term->render.was_flashing &&
|
|
|
|
|
term->is_searching == term->render.was_searching)
|
|
|
|
|
{
|
|
|
|
|
static bool has_warned = false;
|
|
|
|
|
if (!has_warned) {
|
2019-11-02 01:28:29 +01:00
|
|
|
LOG_WARN("it appears your Wayland compositor does not support buffer re-use for SHM clients; expect lower performance.");
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
has_warned = true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-02 01:28:29 +01:00
|
|
|
assert(term->render.last_buf->size == buf->size);
|
|
|
|
|
memcpy(buf->mmapped, term->render.last_buf->mmapped, buf->size);
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
/* Fill area outside the cell grid with the default background color */
|
|
|
|
|
int rmargin = term->x_margin + term->cols * term->cell_width;
|
|
|
|
|
int bmargin = term->y_margin + term->rows * term->cell_height;
|
|
|
|
|
int rmargin_width = term->width - rmargin;
|
|
|
|
|
int bmargin_height = term->height - bmargin;
|
|
|
|
|
|
|
|
|
|
uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
|
|
|
|
|
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha);
|
|
|
|
|
if (term->is_searching)
|
|
|
|
|
pixman_color_dim(&bg);
|
|
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, pix, &bg, 4,
|
|
|
|
|
(pixman_rectangle16_t[]){
|
|
|
|
|
{0, 0, term->width, term->y_margin}, /* Top */
|
|
|
|
|
{0, 0, term->x_margin, term->height}, /* Left */
|
|
|
|
|
{rmargin, 0, rmargin_width, term->height}, /* Right */
|
|
|
|
|
{0, bmargin, term->width, bmargin_height}}); /* Bottom */
|
|
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface, 0, 0, term->width, term->y_margin);
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface, 0, 0, term->x_margin, term->height);
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface, rmargin, 0, rmargin_width, term->height);
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface, 0, bmargin, term->width, bmargin_height);
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
|
|
|
|
|
/* Force a full grid refresh */
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-02 01:28:29 +01:00
|
|
|
term->render.last_buf = buf;
|
render: handle compositors that does buffer swapping
Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.
Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.
Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.
In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).
Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.
Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.
This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
2019-09-27 19:33:45 +02:00
|
|
|
term->render.was_flashing = term->flash.active;
|
|
|
|
|
term->render.was_searching = term->is_searching;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 18:15:24 +02:00
|
|
|
/* Erase old cursor (if we rendered a cursor last time) */
|
2019-07-24 20:21:41 +02:00
|
|
|
if (term->render.last_cursor.cell != NULL) {
|
2019-07-30 20:18:58 +02:00
|
|
|
struct cell *cell = term->render.last_cursor.cell;
|
|
|
|
|
struct coord at = term->render.last_cursor.in_view;
|
|
|
|
|
|
|
|
|
|
if (cell->attrs.clean) {
|
|
|
|
|
cell->attrs.clean = 0;
|
2019-08-16 22:06:06 +02:00
|
|
|
render_cell(term, pix, cell, at.col, at.row, false);
|
2019-07-30 20:18:58 +02:00
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface,
|
2019-08-27 15:25:35 +02:00
|
|
|
term->x_margin + at.col * term->cell_width,
|
|
|
|
|
term->y_margin + at.row * term->cell_height,
|
2019-07-30 20:18:58 +02:00
|
|
|
term->cell_width, term->cell_height);
|
|
|
|
|
}
|
2019-07-24 20:21:41 +02:00
|
|
|
term->render.last_cursor.cell = NULL;
|
2019-07-24 18:15:24 +02:00
|
|
|
|
2019-11-17 09:44:31 +01:00
|
|
|
if (term->render.last_cursor.actual.col != term->cursor.point.col ||
|
|
|
|
|
term->render.last_cursor.actual.row != term->cursor.point.row)
|
2019-07-24 20:26:32 +02:00
|
|
|
{
|
2019-07-24 18:15:24 +02:00
|
|
|
/* Detect cursor movement - we don't dirty cells touched
|
|
|
|
|
* by the cursor, since only the final cell matters. */
|
|
|
|
|
all_clean = false;
|
|
|
|
|
}
|
2019-10-28 17:58:44 +01:00
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-10-29 21:09:37 +01:00
|
|
|
tll_foreach(term->grid->scroll_damage, it) {
|
|
|
|
|
switch (it->item.type) {
|
|
|
|
|
case DAMAGE_SCROLL:
|
|
|
|
|
if (term->grid->view == term->grid->offset)
|
2019-10-28 17:58:44 +01:00
|
|
|
grid_render_scroll(term, buf, &it->item);
|
2019-10-29 21:09:37 +01:00
|
|
|
break;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-10-29 21:09:37 +01:00
|
|
|
case DAMAGE_SCROLL_REVERSE:
|
|
|
|
|
if (term->grid->view == term->grid->offset)
|
2019-10-28 17:58:44 +01:00
|
|
|
grid_render_scroll_reverse(term, buf, &it->item);
|
2019-10-29 21:09:37 +01:00
|
|
|
break;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-10-29 21:09:37 +01:00
|
|
|
case DAMAGE_SCROLL_IN_VIEW:
|
|
|
|
|
grid_render_scroll(term, buf, &it->item);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DAMAGE_SCROLL_REVERSE_IN_VIEW:
|
|
|
|
|
grid_render_scroll_reverse(term, buf, &it->item);
|
|
|
|
|
break;
|
2019-10-28 17:58:44 +01:00
|
|
|
}
|
2019-10-29 21:09:37 +01:00
|
|
|
|
|
|
|
|
tll_remove(term->grid->scroll_damage, it);
|
|
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
if (term->render.workers.count > 0) {
|
2019-07-29 20:13:26 +02:00
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
term->render.workers.buf = buf;
|
|
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++)
|
|
|
|
|
sem_post(&term->render.workers.start);
|
2019-07-29 20:13:26 +02:00
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
assert(tll_length(term->render.workers.queue) == 0);
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
|
|
|
|
|
if (!row->dirty)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
mtx_lock(&term->render.workers.lock);
|
|
|
|
|
tll_push_back(term->render.workers.queue, r);
|
|
|
|
|
cnd_signal(&term->render.workers.cond);
|
|
|
|
|
mtx_unlock(&term->render.workers.lock);
|
|
|
|
|
|
|
|
|
|
row->dirty = false;
|
|
|
|
|
all_clean = false;
|
|
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface,
|
2019-08-27 15:25:35 +02:00
|
|
|
term->x_margin, term->y_margin + r * term->cell_height,
|
|
|
|
|
term->width - term->x_margin, term->cell_height);
|
2019-08-01 20:09:39 +02:00
|
|
|
}
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-07-29 20:13:26 +02:00
|
|
|
mtx_lock(&term->render.workers.lock);
|
2019-08-01 20:09:39 +02:00
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++)
|
|
|
|
|
tll_push_back(term->render.workers.queue, -1);
|
|
|
|
|
cnd_broadcast(&term->render.workers.cond);
|
2019-07-29 20:13:26 +02:00
|
|
|
mtx_unlock(&term->render.workers.lock);
|
2019-08-01 20:09:39 +02:00
|
|
|
} else {
|
|
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, r);
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
if (!row->dirty)
|
|
|
|
|
continue;
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-08-16 22:06:06 +02:00
|
|
|
render_row(term, pix, row, r);
|
2019-07-21 20:11:20 +02:00
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
row->dirty = false;
|
|
|
|
|
all_clean = false;
|
|
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface,
|
2019-08-27 15:25:35 +02:00
|
|
|
term->x_margin, term->y_margin + r * term->cell_height,
|
|
|
|
|
term->width - term->x_margin, term->cell_height);
|
2019-08-01 20:09:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-07-29 20:13:26 +02:00
|
|
|
|
2019-07-22 19:17:57 +02:00
|
|
|
if (term->blink.active) {
|
2019-07-21 20:11:20 +02:00
|
|
|
/* Check if there are still any visible blinking cells */
|
|
|
|
|
bool none_is_blinking = true;
|
|
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
for (int col = 0; col < term->cols; col++) {
|
|
|
|
|
if (row->cells[col].attrs.blink) {
|
|
|
|
|
none_is_blinking = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* No, disarm the blink timer */
|
|
|
|
|
if (none_is_blinking) {
|
|
|
|
|
LOG_DBG("disarming blink timer");
|
|
|
|
|
|
2019-07-22 19:17:57 +02:00
|
|
|
term->blink.active = false;
|
|
|
|
|
term->blink.state = BLINK_ON;
|
2019-07-21 20:11:20 +02:00
|
|
|
|
|
|
|
|
if (timerfd_settime(
|
2019-07-22 19:17:57 +02:00
|
|
|
term->blink.fd, 0,
|
2019-07-30 20:28:21 +02:00
|
|
|
&(struct itimerspec){{0}}, NULL) < 0)
|
2019-07-21 20:11:20 +02:00
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to disarm blink timer");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-08 13:57:31 +02:00
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-07-24 18:15:24 +02:00
|
|
|
/*
|
|
|
|
|
* Determine if we need to render a cursor or not. The cursor
|
|
|
|
|
* could be hidden. Or it could have been scrolled out of view.
|
|
|
|
|
*/
|
2019-07-10 09:51:42 +02:00
|
|
|
bool cursor_is_visible = false;
|
2019-08-22 17:33:23 +02:00
|
|
|
int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1);
|
2019-11-17 09:44:31 +01:00
|
|
|
int cursor_row = (term->grid->offset + term->cursor.point.row) & (term->grid->num_rows - 1);
|
2019-07-10 09:51:42 +02:00
|
|
|
if (view_end >= term->grid->view) {
|
|
|
|
|
/* Not wrapped */
|
|
|
|
|
if (cursor_row >= term->grid->view && cursor_row <= view_end)
|
|
|
|
|
cursor_is_visible = true;
|
|
|
|
|
} else {
|
|
|
|
|
/* Wrapped */
|
|
|
|
|
if (cursor_row >= term->grid->view || cursor_row <= view_end)
|
|
|
|
|
cursor_is_visible = true;
|
|
|
|
|
}
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
/*
|
|
|
|
|
* Wait for workers to finish before we render the cursor. This is
|
|
|
|
|
* because the cursor cell might be dirty, in which case a worker
|
|
|
|
|
* will render it (but without the cursor).
|
|
|
|
|
*/
|
|
|
|
|
if (term->render.workers.count > 0) {
|
|
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++)
|
|
|
|
|
sem_wait(&term->render.workers.done);
|
|
|
|
|
term->render.workers.buf = NULL;
|
|
|
|
|
}
|
2019-07-29 20:13:26 +02:00
|
|
|
|
2019-07-11 09:59:51 +02:00
|
|
|
if (cursor_is_visible && !term->hide_cursor) {
|
2019-07-24 18:15:24 +02:00
|
|
|
/* Remember cursor coordinates so that we can erase it next
|
|
|
|
|
* time. Note that we need to re-align it against the view. */
|
2019-07-24 20:21:41 +02:00
|
|
|
int view_aligned_row
|
2019-08-22 17:33:23 +02:00
|
|
|
= (cursor_row - term->grid->view + term->grid->num_rows) & (term->grid->num_rows - 1);
|
2019-07-24 18:15:24 +02:00
|
|
|
|
2019-11-17 09:44:31 +01:00
|
|
|
term->render.last_cursor.actual = term->cursor.point;
|
2019-07-24 20:21:41 +02:00
|
|
|
term->render.last_cursor.in_view = (struct coord) {
|
2019-11-17 09:44:31 +01:00
|
|
|
term->cursor.point.col, view_aligned_row};
|
2019-07-24 18:15:24 +02:00
|
|
|
|
2019-07-24 20:21:41 +02:00
|
|
|
struct row *row = grid_row_in_view(term->grid, view_aligned_row);
|
2019-11-17 09:44:31 +01:00
|
|
|
struct cell *cell = &row->cells[term->cursor.point.col];
|
2019-07-24 20:21:41 +02:00
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
term->render.last_cursor.cell = cell;
|
2019-08-01 20:09:39 +02:00
|
|
|
int cols_updated = render_cell(
|
2019-11-17 09:44:31 +01:00
|
|
|
term, pix, cell, term->cursor.point.col, view_aligned_row, true);
|
2019-07-10 09:51:42 +02:00
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface,
|
2019-11-17 09:44:31 +01:00
|
|
|
term->x_margin + term->cursor.point.col * term->cell_width,
|
2019-08-27 15:25:35 +02:00
|
|
|
term->y_margin + view_aligned_row * term->cell_height,
|
2019-08-01 20:09:39 +02:00
|
|
|
cols_updated * term->cell_width, term->cell_height);
|
2019-07-10 09:51:42 +02:00
|
|
|
}
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-07-24 18:15:24 +02:00
|
|
|
if (all_clean) {
|
|
|
|
|
buf->busy = false;
|
2019-11-01 20:01:36 +01:00
|
|
|
wl_display_flush(term->wl->display);
|
2019-07-24 18:15:24 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-22 19:15:56 +02:00
|
|
|
if (term->flash.active) {
|
2019-08-16 22:06:06 +02:00
|
|
|
/* Note: alpha is pre-computed in each color component */
|
2019-08-27 17:23:28 +02:00
|
|
|
/* TODO: dim while searching */
|
2019-08-16 22:06:06 +02:00
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_OVER, pix,
|
|
|
|
|
&(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff},
|
|
|
|
|
1, &(pixman_rectangle16_t){0, 0, term->width, term->height});
|
2019-07-21 19:14:19 +02:00
|
|
|
|
|
|
|
|
wl_surface_damage_buffer(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->surface, 0, 0, term->width, term->height);
|
2019-07-21 19:14:19 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-08 15:27:44 +02:00
|
|
|
assert(term->grid->offset >= 0 && term->grid->offset < term->grid->num_rows);
|
2019-07-09 16:26:36 +02:00
|
|
|
assert(term->grid->view >= 0 && term->grid->view < term->grid->num_rows);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
assert(term->window->frame_callback == NULL);
|
|
|
|
|
term->window->frame_callback = wl_surface_frame(term->window->surface);
|
|
|
|
|
wl_callback_add_listener(term->window->frame_callback, &frame_listener, term);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
wl_surface_set_buffer_scale(term->window->surface, term->scale);
|
|
|
|
|
wl_surface_commit(term->window->surface);
|
2019-07-18 09:33:49 +02:00
|
|
|
|
2019-07-18 10:35:27 +02:00
|
|
|
#if TIME_FRAME_RENDERING
|
2019-07-18 09:33:49 +02:00
|
|
|
struct timeval end_time;
|
|
|
|
|
gettimeofday(&end_time, NULL);
|
|
|
|
|
|
|
|
|
|
struct timeval render_time;
|
|
|
|
|
timersub(&end_time, &start_time, &render_time);
|
2019-08-24 11:32:28 +02:00
|
|
|
LOG_INFO("frame rendered in %lds %ldus",
|
|
|
|
|
render_time.tv_sec, render_time.tv_usec);
|
2019-07-18 10:35:27 +02:00
|
|
|
#endif
|
2019-11-01 20:01:36 +01:00
|
|
|
wl_display_flush(term->wl->display);
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
|
|
|
|
|
{
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
assert(term->window->frame_callback == wl_callback);
|
2019-07-05 10:16:56 +02:00
|
|
|
wl_callback_destroy(wl_callback);
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->frame_callback = NULL;
|
2019-07-05 10:16:56 +02:00
|
|
|
grid_render(term);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-29 20:18:06 +02:00
|
|
|
void
|
|
|
|
|
render_search_box(struct terminal *term)
|
|
|
|
|
{
|
2019-10-27 19:08:48 +01:00
|
|
|
assert(term->window->search_sub_surface != NULL);
|
2019-08-29 20:18:06 +02:00
|
|
|
|
|
|
|
|
/* TODO: at least sway allows the subsurface to extend outside the
|
|
|
|
|
* main window. Do we want that? */
|
|
|
|
|
const int scale = term->scale >= 1 ? term->scale : 1;
|
|
|
|
|
const int margin = scale * 3;
|
|
|
|
|
const int width = 2 * margin + max(20, term->search.len) * term->cell_width;
|
|
|
|
|
const int height = 2 * margin + 1 * term->cell_height;
|
|
|
|
|
|
2019-11-02 00:33:37 +01:00
|
|
|
unsigned long cookie = (uintptr_t)term;
|
|
|
|
|
struct buffer *buf = shm_get_buffer(term->wl->shm, width, height, cookie);
|
2019-08-29 20:18:06 +02:00
|
|
|
|
|
|
|
|
/* Background - yellow on empty/match, red on mismatch */
|
|
|
|
|
pixman_color_t color = color_hex_to_pixman(
|
2019-08-30 21:01:13 +02:00
|
|
|
term->search.match_len == term->search.len
|
|
|
|
|
? term->colors.table[3] : term->colors.table[1]);
|
2019-08-29 20:18:06 +02:00
|
|
|
|
|
|
|
|
pixman_image_fill_rectangles(
|
|
|
|
|
PIXMAN_OP_SRC, buf->pix, &color,
|
|
|
|
|
1, &(pixman_rectangle16_t){0, 0, width, height});
|
|
|
|
|
|
2019-10-16 21:52:12 +02:00
|
|
|
struct font *font = term->fonts[0];
|
2019-08-29 20:18:06 +02:00
|
|
|
int x = margin;
|
|
|
|
|
int y = margin;
|
2019-08-30 21:01:13 +02:00
|
|
|
pixman_color_t fg = color_hex_to_pixman(term->colors.table[0]);
|
2019-08-29 20:18:06 +02:00
|
|
|
|
|
|
|
|
/* Text (what the user entered - *not* match(es)) */
|
|
|
|
|
for (size_t i = 0; i < term->search.len; i++) {
|
2019-08-29 21:03:00 +02:00
|
|
|
if (i == term->search.cursor)
|
2019-08-30 19:42:33 +02:00
|
|
|
draw_bar(term, buf->pix, font, &fg, x, y);
|
2019-08-29 21:03:00 +02:00
|
|
|
|
2019-08-30 19:42:33 +02:00
|
|
|
const struct glyph *glyph = font_glyph_for_wc(font, term->search.buf[i]);
|
2019-08-29 20:18:06 +02:00
|
|
|
if (glyph == NULL)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
pixman_image_t *src = pixman_image_create_solid_fill(&fg);
|
|
|
|
|
pixman_image_composite32(
|
|
|
|
|
PIXMAN_OP_OVER, src, glyph->pix, buf->pix, 0, 0, 0, 0,
|
|
|
|
|
x + glyph->x, y + term->fextents.ascent - glyph->y,
|
|
|
|
|
glyph->width, glyph->height);
|
|
|
|
|
pixman_image_unref(src);
|
|
|
|
|
|
2019-08-30 19:35:47 +02:00
|
|
|
x += term->cell_width;
|
2019-08-29 20:18:06 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-29 21:03:00 +02:00
|
|
|
if (term->search.cursor >= term->search.len)
|
2019-08-30 19:42:33 +02:00
|
|
|
draw_bar(term, buf->pix, font, &fg, x, y);
|
2019-08-29 21:03:00 +02:00
|
|
|
|
2019-08-29 20:18:06 +02:00
|
|
|
wl_subsurface_set_position(
|
2019-10-27 19:08:48 +01:00
|
|
|
term->window->search_sub_surface,
|
2019-08-29 20:18:06 +02:00
|
|
|
term->width - width - margin, term->height - height - margin);
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
wl_surface_damage_buffer(term->window->search_surface, 0, 0, width, height);
|
|
|
|
|
wl_surface_attach(term->window->search_surface, buf->wl_buf, 0, 0);
|
|
|
|
|
wl_surface_set_buffer_scale(term->window->search_surface, scale);
|
|
|
|
|
wl_surface_commit(term->window->search_surface);
|
2019-08-29 20:18:06 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-09 09:12:41 +02:00
|
|
|
static void
|
|
|
|
|
reflow(struct row **new_grid, int new_cols, int new_rows,
|
|
|
|
|
struct row *const *old_grid, int old_cols, int old_rows)
|
|
|
|
|
{
|
|
|
|
|
/* TODO: actually reflow */
|
|
|
|
|
for (int r = 0; r < min(new_rows, old_rows); r++) {
|
|
|
|
|
size_t copy_cols = min(new_cols, old_cols);
|
|
|
|
|
size_t clear_cols = new_cols - copy_cols;
|
|
|
|
|
|
2019-07-10 16:27:55 +02:00
|
|
|
if (old_grid[r] == NULL)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (new_grid[r] == NULL)
|
2019-08-22 17:33:23 +02:00
|
|
|
new_grid[r] = grid_row_alloc(new_cols, false);
|
2019-07-10 16:27:55 +02:00
|
|
|
|
2019-07-09 09:12:41 +02:00
|
|
|
struct cell *new_cells = new_grid[r]->cells;
|
|
|
|
|
const struct cell *old_cells = old_grid[r]->cells;
|
|
|
|
|
|
2019-07-09 16:26:36 +02:00
|
|
|
new_grid[r]->dirty = old_grid[r]->dirty;
|
2019-07-09 09:12:41 +02:00
|
|
|
memcpy(new_cells, old_cells, copy_cols * sizeof(new_cells[0]));
|
|
|
|
|
memset(&new_cells[copy_cols], 0, clear_cols * sizeof(new_cells[0]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 10:16:56 +02:00
|
|
|
/* Move to terminal.c? */
|
|
|
|
|
void
|
2019-08-12 21:49:17 +02:00
|
|
|
render_resize(struct terminal *term, int width, int height)
|
2019-07-05 10:16:56 +02:00
|
|
|
{
|
2019-08-12 21:49:17 +02:00
|
|
|
int scale = -1;
|
2019-10-27 19:08:48 +01:00
|
|
|
tll_foreach(term->window->on_outputs, it) {
|
2019-08-12 21:49:17 +02:00
|
|
|
if (it->item->scale > scale)
|
|
|
|
|
scale = it->item->scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (scale == -1) {
|
|
|
|
|
/* Haven't 'entered' an output yet? */
|
|
|
|
|
scale = 1;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-21 17:56:41 +02:00
|
|
|
width *= scale;
|
|
|
|
|
height *= scale;
|
2019-08-12 21:22:38 +02:00
|
|
|
|
2019-08-12 21:32:38 +02:00
|
|
|
if (width == 0 && height == 0) {
|
|
|
|
|
/* Assume we're not fully up and running yet */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (width == term->width && height == term->height && scale == term->scale)
|
2019-07-05 10:16:56 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
term->width = width;
|
|
|
|
|
term->height = height;
|
2019-08-12 21:32:38 +02:00
|
|
|
term->scale = scale;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-08-01 20:09:39 +02:00
|
|
|
const int scrollback_lines = term->render.scrollback_lines;
|
2019-07-09 16:26:36 +02:00
|
|
|
|
2019-07-09 09:12:41 +02:00
|
|
|
const int old_cols = term->cols;
|
2019-07-08 13:57:31 +02:00
|
|
|
const int old_rows = term->rows;
|
2019-07-09 09:12:41 +02:00
|
|
|
const int old_normal_grid_rows = term->normal.num_rows;
|
|
|
|
|
const int old_alt_grid_rows = term->alt.num_rows;
|
|
|
|
|
|
2019-07-08 13:57:31 +02:00
|
|
|
const int new_cols = term->width / term->cell_width;
|
|
|
|
|
const int new_rows = term->height / term->cell_height;
|
2019-08-22 17:33:23 +02:00
|
|
|
const int new_normal_grid_rows = 1 << (32 - __builtin_clz(new_rows + scrollback_lines - 1));
|
|
|
|
|
const int new_alt_grid_rows = 1 << (32 - __builtin_clz(new_rows));
|
2019-07-09 09:12:41 +02:00
|
|
|
|
2019-08-27 15:25:35 +02:00
|
|
|
term->x_margin = (term->width - new_cols * term->cell_width) / 2;
|
|
|
|
|
term->y_margin = (term->height - new_rows * term->cell_height) / 2;
|
|
|
|
|
|
2019-08-04 18:34:14 +02:00
|
|
|
term->normal.offset %= new_normal_grid_rows;
|
|
|
|
|
term->normal.view %= new_normal_grid_rows;
|
|
|
|
|
|
|
|
|
|
term->alt.offset %= new_alt_grid_rows;
|
|
|
|
|
term->alt.view %= new_alt_grid_rows;
|
|
|
|
|
|
2019-07-09 09:12:41 +02:00
|
|
|
/* Allocate new 'normal' grid */
|
2019-07-10 16:27:55 +02:00
|
|
|
struct row **normal = calloc(new_normal_grid_rows, sizeof(normal[0]));
|
2019-08-27 15:25:35 +02:00
|
|
|
for (int r = 0; r < new_rows; r++) {
|
|
|
|
|
size_t real_r = (term->normal.view + r) & (new_normal_grid_rows - 1);
|
|
|
|
|
normal[real_r] = grid_row_alloc(new_cols, true);
|
|
|
|
|
}
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2019-07-09 09:12:41 +02:00
|
|
|
/* Allocate new 'alt' grid */
|
2019-07-10 16:27:55 +02:00
|
|
|
struct row **alt = calloc(new_alt_grid_rows, sizeof(alt[0]));
|
2019-08-27 15:25:35 +02:00
|
|
|
for (int r = 0; r < new_rows; r++) {
|
|
|
|
|
int real_r = (term->alt.view + r) & (new_alt_grid_rows - 1);
|
|
|
|
|
alt[real_r] = grid_row_alloc(new_cols, true);
|
|
|
|
|
}
|
2019-07-09 09:12:41 +02:00
|
|
|
|
|
|
|
|
/* Reflow content */
|
|
|
|
|
reflow(normal, new_cols, new_normal_grid_rows,
|
|
|
|
|
term->normal.rows, old_cols, old_normal_grid_rows);
|
|
|
|
|
reflow(alt, new_cols, new_alt_grid_rows,
|
|
|
|
|
term->alt.rows, old_cols, old_alt_grid_rows);
|
|
|
|
|
|
|
|
|
|
/* Free old 'normal' grid */
|
2019-07-10 16:27:55 +02:00
|
|
|
for (int r = 0; r < term->normal.num_rows; r++)
|
|
|
|
|
grid_row_free(term->normal.rows[r]);
|
2019-07-08 13:57:31 +02:00
|
|
|
free(term->normal.rows);
|
|
|
|
|
|
2019-07-09 09:12:41 +02:00
|
|
|
/* Free old 'alt' grid */
|
2019-07-10 16:27:55 +02:00
|
|
|
for (int r = 0; r < term->alt.num_rows; r++)
|
|
|
|
|
grid_row_free(term->alt.rows[r]);
|
2019-07-08 13:57:31 +02:00
|
|
|
free(term->alt.rows);
|
|
|
|
|
|
2019-11-16 10:54:56 +01:00
|
|
|
/* Reset tab stops */
|
|
|
|
|
tll_free(term->tab_stops);
|
|
|
|
|
for (int c = 0; c < new_cols; c += 8)
|
|
|
|
|
tll_push_back(term->tab_stops, c);
|
|
|
|
|
|
2019-07-08 13:57:31 +02:00
|
|
|
term->cols = new_cols;
|
|
|
|
|
term->rows = new_rows;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-07-09 09:12:41 +02:00
|
|
|
term->normal.rows = normal;
|
|
|
|
|
term->normal.num_rows = new_normal_grid_rows;
|
2019-07-10 16:27:55 +02:00
|
|
|
term->normal.num_cols = new_cols;
|
2019-07-09 09:12:41 +02:00
|
|
|
term->alt.rows = alt;
|
|
|
|
|
term->alt.num_rows = new_alt_grid_rows;
|
2019-07-10 16:27:55 +02:00
|
|
|
term->alt.num_cols = new_cols;
|
2019-07-09 09:12:41 +02:00
|
|
|
|
2019-08-27 15:25:35 +02:00
|
|
|
LOG_INFO("resize: %dx%d, grid: cols=%d, rows=%d (x-margin=%d, y-margin=%d)",
|
|
|
|
|
term->width, term->height, term->cols, term->rows,
|
|
|
|
|
term->x_margin, term->y_margin);
|
2019-07-05 10:16:56 +02:00
|
|
|
|
|
|
|
|
/* Signal TIOCSWINSZ */
|
|
|
|
|
if (ioctl(term->ptmx, TIOCSWINSZ,
|
|
|
|
|
&(struct winsize){
|
|
|
|
|
.ws_row = term->rows,
|
|
|
|
|
.ws_col = term->cols,
|
|
|
|
|
.ws_xpixel = term->width,
|
|
|
|
|
.ws_ypixel = term->height}) == -1)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("TIOCSWINSZ");
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-09 09:12:41 +02:00
|
|
|
if (term->scroll_region.start >= term->rows)
|
|
|
|
|
term->scroll_region.start = 0;
|
|
|
|
|
|
|
|
|
|
if (term->scroll_region.end >= old_rows)
|
2019-07-05 10:16:56 +02:00
|
|
|
term->scroll_region.end = term->rows;
|
|
|
|
|
|
|
|
|
|
term_cursor_to(
|
|
|
|
|
term,
|
2019-11-17 09:44:31 +01:00
|
|
|
min(term->cursor.point.row, term->rows - 1),
|
|
|
|
|
min(term->cursor.point.col, term->cols - 1));
|
2019-07-09 16:26:36 +02:00
|
|
|
|
2019-07-26 18:51:47 +02:00
|
|
|
term->render.last_cursor.cell = NULL;
|
2019-07-05 10:16:56 +02:00
|
|
|
|
2019-07-26 18:51:47 +02:00
|
|
|
term_damage_view(term);
|
2019-07-24 20:09:49 +02:00
|
|
|
render_refresh(term);
|
2019-07-05 10:16:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2019-11-01 20:25:44 +01:00
|
|
|
render_set_title(struct terminal *term, const char *_title)
|
2019-07-05 10:16:56 +02:00
|
|
|
{
|
2019-11-01 20:25:44 +01:00
|
|
|
/* TODO: figure out what the limit actually is */
|
|
|
|
|
static const size_t max_len = 100;
|
|
|
|
|
|
|
|
|
|
const char *title = _title;
|
|
|
|
|
char *copy = NULL;
|
|
|
|
|
|
|
|
|
|
if (strlen(title) > max_len) {
|
|
|
|
|
copy = strndup(_title, max_len);
|
|
|
|
|
title = copy;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 19:08:48 +01:00
|
|
|
xdg_toplevel_set_title(term->window->xdg_toplevel, title);
|
2019-11-01 20:25:44 +01:00
|
|
|
free(copy);
|
2019-07-05 10:44:09 +02:00
|
|
|
}
|
2019-07-24 20:09:49 +02:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
render_refresh(struct terminal *term)
|
|
|
|
|
{
|
2019-10-27 19:08:48 +01:00
|
|
|
if (term->window->frame_callback == NULL)
|
2019-07-24 20:09:49 +02:00
|
|
|
grid_render(term);
|
|
|
|
|
}
|