Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : #ifndef PLATFORM_TERMINAL_H
5 : #define PLATFORM_TERMINAL_H
6 :
7 : #include <stddef.h>
8 : #include <stdint.h>
9 :
10 : /** Logical key codes returned by terminal_read_key(). */
11 : typedef enum {
12 : TERM_KEY_QUIT = 0, /* Ctrl-C */
13 : TERM_KEY_NEXT_PAGE = 1, /* PgDn */
14 : TERM_KEY_PREV_PAGE = 2, /* PgUp */
15 : TERM_KEY_NEXT_LINE = 3, /* Down-arrow */
16 : TERM_KEY_PREV_LINE = 4, /* Up-arrow */
17 : TERM_KEY_IGNORE = 5, /* unknown / Space */
18 : TERM_KEY_ENTER = 6, /* Enter (\n or \r) */
19 : TERM_KEY_ESC = 7, /* bare ESC */
20 : TERM_KEY_BACK = 8, /* Backspace / DEL */
21 : TERM_KEY_LEFT = 9, /* Left arrow */
22 : TERM_KEY_RIGHT = 10, /* Right arrow */
23 : TERM_KEY_HOME = 11, /* Home key */
24 : TERM_KEY_END = 12, /* End key */
25 : TERM_KEY_DELETE = 13, /* Delete (forward delete) */
26 : TERM_KEY_CTRL_A = 14, /* Ctrl-A (beginning of line) */
27 : TERM_KEY_CTRL_E = 15, /* Ctrl-E (end of line) */
28 : TERM_KEY_CTRL_K = 16, /* Ctrl-K (kill to end of line) */
29 : TERM_KEY_CTRL_W = 17, /* Ctrl-W (delete previous word) */
30 : TERM_KEY_CTRL_D = 18, /* Ctrl-D (EOF / delete forward) */
31 : } TermKey;
32 :
33 : /** Opaque saved terminal state (used for raw-mode enter/exit). */
34 : typedef struct TermRawState TermRawState;
35 :
36 : /** Returns the terminal width in columns, or 80 if unknown. */
37 : int terminal_cols(void);
38 :
39 : /** Returns the terminal height in rows, or 0 if unknown. */
40 : int terminal_rows(void);
41 :
42 : /** Returns 1 if fd is connected to a terminal, 0 otherwise. */
43 : int terminal_is_tty(int fd);
44 :
45 : /**
46 : * Save current terminal mode and enter raw mode
47 : * (no echo, no canonical, no signal generation).
48 : * Returns an allocated TermRawState on success, NULL on failure.
49 : * Caller must call terminal_raw_exit() to restore and free.
50 : */
51 : TermRawState *terminal_raw_enter(void);
52 :
53 : /**
54 : * Restore the terminal to the state saved in *state and free it.
55 : * Sets *state = NULL. Safe to call with NULL or *state == NULL.
56 : */
57 : void terminal_raw_exit(TermRawState **state);
58 :
59 : /** RAII cleanup wrapper for terminal_raw_exit. */
60 25 : static inline void terminal_raw_exit_ptr(TermRawState **p) {
61 25 : terminal_raw_exit(p);
62 25 : }
63 : #define RAII_TERM_RAW __attribute__((cleanup(terminal_raw_exit_ptr)))
64 :
65 : /**
66 : * Read one keypress and return a TermKey code.
67 : * The terminal must already be in raw mode.
68 : * Fully consumes multi-byte escape sequences.
69 : */
70 : TermKey terminal_read_key(void);
71 :
72 : /**
73 : * Wait up to @p timeout_ms for a keystroke to be ready on stdin.
74 : * Returns 1 when at least one byte is pending (read_key will not
75 : * block for that first byte), 0 on timeout, -1 on interrupt or
76 : * error. The terminal need not be in raw mode. Pass a negative
77 : * timeout to block indefinitely.
78 : */
79 : int terminal_wait_key(int timeout_ms);
80 :
81 : /**
82 : * Returns the last printable ASCII character (32–126) that caused
83 : * terminal_read_key() to return TERM_KEY_IGNORE.
84 : * Returns 0 if the last ignored keystroke was not a printable character.
85 : */
86 : int terminal_last_printable(void);
87 :
88 : /**
89 : * Display column width of Unicode codepoint cp.
90 : * Returns 0 for non-printable/control characters,
91 : * 1 for normal characters, 2 for wide (CJK/emoji) characters.
92 : */
93 : int terminal_wcwidth(uint32_t cp);
94 :
95 : /**
96 : * Prompt for a password with echo suppressed.
97 : * Writes at most size-1 bytes to buf (NUL-terminated).
98 : * Returns the number of characters read, or -1 on error.
99 : */
100 : int terminal_read_password(const char *prompt, char *buf, size_t size);
101 :
102 : /**
103 : * Enable terminal-resize notifications. On POSIX this installs a
104 : * SIGWINCH handler that flips an internal flag; on Windows this is a
105 : * no-op (resize is picked up on the next read via a different path).
106 : * Safe to call multiple times.
107 : */
108 : void terminal_enable_resize_notifications(void);
109 :
110 : /**
111 : * If a terminal-resize event has occurred since the last call, clear
112 : * the pending flag and return 1; otherwise return 0. Used by TUI
113 : * loops to detect SIGWINCH between reads.
114 : */
115 : int terminal_consume_resize(void);
116 :
117 : /**
118 : * Install signal handlers for SIGTERM, SIGHUP, and SIGINT that restore
119 : * the terminal from raw mode before allowing the default handler to run.
120 : *
121 : * The handlers:
122 : * 1. Call tcsetattr(STDIN_FILENO, TCSANOW, &saved) using the termios
123 : * state captured by the most recent terminal_raw_enter() call.
124 : * 2. Write "\033[?25h" (show cursor) to STDOUT_FILENO via write(2).
125 : * 3. Reset the signal to SIG_DFL and re-raise it so the shell sees
126 : * the correct exit status (e.g. 128+SIGTERM).
127 : *
128 : * Must be called after terminal_raw_enter() succeeds. All three
129 : * operations used (tcsetattr, write, signal, raise) are
130 : * async-signal-safe per POSIX.
131 : *
132 : * Safe to call multiple times; subsequent calls update the saved state
133 : * pointer to the most recent TermRawState.
134 : */
135 : void terminal_install_cleanup_handlers(TermRawState *state);
136 :
137 : #endif /* PLATFORM_TERMINAL_H */
|