You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

2467 lines
78 KiB

  1. #include "render.h"
  2. #include <string.h>
  3. #include <sys/ioctl.h>
  4. #include <sys/time.h>
  5. #include <sys/timerfd.h>
  6. #include <sys/prctl.h>
  7. #include <wayland-cursor.h>
  8. #include <xdg-shell.h>
  9. #include <presentation-time.h>
  10. #include <fcft/fcft.h>
  11. #define LOG_MODULE "render"
  12. #define LOG_ENABLE_DBG 0
  13. #include "log.h"
  14. #include "config.h"
  15. #include "grid.h"
  16. #include "quirks.h"
  17. #include "selection.h"
  18. #include "sixel.h"
  19. #include "shm.h"
  20. #include "util.h"
  21. #include "xmalloc.h"
  22. #define TIME_SCROLL_DAMAGE 0
  23. struct renderer {
  24. struct fdm *fdm;
  25. struct wayland *wayl;
  26. };
  27. static struct {
  28. size_t total;
  29. size_t zero; /* commits presented in less than one frame interval */
  30. size_t one; /* commits presented in one frame interval */
  31. size_t two; /* commits presented in two or more frame intervals */
  32. } presentation_statistics = {0};
  33. static void fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data);
  34. struct renderer *
  35. render_init(struct fdm *fdm, struct wayland *wayl)
  36. {
  37. struct renderer *renderer = malloc(sizeof(*renderer));
  38. if (unlikely(renderer == NULL)) {
  39. LOG_ERRNO("malloc() failed");
  40. return NULL;
  41. }
  42. *renderer = (struct renderer) {
  43. .fdm = fdm,
  44. .wayl = wayl,
  45. };
  46. if (!fdm_hook_add(fdm, &fdm_hook_refresh_pending_terminals, renderer,
  47. FDM_HOOK_PRIORITY_NORMAL))
  48. {
  49. LOG_ERR("failed to register FDM hook");
  50. free(renderer);
  51. return NULL;
  52. }
  53. return renderer;
  54. }
  55. void
  56. render_destroy(struct renderer *renderer)
  57. {
  58. if (renderer == NULL)
  59. return;
  60. fdm_hook_del(renderer->fdm, &fdm_hook_refresh_pending_terminals,
  61. FDM_HOOK_PRIORITY_NORMAL);
  62. free(renderer);
  63. }
  64. static void __attribute__((destructor))
  65. log_presentation_statistics(void)
  66. {
  67. if (presentation_statistics.total == 0)
  68. return;
  69. const size_t total = presentation_statistics.total;
  70. LOG_INFO("presentation statistics: zero=%f%%, one=%f%%, two=%f%%",
  71. 100. * presentation_statistics.zero / total,
  72. 100. * presentation_statistics.one / total,
  73. 100. * presentation_statistics.two / total);
  74. }
  75. static void
  76. sync_output(void *data,
  77. struct wp_presentation_feedback *wp_presentation_feedback,
  78. struct wl_output *output)
  79. {
  80. }
  81. struct presentation_context {
  82. struct terminal *term;
  83. struct timeval input;
  84. struct timeval commit;
  85. };
  86. static void
  87. presented(void *data,
  88. struct wp_presentation_feedback *wp_presentation_feedback,
  89. uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec,
  90. uint32_t refresh, uint32_t seq_hi, uint32_t seq_lo, uint32_t flags)
  91. {
  92. struct presentation_context *ctx = data;
  93. struct terminal *term = ctx->term;
  94. const struct timeval *input = &ctx->input;
  95. const struct timeval *commit = &ctx->commit;
  96. const struct timeval presented = {
  97. .tv_sec = (uint64_t)tv_sec_hi << 32 | tv_sec_lo,
  98. .tv_usec = tv_nsec / 1000,
  99. };
  100. bool use_input = (input->tv_sec > 0 || input->tv_usec > 0) &&
  101. timercmp(&presented, input, >);
  102. char msg[1024];
  103. int chars = 0;
  104. if (use_input && timercmp(&presented, input, <))
  105. return;
  106. else if (timercmp(&presented, commit, <))
  107. return;
  108. LOG_DBG("commit: %lu s %lu µs, presented: %lu s %lu µs",
  109. commit->tv_sec, commit->tv_usec, presented.tv_sec, presented.tv_usec);
  110. if (use_input) {
  111. struct timeval diff;
  112. timersub(commit, input, &diff);
  113. chars += snprintf(
  114. &msg[chars], sizeof(msg) - chars,
  115. "input - %llu µs -> ", (unsigned long long)diff.tv_usec);
  116. }
  117. struct timeval diff;
  118. timersub(&presented, commit, &diff);
  119. chars += snprintf(
  120. &msg[chars], sizeof(msg) - chars,
  121. "commit - %llu µs -> ", (unsigned long long)diff.tv_usec);
  122. if (use_input) {
  123. assert(timercmp(&presented, input, >));
  124. timersub(&presented, input, &diff);
  125. } else {
  126. assert(timercmp(&presented, commit, >));
  127. timersub(&presented, commit, &diff);
  128. }
  129. chars += snprintf(
  130. &msg[chars], sizeof(msg) - chars,
  131. "presented (total: %llu µs)", (unsigned long long)diff.tv_usec);
  132. unsigned frame_count = 0;
  133. if (tll_length(term->window->on_outputs) > 0) {
  134. const struct monitor *mon = tll_front(term->window->on_outputs);
  135. frame_count = (diff.tv_sec * 1000000. + diff.tv_usec) / (1000000. / mon->refresh);
  136. }
  137. presentation_statistics.total++;
  138. if (frame_count >= 2)
  139. presentation_statistics.two++;
  140. else if (frame_count >= 1)
  141. presentation_statistics.one++;
  142. else
  143. presentation_statistics.zero++;
  144. #define _log_fmt "%s (more than %u frames)"
  145. if (frame_count >= 2)
  146. LOG_ERR(_log_fmt, msg, frame_count);
  147. else if (frame_count >= 1)
  148. LOG_WARN(_log_fmt, msg, frame_count);
  149. else
  150. LOG_INFO(_log_fmt, msg, frame_count);
  151. #undef _log_fmt
  152. wp_presentation_feedback_destroy(wp_presentation_feedback);
  153. free(ctx);
  154. }
  155. static void
  156. discarded(void *data, struct wp_presentation_feedback *wp_presentation_feedback)
  157. {
  158. struct presentation_context *ctx = data;
  159. wp_presentation_feedback_destroy(wp_presentation_feedback);
  160. free(ctx);
  161. }
  162. static const struct wp_presentation_feedback_listener presentation_feedback_listener = {
  163. .sync_output = &sync_output,
  164. .presented = &presented,
  165. .discarded = &discarded,
  166. };
  167. static struct fcft_font *
  168. attrs_to_font(const struct terminal *term, const struct attributes *attrs)
  169. {
  170. int idx = attrs->italic << 1 | attrs->bold;
  171. return term->fonts[idx];
  172. }
  173. static inline pixman_color_t
  174. color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha)
  175. {
  176. if (alpha == 0)
  177. return (pixman_color_t){0, 0, 0, 0};
  178. int alpha_div = 0xffff / alpha;
  179. return (pixman_color_t){
  180. .red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)) / alpha_div,
  181. .green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)) / alpha_div,
  182. .blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)) / alpha_div,
  183. .alpha = alpha,
  184. };
  185. }
  186. static inline pixman_color_t
  187. color_hex_to_pixman(uint32_t color)
  188. {
  189. /* Count on the compiler optimizing this */
  190. return color_hex_to_pixman_with_alpha(color, 0xffff);
  191. }
  192. static inline void
  193. color_dim(pixman_color_t *color)
  194. {
  195. color->red /= 2;
  196. color->green /= 2;
  197. color->blue /= 2;
  198. }
  199. static inline void
  200. color_dim_for_search(pixman_color_t *color)
  201. {
  202. color->red /= 2;
  203. color->green /= 2;
  204. color->blue /= 2;
  205. }
  206. static inline int
  207. font_baseline(const struct terminal *term)
  208. {
  209. return term->fonts[0]->ascent;
  210. }
  211. static void
  212. draw_unfocused_block(const struct terminal *term, pixman_image_t *pix,
  213. const pixman_color_t *color, int x, int y, int cell_cols)
  214. {
  215. pixman_image_fill_rectangles(
  216. PIXMAN_OP_SRC, pix, color, 4,
  217. (pixman_rectangle16_t []){
  218. {x, y, cell_cols * term->cell_width, 1}, /* top */
  219. {x, y, 1, term->cell_height}, /* left */
  220. {x + cell_cols * term->cell_width - 1, y, 1, term->cell_height}, /* right */
  221. {x, y + term->cell_height - 1, cell_cols * term->cell_width, 1}, /* bottom */
  222. });
  223. }
  224. static void
  225. draw_bar(const struct terminal *term, pixman_image_t *pix,
  226. const struct fcft_font *font,
  227. const pixman_color_t *color, int x, int y)
  228. {
  229. int baseline = y + font_baseline(term) - term->fonts[0]->ascent;
  230. pixman_image_fill_rectangles(
  231. PIXMAN_OP_SRC, pix, color,
  232. 1, &(pixman_rectangle16_t){
  233. x, baseline,
  234. font->underline.thickness, term->fonts[0]->ascent + term->fonts[0]->descent});
  235. }
  236. static void
  237. draw_underline(const struct terminal *term, pixman_image_t *pix,
  238. const struct fcft_font *font,
  239. const pixman_color_t *color, int x, int y, int cols)
  240. {
  241. pixman_image_fill_rectangles(
  242. PIXMAN_OP_SRC, pix, color,
  243. 1, &(pixman_rectangle16_t){
  244. x, y + font_baseline(term) - font->underline.position,
  245. cols * term->cell_width, font->underline.thickness});
  246. }
  247. static void
  248. draw_strikeout(const struct terminal *term, pixman_image_t *pix,
  249. const struct fcft_font *font,
  250. const pixman_color_t *color, int x, int y, int cols)
  251. {
  252. pixman_image_fill_rectangles(
  253. PIXMAN_OP_SRC, pix, color,
  254. 1, &(pixman_rectangle16_t){
  255. x, y + font_baseline(term) - font->strikeout.position,
  256. cols * term->cell_width, font->strikeout.thickness});
  257. }
  258. static void
  259. draw_cursor(const struct terminal *term, const struct cell *cell,
  260. const struct fcft_font *font, pixman_image_t *pix, pixman_color_t *fg,
  261. const pixman_color_t *bg, int x, int y, int cols)
  262. {
  263. pixman_color_t cursor_color;
  264. pixman_color_t text_color;
  265. bool is_selected = cell->attrs.selected;
  266. if (term->cursor_color.cursor >> 31) {
  267. cursor_color = color_hex_to_pixman(term->cursor_color.cursor);
  268. text_color = color_hex_to_pixman(
  269. term->cursor_color.text >> 31
  270. ? term->cursor_color.text : term->colors.bg);
  271. if (term->reverse ^ cell->attrs.reverse ^ is_selected) {
  272. pixman_color_t swap = cursor_color;
  273. cursor_color = text_color;
  274. text_color = swap;
  275. }
  276. if (term->is_searching && !is_selected) {
  277. color_dim_for_search(&cursor_color);
  278. color_dim_for_search(&text_color);
  279. }
  280. } else {
  281. cursor_color = *fg;
  282. text_color = *bg;
  283. }
  284. switch (term->cursor_style) {
  285. case CURSOR_BLOCK:
  286. if (!term->kbd_focus)
  287. draw_unfocused_block(term, pix, &cursor_color, x, y, cols);
  288. else if (term->cursor_blink.state == CURSOR_BLINK_ON) {
  289. *fg = text_color;
  290. pixman_image_fill_rectangles(
  291. PIXMAN_OP_SRC, pix, &cursor_color, 1,
  292. &(pixman_rectangle16_t){x, y, cols * term->cell_width, term->cell_height});
  293. }
  294. break;
  295. case CURSOR_BAR:
  296. if (term->cursor_blink.state == CURSOR_BLINK_ON || !term->kbd_focus)
  297. draw_bar(term, pix, font, &cursor_color, x, y);
  298. break;
  299. case CURSOR_UNDERLINE:
  300. if (term->cursor_blink.state == CURSOR_BLINK_ON || !term->kbd_focus) {
  301. draw_underline(
  302. term, pix, attrs_to_font(term, &cell->attrs), &cursor_color,
  303. x, y, cols);
  304. }
  305. break;
  306. }
  307. }
  308. static int
  309. render_cell(struct terminal *term, pixman_image_t *pix,
  310. struct row *row, int col, int row_no, bool has_cursor)
  311. {
  312. struct cell *cell = &row->cells[col];
  313. if (cell->attrs.clean)
  314. return 0;
  315. cell->attrs.clean = 1;
  316. int width = term->cell_width;
  317. int height = term->cell_height;
  318. int x = term->margins.left + col * width;
  319. int y = term->margins.top + row_no * height;
  320. assert(cell->attrs.selected == 0 || cell->attrs.selected == 1);
  321. bool is_selected = cell->attrs.selected;
  322. uint32_t _fg = 0;
  323. uint32_t _bg = 0;
  324. if (is_selected && term->conf->colors.selection_uses_custom_colors) {
  325. _fg = term->conf->colors.selection_fg;
  326. _bg = term->conf->colors.selection_bg;
  327. } else {
  328. /* Use cell specific color, if set, otherwise the default colors (possible reversed) */
  329. _fg = cell->attrs.have_fg ? cell->attrs.fg : term->colors.fg;
  330. _bg = cell->attrs.have_bg ? cell->attrs.bg : term->colors.bg;
  331. if (term->reverse ^ cell->attrs.reverse ^ is_selected) {
  332. uint32_t swap = _fg;
  333. _fg = _bg;
  334. _bg = swap;
  335. }
  336. }
  337. if (cell->attrs.blink && term->blink.state == BLINK_OFF)
  338. _fg = _bg;
  339. pixman_color_t fg = color_hex_to_pixman(_fg);
  340. pixman_color_t bg = color_hex_to_pixman_with_alpha(
  341. _bg,
  342. (_bg == (term->reverse ? term->colors.fg : term->colors.bg)
  343. ? term->colors.alpha : 0xffff));
  344. if (cell->attrs.dim)
  345. color_dim(&fg);
  346. if (term->is_searching && !is_selected) {
  347. color_dim_for_search(&fg);
  348. color_dim_for_search(&bg);
  349. }
  350. struct fcft_font *font = attrs_to_font(term, &cell->attrs);
  351. const struct fcft_glyph *glyph = NULL;
  352. const struct composed *composed = NULL;
  353. if (cell->wc != 0) {
  354. wchar_t base = cell->wc;
  355. if (base >= CELL_COMB_CHARS_LO &&
  356. base < (CELL_COMB_CHARS_LO + term->composed_count))
  357. {
  358. composed = &term->composed[base - CELL_COMB_CHARS_LO];
  359. base = composed->base;
  360. }
  361. glyph = fcft_glyph_rasterize(font, base, term->font_subpixel);
  362. }
  363. const int cols_left = term->cols - col;
  364. int cell_cols = glyph != NULL ? max(1, min(glyph->cols, cols_left)) : 1;
  365. /*
  366. * Hack!
  367. *
  368. * Deal with double-width glyphs for which wcwidth() returns
  369. * 1. Typically Unicode private usage area characters,
  370. * e.g. powerline, or nerd hack fonts.
  371. *
  372. * Users can enable a tweak option that lets this glyphs
  373. * overflow/bleed into the neighbouring cell.
  374. *
  375. * We only apply this workaround if:
  376. * - the user has explicitly enabled this feature
  377. * - the *character* width is 1
  378. * - the *glyph* width is at least 1.5 cells
  379. * - the *glyph* width is less than 3 cells
  380. */
  381. if (term->conf->tweak.allow_overflowing_double_width_glyphs &&
  382. glyph != NULL &&
  383. glyph->cols == 1 &&
  384. glyph->width >= term->cell_width * 15 / 10 &&
  385. glyph->width < 3 * term->cell_width)
  386. {
  387. cell_cols = min(2, cols_left);
  388. }
  389. pixman_region32_t clip;
  390. pixman_region32_init_rect(
  391. &clip, x, y,
  392. cell_cols * term->cell_width, term->cell_height);
  393. pixman_image_set_clip_region32(pix, &clip);
  394. /* Background */
  395. pixman_image_fill_rectangles(
  396. PIXMAN_OP_SRC, pix, &bg, 1,
  397. &(pixman_rectangle16_t){x, y, cell_cols * width, height});
  398. if (cell->attrs.blink)
  399. term_arm_blink_timer(term);
  400. if (has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus)
  401. draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols);
  402. if (cell->wc == 0 || cell->wc == CELL_MULT_COL_SPACER || cell->attrs.conceal)
  403. goto draw_cursor;
  404. pixman_image_t *clr_pix = pixman_image_create_solid_fill(&fg);
  405. if (glyph != NULL) {
  406. /* Clip to cell */
  407. if (unlikely(pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)) {
  408. /* Glyph surface is a pre-rendered image (typically a color emoji...) */
  409. if (!(cell->attrs.blink && term->blink.state == BLINK_OFF)) {
  410. pixman_image_composite32(
  411. PIXMAN_OP_OVER, glyph->pix, NULL, pix, 0, 0, 0, 0,
  412. x + glyph->x, y + font_baseline(term) - glyph->y,
  413. glyph->width, glyph->height);
  414. }
  415. } else {
  416. pixman_image_composite32(
  417. PIXMAN_OP_OVER, clr_pix, glyph->pix, pix, 0, 0, 0, 0,
  418. x + glyph->x, y + font_baseline(term) - glyph->y,
  419. glyph->width, glyph->height);
  420. /* Combining characters */
  421. if (composed != NULL) {
  422. for (size_t i = 0; i < composed->count; i++) {
  423. const struct fcft_glyph *g = fcft_glyph_rasterize(
  424. font, composed->combining[i], term->font_subpixel);
  425. if (g == NULL)
  426. continue;
  427. pixman_image_composite32(
  428. PIXMAN_OP_OVER, clr_pix, g->pix, pix, 0, 0, 0, 0,
  429. /* Some fonts use a negative offset, while others use a
  430. * "normal" offset */
  431. x + (g->x < 0 ? term->cell_width : 0) + g->x,
  432. y + font_baseline(term) - g->y,
  433. g->width, g->height);
  434. }
  435. }
  436. }
  437. }
  438. pixman_image_unref(clr_pix);
  439. /* Underline */
  440. if (cell->attrs.underline) {
  441. draw_underline(term, pix, attrs_to_font(term, &cell->attrs),
  442. &fg, x, y, cell_cols);
  443. }
  444. if (cell->attrs.strikethrough) {
  445. draw_strikeout(term, pix, attrs_to_font(term, &cell->attrs),
  446. &fg, x, y, cell_cols);
  447. }
  448. draw_cursor:
  449. if (has_cursor && (term->cursor_style != CURSOR_BLOCK || !term->kbd_focus))
  450. draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols);
  451. pixman_image_set_clip_region32(pix, NULL);
  452. return cell_cols;
  453. }
  454. static void
  455. render_urgency(struct terminal *term, struct buffer *buf)
  456. {
  457. uint32_t red = term->colors.table[1];
  458. pixman_color_t bg = color_hex_to_pixman(red);
  459. if (term->is_searching)
  460. color_dim(&bg);
  461. int width = min(min(term->margins.left, term->margins.right),
  462. min(term->margins.top, term->margins.bottom));
  463. pixman_image_fill_rectangles(
  464. PIXMAN_OP_SRC, buf->pix[0], &bg, 4,
  465. (pixman_rectangle16_t[]){
  466. /* Top */
  467. {0, 0, term->width, width},
  468. /* Bottom */
  469. {0, term->height - width, term->width, width},
  470. /* Left */
  471. {0, width, width, term->height - 2 * width},
  472. /* Right */
  473. {term->width - width, width, width, term->height - 2 * width},
  474. });
  475. }
  476. static void
  477. render_margin(struct terminal *term, struct buffer *buf,
  478. int start_line, int end_line, bool apply_damage)
  479. {
  480. /* Fill area outside the cell grid with the default background color */
  481. const int rmargin = term->width - term->margins.right;
  482. const int bmargin = term->height - term->margins.bottom;
  483. const int line_count = end_line - start_line;
  484. uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
  485. pixman_color_t bg = color_hex_to_pixman_with_alpha(
  486. _bg,
  487. (_bg == (term->reverse ? term->colors.fg : term->colors.bg)
  488. ? term->colors.alpha : 0xffff));
  489. if (term->is_searching)
  490. color_dim(&bg);
  491. pixman_image_fill_rectangles(
  492. PIXMAN_OP_SRC, buf->pix[0], &bg, 4,
  493. (pixman_rectangle16_t[]){
  494. /* Top */
  495. {0, 0, term->width, term->margins.top},
  496. /* Bottom */
  497. {0, bmargin, term->width, term->margins.bottom},
  498. /* Left */
  499. {0, term->margins.top + start_line * term->cell_height,
  500. term->margins.left, line_count * term->cell_height},
  501. /* Right */
  502. {rmargin, term->margins.top + start_line * term->cell_height,
  503. term->margins.right, line_count * term->cell_height},
  504. });
  505. if (term->render.urgency)
  506. render_urgency(term, buf);
  507. if (apply_damage) {
  508. /* Top */
  509. wl_surface_damage_buffer(
  510. term->window->surface, 0, 0, term->width, term->margins.top);
  511. /* Bottom */
  512. wl_surface_damage_buffer(
  513. term->window->surface, 0, bmargin, term->width, term->margins.bottom);
  514. /* Left */
  515. wl_surface_damage_buffer(
  516. term->window->surface,
  517. 0, term->margins.top + start_line * term->cell_height,
  518. term->margins.left, line_count * term->cell_height);
  519. /* Right */
  520. wl_surface_damage_buffer(
  521. term->window->surface,
  522. rmargin, term->margins.top + start_line * term->cell_height,
  523. term->margins.right, line_count * term->cell_height);
  524. }
  525. }
  526. static void
  527. grid_render_scroll(struct terminal *term, struct buffer *buf,
  528. const struct damage *dmg)
  529. {
  530. int height = (dmg->region.end - dmg->region.start - dmg->lines) * term->cell_height;
  531. LOG_DBG(
  532. "damage: SCROLL: %d-%d by %d lines",
  533. dmg->region.start, dmg->region.end, dmg->lines);
  534. if (height <= 0)
  535. return;
  536. #if TIME_SCROLL_DAMAGE
  537. struct timeval start_time;
  538. gettimeofday(&start_time, NULL);
  539. #endif
  540. int dst_y = term->margins.top + (dmg->region.start + 0) * term->cell_height;
  541. int src_y = term->margins.top + (dmg->region.start + dmg->lines) * term->cell_height;
  542. /*
  543. * SHM scrolling can be *much* faster, but it depends on how many
  544. * lines we're scrolling, and how much repairing we need to do.
  545. *
  546. * In short, scrolling a *large* number of rows is faster with a
  547. * memmove, while scrolling a *small* number of lines is faster
  548. * with SHM scrolling.
  549. *
  550. * However, since we need to restore the scrolling regions when
  551. * SHM scrolling, we also need to take this into account.
  552. *
  553. * Finally, we also have to restore the window margins, and this
  554. * is a *huge* performance hit when scrolling a large number of
  555. * lines (in addition to the sloweness of SHM scrolling as
  556. * method).
  557. *
  558. * So, we need to figure out when to SHM scroll, and when to
  559. * memmove.
  560. *
  561. * For now, assume that the both methods perform roughly the same,
  562. * given an equal number of bytes to move/allocate, and use the
  563. * method that results in the least amount of bytes to touch.
  564. *
  565. * Since number of lines directly translates to bytes, we can
  566. * simply count lines.
  567. *
  568. * SHM scrolling needs to first "move" (punch hole + allocate)
  569. * dmg->lines number of lines, and then we need to restore
  570. * the bottom scroll region.
  571. *
  572. * If the total number of lines is less than half the screen - use
  573. * SHM. Otherwise use memmove.
  574. */
  575. bool try_shm_scroll =
  576. shm_can_scroll(buf) && (
  577. dmg->lines +
  578. dmg->region.start +
  579. (term->rows - dmg->region.end)) < term->rows / 2;
  580. bool did_shm_scroll = false;
  581. //try_shm_scroll = false;
  582. //try_shm_scroll = true;
  583. if (try_shm_scroll) {
  584. did_shm_scroll = shm_scroll(
  585. term->wl->shm, buf, dmg->lines * term->cell_height,
  586. term->margins.top, dmg->region.start * term->cell_height,
  587. term->margins.bottom, (term->rows - dmg->region.end) * term->cell_height);
  588. }
  589. if (did_shm_scroll) {
  590. /* Restore margins */
  591. render_margin(
  592. term, buf, dmg->region.end - dmg->lines, term->rows, false);
  593. } else {
  594. /* Fallback for when we either cannot do SHM scrolling, or it failed */
  595. uint8_t *raw = buf->mmapped;
  596. memmove(raw + dst_y * buf->stride,
  597. raw + src_y * buf->stride,
  598. height * buf->stride);
  599. }
  600. #if TIME_SCROLL_DAMAGE
  601. struct timeval end_time;
  602. gettimeofday(&end_time, NULL);
  603. struct timeval memmove_time;
  604. timersub(&end_time, &start_time, &memmove_time);
  605. LOG_INFO("scrolled %dKB (%d lines) using %s in %lds %ldus",
  606. height * buf->stride / 1024, dmg->lines,
  607. did_shm_scroll ? "SHM" : try_shm_scroll ? "memmove (SHM failed)" : "memmove",
  608. memmove_time.tv_sec, memmove_time.tv_usec);
  609. #endif
  610. wl_surface_damage_buffer(
  611. term->window->surface, term->margins.left, dst_y,
  612. term->width - term->margins.left - term->margins.right, height);
  613. }
  614. static void
  615. grid_render_scroll_reverse(struct terminal *term, struct buffer *buf,
  616. const struct damage *dmg)
  617. {
  618. int height = (dmg->region.end - dmg->region.start - dmg->lines) * term->cell_height;
  619. LOG_DBG(
  620. "damage: SCROLL REVERSE: %d-%d by %d lines",
  621. dmg->region.start, dmg->region.end, dmg->lines);
  622. if (height <= 0)
  623. return;
  624. #if TIME_SCROLL_DAMAGE
  625. struct timeval start_time;
  626. gettimeofday(&start_time, NULL);
  627. #endif
  628. int src_y = term->margins.top + (dmg->region.start + 0) * term->cell_height;
  629. int dst_y = term->margins.top + (dmg->region.start + dmg->lines) * term->cell_height;
  630. bool try_shm_scroll =
  631. shm_can_scroll(buf) && (
  632. dmg->lines +
  633. dmg->region.start +
  634. (term->rows - dmg->region.end)) < term->rows / 2;
  635. bool did_shm_scroll = false;
  636. if (try_shm_scroll) {
  637. did_shm_scroll = shm_scroll(
  638. term->wl->shm, buf, -dmg->lines * term->cell_height,
  639. term->margins.top, dmg->region.start * term->cell_height,
  640. term->margins.bottom, (term->rows - dmg->region.end) * term->cell_height);
  641. }
  642. if (did_shm_scroll) {
  643. /* Restore margins */
  644. render_margin(
  645. term, buf, dmg->region.start, dmg->region.start + dmg->lines, false);
  646. } else {
  647. /* Fallback for when we either cannot do SHM scrolling, or it failed */
  648. uint8_t *raw = buf->mmapped;
  649. memmove(raw + dst_y * buf->stride,
  650. raw + src_y * buf->stride,
  651. height * buf->stride);
  652. }
  653. #if TIME_SCROLL_DAMAGE
  654. struct timeval end_time;
  655. gettimeofday(&end_time, NULL);
  656. struct timeval memmove_time;
  657. timersub(&end_time, &start_time, &memmove_time);
  658. LOG_INFO("scrolled REVERSE %dKB (%d lines) using %s in %lds %ldus",
  659. height * buf->stride / 1024, dmg->lines,
  660. did_shm_scroll ? "SHM" : try_shm_scroll ? "memmove (SHM failed)" : "memmove",
  661. memmove_time.tv_sec, memmove_time.tv_usec);
  662. #endif
  663. wl_surface_damage_buffer(
  664. term->window->surface, term->margins.left, dst_y,
  665. term->width - term->margins.left - term->margins.right, height);
  666. }
  667. static void
  668. render_sixel(struct terminal *term, pixman_image_t *pix,
  669. const struct sixel *sixel)
  670. {
  671. int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1);
  672. int first_visible_row = -1;
  673. for (size_t i = sixel->pos.row; i < sixel->pos.row + sixel->rows; i++) {
  674. int row = i & (term->grid->num_rows - 1);
  675. if (view_end >= term->grid->view) {
  676. /* Not wrapped */
  677. if (row >= term->grid->view && row <= view_end) {
  678. first_visible_row = i;
  679. break;
  680. }
  681. } else {
  682. /* Wrapped */
  683. if (row >= term->grid->view || row <= view_end) {
  684. first_visible_row = i;
  685. break;
  686. }
  687. }
  688. }
  689. if (first_visible_row < 0)
  690. return;
  691. /* First visible (0 based) row of the image */
  692. const int first_img_row = first_visible_row - sixel->pos.row;
  693. /* Map first visible line to current grid view */
  694. const int row = first_visible_row & (term->grid->num_rows - 1);
  695. const int view_aligned =
  696. (row - term->grid->view + term->grid->num_rows) & (term->grid->num_rows - 1);
  697. /* Translate row/column to x/y pixel values */
  698. const int x = term->margins.left + sixel->pos.col * term->cell_width;
  699. const int y = max(
  700. term->margins.top, term->margins.top + view_aligned * term->cell_height);
  701. /* Width/height, in pixels - and don't touch the window margins */
  702. const int width = min(sixel->width, term->width - x - term->margins.right);
  703. const int height = min(
  704. sixel->height - first_img_row * term->cell_height,
  705. term->height - y - term->margins.bottom);
  706. /* Verify we're not stepping outside the grid */
  707. assert(x >= term->margins.left);
  708. assert(y >= term->margins.top);
  709. assert(x + width <= term->width - term->margins.right);
  710. assert(y + height <= term->height - term->margins.bottom);
  711. pixman_image_composite32(
  712. PIXMAN_OP_SRC,
  713. sixel->pix,
  714. NULL,
  715. pix,
  716. 0, first_img_row * term->cell_height,
  717. 0, 0,
  718. x, y,
  719. width, height);
  720. wl_surface_damage_buffer(term->window->surface, x, y, width, height);
  721. }
  722. static void
  723. render_sixel_images(struct terminal *term, pixman_image_t *pix)
  724. {
  725. if (likely(tll_length(term->grid->sixel_images)) == 0)
  726. return;
  727. const int scrollback_end
  728. = (term->grid->offset + term->rows) & (term->grid->num_rows - 1);
  729. const int view_start
  730. = (term->grid->view
  731. - scrollback_end
  732. + term->grid->num_rows) & (term->grid->num_rows - 1);
  733. const int view_end = view_start + term->rows - 1;
  734. //LOG_DBG("SIXELS: %zu images, view=%d-%d",
  735. // tll_length(term->grid->sixel_images), view_start, view_end);
  736. tll_foreach(term->grid->sixel_images, it) {
  737. const struct sixel *six = &it->item;
  738. const int start
  739. = (six->pos.row
  740. - scrollback_end
  741. + term->grid->num_rows) & (term->grid->num_rows - 1);
  742. const int end = start + six->rows - 1;
  743. //LOG_DBG(" sixel: %d-%d", start, end);
  744. if (start > view_end) {
  745. /* Sixel starts after view ends, no need to try to render it */
  746. continue;
  747. } else if (end < view_start) {
  748. /* Image ends before view starts. Since the image list is
  749. * sorted, we can safely stop here */
  750. break;
  751. }
  752. render_sixel(term, pix, &it->item);
  753. }
  754. }
  755. static void
  756. render_row(struct terminal *term, pixman_image_t *pix, struct row *row,
  757. int row_no, int cursor_col)
  758. {
  759. for (int col = term->cols - 1; col >= 0; col--)
  760. render_cell(term, pix, row, col, row_no, cursor_col == col);
  761. }
  762. int
  763. render_worker_thread(void *_ctx)
  764. {
  765. struct render_worker_context *ctx = _ctx;
  766. struct terminal *term = ctx->term;
  767. const int my_id = ctx->my_id;
  768. free(ctx);
  769. char proc_title[16];
  770. snprintf(proc_title, sizeof(proc_title), "foot:render:%d", my_id);
  771. if (prctl(PR_SET_NAME, proc_title, 0, 0, 0) < 0)
  772. LOG_ERRNO("render worker %d: failed to set process title", my_id);
  773. sem_t *start = &term->render.workers.start;
  774. sem_t *done = &term->render.workers.done;
  775. mtx_t *lock = &term->render.workers.lock;
  776. while (true) {
  777. sem_wait(start);
  778. struct buffer *buf = term->render.workers.buf;
  779. bool frame_done = false;
  780. /* Translate offset-relative cursor row to view-relative */
  781. struct coord cursor = {-1, -1};
  782. if (!term->hide_cursor) {
  783. cursor = term->grid->cursor.point;
  784. cursor.row += term->grid->offset;
  785. cursor.row -= term->grid->view;
  786. cursor.row &= term->grid->num_rows - 1;
  787. }
  788. while (!frame_done) {
  789. mtx_lock(lock);
  790. assert(tll_length(term->render.workers.queue) > 0);
  791. int row_no = tll_pop_front(term->render.workers.queue);
  792. mtx_unlock(lock);
  793. switch (row_no) {
  794. default: {
  795. assert(buf != NULL);
  796. struct row *row = grid_row_in_view(term->grid, row_no);
  797. int cursor_col = cursor.row == row_no ? cursor.col : -1;
  798. render_row(term, buf->pix[my_id], row, row_no, cursor_col);
  799. break;
  800. }
  801. case -1:
  802. frame_done = true;
  803. sem_post(done);
  804. break;
  805. case -2:
  806. return 0;
  807. }
  808. }
  809. };
  810. return -1;
  811. }
  812. struct csd_data {
  813. int x;
  814. int y;
  815. int width;
  816. int height;
  817. };
  818. static struct csd_data
  819. get_csd_data(const struct terminal *term, enum csd_surface surf_idx)
  820. {
  821. assert(term->window->use_csd == CSD_YES);
  822. /* Only title bar is rendered in maximized mode */
  823. const int border_width = !term->window->is_maximized
  824. ? term->conf->csd.border_width * term->scale : 0;
  825. const int title_height = term->window->is_fullscreen
  826. ? 0
  827. : term->conf->csd.title_height * term->scale;
  828. const int button_width = !term->window->is_fullscreen
  829. ? term->conf->csd.button_width * term->scale : 0;
  830. const int button_close_width = term->width >= 1 * button_width
  831. ? button_width : 0;
  832. const int button_maximize_width = term->width >= 2 * button_width
  833. ? button_width : 0;
  834. const int button_minimize_width = term->width >= 3 * button_width
  835. ? button_width : 0;
  836. switch (surf_idx) {
  837. case CSD_SURF_TITLE: return (struct csd_data){ 0, -title_height, term->width, title_height};
  838. case CSD_SURF_LEFT: return (struct csd_data){-border_width, -title_height, border_width, title_height + term->height};
  839. case CSD_SURF_RIGHT: return (struct csd_data){ term->width, -title_height, border_width, title_height + term->height};
  840. case CSD_SURF_TOP: return (struct csd_data){-border_width, -title_height - border_width, term->width + 2 * border_width, border_width};
  841. case CSD_SURF_BOTTOM: return (struct csd_data){-border_width, term->height, term->width + 2 * border_width, border_width};
  842. /* Positioned relative to CSD_SURF_TITLE */
  843. case CSD_SURF_MINIMIZE: return (struct csd_data){term->width - 3 * button_width, 0, button_minimize_width, title_height};
  844. case CSD_SURF_MAXIMIZE: return (struct csd_data){term->width - 2 * button_width, 0, button_maximize_width, title_height};
  845. case CSD_SURF_CLOSE: return (struct csd_data){term->width - 1 * button_width, 0, button_close_width, title_height};
  846. case CSD_SURF_COUNT:
  847. assert(false);
  848. return (struct csd_data){0};
  849. }
  850. assert(false);
  851. return (struct csd_data){0};
  852. }
  853. static void
  854. csd_commit(struct terminal *term, struct wl_surface *surf, struct buffer *buf)
  855. {
  856. wl_surface_attach(surf, buf->wl_buf, 0, 0);
  857. wl_surface_damage_buffer(surf, 0, 0, buf->width, buf->height);
  858. wl_surface_set_buffer_scale(surf, term->scale);
  859. wl_surface_commit(surf);
  860. }
  861. static void
  862. render_csd_part(struct terminal *term,
  863. struct wl_surface *surf, struct buffer *buf,
  864. int width, int height, pixman_color_t *color)
  865. {
  866. assert(term->window->use_csd == CSD_YES);
  867. pixman_image_t *src = pixman_image_create_solid_fill(color);
  868. pixman_image_fill_rectangles(
  869. PIXMAN_OP_SRC, buf->pix[0], color, 1,
  870. &(pixman_rectangle16_t){0, 0, buf->width, buf->height});
  871. pixman_image_unref(src);
  872. }
  873. static void
  874. render_csd_title(struct terminal *term)
  875. {
  876. assert(term->window->use_csd == CSD_YES);
  877. struct csd_data info = get_csd_data(term, CSD_SURF_TITLE);
  878. struct wl_surface *surf = term->window->csd.surface[CSD_SURF_TITLE];
  879. assert(info.width > 0 && info.height > 0);
  880. unsigned long cookie = shm_cookie_csd(term, CSD_SURF_TITLE);
  881. struct buffer *buf = shm_get_buffer(
  882. term->wl->shm, info.width, info.height, cookie, false, 1);
  883. uint32_t _color = term->colors.default_fg;
  884. uint16_t alpha = 0xffff;
  885. if (term->conf->csd.color.title_set) {
  886. _color = term->conf->csd.color.title;
  887. alpha = _color >> 24 | (_color >> 24 << 8);
  888. }
  889. pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha);
  890. if (!term->visual_focus)
  891. color_dim(&color);
  892. render_csd_part(term, surf, buf, info.width, info.height, &color);
  893. csd_commit(term, surf, buf);
  894. }
  895. static void
  896. render_csd_border(struct terminal *term, enum csd_surface surf_idx)
  897. {
  898. assert(term->window->use_csd == CSD_YES);
  899. assert(surf_idx >= CSD_SURF_LEFT && surf_idx <= CSD_SURF_BOTTOM);
  900. struct csd_data info = get_csd_data(term, surf_idx);
  901. struct wl_surface *surf = term->window->csd.surface[surf_idx];
  902. if (info.width == 0 || info.height == 0)
  903. return;
  904. unsigned long cookie = shm_cookie_csd(term, surf_idx);
  905. struct buffer *buf = shm_get_buffer(
  906. term->wl->shm, info.width, info.height, cookie, false, 1);
  907. pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0);
  908. render_csd_part(term, surf, buf, info.width, info.height, &color);
  909. csd_commit(term, surf, buf);
  910. }
  911. static void
  912. render_csd_button_minimize(struct terminal *term, struct buffer *buf)
  913. {
  914. pixman_color_t color = color_hex_to_pixman(term->colors.default_bg);
  915. pixman_image_t *src = pixman_image_create_solid_fill(&color);
  916. const int max_height = buf->height / 2;
  917. const int max_width = buf->width / 2;
  918. int width = max_width;
  919. int height = max_width / 2;
  920. if (height > max_height) {
  921. height = max_height;
  922. width = height * 2;
  923. }
  924. assert(width <= max_width);
  925. assert(height <= max_height);
  926. int x_margin = (buf->width - width) / 2.;
  927. int y_margin = (buf->height - height) / 2.;
  928. pixman_triangle_t tri = {
  929. .p1 = {
  930. .x = pixman_int_to_fixed(x_margin),
  931. .y = pixman_int_to_fixed(y_margin),
  932. },
  933. .p2 = {
  934. .x = pixman_int_to_fixed(x_margin + width),
  935. .y = pixman_int_to_fixed(y_margin),
  936. },
  937. .p3 = {
  938. .x = pixman_int_to_fixed(buf->width / 2),
  939. .y = pixman_int_to_fixed(y_margin + height),
  940. },
  941. };
  942. pixman_composite_triangles(
  943. PIXMAN_OP_OVER, src, buf->pix[0], PIXMAN_a1,
  944. 0, 0, 0, 0, 1, &tri);
  945. pixman_image_unref(src);
  946. }
  947. static void
  948. render_csd_button_maximize_maximized(
  949. struct terminal *term, struct buffer *buf)
  950. {
  951. pixman_color_t color = color_hex_to_pixman(term->colors.default_bg);
  952. pixman_image_t *src = pixman_image_create_solid_fill(&color);
  953. const int max_height = buf->height / 3;
  954. const int max_width = buf->width / 3;
  955. int width = min(max_height, max_width);
  956. int thick = 1 * term->scale;
  957. const int x_margin = (buf->width - width) / 2;
  958. const int y_margin = (buf->height - width) / 2;
  959. pixman_image_fill_rectangles(
  960. PIXMAN_OP_SRC, buf->pix[0], &color, 4,
  961. (pixman_rectangle16_t[]){
  962. {x_margin, y_margin, width, thick},
  963. {x_margin, y_margin + thick, thick, width - 2 * thick},
  964. {x_margin + width - thick, y_margin + thick, thick, width - 2 * thick},
  965. {x_margin, y_margin + width - thick, width, thick}});
  966. pixman_image_unref(src);
  967. }
  968. static void
  969. render_csd_button_maximize_window(
  970. struct terminal *term, struct buffer *buf)
  971. {
  972. pixman_color_t color = color_hex_to_pixman(term->colors.default_bg);
  973. pixman_image_t *src = pixman_image_create_solid_fill(&color);
  974. const int max_height = buf->height / 2;
  975. const int max_width = buf->width / 2;
  976. int width = max_width;
  977. int height = max_width / 2;
  978. if (height > max_height) {
  979. height = max_height;
  980. width = height * 2;
  981. }
  982. assert(width <= max_width);
  983. assert(height <= max_height);
  984. int x_margin = (buf->width - width) / 2.;
  985. int y_margin = (buf->height - height) / 2.;
  986. pixman_triangle_t tri = {
  987. .p1 = {
  988. .x = pixman_int_to_fixed(buf->width / 2),
  989. .y = pixman_int_to_fixed(y_margin),
  990. },
  991. .p2 = {
  992. .x = pixman_int_to_fixed(x_margin),
  993. .y = pixman_int_to_fixed(y_margin + height),
  994. },
  995. .p3 = {
  996. .x = pixman_int_to_fixed(x_margin + width),
  997. .y = pixman_int_to_fixed(y_margin + height),
  998. },
  999. };
  1000. pixman_composite_triangles(
  1001. PIXMAN_OP_OVER, src, buf->pix[0], PIXMAN_a1,
  1002. 0, 0, 0, 0, 1, &tri);
  1003. pixman_image_unref(src);
  1004. }
  1005. static void
  1006. render_csd_button_maximize(struct terminal *term, struct buffer *buf)
  1007. {
  1008. if (term->window->is_maximized)
  1009. render_csd_button_maximize_maximized(term, buf);
  1010. else
  1011. render_csd_button_maximize_window(term, buf);
  1012. }
  1013. static void
  1014. render_csd_button_close(struct terminal *term, struct buffer *buf)
  1015. {
  1016. pixman_color_t color = color_hex_to_pixman(term->colors.default_bg);
  1017. pixman_image_t *src = pixman_image_create_solid_fill(&color);
  1018. const int max_height = buf->height / 3;
  1019. const int max_width = buf->width / 3;
  1020. int width = min(max_height, max_width);
  1021. const int x_margin = (buf->width - width) / 2;
  1022. const int y_margin = (buf->height - width) / 2;
  1023. pixman_image_fill_rectangles(
  1024. PIXMAN_OP_SRC, buf->pix[0], &color, 1,
  1025. &(pixman_rectangle16_t){x_margin, y_margin, width, width});
  1026. pixman_image_unref(src);
  1027. }
  1028. static void
  1029. render_csd_button(struct terminal *term, enum csd_surface surf_idx)
  1030. {
  1031. assert(term->window->use_csd == CSD_YES);
  1032. assert(surf_idx >= CSD_SURF_MINIMIZE && surf_idx <= CSD_SURF_CLOSE);
  1033. struct csd_data info = get_csd_data(term, surf_idx);
  1034. struct wl_surface *surf = term->window->csd.surface[surf_idx];
  1035. if (info.width == 0 || info.height == 0)
  1036. return;
  1037. unsigned long cookie = shm_cookie_csd(term, surf_idx);
  1038. struct buffer *buf = shm_get_buffer(
  1039. term->wl->shm, info.width, info.height, cookie, false, 1);
  1040. uint32_t _color;
  1041. uint16_t alpha = 0xffff;
  1042. bool is_active = false;
  1043. const bool *is_set = NULL;
  1044. const uint32_t *conf_color = NULL;
  1045. switch (surf_idx) {
  1046. case CSD_SURF_MINIMIZE:
  1047. _color = term->colors.default_table[4]; /* blue */
  1048. is_set = &term->conf->csd.color.minimize_set;
  1049. conf_color = &term->conf->csd.color.minimize;
  1050. is_active = term->active_surface == TERM_SURF_BUTTON_MINIMIZE;
  1051. break;
  1052. case CSD_SURF_MAXIMIZE:
  1053. _color = term->colors.default_table[2]; /* green */
  1054. is_set = &term->conf->csd.color.maximize_set;
  1055. conf_color = &term->conf->csd.color.maximize;
  1056. is_active = term->active_surface == TERM_SURF_BUTTON_MAXIMIZE;
  1057. break;
  1058. case CSD_SURF_CLOSE:
  1059. _color = term->colors.default_table[1]; /* red */
  1060. is_set = &term->conf->csd.color.close_set;
  1061. conf_color = &term->conf->csd.color.close;
  1062. is_active = term->active_surface == TERM_SURF_BUTTON_CLOSE;
  1063. break;
  1064. default:
  1065. assert(false);
  1066. break;
  1067. }
  1068. if (is_active) {
  1069. if (*is_set) {
  1070. _color = *conf_color;
  1071. alpha = _color >> 24 | (_color >> 24 << 8);
  1072. }
  1073. } else {
  1074. _color = 0;
  1075. alpha = 0;
  1076. }
  1077. pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha);
  1078. if (!term->visual_focus)
  1079. color_dim(&color);
  1080. render_csd_part(term, surf, buf, info.width, info.height, &color);
  1081. switch (surf_idx) {
  1082. case CSD_SURF_MINIMIZE: render_csd_button_minimize(term, buf); break;
  1083. case CSD_SURF_MAXIMIZE: render_csd_button_maximize(term, buf); break;
  1084. case CSD_SURF_CLOSE: render_csd_button_close(term, buf); break;
  1085. break;
  1086. default:
  1087. assert(false);
  1088. break;
  1089. }
  1090. csd_commit(term, surf, buf);
  1091. }
  1092. static void
  1093. render_csd(struct terminal *term)
  1094. {
  1095. assert(term->window->use_csd == CSD_YES);
  1096. if (term->window->is_fullscreen)
  1097. return;
  1098. for (size_t i = 0; i < CSD_SURF_COUNT; i++) {
  1099. struct csd_data info = get_csd_data(term, i);
  1100. const int x = info.x;
  1101. const int y = info.y;
  1102. const int width = info.width;
  1103. const int height = info.height;
  1104. struct wl_surface *surf = term->window->csd.surface[i];
  1105. struct wl_subsurface *sub = term->window->csd.sub_surface[i];
  1106. assert(surf != NULL);
  1107. assert(sub != NULL);
  1108. if (width == 0 || height == 0) {
  1109. wl_subsurface_set_position(sub, 0, 0);
  1110. wl_surface_attach(surf, NULL, 0, 0);
  1111. wl_surface_commit(surf);
  1112. continue;
  1113. }
  1114. wl_subsurface_set_position(sub, x / term->scale, y / term->scale);
  1115. }
  1116. for (size_t i = CSD_SURF_LEFT; i <= CSD_SURF_BOTTOM; i++)
  1117. render_csd_border(term, i);
  1118. for (size_t i = CSD_SURF_MINIMIZE; i <= CSD_SURF_CLOSE; i++)
  1119. render_csd_button(term, i);
  1120. render_csd_title(term);
  1121. }
  1122. static void
  1123. render_osd(struct terminal *term,
  1124. struct wl_surface *surf, struct wl_subsurface *sub_surf,
  1125. struct buffer *buf,
  1126. const wchar_t *text, uint32_t _fg, uint32_t _bg,
  1127. unsigned width, unsigned height, unsigned x, unsigned y)
  1128. {
  1129. pixman_color_t bg = color_hex_to_pixman(_bg);
  1130. pixman_image_fill_rectangles(
  1131. PIXMAN_OP_SRC, buf->pix[0], &bg, 1,
  1132. &(pixman_rectangle16_t){0, 0, width, height});
  1133. struct fcft_font *font = term->fonts[0];
  1134. pixman_color_t fg = color_hex_to_pixman(_fg);
  1135. for (size_t i = 0; i < wcslen(text); i++) {
  1136. const struct fcft_glyph *glyph = fcft_glyph_rasterize(
  1137. font, text[i], term->font_subpixel);
  1138. if (glyph == NULL)
  1139. continue;
  1140. pixman_image_t *src = pixman_image_create_solid_fill(&fg);
  1141. pixman_image_composite32(
  1142. PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0,
  1143. x + glyph->x, y + font_baseline(term) - glyph->y,
  1144. glyph->width, glyph->height);
  1145. pixman_image_unref(src);
  1146. x += term->cell_width;
  1147. }
  1148. quirk_weston_subsurface_desync_on(sub_surf);
  1149. wl_surface_attach(surf, buf->wl_buf, 0, 0);
  1150. wl_surface_damage_buffer(surf, 0, 0, width, height);
  1151. wl_surface_set_buffer_scale(surf, term->scale);
  1152. struct wl_region *region = wl_compositor_create_region(term->wl->compositor);
  1153. if (region != NULL) {
  1154. wl_region_add(region, 0, 0, width, height);
  1155. wl_surface_set_opaque_region(surf, region);
  1156. wl_region_destroy(region);
  1157. }
  1158. wl_surface_commit(surf);
  1159. quirk_weston_subsurface_desync_off(sub_surf);
  1160. }
  1161. static void
  1162. render_scrollback_position(struct terminal *term)
  1163. {
  1164. if (term->conf->scrollback.indicator.position == SCROLLBACK_INDICATOR_POSITION_NONE)
  1165. return;
  1166. struct wayland *wayl = term->wl;
  1167. struct wl_window *win = term->window;
  1168. if (term->grid->view == term->grid->offset) {
  1169. if (win->scrollback_indicator_surface != NULL) {
  1170. wl_subsurface_destroy(win->scrollback_indicator_sub_surface);
  1171. wl_surface_destroy(win->scrollback_indicator_surface);
  1172. win->scrollback_indicator_surface = NULL;
  1173. win->scrollback_indicator_sub_surface = NULL;
  1174. }
  1175. return;
  1176. }
  1177. if (win->scrollback_indicator_surface == NULL) {
  1178. win->scrollback_indicator_surface
  1179. = wl_compositor_create_surface(wayl->compositor);
  1180. if (win->scrollback_indicator_surface == NULL) {
  1181. LOG_ERR("failed to create scrollback indicator surface");
  1182. return;
  1183. }
  1184. wl_surface_set_user_data(win->scrollback_indicator_surface, win);
  1185. term->window->scrollback_indicator_sub_surface
  1186. = wl_subcompositor_get_subsurface(
  1187. wayl->sub_compositor,
  1188. win->scrollback_indicator_surface,
  1189. win->surface);
  1190. if (win->scrollback_indicator_sub_surface == NULL) {
  1191. LOG_ERR("failed to create scrollback indicator sub-surface");
  1192. wl_surface_destroy(win->scrollback_indicator_surface);
  1193. win->scrollback_indicator_surface = NULL;
  1194. return;
  1195. }
  1196. wl_subsurface_set_sync(win->scrollback_indicator_sub_surface);
  1197. }
  1198. assert(win->scrollback_indicator_surface != NULL);
  1199. assert(win->scrollback_indicator_sub_surface != NULL);
  1200. /* Find absolute row number of the scrollback start */
  1201. int scrollback_start = term->grid->offset + term->rows;
  1202. int empty_rows = 0;
  1203. while (term->grid->rows[scrollback_start & (term->grid->num_rows - 1)] == NULL) {
  1204. scrollback_start++;
  1205. empty_rows++;
  1206. }
  1207. /* Rebase viewport against scrollback start (so that 0 is at
  1208. * the beginning of the scrollback) */
  1209. int rebased_view = term->grid->view - scrollback_start + term->grid->num_rows;
  1210. rebased_view &= term->grid->num_rows - 1;
  1211. /* How much of the scrollback is actually used? */
  1212. int populated_rows = term->grid->num_rows - empty_rows;
  1213. assert(populated_rows > 0);
  1214. assert(populated_rows <= term->grid->num_rows);
  1215. /*
  1216. * How far down in the scrollback we are.
  1217. *
  1218. * 0% -> at the beginning of the scrollback
  1219. * 100% -> at the bottom, i.e. where new lines are inserted
  1220. */
  1221. double percent =
  1222. rebased_view + term->rows == populated_rows
  1223. ? 1.0
  1224. : (double)rebased_view / (populated_rows - term->rows);
  1225. wchar_t _text[64];
  1226. const wchar_t *text = _text;
  1227. int cell_count = 0;
  1228. /* *What* to render */
  1229. switch (term->conf->scrollback.indicator.format) {
  1230. case SCROLLBACK_INDICATOR_FORMAT_PERCENTAGE:
  1231. swprintf(_text, sizeof(_text) / sizeof(_text[0]), L"%u%%", (int)(100 * percent));
  1232. cell_count = 3;
  1233. break;
  1234. case SCROLLBACK_INDICATOR_FORMAT_LINENO:
  1235. swprintf(_text, sizeof(_text) / sizeof(_text[0]), L"%d", rebased_view + 1);
  1236. cell_count = 1 + (int)log10(term->grid->num_rows);
  1237. break;
  1238. case SCROLLBACK_INDICATOR_FORMAT_TEXT:
  1239. text = term->conf->scrollback.indicator.text;
  1240. cell_count = wcslen(text);
  1241. break;
  1242. }
  1243. const int scale = term->scale;
  1244. const int margin = 3 * scale;
  1245. const int width = 2 * margin + cell_count * term->cell_width;
  1246. const int height = 2 * margin + term->cell_height;
  1247. unsigned long cookie = shm_cookie_scrollback_indicator(term);
  1248. struct buffer *buf = shm_get_buffer(
  1249. term->wl->shm, width, height, cookie, false, 1);
  1250. /* *Where* to render - parent relative coordinates */
  1251. int surf_top = 0;
  1252. switch (term->conf->scrollback.indicator.position) {
  1253. case SCROLLBACK_INDICATOR_POSITION_NONE:
  1254. assert(false);
  1255. return;
  1256. case SCROLLBACK_INDICATOR_POSITION_FIXED:
  1257. surf_top = term->cell_height - margin;
  1258. break;
  1259. case SCROLLBACK_INDICATOR_POSITION_RELATIVE: {
  1260. int lines = term->rows - 2; /* Avoid using first and last rows */
  1261. if (term->is_searching) {
  1262. /* Make sure we don't collide with the scrollback search box */
  1263. lines--;
  1264. }
  1265. assert(lines > 0);
  1266. int pixels = lines * term->cell_height - height + 2 * margin;
  1267. surf_top = term->cell_height - margin + (int)(percent * pixels);
  1268. break;
  1269. }
  1270. }
  1271. wl_subsurface_set_position(
  1272. win->scrollback_indicator_sub_surface,
  1273. (term->width - margin - width) / scale,
  1274. (term->margins.top + surf_top) / scale);
  1275. render_osd(
  1276. term,
  1277. win->scrollback_indicator_surface, win->scrollback_indicator_sub_surface,
  1278. buf, text,
  1279. term->colors.table[0], term->colors.table[8 + 4],
  1280. width, height, width - margin - wcslen(text) * term->cell_width, margin);
  1281. }
  1282. static void
  1283. render_render_timer(struct terminal *term, struct timeval render_time)
  1284. {
  1285. struct wl_window *win = term->window;
  1286. wchar_t text[256];
  1287. double usecs = render_time.tv_sec * 1000000 + render_time.tv_usec;
  1288. swprintf(text, sizeof(text) / sizeof(text[0]), L"%.2f µs", usecs);
  1289. const int cell_count = wcslen(text);
  1290. const int margin = 3 * term->scale;
  1291. const int width = 2 * margin + cell_count * term->cell_width;
  1292. const int height = 2 * margin + term->cell_height;
  1293. unsigned long cookie = shm_cookie_render_timer(term);
  1294. struct buffer *buf = shm_get_buffer(
  1295. term->wl->shm, width, height, cookie, false, 1);
  1296. wl_subsurface_set_position(
  1297. win->render_timer_sub_surface,
  1298. margin / term->scale,
  1299. (term->margins.top + term->cell_height - margin) / term->scale);
  1300. render_osd(
  1301. term,
  1302. win->render_timer_surface, win->render_timer_sub_surface,
  1303. buf, text,
  1304. term->colors.table[0], term->colors.table[8 + 1],
  1305. width, height, margin, margin);
  1306. }
  1307. static void frame_callback(
  1308. void *data, struct wl_callback *wl_callback, uint32_t callback_data);
  1309. static const struct wl_callback_listener frame_listener = {
  1310. .done = &frame_callback,
  1311. };
  1312. static void
  1313. grid_render(struct terminal *term)
  1314. {
  1315. if (term->is_shutting_down)
  1316. return;
  1317. struct timeval start_time;
  1318. if (term->conf->tweak.render_timer_osd || term->conf->tweak.render_timer_log)
  1319. gettimeofday(&start_time, NULL);
  1320. assert(term->width > 0);
  1321. assert(term->height > 0);
  1322. unsigned long cookie = shm_cookie_grid(term);
  1323. struct buffer *buf = shm_get_buffer(
  1324. term->wl->shm, term->width, term->height, cookie, true, 1 + term->render.workers.count);
  1325. /* If we resized the window, or is flashing, or just stopped flashing */
  1326. if (term->render.last_buf != buf ||
  1327. term->flash.active || term->render.was_flashing ||
  1328. term->is_searching != term->render.was_searching ||
  1329. term->render.margins)
  1330. {
  1331. if (term->render.last_buf != NULL &&
  1332. term->render.last_buf->width == buf->width &&
  1333. term->render.last_buf->height == buf->height &&
  1334. !term->flash.active &&
  1335. !term->render.was_flashing &&
  1336. term->is_searching == term->render.was_searching &&
  1337. !term->render.margins)
  1338. {
  1339. static bool has_warned = false;
  1340. if (!has_warned) {
  1341. LOG_WARN(
  1342. "it appears your Wayland compositor does not support "
  1343. "buffer re-use for SHM clients; expect lower "
  1344. "performance.");
  1345. has_warned = true;
  1346. }
  1347. assert(term->render.last_buf->size == buf->size);
  1348. memcpy(buf->mmapped, term->render.last_buf->mmapped, buf->size);
  1349. }
  1350. else {
  1351. tll_free(term->grid->scroll_damage);
  1352. render_margin(term, buf, 0, term->rows, true);
  1353. term_damage_view(term);
  1354. }
  1355. term->render.last_buf = buf;
  1356. term->render.was_flashing = term->flash.active;
  1357. term->render.was_searching = term->is_searching;
  1358. }
  1359. tll_foreach(term->grid->scroll_damage, it) {
  1360. switch (it->item.type) {
  1361. case DAMAGE_SCROLL:
  1362. if (term->grid->view == term->grid->offset)
  1363. grid_render_scroll(term, buf, &it->item);
  1364. break;
  1365. case DAMAGE_SCROLL_REVERSE:
  1366. if (term->grid->view == term->grid->offset)
  1367. grid_render_scroll_reverse(term, buf, &it->item);
  1368. break;
  1369. case DAMAGE_SCROLL_IN_VIEW:
  1370. grid_render_scroll(term, buf, &it->item);
  1371. break;
  1372. case DAMAGE_SCROLL_REVERSE_IN_VIEW:
  1373. grid_render_scroll_reverse(term, buf, &it->item);
  1374. break;
  1375. }
  1376. tll_remove(term->grid->scroll_damage, it);
  1377. }
  1378. /*
  1379. * Ensure selected cells have their 'selected' bit set. This is
  1380. * normally "automatically" true - the bit is set when the
  1381. * selection is made.
  1382. *
  1383. * However, if the cell is updated (printed to) while the
  1384. * selection is active, the 'selected' bit is cleared. Checking
  1385. * for this and re-setting the bit in term_print() is too
  1386. * expensive performance wise.
  1387. *
  1388. * Instead, we synchronize the selection bits here and now. This
  1389. * makes the performance impact linear to the number of selected
  1390. * cells rather than to the number of updated cells.
  1391. *
  1392. * (note that selection_dirty_cells() will not set the dirty flag
  1393. * on cells where the 'selected' bit is already set)
  1394. */
  1395. selection_dirty_cells(term);
  1396. /* Mark old cursor cell as dirty, to force it to be re-rendered */
  1397. if (term->render.last_cursor.row != NULL && !term->render.last_cursor.hidden) {
  1398. struct row *row = term->render.last_cursor.row;
  1399. struct cell *cell = &row->cells[term->render.last_cursor.col];
  1400. cell->attrs.clean = 0;
  1401. row->dirty = true;
  1402. }
  1403. /* Remember current cursor position, for the next frame */
  1404. term->render.last_cursor.row = grid_row(term->grid, term->grid->cursor.point.row);
  1405. term->render.last_cursor.col = term->grid->cursor.point.col;
  1406. term->render.last_cursor.hidden = term->hide_cursor;
  1407. /* Mark current cursor cell as dirty, to ensure it is rendered */
  1408. if (!term->hide_cursor) {
  1409. const struct coord *cursor = &term->grid->cursor.point;
  1410. struct row *row = grid_row(term->grid, cursor->row);
  1411. struct cell *cell = &row->cells[cursor->col];
  1412. cell->attrs.clean = 0;
  1413. row->dirty = true;
  1414. }
  1415. /* Translate offset-relative row to view-relative, unless cursor
  1416. * is hidden, then we just set it to -1 */
  1417. struct coord cursor = {-1, -1};
  1418. if (!term->hide_cursor) {
  1419. cursor = term->grid->cursor.point;
  1420. cursor.row += term->grid->offset;
  1421. cursor.row -= term->grid->view;
  1422. cursor.row &= term->grid->num_rows - 1;
  1423. }
  1424. if (term->render.workers.count > 0) {
  1425. mtx_lock(&term->render.workers.lock);
  1426. term->render.workers.buf = buf;
  1427. for (size_t i = 0; i < term->render.workers.count; i++)
  1428. sem_post(&term->render.workers.start);
  1429. assert(tll_length(term->render.workers.queue) == 0);
  1430. }
  1431. int first_dirty_row = -1;
  1432. for (int r = 0; r < term->rows; r++) {
  1433. struct row *row = grid_row_in_view(term->grid, r);
  1434. if (!row->dirty) {
  1435. if (first_dirty_row >= 0) {
  1436. wl_surface_damage_buffer(
  1437. term->window->surface,
  1438. term->margins.left,
  1439. term->margins.top + first_dirty_row * term->cell_height,
  1440. term->width - term->margins.left - term->margins.right,
  1441. (r - first_dirty_row) * term->cell_height);
  1442. }
  1443. first_dirty_row = -1;
  1444. continue;
  1445. }
  1446. if (first_dirty_row < 0)
  1447. first_dirty_row = r;
  1448. row->dirty = false;
  1449. if (term->render.workers.count > 0)
  1450. tll_push_back(term->render.workers.queue, r);
  1451. else {
  1452. int cursor_col = cursor.row == r ? cursor.col : -1;
  1453. render_row(term, buf->pix[0], row, r, cursor_col);
  1454. }
  1455. }
  1456. if (first_dirty_row >= 0) {
  1457. wl_surface_damage_buffer(
  1458. term->window->surface,
  1459. term->margins.left,
  1460. term->margins.top + first_dirty_row * term->cell_height,
  1461. term->width - term->margins.left - term->margins.right,
  1462. (term->rows - first_dirty_row) * term->cell_height);
  1463. }
  1464. /* Signal workers the frame is done */
  1465. if (term->render.workers.count > 0) {
  1466. for (size_t i = 0; i < term->render.workers.count; i++)
  1467. tll_push_back(term->render.workers.queue, -1);
  1468. mtx_unlock(&term->render.workers.lock);
  1469. for (size_t i = 0; i < term->render.workers.count; i++)
  1470. sem_wait(&term->render.workers.done);
  1471. term->render.workers.buf = NULL;
  1472. }
  1473. render_sixel_images(term, buf->pix[0]);
  1474. if (term->flash.active) {
  1475. /* Note: alpha is pre-computed in each color component */
  1476. /* TODO: dim while searching */
  1477. pixman_image_fill_rectangles(
  1478. PIXMAN_OP_OVER, buf->pix[0],
  1479. &(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff},
  1480. 1, &(pixman_rectangle16_t){0, 0, term->width, term->height});
  1481. wl_surface_damage_buffer(
  1482. term->window->surface, 0, 0, term->width, term->height);
  1483. }
  1484. render_scrollback_position(term);
  1485. if (term->conf->tweak.render_timer_osd || term->conf->tweak.render_timer_log) {
  1486. struct timeval end_time;
  1487. gettimeofday(&end_time, NULL);
  1488. struct timeval render_time;
  1489. timersub(&end_time, &start_time, &render_time);
  1490. if (term->conf->tweak.render_timer_log) {
  1491. LOG_INFO("frame rendered in %llds %lld µs",
  1492. (long long)render_time.tv_sec,
  1493. (long long)render_time.tv_usec);
  1494. }
  1495. if (term->conf->tweak.render_timer_osd)
  1496. render_render_timer(term, render_time);
  1497. }
  1498. assert(term->grid->offset >= 0 && term->grid->offset < term->grid->num_rows);
  1499. assert(term->grid->view >= 0 && term->grid->view < term->grid->num_rows);
  1500. assert(term->window->frame_callback == NULL);
  1501. term->window->frame_callback = wl_surface_frame(term->window->surface);
  1502. wl_callback_add_listener(term->window->frame_callback, &frame_listener, term);
  1503. wl_surface_set_buffer_scale(term->window->surface, term->scale);
  1504. if (term->wl->presentation != NULL && term->render.presentation_timings) {
  1505. struct timespec commit_time;
  1506. clock_gettime(term->wl->presentation_clock_id, &commit_time);
  1507. struct wp_presentation_feedback *feedback = wp_presentation_feedback(
  1508. term->wl->presentation, term->window->surface);
  1509. if (feedback == NULL) {
  1510. LOG_WARN("failed to create presentation feedback");
  1511. } else {
  1512. struct presentation_context *ctx = xmalloc(sizeof(*ctx));
  1513. *ctx = (struct presentation_context){
  1514. .term = term,
  1515. .input.tv_sec = term->render.input_time.tv_sec,
  1516. .input.tv_usec = term->render.input_time.tv_nsec / 1000,
  1517. .commit.tv_sec = commit_time.tv_sec,
  1518. .commit.tv_usec = commit_time.tv_nsec / 1000,
  1519. };
  1520. wp_presentation_feedback_add_listener(
  1521. feedback, &presentation_feedback_listener, ctx);
  1522. term->render.input_time.tv_sec = 0;
  1523. term->render.input_time.tv_nsec = 0;
  1524. }
  1525. }
  1526. if (term->conf->tweak.damage_whole_window) {
  1527. wl_surface_damage_buffer(
  1528. term->window->surface, 0, 0, INT32_MAX, INT32_MAX);
  1529. }
  1530. wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0);
  1531. quirk_kde_damage_before_attach(term->window->surface);
  1532. wl_surface_commit(term->window->surface);
  1533. }
  1534. static void
  1535. render_search_box(struct terminal *term)
  1536. {
  1537. assert(term->window->search_sub_surface != NULL);
  1538. const size_t wanted_visible_chars = max(20, term->search.len);
  1539. assert(term->scale >= 1);
  1540. const int scale = term->scale;
  1541. const size_t margin = 3 * scale;
  1542. const size_t width = term->width - 2 * margin;
  1543. const size_t visible_width = min(
  1544. term->width - 2 * margin,
  1545. 2 * margin + wanted_visible_chars * term->cell_width);
  1546. const size_t height = min(
  1547. term->height - 2 * margin,
  1548. 2 * margin + 1 * term->cell_height);
  1549. const size_t visible_chars = (visible_width - 2 * margin) / term->cell_width;
  1550. size_t glyph_offset = term->render.search_glyph_offset;
  1551. unsigned long cookie = shm_cookie_search(term);
  1552. struct buffer *buf = shm_get_buffer(term->wl->shm, width, height, cookie, false, 1);
  1553. /* Background - yellow on empty/match, red on mismatch */
  1554. pixman_color_t color = color_hex_to_pixman(
  1555. term->search.match_len == term->search.len
  1556. ? term->colors.table[3] : term->colors.table[1]);
  1557. pixman_image_fill_rectangles(
  1558. PIXMAN_OP_SRC, buf->pix[0], &color,
  1559. 1, &(pixman_rectangle16_t){width - visible_width, 0, visible_width, height});
  1560. pixman_color_t transparent = color_hex_to_pixman_with_alpha(0, 0);
  1561. pixman_image_fill_rectangles(
  1562. PIXMAN_OP_SRC, buf->pix[0], &transparent,
  1563. 1, &(pixman_rectangle16_t){0, 0, width - visible_width, height});
  1564. struct fcft_font *font = term->fonts[0];
  1565. int x = width - visible_width + margin;
  1566. int y = margin;
  1567. pixman_color_t fg = color_hex_to_pixman(term->colors.table[0]);
  1568. /* Ensure cursor is visible */
  1569. if (term->search.cursor < glyph_offset)
  1570. term->render.search_glyph_offset = glyph_offset = term->search.cursor;
  1571. else if (term->search.cursor > glyph_offset + visible_chars) {
  1572. term->render.search_glyph_offset = glyph_offset =
  1573. term->search.cursor - min(term->search.cursor, visible_chars);
  1574. }
  1575. /* Move offset if there is free space available */
  1576. if (term->search.len - glyph_offset < visible_chars)
  1577. term->render.search_glyph_offset = glyph_offset =
  1578. term->search.len - min(term->search.len, visible_chars);
  1579. /* Text (what the user entered - *not* match(es)) */
  1580. for (size_t i = glyph_offset;
  1581. i < term->search.len && i - glyph_offset < visible_chars;
  1582. i++)
  1583. {
  1584. if (i == term->search.cursor)
  1585. draw_bar(term, buf->pix[0], font, &fg, x, y);
  1586. const struct fcft_glyph *glyph = fcft_glyph_rasterize(
  1587. font, term->search.buf[i], term->font_subpixel);
  1588. if (glyph == NULL)
  1589. continue;
  1590. pixman_image_t *src = pixman_image_create_solid_fill(&fg);
  1591. pixman_image_composite32(
  1592. PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0,
  1593. x + glyph->x, y + font_baseline(term) - glyph->y,
  1594. glyph->width, glyph->height);
  1595. pixman_image_unref(src);
  1596. x += term->cell_width;
  1597. }
  1598. if (term->search.cursor >= term->search.len)
  1599. draw_bar(term, buf->pix[0], font, &fg, x, y);
  1600. quirk_weston_subsurface_desync_on(term->window->search_sub_surface);
  1601. /* TODO: this is only necessary on a window resize */
  1602. wl_subsurface_set_position(
  1603. term->window->search_sub_surface,
  1604. margin / scale,
  1605. max(0, (int32_t)term->height - height - margin) / scale);
  1606. wl_surface_attach(term->window->search_surface, buf->wl_buf, 0, 0);
  1607. wl_surface_damage_buffer(term->window->search_surface, 0, 0, width, height);
  1608. wl_surface_set_buffer_scale(term->window->search_surface, scale);
  1609. struct wl_region *region = wl_compositor_create_region(term->wl->compositor);
  1610. if (region != NULL) {
  1611. wl_region_add(region, width - visible_width, 0, visible_width, height);
  1612. wl_surface_set_opaque_region(term->window->search_surface, region);
  1613. wl_region_destroy(region);
  1614. }
  1615. wl_surface_commit(term->window->search_surface);
  1616. quirk_weston_subsurface_desync_off(term->window->search_sub_surface);
  1617. }
  1618. static void
  1619. render_update_title(struct terminal *term)
  1620. {
  1621. static const size_t max_len = 2048;
  1622. const char *title = term->window_title != NULL ? term->window_title : "foot";
  1623. char *copy = NULL;
  1624. if (strlen(title) > max_len) {
  1625. copy = xstrndup(title, max_len);
  1626. title = copy;
  1627. }
  1628. xdg_toplevel_set_title(term->window->xdg_toplevel, title);
  1629. free(copy);
  1630. }
  1631. static void
  1632. frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
  1633. {
  1634. struct terminal *term = data;
  1635. assert(term->window->frame_callback == wl_callback);
  1636. wl_callback_destroy(wl_callback);
  1637. term->window->frame_callback = NULL;
  1638. bool grid = term->render.pending.grid;
  1639. bool csd = term->render.pending.csd;
  1640. bool search = term->render.pending.search;
  1641. bool title = term->render.pending.title;
  1642. term->render.pending.grid = false;
  1643. term->render.pending.csd = false;
  1644. term->render.pending.search = false;
  1645. term->render.pending.title = false;
  1646. if (csd && term->window->use_csd == CSD_YES) {
  1647. quirk_weston_csd_on(term);
  1648. render_csd(term);
  1649. quirk_weston_csd_off(term);
  1650. }
  1651. if (title)
  1652. render_update_title(term);
  1653. if (search && term->is_searching)
  1654. render_search_box(term);
  1655. if (grid && (!term->delayed_render_timer.is_armed || csd || search))
  1656. grid_render(term);
  1657. }
  1658. /* Move to terminal.c? */
  1659. static bool
  1660. maybe_resize(struct terminal *term, int width, int height, bool force)
  1661. {
  1662. if (term->is_shutting_down)
  1663. return false;
  1664. if (!term->window->is_configured)
  1665. return false;
  1666. if (term->cell_width == 0 && term->cell_height == 0)
  1667. return false;
  1668. int scale = -1;
  1669. tll_foreach(term->window->on_outputs, it) {
  1670. if (it->item->scale > scale)
  1671. scale = it->item->scale;
  1672. }
  1673. if (scale == -1) {
  1674. /* Haven't 'entered' an output yet? */
  1675. scale = term->scale;
  1676. }
  1677. width *= scale;
  1678. height *= scale;
  1679. if (width == 0 && height == 0) {
  1680. /*
  1681. * The compositor is letting us choose the size
  1682. *
  1683. * If we have a "last" used size - use that. Otherwise, use
  1684. * the size from the user configuration.
  1685. */
  1686. if (term->unmaximized_width != 0 && term->unmaximized_height != 0) {
  1687. width = term->unmaximized_width;
  1688. height = term->unmaximized_height;
  1689. } else {
  1690. switch (term->conf->size.type) {
  1691. case CONF_SIZE_PX:
  1692. width = term->conf->size.px.width;
  1693. height = term->conf->size.px.height;
  1694. if (term->window->use_csd == CSD_YES) {
  1695. /* Take CSD title bar into account */
  1696. assert(!term->window->is_fullscreen);
  1697. height -= term->conf->csd.title_height;
  1698. }
  1699. width *= scale;
  1700. height *= scale;
  1701. break;
  1702. case CONF_SIZE_CELLS:
  1703. width = term->conf->size.cells.width * term->cell_width;
  1704. height = term->conf->size.cells.height * term->cell_height;
  1705. width += 2 * term->conf->pad_x * scale;
  1706. height += 2 * term->conf->pad_y * scale;
  1707. /*
  1708. * Ensure we can scale to logical size, and back to
  1709. * pixels without truncating.
  1710. */
  1711. if (width % scale)
  1712. width += scale - width % scale;
  1713. if (height % scale)
  1714. height += scale - height % scale;
  1715. assert(width % scale == 0);
  1716. assert(height % scale == 0);
  1717. break;
  1718. }
  1719. }
  1720. }
  1721. /* Don't shrink grid too much */
  1722. const int min_cols = 2;
  1723. const int min_rows = 1;
  1724. /* Minimum window size */
  1725. const int min_width = min_cols * term->cell_width;
  1726. const int min_height = min_rows * term->cell_height;
  1727. width = max(width, min_width);
  1728. height = max(height, min_height);
  1729. /* Padding */
  1730. const int max_pad_x = (width - min_width) / 2;
  1731. const int max_pad_y = (height - min_height) / 2;
  1732. const int pad_x = min(max_pad_x, scale * term->conf->pad_x);
  1733. const int pad_y = min(max_pad_y, scale * term->conf->pad_y);
  1734. if (!force && width == term->width && height == term->height && scale == term->scale)
  1735. return false;
  1736. /* Cancel an application initiated "Synchronized Update" */
  1737. term_disable_app_sync_updates(term);
  1738. term->width = width;
  1739. term->height = height;
  1740. term->scale = scale;
  1741. const int scrollback_lines = term->render.scrollback_lines;
  1742. /* Screen rows/cols before resize */
  1743. const int old_cols = term->cols;
  1744. const int old_rows = term->rows;
  1745. /* Screen rows/cols after resize */
  1746. const int new_cols = (term->width - 2 * pad_x) / term->cell_width;
  1747. const int new_rows = (term->height - 2 * pad_y) / term->cell_height;
  1748. /* Grid rows/cols after resize */
  1749. const int new_normal_grid_rows = 1 << (32 - __builtin_clz(new_rows + scrollback_lines - 1));
  1750. const int new_alt_grid_rows = 1 << (32 - __builtin_clz(new_rows));
  1751. assert(new_cols >= 1);
  1752. assert(new_rows >= 1);
  1753. /* Margins */
  1754. term->margins.left = pad_x;
  1755. term->margins.top = pad_y;
  1756. term->margins.right = term->width - new_cols * term->cell_width - term->margins.left;
  1757. term->margins.bottom = term->height - new_rows * term->cell_height - term->margins.top;
  1758. assert(term->margins.left >= pad_x);
  1759. assert(term->margins.right >= pad_x);
  1760. assert(term->margins.top >= pad_y);
  1761. assert(term->margins.bottom >= pad_y);
  1762. if (new_cols == old_cols && new_rows == old_rows) {
  1763. LOG_DBG("grid layout unaffected; skipping reflow");
  1764. goto damage_view;
  1765. }
  1766. struct coord *const tracking_points[] = {
  1767. &term->selection.start,
  1768. &term->selection.end,
  1769. };
  1770. /* Reflow grids */
  1771. grid_reflow(
  1772. &term->normal, new_normal_grid_rows, new_cols, old_rows, new_rows,
  1773. term->grid == &term->normal ? ALEN(tracking_points) : 0,
  1774. term->grid == &term->normal ? tracking_points : NULL,
  1775. term->composed_count, term->composed);
  1776. grid_reflow(
  1777. &term->alt, new_alt_grid_rows, new_cols, old_rows, new_rows,
  1778. term->grid == &term->alt ? ALEN(tracking_points) : 0,
  1779. term->grid == &term->alt ? tracking_points : NULL,
  1780. term->composed_count, term->composed);
  1781. /* Reset tab stops */
  1782. tll_free(term->tab_stops);
  1783. for (int c = 0; c < new_cols; c += 8)
  1784. tll_push_back(term->tab_stops, c);
  1785. term->cols = new_cols;
  1786. term->rows = new_rows;
  1787. sixel_reflow(term);
  1788. LOG_DBG("resize: %dx%d, grid: cols=%d, rows=%d "
  1789. "(left-margin=%d, right-margin=%d, top-margin=%d, bottom-margin=%d)",
  1790. term->width, term->height, term->cols, term->rows,
  1791. term->margins.left, term->margins.right, term->margins.top, term->margins.bottom);
  1792. /* Signal TIOCSWINSZ */
  1793. if (term->ptmx >= 0 && ioctl(term->ptmx, TIOCSWINSZ,
  1794. &(struct winsize){
  1795. .ws_row = term->rows,
  1796. .ws_col = term->cols,
  1797. .ws_xpixel = term->cols * term->cell_width,
  1798. .ws_ypixel = term->rows * term->cell_height}) == -1)
  1799. {
  1800. LOG_ERRNO("TIOCSWINSZ");
  1801. }
  1802. if (term->scroll_region.start >= term->rows)
  1803. term->scroll_region.start = 0;
  1804. if (term->scroll_region.end >= old_rows)
  1805. term->scroll_region.end = term->rows;
  1806. term->render.last_cursor.row = NULL;
  1807. damage_view:
  1808. if (!term->window->is_maximized && !term->window->is_fullscreen) {
  1809. term->unmaximized_width = term->width;
  1810. term->unmaximized_height = term->height;
  1811. }
  1812. #if 0
  1813. /* TODO: doesn't include CSD title bar */
  1814. xdg_toplevel_set_min_size(
  1815. term->window->xdg_toplevel, min_width / scale, min_height / scale);
  1816. #endif
  1817. {
  1818. bool title_shown = !term->window->is_fullscreen &&
  1819. term->window->use_csd == CSD_YES;
  1820. int title_height = title_shown ? term->conf->csd.title_height : 0;
  1821. xdg_surface_set_window_geometry(
  1822. term->window->xdg_surface,
  1823. 0,
  1824. -title_height,
  1825. term->width / term->scale,
  1826. term->height / term->scale + title_height);
  1827. }
  1828. tll_free(term->normal.scroll_damage);
  1829. tll_free(term->alt.scroll_damage);
  1830. term->render.last_buf = NULL;
  1831. term_damage_view(term);
  1832. render_refresh_csd(term);
  1833. render_refresh_search(term);
  1834. render_refresh(term);
  1835. return true;
  1836. }
  1837. bool
  1838. render_resize(struct terminal *term, int width, int height)
  1839. {
  1840. return maybe_resize(term, width, height, false);
  1841. }
  1842. bool
  1843. render_resize_force(struct terminal *term, int width, int height)
  1844. {
  1845. return maybe_resize(term, width, height, true);
  1846. }
  1847. static void xcursor_callback(
  1848. void *data, struct wl_callback *wl_callback, uint32_t callback_data);
  1849. static const struct wl_callback_listener xcursor_listener = {
  1850. .done = &xcursor_callback,
  1851. };
  1852. static void
  1853. render_xcursor_update(struct seat *seat)
  1854. {
  1855. /* If called from a frame callback, we may no longer have mouse focus */
  1856. if (!seat->mouse_focus)
  1857. return;
  1858. assert(seat->pointer.xcursor != NULL);
  1859. if (seat->pointer.xcursor == XCURSOR_HIDDEN) {
  1860. /* Hide cursor */
  1861. wl_surface_attach(seat->pointer.surface, NULL, 0, 0);
  1862. wl_surface_commit(seat->pointer.surface);
  1863. return;
  1864. }
  1865. seat->pointer.cursor = wl_cursor_theme_get_cursor(
  1866. seat->pointer.theme, seat->pointer.xcursor);
  1867. if (seat->pointer.cursor == NULL) {
  1868. LOG_ERR("failed to load xcursor pointer '%s'", seat->pointer.xcursor);
  1869. return;
  1870. }
  1871. const int scale = seat->pointer.scale;
  1872. struct wl_cursor_image *image = seat->pointer.cursor->images[0];
  1873. wl_surface_attach(
  1874. seat->pointer.surface, wl_cursor_image_get_buffer(image), 0, 0);
  1875. wl_pointer_set_cursor(
  1876. seat->wl_pointer, seat->pointer.serial,
  1877. seat->pointer.surface,
  1878. image->hotspot_x / scale, image->hotspot_y / scale);
  1879. wl_surface_damage_buffer(
  1880. seat->pointer.surface, 0, 0, INT32_MAX, INT32_MAX);
  1881. wl_surface_set_buffer_scale(seat->pointer.surface, scale);
  1882. assert(seat->pointer.xcursor_callback == NULL);
  1883. seat->pointer.xcursor_callback = wl_surface_frame(seat->pointer.surface);
  1884. wl_callback_add_listener(seat->pointer.xcursor_callback, &xcursor_listener, seat);
  1885. wl_surface_commit(seat->pointer.surface);
  1886. }
  1887. static void
  1888. xcursor_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
  1889. {
  1890. struct seat *seat = data;
  1891. assert(seat->pointer.xcursor_callback == wl_callback);
  1892. wl_callback_destroy(wl_callback);
  1893. seat->pointer.xcursor_callback = NULL;
  1894. if (seat->pointer.xcursor_pending) {
  1895. render_xcursor_update(seat);
  1896. seat->pointer.xcursor_pending = false;
  1897. }
  1898. }
  1899. static void
  1900. fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data)
  1901. {
  1902. struct renderer *renderer = data;
  1903. struct wayland *wayl = renderer->wayl;
  1904. tll_foreach(renderer->wayl->terms, it) {
  1905. struct terminal *term = it->item;
  1906. if (!term->render.refresh.grid &&
  1907. !term->render.refresh.csd &&
  1908. !term->render.refresh.search)
  1909. {
  1910. continue;
  1911. }
  1912. if (term->render.app_sync_updates.enabled &&
  1913. !term->render.refresh.csd &&
  1914. !term->render.refresh.search)
  1915. {
  1916. continue;
  1917. }
  1918. if (term->render.refresh.csd || term->render.refresh.search) {
  1919. /* Force update of parent surface */
  1920. term->render.refresh.grid = true;
  1921. }
  1922. assert(term->window->is_configured);
  1923. bool grid = term->render.refresh.grid;
  1924. bool csd = term->render.refresh.csd;
  1925. bool search = term->render.refresh.search;
  1926. bool title = term->render.refresh.title;
  1927. term->render.refresh.grid = false;
  1928. term->render.refresh.csd = false;
  1929. term->render.refresh.search = false;
  1930. term->render.refresh.title = false;
  1931. if (term->window->frame_callback == NULL) {
  1932. if (csd && term->window->use_csd == CSD_YES) {
  1933. quirk_weston_csd_on(term);
  1934. render_csd(term);
  1935. quirk_weston_csd_off(term);
  1936. }
  1937. if (title)
  1938. render_update_title(term);
  1939. if (search)
  1940. render_search_box(term);
  1941. if (grid)
  1942. grid_render(term);
  1943. } else {
  1944. /* Tells the frame callback to render again */
  1945. term->render.pending.grid |= grid;
  1946. term->render.pending.csd |= csd;
  1947. term->render.pending.search |= search;
  1948. term->render.pending.title |= title;
  1949. }
  1950. }
  1951. tll_foreach(wayl->seats, it) {
  1952. if (it->item.pointer.xcursor_pending) {
  1953. if (it->item.pointer.xcursor_callback == NULL) {
  1954. render_xcursor_update(&it->item);
  1955. it->item.pointer.xcursor_pending = false;
  1956. } else {
  1957. /* Frame callback will call render_xcursor_update() */
  1958. }
  1959. }
  1960. }
  1961. }
  1962. void
  1963. render_refresh_title(struct terminal *term)
  1964. {
  1965. term->render.refresh.title = true;
  1966. }
  1967. void
  1968. render_refresh(struct terminal *term)
  1969. {
  1970. term->render.refresh.grid = true;
  1971. }
  1972. void
  1973. render_refresh_csd(struct terminal *term)
  1974. {
  1975. if (term->window->use_csd == CSD_YES)
  1976. term->render.refresh.csd = true;
  1977. }
  1978. void
  1979. render_refresh_search(struct terminal *term)
  1980. {
  1981. if (term->is_searching)
  1982. term->render.refresh.search = true;
  1983. }
  1984. bool
  1985. render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor)
  1986. {
  1987. if (seat->pointer.theme == NULL)
  1988. return false;
  1989. if (seat->mouse_focus == NULL) {
  1990. seat->pointer.xcursor = NULL;
  1991. return true;
  1992. }
  1993. if (seat->mouse_focus != term) {
  1994. /* This terminal doesn't have mouse focus */
  1995. return true;
  1996. }
  1997. if (seat->pointer.xcursor == xcursor)
  1998. return true;
  1999. /* FDM hook takes care of actual rendering */
  2000. seat->pointer.xcursor_pending = true;
  2001. seat->pointer.xcursor = xcursor;
  2002. return true;
  2003. }