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

saitoha / libsixel / 20609368106

31 Dec 2025 12:57AM UTC coverage: 52.011% (-6.3%) from 58.281%
20609368106

push

github

saitoha
tests: split converter option tap suites

14741 of 45141 branches covered (32.66%)

21394 of 41134 relevant lines covered (52.01%)

3932390.77 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
#if defined(HAVE_CONFIG_H)
26
#include "config.h"
27
#endif
28

29
/* STDC_HEADERS */
30
#include <stdio.h>
31
#include <stdlib.h>
32
#include <string.h>
33

34
#include <stdint.h>
35

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

62
#include <sixel.h>
63
#include "tty.h"
64
#include "compat_stub.h"
65

66
#if defined(_WIN32)
67
# if !defined(UNICODE)
68
#  define UNICODE
69
# endif
70
# if !defined(_UNICODE)
71
#  define _UNICODE
72
# endif
73
# if !defined(WIN32_LEAN_AND_MEAN)
74
#  define WIN32_LEAN_AND_MEAN
75
# endif
76
# include <windows.h>
77
# include <io.h>
78
# if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
79
#  define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
80
# endif
81
#endif
82

83
/*
84
 * Cache describing the capabilities of the active output device.
85
 * The struct lives in static storage because the helper routines are
86
 * frequently used from the CLI tools without an explicit lifecycle.
87
 */
88
static struct sixel_tty_output_state tty_output_state = {0, 0, 0, 0};
89

90
static int
91
sixel_tty_term_supports_ansi(const char *term);
92

93
static int
94
sixel_tty_term_supports_color(const char *term, const char *colorterm);
95

96
static int
97
sixel_tty_term_supports_ansi(const char *term)
×
98
{
99
    size_t i;
×
100
    size_t count;
×
101
    const char *const *entry;
×
102
    static const char *const denylist[] = {
×
103
        "dumb",
104
        "emacs",
105
        "unknown",
106
        "cons25",
107
        "vt100-nam"
108
    };
109
    static const char *const allowlist[] = {
×
110
        "ansi",
111
        "color",
112
        "xterm",
113
        "rxvt",
114
        "tmux",
115
        "screen",
116
        "linux",
117
        "foot",
118
        "wezterm",
119
        "alacritty",
120
        "konsole",
121
        "kitty",
122
        "gnome",
123
        "eterm",
124
        "cygwin",
125
        "putty",
126
        "vt100",
127
        "vt102",
128
        "vt220",
129
        "st-",
130
        "st"
131
    };
132

133
    if (term == NULL) {
×
134
        return 0;
135
    }
136

137
    count = sizeof(denylist) / sizeof(denylist[0]);
138
    for (i = 0; i < count; ++i) {
×
139
        entry = &denylist[i];
×
140
        if (strcmp(term, *entry) == 0) {
×
141
            return 0;
142
        }
143
    }
144

145
    count = sizeof(allowlist) / sizeof(allowlist[0]);
146
    for (i = 0; i < count; ++i) {
×
147
        entry = &allowlist[i];
×
148
        if (strstr(term, *entry) != NULL) {
×
149
            return 1;
150
        }
151
    }
152

153
    return 0;
154
}
155

156
static int
157
sixel_tty_term_supports_color(const char *term, const char *colorterm)
×
158
{
159
    size_t i;
×
160
    size_t count;
×
161
    const char *const *entry;
×
162
    static const char *const allowlist[] = {
×
163
        "256color",
164
        "color",
165
        "xterm",
166
        "rxvt",
167
        "tmux",
168
        "screen",
169
        "linux",
170
        "foot",
171
        "wezterm",
172
        "alacritty",
173
        "konsole",
174
        "kitty",
175
        "gnome",
176
        "eterm",
177
        "cygwin",
178
        "putty",
179
        "vt220",
180
        "vt340",
181
        "ansi"
182
    };
183

184
    if (colorterm != NULL && colorterm[0] != '\0') {
×
185
        return 1;
186
    }
187

188
    if (term == NULL) {
×
189
        return 0;
190
    }
191

192
    if (strstr(term, "mono") != NULL || strstr(term, "bw") != NULL) {
×
193
        return 0;
194
    }
195

196
    count = sizeof(allowlist) / sizeof(allowlist[0]);
197
    for (i = 0; i < count; ++i) {
×
198
        entry = &allowlist[i];
×
199
        if (strstr(term, *entry) != NULL) {
×
200
            return 1;
201
        }
202
    }
203

204
    return 0;
205
}
206

