ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
common/session/protocol.c
Go to the documentation of this file.
1#include <defer.h>
3#include "session/protocol.h"
6#include <ascii-chat/common.h>
7#include <ascii-chat/buffer.h>
14#include <string.h>
17#include <libplatform/terminal.h>
20
21static void receive_frame(const ascii_frame_packet_t *header, const void *data, size_t length, void *context) {
22 session_protocol_t *protocol = context;
24 uint32_t effects = 0;
25 bool has_contract = (header->flags & FRAME_FLAG_RENDER_CONTRACT) != 0;
26 if (header->flags & ~(FRAME_FLAG_RENDER_CONTRACT | FRAME_FLAG_IS_COMPRESSED | 1u)) {
27 protocol->callback_result = SET_ERRNO(ERROR_INVALID_FRAME, "Unknown participant frame flags");
28 return;
29 }
30 if (has_contract) {
31 size_t prefix;
32 protocol->callback_result = frame_render_contract_decode(data, length, &contract, &effects, &prefix);
33 if (protocol->callback_result != ASCIICHAT_OK)
34 return;
35 data = (const uint8_t *)data + prefix;
36 length -= prefix;
37 if (contract.revision && contract.revision == protocol->settings_revision &&
39 strcmp(contract.palette, protocol->effective_render_settings.palette) != 0 &&
40 protocol->effective_render_settings.caps.utf8_support) {
41 protocol->callback_result = SET_ERRNO(ERROR_INVALID_FRAME, "Frame palette contradicts acknowledged revision");
42 return;
43 }
44 }
45 bool compressed = (header->flags & FRAME_FLAG_IS_COMPRESSED) != 0;
46 size_t expected = compressed ? header->compressed_size : header->original_size;
47 if (!header->original_size || expected > FRAME_RENDER_MAX_BYTES || length != expected ||
48 (compressed && !header->compressed_size) || (!compressed && header->compressed_size)) {
49 protocol->callback_result = SET_ERRNO(ERROR_INVALID_FRAME, "Invalid participant ASCII frame length");
50 return;
51 }
52 // Bound decoded work before allocation/decompression, matching the UI frame budget.
53 if (header->original_size > FRAME_RENDER_MAX_BYTES) {
54 protocol->callback_result = SET_ERRNO(ERROR_INVALID_FRAME, "Participant ASCII frame exceeds byte budget");
55 return;
56 }
58 char *text;
59 if (protocol->media.frame_workspace) {
61 protocol->media.frame_workspace, data, length, compressed, header->original_size, header->compressed_size);
62 if (protocol->callback_result != ASCIICHAT_OK)
63 return;
64 text = protocol->media.frame_workspace->data;
65 } else {
66 text = packet_decode_frame_data_malloc(data, length, compressed, header->original_size, header->compressed_size);
67 }
68 if (!text) {
70 protocol->callback_result = errno_peek_since(decode_scope, &cause)
71 ? cause.code
72 : SET_ERRNO(ERROR_INVALID_FRAME, "Could not decode participant ASCII frame");
73 return;
74 }
75 if (asciichat_crc32(text, header->original_size) != header->checksum) {
76 protocol->callback_result = SET_ERRNO(ERROR_INVALID_FRAME, "Participant ASCII frame checksum mismatch");
77 } else if ((protocol->callback_result =
78 session_frame_validate_contract(text, header->original_size, header->width, header->height,
79 has_contract ? &contract : NULL, effects)) == ASCIICHAT_OK) {
80 // A valid old frame may arrive after a resize/settings update. Skip it
81 // without disconnecting the transport or disturbing audio and recovery.
82 if (protocol->media.terminal_output) {
83 // Redirected snapshots have no physical viewport; the UI fallback is
84 // not a bound on their explicitly configured or default output size.
85 terminal_size_t viewport =
86 (ui_controller_is_presenting() || terminal_is_stdout_tty()) ? ui_controller_size() : (terminal_size_t){0};
87 uint32_t width = terminal_get_effective_width(GET_OPTION(width), 80),
88 height = terminal_get_effective_height(GET_OPTION(height), 24);
89 if ((viewport.cols > 0 && header->width > (uint32_t)viewport.cols) ||
90 (viewport.rows > 0 && header->height > (uint32_t)viewport.rows) || header->width > width ||
91 header->height > height) {
92 protocol->frames_skipped_viewport++;
93 goto done;
94 }
95 }
96 protocol->frames_received++;
97 if (protocol->media.on_frame)
98 protocol->callback_result = protocol->media.on_frame(header, text, protocol->media.context);
99 }
100done:
101 if (!protocol->media.frame_workspace)
102 SAFE_FREE(text);
103}
104
105static void receive_samples(const audio_batch_packet_t *header, const float *samples, size_t count, void *context) {
106 session_protocol_t *protocol = context;
107 if (header && (header->sample_rate != AUDIO_SAMPLE_RATE || (header->channels != 1 && header->channels != 2) ||
108 count % header->channels)) {
109 protocol->callback_result = SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Unsupported participant audio format");
110 return;
111 }
112 if (protocol->media.on_samples) {
113 if (header && header->channels == 1) {
114 float *stereo = SAFE_MALLOC(count * AUDIO_CHANNELS * sizeof(float), float *);
115 protocol->callback_result = audio_convert_channels(samples, count, 1, stereo, AUDIO_CHANNELS);
116 if (protocol->callback_result == ASCIICHAT_OK)
117 protocol->callback_result = protocol->media.on_samples(stereo, count * AUDIO_CHANNELS, protocol->media.context);
118 SAFE_FREE(stereo);
119 } else {
120 protocol->callback_result = protocol->media.on_samples(samples, count, protocol->media.context);
121 }
122 }
123}
124
125static void receive_opus(const void *data, size_t length, void *context) {
126 session_protocol_t *protocol = context;
127 if (!protocol->media.decode_opus || !protocol->media.on_samples)
128 return;
129 float samples[AUDIO_OPUS_MAX_DECODE_SAMPLES * AUDIO_CHANNELS]; // 120 ms at 48 kHz.
130 size_t decoded = 0;
131 protocol->callback_result = protocol->media.decode_opus(
132 data, length, samples, AUDIO_OPUS_MAX_DECODE_SAMPLES * AUDIO_CHANNELS, &decoded, protocol->media.context);
133 if (protocol->callback_result != ASCIICHAT_OK)
134 return;
136 protocol->callback_result = SET_ERRNO(ERROR_AUDIO, "Participant decoder exceeded sample capacity");
137 return;
138 }
139 if (decoded)
140 receive_samples(NULL, samples, decoded, context);
141}
142
143static void receive_opus_batch(const void *data, size_t length, void *context) {
144 session_protocol_t *protocol = context;
145 const uint8_t *opus;
146 const uint16_t *sizes;
147 size_t opus_length;
148 int rate, duration, count;
149 protocol->callback_result =
150 packet_parse_opus_batch(data, length, &opus, &opus_length, &sizes, &rate, &duration, &count);
151 if (protocol->callback_result != ASCIICHAT_OK)
152 return;
153 if (rate != AUDIO_SAMPLE_RATE || duration < 1 || duration > 120 || count < 1 || count > 256) {
154 protocol->callback_result = SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid participant Opus batch format");
155 return;
156 }
157 // Validate the whole batch before delivering any samples, including unaligned input.
158 size_t total = 0;
159 for (int i = 0; i < count; i++) {
160 uint16_t size;
161 memcpy(&size, (const uint8_t *)sizes + (size_t)i * sizeof(size), sizeof(size));
162 size = NET_TO_HOST_U16(size);
163 if (!size || size > opus_length - total) {
164 protocol->callback_result = SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Truncated participant Opus batch");
165 return;
166 }
167 total += size;
168 }
169 if (total != opus_length) {
170 protocol->callback_result = SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Trailing participant Opus batch data");
171 return;
172 }
173 size_t offset = 0;
174 for (int i = 0; i < count; i++) {
175 uint16_t size;
176 memcpy(&size, (const uint8_t *)sizes + (size_t)i * sizeof(size), sizeof(size));
177 size = NET_TO_HOST_U16(size);
178 receive_opus(opus + offset, size, context);
179 if (protocol->callback_result != ASCIICHAT_OK)
180 return;
181 offset += size;
182 }
183}
184
185static void receive_state(const server_state_packet_t *state, void *context) {
186 session_protocol_t *protocol = context;
187 uint32_t active = NET_TO_HOST_U32(state->active_client_count);
188 if (!protocol->server_state_initialized || protocol->active_client_count != active)
189 protocol->layout_changed = true;
190 protocol->server_state_initialized = true;
191 protocol->active_client_count = active;
192 if (protocol->control.on_server_state)
193 protocol->control.on_server_state(state, protocol->control.app_ctx);
194}
195
196static void receive_error(const error_packet_t *header, const char *message, void *context) {
197 session_protocol_t *protocol = context;
198 if (protocol->control.on_error)
199 protocol->control.on_error(header, message, protocol->control.app_ctx);
200 else
201 protocol->callback_result = SET_ERRNO(ERROR_NETWORK, "Participant peer error: %s", message);
202}
203
205 acip_client_callbacks_t control) {
206 *protocol = (session_protocol_t){.transport = transport, .media = media, .control = control};
207 protocol->effective_render_settings.caps.utf8_support = GET_OPTION(force_utf8) != UTF8_SETTING_FALSE;
208}
209
211 size_t length) {
212 if (!protocol || (!payload && length))
213 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid participant protocol packet");
214 protocol->callback_result = ASCIICHAT_OK;
215 if (type == PACKET_TYPE_ACIP_SETTINGS_ACK) {
217 const char *json;
218 size_t json_length;
219 asciichat_error_t result = acip_settings_decode(payload, length, &header, &json, &json_length);
220 if (result != ASCIICHAT_OK)
221 return result;
222 if ((header.request && header.request < protocol->settings_request) ||
223 header.revision < protocol->settings_revision)
224 return ASCIICHAT_OK;
225 options_t *current = SAFE_MALLOC(sizeof(*current), options_t *),
226 *candidate = SAFE_MALLOC(sizeof(*candidate), options_t *);
228 result = options_prepare_json(current, json, json_length, candidate, true);
229 if (result == ASCIICHAT_OK)
230 result = acip_render_settings_prepare(&protocol->effective_render_settings, candidate);
231 SAFE_FREE(current);
232 SAFE_FREE(candidate);
233 if (result != ASCIICHAT_OK)
234 return result;
235 protocol->effective_render_settings.revision = header.revision;
236 if (header.request)
237 protocol->settings_request = header.request;
238 protocol->settings_revision = header.revision;
239 protocol->settings_status = header.status;
240 if (header.status)
241 log_warn("Remote rendering update rejected (request=%u, status=%u)", header.request, header.status);
242 else
243 log_info("Remote rendering update accepted (request=%u, revision=%u)", header.request, header.revision);
244 return ASCIICHAT_OK;
245 }
246 acip_client_callbacks_t media = {.on_ascii_frame = receive_frame,
247 .on_audio_batch = receive_samples,
248 .on_audio_opus_batch = receive_opus_batch,
249 .on_server_state = receive_state,
250 .on_error = receive_error,
251 .app_ctx = protocol};
252 bool audio = type == PACKET_TYPE_AUDIO_BATCH || type == PACKET_TYPE_AUDIO_OPUS_BATCH;
253 bool common =
255 asciichat_error_t result;
256 if (type == PACKET_TYPE_PING && !protocol->control.on_ping)
257 result = acip_send_pong(protocol->transport);
258 else
259 result =
260 acip_handle_client_packet(protocol->transport, type, payload, length, common ? &media : &protocol->control);
261 if (result == ASCIICHAT_OK)
262 result = protocol->callback_result;
263 if (result == ASCIICHAT_OK) {
264 protocol->packets_received++;
265 if (audio) {
266 protocol->audio_packets_received++;
267 if (protocol->media.on_audio_packet)
268 protocol->media.on_audio_packet(length, protocol->media.context);
269 }
270 }
271 return result;
272}
273
274void session_protocol_build_capabilities(unsigned short width, unsigned short height,
276 const options_t *opts = options_get();
277 // Log the dimensions being sent to server (helps debug dimension mismatch issues)
278 log_debug("Sending terminal size to server: %ux%u (auto_width=%d, auto_height=%d)", width, height, opts->auto_width,
279 opts->auto_height);
280
281 // Connection and transport availability is checked by threaded_send_packet()
282
283 // Build terminal capabilities packet locally
284 // Detect terminal capabilities automatically
285 terminal_capabilities_t caps = detect_terminal_capabilities(options_terminal_settings());
286
287 // Set wants_padding based on snapshot mode and TTY status
288 // Disable padding when:
289 // - In snapshot mode (one frame and exit)
290 // - When stdout is not a TTY (piped/redirected output)
291 // Enable padding for interactive terminal sessions
292 bool is_snapshot_mode = opts->snapshot_mode;
293 bool is_interactive = terminal_is_interactive();
294 caps.wants_padding = is_interactive && !is_snapshot_mode;
295
296 log_debug("Client capabilities: wants_padding=%d (snapshot=%d, interactive=%d, stdin_tty=%d, stdout_tty=%d)",
297 caps.wants_padding, is_snapshot_mode, is_interactive, terminal_is_stdin_tty(), terminal_is_stdout_tty());
298
299 // Apply user's color mode override
300 caps = apply_color_mode_override(caps, options_terminal_settings());
301
302 // TERM_COLOR_AUTO is an option/configuration sentinel, not a wire-level
303 // capability. A remote peer must receive a concrete color level; this can
304 // otherwise make the server reject the complete capabilities packet. The
305 // browser/native renderer contract supports truecolor as the safe default.
306 terminal_color_mode_t normalized_color = acip_render_settings_normalize_color_mode(caps.color_level);
307 if (normalized_color != caps.color_level) {
308 log_debug("Terminal color detection returned AUTO; advertising truecolor");
309 caps.color_level = normalized_color;
310 caps.color_count = 16777216;
311 caps.capabilities |= TERM_CAP_COLOR_16 | TERM_CAP_COLOR_256 | TERM_CAP_COLOR_TRUE;
312 }
313
314 // Check if detection was reliable, use fallback only for auto-detection
315 if (!caps.detection_reliable && opts->color_mode == COLOR_MODE_AUTO) {
316 log_warn("Terminal capability detection not reliable, using fallback");
317 SAFE_MEMSET(&caps, sizeof(caps), 0, sizeof(caps));
318 caps.color_level = TERM_COLOR_NONE;
319 caps.color_count = 2;
320 caps.capabilities = 0;
321 SAFE_STRNCPY(caps.term_type, "unknown", sizeof(caps.term_type));
322 SAFE_STRNCPY(caps.colorterm, "", sizeof(caps.colorterm));
323 caps.detection_reliable = 0;
324 // Preserve wants_padding even in fallback mode
325 caps.wants_padding = is_interactive && !is_snapshot_mode;
326 }
327
328 // Convert to network packet format with proper byte order
329 terminal_capabilities_packet_t net_packet = {0};
330 if (opts->matrix_rain)
331 caps.capabilities |= TERM_CAP_MATRIX_RAIN;
332 else
333 caps.capabilities &= ~(uint32_t)TERM_CAP_MATRIX_RAIN;
334 net_packet.capabilities = HOST_TO_NET_U32(caps.capabilities);
335 net_packet.color_level = HOST_TO_NET_U32(caps.color_level);
336 net_packet.color_count = HOST_TO_NET_U32(caps.color_count);
337 net_packet.render_mode = HOST_TO_NET_U32(opts->render_mode);
338 net_packet.color_filter = (uint8_t)opts->color_filter;
339 net_packet.width = HOST_TO_NET_U16(width);
340 net_packet.height = HOST_TO_NET_U16(height);
341 net_packet.palette_type = HOST_TO_NET_U32(opts->palette_type);
342 net_packet.utf8_support = HOST_TO_NET_U32(caps.utf8_support ? 1 : 0);
343
344 if (opts->palette_type == PALETTE_CUSTOM && opts->palette_custom_set) {
345 const char *palette_custom = opts && opts->palette_custom_set ? opts->palette_custom : "";
346 SAFE_STRNCPY(net_packet.palette_custom, palette_custom, sizeof(net_packet.palette_custom));
347 net_packet.palette_custom[sizeof(net_packet.palette_custom) - 1] = '\0';
348 } else {
349 SAFE_MEMSET(net_packet.palette_custom, sizeof(net_packet.palette_custom), 0, sizeof(net_packet.palette_custom));
350 }
351
352 // Set desired FPS
353 int fps = opts->fps;
354 if (fps > 0) {
355 net_packet.desired_fps = (uint8_t)(fps > 144 ? 144 : fps);
356 } else {
357 net_packet.desired_fps = caps.desired_fps;
358 }
359
360 if (net_packet.desired_fps == 0) {
361 net_packet.desired_fps = DEFAULT_MAX_FPS;
362 }
363
364 SAFE_STRNCPY(net_packet.term_type, caps.term_type, sizeof(net_packet.term_type));
365 net_packet.term_type[sizeof(net_packet.term_type) - 1] = '\0';
366
367 SAFE_STRNCPY(net_packet.colorterm, caps.colorterm, sizeof(net_packet.colorterm));
368 net_packet.colorterm[sizeof(net_packet.colorterm) - 1] = '\0';
369
370 net_packet.detection_reliable = caps.detection_reliable;
371 // Send UTF-8 support flag: true for AUTO (default) and TRUE settings, false for FALSE setting
372 net_packet.utf8_support = (opts->force_utf8 != UTF8_SETTING_FALSE) ? 1 : 0;
373
374 // Set wants_padding flag (1=padding enabled, 0=no padding for snapshot/piped modes)
375 net_packet.wants_padding = caps.wants_padding ? 1 : 0;
376
377 // Set codec capabilities (client supports all codecs by default)
380
381 *packet = net_packet;
382}
383
384asciichat_error_t session_protocol_update_settings(terminal_size_t *advertised, uint64_t *advertised_revision,
385 session_protocol_send_settings_fn send, void *context) {
386 if (!advertised || !advertised_revision || !send)
387 return SET_ERRNO(ERROR_INVALID_PARAM, "Settings update requires state and a send callback");
388 if (advertised->cols > 0 && advertised->rows > 0 && *advertised_revision == options_revision())
389 return ASCIICHAT_OK;
391 asciichat_error_t result = options_read_snapshot(snapshot);
392 if (result != ASCIICHAT_OK) {
393 return result;
394 }
395 terminal_size_t size = {.cols = snapshot->values.width, .rows = snapshot->values.height};
396 if (size.cols <= 0 || size.rows <= 0) {
397 return ASCIICHAT_OK;
398 }
399 if (snapshot->values.color_mode == TERM_COLOR_AUTO) {
400 terminal_capabilities_t caps = detect_terminal_capabilities(options_terminal_settings());
401 snapshot->values.color_mode = caps.detection_reliable ? caps.color_level : TERM_COLOR_NONE;
402 }
403 result = send(&snapshot->values, (uint32_t)snapshot->revision, context);
404 if (result == ASCIICHAT_OK) {
405 *advertised = size;
406 *advertised_revision = snapshot->revision;
407 }
408
409 return result;
410}
bool errno_peek_since(asciichat_errno_scope_t scope, asciichat_error_context_t *out)
asciichat_errno_scope_t errno_checkpoint(void)
#define SET_ERRNO(code,...)
asciichat_error_t audio_convert_channels(const float *input, size_t frames, unsigned input_channels, float *output, unsigned output_channels)
#define AUDIO_OPUS_MAX_DECODE_SAMPLES
#define AUDIO_SAMPLE_RATE
Definition audio/limits.h:4
#define AUDIO_CHANNELS
Definition audio/limits.h:5
Video and audio codec definitions and capability bitmasks.
#define AUDIO_CODEC_CAP_ALL
Definition codecs.h:80
asciichat_error_t session_protocol_update_settings(terminal_size_t *advertised, uint64_t *advertised_revision, session_protocol_send_settings_fn send, void *context)
asciichat_error_t session_protocol_dispatch(session_protocol_t *protocol, packet_type_t type, const void *payload, size_t length)
void session_protocol_init(session_protocol_t *protocol, acip_transport_t *transport, session_protocol_media_t media, acip_client_callbacks_t control)
void session_protocol_build_capabilities(unsigned short width, unsigned short height, terminal_capabilities_packet_t *packet)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Hardware-Accelerated CRC32 Checksum Computation.
🔄 Network byte order conversion helpers
#define HOST_TO_NET_U16(val)
Definition endian.h:96
#define HOST_TO_NET_U32(val)
Definition endian.h:66
#define NET_TO_HOST_U16(val)
Definition endian.h:111
#define NET_TO_HOST_U32(val)
Definition endian.h:81
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
asciichat_error_t frame_render_contract_decode(const void *wire, size_t available, acip_render_settings_t *settings, uint32_t *effects, size_t *length)
asciichat_error_t session_frame_validate_contract(const char *text, size_t length, uint32_t width, uint32_t height, const acip_render_settings_t *settings, uint32_t effects)
unsigned short uint16_t
Definition common.h:61
unsigned int uint32_t
Definition common.h:62
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:484
#define SAFE_MALLOC_DEFER_FREE(name, size, cast)
Definition common.h:386
#define SAFE_MALLOC(size, cast)
Definition common.h:265
unsigned long long uint64_t
Definition common.h:63
unsigned char uint8_t
Definition common.h:60
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_NETWORK
Definition error_codes.h:72
@ ERROR_AUDIO
Definition error_codes.h:62
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:76
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_FRAME
@ ERROR_INVALID_PARAM
#define DEFAULT_MAX_FPS
Default maximum frame rate (frames per second).
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define VIDEO_CODEC_CAP_ALL
Default video codec capabilities (all codecs supported).
Definition codecs.h:52
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:301
@ PACKET_TYPE_AUDIO_OPUS_BATCH
Batched Opus-encoded audio frames.
Definition packet.h:398
@ PACKET_TYPE_SERVER_STATE
Server sends current state to clients.
Definition packet.h:421
@ PACKET_TYPE_ACIP_SETTINGS_ACK
Settings acknowledgment (Participant -> Initiator).
Definition packet.h:487
@ PACKET_TYPE_ASCII_FRAME
Complete ASCII frame with all metadata.
Definition packet.h:383
@ PACKET_TYPE_ERROR_MESSAGE
Error packet with asciichat_error_t code and human-readable message.
Definition packet.h:374
@ PACKET_TYPE_PING
Keepalive ping packet.
Definition packet.h:407
@ PACKET_TYPE_AUDIO_BATCH
Batched audio packets for efficiency.
Definition packet.h:396
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
#define COLOR_MODE_AUTO
Backward compatibility aliases for color mode enum values.
Definition options.h:222
struct options_state options_t
Immutable, RCU-published snapshot of the application's runtime configuration.
const options_t * options_get(void)
Get pointer to current options struct (lock-free, thread-safe).
Definition rcu.c:511
#define options_terminal_settings()
Definition options.h:1296
@ UTF8_SETTING_FALSE
Force UTF-8 OFF: disable UTF-8 support.
Definition options.h:268
asciichat_error_t packet_parse_opus_batch(const void *packet_data, size_t packet_len, const uint8_t **out_opus_data, size_t *out_opus_size, const uint16_t **out_frame_sizes, int *out_sample_rate, int *out_frame_duration, int *out_frame_count)
Parse Opus audio batch packet header and extract frame data.
asciichat_error_t packet_decode_frame_data_workspace(buffer_workspace_t *workspace, const void *data, size_t length, bool compressed, uint32_t original_size, uint32_t compressed_size)
char * packet_decode_frame_data_malloc(const char *data, size_t length, bool compressed, uint32_t original_size, uint32_t compressed_size)
Decode frame data (malloc version for client handlers).
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:104
#define FRAME_FLAG_RENDER_CONTRACT
Explicit versioned render contract precedes encoded ASCII text.
#define FRAME_FLAG_IS_COMPRESSED
Frame data is compressed.
struct acip_transport acip_transport_t
Forward declaration of ACIP transport.
Definition host.h:506
#define asciichat_crc32(data, len)
Main CRC32 dispatcher macro - use this in application code.
Definition crc32.h:147
asciichat_error_t acip_handle_client_packet(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, const acip_client_callbacks_t *callbacks)
Client-side packet handler callbacks.
Definition handlers.c:203
🔊 Audio Capture and Playback Interface for ascii-chat
asciichat_error_t acip_render_settings_prepare(acip_render_settings_t *candidate, const options_t *values)
terminal_color_mode_t acip_render_settings_normalize_color_mode(terminal_color_mode_t mode)
asciichat_error_t acip_settings_decode(const void *payload, size_t length, acip_settings_header_t *header, const char **json, size_t *json_length)
void acip_render_settings_options(const acip_render_settings_t *state, options_t *values)
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
ASCII Palette Management for Video-to-ASCII Conversion.
uint64_t options_revision(void)
Definition rcu.c:876
asciichat_error_t options_prepare_json(const options_t *current, const char *json, size_t length, options_t *candidate, bool remote_only)
Definition rcu.c:987
asciichat_error_t options_read_snapshot(options_snapshot_t *snapshot)
Definition rcu.c:865
#define FRAME_RENDER_MAX_BYTES
asciichat_error_t acip_send_pong(acip_transport_t *transport)
Send pong packet.
Definition send.c:395
ACIP shared/bidirectional packet sending functions.
asciichat_error_t(* session_protocol_send_settings_fn)(const options_t *values, uint32_t request, void *context)
🔤 String Manipulation and Shell Escaping Utilities
Client-side packet handler callbacks.
Definition handlers.h:51
void(* on_error)(const error_packet_t *header, const char *message, void *ctx)
Called when error message received.
Definition handlers.h:71
void(* on_ping)(void *ctx)
Called when ping received (should send pong).
Definition handlers.h:77
void * app_ctx
Application context (passed to all callbacks).
Definition handlers.h:113
void(* on_server_state)(const server_state_packet_t *state, void *ctx)
Called when server state update received.
Definition handlers.h:68
acip render settings keeps the values that move through the subsystem: caps, width,...
acip settings header keeps the values that move through the subsystem: schema, request,...
The wire metadata for one rendered ASCII frame. width and height describe the terminal grid,...
Definition packet.h:902
When code needs to work with asciichat errno snapshot, it keeps total, copied here instead of scatter...
The metadata prefix for a batch of encoded audio samples. batch_count, total_samples,...
Definition packet.h:960
Error packet structure carrying error code and textual description.
Definition packet.h:767
bool snapshot_mode
Snapshot mode (one frame and exit).
Definition options.h:1149
terminal_color_mode_t color_mode
Color mode (auto/none/16/256/truecolor).
Definition options.h:1092
int force_utf8
UTF-8 support setting (auto/true/false).
Definition options.h:1097
bool matrix_rain
Matrix digital rain effect (false = disabled).
Definition options.h:1152
bool auto_height
Auto-detect height from terminal.
Definition options.h:999
char palette_custom[256]
Custom palette characters.
Definition options.h:1230
bool palette_custom_set
True if custom palette was set.
Definition options.h:1231
terminal_render_mode_t render_mode
Render mode (foreground/background/half-block).
Definition options.h:1095
color_filter_t color_filter
Monochromatic color filter (none/black/white/green/etc).
Definition options.h:1093
int fps
Target framerate (0 = auto, 1-144).
Definition options.h:1099
palette_type_t palette_type
Selected palette type.
Definition options.h:1229
bool auto_width
Auto-detect width from terminal.
Definition options.h:998
Server state packet structure.
Definition packet.h:746
asciichat_error_t(* on_samples)(const float *samples, size_t count, void *context)
asciichat_error_t(* decode_opus)(const void *data, size_t length, float *samples, size_t capacity, size_t *decoded, void *context)
asciichat_error_t(* on_frame)(const ascii_frame_packet_t *header, const char *text, void *context)
void(* on_audio_packet)(size_t length, void *context)
session protocol media keeps the values that move through the subsystem: context, terminal_output,...
acip_render_settings_t effective_render_settings
session_protocol_media_t media
acip_client_callbacks_t control
Terminal capabilities packet structure (Packet Type 5).
Definition packet.h:1074
uint32_t codec_capabilities_video
Video codec capabilities bitmask (VIDEO_CODEC_CAP_*).
Definition packet.h:1106
uint32_t capabilities
Terminal capabilities bitmask (TERM_CAP_* flags).
Definition packet.h:1076
uint32_t color_level
Color level enum value (terminal_color_mode_t).
Definition packet.h:1078
uint32_t color_count
Actual color count (16, 256, or 16777216).
Definition packet.h:1080
uint16_t height
Terminal height in characters.
Definition packet.h:1086
uint8_t wants_padding
Whether client wants frame padding (1=padding enabled, 0=no padding for snapshot/piped modes).
Definition packet.h:1104
uint32_t render_mode
Render mode enum value (foreground/background/half-block).
Definition packet.h:1082
uint16_t width
Terminal width in characters.
Definition packet.h:1084
uint8_t desired_fps
Client's desired frame rate (1-144 FPS).
Definition packet.h:1100
uint8_t detection_reliable
Detection reliability flag (1=reliable detection, 0=best guess).
Definition packet.h:1092
uint32_t codec_capabilities_audio
Audio codec capabilities bitmask (AUDIO_CODEC_CAP_*).
Definition packet.h:1108
char palette_custom[64]
Custom palette characters (if palette_type == PALETTE_CUSTOM).
Definition packet.h:1098
char colorterm[32]
$COLORTERM environment variable value (for debugging)
Definition packet.h:1090
uint32_t palette_type
Palette type enum value (palette_type_t).
Definition packet.h:1096
uint8_t color_filter
Monochromatic color filter enum value (color_filter_t).
Definition packet.h:1102
uint32_t utf8_support
UTF-8 support flag (0=no UTF-8, 1=UTF-8 supported).
Definition packet.h:1094
char term_type[32]
$TERM environment variable value (for debugging)
Definition packet.h:1088
⏱️ High-precision timing utilities using sokol_time.h and uthash
bool ui_controller_is_presenting(void)
terminal_size_t ui_controller_size(void)