Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ cmake_install.cmake
Makefile
compile_commands.json
external/
/.codex
/.omx
/package-lock.json
/docs
/.claude
.codex/
.omx/
package-lock.json
docs/
.claude/
CLAUDE.md
AGENTS.md
Testing/
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ may contain breaking API changes; patch versions never do).

## [Unreleased]

### Added
- **`LT_HEX(rgb)`** — pack a `0xRRGGBB` hex color into an `lt_attr`, complementing
`LT_RGB(r,g,b)`. High bits (attribute flags + the `LT_HI_BLACK` sentinel) are
masked, so it is a pure color packer; `LT_HEX(0xRRGGBB) == LT_RGB(0xRR,0xGG,0xBB)`.

## [0.1.1] - 2026-06-13

Tooling + compatibility release. The core library API and ABI are unchanged
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ On **kitty-capable terminals** (negotiated by default), POSIX now delivers `LT_M

### Colors (`TB_DEFAULT/BLACK/RED/…` → `LT_DEFAULT/BLACK/RED/…`)

Declared: `LT_DEFAULT`, `LT_BLACK`, `LT_RED`, `LT_GREEN`, `LT_YELLOW`, `LT_BLUE`, `LT_MAGENTA`, `LT_CYAN`, `LT_WHITE`, plus `LT_RGB(r,g,b)` (24-bit pack) and the `LT_HI_BLACK` sentinel. Emitted on **both platforms** via mode-aware SGR in the shared `lt__emit_sgr` (`src/shared/sgr.c`) — named (NORMAL), 8-bit palette index (256/216/grayscale), and 24-bit RGB (TRUECOLOR), with `LT_HI_BLACK` distinguishing the terminal default from real black. Emitted bytes asserted on POSIX in `tests/test_posix_sgr_output.c`; Windows runs the same shared code (real-terminal/byte verification pending).
Declared: `LT_DEFAULT`, `LT_BLACK`, `LT_RED`, `LT_GREEN`, `LT_YELLOW`, `LT_BLUE`, `LT_MAGENTA`, `LT_CYAN`, `LT_WHITE`, plus `LT_RGB(r,g,b)` (24-bit pack), `LT_HEX(0xRRGGBB)` (hex pack, high bits masked), and the `LT_HI_BLACK` sentinel. Emitted on **both platforms** via mode-aware SGR in the shared `lt__emit_sgr` (`src/shared/sgr.c`) — named (NORMAL), 8-bit palette index (256/216/grayscale), and 24-bit RGB (TRUECOLOR), with `LT_HI_BLACK` distinguishing the terminal default from real black. Emitted bytes asserted on POSIX in `tests/test_posix_sgr_output.c`; Windows runs the same shared code (real-terminal/byte verification pending).

### Attributes (`TB_BOLD/UNDERLINE/…` → `LT_BOLD/UNDERLINE/…`)

Expand Down
28 changes: 12 additions & 16 deletions examples/truecolor.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@

#include <stdio.h>

static void puts_at(int x, int y, const char *s, lt_attr fg, lt_attr bg) {
for (int i = 0; s[i]; i++)
lt_set_cell(x + i, y, (lt_uchar)s[i], fg, bg);
}

int main(void) {
int rc = lt_init();
if (rc != LT_OK) {
Expand All @@ -35,11 +30,11 @@ int main(void) {
char title[128];
snprintf(title, sizeof title,
"libterm truecolor demo [%s] (press q or Esc to quit)", depth_note);
puts_at(2, 1, title, LT_DEFAULT, LT_DEFAULT);
lt_print(2, 1, LT_DEFAULT, LT_DEFAULT, title);

/* 24-bit RGB gradient: blue -> red across the row, drawn as cell
* backgrounds (space glyphs). Each column is a distinct true color. */
puts_at(2, 3, "24-bit gradient:", LT_DEFAULT, LT_DEFAULT);
lt_print(2, 3, LT_DEFAULT, LT_DEFAULT, "24-bit gradient:");
for (int i = 0; i < 36; i++) {
int r = i * 7;
int b = 255 - i * 7;
Expand All @@ -52,21 +47,22 @@ int main(void) {

/* Named LT_RGB swatches: a label drawn on its own background color. The
* orange swatch uses LT_RGB(0,0,0)|LT_HI_BLACK for genuinely-black text. */
puts_at(2, 6, "LT_RGB swatches:", LT_DEFAULT, LT_DEFAULT);
puts_at(2, 7, " orange ", LT_RGB(0, 0, 0) | LT_HI_BLACK, LT_RGB(255, 128, 0));
puts_at(11, 7, " teal ", LT_RGB(255, 255, 255), LT_RGB(0, 128, 128));
puts_at(18, 7, " purple ", LT_RGB(255, 255, 255), LT_RGB(128, 0, 128));
lt_print(2, 6, LT_DEFAULT, LT_DEFAULT, "LT_RGB swatches:");
lt_print(2, 7, LT_RGB(0, 0, 0) | LT_HI_BLACK, LT_RGB(255, 128, 0),
" orange ");
lt_print(11, 7, LT_RGB(255, 255, 255), LT_RGB(0, 128, 128), " teal ");
lt_print(18, 7, LT_RGB(255, 255, 255), LT_RGB(128, 0, 128), " purple ");

/* LT_HI_BLACK sentinel: terminal default vs real black. The left cell uses
* the terminal's default background; the right forces RGB(0,0,0). */
puts_at(2, 9, "HI_BLACK vs default:", LT_DEFAULT, LT_DEFAULT);
puts_at(2, 10, " default-bg ", LT_DEFAULT, LT_DEFAULT);
puts_at(15, 10, " real-black ", LT_RGB(255, 255, 255),
LT_RGB(0, 0, 0) | LT_HI_BLACK);
lt_print(2, 9, LT_DEFAULT, LT_DEFAULT, "HI_BLACK vs default:");
lt_print(2, 10, LT_DEFAULT, LT_DEFAULT, " default-bg ");
lt_print(15, 10, LT_RGB(255, 255, 255), LT_RGB(0, 0, 0) | LT_HI_BLACK,
" real-black ");

/* truecolor color composed with an attribute (bold) — proves attrs still
* work after their bits moved above the 24-bit color field. */
puts_at(2, 12, "truecolor + bold", LT_RGB(255, 96, 0) | LT_BOLD, LT_DEFAULT);
lt_print(2, 12, LT_RGB(255, 96, 0) | LT_BOLD, LT_DEFAULT, "truecolor + bold");

lt_present();

Expand Down
6 changes: 6 additions & 0 deletions include/libterm/libterm.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ extern "C" {
((lt_attr)((((lt_attr)(r) & 0xFFu) << 16) | (((lt_attr)(g) & 0xFFu) << 8) | \
((lt_attr)(b) & 0xFFu)))

/* Pack a 0xRRGGBB hex color into bits 0-23 for LT_OUTPUT_TRUECOLOR. High bits
* (attribute flags + the LT_HI_BLACK sentinel) are masked off, so LT_HEX is a
* pure color packer: LT_HEX(0xRRGGBB) packs the same bits as
* LT_RGB(0xRR, 0xGG, 0xBB). */
#define LT_HEX(rgb) ((lt_attr)((rgb) & 0xFFFFFFu))

/* ---- types ---- */
typedef uint32_t lt_uchar;
typedef uint32_t lt_attr;
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ libterm_add_test(test_simd_diff)
libterm_add_test(test_keymap)
libterm_add_test(test_detect_color_depth)
libterm_add_test(test_color_parse)
libterm_add_test(test_color_macros)
# compat/termbox2.h drop-in layer: compile+link proof that every aliased symbol
# resolves. Platform-agnostic (no terminal init), so it runs everywhere.
add_executable(compat_smoke compat_smoke.c)
Expand Down
27 changes: 27 additions & 0 deletions tests/test_color_macros.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* LT_HEX color-packing macro: proves hex packing is identical to LT_RGB and
* that the high bits (the 7 attribute flags + the LT_HI_BLACK sentinel) are
* masked off, so LT_HEX can never leak an attribute. Pure compile-time macro
* checks via _Static_assert — no terminal init, runs everywhere, immune to
* NDEBUG. */
#include "libterm/libterm.h"

/* Packing parity: a 0xRRGGBB hex literal equals the three-channel LT_RGB form.
*/
_Static_assert(LT_HEX(0xFF8000) == LT_RGB(255, 128, 0),
"LT_HEX packing must match LT_RGB");

/* High bits masked: a value with every bit set yields pure white, with no
* attribute or LT_HI_BLACK bits leaking through. */
_Static_assert(LT_HEX(0xFFFFFFFF) == LT_RGB(255, 255, 255),
"LT_HEX must mask bits above the 24-bit color field");
_Static_assert((LT_HEX(0xFFFFFFFF) & 0xFF000000u) == 0u,
"LT_HEX must not set any attribute/sentinel bit");

/* Default-vs-black semantics match LT_RGB: a zero color is the terminal
* default; OR in LT_HI_BLACK for real black. */
_Static_assert(LT_HEX(0x000000) == LT_DEFAULT,
"LT_HEX(0) must equal the terminal default");
_Static_assert((LT_HEX(0) | LT_HI_BLACK) == LT_HI_BLACK,
"LT_HEX(0) | LT_HI_BLACK must select real black");

int main(void) { return 0; }
Loading