From 2f1c7c82ba729eb39f252e2c4bc77d52fdb8f4fb Mon Sep 17 00:00:00 2001
From: Pavel Butsykin
Date: Fri, 27 Feb 2026 00:29:27 +0400
Subject: [PATCH 1/7] Enable -Wall -Wextra -Werror for vtrenderlib
Signed-off-by: Pavel Butsykin
---
CMakeLists.txt | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6a5caac..400d8c2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,6 +3,8 @@ project(vtrenderlib C)
add_library(vtrenderlib STATIC vtrenderlib.c)
+target_compile_options(vtrenderlib PRIVATE -Wall -Wextra -Werror)
+
# Public include directory for consumers
target_include_directories(vtrenderlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
From edb3d7e1c7823927118a3e545b432136b33cb09e Mon Sep 17 00:00:00 2001
From: Pavel Butsykin
Date: Fri, 27 Feb 2026 00:33:38 +0400
Subject: [PATCH 2/7] Fix -Werror=sign-compare warning in sendseq
The cast to size_t is safe here since res == -1 is checked first.
Signed-off-by: Pavel Butsykin
---
vtrenderlib.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/vtrenderlib.c b/vtrenderlib.c
index 7af0c18..29a4eca 100644
--- a/vtrenderlib.c
+++ b/vtrenderlib.c
@@ -261,7 +261,7 @@ struct vtr_canvas* vtr_canvas_create(int ttyfd)
static int sendseq(int ttyfd, const char* seq, size_t nbytes)
{
ssize_t res = write(ttyfd, seq, nbytes);
- if (res == -1 || res != nbytes) {
+ if (res == -1 || (size_t)res != nbytes) {
return -1;
}
From 35be0e54553eb010974149fff1f62ebb16fda1a6 Mon Sep 17 00:00:00 2001
From: Pavel Butsykin
Date: Fri, 27 Feb 2026 00:44:10 +0400
Subject: [PATCH 3/7] Remove redundant >= 0 checks on unsigned types in
scan_line
Comparison is always true due to limited range of data type
[-Werror=type-limits].
Signed-off-by: Pavel Butsykin
---
vtrenderlib.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/vtrenderlib.c b/vtrenderlib.c
index 29a4eca..288f5e3 100644
--- a/vtrenderlib.c
+++ b/vtrenderlib.c
@@ -564,10 +564,10 @@ static float calc_slope(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1)
static void scan_line(struct vtr_stencil_buf* sb, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, enum vtr_color fgc)
{
- assert(x0 >= 0 && x0 < sb->xdots);
- assert(x1 >= 0 && x1 < sb->xdots);
- assert(y0 >= 0 && y0 < sb->ydots);
- assert(y1 >= 0 && y1 < sb->ydots);
+ assert(x0 < sb->xdots);
+ assert(x1 < sb->xdots);
+ assert(y0 < sb->ydots);
+ assert(y1 < sb->ydots);
float m = calc_slope(x0, y0, x1, y1);
int hdir = (x0 < x1 ? 1 : -1);
From b5b9fdf6f55705f19555e22e8c24373250aaf917 Mon Sep 17 00:00:00 2001
From: Pavel Butsykin
Date: Fri, 27 Feb 2026 00:58:52 +0400
Subject: [PATCH 4/7] Annotate intentional switch fallthrough in
vtr_trace_polyc
Signed-off-by: Pavel Butsykin
---
vtrenderlib.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/vtrenderlib.c b/vtrenderlib.c
index 288f5e3..afef5b4 100644
--- a/vtrenderlib.c
+++ b/vtrenderlib.c
@@ -761,7 +761,9 @@ int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_ve
// Intercepts can repeat if 2 edges meet at current y, ignore that
switch(ncepts) {
case 2: if (xcepts[1] == xcept) continue;
+ /* fallthrough */
case 1: if (xcepts[0] == xcept) continue;
+ /* fallthrough */
case 0: break;
}
From 9c6667dd48cc61e071d9cc6214492f4b3862803e Mon Sep 17 00:00:00 2001
From: Pavel Butsykin
Date: Fri, 27 Feb 2026 01:10:00 +0400
Subject: [PATCH 5/7] Fix PTY slave/master typo in escape sequence data flow
comment
Signed-off-by: Pavel Butsykin
---
vtrenderlib.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/vtrenderlib.c b/vtrenderlib.c
index afef5b4..5245a57 100644
--- a/vtrenderlib.c
+++ b/vtrenderlib.c
@@ -18,8 +18,8 @@
// - VT/xterm escape sequences work in the same manner:
// 1. Shell/app: 0x1B 0x5B 0x33 0x31 0x6D # ESC [ 3 1 m → “set foreground color to red” -> written to PTY slave via STDOUT_FILENO
// 2. Kernel PTY layer: handles line discipline, raw mode (bytes pass through unchanged) vs canonical mode (control chars handled),
-// forwards data to PTY slave.
-// 3. Terminal emulator: reads data from PTY slave, recognizes `ESC [ 31 m`, changes rendering color, paints to the screen.
+// forwards data to PTY master.
+// 3. Terminal emulator: reads data from PTY master, recognizes `ESC [ 31 m`, changes rendering color, paints to the screen.
//
//
From 22e07992831337b6472c9cf48b0ef9016df18eaa Mon Sep 17 00:00:00 2001
From: Pavel Butsykin
Date: Fri, 27 Feb 2026 17:55:54 +0400
Subject: [PATCH 6/7] =?UTF-8?q?Fix=20unused=20parameter=20=E2=80=98seqcap?=
=?UTF-8?q?=E2=80=99=20for=20NDEBUG=20build?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Pavel Butsykin
---
vtrenderlib.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/vtrenderlib.c b/vtrenderlib.c
index 5245a57..b25cfa4 100644
--- a/vtrenderlib.c
+++ b/vtrenderlib.c
@@ -406,7 +406,7 @@ static size_t set_pos_s(char* seq, size_t seqcap, uint16_t row, uint16_t col)
return nwritten;
}
-static size_t draw_cell_s(char* seq, size_t seqcap, uint8_t mask)
+static size_t draw_cell_s(char* seq, __attribute__((unused)) size_t seqcap, uint8_t mask)
{
assert(seqcap >= 3);
@@ -417,7 +417,7 @@ static size_t draw_cell_s(char* seq, size_t seqcap, uint8_t mask)
return 3;
}
-static size_t set_foreground_color_s(char* seq, size_t seqcap, uint8_t fgc)
+static size_t set_foreground_color_s(char* seq, __attribute__((unused)) size_t seqcap, uint8_t fgc)
{
assert(fgc >= VTR_COLOR_DEFAULT && fgc < VTR_COLOR_TOTAL);
assert(seqcap >= 5);
@@ -431,7 +431,7 @@ static size_t set_foreground_color_s(char* seq, size_t seqcap, uint8_t fgc)
return 5;
}
-static size_t put_char_s(char* seq, size_t seqcap, char chr)
+static size_t put_char_s(char* seq, __attribute__((unused)) size_t seqcap, char chr)
{
assert(seqcap >= 1);
seq[0] = chr;
From 4a909106885d4b0078bc60783a8f2e5f23eb249a Mon Sep 17 00:00:00 2001
From: Pavel Butsykin
Date: Fri, 27 Feb 2026 01:25:11 +0400
Subject: [PATCH 7/7] Add 256 color indexed palette support
Use the 256-color escape sequence ESC[38;5;INDEXm for all foreground
colors, replacing the legacy SGR 30-37 path. The existing enum vtr_color
values remain valid because the standard ANSI colors 0-7 share the same
palette indices in the 256-color mode. VTR_COLOR_DEFAULT still emits
ESC[39m to reset to the terminal default color.
Palette indices are stored as idx+1 in vtr_fgcolor_t since value 0 is
reserved for VTR_COLOR_DEFAULT. This means index 255 would overflow
uint8_t, so it's mapped to VTR_COLOR_WHITE (the closest color match).
Signed-off-by: Pavel Butsykin
---
demos/boids/main.c | 4 ++--
vtrenderlib.c | 55 +++++++++++++++++++++++++++++++++-------------
vtrenderlib.h | 19 ++++++++++++----
3 files changed, 57 insertions(+), 21 deletions(-)
diff --git a/demos/boids/main.c b/demos/boids/main.c
index 98cdc37..7e77680 100644
--- a/demos/boids/main.c
+++ b/demos/boids/main.c
@@ -96,7 +96,7 @@ struct vt_boid
int cur_heading_time;
// rendering data
- enum vtr_color color;
+ vtr_fgcolor_t color;
struct vec2f trail[VT_BOID_TRAIL_SIZE];
size_t trail_idx;
size_t trail_len;
@@ -241,7 +241,7 @@ static void debug_print(uint16_t row, uint16_t col, const char* fmt, ...)
va_end(args);
}
-static void debug_vec(struct vec2f origin, struct vec2f vec, float scale, enum vtr_color fgc)
+static void debug_vec(struct vec2f origin, struct vec2f vec, float scale, vtr_fgcolor_t fgc)
{
struct vtr_vertex start = vec2f_project(origin);
struct vtr_vertex end = vec2f_project(vec2f_mul_add(origin, vec, scale));
diff --git a/vtrenderlib.c b/vtrenderlib.c
index b25cfa4..1daa098 100644
--- a/vtrenderlib.c
+++ b/vtrenderlib.c
@@ -121,10 +121,13 @@
#define VT_CELL_YDOTS ((uint16_t)4)
#define VT_CELL_XDOTS ((uint16_t)2)
-// We preallocate enough space to hold the biggest number of draw calls (3 + 6 colored bytes per escape seq)
+// We preallocate enough space to hold the biggest number of draw calls (3 + 11 colored bytes per escape seq)
// plus some slack to hold a cursor reset commands.
#define VT_MIN_SEQLIST_SLACK ((size_t)64)
-#define VT_SEQLIST_BUFFER_SIZE(rows, cols) MAX((size_t)((rows + 1) * cols + 1) * 9, VT_MIN_SEQLIST_SLACK)
+#define VT_COLOR_SEQ_MAXLEN ((size_t)11)
+#define VT_DRAW_SEQ_LEN ((size_t)3)
+#define VT_CELL_SEQS_MAX (VT_COLOR_SEQ_MAXLEN + VT_DRAW_SEQ_LEN)
+#define VT_SEQLIST_BUFFER_SIZE(rows, cols) MAX((size_t)((rows + 1) * cols + 1) * VT_CELL_SEQS_MAX, VT_MIN_SEQLIST_SLACK)
struct vtr_stencil_buf
{
@@ -417,18 +420,40 @@ static size_t draw_cell_s(char* seq, __attribute__((unused)) size_t seqcap, uint
return 3;
}
+static size_t u8_to_dec(char* dst, uint8_t v)
+{
+ if (v >= 100) {
+ dst[0] = '0' + v / 100;
+ dst[1] = '0' + (v / 10) % 10;
+ dst[2] = '0' + v % 10;
+ return 3;
+ }
+ if (v >= 10) {
+ dst[0] = '0' + v / 10;
+ dst[1] = '0' + v % 10;
+ return 2;
+ }
+ dst[0] = '0' + v;
+ return 1;
+}
+
static size_t set_foreground_color_s(char* seq, __attribute__((unused)) size_t seqcap, uint8_t fgc)
{
- assert(fgc >= VTR_COLOR_DEFAULT && fgc < VTR_COLOR_TOTAL);
- assert(seqcap >= 5);
+ assert(seqcap >= VT_COLOR_SEQ_MAXLEN);
+
+ if (fgc == VTR_COLOR_DEFAULT) {
+ memcpy(seq, "\x1b[39m", 5);
+ return 5;
+ }
+ const char fgc256[] = "\x1b[38;5;";
+ size_t fgc256_len = sizeof(fgc256) - 1;
- seq[0] = 0x1b;
- seq[1] = '[';
- seq[2] = '3';
- seq[3] = (fgc == VTR_COLOR_DEFAULT ? '9' : '0' + fgc - 1);
- seq[4] = 'm';
+ memcpy(seq, fgc256, fgc256_len);
+ size_t n = u8_to_dec(&seq[fgc256_len], fgc - 1);
+ seq[fgc256_len + n] = 'm';
- return 5;
+ assert(fgc256_len + n + 1 <= VT_COLOR_SEQ_MAXLEN);
+ return fgc256_len + n + 1;
}
static size_t put_char_s(char* seq, __attribute__((unused)) size_t seqcap, char chr)
@@ -517,7 +542,7 @@ int vtr_swap_buffers(struct vtr_canvas* vt)
return sendseq(vt->fd, vt->seqlist, seqlen);
}
-static void render_dot(struct vtr_stencil_buf* sb, uint16_t x, uint16_t y, enum vtr_color fgc)
+static void render_dot(struct vtr_stencil_buf* sb, uint16_t x, uint16_t y, vtr_fgcolor_t fgc)
{
assert(x < sb->xdots && y < sb->ydots);
@@ -546,7 +571,7 @@ void vtr_render_dot(struct vtr_canvas* vt, int x, int y)
vtr_render_dotc(vt, x, y, VTR_COLOR_DEFAULT);
}
-void vtr_render_dotc(struct vtr_canvas* vt, int x, int y, enum vtr_color fgc)
+void vtr_render_dotc(struct vtr_canvas* vt, int x, int y, vtr_fgcolor_t fgc)
{
if (point_test(vt, x, y)) {
render_dot(vt->cur_sb, x, y, fgc);
@@ -562,7 +587,7 @@ static float calc_slope(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1)
return (x1 == x0 ? INFINITY : ((float)y1 - y0) / ((float)x1 - x0));
}
-static void scan_line(struct vtr_stencil_buf* sb, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, enum vtr_color fgc)
+static void scan_line(struct vtr_stencil_buf* sb, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, vtr_fgcolor_t fgc)
{
assert(x0 < sb->xdots);
assert(x1 < sb->xdots);
@@ -637,7 +662,7 @@ void vtr_scan_line(struct vtr_canvas* vt, int x0, int y0, int x1, int y1)
vtr_scan_linec(vt, x0, y0, x1, y1, VTR_COLOR_DEFAULT);
}
-void vtr_scan_linec(struct vtr_canvas* vt, int x0, int y0, int x1, int y1, enum vtr_color fgc)
+void vtr_scan_linec(struct vtr_canvas* vt, int x0, int y0, int x1, int y1, vtr_fgcolor_t fgc)
{
int dx = x1 - x0, dy = y1 - y0;
int xmin = 0, ymin = 0;
@@ -688,7 +713,7 @@ int vtr_trace_poly(struct vtr_canvas* vt, size_t nvertices, const struct vtr_ver
return vtr_trace_polyc(vt, nvertices, vlist, VTR_COLOR_DEFAULT);
}
-int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vlist, enum vtr_color fgc)
+int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vlist, vtr_fgcolor_t fgc)
{
assert(vt);
assert(vlist);
diff --git a/vtrenderlib.h b/vtrenderlib.h
index cb3552d..477efe2 100644
--- a/vtrenderlib.h
+++ b/vtrenderlib.h
@@ -77,27 +77,38 @@ enum vtr_color
VTR_COLOR_CYAN,
VTR_COLOR_WHITE,
- VTR_COLOR_TOTAL // always last
+ VTR_COLOR_SGR8_TOTAL // always last
};
+/*
+ * Foreground color type. Accepts enum vtr_color or VTR_COLOR256(idx) values.
+ */
+typedef uint8_t vtr_fgcolor_t;
+
+/*
+ * 256-color palette (ITU-T T.416 / ISO/IEC 8613-6).
+ * Index 255 (light grey) is mapped to VTR_COLOR_WHITE since 0 is reserved for VTR_COLOR_DEFAULT.
+ */
+#define VTR_COLOR256(_idx) ((vtr_fgcolor_t)((_idx) >= 255 ? VTR_COLOR_WHITE : (_idx) + 1))
+
/**
* Render a dot at given VT coordinates.
*/
void vtr_render_dot(struct vtr_canvas* vt, int x, int y);
-void vtr_render_dotc(struct vtr_canvas* vt, int x, int y, enum vtr_color fgc);
+void vtr_render_dotc(struct vtr_canvas* vt, int x, int y, vtr_fgcolor_t fgc);
/**
* Scan a line given two dot coordinates.
*/
void vtr_scan_line(struct vtr_canvas* vt, int x0, int y0, int x1, int y1);
-void vtr_scan_linec(struct vtr_canvas* vt, int x0, int y0, int x1, int y1, enum vtr_color fgc);
+void vtr_scan_linec(struct vtr_canvas* vt, int x0, int y0, int x1, int y1, vtr_fgcolor_t fgc);
/**
* Trace a polygon path given a list of vertices.
* The last vertex is traced back to the first one and the resulting polygon filled.
*/
int vtr_trace_poly(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vertexlist);
-int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vertexlist, enum vtr_color fgc);
+int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vertexlist, vtr_fgcolor_t fgc);
/**
* Print some text at the specified location.