1 #include "terminal.h"
2
3 #if defined(__GLIBC__)
4 #include <malloc.h>
5 #endif
6 #include <signal.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <errno.h>
10
11 #include <sys/stat.h>
12 #include <sys/wait.h>
13 #include <sys/ioctl.h>
14 #include <sys/epoll.h>
15 #include <sys/eventfd.h>
16 #include <sys/timerfd.h>
17 #include <fcntl.h>
18 #include <linux/input-event-codes.h>
19 #include <xdg-shell.h>
20
21 #define LOG_MODULE "terminal"
22 #define LOG_ENABLE_DBG 0
23 #include "log.h"
24
25 #include "async.h"
26 #include "config.h"
27 #include "debug.h"
28 #include "extract.h"
29 #include "grid.h"
30 #include "ime.h"
31 #include "input.h"
32 #include "notify.h"
33 #include "quirks.h"
34 #include "reaper.h"
35 #include "render.h"
36 #include "selection.h"
37 #include "sixel.h"
38 #include "slave.h"
39 #include "shm.h"
40 #include "spawn.h"
41 #include "url-mode.h"
42 #include "util.h"
43 #include "vt.h"
44 #include "xmalloc.h"
45
46 #define PTMX_TIMING 0
47
48 const char *const XCURSOR_HIDDEN = "hidden";
49 const char *const XCURSOR_LEFT_PTR = "left_ptr";
50 const char *const XCURSOR_TEXT = "text";
51 //const char *const XCURSOR_HAND2 = "hand2";
52 const char *const XCURSOR_TOP_LEFT_CORNER = "top_left_corner";
53 const char *const XCURSOR_TOP_RIGHT_CORNER = "top_right_corner";
54 const char *const XCURSOR_BOTTOM_LEFT_CORNER = "bottom_left_corner";
55 const char *const XCURSOR_BOTTOM_RIGHT_CORNER = "bottom_right_corner";
56 const char *const XCURSOR_LEFT_SIDE = "left_side";
57 const char *const XCURSOR_RIGHT_SIDE = "right_side";
58 const char *const XCURSOR_TOP_SIDE = "top_side";
59 const char *const XCURSOR_BOTTOM_SIDE = "bottom_side";
60
61 static void
enqueue_data_for_slave(const void * data,size_t len,size_t offset,ptmx_buffer_list_t * buffer_list)62 enqueue_data_for_slave(const void *data, size_t len, size_t offset,
63 ptmx_buffer_list_t *buffer_list)
64 {
65 void *copy = xmalloc(len);
66 memcpy(copy, data, len);
67
68 struct ptmx_buffer queued = {
69 .data = copy,
70 .len = len,
71 .idx = offset,
72 };
73 tll_push_back(*buffer_list, queued);
74 }
75
76 static bool
data_to_slave(struct terminal * term,const void * data,size_t len,ptmx_buffer_list_t * buffer_list)77 data_to_slave(struct terminal *term, const void *data, size_t len,
78 ptmx_buffer_list_t *buffer_list)
79 {
80 /*
81 * Try a synchronous write first. If we fail to write everything,
82 * switch to asynchronous.
83 */
84
85 size_t async_idx = 0;
86 switch (async_write(term->ptmx, data, len, &async_idx)) {
87 case ASYNC_WRITE_REMAIN:
88 /* Switch to asynchronous mode; let FDM write the remaining data */
89 if (!fdm_event_add(term->fdm, term->ptmx, EPOLLOUT))
90 return false;
91 enqueue_data_for_slave(data, len, async_idx, buffer_list);
92 return true;
93
94 case ASYNC_WRITE_DONE:
95 return true;
96
97 case ASYNC_WRITE_ERR:
98 LOG_ERRNO("failed to synchronously write %zu bytes to slave", len);
99 return false;
100 }
101
102 BUG("Unexpected async_write() return value");
103 return false;
104 }
105
106 bool
term_paste_data_to_slave(struct terminal * term,const void * data,size_t len)107 term_paste_data_to_slave(struct terminal *term, const void *data, size_t len)
108 {
109 xassert(term->is_sending_paste_data);
110
111 if (term->ptmx < 0) {
112 /* We're probably in "hold" */
113 return false;
114 }
115
116 if (tll_length(term->ptmx_paste_buffers) > 0) {
117 /* Don't even try to send data *now* if there's queued up
118 * data, since that would result in events arriving out of
119 * order. */
120 enqueue_data_for_slave(data, len, 0, &term->ptmx_paste_buffers);
121 return true;
122 }
123
124 return data_to_slave(term, data, len, &term->ptmx_paste_buffers);
125 }
126
127 bool
term_to_slave(struct terminal * term,const void * data,size_t len)128 term_to_slave(struct terminal *term, const void *data, size_t len)
129 {
130 if (term->ptmx < 0) {
131 /* We're probably in "hold" */
132 return false;
133 }
134
135 if (tll_length(term->ptmx_buffers) > 0 || term->is_sending_paste_data) {
136 /*
137 * Don't even try to send data *now* if there's queued up
138 * data, since that would result in events arriving out of
139 * order.
140 *
141 * Furthermore, if we're currently sending paste data to the
142 * client, do *not* mix that stream with other events
143 * (https://codeberg.org/dnkl/foot/issues/101).
144 */
145 enqueue_data_for_slave(data, len, 0, &term->ptmx_buffers);
146 return true;
147 }
148
149 return data_to_slave(term, data, len, &term->ptmx_buffers);
150 }
151
152 static bool
fdm_ptmx_out(struct fdm * fdm,int fd,int events,void * data)153 fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data)
154 {
155 struct terminal *term = data;
156
157 /* If there is no queued data, then we shouldn't be in asynchronous mode */
158 xassert(tll_length(term->ptmx_buffers) > 0 ||
159 tll_length(term->ptmx_paste_buffers) > 0);
160
161 /* Writes a single buffer, returns if not all of it could be written */
162 #define write_one_buffer(buffer_list) \
163 { \
164 switch (async_write(term->ptmx, it->item.data, it->item.len, &it->item.idx)) { \
165 case ASYNC_WRITE_DONE: \
166 free(it->item.data); \
167 tll_remove(buffer_list, it); \
168 break; \
169 case ASYNC_WRITE_REMAIN: \
170 /* to_slave() updated it->item.idx */ \
171 return true; \
172 case ASYNC_WRITE_ERR: \
173 LOG_ERRNO("failed to asynchronously write %zu bytes to slave", \
174 it->item.len - it->item.idx); \
175 return false; \
176 } \
177 }
178
179 tll_foreach(term->ptmx_paste_buffers, it)
180 write_one_buffer(term->ptmx_paste_buffers);
181
182 /* If we get here, *all* paste data buffers were successfully
183 * flushed */
184
185 if (!term->is_sending_paste_data) {
186 tll_foreach(term->ptmx_buffers, it)
187 write_one_buffer(term->ptmx_buffers);
188 }
189
190 /*
191 * If we get here, *all* buffers were successfully flushed.
192 *
193 * Or, we're still sending paste data, in which case we do *not*
194 * want to send the "normal" queued up data
195 *
196 * In both cases, we want to *disable* the FDM callback since
197 * otherwise we'd just be called right away again, with nothing to
198 * write.
199 */
200 fdm_event_del(term->fdm, term->ptmx, EPOLLOUT);
201 return true;
202 }
203
204 #if PTMX_TIMING
205 static struct timespec last = {0};
206 #endif
207
208 static bool cursor_blink_rearm_timer(struct terminal *term);
209
210 /* Externally visible, but not declared in terminal.h, to enable pgo
211 * to call this function directly */
212 bool
fdm_ptmx(struct fdm * fdm,int fd,int events,void * data)213 fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
214 {
215 struct terminal *term = data;
216
217 const bool pollin = events & EPOLLIN;
218 const bool pollout = events & EPOLLOUT;
219 const bool hup = events & EPOLLHUP;
220
221 if (pollout) {
222 if (!fdm_ptmx_out(fdm, fd, events, data))
223 return false;
224 }
225
226 /* Prevent blinking while typing */
227 if (term->cursor_blink.fd >= 0) {
228 term->cursor_blink.state = CURSOR_BLINK_ON;
229 cursor_blink_rearm_timer(term);
230 }
231
232 uint8_t buf[24 * 1024];
233 const size_t max_iterations = !hup ? 10 : (size_t)-1ll;
234
235 for (size_t i = 0; i < max_iterations && pollin; i++) {
236 xassert(pollin);
237 ssize_t count = read(term->ptmx, buf, sizeof(buf));
238
239 if (count < 0) {
240 if (errno == EAGAIN || errno == EIO) {
241 /*
242 * EAGAIN: no more to read - FDM will trigger us again
243 * EIO: assume PTY was closed - we already have, or will get, a EPOLLHUP
244 */
245 break;
246 }
247
248 LOG_ERRNO("failed to read from pseudo terminal");
249 return false;
250 } else if (count == 0) {
251 /* Reached end-of-file */
252 break;
253 }
254
255 vt_from_slave(term, buf, count);
256 }
257
258 if (!term->render.app_sync_updates.enabled) {
259 /*
260 * We likely need to re-render. But, we don't want to do it
261 * immediately. Often, a single client update is done through
262 * multiple writes. This could lead to us rendering one frame with
263 * "intermediate" state.
264 *
265 * For example, we might end up rendering a frame
266 * where the client just erased a line, while in the
267 * next frame, the client wrote to the same line. This
268 * causes screen "flickering".
269 *
270 * Mitigate by always incuring a small delay before
271 * rendering the next frame. This gives the client
272 * some time to finish the operation (and thus gives
273 * us time to receive the last writes before doing any
274 * actual rendering).
275 *
276 * We incur this delay *every* time we receive
277 * input. To ensure we don't delay rendering
278 * indefinitely, we start a second timer that is only
279 * reset when we render.
280 *
281 * Note that when the client is producing data at a
282 * very high pace, we're rate limited by the wayland
283 * compositor anyway. The delay we introduce here only
284 * has any effect when the renderer is idle.
285 */
286 uint64_t lower_ns = term->conf->tweak.delayed_render_lower_ns;
287 uint64_t upper_ns = term->conf->tweak.delayed_render_upper_ns;
288
289 if (lower_ns > 0 && upper_ns > 0) {
290 #if PTMX_TIMING
291 struct timespec now;
292
293 clock_gettime(1, &now);
294 if (last.tv_sec > 0 || last.tv_nsec > 0) {
295 struct timeval diff;
296 struct timeval l = {last.tv_sec, last.tv_nsec / 1000};
297 struct timeval n = {now.tv_sec, now.tv_nsec / 1000};
298
299 timersub(&n, &l, &diff);
300 LOG_INFO("waited %lu µs for more input", diff.tv_usec);
301 }
302 last = now;
303 #endif
304
305 xassert(lower_ns < 1000000000);
306 xassert(upper_ns < 1000000000);
307 xassert(upper_ns > lower_ns);
308
309 timerfd_settime(
310 term->delayed_render_timer.lower_fd, 0,
311 &(struct itimerspec){.it_value = {.tv_nsec = lower_ns}},
312 NULL);
313
314 /* Second timeout - only reset when we render. Set to one
315 * frame (assuming 60Hz) */
316 if (!term->delayed_render_timer.is_armed) {
317 timerfd_settime(
318 term->delayed_render_timer.upper_fd, 0,
319 &(struct itimerspec){.it_value = {.tv_nsec = upper_ns}},
320 NULL);
321 term->delayed_render_timer.is_armed = true;
322 }
323 } else
324 render_refresh(term);
325 }
326
327 if (hup) {
328 fdm_del(fdm, fd);
329 term->ptmx = -1;
330 }
331
332 return true;
333 }
334
335 static bool
fdm_flash(struct fdm * fdm,int fd,int events,void * data)336 fdm_flash(struct fdm *fdm, int fd, int events, void *data)
337 {
338 if (events & EPOLLHUP)
339 return false;
340
341 struct terminal *term = data;
342 uint64_t expiration_count;
343 ssize_t ret = read(
344 term->flash.fd, &expiration_count, sizeof(expiration_count));
345
346 if (ret < 0) {
347 if (errno == EAGAIN)
348 return true;
349
350 LOG_ERRNO("failed to read flash timer");
351 return false;
352 }
353
354 LOG_DBG("flash timer expired %llu times",
355 (unsigned long long)expiration_count);
356
357 term->flash.active = false;
358 term_damage_view(term);
359 render_refresh(term);
360 return true;
361 }
362
363 static bool
fdm_blink(struct fdm * fdm,int fd,int events,void * data)364 fdm_blink(struct fdm *fdm, int fd, int events, void *data)
365 {
366 if (events & EPOLLHUP)
367 return false;
368
369 struct terminal *term = data;
370 uint64_t expiration_count;
371 ssize_t ret = read(
372 term->blink.fd, &expiration_count, sizeof(expiration_count));
373
374 if (ret < 0) {
375 if (errno == EAGAIN)
376 return true;
377
378 LOG_ERRNO("failed to read blink timer");
379 return false;
380 }
381
382 LOG_DBG("blink timer expired %llu times",
383 (unsigned long long)expiration_count);
384
385 /* Invert blink state */
386 term->blink.state = term->blink.state == BLINK_ON
387 ? BLINK_OFF : BLINK_ON;
388
389 /* Scan all visible cells and mark rows with blinking cells dirty */
390 bool no_blinking_cells = true;
391 for (int r = 0; r < term->rows; r++) {
392 struct row *row = grid_row_in_view(term->grid, r);
393 for (int col = 0; col < term->cols; col++) {
394 struct cell *cell = &row->cells[col];
395
396 if (cell->attrs.blink) {
397 cell->attrs.clean = 0;
398 row->dirty = true;
399 no_blinking_cells = false;
400 }
401 }
402 }
403
404 if (no_blinking_cells) {
405 LOG_DBG("disarming blink timer");
406
407 term->blink.state = BLINK_ON;
408 fdm_del(term->fdm, term->blink.fd);
409 term->blink.fd = -1;
410 } else
411 render_refresh(term);
412 return true;
413 }
414
415 void
term_arm_blink_timer(struct terminal * term)416 term_arm_blink_timer(struct terminal *term)
417 {
418 if (term->blink.fd >= 0)
419 return;
420
421 LOG_DBG("arming blink timer");
422
423 int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
424 if (fd < 0) {
425 LOG_ERRNO("failed to create blink timer FD");
426 return;
427 }
428
429 if (!fdm_add(term->fdm, fd, EPOLLIN, &fdm_blink, term)) {
430 close(fd);
431 return;
432 }
433
434 struct itimerspec alarm = {
435 .it_value = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
436 .it_interval = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
437 };
438
439 if (timerfd_settime(fd, 0, &alarm, NULL) < 0) {
440 LOG_ERRNO("failed to arm blink timer");
441 fdm_del(term->fdm, fd);
442 }
443
444 term->blink.fd = fd;
445 }
446
447 static void
cursor_refresh(struct terminal * term)448 cursor_refresh(struct terminal *term)
449 {
450 term->grid->cur_row->cells[term->grid->cursor.point.col].attrs.clean = 0;
451 term->grid->cur_row->dirty = true;
452 render_refresh(term);
453 }
454
455 static bool
fdm_cursor_blink(struct fdm * fdm,int fd,int events,void * data)456 fdm_cursor_blink(struct fdm *fdm, int fd, int events, void *data)
457 {
458 if (events & EPOLLHUP)
459 return false;
460
461 struct terminal *term = data;
462 uint64_t expiration_count;
463 ssize_t ret = read(
464 term->cursor_blink.fd, &expiration_count, sizeof(expiration_count));
465
466 if (ret < 0) {
467 if (errno == EAGAIN)
468 return true;
469
470 LOG_ERRNO("failed to read cursor blink timer");
471 return false;
472 }
473
474 LOG_DBG("cursor blink timer expired %llu times",
475 (unsigned long long)expiration_count);
476
477 /* Invert blink state */
478 term->cursor_blink.state = term->cursor_blink.state == CURSOR_BLINK_ON
479 ? CURSOR_BLINK_OFF : CURSOR_BLINK_ON;
480
481 cursor_refresh(term);
482 return true;
483 }
484
485 static bool
fdm_delayed_render(struct fdm * fdm,int fd,int events,void * data)486 fdm_delayed_render(struct fdm *fdm, int fd, int events, void *data)
487 {
488 if (events & EPOLLHUP)
489 return false;
490
491 struct terminal *term = data;
492
493 uint64_t unused;
494 ssize_t ret1 = 0;
495 ssize_t ret2 = 0;
496
497 if (fd == term->delayed_render_timer.lower_fd)
498 ret1 = read(term->delayed_render_timer.lower_fd, &unused, sizeof(unused));
499 if (fd == term->delayed_render_timer.upper_fd)
500 ret2 = read(term->delayed_render_timer.upper_fd, &unused, sizeof(unused));
501
502 if ((ret1 < 0 || ret2 < 0)) {
503 if (errno == EAGAIN)
504 return true;
505
506 LOG_ERRNO("failed to read timeout timer");
507 return false;
508 }
509
510 if (ret1 > 0)
511 LOG_DBG("lower delay timer expired");
512 else if (ret2 > 0)
513 LOG_DBG("upper delay timer expired");
514
515 if (ret1 == 0 && ret2 == 0)
516 return true;
517
518 #if PTMX_TIMING
519 last = (struct timespec){0};
520 #endif
521
522 /* Reset timers */
523 struct itimerspec reset = {{0}};
524 timerfd_settime(term->delayed_render_timer.lower_fd, 0, &reset, NULL);
525 timerfd_settime(term->delayed_render_timer.upper_fd, 0, &reset, NULL);
526 term->delayed_render_timer.is_armed = false;
527
528 render_refresh(term);
529 return true;
530 }
531
532 static bool
fdm_app_sync_updates_timeout(struct fdm * fdm,int fd,int events,void * data)533 fdm_app_sync_updates_timeout(
534 struct fdm *fdm, int fd, int events, void *data)
535 {
536 if (events & EPOLLHUP)
537 return false;
538
539 struct terminal *term = data;
540 uint64_t unused;
541 ssize_t ret = read(term->render.app_sync_updates.timer_fd,
542 &unused, sizeof(unused));
543
544 if (ret < 0) {
545 if (errno == EAGAIN)
546 return true;
547 LOG_ERRNO("failed to read application synchronized updates timeout timer");
548 return false;
549 }
550
551 term_disable_app_sync_updates(term);
552 return true;
553 }
554
555 static bool
fdm_title_update_timeout(struct fdm * fdm,int fd,int events,void * data)556 fdm_title_update_timeout(struct fdm *fdm, int fd, int events, void *data)
557 {
558 if (events & EPOLLHUP)
559 return false;
560
561 struct terminal *term = data;
562 uint64_t unused;
563 ssize_t ret = read(term->render.title.timer_fd, &unused, sizeof(unused));
564
565 if (ret < 0) {
566 if (errno == EAGAIN)
567 return true;
568 LOG_ERRNO("failed to read title update throttle timer");
569 return false;
570 }
571
572 struct itimerspec reset = {{0}};
573 timerfd_settime(term->render.title.timer_fd, 0, &reset, NULL);
574 term->render.title.is_armed = false;
575
576 render_refresh_title(term);
577 return true;
578 }
579
580 static bool
initialize_render_workers(struct terminal * term)581 initialize_render_workers(struct terminal *term)
582 {
583 LOG_INFO("using %hu rendering threads", term->render.workers.count);
584
585 if (sem_init(&term->render.workers.start, 0, 0) < 0 ||
586 sem_init(&term->render.workers.done, 0, 0) < 0)
587 {
588 LOG_ERRNO("failed to instantiate render worker semaphores");
589 return false;
590 }
591
592 int err;
593 if ((err = mtx_init(&term->render.workers.lock, mtx_plain)) != thrd_success) {
594 LOG_ERR("failed to instantiate render worker mutex: %s (%d)",
595 thrd_err_as_string(err), err);
596 goto err_sem_destroy;
597 }
598
599 term->render.workers.threads = xcalloc(
600 term->render.workers.count, sizeof(term->render.workers.threads[0]));
601
602 for (size_t i = 0; i < term->render.workers.count; i++) {
603 struct render_worker_context *ctx = xmalloc(sizeof(*ctx));
604 *ctx = (struct render_worker_context) {
605 .term = term,
606 .my_id = 1 + i,
607 };
608
609 int ret = thrd_create(
610 &term->render.workers.threads[i], &render_worker_thread, ctx);
611 if (ret != thrd_success) {
612
613 LOG_ERR("failed to create render worker thread: %s (%d)",
614 thrd_err_as_string(ret), ret);
615 term->render.workers.threads[i] = 0;
616 return false;
617 }
618 }
619
620 return true;
621
622 err_sem_destroy:
623 sem_destroy(&term->render.workers.start);
624 sem_destroy(&term->render.workers.done);
625 return false;
626 }
627
628 static void
free_custom_glyph(struct fcft_glyph ** glyph)629 free_custom_glyph(struct fcft_glyph **glyph)
630 {
631 if (*glyph == NULL)
632 return;
633
634 free(pixman_image_get_data((*glyph)->pix));
635 pixman_image_unref((*glyph)->pix);
636 free(*glyph);
637 *glyph = NULL;
638 }
639
640 static void
free_custom_glyphs(struct fcft_glyph *** glyphs,size_t count)641 free_custom_glyphs(struct fcft_glyph ***glyphs, size_t count)
642 {
643 if (*glyphs == NULL)
644 return;
645
646 for (size_t i = 0; i < count; i++)
647 free_custom_glyph(&(*glyphs)[i]);
648
649 free(*glyphs);
650 *glyphs = NULL;
651 }
652
653 static bool
term_set_fonts(struct terminal * term,struct fcft_font * fonts[static4])654 term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4])
655 {
656 for (size_t i = 0; i < 4; i++) {
657 xassert(fonts[i] != NULL);
658
659 fcft_destroy(term->fonts[i]);
660 term->fonts[i] = fonts[i];
661 }
662
663 free_custom_glyphs(
664 &term->custom_glyphs.box_drawing, GLYPH_BOX_DRAWING_COUNT);
665 free_custom_glyphs(
666 &term->custom_glyphs.braille, GLYPH_BRAILLE_COUNT);
667 free_custom_glyphs(
668 &term->custom_glyphs.legacy, GLYPH_LEGACY_COUNT);
669
670 const int old_cell_width = term->cell_width;
671 const int old_cell_height = term->cell_height;
672
673 const struct config *conf = term->conf;
674
675 const struct fcft_glyph *M = fcft_glyph_rasterize(
676 term->fonts[0], L'M', term->font_subpixel);
677
678 term->cell_width =
679 (M != NULL
680 ? M->advance.x
681 : (term->fonts[0]->space_advance.x > 0
682 ? term->fonts[0]->space_advance.x
683 : term->fonts[0]->max_advance.x))
684 + term_pt_or_px_as_pixels(term, &conf->letter_spacing);
685
686 term->cell_height = term->font_line_height.px >= 0
687 ? term_pt_or_px_as_pixels(term, &term->font_line_height)
688 : max(term->fonts[0]->height,
689 term->fonts[0]->ascent + term->fonts[0]->descent);
690
691 if (term->cell_width <= 0)
692 term->cell_width = 1;
693 if (term->cell_height <= 0)
694 term->cell_height = 1;
695
696 term->font_x_ofs = term_pt_or_px_as_pixels(term, &conf->horizontal_letter_offset);
697 term->font_y_ofs = term_pt_or_px_as_pixels(term, &conf->vertical_letter_offset);
698
699 LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height);
700
701 if (term->cell_width < old_cell_width ||
702 term->cell_height < old_cell_height)
703 {
704 /*
705 * The cell size has decreased.
706 *
707 * This means sixels, which we cannot resize, no longer fit
708 * into their "allocated" grid space.
709 *
710 * To be able to fit them, we would have to change the grid
711 * content. Inserting empty lines _might_ seem acceptable, but
712 * we'd also need to insert empty columns, which would break
713 * existing layout completely.
714 *
715 * So we delete them.
716 */
717 sixel_destroy_all(term);
718 } else if (term->cell_width != old_cell_width ||
719 term->cell_height != old_cell_height)
720 {
721 sixel_cell_size_changed(term);
722 }
723
724 /* Use force, since cell-width/height may have changed */
725 render_resize_force(term, term->width / term->scale, term->height / term->scale);
726 return true;
727 }
728
729 static float
get_font_dpi(const struct terminal * term)730 get_font_dpi(const struct terminal *term)
731 {
732 /*
733 * Use output's DPI to scale font. This is to ensure the font has
734 * the same physical height (if measured by a ruler) regardless of
735 * monitor.
736 *
737 * Conceptually, we use the physical monitor specs to calculate
738 * the DPI, and we ignore the output's scaling factor.
739 *
740 * However, to deal with fractional scaling, where we're told to
741 * render at e.g. 2x, but are then downscaled by the compositor to
742 * e.g. 1.25, we use the scaled DPI value multiplied by the scale
743 * factor instead.
744 *
745 * For integral scaling factors the resulting DPI is the same as
746 * if we had used the physical DPI.
747 *
748 * For fractional scaling factors we'll get a DPI *larger* than
749 * the physical DPI, that ends up being right when later
750 * downscaled by the compositor.
751 */
752
753 /* Use highest DPI from outputs we're mapped on */
754 double dpi = 0.0;
755 xassert(term->window != NULL);
756 tll_foreach(term->window->on_outputs, it) {
757 if (it->item->dpi > dpi)
758 dpi = it->item->dpi;
759 }
760
761 /* If we're not mapped, use DPI from first monitor. Hopefully this is where we'll get mapped later... */
762 if (dpi == 0.) {
763 tll_foreach(term->wl->monitors, it) {
764 dpi = it->item.dpi;
765 break;
766 }
767 }
768
769 if (dpi == 0) {
770 /* No monitors? */
771 dpi = 96.;
772 }
773
774 return dpi;
775 }
776
777 static enum fcft_subpixel
get_font_subpixel(const struct terminal * term)778 get_font_subpixel(const struct terminal *term)
779 {
780 if (term->colors.alpha != 0xffff) {
781 /* Can't do subpixel rendering on transparent background */
782 return FCFT_SUBPIXEL_NONE;
783 }
784
785 enum wl_output_subpixel wl_subpixel;
786
787 /*
788 * Wayland doesn't tell us *which* part of the surface that goes
789 * on a specific output, only whether the surface is mapped to an
790 * output or not.
791 *
792 * Thus, when determining which subpixel mode to use, we can't do
793 * much but select *an* output. So, we pick the first one.
794 *
795 * If we're not mapped at all, we pick the first available
796 * monitor, and hope that's where we'll eventually get mapped.
797 *
798 * If there aren't any monitors we use the "default" subpixel
799 * mode.
800 */
801
802 if (tll_length(term->window->on_outputs) > 0)
803 wl_subpixel = tll_front(term->window->on_outputs)->subpixel;
804 else if (tll_length(term->wl->monitors) > 0)
805 wl_subpixel = tll_front(term->wl->monitors).subpixel;
806 else
807 wl_subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
808
809 switch (wl_subpixel) {
810 case WL_OUTPUT_SUBPIXEL_UNKNOWN: return FCFT_SUBPIXEL_DEFAULT;
811 case WL_OUTPUT_SUBPIXEL_NONE: return FCFT_SUBPIXEL_NONE;
812 case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: return FCFT_SUBPIXEL_HORIZONTAL_RGB;
813 case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: return FCFT_SUBPIXEL_HORIZONTAL_BGR;
814 case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: return FCFT_SUBPIXEL_VERTICAL_RGB;
815 case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: return FCFT_SUBPIXEL_VERTICAL_BGR;
816 }
817
818 return FCFT_SUBPIXEL_DEFAULT;
819 }
820
821 static bool
term_font_size_by_dpi(const struct terminal * term)822 term_font_size_by_dpi(const struct terminal *term)
823 {
824 switch (term->conf->dpi_aware) {
825 case DPI_AWARE_YES: return true;
826 case DPI_AWARE_NO: return false;
827
828 case DPI_AWARE_AUTO:
829 /*
830 * Scale using DPI if all monitors have a scaling factor or 1.
831 *
832 * The idea is this: if a user, with multiple monitors, have
833 * enabled scaling on at least one monitor, then he/she has
834 * most likely done so to match the size of his/hers other
835 * monitors.
836 *
837 * I.e. if the user has one monitor with a scaling factor of
838 * one, and another with a scaling factor of two, he/she
839 * expects things to be twice as large on the second
840 * monitor.
841 *
842 * If we (foot) scale using DPI on the first monitor, and
843 * using the scaling factor on the second monitor, foot will
844 * *not* look twice as big on the second monitor.
845 */
846 tll_foreach(term->wl->monitors, it) {
847 const struct monitor *mon = &it->item;
848 if (mon->scale > 1)
849 return false;
850 }
851 return true;
852 }
853
854 BUG("unhandled DPI awareness value");
855 }
856
857 int
term_pt_or_px_as_pixels(const struct terminal * term,const struct pt_or_px * pt_or_px)858 term_pt_or_px_as_pixels(const struct terminal *term,
859 const struct pt_or_px *pt_or_px)
860 {
861 double scale = !term->font_is_sized_by_dpi ? term->scale : 1.;
862 double dpi = term->font_is_sized_by_dpi ? term->font_dpi : 96.;
863
864 return pt_or_px->px == 0
865 ? round(pt_or_px->pt * scale * dpi / 72)
866 : pt_or_px->px;
867 }
868
869 struct font_load_data {
870 size_t count;
871 const char **names;
872 const char *attrs;
873
874 struct fcft_font **font;
875 };
876
877 static int
font_loader_thread(void * _data)878 font_loader_thread(void *_data)
879 {
880 struct font_load_data *data = _data;
881 *data->font = fcft_from_name(data->count, data->names, data->attrs);
882 return *data->font != NULL;
883 }
884
885 static bool
reload_fonts(struct terminal * term)886 reload_fonts(struct terminal *term)
887 {
888 const struct config *conf = term->conf;
889
890 const size_t counts[4] = {
891 conf->fonts[0].count,
892 conf->fonts[1].count,
893 conf->fonts[2].count,
894 conf->fonts[3].count,
895 };
896
897 /* Configure size (which may have been changed run-time) */
898 char **names[4];
899 for (size_t i = 0; i < 4; i++) {
900 names[i] = xmalloc(counts[i] * sizeof(names[i][0]));
901
902 const struct config_font_list *font_list = &conf->fonts[i];
903
904 for (size_t j = 0; j < font_list->count; j++) {
905 const struct config_font *font = &font_list->arr[j];
906 bool use_px_size = term->font_sizes[i][j].px_size > 0;
907 char size[64];
908
909 const int scale = term->font_is_sized_by_dpi ? 1 : term->scale;
910
911 if (use_px_size)
912 snprintf(size, sizeof(size), ":pixelsize=%d",
913 term->font_sizes[i][j].px_size * scale);
914 else
915 snprintf(size, sizeof(size), ":size=%.2f",
916 term->font_sizes[i][j].pt_size * (double)scale);
917
918 size_t len = strlen(font->pattern) + strlen(size) + 1;
919 names[i][j] = xmalloc(len);
920
921 strcpy(names[i][j], font->pattern);
922 strcat(names[i][j], size);
923 }
924 }
925
926 /* Did user configure custom bold/italic fonts?
927 * Or should we use the regular font, with weight/slant attributes? */
928 const bool custom_bold = counts[1] > 0;
929 const bool custom_italic = counts[2] > 0;
930 const bool custom_bold_italic = counts[3] > 0;
931
932 const size_t count_regular = counts[0];
933 const char **names_regular = (const char **)names[0];
934
935 const size_t count_bold = custom_bold ? counts[1] : counts[0];
936 const char **names_bold = (const char **)(custom_bold ? names[1] : names[0]);
937
938 const size_t count_italic = custom_italic ? counts[2] : counts[0];
939 const char **names_italic = (const char **)(custom_italic ? names[2] : names[0]);
940
941 const size_t count_bold_italic = custom_bold_italic ? counts[3] : counts[0];
942 const char **names_bold_italic = (const char **)(custom_bold_italic ? names[3] : names[0]);
943
944 const bool use_dpi = term->font_is_sized_by_dpi;
945
946 char *attrs[4] = {NULL};
947 int attr_len[4] = {-1, -1, -1, -1}; /* -1, so that +1 (below) results in 0 */
948
949 for (size_t i = 0; i < 2; i++) {
950 attr_len[0] = snprintf(
951 attrs[0], attr_len[0] + 1, "dpi=%.2f",
952 use_dpi ? term->font_dpi : 96);
953 attr_len[1] = snprintf(
954 attrs[1], attr_len[1] + 1, "dpi=%.2f:%s",
955 use_dpi ? term->font_dpi : 96, !custom_bold ? "weight=bold" : "");
956 attr_len[2] = snprintf(
957 attrs[2], attr_len[2] + 1, "dpi=%.2f:%s",
958 use_dpi ? term->font_dpi : 96, !custom_italic ? "slant=italic" : "");
959 attr_len[3] = snprintf(
960 attrs[3], attr_len[3] + 1, "dpi=%.2f:%s",
961 use_dpi ? term->font_dpi : 96, !custom_bold_italic ? "weight=bold:slant=italic" : "");
962
963 if (i > 0)
964 continue;
965
966 for (size_t i = 0; i < 4; i++)
967 attrs[i] = xmalloc(attr_len[i] + 1);
968 }
969
970 struct fcft_font *fonts[4];
971 struct font_load_data data[4] = {
972 {count_regular, names_regular, attrs[0], &fonts[0]},
973 {count_bold, names_bold, attrs[1], &fonts[1]},
974 {count_italic, names_italic, attrs[2], &fonts[2]},
975 {count_bold_italic, names_bold_italic, attrs[3], &fonts[3]},
976 };
977
978 thrd_t tids[4] = {0};
979 for (size_t i = 0; i < 4; i++) {
980 int ret = thrd_create(&tids[i], &font_loader_thread, &data[i]);
981 if (ret != thrd_success) {
982 LOG_ERR("failed to create font loader thread: %s (%d)",
983 thrd_err_as_string(ret), ret);
984 break;
985 }
986 }
987
988 bool success = true;
989 for (size_t i = 0; i < 4; i++) {
990 if (tids[i] != 0) {
991 int ret;
992 thrd_join(tids[i], &ret);
993 success = success && ret;
994 } else
995 success = false;
996 }
997
998 for (size_t i = 0; i < 4; i++) {
999 for (size_t j = 0; j < counts[i]; j++)
1000 free(names[i][j]);
1001 free(names[i]);
1002 free(attrs[i]);
1003 }
1004
1005 if (!success) {
1006 LOG_ERR("failed to load primary fonts");
1007 for (size_t i = 0; i < 4; i++) {
1008 fcft_destroy(fonts[i]);
1009 fonts[i] = NULL;
1010 }
1011 }
1012
1013 return success ? term_set_fonts(term, fonts) : success;
1014 }
1015
1016 static bool
load_fonts_from_conf(struct terminal * term)1017 load_fonts_from_conf(struct terminal *term)
1018 {
1019 const struct config *conf = term->conf;
1020
1021 for (size_t i = 0; i < 4; i++) {
1022 const struct config_font_list *font_list = &conf->fonts[i];
1023
1024 for (size_t j = 0; j < font_list->count; j++) {
1025 const struct config_font *font = &font_list->arr[j];
1026 term->font_sizes[i][j] = (struct config_font){
1027 .pt_size = font->pt_size, .px_size = font->px_size};
1028 }
1029 }
1030
1031 term->font_line_height = term->conf->line_height;
1032 return reload_fonts(term);
1033 }
1034
1035 static void fdm_client_terminated(
1036 struct reaper *reaper, pid_t pid, int status, void *data);
1037
1038 struct terminal *
term_init(const struct config * conf,struct fdm * fdm,struct reaper * reaper,struct wayland * wayl,const char * foot_exe,const char * cwd,const char * token,int argc,char * const * argv,void (* shutdown_cb)(void * data,int exit_code),void * shutdown_data)1039 term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
1040 struct wayland *wayl, const char *foot_exe, const char *cwd,
1041 const char *token, int argc, char *const *argv,
1042 void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data)
1043 {
1044 int ptmx = -1;
1045 int flash_fd = -1;
1046 int delay_lower_fd = -1;
1047 int delay_upper_fd = -1;
1048 int app_sync_updates_fd = -1;
1049 int title_update_fd = -1;
1050
1051 struct terminal *term = malloc(sizeof(*term));
1052 if (unlikely(term == NULL)) {
1053 LOG_ERRNO("malloc() failed");
1054 return NULL;
1055 }
1056
1057 if ((ptmx = posix_openpt(O_RDWR | O_NOCTTY)) < 0) {
1058 LOG_ERRNO("failed to open PTY");
1059 goto close_fds;
1060 }
1061 if ((flash_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0) {
1062 LOG_ERRNO("failed to create flash timer FD");
1063 goto close_fds;
1064 }
1065 if ((delay_lower_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0 ||
1066 (delay_upper_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0)
1067 {
1068 LOG_ERRNO("failed to create delayed rendering timer FDs");
1069 goto close_fds;
1070 }
1071
1072 if ((app_sync_updates_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0)
1073 {
1074 LOG_ERRNO("failed to create application synchronized updates timer FD");
1075 goto close_fds;
1076 }
1077
1078 if ((title_update_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0)
1079 {
1080 LOG_ERRNO("failed to create title update throttle timer FD");
1081 goto close_fds;
1082 }
1083
1084 if (ioctl(ptmx, (unsigned int)TIOCSWINSZ,
1085 &(struct winsize){.ws_row = 24, .ws_col = 80}) < 0)
1086 {
1087 LOG_ERRNO("failed to set initial TIOCSWINSZ");
1088 goto close_fds;
1089 }
1090
1091 int ptmx_flags;
1092 if ((ptmx_flags = fcntl(ptmx, F_GETFL)) < 0 ||
1093 fcntl(ptmx, F_SETFL, ptmx_flags | O_NONBLOCK) < 0)
1094 {
1095 LOG_ERRNO("failed to configure ptmx as non-blocking");
1096 goto err;
1097 }
1098
1099 /*
1100 * Enable all FDM callbackes *except* ptmx - we can't do that
1101 * until the window has been 'configured' since we don't have a
1102 * size (and thus no grid) before then.
1103 */
1104
1105 if (!fdm_add(fdm, flash_fd, EPOLLIN, &fdm_flash, term) ||
1106 !fdm_add(fdm, delay_lower_fd, EPOLLIN, &fdm_delayed_render, term) ||
1107 !fdm_add(fdm, delay_upper_fd, EPOLLIN, &fdm_delayed_render, term) ||
1108 !fdm_add(fdm, app_sync_updates_fd, EPOLLIN, &fdm_app_sync_updates_timeout, term) ||
1109 !fdm_add(fdm, title_update_fd, EPOLLIN, &fdm_title_update_timeout, term))
1110 {
1111 goto err;
1112 }
1113
1114 /* Initialize configure-based terminal attributes */
1115 *term = (struct terminal) {
1116 .fdm = fdm,
1117 .reaper = reaper,
1118 .conf = conf,
1119 .ptmx = ptmx,
1120 .ptmx_buffers = tll_init(),
1121 .ptmx_paste_buffers = tll_init(),
1122 .font_sizes = {
1123 xmalloc(sizeof(term->font_sizes[0][0]) * conf->fonts[0].count),
1124 xmalloc(sizeof(term->font_sizes[1][0]) * conf->fonts[1].count),
1125 xmalloc(sizeof(term->font_sizes[2][0]) * conf->fonts[2].count),
1126 xmalloc(sizeof(term->font_sizes[3][0]) * conf->fonts[3].count),
1127 },
1128 .font_dpi = 0.,
1129 .font_subpixel = (conf->colors.alpha == 0xffff /* Can't do subpixel rendering on transparent background */
1130 ? FCFT_SUBPIXEL_DEFAULT
1131 : FCFT_SUBPIXEL_NONE),
1132 .cursor_keys_mode = CURSOR_KEYS_NORMAL,
1133 .keypad_keys_mode = KEYPAD_NUMERICAL,
1134 .reverse_wrap = true,
1135 .auto_margin = true,
1136 .window_title_stack = tll_init(),
1137 .scale = 1,
1138 .flash = {.fd = flash_fd},
1139 .blink = {.fd = -1},
1140 .vt = {
1141 .state = 0, /* STATE_GROUND */
1142 },
1143 .colors = {
1144 .fg = conf->colors.fg,
1145 .bg = conf->colors.bg,
1146 .alpha = conf->colors.alpha,
1147 .selection_fg = conf->colors.selection_fg,
1148 .selection_bg = conf->colors.selection_bg,
1149 .use_custom_selection = conf->colors.use_custom.selection,
1150 },
1151 .origin = ORIGIN_ABSOLUTE,
1152 .cursor_style = conf->cursor.style,
1153 .cursor_blink = {
1154 .decset = false,
1155 .deccsusr = conf->cursor.blink,
1156 .state = CURSOR_BLINK_ON,
1157 .fd = -1,
1158 },
1159 .cursor_color = {
1160 .text = conf->cursor.color.text,
1161 .cursor = conf->cursor.color.cursor,
1162 },
1163 .selection = {
1164 .start = {-1, -1},
1165 .end = {-1, -1},
1166 .auto_scroll = {
1167 .fd = -1,
1168 },
1169 },
1170 .normal = {.scroll_damage = tll_init(), .sixel_images = tll_init()},
1171 .alt = {.scroll_damage = tll_init(), .sixel_images = tll_init()},
1172 .grid = &term->normal,
1173 .composed = NULL,
1174 .alt_scrolling = conf->mouse.alternate_scroll_mode,
1175 .meta = {
1176 .esc_prefix = true,
1177 .eight_bit = true,
1178 },
1179 .num_lock_modifier = true,
1180 .bell_action_enabled = true,
1181 .tab_stops = tll_init(),
1182 .wl = wayl,
1183 .render = {
1184 .chains = {
1185 .grid = shm_chain_new(wayl->shm, true, 1 + conf->render_worker_count),
1186 .search = shm_chain_new(wayl->shm, false, 1),
1187 .scrollback_indicator = shm_chain_new(wayl->shm, false, 1),
1188 .render_timer = shm_chain_new(wayl->shm, false, 1),
1189 .url = shm_chain_new(wayl->shm, false, 1),
1190 .csd = shm_chain_new(wayl->shm, false, 1),
1191 },
1192 .scrollback_lines = conf->scrollback.lines,
1193 .app_sync_updates.timer_fd = app_sync_updates_fd,
1194 .title = {
1195 .is_armed = false,
1196 .timer_fd = title_update_fd,
1197 },
1198 .workers = {
1199 .count = conf->render_worker_count,
1200 .queue = tll_init(),
1201 },
1202 .presentation_timings = conf->presentation_timings,
1203 },
1204 .delayed_render_timer = {
1205 .is_armed = false,
1206 .lower_fd = delay_lower_fd,
1207 .upper_fd = delay_upper_fd,
1208 },
1209 .sixel = {
1210 .scrolling = true,
1211 .use_private_palette = true,
1212 .palette_size = SIXEL_MAX_COLORS,
1213 .max_width = SIXEL_MAX_WIDTH,
1214 .max_height = SIXEL_MAX_HEIGHT,
1215 },
1216 .shutdown = {
1217 .terminate_timeout_fd = -1,
1218 .cb = shutdown_cb,
1219 .cb_data = shutdown_data,
1220 },
1221 .foot_exe = xstrdup(foot_exe),
1222 .cwd = xstrdup(cwd),
1223 #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
1224 .ime_enabled = true,
1225 #endif
1226 };
1227
1228 term_update_ascii_printer(term);
1229
1230 for (size_t i = 0; i < 4; i++) {
1231 const struct config_font_list *font_list = &conf->fonts[i];
1232 for (size_t j = 0; j < font_list->count; j++) {
1233 const struct config_font *font = &font_list->arr[j];
1234 term->font_sizes[i][j] = (struct config_font){
1235 .pt_size = font->pt_size, .px_size = font->px_size};
1236 }
1237 }
1238 term->font_line_height = conf->line_height;
1239
1240 /* Start the slave/client */
1241 if ((term->slave = slave_spawn(
1242 term->ptmx, argc, term->cwd, argv,
1243 conf->term, conf->shell, conf->login_shell,
1244 &conf->notifications)) == -1)
1245 {
1246 goto err;
1247 }
1248
1249 reaper_add(term->reaper, term->slave, &fdm_client_terminated, term);
1250
1251 /* Guess scale; we're not mapped yet, so we don't know on which
1252 * output we'll be. Pick highest scale we find for now */
1253 tll_foreach(term->wl->monitors, it) {
1254 if (it->item.scale > term->scale)
1255 term->scale = it->item.scale;
1256 }
1257
1258 memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table));
1259
1260 /* Initialize the Wayland window backend */
1261 if ((term->window = wayl_win_init(term, token)) == NULL)
1262 goto err;
1263
1264 /* Load fonts */
1265 if (!term_font_dpi_changed(term, 0))
1266 goto err;
1267
1268 term->font_subpixel = get_font_subpixel(term);
1269
1270 term_set_window_title(term, conf->title);
1271
1272 /* Let the Wayland backend know we exist */
1273 tll_push_back(wayl->terms, term);
1274
1275 switch (conf->startup_mode) {
1276 case STARTUP_WINDOWED:
1277 break;
1278
1279 case STARTUP_MAXIMIZED:
1280 xdg_toplevel_set_maximized(term->window->xdg_toplevel);
1281 break;
1282
1283 case STARTUP_FULLSCREEN:
1284 xdg_toplevel_set_fullscreen(term->window->xdg_toplevel, NULL);
1285 break;
1286 }
1287
1288 if (!initialize_render_workers(term))
1289 goto err;
1290
1291 return term;
1292
1293 err:
1294 term->shutdown.in_progress = true;
1295 term_destroy(term);
1296 return NULL;
1297
1298 close_fds:
1299 close(ptmx);
1300 fdm_del(fdm, flash_fd);
1301 fdm_del(fdm, delay_lower_fd);
1302 fdm_del(fdm, delay_upper_fd);
1303 fdm_del(fdm, app_sync_updates_fd);
1304 fdm_del(fdm, title_update_fd);
1305
1306 free(term);
1307 return NULL;
1308 }
1309
1310 void
term_window_configured(struct terminal * term)1311 term_window_configured(struct terminal *term)
1312 {
1313 /* Enable ptmx FDM callback */
1314 if (!term->shutdown.in_progress) {
1315 xassert(term->window->is_configured);
1316 fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term);
1317 }
1318 }
1319
1320 /*
1321 * Shutdown logic
1322 *
1323 * A foot instance can be terminated in two ways:
1324 *
1325 * - the client application terminates (user types ‘exit’, or pressed C-d in the
1326 * shell, etc)
1327 * - the foot window is closed
1328 *
1329 * Both variants need to trigger to “other” action. I.e. if the client
1330 * application is terminated, then we need to close the window. If the window is
1331 * closed, we need to terminate the client application.
1332 *
1333 * Only when *both* tasks have completed do we consider ourselves fully
1334 * shutdown. This is when we can call term_destroy(), and the user provided
1335 * shutdown callback.
1336 *
1337 * The functions involved with this are:
1338 *
1339 * - shutdown_maybe_done(): called after any of the two tasks above have
1340 * completed. When it determines that *both* tasks are done, it calls
1341 * term_destroy() and the user provided shutdown callback.
1342 *
1343 * - fdm_client_terminated(): reaper callback, called when the client
1344 * application has terminated.
1345 *
1346 * + Kills the “terminate” timeout timer
1347 * + Calls shutdown_maybe_done() if the shutdown procedure has already
1348 * started (i.e. the window being closed initiated the shutdown)
1349 * -OR-
1350 * Initiates the shutdown itself, by calling term_shutdown() (client
1351 * application termination initiated the shutdown).
1352 *
1353 * - term_shutdown(): unregisters all FDM callbacks, sends SIGTERM to the client
1354 * application and installs a “terminate” timeout timer (if it hasn’t already
1355 * terminated). Finally registers an event FD with the FDM, which is
1356 * immediately triggered. This is done to ensure any pending FDM events are
1357 * handled before shutting down.
1358 *
1359 * - fdm_shutdown(): FDM callback, triggered by the event FD in
1360 * term_shutdown(). Unmaps and destroys the window resources, and ensures the
1361 * seats’ focused pointers don’t reference us. Finally calls
1362 * shutdown_maybe_done().
1363 *
1364 * - fdm_terminate_timeout(): FDM callback for the “terminate” timeout
1365 * timer. This function is called when the client application hasn’t
1366 * terminated after 60 seconds (after the SIGTERM). Sends SIGKILL to the
1367 * client application.
1368 *
1369 * - term_destroy(): normally called from shutdown_maybe_done(), when both the
1370 * window has been unmapped, and the client application has terminated. In
1371 * this case, it simply destroys all resources.
1372 *
1373 * It may however also be called without term_shutdown() having been called
1374 * (typically in error code paths - for example, when the Wayland connection
1375 * is closed by the compositor). In this case, the client application is
1376 * typically still running, and we can’t assume the FDM is running. To handle
1377 * this, we install configure a 60 second SIGALRM, send SIGTERM to the client
1378 * application, and then enter a blocking waitpid().
1379 *
1380 * If the alarm triggers, we send SIGKILL and once again enter a blocking
1381 * waitpid().
1382 */
1383
1384 static void
shutdown_maybe_done(struct terminal * term)1385 shutdown_maybe_done(struct terminal *term)
1386 {
1387 bool shutdown_done =
1388 term->window == NULL && term->shutdown.client_has_terminated;
1389
1390 LOG_DBG("window=%p, slave-has-been-reaped=%d --> %s",
1391 (void *)term->window, term->shutdown.client_has_terminated,
1392 (shutdown_done
1393 ? "shutdown done, calling term_destroy()"
1394 : "no action"));
1395
1396 if (!shutdown_done)
1397 return;
1398
1399 void (*cb)(void *, int) = term->shutdown.cb;
1400 void *cb_data = term->shutdown.cb_data;
1401
1402 int exit_code = term_destroy(term);
1403 if (cb != NULL)
1404 cb(cb_data, exit_code);
1405 }
1406
1407 static void
fdm_client_terminated(struct reaper * reaper,pid_t pid,int status,void * data)1408 fdm_client_terminated(struct reaper *reaper, pid_t pid, int status, void *data)
1409 {
1410 struct terminal *term = data;
1411 LOG_DBG("slave (PID=%u) died", pid);
1412
1413 term->shutdown.client_has_terminated = true;
1414 term->shutdown.exit_status = status;
1415
1416 if (term->shutdown.terminate_timeout_fd >= 0) {
1417 fdm_del(term->fdm, term->shutdown.terminate_timeout_fd);
1418 term->shutdown.terminate_timeout_fd = -1;
1419 }
1420
1421 if (term->shutdown.in_progress)
1422 shutdown_maybe_done(term);
1423 else if (!term->conf->hold_at_exit)
1424 term_shutdown(term);
1425 }
1426
1427 static bool
fdm_shutdown(struct fdm * fdm,int fd,int events,void * data)1428 fdm_shutdown(struct fdm *fdm, int fd, int events, void *data)
1429 {
1430 struct terminal *term = data;
1431
1432 /* Kill the event FD */
1433 fdm_del(term->fdm, fd);
1434
1435 wayl_win_destroy(term->window);
1436 term->window = NULL;
1437
1438 struct wayland *wayl = term->wl;
1439
1440 /*
1441 * Normally we'd get unmapped when we destroy the Wayland
1442 * above.
1443 *
1444 * However, it appears that under certain conditions, those events
1445 * are deferred (for example, when a screen locker is active), and
1446 * thus we can get here without having been unmapped.
1447 */
1448 tll_foreach(wayl->seats, it) {
1449 if (it->item.kbd_focus == term)
1450 it->item.kbd_focus = NULL;
1451 if (it->item.mouse_focus == term)
1452 it->item.mouse_focus = NULL;
1453 }
1454
1455 shutdown_maybe_done(term);
1456 return true;
1457 }
1458
1459 static bool
fdm_terminate_timeout(struct fdm * fdm,int fd,int events,void * data)1460 fdm_terminate_timeout(struct fdm *fdm, int fd, int events, void *data)
1461 {
1462 uint64_t unused;
1463 ssize_t bytes = read(fd, &unused, sizeof(unused));
1464 if (bytes < 0) {
1465 LOG_ERRNO("failed to read from slave terminate timeout FD");
1466 return false;
1467 }
1468
1469 struct terminal *term = data;
1470 xassert(!term->shutdown.client_has_terminated);
1471
1472 LOG_DBG("slave (PID=%u) has not terminated, sending SIGKILL (%d)",
1473 term->slave, SIGKILL);
1474
1475 kill(-term->slave, SIGKILL);
1476 return true;
1477 }
1478
1479 bool
term_shutdown(struct terminal * term)1480 term_shutdown(struct terminal *term)
1481 {
1482 if (term->shutdown.in_progress)
1483 return true;
1484
1485 term->shutdown.in_progress = true;
1486
1487 /*
1488 * Close FDs then postpone self-destruction to the next poll
1489 * iteration, by creating an event FD that we trigger immediately.
1490 */
1491
1492 term_cursor_blink_update(term);
1493 xassert(term->cursor_blink.fd < 0);
1494
1495 fdm_del(term->fdm, term->selection.auto_scroll.fd);
1496 fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
1497 fdm_del(term->fdm, term->render.title.timer_fd);
1498 fdm_del(term->fdm, term->delayed_render_timer.lower_fd);
1499 fdm_del(term->fdm, term->delayed_render_timer.upper_fd);
1500 fdm_del(term->fdm, term->blink.fd);
1501 fdm_del(term->fdm, term->flash.fd);
1502
1503 if (term->window != NULL && term->window->is_configured)
1504 fdm_del(term->fdm, term->ptmx);
1505 else
1506 close(term->ptmx);
1507
1508 if (!term->shutdown.client_has_terminated) {
1509 LOG_DBG("initiating asynchronous terminate of slave; "
1510 "sending SIGTERM to PID=%u", term->slave);
1511
1512 kill(-term->slave, SIGTERM);
1513
1514 const struct itimerspec timeout = {.it_value = {.tv_sec = 60}};
1515
1516 int timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
1517 if (timeout_fd < 0 ||
1518 timerfd_settime(timeout_fd, 0, &timeout, NULL) < 0 ||
1519 !fdm_add(term->fdm, timeout_fd, EPOLLIN, &fdm_terminate_timeout, term))
1520 {
1521 if (timeout_fd >= 0)
1522 close(timeout_fd);
1523 LOG_ERRNO("failed to create slave terminate timeout FD");
1524 return false;
1525 }
1526
1527 xassert(term->shutdown.terminate_timeout_fd < 0);
1528 term->shutdown.terminate_timeout_fd = timeout_fd;
1529 }
1530
1531 term->selection.auto_scroll.fd = -1;
1532 term->render.app_sync_updates.timer_fd = -1;
1533 term->render.title.timer_fd = -1;
1534 term->delayed_render_timer.lower_fd = -1;
1535 term->delayed_render_timer.upper_fd = -1;
1536 term->blink.fd = -1;
1537 term->flash.fd = -1;
1538 term->ptmx = -1;
1539
1540 int event_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
1541 if (event_fd == -1) {
1542 LOG_ERRNO("failed to create terminal shutdown event FD");
1543 return false;
1544 }
1545
1546 if (!fdm_add(term->fdm, event_fd, EPOLLIN, &fdm_shutdown, term)) {
1547 close(event_fd);
1548 return false;
1549 }
1550
1551 if (write(event_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
1552 LOG_ERRNO("failed to send terminal shutdown event");
1553 fdm_del(term->fdm, event_fd);
1554 return false;
1555 }
1556
1557 return true;
1558 }
1559
1560 static volatile sig_atomic_t alarm_raised;
1561
1562 static void
sig_alarm(int signo)1563 sig_alarm(int signo)
1564 {
1565 LOG_DBG("SIGALRM");
1566 alarm_raised = 1;
1567 }
1568
1569 int
term_destroy(struct terminal * term)1570 term_destroy(struct terminal *term)
1571 {
1572 if (term == NULL)
1573 return 0;
1574
1575 tll_foreach(term->wl->terms, it) {
1576 if (it->item == term) {
1577 tll_remove(term->wl->terms, it);
1578 break;
1579 }
1580 }
1581
1582 fdm_del(term->fdm, term->selection.auto_scroll.fd);
1583 fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
1584 fdm_del(term->fdm, term->render.title.timer_fd);
1585 fdm_del(term->fdm, term->delayed_render_timer.lower_fd);
1586 fdm_del(term->fdm, term->delayed_render_timer.upper_fd);
1587 fdm_del(term->fdm, term->cursor_blink.fd);
1588 fdm_del(term->fdm, term->blink.fd);
1589 fdm_del(term->fdm, term->flash.fd);
1590 fdm_del(term->fdm, term->ptmx);
1591 if (term->shutdown.terminate_timeout_fd >= 0)
1592 fdm_del(term->fdm, term->shutdown.terminate_timeout_fd);
1593
1594 if (term->window != NULL) {
1595 wayl_win_destroy(term->window);
1596 term->window = NULL;
1597 }
1598
1599 mtx_lock(&term->render.workers.lock);
1600 xassert(tll_length(term->render.workers.queue) == 0);
1601
1602 /* Count livinig threads - we may get here when only some of the
1603 * threads have been successfully started */
1604 size_t worker_count = 0;
1605 if (term->render.workers.threads != NULL) {
1606 for (size_t i = 0; i < term->render.workers.count; i++, worker_count++) {
1607 if (term->render.workers.threads[i] == 0)
1608 break;
1609 }
1610
1611 for (size_t i = 0; i < worker_count; i++) {
1612 sem_post(&term->render.workers.start);
1613 tll_push_back(term->render.workers.queue, -2);
1614 }
1615 }
1616 mtx_unlock(&term->render.workers.lock);
1617
1618 urls_reset(term);
1619
1620 free(term->vt.osc.data);
1621 free(term->vt.osc8.uri);
1622
1623 composed_free(term->composed);
1624
1625 free(term->window_title);
1626 tll_free_and_free(term->window_title_stack, free);
1627
1628 for (size_t i = 0; i < sizeof(term->fonts) / sizeof(term->fonts[0]); i++)
1629 fcft_destroy(term->fonts[i]);
1630 for (size_t i = 0; i < 4; i++)
1631 free(term->font_sizes[i]);
1632
1633
1634 free_custom_glyphs(
1635 &term->custom_glyphs.box_drawing, GLYPH_BOX_DRAWING_COUNT);
1636 free_custom_glyphs(
1637 &term->custom_glyphs.braille, GLYPH_BRAILLE_COUNT);
1638 free_custom_glyphs(
1639 &term->custom_glyphs.legacy, GLYPH_LEGACY_COUNT);
1640
1641 free(term->search.buf);
1642
1643 if (term->render.workers.threads != NULL) {
1644 for (size_t i = 0; i < term->render.workers.count; i++) {
1645 if (term->render.workers.threads[i] != 0)
1646 thrd_join(term->render.workers.threads[i], NULL);
1647 }
1648 }
1649 free(term->render.workers.threads);
1650 mtx_destroy(&term->render.workers.lock);
1651 sem_destroy(&term->render.workers.start);
1652 sem_destroy(&term->render.workers.done);
1653 xassert(tll_length(term->render.workers.queue) == 0);
1654 tll_free(term->render.workers.queue);
1655
1656 shm_unref(term->render.last_buf);
1657 shm_chain_free(term->render.chains.grid);
1658 shm_chain_free(term->render.chains.search);
1659 shm_chain_free(term->render.chains.scrollback_indicator);
1660 shm_chain_free(term->render.chains.render_timer);
1661 shm_chain_free(term->render.chains.url);
1662 shm_chain_free(term->render.chains.csd);
1663
1664 tll_free(term->tab_stops);
1665
1666 tll_foreach(term->ptmx_buffers, it) {
1667 free(it->item.data);
1668 tll_remove(term->ptmx_buffers, it);
1669 }
1670 tll_foreach(term->ptmx_paste_buffers, it) {
1671 free(it->item.data);
1672 tll_remove(term->ptmx_paste_buffers, it);
1673 }
1674
1675 sixel_fini(term);
1676
1677 term_ime_reset(term);
1678
1679 grid_free(&term->normal);
1680 grid_free(&term->alt);
1681
1682 free(term->foot_exe);
1683 free(term->cwd);
1684
1685 int ret = EXIT_SUCCESS;
1686
1687 if (term->slave > 0) {
1688 /* We’ll deal with this explicitly */
1689 reaper_del(term->reaper, term->slave);
1690
1691 int exit_status;
1692
1693 if (term->shutdown.client_has_terminated)
1694 exit_status = term->shutdown.exit_status;
1695 else {
1696 LOG_DBG("initiating blocking terminate of slave; "
1697 "sending SIGTERM to PID=%u", term->slave);
1698
1699 kill(-term->slave, SIGTERM);
1700
1701 /*
1702 * we’ve closed the ptxm, and sent SIGTERM to the client
1703 * application. It *should* exit...
1704 *
1705 * But, since it is possible to write clients that ignore
1706 * this, we need to handle it in *some* way.
1707 *
1708 * So, what we do is register a SIGALRM handler, and configure a 30
1709 * second alarm. If the slave hasn't died after this time, we send
1710 * it a SIGKILL,
1711 *
1712 * Note that this solution is *not* asynchronous, and any
1713 * other events etc will be ignored during this time. This of
1714 * course only applies to a 'foot --server' instance, where
1715 * there might be other terminals running.
1716 */
1717 sigaction(SIGALRM, &(const struct sigaction){.sa_handler = &sig_alarm}, NULL);
1718 alarm(60);
1719
1720 while (true) {
1721 int r = waitpid(term->slave, &exit_status, 0);
1722
1723 if (r == term->slave)
1724 break;
1725
1726 if (r == -1) {
1727 xassert(errno == EINTR);
1728
1729 if (alarm_raised) {
1730 LOG_DBG(
1731 "slave (PID=%u) has not terminate yet, "
1732 "sending: SIGKILL (%d)", term->slave, SIGKILL);
1733
1734 kill(-term->slave, SIGKILL);
1735 }
1736 }
1737 }
1738
1739 /* Cancel alarm */
1740 alarm(0);
1741 sigaction(SIGALRM, &(const struct sigaction){.sa_handler = SIG_DFL}, NULL);
1742 }
1743
1744 ret = EXIT_FAILURE;
1745 if (WIFEXITED(exit_status)) {
1746 ret = WEXITSTATUS(exit_status);
1747 LOG_DBG("slave exited with code %d", ret);
1748 } else if (WIFSIGNALED(exit_status)) {
1749 ret = WTERMSIG(exit_status);
1750 LOG_WARN("slave exited with signal %d (%s)", ret, strsignal(ret));
1751 } else {
1752 LOG_WARN("slave exited for unknown reason (status = 0x%08x)",
1753 exit_status);
1754 }
1755 }
1756
1757 free(term);
1758
1759 #if defined(__GLIBC__)
1760 if (!malloc_trim(0))
1761 LOG_WARN("failed to trim memory");
1762 #endif
1763
1764 return ret;
1765 }
1766
1767 static inline void
erase_cell_range(struct terminal * term,struct row * row,int start,int end)1768 erase_cell_range(struct terminal *term, struct row *row, int start, int end)
1769 {
1770 xassert(start < term->cols);
1771 xassert(end < term->cols);
1772
1773 row->dirty = true;
1774
1775 const enum color_source bg_src = term->vt.attrs.bg_src;
1776
1777 if (unlikely(bg_src != COLOR_DEFAULT)) {
1778 for (int col = start; col <= end; col++) {
1779 struct cell *c = &row->cells[col];
1780 c->wc = 0;
1781 c->attrs = (struct attributes){.bg_src = bg_src, .bg = term->vt.attrs.bg};
1782 }
1783 } else
1784 memset(&row->cells[start], 0, (end - start + 1) * sizeof(row->cells[0]));
1785
1786 if (unlikely(row->extra != NULL))
1787 grid_row_uri_range_erase(row, start, end);
1788 }
1789
1790 static inline void
erase_line(struct terminal * term,struct row * row)1791 erase_line(struct terminal *term, struct row *row)
1792 {
1793 erase_cell_range(term, row, 0, term->cols - 1);
1794 row->linebreak = false;
1795 }
1796
1797 void
term_reset(struct terminal * term,bool hard)1798 term_reset(struct terminal *term, bool hard)
1799 {
1800 term->cursor_keys_mode = CURSOR_KEYS_NORMAL;
1801 term->keypad_keys_mode = KEYPAD_NUMERICAL;
1802 term->reverse = false;
1803 term->hide_cursor = false;
1804 term->reverse_wrap = true;
1805 term->auto_margin = true;
1806 term->insert_mode = false;
1807 term->bracketed_paste = false;
1808 term->focus_events = false;
1809 term->modify_escape_key = false;
1810 term->num_lock_modifier = true;
1811 term->bell_action_enabled = true;
1812 term->mouse_tracking = MOUSE_NONE;
1813 term->mouse_reporting = MOUSE_NORMAL;
1814 term->charsets.selected = G0;
1815 term->charsets.set[G0] = CHARSET_ASCII;
1816 term->charsets.set[G1] = CHARSET_ASCII;
1817 term->charsets.set[G2] = CHARSET_ASCII;
1818 term->charsets.set[G3] = CHARSET_ASCII;
1819 term->saved_charsets = term->charsets;
1820 tll_free_and_free(term->window_title_stack, free);
1821 term_set_window_title(term, term->conf->title);
1822
1823 memset(term->normal.kitty_kbd.flags, 0, sizeof(term->normal.kitty_kbd.flags));
1824 memset(term->alt.kitty_kbd.flags, 0, sizeof(term->alt.kitty_kbd.flags));
1825 term->normal.kitty_kbd.idx = term->alt.kitty_kbd.idx = 0;
1826
1827 term->scroll_region.start = 0;
1828 term->scroll_region.end = term->rows;
1829
1830 free(term->vt.osc8.uri);
1831 free(term->vt.osc.data);
1832
1833 term->vt = (struct vt){
1834 .state = 0, /* STATE_GROUND */
1835 };
1836
1837 if (term->grid == &term->alt) {
1838 term->grid = &term->normal;
1839 selection_cancel(term);
1840 }
1841
1842 term->meta.esc_prefix = true;
1843 term->meta.eight_bit = true;
1844
1845 tll_foreach(term->normal.sixel_images, it) {
1846 sixel_destroy(&it->item);
1847 tll_remove(term->normal.sixel_images, it);
1848 }
1849 tll_foreach(term->alt.sixel_images, it) {
1850 sixel_destroy(&it->item);
1851 tll_remove(term->alt.sixel_images, it);
1852 }
1853
1854 #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
1855 term_ime_enable(term);
1856 #endif
1857
1858 term_update_ascii_printer(term);
1859
1860 if (!hard)
1861 return;
1862
1863 term->flash.active = false;
1864 term->blink.state = BLINK_ON;
1865 fdm_del(term->fdm, term->blink.fd); term->blink.fd = -1;
1866 term->colors.fg = term->conf->colors.fg;
1867 term->colors.bg = term->conf->colors.bg;
1868 term->colors.alpha = term->conf->colors.alpha;
1869 term->colors.selection_fg = term->conf->colors.selection_fg;
1870 term->colors.selection_bg = term->conf->colors.selection_bg;
1871 term->colors.use_custom_selection = term->conf->colors.use_custom.selection;
1872 memcpy(term->colors.table, term->conf->colors.table,
1873 sizeof(term->colors.table));
1874 term->origin = ORIGIN_ABSOLUTE;
1875 term->normal.cursor.lcf = false;
1876 term->alt.cursor.lcf = false;
1877 term->normal.cursor = (struct cursor){.point = {0, 0}};
1878 term->normal.saved_cursor = (struct cursor){.point = {0, 0}};
1879 term->alt.cursor = (struct cursor){.point = {0, 0}};
1880 term->alt.saved_cursor = (struct cursor){.point = {0, 0}};
1881 term->cursor_style = term->conf->cursor.style;
1882 term->cursor_blink.decset = false;
1883 term->cursor_blink.deccsusr = term->conf->cursor.blink;
1884 term_cursor_blink_update(term);
1885 term->cursor_color.text = term->conf->cursor.color.text;
1886 term->cursor_color.cursor = term->conf->cursor.color.cursor;
1887 selection_cancel(term);
1888 term->normal.offset = term->normal.view = 0;
1889 term->alt.offset = term->alt.view = 0;
1890 for (size_t i = 0; i < term->rows; i++) {
1891 struct row *r = grid_row_and_alloc(&term->normal, i);
1892 erase_line(term, r);
1893 }
1894 for (size_t i = 0; i < term->rows; i++) {
1895 struct row *r = grid_row_and_alloc(&term->alt, i);
1896 erase_line(term, r);
1897 }
1898 for (size_t i = term->rows; i < term->normal.num_rows; i++) {
1899 grid_row_free(term->normal.rows[i]);
1900 term->normal.rows[i] = NULL;
1901 }
1902 for (size_t i = term->rows; i < term->alt.num_rows; i++) {
1903 grid_row_free(term->alt.rows[i]);
1904 term->alt.rows[i] = NULL;
1905 }
1906 term->normal.cur_row = term->normal.rows[0];
1907 term->alt.cur_row = term->alt.rows[0];
1908 tll_free(term->normal.scroll_damage);
1909 tll_free(term->alt.scroll_damage);
1910 term->render.last_cursor.row = NULL;
1911 term->render.was_flashing = false;
1912 term_damage_all(term);
1913 }
1914
1915 static bool
term_font_size_adjust(struct terminal * term,double amount)1916 term_font_size_adjust(struct terminal *term, double amount)
1917 {
1918 const struct config *conf = term->conf;
1919
1920 for (size_t i = 0; i < 4; i++) {
1921 const struct config_font_list *font_list = &conf->fonts[i];
1922
1923 for (size_t j = 0; j < font_list->count; j++) {
1924 double old_pt_size = term->font_sizes[i][j].pt_size;
1925
1926 /*
1927 * To ensure primary and user-configured fallback fonts are
1928 * resizes by the same amount, convert pixel sizes to point
1929 * sizes, and to the adjustment on point sizes only.
1930 */
1931
1932 if (term->font_sizes[i][j].px_size > 0) {
1933 double dpi = term->font_dpi;
1934 old_pt_size = term->font_sizes[i][j].px_size * 72. / dpi;
1935 }
1936
1937 term->font_sizes[i][j].pt_size = fmax(old_pt_size + amount, 0);
1938 term->font_sizes[i][j].px_size = -1;
1939 }
1940 }
1941
1942 if (term->font_line_height.px >= 0) {
1943 double old_pt_size = term->font_line_height.px > 0
1944 ? term->font_line_height.px * 72. / term->font_dpi
1945 : term->font_line_height.pt;
1946
1947 term->font_line_height.px = 0;
1948 term->font_line_height.pt = fmax(old_pt_size + amount, 0);
1949 }
1950
1951 return reload_fonts(term);
1952 }
1953
1954 bool
term_font_size_increase(struct terminal * term)1955 term_font_size_increase(struct terminal *term)
1956 {
1957 if (!term_font_size_adjust(term, 0.5))
1958 return false;
1959
1960 return true;
1961 }
1962
1963 bool
term_font_size_decrease(struct terminal * term)1964 term_font_size_decrease(struct terminal *term)
1965 {
1966 if (!term_font_size_adjust(term, -0.5))
1967 return false;
1968
1969 return true;
1970 }
1971
1972 bool
term_font_size_reset(struct terminal * term)1973 term_font_size_reset(struct terminal *term)
1974 {
1975 return load_fonts_from_conf(term);
1976 }
1977
1978 bool
term_font_dpi_changed(struct terminal * term,int old_scale)1979 term_font_dpi_changed(struct terminal *term, int old_scale)
1980 {
1981 float dpi = get_font_dpi(term);
1982 xassert(term->scale > 0);
1983
1984 bool was_scaled_using_dpi = term->font_is_sized_by_dpi;
1985 bool will_scale_using_dpi = term_font_size_by_dpi(term);
1986
1987 bool need_font_reload =
1988 was_scaled_using_dpi != will_scale_using_dpi ||
1989 (will_scale_using_dpi
1990 ? term->font_dpi != dpi
1991 : old_scale != term->scale);
1992
1993 if (need_font_reload) {
1994 LOG_DBG("DPI/scale change: DPI-awareness=%s, "
1995 "DPI: %.2f -> %.2f, scale: %d -> %d, "
1996 "sizing font based on monitor's %s",
1997 term->conf->dpi_aware == DPI_AWARE_AUTO ? "auto" :
1998 term->conf->dpi_aware == DPI_AWARE_YES ? "yes" : "no",
1999 term->font_dpi, dpi, old_scale, term->scale,
2000 will_scale_using_dpi ? "DPI" : "scaling factor");
2001 }
2002
2003 term->font_dpi = dpi;
2004 term->font_is_sized_by_dpi = will_scale_using_dpi;
2005
2006 if (!need_font_reload)
2007 return true;
2008
2009 return reload_fonts(term);
2010 }
2011
2012 void
term_font_subpixel_changed(struct terminal * term)2013 term_font_subpixel_changed(struct terminal *term)
2014 {
2015 enum fcft_subpixel subpixel = get_font_subpixel(term);
2016
2017 if (term->font_subpixel == subpixel)
2018 return;
2019
2020 #if defined(_DEBUG) && LOG_ENABLE_DBG
2021 static const char *const str[] = {
2022 [FCFT_SUBPIXEL_DEFAULT] = "default",
2023 [FCFT_SUBPIXEL_NONE] = "disabled",
2024 [FCFT_SUBPIXEL_HORIZONTAL_RGB] = "RGB",
2025 [FCFT_SUBPIXEL_HORIZONTAL_BGR] = "BGR",
2026 [FCFT_SUBPIXEL_VERTICAL_RGB] = "V-RGB",
2027 [FCFT_SUBPIXEL_VERTICAL_BGR] = "V-BGR",
2028 };
2029
2030 LOG_DBG("subpixel mode changed: %s -> %s", str[term->font_subpixel], str[subpixel]);
2031 #endif
2032
2033 term->font_subpixel = subpixel;
2034 term_damage_view(term);
2035 render_refresh(term);
2036 }
2037
2038 void
term_damage_rows(struct terminal * term,int start,int end)2039 term_damage_rows(struct terminal *term, int start, int end)
2040 {
2041 xassert(start <= end);
2042 for (int r = start; r <= end; r++) {
2043 struct row *row = grid_row(term->grid, r);
2044 row->dirty = true;
2045 for (int c = 0; c < term->grid->num_cols; c++)
2046 row->cells[c].attrs.clean = 0;
2047 }
2048 }
2049
2050 void
term_damage_rows_in_view(struct terminal * term,int start,int end)2051 term_damage_rows_in_view(struct terminal *term, int start, int end)
2052 {
2053 xassert(start <= end);
2054 for (int r = start; r <= end; r++) {
2055 struct row *row = grid_row_in_view(term->grid, r);
2056 row->dirty = true;
2057 for (int c = 0; c < term->grid->num_cols; c++)
2058 row->cells[c].attrs.clean = 0;
2059 }
2060 }
2061
2062 void
term_damage_all(struct terminal * term)2063 term_damage_all(struct terminal *term)
2064 {
2065 term_damage_rows(term, 0, term->rows - 1);
2066 }
2067
2068 void
term_damage_view(struct terminal * term)2069 term_damage_view(struct terminal *term)
2070 {
2071 term_damage_rows_in_view(term, 0, term->rows - 1);
2072 }
2073
2074 void
term_damage_cursor(struct terminal * term)2075 term_damage_cursor(struct terminal *term)
2076 {
2077 term->grid->cur_row->cells[term->grid->cursor.point.col].attrs.clean = 0;
2078 term->grid->cur_row->dirty = true;
2079 }
2080
2081 void
term_damage_margins(struct terminal * term)2082 term_damage_margins(struct terminal *term)
2083 {
2084 term->render.margins = true;
2085 }
2086
2087 void
term_damage_scroll(struct terminal * term,enum damage_type damage_type,struct scroll_region region,int lines)2088 term_damage_scroll(struct terminal *term, enum damage_type damage_type,
2089 struct scroll_region region, int lines)
2090 {
2091 if (tll_length(term->grid->scroll_damage) > 0) {
2092 struct damage *dmg = &tll_back(term->grid->scroll_damage);
2093
2094 if (dmg->type == damage_type &&
2095 dmg->region.start == region.start &&
2096 dmg->region.end == region.end)
2097 {
2098 dmg->lines += lines;
2099 return;
2100 }
2101 }
2102 struct damage dmg = {
2103 .type = damage_type,
2104 .region = region,
2105 .lines = lines,
2106 };
2107 tll_push_back(term->grid->scroll_damage, dmg);
2108 }
2109
2110 void
term_erase(struct terminal * term,const struct coord * start,const struct coord * end)2111 term_erase(struct terminal *term, const struct coord *start, const struct coord *end)
2112 {
2113 xassert(start->row <= end->row);
2114 xassert(start->col <= end->col || start->row < end->row);
2115
2116 if (start->row == end->row) {
2117 struct row *row = grid_row(term->grid, start->row);
2118 erase_cell_range(term, row, start->col, end->col);
2119 sixel_overwrite_by_row(term, start->row, start->col, end->col - start->col + 1);
2120 return;
2121 }
2122
2123 xassert(end->row > start->row);
2124
2125 erase_cell_range(
2126 term, grid_row(term->grid, start->row), start->col, term->cols - 1);
2127 sixel_overwrite_by_row(term, start->row, start->col, term->cols - start->col);
2128
2129 for (int r = start->row + 1; r < end->row; r++)
2130 erase_line(term, grid_row(term->grid, r));
2131 sixel_overwrite_by_rectangle(
2132 term, start->row + 1, 0, end->row - start->row, term->cols);
2133
2134 erase_cell_range(term, grid_row(term->grid, end->row), 0, end->col);
2135 sixel_overwrite_by_row(term, end->row, 0, end->col + 1);
2136 }
2137
2138 void
term_erase_scrollback(struct terminal * term)2139 term_erase_scrollback(struct terminal *term)
2140 {
2141 const int num_rows = term->grid->num_rows;
2142 const int mask = num_rows - 1;
2143
2144 const int start = (term->grid->offset + term->rows) & mask;
2145 const int end = (term->grid->offset - 1) & mask;
2146
2147 const int scrollback_start = term->grid->offset + term->rows;
2148 const int rel_start = (start - scrollback_start + num_rows) & mask;
2149 const int rel_end = (end - scrollback_start + num_rows) & mask;
2150
2151 const int sel_start = term->selection.start.row;
2152 const int sel_end = term->selection.end.row;
2153
2154 if (sel_end >= 0) {
2155 /*
2156 * Cancel selection if it touches any of the rows in the
2157 * scrollback, since we can’t have the selection reference
2158 * soon-to-be deleted rows.
2159 *
2160 * This is done by range checking the selection range against
2161 * the scrollback range.
2162 *
2163 * To make this comparison simpler, the start/end absolute row
2164 * numbers are “rebased” against the scrollback start, where
2165 * row 0 is the *first* row in the scrollback. A high number
2166 * thus means the row is further *down* in the scrollback,
2167 * closer to the screen bottom.
2168 */
2169
2170 const int rel_sel_start = (sel_start - scrollback_start + num_rows) & mask;
2171 const int rel_sel_end = (sel_end - scrollback_start + num_rows) & mask;
2172
2173 if ((rel_sel_start <= rel_start && rel_sel_end >= rel_start) ||
2174 (rel_sel_start <= rel_end && rel_sel_end >= rel_end) ||
2175 (rel_sel_start >= rel_start && rel_sel_end <= rel_end))
2176 {
2177 selection_cancel(term);
2178 }
2179 }
2180
2181 tll_foreach(term->grid->sixel_images, it) {
2182 struct sixel *six = &it->item;
2183 const int six_start = (six->pos.row - scrollback_start + num_rows) & mask;
2184 const int six_end = (six->pos.row + six->rows - 1 - scrollback_start + num_rows) & mask;
2185
2186 if ((six_start <= rel_start && six_end >= rel_start) ||
2187 (six_start <= rel_end && six_end >= rel_end) ||
2188 (six_start >= rel_start && six_end <= rel_end))
2189 {
2190 sixel_destroy(six);
2191 tll_remove(term->grid->sixel_images, it);
2192 }
2193 }
2194
2195 for (int i = start;; i = (i + 1) & mask) {
2196 struct row *row = term->grid->rows[i];
2197 if (row != NULL) {
2198 if (term->render.last_cursor.row == row)
2199 term->render.last_cursor.row = NULL;
2200
2201 grid_row_free(row);
2202 term->grid->rows[i] = NULL;
2203 }
2204
2205 if (i == end)
2206 break;
2207 }
2208
2209 term->grid->view = term->grid->offset;
2210 term_damage_view(term);
2211 }
2212
2213 UNITTEST
2214 {
2215 const int scrollback_rows = 16;
2216 const int term_rows = 5;
2217 const int cols = 5;
2218
2219 struct fdm *fdm = fdm_init();
2220 xassert(fdm != NULL);
2221
2222 struct terminal term = {
2223 .fdm = fdm,
2224 .rows = term_rows,
2225 .cols = cols,
2226 .normal = {
2227 .rows = xcalloc(scrollback_rows, sizeof(term.normal.rows[0])),
2228 .num_rows = scrollback_rows,
2229 .num_cols = cols,
2230 },
2231 .grid = &term.normal,
2232 .selection = {
2233 .start = {-1, -1},
2234 .end = {-1, -1},
2235 .kind = SELECTION_NONE,
2236 .auto_scroll = {
2237 .fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK),
2238 },
2239 },
2240 };
2241
2242 xassert(term.selection.auto_scroll.fd >= 0);
2243
2244 #define populate_scrollback() do { \
2245 for (int i = 0; i < scrollback_rows; i++) { \
2246 if (term.normal.rows[i] == NULL) { \
2247 struct row *r = xcalloc(1, sizeof(*term.normal.rows[i])); \
2248 r->cells = xcalloc(cols, sizeof(r->cells[0])); \
2249 term.normal.rows[i] = r; \
2250 } \
2251 } \
2252 } while (0)
2253
2254 /*
2255 * Test case 1 - no selection, just verify all rows except those
2256 * on screen have been deleted.
2257 */
2258
2259 populate_scrollback();
2260 term.normal.offset = 11;
2261 term_erase_scrollback(&term);
2262 for (int i = 0; i < scrollback_rows; i++) {
2263 if (i >= term.normal.offset && i < term.normal.offset + term_rows)
2264 xassert(term.normal.rows[i] != NULL);
2265 else
2266 xassert(term.normal.rows[i] == NULL);
2267 }
2268
2269 /*
2270 * Test case 2 - selection that touches the scrollback. Verify the
2271 * selection is cancelled.
2272 */
2273
2274 term.normal.offset = 14; /* Screen covers rows 14,15,0,1,2 */
2275
2276 /* Selection covers rows 15,0,1,2,3 */
2277 term.selection.start = (struct coord){.row = 15};
2278 term.selection.end = (struct coord){.row = 19};
2279 term.selection.kind = SELECTION_CHAR_WISE;
2280
2281 populate_scrollback();
2282 term_erase_scrollback(&term);
2283 xassert(term.selection.start.row < 0);
2284 xassert(term.selection.end.row < 0);
2285 xassert(term.selection.kind == SELECTION_NONE);
2286
2287 /*
2288 * Test case 3 - selection that does *not* touch the
2289 * scrollback. Verify the selection is *not* cancelled.
2290 */
2291
2292 /* Selection covers rows 15,0 */
2293 term.selection.start = (struct coord){.row = 15};
2294 term.selection.end = (struct coord){.row = 16};
2295 term.selection.kind = SELECTION_CHAR_WISE;
2296
2297 populate_scrollback();
2298 term_erase_scrollback(&term);
2299 xassert(term.selection.start.row == 15);
2300 xassert(term.selection.end.row == 16);
2301 xassert(term.selection.kind == SELECTION_CHAR_WISE);
2302
2303 term.selection.start = (struct coord){-1, -1};
2304 term.selection.end = (struct coord){-1, -1};
2305 term.selection.kind = SELECTION_NONE;
2306
2307 /*
2308 * Test case 4 - sixel that touch the scrollback
2309 */
2310
2311 struct sixel six = {
2312 .rows = 5,
2313 .pos = {
2314 .row = 15,
2315 },
2316 };
2317 tll_push_back(term.normal.sixel_images, six);
2318 populate_scrollback();
2319 term_erase_scrollback(&term);
2320 xassert(tll_length(term.normal.sixel_images) == 0);
2321
2322 /*
2323 * Test case 5 - sixel that does *not* touch the scrollback
2324 */
2325 six.rows = 3;
2326 tll_push_back(term.normal.sixel_images, six);
2327 populate_scrollback();
2328 term_erase_scrollback(&term);
2329 xassert(tll_length(term.normal.sixel_images) == 1);
2330
2331 /* Cleanup */
2332 tll_free(term.normal.sixel_images);
2333 close(term.selection.auto_scroll.fd);
2334 for (int i = 0; i < scrollback_rows; i++)
2335 grid_row_free(term.normal.rows[i]);
2336 free(term.normal.rows);
2337 fdm_destroy(fdm);
2338 }
2339
2340 int
term_row_rel_to_abs(const struct terminal * term,int row)2341 term_row_rel_to_abs(const struct terminal *term, int row)
2342 {
2343 switch (term->origin) {
2344 case ORIGIN_ABSOLUTE:
2345 return min(row, term->rows - 1);
2346
2347 case ORIGIN_RELATIVE:
2348 return min(row + term->scroll_region.start, term->scroll_region.end - 1);
2349 }
2350
2351 BUG("Invalid cursor_origin value");
2352 return -1;
2353 }
2354
2355 void
term_cursor_to(struct terminal * term,int row,int col)2356 term_cursor_to(struct terminal *term, int row, int col)
2357 {
2358 xassert(row < term->rows);
2359 xassert(col < term->cols);
2360
2361 term->grid->cursor.lcf = false;
2362
2363 term->grid->cursor.point.col = col;
2364 term->grid->cursor.point.row = row;
2365
2366 term->grid->cur_row = grid_row(term->grid, row);
2367 }
2368
2369 void
term_cursor_home(struct terminal * term)2370 term_cursor_home(struct terminal *term)
2371 {
2372 term_cursor_to(term, term_row_rel_to_abs(term, 0), 0);
2373 }
2374
2375 void
term_cursor_left(struct terminal * term,int count)2376 term_cursor_left(struct terminal *term, int count)
2377 {
2378 int move_amount = min(term->grid->cursor.point.col, count);
2379 term->grid->cursor.point.col -= move_amount;
2380 xassert(term->grid->cursor.point.col >= 0);
2381 term->grid->cursor.lcf = false;
2382 }
2383
2384 void
term_cursor_right(struct terminal * term,int count)2385 term_cursor_right(struct terminal *term, int count)
2386 {
2387 int move_amount = min(term->cols - term->grid->cursor.point.col - 1, count);
2388 term->grid->cursor.point.col += move_amount;
2389 xassert(term->grid->cursor.point.col < term->cols);
2390 term->grid->cursor.lcf = false;
2391 }
2392
2393 void
term_cursor_up(struct terminal * term,int count)2394 term_cursor_up(struct terminal *term, int count)
2395 {
2396 int top = term->origin == ORIGIN_ABSOLUTE ? 0 : term->scroll_region.start;
2397 xassert(term->grid->cursor.point.row >= top);
2398
2399 int move_amount = min(term->grid->cursor.point.row - top, count);
2400 term_cursor_to(term, term->grid->cursor.point.row - move_amount, term->grid->cursor.point.col);
2401 }
2402
2403 void
term_cursor_down(struct terminal * term,int count)2404 term_cursor_down(struct terminal *term, int count)
2405 {
2406 int bottom = term->origin == ORIGIN_ABSOLUTE ? term->rows : term->scroll_region.end;
2407 xassert(bottom >= term->grid->cursor.point.row);
2408
2409 int move_amount = min(bottom - term->grid->cursor.point.row - 1, count);
2410 term_cursor_to(term, term->grid->cursor.point.row + move_amount, term->grid->cursor.point.col);
2411 }
2412
2413 static bool
cursor_blink_rearm_timer(struct terminal * term)2414 cursor_blink_rearm_timer(struct terminal *term)
2415 {
2416 if (term->cursor_blink.fd < 0) {
2417 int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
2418 if (fd < 0) {
2419 LOG_ERRNO("failed to create cursor blink timer FD");
2420 return false;
2421 }
2422
2423 if (!fdm_add(term->fdm, fd, EPOLLIN, &fdm_cursor_blink, term)) {
2424 close(fd);
2425 return false;
2426 }
2427
2428 term->cursor_blink.fd = fd;
2429 }
2430
2431 static const struct itimerspec timer = {
2432 .it_value = {.tv_sec = 0, .tv_nsec = 500000000},
2433 .it_interval = {.tv_sec = 0, .tv_nsec = 500000000},
2434 };
2435
2436 if (timerfd_settime(term->cursor_blink.fd, 0, &timer, NULL) < 0) {
2437 LOG_ERRNO("failed to arm cursor blink timer");
2438 fdm_del(term->fdm, term->cursor_blink.fd);
2439 term->cursor_blink.fd = -1;
2440 return false;
2441 }
2442
2443 return true;
2444 }
2445
2446 static bool
cursor_blink_disarm_timer(struct terminal * term)2447 cursor_blink_disarm_timer(struct terminal *term)
2448 {
2449 fdm_del(term->fdm, term->cursor_blink.fd);
2450 term->cursor_blink.fd = -1;
2451 return true;
2452 }
2453
2454 void
term_cursor_blink_update(struct terminal * term)2455 term_cursor_blink_update(struct terminal *term)
2456 {
2457 bool enable = term->cursor_blink.decset || term->cursor_blink.deccsusr;
2458 bool activate = !term->shutdown.in_progress && enable && term->visual_focus;
2459
2460 LOG_DBG("decset=%d, deccsrusr=%d, focus=%d, shutting-down=%d, enable=%d, activate=%d",
2461 term->cursor_blink.decset, term->cursor_blink.deccsusr,
2462 term->visual_focus, term->shutdown.in_progress,
2463 enable, activate);
2464
2465 if (activate && term->cursor_blink.fd < 0) {
2466 term->cursor_blink.state = CURSOR_BLINK_ON;
2467 cursor_blink_rearm_timer(term);
2468 } else if (!activate && term->cursor_blink.fd >= 0)
2469 cursor_blink_disarm_timer(term);
2470 }
2471
2472 static bool
selection_on_top_region(const struct terminal * term,struct scroll_region region)2473 selection_on_top_region(const struct terminal *term,
2474 struct scroll_region region)
2475 {
2476 return region.start > 0 &&
2477 selection_on_rows(term, 0, region.start - 1);
2478 }
2479
2480 static bool
selection_on_bottom_region(const struct terminal * term,struct scroll_region region)2481 selection_on_bottom_region(const struct terminal *term,
2482 struct scroll_region region)
2483 {
2484 return region.end < term->rows &&
2485 selection_on_rows(term, region.end, term->rows - 1);
2486 }
2487
2488 void
term_scroll_partial(struct terminal * term,struct scroll_region region,int rows)2489 term_scroll_partial(struct terminal *term, struct scroll_region region, int rows)
2490 {
2491 LOG_DBG("scroll: rows=%d, region.start=%d, region.end=%d",
2492 rows, region.start, region.end);
2493
2494 /* Verify scroll amount has been clamped */
2495 xassert(rows <= region.end - region.start);
2496
2497 /* Cancel selections that cannot be scrolled */
2498 if (unlikely(term->selection.end.row >= 0)) {
2499 /*
2500 * Selection is (partly) inside either the top or bottom
2501 * scrolling regions, or on (at least one) of the lines
2502 * scrolled in (i.e. re-used lines).
2503 */
2504 if (selection_on_top_region(term, region) ||
2505 selection_on_bottom_region(term, region) ||
2506 selection_on_rows(term, region.end - rows, region.end - 1))
2507 {
2508 selection_cancel(term);
2509 }
2510 }
2511
2512 sixel_scroll_up(term, rows);
2513
2514 bool view_follows = term->grid->view == term->grid->offset;
2515 term->grid->offset += rows;
2516 term->grid->offset &= term->grid->num_rows - 1;
2517
2518 if (view_follows) {
2519 selection_view_down(term, term->grid->offset);
2520 term->grid->view = term->grid->offset;
2521 }
2522
2523 /* Top non-scrolling region. */
2524 for (int i = region.start - 1; i >= 0; i--)
2525 grid_swap_row(term->grid, i - rows, i);
2526
2527 /* Bottom non-scrolling region */
2528 for (int i = term->rows - 1; i >= region.end; i--)
2529 grid_swap_row(term->grid, i - rows, i);
2530
2531 /* Erase scrolled in lines */
2532 for (int r = region.end - rows; r < region.end; r++) {
2533 struct row *row = grid_row_and_alloc(term->grid, r);
2534 erase_line(term, row);
2535 }
2536
2537 term_damage_scroll(term, DAMAGE_SCROLL, region, rows);
2538 term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
2539
2540 #if defined(_DEBUG)
2541 for (int r = 0; r < term->rows; r++)
2542 xassert(grid_row(term->grid, r) != NULL);
2543 #endif
2544 }
2545
2546 void
term_scroll(struct terminal * term,int rows)2547 term_scroll(struct terminal *term, int rows)
2548 {
2549 term_scroll_partial(term, term->scroll_region, rows);
2550 }
2551
2552 void
term_scroll_reverse_partial(struct terminal * term,struct scroll_region region,int rows)2553 term_scroll_reverse_partial(struct terminal *term,
2554 struct scroll_region region, int rows)
2555 {
2556 LOG_DBG("scroll reverse: rows=%d, region.start=%d, region.end=%d",
2557 rows, region.start, region.end);
2558
2559 /* Verify scroll amount has been clamped */
2560 xassert(rows <= region.end - region.start);
2561
2562 /* Cancel selections that cannot be scrolled */
2563 if (unlikely(term->selection.end.row >= 0)) {
2564 /*
2565 * Selection is (partly) inside either the top or bottom
2566 * scrolling regions, or on (at least one) of the lines
2567 * scrolled in (i.e. re-used lines).
2568 */
2569 if (selection_on_top_region(term, region) ||
2570 selection_on_bottom_region(term, region) ||
2571 selection_on_rows(term, region.start, region.start + rows - 1))
2572 {
2573 selection_cancel(term);
2574 }
2575 }
2576
2577 sixel_scroll_down(term, rows);
2578
2579 bool view_follows = term->grid->view == term->grid->offset;
2580 term->grid->offset -= rows;
2581 while (term->grid->offset < 0)
2582 term->grid->offset += term->grid->num_rows;
2583 term->grid->offset &= term->grid->num_rows - 1;
2584
2585 xassert(term->grid->offset >= 0);
2586 xassert(term->grid->offset < term->grid->num_rows);
2587
2588 if (view_follows) {
2589 selection_view_up(term, term->grid->offset);
2590 term->grid->view = term->grid->offset;
2591 }
2592
2593 /* Bottom non-scrolling region */
2594 for (int i = region.end + rows; i < term->rows + rows; i++)
2595 grid_swap_row(term->grid, i, i - rows);
2596
2597 /* Top non-scrolling region */
2598 for (int i = 0 + rows; i < region.start + rows; i++)
2599 grid_swap_row(term->grid, i, i - rows);
2600
2601 /* Erase scrolled in lines */
2602 for (int r = region.start; r < region.start + rows; r++) {
2603 struct row *row = grid_row_and_alloc(term->grid, r);
2604 erase_line(term, row);
2605 }
2606
2607 term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows);
2608 term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
2609
2610 #if defined(_DEBUG)
2611 for (int r = 0; r < term->rows; r++)
2612 xassert(grid_row(term->grid, r) != NULL);
2613 #endif
2614 }
2615
2616 void
term_scroll_reverse(struct terminal * term,int rows)2617 term_scroll_reverse(struct terminal *term, int rows)
2618 {
2619 term_scroll_reverse_partial(term, term->scroll_region, rows);
2620 }
2621
2622 void
term_carriage_return(struct terminal * term)2623 term_carriage_return(struct terminal *term)
2624 {
2625 term_cursor_left(term, term->grid->cursor.point.col);
2626 }
2627
2628 void
term_linefeed(struct terminal * term)2629 term_linefeed(struct terminal *term)
2630 {
2631 term->grid->cur_row->linebreak = true;
2632 term->grid->cursor.lcf = false;
2633
2634 if (term->grid->cursor.point.row == term->scroll_region.end - 1)
2635 term_scroll(term, 1);
2636 else
2637 term_cursor_down(term, 1);
2638 }
2639
2640 void
term_reverse_index(struct terminal * term)2641 term_reverse_index(struct terminal *term)
2642 {
2643 if (term->grid->cursor.point.row == term->scroll_region.start)
2644 term_scroll_reverse(term, 1);
2645 else
2646 term_cursor_up(term, 1);
2647 }
2648
2649 void
term_reset_view(struct terminal * term)2650 term_reset_view(struct terminal *term)
2651 {
2652 if (term->grid->view == term->grid->offset)
2653 return;
2654
2655 term->grid->view = term->grid->offset;
2656 term_damage_view(term);
2657 }
2658
2659 void
term_save_cursor(struct terminal * term)2660 term_save_cursor(struct terminal *term)
2661 {
2662 term->grid->saved_cursor = term->grid->cursor;
2663 term->vt.saved_attrs = term->vt.attrs;
2664 term->saved_charsets = term->charsets;
2665 }
2666
2667 void
term_restore_cursor(struct terminal * term,const struct cursor * cursor)2668 term_restore_cursor(struct terminal *term, const struct cursor *cursor)
2669 {
2670 int row = min(cursor->point.row, term->rows - 1);
2671 int col = min(cursor->point.col, term->cols - 1);
2672
2673 term_cursor_to(term, row, col);
2674 term->grid->cursor.lcf = cursor->lcf;
2675
2676 term->vt.attrs = term->vt.saved_attrs;
2677 term->charsets = term->saved_charsets;
2678 term_update_ascii_printer(term);
2679 }
2680
2681 void
term_visual_focus_in(struct terminal * term)2682 term_visual_focus_in(struct terminal *term)
2683 {
2684 if (term->visual_focus)
2685 return;
2686
2687 term->visual_focus = true;
2688 term_cursor_blink_update(term);
2689 render_refresh_csd(term);
2690 }
2691
2692 void
term_visual_focus_out(struct terminal * term)2693 term_visual_focus_out(struct terminal *term)
2694 {
2695 if (!term->visual_focus)
2696 return;
2697
2698 term->visual_focus = false;
2699 term_cursor_blink_update(term);
2700 render_refresh_csd(term);
2701 }
2702
2703 void
term_kbd_focus_in(struct terminal * term)2704 term_kbd_focus_in(struct terminal *term)
2705 {
2706 if (term->kbd_focus)
2707 return;
2708
2709 term->kbd_focus = true;
2710
2711 if (term->render.urgency) {
2712 term->render.urgency = false;
2713 term_damage_margins(term);
2714 }
2715
2716 cursor_refresh(term);
2717
2718 if (term->focus_events)
2719 term_to_slave(term, "\033[I", 3);
2720 }
2721
2722 void
term_kbd_focus_out(struct terminal * term)2723 term_kbd_focus_out(struct terminal *term)
2724 {
2725 if (!term->kbd_focus)
2726 return;
2727
2728 tll_foreach(term->wl->seats, it)
2729 if (it->item.kbd_focus == term)
2730 return;
2731
2732 #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
2733 if (term_ime_reset(term))
2734 render_refresh(term);
2735 #endif
2736
2737 term->kbd_focus = false;
2738 cursor_refresh(term);
2739
2740 if (term->focus_events)
2741 term_to_slave(term, "\033[O", 3);
2742 }
2743
2744 static int
linux_mouse_button_to_x(int button)2745 linux_mouse_button_to_x(int button)
2746 {
2747 switch (button) {
2748 case BTN_LEFT: return 1;
2749 case BTN_MIDDLE: return 2;
2750 case BTN_RIGHT: return 3;
2751 case BTN_BACK: return 4;
2752 case BTN_FORWARD: return 5;
2753 case BTN_WHEEL_LEFT: return 6; /* Foot custom define */
2754 case BTN_WHEEL_RIGHT: return 7; /* Foot custom define */
2755 case BTN_SIDE: return 8;
2756 case BTN_EXTRA: return 9;
2757 case BTN_TASK: return -1; /* TODO: ??? */
2758
2759 default:
2760 LOG_WARN("unrecognized mouse button: %d (0x%x)", button, button);
2761 return -1;
2762 }
2763 }
2764
2765 static int
encode_xbutton(int xbutton)2766 encode_xbutton(int xbutton)
2767 {
2768 switch (xbutton) {
2769 case 1: case 2: case 3:
2770 return xbutton - 1;
2771
2772 case 4: case 5: case 6: case 7:
2773 /* Like button 1 and 2, but with 64 added */
2774 return xbutton - 4 + 64;
2775
2776 case 8: case 9: case 10: case 11:
2777 /* Similar to 4 and 5, but adding 128 instead of 64 */
2778 return xbutton - 8 + 128;
2779
2780 default:
2781 LOG_ERR("cannot encode X mouse button: %d", xbutton);
2782 return -1;
2783 }
2784 }
2785
2786 static void
report_mouse_click(struct terminal * term,int encoded_button,int row,int col,bool release)2787 report_mouse_click(struct terminal *term, int encoded_button, int row, int col,
2788 bool release)
2789 {
2790 char response[128];
2791
2792 switch (term->mouse_reporting) {
2793 case MOUSE_NORMAL: {
2794 int encoded_col = 32 + col + 1;
2795 int encoded_row = 32 + row + 1;
2796 if (encoded_col > 255 || encoded_row > 255)
2797 return;
2798
2799 snprintf(response, sizeof(response), "\033[M%c%c%c",
2800 32 + (release ? 3 : encoded_button), encoded_col, encoded_row);
2801 break;
2802 }
2803
2804 case MOUSE_SGR:
2805 snprintf(response, sizeof(response), "\033[<%d;%d;%d%c",
2806 encoded_button, col + 1, row + 1, release ? 'm' : 'M');
2807 break;
2808
2809 case MOUSE_URXVT:
2810 snprintf(response, sizeof(response), "\033[%d;%d;%dM",
2811 32 + (release ? 3 : encoded_button), col + 1, row + 1);
2812 break;
2813
2814 case MOUSE_UTF8:
2815 /* Unimplemented */
2816 return;
2817 }
2818
2819 term_to_slave(term, response, strlen(response));
2820 }
2821
2822 static void
report_mouse_motion(struct terminal * term,int encoded_button,int row,int col)2823 report_mouse_motion(struct terminal *term, int encoded_button, int row, int col)
2824 {
2825 report_mouse_click(term, encoded_button, row, col, false);
2826 }
2827
2828 bool
term_mouse_grabbed(const struct terminal * term,struct seat * seat)2829 term_mouse_grabbed(const struct terminal *term, struct seat *seat)
2830 {
2831 /*
2832 * Mouse is grabbed by us, regardless of whether mouse tracking has been enabled or not.
2833 */
2834 return term->mouse_tracking == MOUSE_NONE ||
2835 (seat->kbd_focus == term &&
2836 seat->kbd.shift &&
2837 !seat->kbd.alt && /*!seat->kbd.ctrl &&*/ !seat->kbd.super);
2838 }
2839
2840 void
term_mouse_down(struct terminal * term,int button,int row,int col,bool _shift,bool _alt,bool _ctrl)2841 term_mouse_down(struct terminal *term, int button, int row, int col,
2842 bool _shift, bool _alt, bool _ctrl)
2843 {
2844 /* Map libevent button event code to X button number */
2845 int xbutton = linux_mouse_button_to_x(button);
2846 if (xbutton == -1)
2847 return;
2848
2849 int encoded = encode_xbutton(xbutton);
2850 if (encoded == -1)
2851 return;
2852
2853
2854 bool has_focus = term->kbd_focus;
2855 bool shift = has_focus ? _shift : false;
2856 bool alt = has_focus ? _alt : false;
2857 bool ctrl = has_focus ? _ctrl : false;
2858
2859 encoded += (shift ? 4 : 0) + (alt ? 8 : 0) + (ctrl ? 16 : 0);
2860
2861 switch (term->mouse_tracking) {
2862 case MOUSE_NONE:
2863 break;
2864
2865 case MOUSE_CLICK:
2866 case MOUSE_DRAG:
2867 case MOUSE_MOTION:
2868 report_mouse_click(term, encoded, row, col, false);
2869 break;
2870
2871 case MOUSE_X10:
2872 /* Never enabled */
2873 BUG("X10 mouse mode not implemented");
2874 break;
2875 }
2876 }
2877
2878 void
term_mouse_up(struct terminal * term,int button,int row,int col,bool _shift,bool _alt,bool _ctrl)2879 term_mouse_up(struct terminal *term, int button, int row, int col,
2880 bool _shift, bool _alt, bool _ctrl)
2881 {
2882 /* Map libevent button event code to X button number */
2883 int xbutton = linux_mouse_button_to_x(button);
2884 if (xbutton == -1)
2885 return;
2886
2887 if (xbutton == 4 || xbutton == 5) {
2888 /* No release events for vertical scroll wheel buttons */
2889 return;
2890 }
2891
2892 int encoded = encode_xbutton(xbutton);
2893 if (encoded == -1)
2894 return;
2895
2896 bool has_focus = term->kbd_focus;
2897 bool shift = has_focus ? _shift : false;
2898 bool alt = has_focus ? _alt : false;
2899 bool ctrl = has_focus ? _ctrl : false;
2900
2901 encoded += (shift ? 4 : 0) + (alt ? 8 : 0) + (ctrl ? 16 : 0);
2902
2903 switch (term->mouse_tracking) {
2904 case MOUSE_NONE:
2905 break;
2906
2907 case MOUSE_CLICK:
2908 case MOUSE_DRAG:
2909 case MOUSE_MOTION:
2910 report_mouse_click(term, encoded, row, col, true);
2911 break;
2912
2913 case MOUSE_X10:
2914 /* Never enabled */
2915 BUG("X10 mouse mode not implemented");
2916 break;
2917 }
2918 }
2919
2920 void
term_mouse_motion(struct terminal * term,int button,int row,int col,bool _shift,bool _alt,bool _ctrl)2921 term_mouse_motion(struct terminal *term, int button, int row, int col,
2922 bool _shift, bool _alt, bool _ctrl)
2923 {
2924 int encoded = 0;
2925
2926 if (button != 0) {
2927 /* Map libevent button event code to X button number */
2928 int xbutton = linux_mouse_button_to_x(button);
2929 if (xbutton == -1)
2930 return;
2931
2932 encoded = encode_xbutton(xbutton);
2933 if (encoded == -1)
2934 return;
2935 } else
2936 encoded = 3; /* "released" */
2937
2938 bool has_focus = term->kbd_focus;
2939 bool shift = has_focus ? _shift : false;
2940 bool alt = has_focus ? _alt : false;
2941 bool ctrl = has_focus ? _ctrl : false;
2942
2943 encoded += 32; /* Motion event */
2944 encoded += (shift ? 4 : 0) + (alt ? 8 : 0) + (ctrl ? 16 : 0);
2945
2946 switch (term->mouse_tracking) {
2947 case MOUSE_NONE:
2948 case MOUSE_CLICK:
2949 return;
2950
2951 case MOUSE_DRAG:
2952 if (button == 0)
2953 return;
2954 /* FALLTHROUGH */
2955
2956 case MOUSE_MOTION:
2957 report_mouse_motion(term, encoded, row, col);
2958 break;
2959
2960 case MOUSE_X10:
2961 /* Never enabled */
2962 BUG("X10 mouse mode not implemented");
2963 break;
2964 }
2965 }
2966
2967 void
term_xcursor_update_for_seat(struct terminal * term,struct seat * seat)2968 term_xcursor_update_for_seat(struct terminal *term, struct seat *seat)
2969 {
2970 const char *xcursor = NULL;
2971
2972 switch (term->active_surface) {
2973 case TERM_SURF_GRID: {
2974 xcursor = seat->pointer.hidden ? XCURSOR_HIDDEN
2975 : term->is_searching ? XCURSOR_LEFT_PTR
2976 : (seat->mouse.col >= 0 &&
2977 seat->mouse.row >= 0 &&
2978 term_mouse_grabbed(term, seat)) ? XCURSOR_TEXT
2979 : XCURSOR_LEFT_PTR;
2980 break;
2981 }
2982 case TERM_SURF_SEARCH:
2983 case TERM_SURF_SCROLLBACK_INDICATOR:
2984 case TERM_SURF_RENDER_TIMER:
2985 case TERM_SURF_JUMP_LABEL:
2986 case TERM_SURF_TITLE:
2987 case TERM_SURF_BUTTON_MINIMIZE:
2988 case TERM_SURF_BUTTON_MAXIMIZE:
2989 case TERM_SURF_BUTTON_CLOSE:
2990 xcursor = XCURSOR_LEFT_PTR;
2991 break;
2992
2993 case TERM_SURF_BORDER_LEFT:
2994 case TERM_SURF_BORDER_RIGHT:
2995 case TERM_SURF_BORDER_TOP:
2996 case TERM_SURF_BORDER_BOTTOM:
2997 xcursor = xcursor_for_csd_border(term, seat->mouse.x, seat->mouse.y);
2998 break;
2999
3000 case TERM_SURF_NONE:
3001 return;
3002 }
3003
3004 if (xcursor == NULL)
3005 BUG("xcursor not set");
3006
3007 render_xcursor_set(seat, term, xcursor);
3008 }
3009
3010 void
term_xcursor_update(struct terminal * term)3011 term_xcursor_update(struct terminal *term)
3012 {
3013 tll_foreach(term->wl->seats, it)
3014 term_xcursor_update_for_seat(term, &it->item);
3015 }
3016
3017 void
term_set_window_title(struct terminal * term,const char * title)3018 term_set_window_title(struct terminal *term, const char *title)
3019 {
3020 if (term->conf->locked_title && term->window_title_has_been_set)
3021 return;
3022
3023 if (term->window_title != NULL && strcmp(term->window_title, title) == 0)
3024 return;
3025
3026 free(term->window_title);
3027 term->window_title = xstrdup(title);
3028 render_refresh_title(term);
3029 term->window_title_has_been_set = true;
3030 }
3031
3032 void
term_flash(struct terminal * term,unsigned duration_ms)3033 term_flash(struct terminal *term, unsigned duration_ms)
3034 {
3035 LOG_DBG("FLASH for %ums", duration_ms);
3036
3037 struct itimerspec alarm = {
3038 .it_value = {.tv_sec = 0, .tv_nsec = duration_ms * 1000000},
3039 };
3040
3041 if (timerfd_settime(term->flash.fd, 0, &alarm, NULL) < 0)
3042 LOG_ERRNO("failed to arm flash timer");
3043 else {
3044 term->flash.active = true;
3045 }
3046 }
3047
3048 void
term_bell(struct terminal * term)3049 term_bell(struct terminal *term)
3050 {
3051 if (!term->bell_action_enabled)
3052 return;
3053
3054 if (term->conf->bell.urgent && !term->kbd_focus) {
3055 if (!wayl_win_set_urgent(term->window)) {
3056 /*
3057 * Urgency (xdg-activation) is relatively new in
3058 * Wayland. Fallback to our old, “faked”, urgency -
3059 * rendering our window margins in red
3060 */
3061 term->render.urgency = true;
3062 term_damage_margins(term);
3063 }
3064 }
3065
3066 if (term->conf->bell.notify)
3067 notify_notify(term, "Bell", "Bell in terminal");
3068
3069 if ((term->conf->bell.command.argv.args != NULL) &&
3070 (!term->kbd_focus || term->conf->bell.command_focused))
3071 {
3072 int devnull = open("/dev/null", O_RDONLY);
3073 spawn(term->reaper, NULL, term->conf->bell.command.argv.args, devnull, -1, -1);
3074
3075 if (devnull >= 0)
3076 close(devnull);
3077 }
3078 }
3079
3080 bool
term_spawn_new(const struct terminal * term)3081 term_spawn_new(const struct terminal *term)
3082 {
3083 return spawn(
3084 term->reaper, term->cwd, (char *const []){term->foot_exe, NULL},
3085 -1, -1, -1);
3086 }
3087
3088 void
term_enable_app_sync_updates(struct terminal * term)3089 term_enable_app_sync_updates(struct terminal *term)
3090 {
3091 term->render.app_sync_updates.enabled = true;
3092
3093 if (timerfd_settime(
3094 term->render.app_sync_updates.timer_fd, 0,
3095 &(struct itimerspec){.it_value = {.tv_sec = 1}}, NULL) < 0)
3096 {
3097 LOG_ERR("failed to arm timer for application synchronized updates");
3098 }
3099
3100 /* Disable pending refresh *iff* the grid is the *only* thing
3101 * scheduled to be re-rendered */
3102 if (!term->render.refresh.csd && !term->render.refresh.search &&
3103 !term->render.pending.csd && !term->render.pending.search)
3104 {
3105 term->render.refresh.grid = false;
3106 term->render.pending.grid = false;
3107 }
3108
3109 /* Disarm delayed rendering timers */
3110 timerfd_settime(
3111 term->delayed_render_timer.lower_fd, 0,
3112 &(struct itimerspec){{0}}, NULL);
3113 timerfd_settime(
3114 term->delayed_render_timer.upper_fd, 0,
3115 &(struct itimerspec){{0}}, NULL);
3116 term->delayed_render_timer.is_armed = false;
3117 }
3118
3119 void
term_disable_app_sync_updates(struct terminal * term)3120 term_disable_app_sync_updates(struct terminal *term)
3121 {
3122 if (!term->render.app_sync_updates.enabled)
3123 return;
3124
3125 term->render.app_sync_updates.enabled = false;
3126 render_refresh(term);
3127
3128 /* Reset timers */
3129 timerfd_settime(
3130 term->render.app_sync_updates.timer_fd, 0,
3131 &(struct itimerspec){{0}}, NULL);
3132 }
3133
3134 static inline void
print_linewrap(struct terminal * term)3135 print_linewrap(struct terminal *term)
3136 {
3137 if (likely(!term->grid->cursor.lcf)) {
3138 /* Not and end of line */
3139 return;
3140 }
3141
3142 if (unlikely(!term->auto_margin)) {
3143 /* Auto-wrap disabled */
3144 return;
3145 }
3146
3147 term->grid->cur_row->linebreak = false;
3148 term->grid->cursor.lcf = false;
3149
3150 const int row = term->grid->cursor.point.row;
3151
3152 if (row == term->scroll_region.end - 1)
3153 term_scroll(term, 1);
3154 else {
3155 const int new_row = min(row + 1, term->rows - 1);
3156 term->grid->cursor.point.row = new_row;
3157 term->grid->cur_row = grid_row(term->grid, new_row);
3158 }
3159
3160 term->grid->cursor.point.col = 0;
3161 }
3162
3163 static inline void
print_insert(struct terminal * term,int width)3164 print_insert(struct terminal *term, int width)
3165 {
3166 if (likely(!term->insert_mode))
3167 return;
3168
3169 xassert(width > 0);
3170
3171 struct row *row = term->grid->cur_row;
3172 const size_t move_count = max(0, term->cols - term->grid->cursor.point.col - width);
3173
3174 memmove(
3175 &row->cells[term->grid->cursor.point.col + width],
3176 &row->cells[term->grid->cursor.point.col],
3177 move_count * sizeof(struct cell));
3178
3179 /* Mark moved cells as dirty */
3180 for (size_t i = term->grid->cursor.point.col + width; i < term->cols; i++)
3181 row->cells[i].attrs.clean = 0;
3182 }
3183
3184 static void
print_spacer(struct terminal * term,int col,int remaining)3185 print_spacer(struct terminal *term, int col, int remaining)
3186 {
3187 struct grid *grid = term->grid;
3188 struct row *row = grid->cur_row;
3189 struct cell *cell = &row->cells[col];
3190
3191 cell->wc = CELL_SPACER + remaining;
3192 cell->attrs = term->vt.attrs;
3193 }
3194
3195 void
term_print(struct terminal * term,wchar_t wc,int width)3196 term_print(struct terminal *term, wchar_t wc, int width)
3197 {
3198 xassert(width > 0);
3199
3200 struct grid *grid = term->grid;
3201
3202 if (unlikely(term->charsets.set[term->charsets.selected] == CHARSET_GRAPHIC) &&
3203 wc >= 0x60 && wc <= 0x7e)
3204 {
3205 /* 0x60 - 0x7e */
3206 static const wchar_t vt100_0[] = {
3207 L'◆', L'▒', L'␉', L'␌', L'␍', L'␊', L'°', L'±', /* ` - g */
3208 L'', L'␋', L'┘', L'┐', L'┌', L'└', L'┼', L'⎺', /* h - o */
3209 L'⎻', L'─', L'⎼', L'⎽', L'├', L'┤', L'┴', L'┬', /* p - w */
3210 L'│', L'≤', L'≥', L'π', L'≠', L'£', L'·', /* x - ~ */
3211 };
3212
3213 xassert(width == 1);
3214 wc = vt100_0[wc - 0x60];
3215 }
3216
3217 print_linewrap(term);
3218 print_insert(term, width);
3219
3220 int col = grid->cursor.point.col;
3221
3222 if (unlikely(width > 1) && likely(term->auto_margin) &&
3223 col + width > term->cols)
3224 {
3225 /* Multi-column character that doesn't fit on current line -
3226 * pad with spacers */
3227 for (size_t i = col; i < term->cols; i++)
3228 print_spacer(term, i, 0);
3229
3230 /* And force a line-wrap */
3231 grid->cursor.lcf = 1;
3232 print_linewrap(term);
3233 col = 0;
3234 }
3235
3236 sixel_overwrite_at_cursor(term, width);
3237
3238 /* *Must* get current cell *after* linewrap+insert */
3239 struct row *row = grid->cur_row;
3240 row->dirty = true;
3241 row->linebreak = true;
3242
3243 struct cell *cell = &row->cells[col];
3244 cell->wc = term->vt.last_printed = wc;
3245 cell->attrs = term->vt.attrs;
3246
3247 if (term->vt.osc8.uri != NULL) {
3248 grid_row_uri_range_put(
3249 row, col, term->vt.osc8.uri, term->vt.osc8.id);
3250
3251 switch (term->conf->url.osc8_underline) {
3252 case OSC8_UNDERLINE_ALWAYS:
3253 cell->attrs.url = true;
3254 break;
3255
3256 case OSC8_UNDERLINE_URL_MODE:
3257 break;
3258 }
3259 }
3260
3261 /* Advance cursor the 'additional' columns while dirty:ing the cells */
3262 for (int i = 1; i < width && col < term->cols - 1; i++) {
3263 col++;
3264 print_spacer(term, col, width - i);
3265 }
3266
3267 /* Advance cursor */
3268 if (unlikely(++col >= term->cols)) {
3269 grid->cursor.lcf = true;
3270 col--;
3271 } else
3272 xassert(!grid->cursor.lcf);
3273
3274 grid->cursor.point.col = col;
3275 }
3276
3277 static void
ascii_printer_generic(struct terminal * term,wchar_t wc)3278 ascii_printer_generic(struct terminal *term, wchar_t wc)
3279 {
3280 term_print(term, wc, 1);
3281 }
3282
3283 static void
ascii_printer_fast(struct terminal * term,wchar_t wc)3284 ascii_printer_fast(struct terminal *term, wchar_t wc)
3285 {
3286 struct grid *grid = term->grid;
3287
3288 xassert(term->charsets.set[term->charsets.selected] == CHARSET_ASCII);
3289 xassert(!term->insert_mode);
3290 xassert(tll_length(grid->sixel_images) == 0);
3291
3292 print_linewrap(term);
3293
3294 /* *Must* get current cell *after* linewrap+insert */
3295 int col = grid->cursor.point.col;
3296 const int uri_start = col;
3297
3298 struct row *row = grid->cur_row;
3299 row->dirty = true;
3300 row->linebreak = true;
3301
3302 struct cell *cell = &row->cells[col];
3303 cell->wc = term->vt.last_printed = wc;
3304 cell->attrs = term->vt.attrs;
3305
3306 /* Advance cursor */
3307 if (unlikely(++col >= term->cols)) {
3308 grid->cursor.lcf = true;
3309 col--;
3310 } else
3311 xassert(!grid->cursor.lcf);
3312
3313 grid->cursor.point.col = col;
3314
3315 if (unlikely(row->extra != NULL))
3316 grid_row_uri_range_erase(row, uri_start, uri_start);
3317 }
3318
3319 static void
ascii_printer_single_shift(struct terminal * term,wchar_t wc)3320 ascii_printer_single_shift(struct terminal *term, wchar_t wc)
3321 {
3322 ascii_printer_generic(term, wc);
3323 term->charsets.selected = term->charsets.saved;
3324 term_update_ascii_printer(term);
3325 }
3326
3327 void
term_update_ascii_printer(struct terminal * term)3328 term_update_ascii_printer(struct terminal *term)
3329 {
3330 void (*new_printer)(struct terminal *term, wchar_t wc) =
3331 unlikely(tll_length(term->grid->sixel_images) > 0 ||
3332 term->vt.osc8.uri != NULL ||
3333 term->charsets.set[term->charsets.selected] == CHARSET_GRAPHIC ||
3334 term->insert_mode)
3335 ? &ascii_printer_generic
3336 : &ascii_printer_fast;
3337
3338 #if defined(_DEBUG) && LOG_ENABLE_DBG
3339 if (term->ascii_printer != new_printer) {
3340 LOG_DBG("§switching ASCII printer %s -> %s",
3341 term->ascii_printer == &ascii_printer_fast ? "fast" : "generic",
3342 new_printer == &ascii_printer_fast ? "fast" : "generic");
3343 }
3344 #endif
3345
3346 term->ascii_printer = new_printer;
3347 }
3348
3349 void
term_single_shift(struct terminal * term,enum charset_designator idx)3350 term_single_shift(struct terminal *term, enum charset_designator idx)
3351 {
3352 term->charsets.saved = term->charsets.selected;
3353 term->charsets.selected = idx;
3354 term->ascii_printer = &ascii_printer_single_shift;
3355 }
3356
3357 enum term_surface
term_surface_kind(const struct terminal * term,const struct wl_surface * surface)3358 term_surface_kind(const struct terminal *term, const struct wl_surface *surface)
3359 {
3360 if (likely(surface == term->window->surface))
3361 return TERM_SURF_GRID;
3362 else if (surface == term->window->search.surf)
3363 return TERM_SURF_SEARCH;
3364 else if (surface == term->window->scrollback_indicator.surf)
3365 return TERM_SURF_SCROLLBACK_INDICATOR;
3366 else if (surface == term->window->render_timer.surf)
3367 return TERM_SURF_RENDER_TIMER;
3368 else if (surface == term->window->csd.surface[CSD_SURF_TITLE].surf)
3369 return TERM_SURF_TITLE;
3370 else if (surface == term->window->csd.surface[CSD_SURF_LEFT].surf)
3371 return TERM_SURF_BORDER_LEFT;
3372 else if (surface == term->window->csd.surface[CSD_SURF_RIGHT].surf)
3373 return TERM_SURF_BORDER_RIGHT;
3374 else if (surface == term->window->csd.surface[CSD_SURF_TOP].surf)
3375 return TERM_SURF_BORDER_TOP;
3376 else if (surface == term->window->csd.surface[CSD_SURF_BOTTOM].surf)
3377 return TERM_SURF_BORDER_BOTTOM;
3378 else if (surface == term->window->csd.surface[CSD_SURF_MINIMIZE].surf)
3379 return TERM_SURF_BUTTON_MINIMIZE;
3380 else if (surface == term->window->csd.surface[CSD_SURF_MAXIMIZE].surf)
3381 return TERM_SURF_BUTTON_MAXIMIZE;
3382 else if (surface == term->window->csd.surface[CSD_SURF_CLOSE].surf)
3383 return TERM_SURF_BUTTON_CLOSE;
3384 else {
3385 tll_foreach(term->window->urls, it) {
3386 if (surface == it->item.surf.surf)
3387 return TERM_SURF_JUMP_LABEL;
3388 }
3389 return TERM_SURF_NONE;
3390 }
3391 }
3392
3393 static bool
rows_to_text(const struct terminal * term,int start,int end,char ** text,size_t * len)3394 rows_to_text(const struct terminal *term, int start, int end,
3395 char **text, size_t *len)
3396 {
3397 struct extraction_context *ctx = extract_begin(SELECTION_NONE, true);
3398 if (ctx == NULL)
3399 return false;
3400
3401 for (size_t r = start;
3402 r != ((end + 1) & (term->grid->num_rows - 1));
3403 r = (r + 1) & (term->grid->num_rows - 1))
3404 {
3405 const struct row *row = term->grid->rows[r];
3406 xassert(row != NULL);
3407
3408 for (int c = 0; c < term->cols; c++)
3409 if (!extract_one(term, row, &row->cells[c], c, ctx))
3410 goto out;
3411 }
3412
3413 out:
3414 return extract_finish(ctx, text, len);
3415 }
3416
3417 bool
term_scrollback_to_text(const struct terminal * term,char ** text,size_t * len)3418 term_scrollback_to_text(const struct terminal *term, char **text, size_t *len)
3419 {
3420 int start = term->grid->offset + term->rows;
3421 int end = term->grid->offset + term->rows - 1;
3422
3423 /* If scrollback isn't full yet, this may be NULL, so scan forward
3424 * until we find the first non-NULL row */
3425 while (term->grid->rows[start] == NULL) {
3426 start++;
3427 start &= term->grid->num_rows - 1;
3428 }
3429
3430 if (end < 0)
3431 end += term->grid->num_rows;
3432
3433 while (term->grid->rows[end] == NULL) {
3434 end--;
3435 if (end < 0)
3436 end += term->grid->num_rows;
3437 }
3438
3439 return rows_to_text(term, start, end, text, len);
3440 }
3441
3442 bool
term_view_to_text(const struct terminal * term,char ** text,size_t * len)3443 term_view_to_text(const struct terminal *term, char **text, size_t *len)
3444 {
3445 int start = grid_row_absolute_in_view(term->grid, 0);
3446 int end = grid_row_absolute_in_view(term->grid, term->rows - 1);
3447 return rows_to_text(term, start, end, text, len);
3448 }
3449
3450 bool
term_ime_is_enabled(const struct terminal * term)3451 term_ime_is_enabled(const struct terminal *term)
3452 {
3453 #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
3454 return term->ime_enabled;
3455 #else
3456 return false;
3457 #endif
3458 }
3459
3460 void
term_ime_enable(struct terminal * term)3461 term_ime_enable(struct terminal *term)
3462 {
3463 #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
3464 if (term->ime_enabled)
3465 return;
3466
3467 LOG_DBG("IME enabled");
3468
3469 term->ime_enabled = true;
3470
3471 /* IME is per seat - enable on all seat currently focusing us */
3472 tll_foreach(term->wl->seats, it) {
3473 if (it->item.kbd_focus == term)
3474 ime_enable(&it->item);
3475 }
3476 #endif
3477 }
3478
3479 void
term_ime_disable(struct terminal * term)3480 term_ime_disable(struct terminal *term)
3481 {
3482 #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
3483 if (!term->ime_enabled)
3484 return;
3485
3486 LOG_DBG("IME disabled");
3487
3488 term->ime_enabled = false;
3489
3490 /* IME is per seat - disable on all seat currently focusing us */
3491 tll_foreach(term->wl->seats, it) {
3492 if (it->item.kbd_focus == term)
3493 ime_disable(&it->item);
3494 }
3495 #endif
3496 }
3497
3498 bool
term_ime_reset(struct terminal * term)3499 term_ime_reset(struct terminal *term)
3500 {
3501 bool at_least_one_seat_was_reset = false;
3502
3503 #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
3504 tll_foreach(term->wl->seats, it) {
3505 struct seat *seat = &it->item;
3506
3507 if (seat->kbd_focus != term)
3508 continue;
3509
3510 ime_reset_preedit(seat);
3511 at_least_one_seat_was_reset = true;
3512 }
3513 #endif
3514
3515 return at_least_one_seat_was_reset;
3516 }
3517
3518 void
term_ime_set_cursor_rect(struct terminal * term,int x,int y,int width,int height)3519 term_ime_set_cursor_rect(struct terminal *term, int x, int y, int width,
3520 int height)
3521 {
3522 #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
3523 tll_foreach(term->wl->seats, it) {
3524 if (it->item.kbd_focus == term) {
3525 it->item.ime.cursor_rect.pending.x = x;
3526 it->item.ime.cursor_rect.pending.y = y;
3527 it->item.ime.cursor_rect.pending.width = width;
3528 it->item.ime.cursor_rect.pending.height = height;
3529 }
3530 }
3531 #endif
3532 }
3533
3534 void
term_osc8_open(struct terminal * term,uint64_t id,const char * uri)3535 term_osc8_open(struct terminal *term, uint64_t id, const char *uri)
3536 {
3537 term_osc8_close(term);
3538 xassert(term->vt.osc8.uri == NULL);
3539
3540 term->vt.osc8.id = id;
3541 term->vt.osc8.uri = xstrdup(uri);
3542 term_update_ascii_printer(term);
3543 }
3544
3545 void
term_osc8_close(struct terminal * term)3546 term_osc8_close(struct terminal *term)
3547 {
3548 free(term->vt.osc8.uri);
3549 term->vt.osc8.uri = NULL;
3550 term->vt.osc8.id = 0;
3551 term_update_ascii_printer(term);
3552 }
3553