29#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
30#define mkdir(path, mode) _mkdir(path)
47#define CONFIG_WARN(fmt, ...) \
49 (void)fprintf(stderr, "WARNING: Config file: " fmt "\n", ##__VA_ARGS__); \
50 (void)fflush(stderr); \
59#define CONFIG_DEBUG(fmt, ...) \
63 log_debug(fmt, ##__VA_ARGS__); \
78static bool config_address_set =
false;
80static bool config_address6_set =
false;
82static bool config_port_set =
false;
84static bool config_width_set =
false;
86static bool config_height_set =
false;
88static bool config_webcam_index_set =
false;
90static bool config_webcam_flip_set =
false;
92static bool config_color_mode_set =
false;
94static bool config_render_mode_set =
false;
96static bool config_palette_set =
false;
98static bool config_palette_chars_set =
false;
100static bool config_audio_enabled_set =
false;
102static bool config_microphone_index_set =
false;
104static bool config_speakers_index_set =
false;
106static bool config_stretch_set =
false;
108static bool config_quiet_set =
false;
110static bool config_snapshot_mode_set =
false;
112static bool config_mirror_mode_set =
false;
114static bool config_snapshot_delay_set =
false;
116static bool config_log_file_set =
false;
118static bool config_encrypt_enabled_set =
false;
120static bool config_encrypt_key_set =
false;
122static bool config_password_set =
false;
124static bool config_encrypt_keyfile_set =
false;
126static bool config_no_encrypt_set =
false;
128static bool config_server_key_set =
false;
130static bool config_client_keys_set =
false;
150static const char *get_toml_string(toml_datum_t datum) {
151 if (datum.type == TOML_STRING) {
188static void apply_network_config(toml_datum_t toptab,
bool is_client,
options_t *opts) {
191 toml_datum_t address = toml_seek(toptab,
"client.address");
192 const char *address_str = get_toml_string(address);
193 if (!address_str || strlen(address_str) == 0) {
195 toml_datum_t network = toml_seek(toptab,
"network");
196 if (network.type == TOML_TABLE) {
197 address = toml_seek(toptab,
"network.address");
198 address_str = get_toml_string(address);
201 if (address_str && strlen(address_str) > 0 && !config_address_set) {
205 sizeof(error_msg)) == 0) {
207 config_address_set =
true;
209 CONFIG_WARN(
"%s (skipping client address)", error_msg);
214 toml_datum_t server = toml_seek(toptab,
"server");
215 if (server.type == TOML_TABLE) {
217 toml_datum_t bind_ipv4 = toml_get(server,
"bind_ipv4");
218 const char *ipv4_str = get_toml_string(bind_ipv4);
219 if (ipv4_str && strlen(ipv4_str) > 0 && !config_address_set) {
223 sizeof(error_msg)) == 0) {
225 config_address_set =
true;
227 const char *errmsg = (strlen(error_msg) > 0) ? error_msg :
"Invalid IPv4 address";
228 CONFIG_WARN(
"Invalid server.bind_ipv4 value '%s': %s (skipping)", ipv4_str, errmsg);
233 toml_datum_t bind_ipv6 = toml_get(server,
"bind_ipv6");
234 const char *ipv6_str = get_toml_string(bind_ipv6);
235 if (ipv6_str && strlen(ipv6_str) > 0 && !config_address6_set) {
239 sizeof(error_msg)) == 0) {
241 config_address6_set =
true;
243 const char *errmsg = (strlen(error_msg) > 0) ? error_msg :
"Invalid IPv6 address";
244 CONFIG_WARN(
"Invalid server.bind_ipv6 value '%s': %s (skipping)", ipv6_str, errmsg);
250 if (!config_address_set) {
251 toml_datum_t network = toml_seek(toptab,
"network");
252 if (network.type == TOML_TABLE) {
253 toml_datum_t address = toml_get(network,
"address");
254 const char *address_str = get_toml_string(address);
255 if (address_str && strlen(address_str) > 0) {
259 sizeof(error_msg)) == 0) {
261 config_address_set =
true;
263 CONFIG_WARN(
"%s (skipping network.address)", error_msg);
271 toml_datum_t network = toml_seek(toptab,
"network");
272 if (network.type != TOML_TABLE) {
277 toml_datum_t port = toml_seek(toptab,
"network.port");
278 if (port.type == TOML_STRING && !config_port_set) {
279 const char *port_str = port.u.s;
283 config_port_set =
true;
285 CONFIG_WARN(
"%s (skipping network.port)", error_msg);
287 }
else if (port.type == TOML_INT64 && !config_port_set) {
288 int64_t port_val = port.u.int64;
289 if (port_val >= 1 && port_val <= 65535) {
291 config_port_set =
true;
293 CONFIG_WARN(
"Invalid port value %lld (must be 1-65535, skipping network.port)", (
long long)port_val);
325static void apply_client_config(toml_datum_t toptab,
bool is_client,
options_t *opts) {
330 toml_datum_t client = toml_seek(toptab,
"client");
331 if (client.type != TOML_TABLE) {
336 toml_datum_t width = toml_seek(toptab,
"client.width");
337 if (width.type == TOML_INT64 && !config_width_set && !opts->
auto_width) {
338 int64_t width_val = width.u.int64;
340 opts->
width = (
unsigned short int)width_val;
342 config_width_set =
true;
344 }
else if (width.type == TOML_STRING && !config_width_set) {
345 const char *width_str = width.u.s;
349 opts->
width = (
unsigned short int)width_val;
351 config_width_set =
true;
353 CONFIG_WARN(
"%s (skipping client.width)", error_msg);
358 toml_datum_t height = toml_seek(toptab,
"client.height");
359 if (height.type == TOML_INT64 && !config_height_set && !opts->
auto_height) {
360 int64_t height_val = height.u.int64;
361 if (height_val > 0) {
362 opts->
height = (
unsigned short int)height_val;
364 config_height_set =
true;
366 }
else if (height.type == TOML_STRING && !config_height_set) {
367 const char *height_str = height.u.s;
370 if (height_val > 0) {
371 opts->
height = (
unsigned short int)height_val;
373 config_height_set =
true;
375 CONFIG_WARN(
"%s (skipping client.height)", error_msg);
380 toml_datum_t webcam_index = toml_seek(toptab,
"client.webcam_index");
381 if (webcam_index.type == TOML_INT64 && !config_webcam_index_set) {
382 int64_t idx = webcam_index.u.int64;
385 config_webcam_index_set =
true;
387 }
else if (webcam_index.type == TOML_STRING && !config_webcam_index_set) {
388 const char *idx_str = webcam_index.u.s;
393 config_webcam_index_set =
true;
395 CONFIG_WARN(
"%s (skipping client.webcam_index)", error_msg);
400 toml_datum_t webcam_flip = toml_seek(toptab,
"client.webcam_flip");
401 if (webcam_flip.type == TOML_BOOLEAN && !config_webcam_flip_set) {
403 config_webcam_flip_set =
true;
407 toml_datum_t color_mode = toml_seek(toptab,
"client.color_mode");
408 const char *color_mode_str = get_toml_string(color_mode);
409 if (color_mode_str && !config_color_mode_set) {
414 config_color_mode_set =
true;
416 CONFIG_WARN(
"%s (skipping client.color_mode)", error_msg);
421 toml_datum_t render_mode = toml_seek(toptab,
"client.render_mode");
422 const char *render_mode_str = get_toml_string(render_mode);
423 if (render_mode_str && !config_render_mode_set) {
428 config_render_mode_set =
true;
430 CONFIG_WARN(
"%s (skipping client.render_mode)", error_msg);
435 toml_datum_t fps = toml_seek(toptab,
"client.fps");
437 if (fps.type == TOML_INT64) {
438 int64_t fps_val = fps.u.int64;
439 if (fps_val >= 1 && fps_val <= 144) {
442 CONFIG_WARN(
"Invalid FPS value %lld (must be 1-144, skipping client.fps)", (
long long)fps_val);
444 }
else if (fps.type == TOML_STRING) {
445 const char *fps_str = fps.u.s;
451 CONFIG_WARN(
"%s (skipping client.fps)", error_msg);
456 toml_datum_t stretch = toml_seek(toptab,
"client.stretch");
457 if (stretch.type == TOML_BOOLEAN && !config_stretch_set) {
458 opts->
stretch = stretch.u.boolean ? 1 : 0;
459 config_stretch_set =
true;
463 toml_datum_t quiet = toml_seek(toptab,
"client.quiet");
464 if (quiet.type == TOML_BOOLEAN && !config_quiet_set) {
465 opts->
quiet = quiet.u.boolean ? 1 : 0;
466 config_quiet_set =
true;
470 toml_datum_t snapshot_mode = toml_seek(toptab,
"client.snapshot_mode");
471 if (snapshot_mode.type == TOML_BOOLEAN && !config_snapshot_mode_set) {
473 config_snapshot_mode_set =
true;
477 toml_datum_t snapshot_delay = toml_seek(toptab,
"client.snapshot_delay");
478 if (snapshot_delay.type == TOML_FP64 && !config_snapshot_delay_set) {
479 double delay = snapshot_delay.u.fp64;
482 config_snapshot_delay_set =
true;
484 CONFIG_WARN(
"Invalid snapshot_delay value %.2f (must be non-negative, skipping)", delay);
486 }
else if (snapshot_delay.type == TOML_STRING && !config_snapshot_delay_set) {
487 const char *delay_str = snapshot_delay.u.s;
492 config_snapshot_delay_set =
true;
494 CONFIG_WARN(
"%s (skipping client.snapshot_delay)", error_msg);
517static void apply_audio_config(toml_datum_t toptab,
bool is_client,
options_t *opts) {
522 toml_datum_t audio = toml_seek(toptab,
"audio");
523 if (audio.type != TOML_TABLE) {
528 toml_datum_t audio_enabled = toml_seek(toptab,
"audio.enabled");
529 if (audio_enabled.type == TOML_BOOLEAN && !config_audio_enabled_set) {
531 config_audio_enabled_set =
true;
535 toml_datum_t microphone_index = toml_seek(toptab,
"audio.microphone_index");
536 if (microphone_index.type == TOML_INT64 && !config_microphone_index_set) {
537 int64_t mic_idx = microphone_index.u.int64;
540 config_microphone_index_set =
true;
542 }
else if (microphone_index.type == TOML_STRING && !config_microphone_index_set) {
543 const char *mic_str = microphone_index.u.s;
546 if (mic_idx != INT_MIN) {
548 config_microphone_index_set =
true;
550 CONFIG_WARN(
"%s (skipping audio.microphone_index)", error_msg);
555 toml_datum_t speakers_index = toml_seek(toptab,
"audio.speakers_index");
556 if (speakers_index.type == TOML_INT64 && !config_speakers_index_set) {
557 int64_t spk_idx = speakers_index.u.int64;
560 config_speakers_index_set =
true;
562 }
else if (speakers_index.type == TOML_STRING && !config_speakers_index_set) {
563 const char *spk_str = speakers_index.u.s;
566 if (spk_idx != INT_MIN) {
568 config_speakers_index_set =
true;
570 CONFIG_WARN(
"%s (skipping audio.speakers_index)", error_msg);
589static void apply_palette_config_from_toml(toml_datum_t toptab,
options_t *opts) {
590 toml_datum_t palette = toml_seek(toptab,
"palette");
591 if (palette.type != TOML_TABLE) {
596 toml_datum_t palette_type = toml_seek(toptab,
"palette.type");
597 const char *palette_type_str = get_toml_string(palette_type);
598 if (palette_type_str && !config_palette_set) {
603 config_palette_set =
true;
605 CONFIG_WARN(
"%s (skipping palette.type)", error_msg);
610 toml_datum_t palette_chars = toml_seek(toptab,
"palette.chars");
611 const char *palette_chars_str = get_toml_string(palette_chars);
612 if (palette_chars_str && !config_palette_chars_set) {
618 config_palette_chars_set =
true;
620 CONFIG_WARN(
"Invalid palette.chars: too long (%zu chars, max %zu, skipping)", strlen(palette_chars_str),
649 toml_datum_t crypto = toml_seek(toptab,
"crypto");
650 if (crypto.type != TOML_TABLE) {
655 toml_datum_t encrypt_enabled = toml_seek(toptab,
"crypto.encrypt_enabled");
656 if (encrypt_enabled.type == TOML_BOOLEAN && !config_encrypt_enabled_set) {
658 config_encrypt_enabled_set =
true;
662 toml_datum_t key = toml_seek(toptab,
"crypto.key");
663 const char *key_str = get_toml_string(key);
664 if (key_str && strlen(key_str) > 0 && !config_encrypt_key_set) {
666 char *normalized_key = NULL;
678 config_encrypt_key_set =
true;
683 toml_datum_t password = toml_seek(toptab,
"crypto.password");
684 const char *password_str = get_toml_string(password);
685 if (password_str && strlen(password_str) > 0 && !config_password_set) {
688 CONFIG_WARN(
"Password stored in config file is insecure! Use CLI --password instead.");
691 config_password_set =
true;
693 CONFIG_WARN(
"%s (skipping crypto.password)", error_msg);
698 toml_datum_t keyfile = toml_seek(toptab,
"crypto.keyfile");
699 const char *keyfile_str = get_toml_string(keyfile);
700 if (keyfile_str && strlen(keyfile_str) > 0 && !config_encrypt_keyfile_set) {
702 char *normalized_keyfile = NULL;
707 return keyfile_result;
715 config_encrypt_keyfile_set =
true;
719 toml_datum_t no_encrypt = toml_seek(toptab,
"crypto.no_encrypt");
720 if (no_encrypt.type == TOML_BOOLEAN && !config_no_encrypt_set) {
721 if (no_encrypt.u.boolean) {
724 config_no_encrypt_set =
true;
730 toml_datum_t server_key = toml_seek(toptab,
"crypto.server_key");
731 const char *server_key_str = get_toml_string(server_key);
732 if (server_key_str && strlen(server_key_str) > 0 && !config_server_key_set) {
734 char *normalized_server_key = NULL;
739 return server_key_result;
746 config_server_key_set =
true;
752 toml_datum_t client_keys = toml_seek(toptab,
"crypto.client_keys");
753 const char *client_keys_str = get_toml_string(client_keys);
754 if (client_keys_str && strlen(client_keys_str) > 0 && !config_client_keys_set) {
756 char *normalized_client_keys = NULL;
761 return client_keys_result;
768 config_client_keys_set =
true;
790 toml_datum_t
log_file = toml_seek(toptab,
"log_file");
791 if (
log_file.type == TOML_UNKNOWN) {
792 log_file = toml_seek(toptab,
"logging.log_file");
795 const char *log_file_str = get_toml_string(
log_file);
796 if (log_file_str && strlen(log_file_str) > 0 && !config_log_file_set) {
797 char *normalized_log = NULL;
805 config_log_file_set =
true;
843 char *config_path_expanded = NULL;
849 if (!config_path_expanded) {
858 size_t len = strlen(config_dir) + strlen(
"config.toml") + 1;
860 if (config_path_expanded) {
862 safe_snprintf(config_path_expanded, len,
"%sconfig.toml", config_dir);
864 safe_snprintf(config_path_expanded, len,
"%sconfig.toml", config_dir);
870 if (!config_path_expanded) {
871 config_path_expanded =
expand_path(
"~/.ascii-chat/config.toml");
875 if (!config_path_expanded) {
882 char *validated_config_path = NULL;
886 return validate_result;
889 config_path_expanded = validated_config_path;
892 const char *display_path = config_path ? config_path : config_path_expanded;
896 if (stat(config_path_expanded, &st) != 0) {
905 if (!S_ISREG(st.st_mode)) {
909 CONFIG_WARN(
"Config file exists but is not a regular file: '%s' (skipping)", display_path);
914 toml_result_t result = toml_parse_file_ex(config_path_expanded);
915 defer(toml_free(result));
919 const char *errmsg = (strlen(result.errmsg) > 0) ? result.errmsg :
"Unknown parse error";
925 char error_buffer[512];
926 safe_snprintf(error_buffer,
sizeof(error_buffer),
"Failed to parse config file '%s': %s", display_path, errmsg);
929 CONFIG_WARN(
"Failed to parse config file '%s': %s (skipping)", display_path, errmsg);
934 apply_network_config(result.toptab, is_client, opts);
935 apply_client_config(result.toptab, is_client, opts);
936 apply_audio_config(result.toptab, is_client, opts);
937 apply_palette_config_from_toml(result.toptab, opts);
938 asciichat_error_t crypto_result = apply_crypto_config(result.toptab, is_client, opts);
940 return crypto_result;
948 config_address_set =
false;
949 config_address6_set =
false;
950 config_port_set =
false;
951 config_width_set =
false;
952 config_height_set =
false;
953 config_webcam_index_set =
false;
954 config_webcam_flip_set =
false;
955 config_color_mode_set =
false;
956 config_render_mode_set =
false;
957 config_palette_set =
false;
958 config_palette_chars_set =
false;
959 config_audio_enabled_set =
false;
960 config_microphone_index_set =
false;
961 config_speakers_index_set =
false;
962 config_stretch_set =
false;
963 config_quiet_set =
false;
964 config_snapshot_mode_set =
false;
965 config_mirror_mode_set =
false;
966 config_snapshot_delay_set =
false;
967 config_log_file_set =
false;
968 config_encrypt_enabled_set =
false;
969 config_encrypt_key_set =
false;
970 config_password_set =
false;
971 config_encrypt_keyfile_set =
false;
972 config_no_encrypt_set =
false;
973 config_server_key_set =
false;
974 config_client_keys_set =
false;
976 CONFIG_DEBUG(
"Loaded configuration from %s", display_path);
1000 char *config_path_expanded = NULL;
1006 if (!config_path_expanded) {
1015 size_t len = strlen(config_dir) + strlen(
"config.toml") + 1;
1017 if (config_path_expanded) {
1019 safe_snprintf(config_path_expanded, len,
"%sconfig.toml", config_dir);
1021 safe_snprintf(config_path_expanded, len,
"%sconfig.toml", config_dir);
1027 if (!config_path_expanded) {
1028 config_path_expanded =
expand_path(
"~/.ascii-chat/config.toml");
1032 if (!config_path_expanded) {
1036 char *validated_config_path = NULL;
1041 return validate_result;
1044 config_path_expanded = validated_config_path;
1048 if (stat(config_path_expanded, &st) == 0) {
1069 char *last_sep = strrchr(dir_path,
PATH_DELIM);
1076 int mkdir_result = _mkdir(dir_path);
1081 if (mkdir_result != 0 &&
errno != EEXIST) {
1084 struct stat test_st;
1085 if (stat(dir_path, &test_st) != 0) {
1104 (void)fprintf(f,
"# ascii-chat configuration file\n");
1105 (void)fprintf(f,
"# Generated by ascii-chat v%d.%d.%d-%s\n", ASCII_CHAT_VERSION_MAJOR, ASCII_CHAT_VERSION_MINOR,
1106 ASCII_CHAT_VERSION_PATCH, ASCII_CHAT_GIT_VERSION);
1107 (void)fprintf(f,
"#\n");
1108 (void)fprintf(f,
"# If you upgrade ascii-chat and this version comment changes, you may need to\n");
1109 (void)fprintf(f,
"# delete and regenerate this file with: ascii-chat --config-create\n");
1110 (void)fprintf(f,
"#\n\n");
1113 (void)fprintf(f,
"[network]\n");
1114 (void)fprintf(f,
"# Port number (1-65535, shared between server and client)\n");
1115 (void)fprintf(f,
"#port = %s\n\n", opts->
port);
1118 (void)fprintf(f,
"[server]\n");
1119 (void)fprintf(f,
"# IPv4 bind address (default: 127.0.0.1)\n");
1120 (void)fprintf(f,
"#bind_ipv4 = \"127.0.0.1\"\n");
1121 (void)fprintf(f,
"# IPv6 bind address (default: ::1 for IPv6-only, or :: for dual-stack)\n");
1122 (void)fprintf(f,
"#bind_ipv6 = \"::1\"\n");
1123 (void)fprintf(f,
"# Legacy bind address (fallback if bind_ipv4/bind_ipv6 not set)\n");
1124 (void)fprintf(f,
"#address = \"::\"\n\n");
1127 (void)fprintf(f,
"[client]\n");
1128 (void)fprintf(f,
"# Server address to connect to\n");
1129 (void)fprintf(f,
"#address = \"%s\"\n", opts->
address);
1130 (void)fprintf(f,
"# Alternative: set via network.address (legacy)\n");
1131 (void)fprintf(f,
"#network.address = \"%s\"\n\n", opts->
address);
1132 (void)fprintf(f,
"# Terminal width in characters (0 = auto-detect)\n");
1133 (void)fprintf(f,
"#width = %hu\n", opts->
width);
1134 (void)fprintf(f,
"# Terminal height in characters (0 = auto-detect)\n");
1135 (void)fprintf(f,
"#height = %hu\n", opts->
height);
1136 (void)fprintf(f,
"# Webcam device index (0 = first webcam)\n");
1137 (void)fprintf(f,
"#webcam_index = %hu\n", opts->
webcam_index);
1138 (void)fprintf(f,
"# Flip webcam image horizontally\n");
1139 (void)fprintf(f,
"#webcam_flip = %s\n", opts->
webcam_flip ?
"true" :
"false");
1140 (void)fprintf(f,
"# Color mode: \"none\", \"16\", \"256\", \"truecolor\" (or \"auto\" for auto-detect)\n");
1141 (void)fprintf(f,
"#color_mode = \"auto\"\n");
1142 (void)fprintf(f,
"# Render mode: \"foreground\", \"background\", \"half-block\"\n");
1143 (void)fprintf(f,
"#render_mode = \"foreground\"\n");
1144 (void)fprintf(f,
"# Frames per second (1-144, default: 30 for Windows, 60 for Unix)\n");
1146 (void)fprintf(f,
"#fps = 30\n");
1148 (void)fprintf(f,
"#fps = 60\n");
1150 (void)fprintf(f,
"# Stretch video to terminal size (without preserving aspect ratio)\n");
1151 (void)fprintf(f,
"#stretch = %s\n", opts->
stretch ?
"true" :
"false");
1152 (void)fprintf(f,
"# Quiet mode (disable console logging)\n");
1153 (void)fprintf(f,
"#quiet = %s\n", opts->
quiet ?
"true" :
"false");
1154 (void)fprintf(f,
"# Snapshot mode (capture one frame and exit)\n");
1155 (void)fprintf(f,
"#snapshot_mode = %s\n", opts->
snapshot_mode ?
"true" :
"false");
1156 (void)fprintf(f,
"# Snapshot delay in seconds (for webcam warmup)\n");
1157 (void)fprintf(f,
"#snapshot_delay = %.1f\n", (
double)opts->
snapshot_delay);
1158 (void)fprintf(f,
"# Use test pattern instead of real webcam\n");
1159 (void)fprintf(f,
"#test_pattern = %s\n", opts->
test_pattern ?
"true" :
"false");
1160 (void)fprintf(f,
"# Show terminal capabilities and exit\n");
1161 (void)fprintf(f,
"#show_capabilities = %s\n", opts->
show_capabilities ?
"true" :
"false");
1162 (void)fprintf(f,
"# Force UTF-8 support\n");
1163 (void)fprintf(f,
"#force_utf8 = %s\n\n", opts->
force_utf8 ?
"true" :
"false");
1166 (void)fprintf(f,
"[audio]\n");
1167 (void)fprintf(f,
"# Enable audio streaming\n");
1168 (void)fprintf(f,
"#enabled = %s\n", opts->
audio_enabled ?
"true" :
"false");
1169 (void)fprintf(f,
"# Microphone device index (-1 = use default)\n");
1171 (void)fprintf(f,
"# Speakers device index (-1 = use default)\n");
1172 (void)fprintf(f,
"#speakers_index = %d\n\n", opts->
speakers_index);
1175 (void)fprintf(f,
"[palette]\n");
1176 (void)fprintf(f,
"# Palette type: \"blocks\", \"half-blocks\", \"chars\", \"custom\"\n");
1177 (void)fprintf(f,
"#type = \"half-blocks\"\n");
1178 (void)fprintf(f,
"# Custom palette characters (only used if type = \"custom\")\n");
1179 (void)fprintf(f,
"#chars = \" ...',;:clodxkO0KXNWM\"\n\n");
1182 (void)fprintf(f,
"[crypto]\n");
1183 (void)fprintf(f,
"# Enable encryption\n");
1184 (void)fprintf(f,
"#encrypt_enabled = %s\n", opts->
encrypt_enabled ?
"true" :
"false");
1185 (void)fprintf(f,
"# Encryption key identifier (e.g., \"gpg:keyid\" or \"github:username\")\n");
1186 (void)fprintf(f,
"#key = \"%s\"\n", opts->
encrypt_key);
1187 (void)fprintf(f,
"# Password for encryption (WARNING: storing passwords in config files is insecure!)\n");
1188 (void)fprintf(f,
"# Use CLI --password or environment variables instead.\n");
1189 (void)fprintf(f,
"#password = \"%s\"\n", opts->
password);
1190 (void)fprintf(f,
"# Key file path\n");
1192 (void)fprintf(f,
"# Disable encryption (opt-out)\n");
1193 (void)fprintf(f,
"#no_encrypt = %s\n", opts->
no_encrypt ?
"true" :
"false");
1194 (void)fprintf(f,
"# Server public key (client only)\n");
1195 (void)fprintf(f,
"#server_key = \"%s\"\n", opts->
server_key);
1196 (void)fprintf(f,
"# Client keys directory (server only)\n");
1197 (void)fprintf(f,
"#client_keys = \"%s\"\n\n", opts->
client_keys);
1200 (void)fprintf(f,
"[logging]\n");
1201 (void)fprintf(f,
"# Log file path (empty string = no file logging)\n");
1202 (void)fprintf(f,
"#log_file = \"%s\"\n", opts->
log_file);
1211#ifndef ASCIICHAT_INSTALL_PREFIX
1212#define ASCIICHAT_INSTALL_PREFIX "/usr"
1216 char system_config_path[1024];
1218 SAFE_SNPRINTF(system_config_path,
sizeof(system_config_path),
"%s\\etc\\ascii-chat\\config.toml",
1221 SAFE_SNPRINTF(system_config_path,
sizeof(system_config_path),
"%s/etc/ascii-chat/config.toml",
1226 CONFIG_DEBUG(
"Attempting to load system config from: %s", system_config_path);
1231 CONFIG_DEBUG(
"System config not loaded (this is normal if file doesn't exist)");
1238 CONFIG_DEBUG(
"Loading user config (strict=%s)", strict ?
"true" :
"false");
#define ASCIICHAT_INSTALL_PREFIX
#define CONFIG_DEBUG(fmt,...)
Print configuration debug message.
#define CONFIG_WARN(fmt,...)
Print configuration warning to stderr.
TOML configuration file support for ascii-chat.
Defer macro definition for source-to-source transformation.
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define SAFE_SNPRINTF(buffer, buffer_size,...)
asciichat_error_t config_create_default(const char *config_path, const options_t *opts)
Create default configuration file with all default values.
asciichat_error_t config_load_system_and_user(bool is_client, const char *user_config_path, bool strict, options_t *opts)
Load system config first, then user config (user config overrides system)
asciichat_error_t config_load_and_apply(bool is_client, const char *config_path, bool strict, options_t *opts)
Main function to load configuration from file and apply to global options.
#define defer(action)
Defer a cleanup action until function scope exit.
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
#define CLEAR_ERRNO()
Clear the current error state.
asciichat_error_t
Error and exit codes - unified status values (0-255)
int g_max_fps
Runtime configurable maximum frame rate (can be overridden via environment or command line)
#define log_plain_stderr(...)
Plain logging to stderr with newline.
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
int validate_opt_device_index(const char *value_str, char *error_msg, size_t error_msg_size)
Validate device index (-1 for default, 0+ for specific device)
int validate_opt_color_mode(const char *value_str, char *error_msg, size_t error_msg_size)
Validate color mode string.
int validate_opt_fps(const char *value_str, char *error_msg, size_t error_msg_size)
Validate FPS value (1-144)
int validate_opt_palette(const char *value_str, char *error_msg, size_t error_msg_size)
Validate palette type string.
int validate_opt_positive_int(const char *value_str, char *error_msg, size_t error_msg_size)
Validate positive integer.
int validate_opt_password(const char *value_str, char *error_msg, size_t error_msg_size)
Validate password (8-256 characters)
#define OPTIONS_BUFF_SIZE
Buffer size for option string values.
float validate_opt_float_non_negative(const char *value_str, char *error_msg, size_t error_msg_size)
Validate non-negative float value.
int validate_opt_non_negative_int(const char *value_str, char *error_msg, size_t error_msg_size)
Validate non-negative integer.
int validate_opt_render_mode(const char *value_str, char *error_msg, size_t error_msg_size)
Validate render mode string.
int validate_opt_port(const char *value_str, char *error_msg, size_t error_msg_size)
Validate port number (1-65535)
int validate_opt_ip_address(const char *value_str, char *parsed_address, size_t address_size, bool is_client, char *error_msg, size_t error_msg_size)
Validate IP address or hostname.
palette_type_t
Built-in palette type enumeration.
@ PALETTE_CUSTOM
User-defined via –palette-chars.
bool path_looks_like_path(const char *value)
Determine if a string is likely intended to reference the filesystem.
asciichat_error_t path_validate_user_path(const char *input, path_role_t role, char **normalized_out)
Validate and canonicalize a user-supplied filesystem path.
char * expand_path(const char *path)
Expand path with tilde (~) support.
char * get_config_dir(void)
Get configuration directory path with XDG_CONFIG_HOME support.
📝 Logging API with multiple log levels and terminal output control
Validation functions for options parsing.
⚙️ Command-line options parsing and configuration management for ascii-chat
📂 Path Manipulation Utilities
Cross-platform interactive prompting utilities.
Consolidated options structure.
terminal_color_mode_t color_mode
Color mode (auto/none/16/256/truecolor)
char port[256]
Server port number.
unsigned short int no_encrypt
Disable encryption (opt-out)
char password[256]
Password string.
int microphone_index
Microphone device index (-1 = default)
unsigned short int force_utf8
Force UTF-8 support.
unsigned short int quiet
Quiet mode (suppress logs)
unsigned short int encrypt_enabled
Enable encryption.
unsigned short int webcam_index
Webcam device index (0 = first)
unsigned short int width
Terminal width in characters.
bool auto_height
Auto-detect height from terminal.
char palette_custom[256]
Custom palette characters.
unsigned short int height
Terminal height in characters.
bool palette_custom_set
True if custom palette was set.
render_mode_t render_mode
Render mode (foreground/background/half-block)
char server_key[256]
Expected server public key (client)
unsigned short int show_capabilities
Show terminal capabilities and exit.
char encrypt_keyfile[256]
Alternative key file path.
char address6[256]
IPv6 bind address (server only)
int speakers_index
Speakers device index (-1 = default)
double snapshot_delay
Snapshot delay in seconds.
unsigned short int audio_enabled
Enable audio streaming.
bool test_pattern
Use test pattern instead of webcam.
bool webcam_flip
Flip webcam image horizontally.
unsigned short int snapshot_mode
Snapshot mode (one frame and exit)
palette_type_t palette_type
Selected palette type.
unsigned short int stretch
Allow aspect ratio distortion.
char encrypt_key[256]
SSH/GPG key file path.
char log_file[256]
Log file path.
char address[256]
Server address (client) or bind address (server)
bool auto_width
Auto-detect width from terminal.
char client_keys[256]
Allowed client keys (server)
Cross-platform system functions interface for ascii-chat.
🖥️ Cross-platform terminal interface for ascii-chat
Common SIMD utilities and structures.