This repository has been archived by the owner on Jul 5, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathterminal.c
3156 lines (2634 loc) · 90.1 KB
/
terminal.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "terminal.h"
#if defined(__GLIBC__)
#include <malloc.h>
#endif
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <fcntl.h>
#include <linux/input-event-codes.h>
#include <xdg-shell.h>
#define LOG_MODULE "terminal"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "async.h"
#include "config.h"
#include "debug.h"
#include "extract.h"
#include "grid.h"
#include "ime.h"
#include "notify.h"
#include "quirks.h"
#include "reaper.h"
#include "render.h"
#include "selection.h"
#include "sixel.h"
#include "slave.h"
#include "spawn.h"
#include "url-mode.h"
#include "util.h"
#include "vt.h"
#include "xmalloc.h"
#define PTMX_TIMING 0
const char *const XCURSOR_HIDDEN = "hidden";
const char *const XCURSOR_LEFT_PTR = "left_ptr";
const char *const XCURSOR_TEXT = "text";
//const char *const XCURSOR_HAND2 = "hand2";
const char *const XCURSOR_TOP_LEFT_CORNER = "top_left_corner";
const char *const XCURSOR_TOP_RIGHT_CORNER = "top_right_corner";
const char *const XCURSOR_BOTTOM_LEFT_CORNER = "bottom_left_corner";
const char *const XCURSOR_BOTTOM_RIGHT_CORNER = "bottom_right_corner";
const char *const XCURSOR_LEFT_SIDE = "left_side";
const char *const XCURSOR_RIGHT_SIDE = "right_side";
const char *const XCURSOR_TOP_SIDE = "top_side";
const char *const XCURSOR_BOTTOM_SIDE = "bottom_side";
static void
enqueue_data_for_slave(const void *data, size_t len, size_t offset,
ptmx_buffer_list_t *buffer_list)
{
void *copy = xmalloc(len);
memcpy(copy, data, len);
struct ptmx_buffer queued = {
.data = copy,
.len = len,
.idx = offset,
};
tll_push_back(*buffer_list, queued);
}
static bool
data_to_slave(struct terminal *term, const void *data, size_t len,
ptmx_buffer_list_t *buffer_list)
{
/*
* Try a synchronous write first. If we fail to write everything,
* switch to asynchronous.
*/
size_t async_idx = 0;
switch (async_write(term->ptmx, data, len, &async_idx)) {
case ASYNC_WRITE_REMAIN:
/* Switch to asynchronous mode; let FDM write the remaining data */
if (!fdm_event_add(term->fdm, term->ptmx, EPOLLOUT))
return false;
enqueue_data_for_slave(data, len, async_idx, buffer_list);
return true;
case ASYNC_WRITE_DONE:
return true;
case ASYNC_WRITE_ERR:
LOG_ERRNO("failed to synchronously write %zu bytes to slave", len);
return false;
}
BUG("Unexpected async_write() return value");
return false;
}
bool
term_paste_data_to_slave(struct terminal *term, const void *data, size_t len)
{
xassert(term->is_sending_paste_data);
if (term->ptmx < 0) {
/* We're probably in "hold" */
return false;
}
if (tll_length(term->ptmx_paste_buffers) > 0) {
/* Don't even try to send data *now* if there's queued up
* data, since that would result in events arriving out of
* order. */
enqueue_data_for_slave(data, len, 0, &term->ptmx_paste_buffers);
return true;
}
return data_to_slave(term, data, len, &term->ptmx_paste_buffers);
}
bool
term_to_slave(struct terminal *term, const void *data, size_t len)
{
if (term->ptmx < 0) {
/* We're probably in "hold" */
return false;
}
if (tll_length(term->ptmx_buffers) > 0 || term->is_sending_paste_data) {
/*
* Don't even try to send data *now* if there's queued up
* data, since that would result in events arriving out of
* order.
*
* Furthermore, if we're currently sending paste data to the
* client, do *not* mix that stream with other events
* (https://codeberg.org/dnkl/foot/issues/101).
*/
enqueue_data_for_slave(data, len, 0, &term->ptmx_buffers);
return true;
}
return data_to_slave(term, data, len, &term->ptmx_buffers);
}
static bool
fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data)
{
struct terminal *term = data;
/* If there is no queued data, then we shouldn't be in asynchronous mode */
xassert(tll_length(term->ptmx_buffers) > 0 ||
tll_length(term->ptmx_paste_buffers) > 0);
/* Writes a single buffer, returns if not all of it could be written */
#define write_one_buffer(buffer_list) \
{ \
switch (async_write(term->ptmx, it->item.data, it->item.len, &it->item.idx)) { \
case ASYNC_WRITE_DONE: \
free(it->item.data); \
tll_remove(buffer_list, it); \
break; \
case ASYNC_WRITE_REMAIN: \
/* to_slave() updated it->item.idx */ \
return true; \
case ASYNC_WRITE_ERR: \
LOG_ERRNO("failed to asynchronously write %zu bytes to slave", \
it->item.len - it->item.idx); \
return false; \
} \
}
tll_foreach(term->ptmx_paste_buffers, it)
write_one_buffer(term->ptmx_paste_buffers);
/* If we get here, *all* paste data buffers were successfully
* flushed */
if (!term->is_sending_paste_data) {
tll_foreach(term->ptmx_buffers, it)
write_one_buffer(term->ptmx_buffers);
}
/*
* If we get here, *all* buffers were successfully flushed.
*
* Or, we're still sending paste data, in which case we do *not*
* want to send the "normal" queued up data
*
* In both cases, we want to *disable* the FDM callback since
* otherwise we'd just be called right away again, with nothing to
* write.
*/
fdm_event_del(term->fdm, term->ptmx, EPOLLOUT);
return true;
}
#if PTMX_TIMING
static struct timespec last = {0};
#endif
static bool cursor_blink_rearm_timer(struct terminal *term);
/* Externally visible, but not declared in terminal.h, to enable pgo
* to call this function directly */
bool
fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
{
struct terminal *term = data;
const bool pollin = events & EPOLLIN;
const bool pollout = events & EPOLLOUT;
const bool hup = events & EPOLLHUP;
if (pollout) {
if (!fdm_ptmx_out(fdm, fd, events, data))
return false;
}
/* Prevent blinking while typing */
if (term->cursor_blink.fd >= 0) {
term->cursor_blink.state = CURSOR_BLINK_ON;
cursor_blink_rearm_timer(term);
}
uint8_t buf[24 * 1024];
ssize_t count = sizeof(buf);
const size_t max_iterations = !hup ? 10 : (size_t)-1ll;
for (size_t i = 0; i < max_iterations && pollin; i++) {
xassert(pollin);
count = read(term->ptmx, buf, sizeof(buf));
if (count < 0) {
if (errno == EAGAIN || errno == EIO) {
/*
* EAGAIN: no more to read - FDM will trigger us again
* EIO: assume PTY was closed - we already have, or will get, a EPOLLHUP
*/
break;
}
LOG_ERRNO("failed to read from pseudo terminal");
return false;
} else if (count == 0) {
/* Reached end-of-file */
break;
}
vt_from_slave(term, buf, count);
}
if (!term->render.app_sync_updates.enabled) {
/*
* We likely need to re-render. But, we don't want to do it
* immediately. Often, a single client update is done through
* multiple writes. This could lead to us rendering one frame with
* "intermediate" state.
*
* For example, we might end up rendering a frame
* where the client just erased a line, while in the
* next frame, the client wrote to the same line. This
* causes screen "flickering".
*
* Mitigate by always incuring a small delay before
* rendering the next frame. This gives the client
* some time to finish the operation (and thus gives
* us time to receive the last writes before doing any
* actual rendering).
*
* We incur this delay *every* time we receive
* input. To ensure we don't delay rendering
* indefinitely, we start a second timer that is only
* reset when we render.
*
* Note that when the client is producing data at a
* very high pace, we're rate limited by the wayland
* compositor anyway. The delay we introduce here only
* has any effect when the renderer is idle.
*/
uint64_t lower_ns = term->conf->tweak.delayed_render_lower_ns;
uint64_t upper_ns = term->conf->tweak.delayed_render_upper_ns;
if (lower_ns > 0 && upper_ns > 0) {
#if PTMX_TIMING
struct timespec now;
clock_gettime(1, &now);
if (last.tv_sec > 0 || last.tv_nsec > 0) {
struct timeval diff;
struct timeval l = {last.tv_sec, last.tv_nsec / 1000};
struct timeval n = {now.tv_sec, now.tv_nsec / 1000};
timersub(&n, &l, &diff);
LOG_INFO("waited %lu µs for more input", diff.tv_usec);
}
last = now;
#endif
xassert(lower_ns < 1000000000);
xassert(upper_ns < 1000000000);
xassert(upper_ns > lower_ns);
timerfd_settime(
term->delayed_render_timer.lower_fd, 0,
&(struct itimerspec){.it_value = {.tv_nsec = lower_ns}},
NULL);
/* Second timeout - only reset when we render. Set to one
* frame (assuming 60Hz) */
if (!term->delayed_render_timer.is_armed) {
timerfd_settime(
term->delayed_render_timer.upper_fd, 0,
&(struct itimerspec){.it_value = {.tv_nsec = upper_ns}},
NULL);
term->delayed_render_timer.is_armed = true;
}
} else
render_refresh(term);
}
if (hup) {
fdm_del(fdm, fd);
term->ptmx = -1;
}
return true;
}
static bool
fdm_flash(struct fdm *fdm, int fd, int events, void *data)
{
if (events & EPOLLHUP)
return false;
struct terminal *term = data;
uint64_t expiration_count;
ssize_t ret = read(
term->flash.fd, &expiration_count, sizeof(expiration_count));
if (ret < 0) {
if (errno == EAGAIN)
return true;
LOG_ERRNO("failed to read flash timer");
return false;
}
LOG_DBG("flash timer expired %llu times",
(unsigned long long)expiration_count);
term->flash.active = false;
term_damage_view(term);
render_refresh(term);
return true;
}
static bool
fdm_blink(struct fdm *fdm, int fd, int events, void *data)
{
if (events & EPOLLHUP)
return false;
struct terminal *term = data;
uint64_t expiration_count;
ssize_t ret = read(
term->blink.fd, &expiration_count, sizeof(expiration_count));
if (ret < 0) {
if (errno == EAGAIN)
return true;
LOG_ERRNO("failed to read blink timer");
return false;
}
LOG_DBG("blink timer expired %llu times",
(unsigned long long)expiration_count);
/* Invert blink state */
term->blink.state = term->blink.state == BLINK_ON
? BLINK_OFF : BLINK_ON;
/* Scan all visible cells and mark rows with blinking cells dirty */
bool no_blinking_cells = true;
for (int r = 0; r < term->rows; r++) {
struct row *row = grid_row_in_view(term->grid, r);
for (int col = 0; col < term->cols; col++) {
struct cell *cell = &row->cells[col];
if (cell->attrs.blink) {
cell->attrs.clean = 0;
row->dirty = true;
no_blinking_cells = false;
}
}
}
if (no_blinking_cells) {
LOG_DBG("disarming blink timer");
term->blink.state = BLINK_ON;
fdm_del(term->fdm, term->blink.fd);
term->blink.fd = -1;
} else
render_refresh(term);
return true;
}
void
term_arm_blink_timer(struct terminal *term)
{
if (term->blink.fd >= 0)
return;
LOG_DBG("arming blink timer");
int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
if (fd < 0) {
LOG_ERRNO("failed to create blink timer FD");
return;
}
if (!fdm_add(term->fdm, fd, EPOLLIN, &fdm_blink, term)) {
close(fd);
return;
}
struct itimerspec alarm = {
.it_value = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
.it_interval = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
};
if (timerfd_settime(fd, 0, &alarm, NULL) < 0) {
LOG_ERRNO("failed to arm blink timer");
fdm_del(term->fdm, fd);
}
term->blink.fd = fd;
}
static void
cursor_refresh(struct terminal *term)
{
term->grid->cur_row->cells[term->grid->cursor.point.col].attrs.clean = 0;
term->grid->cur_row->dirty = true;
render_refresh(term);
}
static bool
fdm_cursor_blink(struct fdm *fdm, int fd, int events, void *data)
{
if (events & EPOLLHUP)
return false;
struct terminal *term = data;
uint64_t expiration_count;
ssize_t ret = read(
term->cursor_blink.fd, &expiration_count, sizeof(expiration_count));
if (ret < 0) {
if (errno == EAGAIN)
return true;
LOG_ERRNO("failed to read cursor blink timer");
return false;
}
LOG_DBG("cursor blink timer expired %llu times",
(unsigned long long)expiration_count);
/* Invert blink state */
term->cursor_blink.state = term->cursor_blink.state == CURSOR_BLINK_ON
? CURSOR_BLINK_OFF : CURSOR_BLINK_ON;
cursor_refresh(term);
return true;
}
static bool
fdm_delayed_render(struct fdm *fdm, int fd, int events, void *data)
{
if (events & EPOLLHUP)
return false;
struct terminal *term = data;
uint64_t unused;
ssize_t ret1 = 0;
ssize_t ret2 = 0;
if (fd == term->delayed_render_timer.lower_fd)
ret1 = read(term->delayed_render_timer.lower_fd, &unused, sizeof(unused));
if (fd == term->delayed_render_timer.upper_fd)
ret2 = read(term->delayed_render_timer.upper_fd, &unused, sizeof(unused));
if ((ret1 < 0 || ret2 < 0)) {
if (errno == EAGAIN)
return true;
LOG_ERRNO("failed to read timeout timer");
return false;
}
if (ret1 > 0)
LOG_DBG("lower delay timer expired");
else if (ret2 > 0)
LOG_DBG("upper delay timer expired");
if (ret1 == 0 && ret2 == 0)
return true;
#if PTMX_TIMING
last = (struct timespec){0};
#endif
/* Reset timers */
struct itimerspec reset = {{0}};
timerfd_settime(term->delayed_render_timer.lower_fd, 0, &reset, NULL);
timerfd_settime(term->delayed_render_timer.upper_fd, 0, &reset, NULL);
term->delayed_render_timer.is_armed = false;
render_refresh(term);
return true;
}
static bool
fdm_app_sync_updates_timeout(
struct fdm *fdm, int fd, int events, void *data)
{
if (events & EPOLLHUP)
return false;
struct terminal *term = data;
uint64_t unused;
ssize_t ret = read(term->render.app_sync_updates.timer_fd,
&unused, sizeof(unused));
if (ret < 0) {
if (errno == EAGAIN)
return true;
LOG_ERRNO("failed to read application synchronized updates timeout timer");
return false;
}
term_disable_app_sync_updates(term);
return true;
}
static bool
initialize_render_workers(struct terminal *term)
{
LOG_INFO("using %zu rendering threads", term->render.workers.count);
if (sem_init(&term->render.workers.start, 0, 0) < 0 ||
sem_init(&term->render.workers.done, 0, 0) < 0)
{
LOG_ERRNO("failed to instantiate render worker semaphores");
return false;
}
int err;
if ((err = mtx_init(&term->render.workers.lock, mtx_plain)) != thrd_success) {
LOG_ERR("failed to instantiate render worker mutex: %s (%d)",
thrd_err_as_string(err), err);
goto err_sem_destroy;
}
term->render.workers.threads = xcalloc(
term->render.workers.count, sizeof(term->render.workers.threads[0]));
for (size_t i = 0; i < term->render.workers.count; i++) {
struct render_worker_context *ctx = xmalloc(sizeof(*ctx));
*ctx = (struct render_worker_context) {
.term = term,
.my_id = 1 + i,
};
int ret = thrd_create(
&term->render.workers.threads[i], &render_worker_thread, ctx);
if (ret != thrd_success) {
LOG_ERR("failed to create render worker thread: %s (%d)",
thrd_err_as_string(ret), ret);
term->render.workers.threads[i] = 0;
return false;
}
}
return true;
err_sem_destroy:
sem_destroy(&term->render.workers.start);
sem_destroy(&term->render.workers.done);
return false;
}
int
term_pt_or_px_as_pixels(const struct terminal *term,
const struct pt_or_px *pt_or_px)
{
return pt_or_px->px == 0
? round(pt_or_px->pt * term->font_dpi / 72)
: pt_or_px->px;
}
static void
free_box_drawing(struct fcft_glyph **box_drawing)
{
if (*box_drawing == NULL)
return;
free(pixman_image_get_data((*box_drawing)->pix));
pixman_image_unref((*box_drawing)->pix);
free(*box_drawing);
*box_drawing = NULL;
}
static bool
term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4])
{
for (size_t i = 0; i < 4; i++) {
xassert(fonts[i] != NULL);
fcft_destroy(term->fonts[i]);
term->fonts[i] = fonts[i];
}
for (size_t i = 0; i < ALEN(term->box_drawing); i++)
free_box_drawing(&term->box_drawing[i]);
const int old_cell_width = term->cell_width;
const int old_cell_height = term->cell_height;
const struct config *conf = term->conf;
term->cell_width =
(term->fonts[0]->space_advance.x > 0
? term->fonts[0]->space_advance.x
: term->fonts[0]->max_advance.x)
+ term_pt_or_px_as_pixels(term, &conf->letter_spacing);
term->cell_height = term->font_line_height.px >= 0
? term_pt_or_px_as_pixels(term, &term->font_line_height)
: max(term->fonts[0]->height,
term->fonts[0]->ascent + term->fonts[0]->descent);
term->font_x_ofs = term_pt_or_px_as_pixels(term, &conf->horizontal_letter_offset);
term->font_y_ofs = term_pt_or_px_as_pixels(term, &conf->vertical_letter_offset);
LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height);
if (term->cell_width < old_cell_width ||
term->cell_height < old_cell_height)
{
/*
* The cell size has decreased.
*
* This means sixels, which we cannot resize, no longer fit
* into their "allocated" grid space.
*
* To be able to fit them, we would have to change the grid
* content. Inserting empty lines _might_ seem acceptable, but
* we'd also need to insert empty columns, which would break
* existing layout completely.
*
* So we delete them.
*/
sixel_destroy_all(term);
} else if (term->cell_width != old_cell_width ||
term->cell_height != old_cell_height)
{
sixel_cell_size_changed(term);
}
/* Use force, since cell-width/height may have changed */
render_resize_force(term, term->width / term->scale, term->height / term->scale);
return true;
}
static float
get_font_dpi(const struct terminal *term)
{
/*
* Use output's DPI to scale font. This is to ensure the font has
* the same physical height (if measured by a ruler) regardless of
* monitor.
*
* Conceptually, we use the physical monitor specs to calculate
* the DPI, and we ignore the output's scaling factor.
*
* However, to deal with fractional scaling, where we're told to
* render at e.g. 2x, but are then downscaled by the compositor to
* e.g. 1.25, we use the scaled DPI value multiplied by the scale
* factor instead.
*
* For integral scaling factors the resulting DPI is the same as
* if we had used the physical DPI.
*
* For fractional scaling factors we'll get a DPI *larger* than
* the physical DPI, that ends up being right when later
* downscaled by the compositor.
*/
/* Use highest DPI from outputs we're mapped on */
double dpi = 0.0;
xassert(term->window != NULL);
tll_foreach(term->window->on_outputs, it) {
if (it->item->dpi > dpi)
dpi = it->item->dpi;
}
/* If we're not mapped, use DPI from first monitor. Hopefully this is where we'll get mapped later... */
if (dpi == 0.) {
tll_foreach(term->wl->monitors, it) {
dpi = it->item.dpi;
break;
}
}
if (dpi == 0) {
/* No monitors? */
dpi = 96.;
}
return dpi;
}
static enum fcft_subpixel
get_font_subpixel(const struct terminal *term)
{
if (term->colors.alpha != 0xffff) {
/* Can't do subpixel rendering on transparent background */
return FCFT_SUBPIXEL_NONE;
}
enum wl_output_subpixel wl_subpixel;
/*
* Wayland doesn't tell us *which* part of the surface that goes
* on a specific output, only whether the surface is mapped to an
* output or not.
*
* Thus, when determining which subpixel mode to use, we can't do
* much but select *an* output. So, we pick the first one.
*
* If we're not mapped at all, we pick the first available
* monitor, and hope that's where we'll eventually get mapped.
*
* If there aren't any monitors we use the "default" subpixel
* mode.
*/
if (tll_length(term->window->on_outputs) > 0)
wl_subpixel = tll_front(term->window->on_outputs)->subpixel;
else if (tll_length(term->wl->monitors) > 0)
wl_subpixel = tll_front(term->wl->monitors).subpixel;
else
wl_subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
switch (wl_subpixel) {
case WL_OUTPUT_SUBPIXEL_UNKNOWN: return FCFT_SUBPIXEL_DEFAULT;
case WL_OUTPUT_SUBPIXEL_NONE: return FCFT_SUBPIXEL_NONE;
case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: return FCFT_SUBPIXEL_HORIZONTAL_RGB;
case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: return FCFT_SUBPIXEL_HORIZONTAL_BGR;
case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: return FCFT_SUBPIXEL_VERTICAL_RGB;
case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: return FCFT_SUBPIXEL_VERTICAL_BGR;
}
return FCFT_SUBPIXEL_DEFAULT;
}
static bool
font_sized_by_dpi(const struct terminal *term, int scale)
{
return term->conf->dpi_aware == DPI_AWARE_YES ||
(term->conf->dpi_aware == DPI_AWARE_AUTO && scale <= 1);
}
static bool
font_sized_by_scale(const struct terminal *term, int scale)
{
return !font_sized_by_dpi(term, scale);
}
struct font_load_data {
size_t count;
const char **names;
const char *attrs;
struct fcft_font **font;
};
static int
font_loader_thread(void *_data)
{
struct font_load_data *data = _data;
*data->font = fcft_from_name(data->count, data->names, data->attrs);
return *data->font != NULL;
}
static bool
reload_fonts(struct terminal *term)
{
const size_t counts[4] = {
tll_length(term->conf->fonts[0]),
tll_length(term->conf->fonts[1]),
tll_length(term->conf->fonts[2]),
tll_length(term->conf->fonts[3]),
};
/* Configure size (which may have been changed run-time) */
char **names[4];
for (size_t i = 0; i < 4; i++) {
names[i] = xmalloc(counts[i] * sizeof(names[i][0]));
size_t j = 0;
tll_foreach(term->conf->fonts[i], it) {
bool use_px_size = term->font_sizes[i][j].px_size > 0;
char size[64];
const int scale =
font_sized_by_scale(term, term->scale) ? term->scale : 1;
if (use_px_size)
snprintf(size, sizeof(size), ":pixelsize=%d",
term->font_sizes[i][j].px_size * scale);
else
snprintf(size, sizeof(size), ":size=%.2f",
term->font_sizes[i][j].pt_size * (double)scale);
size_t len = strlen(it->item.pattern) + strlen(size) + 1;
names[i][j] = xmalloc(len);
strcpy(names[i][j], it->item.pattern);
strcat(names[i][j], size);
j++;
}
}
/* Did user configure custom bold/italic fonts?
* Or should we use the regular font, with weight/slant attributes? */
const bool custom_bold = counts[1] > 0;
const bool custom_italic = counts[2] > 0;
const bool custom_bold_italic = counts[3] > 0;
const size_t count_regular = counts[0];
const char **names_regular = (const char **)names[0];
const size_t count_bold = custom_bold ? counts[1] : counts[0];
const char **names_bold = (const char **)(custom_bold ? names[1] : names[0]);
const size_t count_italic = custom_italic ? counts[2] : counts[0];
const char **names_italic = (const char **)(custom_italic ? names[2] : names[0]);
const size_t count_bold_italic = custom_bold_italic ? counts[3] : counts[0];
const char **names_bold_italic = (const char **)(custom_bold_italic ? names[3] : names[0]);
const bool use_dpi = font_sized_by_dpi(term, term->scale);
char *attrs[4] = {NULL};
int attr_len[4] = {-1, -1, -1, -1}; /* -1, so that +1 (below) results in 0 */
for (size_t i = 0; i < 2; i++) {
attr_len[0] = snprintf(
attrs[0], attr_len[0] + 1, "dpi=%.2f",
use_dpi ? term->font_dpi : 96);
attr_len[1] = snprintf(
attrs[1], attr_len[1] + 1, "dpi=%.2f:%s",
use_dpi ? term->font_dpi : 96, !custom_bold ? "weight=bold" : "");
attr_len[2] = snprintf(
attrs[2], attr_len[2] + 1, "dpi=%.2f:%s",
use_dpi ? term->font_dpi : 96, !custom_italic ? "slant=italic" : "");
attr_len[3] = snprintf(
attrs[3], attr_len[3] + 1, "dpi=%.2f:%s",
use_dpi ? term->font_dpi : 96, !custom_bold_italic ? "weight=bold:slant=italic" : "");
if (i > 0)
continue;
for (size_t i = 0; i < 4; i++)
attrs[i] = xmalloc(attr_len[i] + 1);
}
struct fcft_font *fonts[4];
struct font_load_data data[4] = {
{count_regular, names_regular, attrs[0], &fonts[0]},
{count_bold, names_bold, attrs[1], &fonts[1]},
{count_italic, names_italic, attrs[2], &fonts[2]},
{count_bold_italic, names_bold_italic, attrs[3], &fonts[3]},
};
thrd_t tids[4] = {0};
for (size_t i = 0; i < 4; i++) {
int ret = thrd_create(&tids[i], &font_loader_thread, &data[i]);
if (ret != thrd_success) {
LOG_ERR("failed to create font loader thread: %s (%d)",
thrd_err_as_string(ret), ret);
break;
}
}
bool success = true;
for (size_t i = 0; i < 4; i++) {
if (tids[i] != 0) {
int ret;
thrd_join(tids[i], &ret);
success = success && ret;
} else
success = false;
}
for (size_t i = 0; i < 4; i++) {
for (size_t j = 0; j < counts[i]; j++)
free(names[i][j]);
free(names[i]);
free(attrs[i]);
}
if (!success) {
LOG_ERR("failed to load primary fonts");
for (size_t i = 0; i < 4; i++) {
fcft_destroy(fonts[i]);
fonts[i] = NULL;
}
}
return success ? term_set_fonts(term, fonts) : success;
}
static bool
load_fonts_from_conf(struct terminal *term)
{
for (size_t i = 0; i < 4; i++) {
size_t j = 0;
tll_foreach(term->conf->fonts[i], it) {
term->font_sizes[i][j++] = (struct config_font){
.pt_size = it->item.pt_size, .px_size = it->item.px_size};
}
}
term->font_line_height = term->conf->line_height;
return reload_fonts(term);
}
static void
slave_died(struct reaper *reaper, pid_t pid, int status, void *data)
{
struct terminal *term = data;
LOG_DBG("slave (PID=%u) died", pid);
term->slave_has_been_reaped = true;
term->exit_status = status;
if (!term->conf->hold_at_exit)
term_shutdown(term);
}
struct terminal *
term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
struct wayland *wayl, const char *foot_exe, const char *cwd,
int argc, char *const *argv,
void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data)
{
int ptmx = -1;
int flash_fd = -1;
int delay_lower_fd = -1;
int delay_upper_fd = -1;
int app_sync_updates_fd = -1;
struct terminal *term = malloc(sizeof(*term));
if (unlikely(term == NULL)) {
LOG_ERRNO("malloc() failed");
return NULL;
}
if ((ptmx = posix_openpt(O_RDWR | O_NOCTTY)) == -1) {
LOG_ERRNO("failed to open PTY");
goto close_fds;
}
if ((flash_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) {
LOG_ERRNO("failed to create flash timer FD");
goto close_fds;
}
if ((delay_lower_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1 ||
(delay_upper_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1)
{
LOG_ERRNO("failed to create delayed rendering timer FDs");
goto close_fds;
}
if ((app_sync_updates_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1)
{
LOG_ERRNO("failed to create application synchronized updates timer FD");