• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saitoha / libsixel / 20466639304

23 Dec 2025 04:53PM UTC coverage: 51.46% (-6.3%) from 57.773%
20466639304

push

github

saitoha
build: fix windows find path in images meson build

14511 of 44933 branches covered (32.29%)

21089 of 40981 relevant lines covered (51.46%)

3915123.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

17.65
/src/tty.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 * Copyright (c) 2014-2016 Hayaki Saito
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
8
 * this software and associated documentation files (the "Software"), to deal in
9
 * the Software without restriction, including without limitation the rights to
10
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11
 * the Software, and to permit persons to whom the Software is furnished to do so,
12
 * subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in all
15
 * copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
 */
24

25
#include "config.h"
26

27
/* STDC_HEADERS */
28
#include <stdio.h>
29
#include <stdlib.h>
30
#include <string.h>
31

32
#include <stdint.h>
33

34
#if HAVE_TIME_H
35
# include <time.h>
36
#elif HAVE_SYS_TIME_H
37
# include <sys/time.h>
38
#endif  /* HAVE_SYS_TIME_H */
39
#if HAVE_SYS_TYPES_H
40
# include <sys/types.h>
41
#endif  /* HAVE_SYS_TYPES_H */
42
#if HAVE_UNISTD_H
43
# include <unistd.h>
44
#elif HAVE_SYS_UNISTD_H
45
# include <sys/unistd.h>
46
#endif  /* HAVE_SYS_UNISTD_H */
47
#if HAVE_SYS_SELECT_H
48
# include <sys/select.h>
49
#endif  /* HAVE_SYS_SELECT_H */
50
#if HAVE_ERRNO_H
51
# include <errno.h>
52
#endif  /* HAVE_ERRNO_H */
53
#if HAVE_TERMIOS_H
54
# include <termios.h>
55
#endif  /* HAVE_TERMIOS_H */
56
#if HAVE_SYS_IOCTL_H
57
# include <sys/ioctl.h>
58
#endif  /* HAVE_SYS_IOCTL_H */
59

60
#include <sixel.h>
61
#include "tty.h"
62
#include "compat_stub.h"
63

64
#if defined(_WIN32)
65
# include <io.h>
66
# include <windows.h>
67
# if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
68
#  define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
69
# endif
70
#endif
71

72
/*
73
 * Cache describing the capabilities of the active output device.
74
 * The struct lives in static storage because the helper routines are
75
 * frequently used from the CLI tools without an explicit lifecycle.
76
 */
77
static struct sixel_tty_output_state tty_output_state = {0, 0, 0, 0};
78

79
static int
80
sixel_tty_term_supports_ansi(const char *term);
81

82
static int
83
sixel_tty_term_supports_color(const char *term, const char *colorterm);
84

85
static int
86
sixel_tty_term_supports_ansi(const char *term)
×
87
{
88
    size_t i;
×
89
    size_t count;
×
90
    const char *const *entry;
×
91
    static const char *const denylist[] = {
×
92
        "dumb",
93
        "emacs",
94
        "unknown",
95
        "cons25",
96
        "vt100-nam"
97
    };
98
    static const char *const allowlist[] = {
×
99
        "ansi",
100
        "color",
101
        "xterm",
102
        "rxvt",
103
        "tmux",
104
        "screen",
105
        "linux",
106
        "foot",
107
        "wezterm",
108
        "alacritty",
109
        "konsole",
110
        "kitty",
111
        "gnome",
112
        "eterm",
113
        "cygwin",
114
        "putty",
115
        "vt100",
116
        "vt102",
117
        "vt220",
118
        "st-",
119
        "st"
120
    };
121

122
    if (term == NULL) {
×
123
        return 0;
124
    }
125

126
    count = sizeof(denylist) / sizeof(denylist[0]);
127
    for (i = 0; i < count; ++i) {
×
128
        entry = &denylist[i];
×
129
        if (strcmp(term, *entry) == 0) {
×
130
            return 0;
131
        }
132
    }
133

134
    count = sizeof(allowlist) / sizeof(allowlist[0]);
135
    for (i = 0; i < count; ++i) {
×
136
        entry = &allowlist[i];
×
137
        if (strstr(term, *entry) != NULL) {
×
138
            return 1;
139
        }
140
    }
141

142
    return 0;
143
}
144

