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

saitoha / libsixel / 19918707358

04 Dec 2025 05:12AM UTC coverage: 38.402% (-4.0%) from 42.395%
19918707358

push

github

saitoha
tests: fix meson msys dll lookup

9738 of 38220 branches covered (25.48%)

12841 of 33438 relevant lines covered (38.4%)

782420.02 hits per line

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

17.74
/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)
600✔
198
{
199
    int istty;
600✔
200
    const char *term;
600✔
201
    const char *colorterm;
600✔
202
    struct sixel_tty_output_state *state;
600✔
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;
600✔
211
    state->is_tty = 0;
600✔
212
    state->use_ansi_sequences = 0;
600✔
213
    state->supports_bold = 0;
600✔
214
    state->supports_color = 0;
600✔
215
    istty = 0;
600✔
216

217
#if HAVE_ISATTY
218
    if (isatty(fd)) {
600!
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 = getenv("TERM");
×
249
    colorterm = 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)
222✔
276
{
277
    return &tty_output_state;
222✔
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(
108✔
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;
108✔
384
    int nwrite;
108✔
385
#if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H && HAVE_ISATTY
386
    struct winsize size = {0, 0, 0, 0};
108✔
387
    struct termios old_termios;
108✔
388
    struct termios new_termios;
108✔
389
    int row = 0;
108✔
390
    int col = 0;
108✔
391
    int cellheight;
108✔
392
    int scroll;
108✔
393
    char buffer[256];
108✔
394
    int result;
108✔
395

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

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

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

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

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

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

461
    /* wait cursor position report */
462
    if (SIXEL_FAILED(sixel_tty_wait_stdin(1000 * 1000))) { /* wait up to 1 sec */
×
463
        /* If we can't get any response from the terminal,
464
         * move cursor to (1, 1). */
465
        nwrite = f_write("\033[H", 3, 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
        status = SIXEL_OK;
×
473
        goto end;
×
474
    }
475

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

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

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

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

540
    status = SIXEL_OK;
541

542
end:
108✔
543
    return status;
108✔
544
}
545

546
/* emacs Local Variables:      */
547
/* emacs mode: c               */
548
/* emacs tab-width: 4          */
549
/* emacs indent-tabs-mode: nil */
550
/* emacs c-basic-offset: 4     */
551
/* emacs End:                  */
552
/* vim: set expandtab ts=4 sts=4 sw=4 : */
553
/* 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