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