145
static int
146
sixel_tty_term_supports_color(const char *term, const char *colorterm)
×
147
{
148
    size_t i;
×
149
    size_t count;
×
150
    const char *const *entry;
×
151
    static const char *const allowlist[] = {
×
152
        "256color",
153
        "color",
154
        "xterm",
155
        "rxvt",
156
        "tmux",
157
        "screen",
158
        "linux",
159
        "foot",
160
        "wezterm",
161
        "alacritty",
162
        "konsole",
163
        "kitty",
164
        "gnome",
165
        "eterm",
166
        "cygwin",
167
        "putty",
168
        "vt220",
169
        "vt340",
170
        "ansi"
171
    };
172

173
    if (colorterm != NULL && colorterm[0] != '\0') {
×
174
        return 1;
175
    }
176

177
    if (term == NULL) {
×
178
        return 0;
179
    }
180

181
    if (strstr(term, "mono") != NULL || strstr(term, "bw") != NULL) {
×
182
        return 0;
183
    }
184

185
    count = sizeof(allowlist) / sizeof(allowlist[0]);
186
    for (i = 0; i < count; ++i) {
×
187
        entry = &allowlist[i];
×
188
        if (strstr(term, *entry) != NULL) {
×
189
            return 1;
190
        }
191
    }
192

193
    return 0;
194
}
195

196
SIXELAPI void
197
sixel_tty_init_output_device(int fd)
812✔
198
{
199
    int istty;
812✔
200
    const char *term;
812✔
201
    const char *colorterm;
812✔
202
    struct sixel_tty_output_state *state;
812✔
203
#if defined(_WIN32)
204
    intptr_t handle_value;
205
    HANDLE handle;
206
    DWORD mode;
207
    DWORD desired;
208
#endif
209

210
    state = &tty_output_state;
812✔
211
    state->is_tty = 0;
812✔
212
    state->use_ansi_sequences = 0;
812✔
213
    state->supports_bold = 0;
812✔
214
    state->supports_color = 0;
812✔
215
    istty = 0;
812✔
216

217
#if HAVE_ISATTY
218
    if (sixel_compat_isatty(fd)) {
812!
219
        istty = 1;
×
220
    }
221
#endif
222

223
    if (istty == 0) {
×
224
        return;
225
    }
226

227
    state->is_tty = 1;
×
228

229
#if defined(_WIN32)
230
    handle_value = _get_osfhandle(fd);
231
    if (handle_value != (intptr_t)-1) {
232
        handle = (HANDLE)handle_value;
233
        if (GetConsoleMode(handle, &mode) != 0) {
234
            desired = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
235
            if (SetConsoleMode(handle, desired) != 0) {
236
                mode = desired;
237
            }
238
            if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) {
239
                state->use_ansi_sequences = 1;
240
                state->supports_bold = 1;
241
                state->supports_color = 1;
242
                return;
243
            }
244
        }
245
    }
246
#endif
247

248
    term = sixel_compat_getenv("TERM");
×
249
    colorterm = sixel_compat_getenv("COLORTERM");
×
250

251
    if (term == NULL || term[0] == '\0') {
×
252
        return;
253
    }
254

255
    if (sixel_tty_term_supports_ansi(term) != 0) {
×
256
        state->use_ansi_sequences = 1;
×
257
        state->supports_bold = 1;
×
258
    }
259

260
    if (state->use_ansi_sequences == 0 &&
×
261
            colorterm != NULL && colorterm[0] != '\0') {
×
262
        state->use_ansi_sequences = 1;
×
263
        state->supports_bold = 1;
×
264
    }
265

266
    if (state->use_ansi_sequences != 0) {
×
267
        if (sixel_tty_term_supports_color(term, colorterm) != 0) {
×
268
            state->supports_color = 1;
×
269
        }
270
    }
271

272
}
1!
273

274
SIXELAPI struct sixel_tty_output_state const *
275
sixel_tty_get_output_state(void)
292✔
276
{
277
    return &tty_output_state;
292✔
278
}
279

280
#if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY
281
SIXELSTATUS
282
sixel_tty_cbreak(struct termios *old_termios, struct termios *new_termios)
×
283
{
284
    SIXELSTATUS status = SIXEL_FALSE;
×
285
    int ret;
×
286

287
    /* set the terminal to cbreak mode */
288
    ret = tcgetattr(STDIN_FILENO, old_termios);
×
289
    if (ret != 0) {
×
290
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
291
        sixel_helper_set_additional_message(
×
292
            "sixel_tty_cbreak: tcgetattr() failed.");
293
        goto end;
×
294
    }
295

296
    (void) memcpy(new_termios, old_termios, sizeof(*old_termios));
×
297
    new_termios->c_lflag &= (tcflag_t)~(ECHO | ICANON);
×
298
    new_termios->c_cc[VMIN] = 1;
×
299
    new_termios->c_cc[VTIME] = 0;
×
300

301
    ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, new_termios);