207
SIXELAPI void
208
sixel_tty_init_output_device(int fd)
868✔
209
{
210
    int istty;
868✔
211
    const char *term;
868✔
212
    const char *colorterm;
868✔
213
    struct sixel_tty_output_state *state;
868✔
214
#if defined(_WIN32)
215
    intptr_t handle_value;
216
    HANDLE handle;
217
    DWORD mode;
218
    DWORD desired;
219
#endif
220

221
    state = &tty_output_state;
868✔
222
    state->is_tty = 0;
868✔
223
    state->use_ansi_sequences = 0;
868✔
224
    state->supports_bold = 0;
868✔
225
    state->supports_color = 0;
868✔
226
    istty = 0;
868✔
227

228
#if HAVE_ISATTY
229
    if (sixel_compat_isatty(fd)) {
868!
230
        istty = 1;
×
231
    }
232
#endif
233

234
    if (istty == 0) {
×
235
        return;
236
    }
237

238
    state->is_tty = 1;
×
239

240
#if defined(_WIN32)
241
    handle_value = _get_osfhandle(fd);
242
    if (handle_value != (intptr_t)-1) {
243
        handle = (HANDLE)handle_value;
244
        if (GetConsoleMode(handle, &mode) != 0) {
245
            desired = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
246
            if (SetConsoleMode(handle, desired) != 0) {
247
                mode = desired;
248
            }
249
            if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) {
250
                state->use_ansi_sequences = 1;
251
                state->supports_bold = 1;
252
                state->supports_color = 1;
253
                return;
254
            }
255
        }
256
    }
257
#endif
258

259
    term = sixel_compat_getenv("TERM");
×
260
    colorterm = sixel_compat_getenv("COLORTERM");
×
261

262
    if (term == NULL || term[0] == '\0') {
×
263
        return;
264
    }
265

266
    if (sixel_tty_term_supports_ansi(term) != 0) {
×
267
        state->use_ansi_sequences = 1;
×
268
        state->supports_bold = 1;
×
269
    }
270

271
    if (state->use_ansi_sequences == 0 &&
×
272
            colorterm != NULL && colorterm[0] != '\0') {
×
273
        state->use_ansi_sequences = 1;
×
274
        state->supports_bold = 1;
×
275
    }
276

277
    if (state->use_ansi_sequences != 0) {
×
278
        if (sixel_tty_term_supports_color(term, colorterm) != 0) {
×
279
            state->supports_color = 1;
×
280
        }
281
    }
282

283
}
1!
284

285
SIXELAPI struct sixel_tty_output_state const *
286
sixel_tty_get_output_state(void)
308✔
287
{
288
    return &tty_output_state;
308✔
289
}
290

291
#if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY
292
SIXELSTATUS
293
sixel_tty_cbreak(struct termios *old_termios, struct termios *new_termios)
×
294
{
295
    SIXELSTATUS status = SIXEL_FALSE;
×
296
    int ret;
×
297

298
    /* set the terminal to cbreak mode */
299
    ret = tcgetattr(STDIN_FILENO, old_termios);
×
300
    if (ret != 0) {
×
301
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
302
        sixel_helper_set_additional_message(
×
303
            "sixel_tty_cbreak: tcgetattr() failed.");
304
        goto end;
×
305
    }
306

307
    (void) memcpy(new_termios, old_termios, sizeof(*old_termios));
×
308
    new_termios->c_lflag &= (tcflag_t)~(ECHO | ICANON);
×
309
    new_termios->c_cc[VMIN] = 1;
×
310
    new_termios->c_cc[VTIME] = 0;
×
311

312
    ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, new_termios);
×
313
    if (ret != 0) {
×
314
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
315
        sixel_helper_set_additional_message(
×
316
            "sixel_tty_cbreak: tcsetattr() failed.");
317
        goto end;
×
318
    }
319

320
    status = SIXEL_OK;
321

322
end:
×
323
    return status;
×
324
}
325
#endif  /* HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY */
326

327

328
#if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY
329
SIXELSTATUS
330
sixel_tty_restore(struct termios *old_termios)
×
331
{
332
    SIXELSTATUS status = SIXEL_FALSE;
×
333
    int ret;
×
334

335
    ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, old_termios);
×
336
    if (ret != 0) {
×
337
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
338
        sixel_helper_set_additional_message(
×
339
            "sixel_tty_restore: tcsetattr() failed.");
340
        goto end;
×
341
    }
342

343
    status = SIXEL_OK;
344

345
end:
×
346
    return status;
×
347
}
348
#endif  /* HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY */
349

350

351
SIXELSTATUS
352
sixel_tty_wait_stdin(int usec)
×
353
{
354
#if HAVE_SYS_SELECT_H && !defined(__EMSCRIPTEN__)
355
    fd_set rfds;
×
356
    struct timeval tv;
×
357
    int ret = 0;
×
358
#endif  /* HAVE_SYS_SELECT_H */
359
    SIXELSTATUS status = SIXEL_FALSE;
×
360

361
#if HAVE_SYS_SELECT_H && !defined(__EMSCRIPTEN__)
362
    tv.tv_sec = usec / 1000000;
×
363
    tv.tv_usec = usec % 1000000;
×
364
    FD_ZERO(&rfds);
×
365
    FD_SET(STDIN_FILENO, &rfds);
×
366
    ret = select(STDIN_FILENO + 1, &rfds, NULL, NULL, &tv);
×
367
    if (ret < 0) {
×
368
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
369
        sixel_helper_set_additional_message(
×
370
            "sixel_tty_wait_stdin: select() failed.");
371
        goto end;
×
372
    }
373

374
    /* success */
375
    status = SIXEL_OK;
376
#else
377
    (void) usec;
378
    goto end;
379
#endif  /* HAVE_SYS_SELECT_H */
380

381
end:
×
382
    return status;
×
383
}
384

385

386
SIXELSTATUS
387
sixel_tty_scroll(
144✔
388
    sixel_write_function f_write,
389
    void *priv,
390
    int outfd,
391
    int height,
392
    int is_animation)
