13#include <ascii-chat/options/actions.h>
17#include <ascii-chat/asciichat_errno.h>
18#include <ascii-chat/common.h>
19#include <ascii-chat/log/logging.h>
20#include <ascii-chat/network/update_checker.h>
21#include <ascii-chat/options/manpage.h>
22#include <ascii-chat/options/presets.h>
23#include <ascii-chat/options/config.h>
24#include <ascii-chat/options/completions/completions.h>
25#include <ascii-chat/options/schema.h>
26#include <ascii-chat/platform/terminal.h>
27#include <ascii-chat/platform/question.h>
28#include <ascii-chat/platform/stat.h>
29#include <ascii-chat/platform/util.h>
30#include <ascii-chat/version.h>
31#include <ascii-chat/video/webcam/webcam.h>
32#include <ascii-chat/audio/audio.h>
33#include <ascii-chat/util/string.h>
34#include <ascii-chat/util/path.h>
51static _Noreturn
void action_exit(
int code) {
70} g_deferred_action_state = {
71 .action = ACTION_NONE,
78 if (g_deferred_action_state.has_action) {
82 g_deferred_action_state.action =
action;
83 g_deferred_action_state.has_action =
true;
87 memcpy(&g_deferred_action_state.args,
args,
sizeof(action_args_t));
89 memset(&g_deferred_action_state.args, 0,
sizeof(action_args_t));
94 return g_deferred_action_state.action;
98 if (!g_deferred_action_state.has_action) {
101 return &g_deferred_action_state.args;
119static void execute_list_webcams(
void) {
120 webcam_device_info_t *devices = NULL;
121 unsigned int device_count = 0;
124 if (result != ASCIICHAT_OK) {
125 log_plain_stderr(
"Error: Failed to enumerate webcam devices");
126 action_exit(ERROR_WEBCAM);
129 if (device_count == 0) {
130 log_plain_stderr(
"%s",
colored_string(LOG_COLOR_ERROR,
"No webcam devices found."));
132 log_plain_stderr(
"%s",
colored_string(LOG_COLOR_DEV,
"Available Webcam Devices:"));
133 for (
unsigned int i = 0; i < device_count; i++) {
135 safe_snprintf(index_str,
sizeof(index_str),
"%u", devices[i].index);
136 log_plain_stderr(
" %s %s",
colored_string(LOG_COLOR_GREY, index_str), devices[i].name);
159static void execute_list_microphones(
void) {
160 audio_device_info_t *devices = NULL;
161 unsigned int device_count = 0;
164 if (result != ASCIICHAT_OK) {
165 log_plain_stderr(
"Error: Failed to enumerate audio input devices");
166 action_exit(ERROR_AUDIO);
169 if (device_count == 0) {
170 log_plain_stderr(
"%s",
colored_string(LOG_COLOR_ERROR,
"No microphone devices found."));
172 log_plain_stderr(
"%s",
colored_string(LOG_COLOR_DEV,
"Available Microphone Devices:"));
173 for (
unsigned int i = 0; i < device_count; i++) {
175 safe_snprintf(index_str,
sizeof(index_str),
"%d", devices[i].index);
176 char device_line[512];
177 char *line_ptr = device_line;
178 int remaining =
sizeof(device_line);
180 if (devices[i].is_default_input) {
181 size_t len = strlen(device_line);
182 line_ptr = device_line + len;
183 remaining =
sizeof(device_line) - (
int)len;
186 log_plain_stderr(
"%s", device_line);
205static void execute_list_speakers(
void) {
206 audio_device_info_t *devices = NULL;
207 unsigned int device_count = 0;
210 if (result != ASCIICHAT_OK) {
211 log_plain_stderr(
"Error: Failed to enumerate audio output devices");
212 action_exit(ERROR_AUDIO);
215 if (device_count == 0) {
216 log_plain_stderr(
"%s",
colored_string(LOG_COLOR_ERROR,
"No speaker devices found."));
218 log_plain_stderr(
"%s",
colored_string(LOG_COLOR_DEV,
"Available Speaker Devices:"));
219 for (
unsigned int i = 0; i < device_count; i++) {
221 safe_snprintf(index_str,
sizeof(index_str),
"%d", devices[i].index);
222 char device_line[512];
223 char *line_ptr = device_line;
224 int remaining =
sizeof(device_line);
226 if (devices[i].is_default_output) {
227 size_t len = strlen(device_line);
228 line_ptr = device_line + len;
229 remaining =
sizeof(device_line) - (
int)len;
232 log_plain_stderr(
"%s", device_line);
255 caps.color_level = TERM_COLOR_NONE;
256 caps.color_count = 0;
257 caps.capabilities &= ~((uint32_t)(TERM_CAP_COLOR_TRUE | TERM_CAP_COLOR_256 | TERM_CAP_COLOR_16));
261 terminal_size_t size;
263 unsigned short width = size.cols;
264 unsigned short height = size.rows;
266 log_color_t label_color = use_colors ? LOG_COLOR_GREY : LOG_COLOR_GREY;
267 log_color_t string_color = use_colors ? LOG_COLOR_DEBUG : LOG_COLOR_GREY;
268 log_color_t good_color = use_colors ? LOG_COLOR_INFO : LOG_COLOR_GREY;
269 log_color_t bad_color = use_colors ? LOG_COLOR_ERROR : LOG_COLOR_GREY;
270 log_color_t number_color = use_colors ? LOG_COLOR_FATAL : LOG_COLOR_GREY;
272 printf(
"%s\n",
colored_string(LOG_COLOR_WARN,
"Terminal Capabilities:"));
275 snprintf(size_buf,
sizeof(size_buf),
"%ux%u", width, height);
278 const char *color_level_name = terminal_color_level_name(caps.color_level);
279 log_color_t color_level_color = (caps.color_level == TERM_COLOR_NONE) ? bad_color : string_color;
284 snprintf(colors_buf,
sizeof(colors_buf),
"%u", caps.color_count);
287 log_color_t utf8_color = caps.utf8_support ? good_color : bad_color;
289 colored_string(utf8_color, (
char *)(caps.utf8_support ?
"Yes" :
"No")));
291 const char *render_mode_str =
"unknown";
292 if (caps.render_mode == RENDER_MODE_FOREGROUND) {
293 render_mode_str =
"foreground";
294 }
else if (caps.render_mode == RENDER_MODE_BACKGROUND) {
295 render_mode_str =
"background";
296 }
else if (caps.render_mode == RENDER_MODE_HALF_BLOCK) {
297 render_mode_str =
"half-block";
303 colored_string(string_color, (
char *)(strlen(caps.colorterm) ? caps.colorterm :
"(not set)")));
305 log_color_t reliable_color = caps.detection_reliable ? good_color : bad_color;
306 printf(
" %s: %s\n",
colored_string(label_color,
"Detection Reliable"),
307 colored_string(reliable_color, (
char *)(caps.detection_reliable ?
"Yes" :
"No")));
309 char bitmask_buf[64];
310 snprintf(bitmask_buf,
sizeof(bitmask_buf),
"0x%08x", caps.capabilities);
331 printf(
"Checking for updates...\n");
332 update_check_result_t result;
334 if (err != ASCIICHAT_OK) {
335 printf(
"\nFailed to check for updates.\n\n");
338 if (result.update_available) {
339 char notification[1024];
341 printf(
"\n%s\n\n", notification);
343 printf(
"\nYou are already on the latest version: %s (%.8s)\n\n", result.current_version, result.current_sha);
363static void execute_show_capabilities(
void) {
372 caps.color_level = TERM_COLOR_NONE;
373 caps.color_count = 0;
374 caps.capabilities &= ~((uint32_t)(TERM_CAP_COLOR_TRUE | TERM_CAP_COLOR_256 | TERM_CAP_COLOR_16));
379 unsigned short width = opts ? opts->width : 110;
380 unsigned short height = opts ? opts->height : 70;
388 log_color_t label_color = use_colors ? LOG_COLOR_GREY : LOG_COLOR_GREY;
389 log_color_t string_color = use_colors ? LOG_COLOR_DEBUG : LOG_COLOR_GREY;
390 log_color_t good_color = use_colors ? LOG_COLOR_INFO : LOG_COLOR_GREY;
391 log_color_t bad_color = use_colors ? LOG_COLOR_ERROR : LOG_COLOR_GREY;
392 log_color_t number_color = use_colors ? LOG_COLOR_FATAL : LOG_COLOR_GREY;
394 printf(
"%s\n",
colored_string(LOG_COLOR_WARN,
"Terminal Capabilities:"));
397 snprintf(size_buf,
sizeof(size_buf),
"%ux%u", width, height);
401 const char *color_level_name = terminal_color_level_name(caps.color_level);
402 log_color_t color_level_color = (caps.color_level == TERM_COLOR_NONE) ? bad_color : string_color;
407 snprintf(colors_buf,
sizeof(colors_buf),
"%u", caps.color_count);
411 log_color_t utf8_color = caps.utf8_support ? good_color : bad_color;
413 colored_string(utf8_color, (
char *)(caps.utf8_support ?
"Yes" :
"No")));
415 const char *render_mode_str =
"unknown";
416 if (caps.render_mode == RENDER_MODE_FOREGROUND) {
417 render_mode_str =
"foreground";
418 }
else if (caps.render_mode == RENDER_MODE_BACKGROUND) {
419 render_mode_str =
"background";
420 }
else if (caps.render_mode == RENDER_MODE_HALF_BLOCK) {
421 render_mode_str =
"half-block";
427 colored_string(string_color, (
char *)(strlen(caps.colorterm) ? caps.colorterm :
"(not set)")));
430 log_color_t reliable_color = caps.detection_reliable ? good_color : bad_color;
431 printf(
" %s: %s\n",
colored_string(label_color,
"Detection Reliable"),
432 colored_string(reliable_color, (
char *)(caps.detection_reliable ?
"Yes" :
"No")));
434 char bitmask_buf[64];
435 snprintf(bitmask_buf,
sizeof(bitmask_buf),
"0x%08x", caps.capabilities);
450 printf(
"%s %s (%s, %s)\n",
colored_string(LOG_COLOR_WARN,
"ascii-chat"),
455 printf(
"ascii-chat %s (%s, %s)\n", ASCII_CHAT_VERSION_FULL, ASCII_CHAT_BUILD_TYPE, ASCII_CHAT_BUILD_DATE);
466 usage(stdout, MODE_SERVER);
471 usage(stdout, MODE_CLIENT);
476 usage(stdout, MODE_MIRROR);
481 usage(stdout, MODE_DISCOVERY_SERVICE);
486 usage(stdout, MODE_DISCOVERY);
499 log_plain_stderr(
"Error: Failed to get binary options config");
500 action_exit(ERROR_FILE_OPERATION);
504 const char *path_to_use = NULL;
505 if (output_path && strlen(output_path) > 0 && strcmp(output_path,
"-") != 0) {
507 path_to_use = output_path;
517 asciichat_error_t err =
520 if (err != ASCIICHAT_OK) {
521 asciichat_error_context_t err_ctx;
522 if (HAS_ERRNO(&err_ctx)) {
523 log_plain_stderr(
"Error: %s", err_ctx.context_message);
525 log_plain_stderr(
"Error: Failed to generate man page");
527 action_exit(ERROR_FILE_OPERATION);
531 log_plain_stderr(
"Man page written to: %s", path_to_use);
533 log_plain_stderr(
"Man page written to stdout");
546 if (unified_config) {
548 if (schema_build_result != ASCIICHAT_OK) {
550 (void)schema_build_result;
555 const char *config_path = NULL;
558 if (output_path && strlen(output_path) > 0 && strcmp(output_path,
"-") != 0) {
560 config_path = output_path;
568 if (result != ASCIICHAT_OK) {
569 asciichat_error_context_t err_ctx;
570 if (HAS_ERRNO(&err_ctx)) {
571 log_plain_stderr(
"Error creating config: %s", err_ctx.context_message);
573 log_plain_stderr(
"Error: Failed to create config file");
575 action_exit(ERROR_CONFIG);
579 log_plain_stderr(
"Created default config file at: %s", config_path);
581 log_plain_stderr(
"Config written to stdout");
591 if (!shell_name || strlen(shell_name) == 0) {
592 log_plain_stderr(
"Error: --completions requires shell name (bash, fish, zsh, powershell)");
593 action_exit(ERROR_USAGE);
597 if (format == COMPLETION_FORMAT_UNKNOWN) {
598 log_plain_stderr(
"Error: Unknown shell '%s' (supported: bash, fish, zsh, powershell)", shell_name);
599 action_exit(ERROR_USAGE);
602 FILE *output = stdout;
603 bool should_close =
false;
606 if (output_path && strlen(output_path) > 0 && strcmp(output_path,
"-") != 0) {
609 if (stat(output_path, &st) == 0) {
611 log_plain(
"Completions file already exists: %s", output_path);
615 log_plain(
"Completions generation cancelled.");
619 log_plain(
"Overwriting existing completions file...");
624 log_plain_stderr(
"Error: Failed to open %s for writing", output_path);
625 action_exit(ERROR_FILE_OPERATION);
636 if (result != ASCIICHAT_OK) {
638 action_exit(ERROR_USAGE);
651static void execute_check_update(
void) {
652 printf(
"Checking for updates...\n");
654 update_check_result_t result;
657 if (err != ASCIICHAT_OK) {
658 asciichat_error_context_t ctx;
659 if (HAS_ERRNO(&ctx)) {
660 fprintf(stderr,
"Update check failed: %s\n", ctx.context_message);
666 if (result.update_available) {
667 char notification[1024];
669 printf(
"\n%s\n\n", notification);
671 printf(
"\nYou are already on the latest version: %s (%.8s)\n\n", result.current_version, result.current_sha);
689 case ACTION_LIST_WEBCAMS:
690 execute_list_webcams();
693 case ACTION_LIST_MICROPHONES:
694 execute_list_microphones();
697 case ACTION_LIST_SPEAKERS:
698 execute_list_speakers();
701 case ACTION_SHOW_CAPABILITIES:
702 execute_show_capabilities();
705 case ACTION_CHECK_UPDATE:
706 execute_check_update();
710 log_warn(
"Unknown deferred action: %d",
action);
completion_format_t completions_parse_shell_name(const char *shell_name)
asciichat_error_t completions_generate_for_shell(completion_format_t format, FILE *output)
const char * completions_get_shell_name(completion_format_t format)
asciichat_error_t config_create_default(const char *config_path)
void audio_free_device_list(audio_device_info_t *devices)
asciichat_error_t audio_list_input_devices(audio_device_info_t **out_devices, unsigned int *out_count)
asciichat_error_t audio_list_output_devices(audio_device_info_t **out_devices, unsigned int *out_count)
void usage(FILE *desc, asciichat_mode_t mode)
asciichat_error_t options_config_generate_manpage_merged(const options_config_t *config, const char *program_name, const char *mode_name, const char *output_path, const char *brief_description)
void action_check_update_immediate(void)
Execute update check immediately (for early binary-level execution)
const action_args_t * actions_get_args(void)
void action_check_update(void)
deferred_action_t actions_get_deferred(void)
void actions_defer(deferred_action_t action, const action_args_t *args)
void action_help_acds(void)
void action_show_capabilities(void)
void action_help_server(void)
void action_help_discovery(void)
void action_create_manpage(const char *output_path)
void action_completions(const char *shell_name, const char *output_path)
void action_list_microphones(void)
void action_show_version(void)
void action_help_mirror(void)
void action_create_config(const char *output_path)
void action_list_webcams(void)
void action_list_speakers(void)
void action_help_client(void)
void actions_execute_deferred(void)
void action_show_capabilities_immediate(void)
Execute show capabilities immediately (for early binary-level execution)
options_config_t * options_preset_unified(const char *program_name, const char *description)
Build unified options config with ALL options (binary + all modes)
const options_t * options_get(void)
asciichat_error_t config_schema_build_from_configs(const options_config_t **configs, size_t num_configs)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
asciichat_error_t update_check_perform(update_check_result_t *result)
void update_check_format_notification(const update_check_result_t *result, char *buffer, size_t buffer_size)
const char * colored_string(log_color_t color, const char *text)
bool platform_prompt_yes_no(const char *question, bool default_yes)
void webcam_free_device_list(webcam_device_info_t *devices)
asciichat_error_t webcam_list_devices(webcam_device_info_t **out_devices, unsigned int *out_count)
FILE * platform_fopen(const char *filename, const char *mode)