×
302
    if (ret != 0) {
×
303
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
304
        sixel_helper_set_additional_message(
×
305
            "sixel_tty_cbreak: tcsetattr() failed.");
306
        goto end;
×
307
    }
308

309
    status = SIXEL_OK;
310

311
end:
×
312
    return status;
×
313
}
314
#endif  /* HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY */
315

316

317
#if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY
318
SIXELSTATUS
319
sixel_tty_restore(struct termios *old_termios)
×
320
{
321
    SIXELSTATUS status = SIXEL_FALSE;
×
322
    int ret;
×
323

324
    ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, old_termios);
×
325
    if (ret != 0) {
×
326
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
327
        sixel_helper_set_additional_message(
×
328
            "sixel_tty_restore: tcsetattr() failed.");
329
        goto end;
×
330
    }
331

332
    status = SIXEL_OK;
333

334
end:
×
335
    return status;
×
336
}
337
#endif  /* HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY */
338

339

340
SIXELSTATUS
341
sixel_tty_wait_stdin(int usec)
×
342
{
343
#if HAVE_SYS_SELECT_H
344
    fd_set rfds;
×
345
    struct timeval tv;
×
346
    int ret = 0;
×
347
#endif  /* HAVE_SYS_SELECT_H */
348
    SIXELSTATUS status = SIXEL_FALSE;
×
349

350
#if HAVE_SYS_SELECT_H
351
    tv.tv_sec = usec / 1000000;
×
352
    tv.tv_usec = usec % 1000000;
×
353
    FD_ZERO(&rfds);
×
354
    FD_SET(STDIN_FILENO, &rfds);
×
355
    ret = select(STDIN_FILENO + 1, &rfds, NULL, NULL, &tv);
×
356
    if (ret < 0) {
×
357
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
358
        sixel_helper_set_additional_message(
×
359
            "sixel_tty_wait_stdin: select() failed.");
360
        goto end;
×
361
    }
362

363
    /* success */
364
    status = SIXEL_OK;
365
#else
366
    (void) usec;
367
    goto end;
368
#endif  /* HAVE_SYS_SELECT_H */
369

370
end:
×
371
    return status;
×
372
}
373

374

375
SIXELSTATUS
376
sixel_tty_scroll(
144✔
377
    sixel_write_function f_write,
378
    void *priv,
379
    int outfd,
380
    int height,
381
    int is_animation)
