#ifndef TERM_H #define TERM_H /* * Terminal emulator core. * * A struct term parses the bytes that a program writes and keeps the screen, * the history, and the modes. The application feeds bytes with term_feed() * and reads state with the getters below. A renderer never reads the * terminal directly: it copies the viewport into a struct term_snapshot and * paints from that copy. * * All functions take the terminal as the first argument. Functions that take * a const struct term do not change what it shows. Selection, history, link, * directory, and pointer-shape queries return owned strings; release them * with term_text_free(). * * Threads. Any thread can call any function. Each call that reads or changes * a terminal enters its writer section, so calls on one terminal run one at a * time. A thread that waits for the section sleeps. On POSIX the section is a * mutex with priority inheritance where the platform has it. A snapshot * belongs to one thread at a time. term_free() is the last call on a terminal. */ #include #include #include #include "term_input.h" #include "term_types.h" #ifdef __cplusplus extern "C" { #endif struct term; struct term_snapshot; /* A row holds at most this many cells. */ #define TERM_MAX_COLS 65535 /* The history limit when term_options.scrollback is 0. */ #define TERM_SCROLLBACK_DEFAULT 10000 /* * Colors. A term_color is either a direct color (alpha 0xFF, then RGB) or a * palette slot: an index 0-255, the default foreground, or the default * background. A slot resolves when a snapshot is painted, so a theme change * recolors text that is already on screen. */ typedef uint32_t term_color; #define TERM_COLOR_SLOT 0x01000000u /* The palette index of a slot; see struct term_view.palette. */ #define TERM_COLOR_SLOT_INDEX 0x1FFu #define TERM_COLOR_FG (TERM_COLOR_SLOT | 0x100u) #define TERM_COLOR_BG (TERM_COLOR_SLOT | 0x101u) static inline term_color term_color_index(int n) { return TERM_COLOR_SLOT | ((uint32_t)n & 0xFFu); } static inline term_color term_color_rgb(uint8_t r, uint8_t g, uint8_t b) { return 0xFF000000u | (uint32_t)r << 16 | (uint32_t)g << 8 | b; } static inline bool term_color_is_slot(term_color c) { return (c >> 24) == (TERM_COLOR_SLOT >> 24); } /* Parse an X11 or OSC color specification ("#rgb", "rgb:r/g/b", ...). */ TERM_API bool term_color_parse(const char *spec, uint32_t *argb); /* The colors a terminal starts with. All values are ARGB. An all-zero theme * selects the built-in theme. */ struct term_theme { uint32_t fg, bg, cursor; uint32_t ansi[16]; }; /* Fill theme with the built-in theme. */ TERM_API void term_theme_default(struct term_theme *theme); /* Fill theme with the high-contrast theme. */ TERM_API void term_theme_high_contrast(struct term_theme *theme); /* Sets one palette key: foreground, background, or color0 to color15. * An empty value resets the key to the built-in theme. False, with no * change, for an unknown key or a bad color. */ TERM_API bool term_theme_set_key(struct term_theme *p, const char *key, const char *val); enum term_cursor_style { TERM_CURSOR_BLOCK, TERM_CURSOR_UNDERLINE, TERM_CURSOR_BAR, }; /* Mouse reporting modes and encodings. The values are the DEC mode numbers. */ enum term_mouse { TERM_MOUSE_OFF = 0, TERM_MOUSE_X10 = 9, TERM_MOUSE_NORMAL = 1000, TERM_MOUSE_BUTTON = 1002, TERM_MOUSE_ANY = 1003, }; enum term_mouse_encoding { TERM_MOUSE_ENC_X10 = 0, TERM_MOUSE_ENC_UTF8 = 1005, TERM_MOUSE_ENC_SGR = 1006, TERM_MOUSE_ENC_URXVT = 1015, TERM_MOUSE_ENC_SGR_PIXELS = 1016, }; /* * Callbacks. They run inside term_feed() and term_focus(), on the thread * that calls those functions. A callback must not call back into the * terminal. String arguments are valid only during the call. */ struct term_callbacks { void *ud; /* Bytes the terminal sends back to the program (replies to queries). */ void (*reply)(void *ud, const char *bytes, size_t len); void (*title)(void *ud, const char *title); /* * OSC 52 clipboard callback. * selection names the target clipboard buffer(s), commonly: * "c" - clipboard (system clipboard) * "p" - primary selection (X11 primary) * "q" - secondary selection * "s" - select selection * "0".."7" - cut buffers 0 to 7 * A parameter may contain multiple targets (e.g. "cp"). * data contains decoded bytes of length len. */ void (*clipboard)(void *ud, const char *selection, const char *data, size_t len); void (*notify)(void *ud, const char *title, const char *body); void (*bell)(void *ud); /* Parser trace lines for debugging. NULL turns tracing off. The line has no newline. */ void (*trace)(void *ud, const char *line); }; /* Options for term_new(). An all-zero value is valid. */ struct term_options { struct term_vector2i size; /* columns and rows; each is at least 1 */ int scrollback; /* history lines; 0 selects TERM_SCROLLBACK_DEFAULT */ struct term_theme theme; enum term_cursor_style cursor_style; bool cursor_steady; /* false: the cursor blinks */ struct term_callbacks callbacks; }; /* NULL when memory runs out. */ /* The Unicode version of the width tables, such as "18.0.0". A host can * pass it to programs as UNICODE_VERSION. */ TERM_API const char *term_unicode_version(void); /* The length of the prefix of buf made of bytes in [0x20, hi). With hi * 0x7f, it finds a run of printable ASCII in UTF-8 text, so a host can * skip shaping for it. Any thread can call it. */ TERM_API size_t term_scan_printable_span(const char *buf, size_t len, unsigned char hi); /* The instruction set level of the running code: "base", "avx2", "avx512", * or "neon". On x86-64, the first term_new() picks the highest level the CPU * supports. Set TERM_HEADLESS_ISA to a lower level to force it, for tests * and comparisons. */ TERM_API const char *term_isa(void); TERM_API struct term *term_new(const struct term_options *opt); TERM_API void term_free(struct term *t); /* Hold the writer section across several calls, so that no other thread * changes the terminal between them. The section is a mutex, and a waiting * thread sleeps. Keep it short. The thread that holds it can call every * function, and it can enter the section again. Each term_lock() needs one * term_unlock(). */ TERM_API void term_lock(struct term *t); TERM_API void term_unlock(struct term *t); /* Change the size in cells. Returns false when memory runs out; the terminal * is then unchanged. The screen reflows to the new width. */ TERM_API bool term_resize(struct term *t, struct term_vector2i size); /* Replace the theme. A different theme also drops the colors that the * program set with OSC; the same theme changes nothing. */ TERM_API void term_set_theme(struct term *t, const struct term_theme *theme); /* The cursor style that DECSCUSR 0 and a reset restore. */ TERM_API void term_set_cursor_default(struct term *t, enum term_cursor_style style, bool steady); /* * Bytes from the program. * * TERM_FEED_ONLCR: a line feed also returns the cursor to column 0, as a tty * with OPOST and ONLCR does. Set it when the bytes did not pass through the * tty output processing (for example the fast pipe). */ enum term_feed_flags { TERM_FEED_ONLCR = 1u << 0, }; TERM_API void term_feed(struct term *t, const char *bytes, size_t len, unsigned flags); /* Report a focus change. The terminal replies only in focus-event mode. */ TERM_API void term_focus(struct term *t, bool focused); /* Modes that affect how the application encodes input. */ struct term_modes { unsigned keyboard; /* kitty keyboard protocol flags */ enum term_mouse mouse; enum term_mouse_encoding mouse_encoding; struct term_vector2i grid; /* columns and rows */ bool app_cursor; /* DECCKM */ bool app_keypad; /* DECKPAM */ bool backarrow_bs; /* DECBKM */ bool bracketed_paste; bool focus_events; bool alternate; /* the alternate screen is active */ bool reverse_video; /* DECSCNM */ bool synchronized; /* mode 2026 */ }; struct term_cursor { struct term_vector2i pos; enum term_cursor_style style; bool visible; bool blink; }; TERM_API struct term_vector2i term_size(const struct term *t); TERM_API struct term_modes term_modes(const struct term *t); /* * Encode an input event as the bytes the program expects. Get modes from * term_modes(). Record ev in keyboard with term_keyboard_update() first. Send * the bytes to the program yourself. */ TERM_API struct term_input_result term_input_encode_key( const struct term_keyboard_state *keyboard, const struct term_key_event *ev, const struct term_modes *modes, char *buf, size_t capacity ); TERM_API struct term_input_result term_input_encode_mouse( const struct term_mouse_event *ev, const struct term_modes *modes, char *buf, size_t capacity ); /* Bytes term_input_encode_paste() adds to the text at most. */ #define TERM_PASTE_EXTRA 12 /* * Encode pasted text. Drop control characters and bidi controls, so the * program sees only what the user saw. Send a line break as CR. Add the * bracketed-paste markers when the program asked for them. A buffer of * len + TERM_PASTE_EXTRA bytes is always large enough. Text that is empty * after the filter gives TERM_INPUT_CONSUMED. */ TERM_API struct term_input_result term_input_encode_paste( const struct term_modes *modes, const char *text, size_t len, char *buf, size_t capacity ); /* The cursor in screen coordinates (not the viewport). */ TERM_API struct term_cursor term_cursor(const struct term *t); /* Owned, NUL-terminated text from OSC 7 and OSC 22. NULL when unavailable * or allocation fails. The strings stay valid after the terminal changes * or is freed. Release them with term_text_free(). */ TERM_API char *term_cwd(const struct term *t); TERM_API char *term_pointer_shape(const struct term *t); /* Release any owned text returned by the library. NULL is safe. */ TERM_API void term_text_free(char *text); /* * Viewport. The viewport shows as many rows as the screen. Offset 0 shows * the live screen; a larger offset shows that many rows of history above it. * The alternate screen has no history, so its offset is always 0. */ struct term_scrollback { int offset; /* rows between the viewport and the live screen */ int total; /* history rows at the current width */ }; TERM_API struct term_scrollback term_scrollback(const struct term *t); /* Scroll the viewport. A positive count moves back into history. */ TERM_API void term_scroll(struct term *t, int lines); TERM_API void term_scroll_to(struct term *t, int offset); /* * Selection, in viewport coordinates. A selection starts at one cell and * extends to another; the unit widens both ends to words or whole lines. * The terminal clears the selection when the text under it changes. */ enum term_select_unit { TERM_SELECT_CELL, TERM_SELECT_WORD, TERM_SELECT_LINE, }; /* Start a selection at cell. For TERM_SELECT_CELL, right_half means the * pointer is on the right half of the cell, so the selection starts after it. */ TERM_API void term_select_start(struct term *t, struct term_vector2i cell, bool right_half, enum term_select_unit unit); /* Extend the selection from its start to cell. */ TERM_API void term_select_extend(struct term *t, struct term_vector2i cell, bool right_half); TERM_API void term_select_clear(struct term *t); TERM_API bool term_has_selection(const struct term *t); /* Owned, NUL-terminated UTF-8 of the selection. NULL when nothing is * selected or allocation fails; a selection of blanks may produce an empty * string. Capture is atomic with respect to other calls on t. Release the * result with term_text_free(). */ TERM_API char *term_selection_text(const struct term *t); /* Owned OSC 8 link or detected URL at a viewport cell. NULL when there is * no link or allocation fails. Release the result with term_text_free(). */ TERM_API char *term_link_at(const struct term *t, struct term_vector2i cell); /* Cell attributes share the write core's bit layout. Underline styles are * mutually exclusive flags, not a shifted enum; use term_attr_underline(). */ enum term_attr { TERM_ATTR_BOLD = 1u << 0, TERM_ATTR_ITALIC = 1u << 1, TERM_ATTR_UNDERLINE = 1u << 2, TERM_ATTR_STRIKE = 1u << 3, TERM_ATTR_OVERLINE = 1u << 4, TERM_ATTR_EMOJI = 1u << 5, /* the text requested emoji presentation */ TERM_ATTR_TEXT = 1u << 6, /* the text requested text presentation */ TERM_ATTR_FAINT = 1u << 10, TERM_ATTR_DOUBLE_UNDERLINE = 1u << 11, TERM_ATTR_CURLY_UNDERLINE = 1u << 12, TERM_ATTR_DOTTED_UNDERLINE = 1u << 13, TERM_ATTR_DASHED_UNDERLINE = 1u << 14, TERM_ATTR_LINK = 1u << 15, }; #define TERM_ATTR_UNDERLINE_MASK \ (TERM_ATTR_UNDERLINE | TERM_ATTR_DOUBLE_UNDERLINE | TERM_ATTR_CURLY_UNDERLINE | \ TERM_ATTR_DOTTED_UNDERLINE | TERM_ATTR_DASHED_UNDERLINE) enum term_underline { TERM_UNDERLINE_NONE, TERM_UNDERLINE_SINGLE, TERM_UNDERLINE_DOUBLE, TERM_UNDERLINE_CURLY, TERM_UNDERLINE_DOTTED, TERM_UNDERLINE_DASHED, }; static inline enum term_underline term_attr_underline(uint16_t attrs) { if (attrs & TERM_ATTR_DOUBLE_UNDERLINE) return TERM_UNDERLINE_DOUBLE; if (attrs & TERM_ATTR_CURLY_UNDERLINE) return TERM_UNDERLINE_CURLY; if (attrs & TERM_ATTR_DOTTED_UNDERLINE) return TERM_UNDERLINE_DOTTED; if (attrs & TERM_ATTR_DASHED_UNDERLINE) return TERM_UNDERLINE_DASHED; return attrs & TERM_ATTR_UNDERLINE ? TERM_UNDERLINE_SINGLE : TERM_UNDERLINE_NONE; } /* text refers to a grapheme cluster in the snapshot; see term_snapshot_cluster(). */ #define TERM_TEXT_CLUSTER 0x80000000u /* One cell as a snapshot shows it. */ struct term_cell { uint32_t text; /* a code point, TERM_TEXT_CLUSTER | n, or 0 for an empty cell */ term_color fg, bg; uint16_t attrs; /* enum term_attr flags; at most one underline flag */ uint8_t width; /* 1; 2 for a wide glyph; 0 for the right half of a wide glyph */ uint8_t reserved; }; #ifdef __cplusplus static_assert(sizeof(struct term_cell) == 16, "four cells fill one 64-byte line"); #else _Static_assert(sizeof(struct term_cell) == 16, "four cells fill one 64-byte line"); #endif /* * Snapshot: a copy of the viewport for a renderer. It holds no pointer into * the terminal, so it stays valid while the terminal changes. One terminal * may have several snapshots, for example a window and a tab preview. */ TERM_API struct term_snapshot *term_snapshot_new(void); TERM_API void term_snapshot_free(struct term_snapshot *s); /* Copy the viewport of t into s. Keep rows with unchanged content keys. * Update the history view index and refine counts for visible wide lines. * Return false when memory runs out. The snapshot is then empty. */ TERM_API bool term_snapshot_update(struct term_snapshot *s, struct term *t); /* Palette entries of struct term_view.palette after the 256 indexed colors. */ enum { TERM_PALETTE_FG = 256, TERM_PALETTE_BG = 257, TERM_PALETTE_CURSOR = 258, TERM_PALETTE_SIZE = 259, }; struct term_view { struct term_vector2i size; struct term_cursor cursor; /* viewport coordinates; not visible when scrolled away */ int scroll_offset; /* Rows the content moved up since the previous update, when that movement * explains the change; otherwise 0. */ int scrolled; bool reverse_video; /* Changes when any part of the view changes. */ uint64_t version; /* Changes when a palette entry changes. */ uint64_t palette_version; const uint32_t *palette; /* TERM_PALETTE_SIZE ARGB entries */ }; TERM_API const struct term_view *term_snapshot_view(const struct term_snapshot *s); /* The cells of viewport row y: view->size.x entries. */ TERM_API const struct term_cell *term_snapshot_row(const struct term_snapshot *s, int y); /* An identity for the content of row y. Two rows with the same key hold the * same cells, also when the content moved to another row. */ TERM_API uint64_t term_snapshot_row_key(const struct term_snapshot *s, int y); /* Number of rows in history above the screen. 0 if alternate screen or no history. */ TERM_API int term_history_rows(const struct term *t); /* Owned, NUL-terminated UTF-8 of up to max_rows history rows before the * current viewport. Join soft-wrapped rows and end closed lines with a * newline. Does not change the selection. No history or max_rows <= 0 gives * an empty string; NULL means allocation failed or t is NULL. Release the * result with term_text_free(). */ TERM_API char *term_history_text(struct term *t, int max_rows); TERM_API struct term_span term_snapshot_row_selection(const struct term_snapshot *s, int y); /* The code points of a cluster cell. Returns the number of code points and * writes at most cap of them. */ TERM_API int term_snapshot_cluster(const struct term_snapshot *s, uint32_t text, uint32_t *cps, int cap); /* The ARGB value of a color in a view. */ static inline uint32_t term_view_color(const struct term_view *v, term_color c) { if (!term_color_is_slot(c)) return c; return v->palette[c & TERM_COLOR_SLOT_INDEX]; } #ifdef __cplusplus } #endif #endif