393
{
394
    SIXELSTATUS status = SIXEL_FALSE;
144✔
395
    int nwrite;
144✔
396
#if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY && !defined(__EMSCRIPTEN__)
397
    struct winsize size = {0, 0, 0, 0};
144✔
398
    struct termios old_termios;
144✔
399
    struct termios new_termios;
144✔
400
    int row = 0;
144✔
401
    int col = 0;
144✔
402
    int cellheight;
144✔
403
    int scroll;
144✔
404
    char buffer[256];
144✔
405
    int result;
144✔
406

407
    /* confirm I/O file descriptors are tty devices */
408
    if (!sixel_compat_isatty(STDIN_FILENO)
144!
409
        || !sixel_compat_isatty(outfd)) {
×
410
        /* set cursor position to top-left */
411
        nwrite = f_write("\033[H", 3, priv);
144✔
412
        if (nwrite < 0) {
144!
413
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
414
            sixel_helper_set_additional_message(
×
415
                "sixel_tty_scroll: f_write() failed.");
416
            goto end;
×
417
        }
418
        status = SIXEL_OK;
144✔
419
        goto end;
144✔
420
    }
421

422
    /* request terminal size to tty device with TIOCGWINSZ ioctl */
423
    result = ioctl(outfd, TIOCGWINSZ, &size);
×
424
    if (result != 0) {
×
425
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
426
        sixel_helper_set_additional_message("ioctl() failed.");
×
427
        goto end;
×
428
    }
429

430
    /* if we can not retrieve terminal pixel size over TIOCGWINSZ ioctl,
431
       return immediatly */
432
    if (size.ws_ypixel <= 0) {
×
433
        nwrite = f_write("\033[H", 3, priv);
×
434
        if (nwrite < 0) {
×
435
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
436
            sixel_helper_set_additional_message(
×
437
                "sixel_tty_scroll: f_write() failed.");
438
            goto end;
×
439
        }
440
        status = SIXEL_OK;
×
441
        goto end;
×
442
    }
443

444
    /* if input source is animation and frame No. is more than 1,
445
       output DECSC sequence */
446
    if (is_animation) {
×
447
        nwrite = f_write("\0338", 2, priv);
×
448
        if (nwrite < 0) {
×
449
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
450
            sixel_helper_set_additional_message(
×
451
                "sixel_tty_scroll: f_write() failed.");
452
            goto end;
×
453
        }
454
        status = SIXEL_OK;
×
455
        goto end;
×
456
    }
457

458
    /* set the terminal to cbreak mode */
459
    status = sixel_tty_cbreak(&old_termios, &new_termios);
×
460
    if (SIXEL_FAILED(status)) {
×
461
        goto end;
×
462
    }
463

464
    /* request cursor position report */
465
    nwrite = f_write("\033[6n", 4, priv);
×
466
    if (nwrite < 0) {
×
467
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
468
        sixel_helper_set_additional_message(
×
469
            "sixel_tty_scroll: f_write() failed.");
470
        goto end;
×
471
    }
472

473
    /* wait cursor position report */
474
    if (SIXEL_FAILED(sixel_tty_wait_stdin(1000 * 1000))) { /* wait up to 1 sec */
×
475
        /* If we can't get any response from the terminal,
476
         * move cursor to (1, 1). */
477
        nwrite = f_write("\033[H", 3, priv);
×
478
        if (nwrite < 0) {
×
479
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
480
            sixel_helper_set_additional_message(
×
481
                "sixel_tty_scroll: f_write() failed.");
482
            goto end;
×
483
        }
484
        status = SIXEL_OK;
×
485
        goto end;
×
486
    }
487

488
    /* scan cursor position report */
489
    if (scanf("\033[%d;%dR", &row, &col) != 2) {
×
490
        nwrite = f_write("\033[H", 3, priv);
×
491
        if (nwrite < 0) {
×
492
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
493
            sixel_helper_set_additional_message(
×
494
                "sixel_tty_scroll: f_write() failed.");
495
            goto end;
×
496
        }
497
        status = SIXEL_OK;
×
498
        goto end;
×
499
    }
500

501
    /* restore the terminal mode */
502
    status = sixel_tty_restore(&old_termios);
×
503
    if (SIXEL_FAILED(status)) {
×
504
        goto end;
×
505
    }
506

507
    /* calculate scrolling amount in pixels */
508
    cellheight = height * size.ws_row / size.ws_ypixel + 1;
×
509
    scroll = cellheight + row - size.ws_row + 1;
×
510
    if (scroll > 0) {
×
511
        nwrite = sixel_compat_snprintf(
×
512
            buffer,
513
            sizeof(buffer),
514
            "\033[%dS\033[%dA",
515
            scroll,
516
            scroll);
517
        if (nwrite < 0) {
×
518
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
519
            sixel_helper_set_additional_message(
×
520
                "sixel_tty_scroll: command format failed.");
521
        }
522
        nwrite = f_write(buffer, (int)strlen(buffer), priv);
×
523
        if (nwrite < 0) {
×
524
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
525
            sixel_helper_set_additional_message(
×
526
                "sixel_tty_scroll: f_write() failed.");
527
            goto end;
×
528
        }
529
    }
530

531
    /* emit DECSC sequence */
532
    nwrite = f_write("\0337", 2, 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
#else  /* mingw */
540
    (void) outfd;
541
    (void) height;
542
    (void) is_animation;
543
    nwrite = f_write("\033[H", 3, priv);
544
    if (nwrite < 0) {
545
        status = (SIXEL_LIBC_ERROR | (errno & 0xff));
546
        sixel_helper_set_additional_message(
547
            "sixel_tty_scroll: f_write() failed.");
548
        goto end;
549
    }
550
#endif  /* HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY && !defined(__EMSCRIPTEN__) */
551

552
    status = SIXEL_OK;
553

554
end:
144✔
555
    return status;
144✔
556
}
557

558
/* emacs Local Variables:      */
559
/* emacs mode: c               */
560
/* emacs tab-width: 4          */
561
/* emacs indent-tabs-mode: nil */
562
/* emacs c-basic-offset: 4     */
563
/* emacs End:                  */
564
/* vim: set expandtab ts=4 sts=4 sw=4 : */
565
/* 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