382
{
383
    SIXELSTATUS status = SIXEL_FALSE;
144✔
384
    int nwrite;
144✔
385
#if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY
386
    struct winsize size = {0, 0, 0, 0};
144✔
387
    struct termios old_termios;
144✔
388
    struct termios new_termios;
144✔
389
    int row = 0;
144✔
390
    int col = 0;
144✔
391
    int cellheight;
144✔
392
    int scroll;
144✔
393
    char buffer[256];
144✔
394
    int result;
144✔
395

396
    /* confirm I/O file descriptors are tty devices */
397
    if (!sixel_compat_isatty(STDIN_FILENO)
144!
398
        || !sixel_compat_isatty(outfd)) {
×
399
        /* set cursor position to top-left */
400
        nwrite = f_write("\033[H", 3, priv);
144✔
401
        if (nwrite < 0) {
144!
402
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
403
            sixel_helper_set_additional_message(
×
404
                "sixel_tty_scroll: f_write() failed.");
405
            goto end;
×
406
        }
407
        status = SIXEL_OK;
144✔
408
        goto end;
144✔
409
    }
410

411
    /* request terminal size to tty device with TIOCGWINSZ ioctl */
412
    result = ioctl(outfd, TIOCGWINSZ, &size);
×
413
    if (result != 0) {
×
414
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
415
        sixel_helper_set_additional_message("ioctl() failed.");
×
416
        goto end;
×
417
    }
418

419
    /* if we can not retrieve terminal pixel size over TIOCGWINSZ ioctl,
420
       return immediatly */
421
    if (size.ws_ypixel <= 0) {
×
422
        nwrite = f_write("\033[H", 3, priv);
×
423
        if (nwrite < 0) {
×
424
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
425
            sixel_helper_set_additional_message(
×
426
                "sixel_tty_scroll: f_write() failed.");
427
            goto end;
×
428
        }
429
        status = SIXEL_OK;
×
430
        goto end;
×
431
    }
432

433
    /* if input source is animation and frame No. is more than 1,
434
       output DECSC sequence */
435
    if (is_animation) {
×
436
        nwrite = f_write("\0338", 2, priv);
×
437
        if (nwrite < 0) {
×
438
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
439
            sixel_helper_set_additional_message(
×
440
                "sixel_tty_scroll: f_write() failed.");
441
            goto end;
×
442
        }
443
        status = SIXEL_OK;
×
444
        goto end;
×
445
    }
446

447
    /* set the terminal to cbreak mode */
448
    status = sixel_tty_cbreak(&old_termios, &new_termios);
×
449
    if (SIXEL_FAILED(status)) {
×
450
        goto end;
×
451
    }
452

453
    /* request cursor position report */
454
    nwrite = f_write("\033[6n", 4, priv);
×
455
    if (nwrite < 0) {
×
456
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
457
        sixel_helper_set_additional_message(
×
458
            "sixel_tty_scroll: f_write() failed.");
459
        goto end;
×
460
    }
461

462
    /* wait cursor position report */
463
    if (SIXEL_FAILED(sixel_tty_wait_stdin(1000 * 1000))) { /* wait up to 1 sec */
×
464
        /* If we can't get any response from the terminal,
465
         * move cursor to (1, 1). */
466
        nwrite = f_write("\033[H", 3, priv);
×
467
        if (nwrite < 0) {
×
468
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
469
            sixel_helper_set_additional_message(
×
470
                "sixel_tty_scroll: f_write() failed.");
471
            goto end;
×
472
        }
473
        status = SIXEL_OK;
×
474
        goto end;
×
475
    }
476

477
    /* scan cursor position report */
478
    if (scanf("\033[%d;%dR", &row, &col) != 2) {
×
479
        nwrite = f_write("\033[H", 3, priv);
×
480
        if (nwrite < 0) {
×
481
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
482
            sixel_helper_set_additional_message(
×
483
                "sixel_tty_scroll: f_write() failed.");
484
            goto end;
×
485
        }
486
        status = SIXEL_OK;
×
487
        goto end;
×
488
    }
489

490
    /* restore the terminal mode */
491
    status = sixel_tty_restore(&old_termios);
×
492
    if (SIXEL_FAILED(status)) {
×
493
        goto end;
×
494
    }
495

496
    /* calculate scrolling amount in pixels */
497
    cellheight = height * size.ws_row / size.ws_ypixel + 1;
×
498
    scroll = cellheight + row - size.ws_row + 1;
×
499
    if (scroll > 0) {
×
500
        nwrite = sixel_compat_snprintf(
×
501
            buffer,
502
            sizeof(buffer),
503
            "\033[%dS\033[%dA",
504
            scroll,
505
            scroll);
506
        if (nwrite < 0) {
×
507
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
508
            sixel_helper_set_additional_message(
×
509
                "sixel_tty_scroll: command format failed.");
510
        }
511
        nwrite = f_write(buffer, (int)strlen(buffer), priv);
×
512
        if (nwrite < 0) {
×
513
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
514
            sixel_helper_set_additional_message(
×
515
                "sixel_tty_scroll: f_write() failed.");
516
            goto end;
×
517
        }
518
    }
519

520
    /* emit DECSC sequence */
521
    nwrite = f_write("\0337", 2, priv);
×
522
    if (nwrite < 0) {
×
523
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
524
        sixel_helper_set_additional_message(
×
525
            "sixel_tty_scroll: f_write() failed.");
526
        goto end;
×
527
    }
528
#else  /* mingw */
529
    (void) outfd;
530
    (void) height;
531
    (void) is_animation;
532
    nwrite = f_write("\033[H", 3, priv);
533
    if (nwrite < 0) {
534
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
535
        sixel_helper_set_additional_message(
536
            "sixel_tty_scroll: f_write() failed.");
537
        goto end;
538
    }
539
#endif  /* HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY */
540

541
    status = SIXEL_OK;
542

543
end:
144✔
544
    return status;
144✔
545
}
546

547
/* emacs Local Variables:      */
548
/* emacs mode: c               */
549
/* emacs tab-width: 4          */
550
/* emacs indent-tabs-mode: nil */
551
/* emacs c-basic-offset: 4     */
552
/* emacs End:                  */
553
/* vim: set expandtab ts=4 sts=4 sw=4 : */
554
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc