diff --git a/Makefile b/Makefile index 025f4d0..28554f7 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ CC = gcc -CFLAGS = -Wall -Wextra -std=c99 -Iinclude +# Updated include paths for modular headers +CFLAGS = -Wall -Wextra -std=c99 -Iinclude -Iinclude/app -Iinclude/audio -Iinclude/common -Iinclude/sound -Iinclude/config PREFIX ?= /usr # Pass PACKAGE_PREFIX macro for config.h @@ -13,10 +14,10 @@ KeyVibe_TARGET = keyvibe SOUND_TARGET = audio KEYBOARD_TARGET = input -# Sources -KeyVibe_SOURCE = src/main.c src/utils.c src/config_io.c src/soundpacks.c -SOUND_SOURCE = src/audio.c src/utils.c -KEYBOARD_SOURCE = src/input.c src/utils.c +# Sources (reorganized) +KeyVibe_SOURCE = src/main.c src/common/utils.c src/config.c src/soundpacks.c src/app/process.c src/app/watch.c src/cli.c src/app/reload.c +SOUND_SOURCE = src/audio/main.c src/audio/config.c src/audio/playback.c src/common/utils.c +KEYBOARD_SOURCE = src/input.c src/common/utils.c # Install paths BINDIR = $(PREFIX)/bin diff --git a/include/app/cli.h b/include/app/cli.h new file mode 100644 index 0000000..00c8e70 --- /dev/null +++ b/include/app/cli.h @@ -0,0 +1,23 @@ +#ifndef KEYVIBE_CLI_H +#define KEYVIBE_CLI_H + +typedef struct { + char *sound; + char *mouse_sound; + int volume; + int keyboard_volume; + int mouse_volume; + int daemon_flag; + int stop_flag; + int list_flag; + int verbose; + int keyboard_mute; // -1 unchanged, 0 unmute, 1 mute + int mouse_mute; // -1 unchanged, 0 unmute, 1 mute +} CliOptions; + +void print_usage(const char *program_name); +int parse_cli(int argc, char **argv, CliOptions *out); + +#endif // KEYVIBE_CLI_H + + diff --git a/include/app/process.h b/include/app/process.h new file mode 100644 index 0000000..55d91f3 --- /dev/null +++ b/include/app/process.h @@ -0,0 +1,23 @@ +#ifndef KEYVIBE_PROCESS_H +#define KEYVIBE_PROCESS_H + +#include +#include + +extern pid_t keyboard_pid; +extern pid_t sound_pid; +extern char pidfile_path[]; +extern int is_daemon; +extern volatile sig_atomic_t reload_requested; + +void cleanup_processes(int sig); +int require_running_pid(pid_t *out_pid); +void daemonize_self(void); +void stop_children(void); +int start_children(const char *sound_dir, const char *config_path, int volume, + int verbose, int mute, const char *mouse_sound_dir, + const char *mouse_config_path, int mouse_volume, + int keyboard_mute, int mouse_mute); +void handle_sighup(int sig); + +#endif // KEYVIBE_PROCESS_H diff --git a/include/app/reload.h b/include/app/reload.h new file mode 100644 index 0000000..681b366 --- /dev/null +++ b/include/app/reload.h @@ -0,0 +1,15 @@ +#ifndef KEYVIBE_RELOAD_H +#define KEYVIBE_RELOAD_H + +int handle_reload(const char *user_cfg_path, + char *current_sound_name, char *current_mouse_sound_name, + int *current_keyboard_volume, int *current_mouse_volume, + char *current_config_path, char *current_sound_dir, + char *current_mouse_config_path, char *current_mouse_sound_dir, + int current_mute, int current_verbose, + int current_mouse_volume_arg, int current_keyboard_mute, + int current_mouse_mute); + +#endif // KEYVIBE_RELOAD_H + + diff --git a/include/app/watch.h b/include/app/watch.h new file mode 100644 index 0000000..5ff54a5 --- /dev/null +++ b/include/app/watch.h @@ -0,0 +1,6 @@ +#ifndef KEYVIBE_WATCH_H +#define KEYVIBE_WATCH_H + +int start_config_watcher(const char *path); + +#endif // KEYVIBE_WATCH_H diff --git a/include/audio/playback.h b/include/audio/playback.h new file mode 100644 index 0000000..a0cba5c --- /dev/null +++ b/include/audio/playback.h @@ -0,0 +1,10 @@ +#ifndef KEYVIBE_AUDIO_PLAYBACK_H +#define KEYVIBE_AUDIO_PLAYBACK_H + +int init_audio(void); +int parse_keyboard_event(const char *json_line, int *key_code, int *is_pressed); +void play_sound_segment(int key_code, int is_pressed); + +#endif // KEYVIBE_AUDIO_PLAYBACK_H + + diff --git a/include/audio/types.h b/include/audio/types.h new file mode 100644 index 0000000..8e60206 --- /dev/null +++ b/include/audio/types.h @@ -0,0 +1,43 @@ +#ifndef KEYVIBE_AUDIO_TYPES_H +#define KEYVIBE_AUDIO_TYPES_H + +#include + +#define MAX_CONCURRENT_SOUNDS 10 + +typedef struct { + int start_ms; + int duration_ms; +} SoundMapping; + +typedef struct { + char press_file[256]; + char release_file[256]; + char generic_press_files[5][256]; + int num_generic_press_files; + char sound_file[256]; + SoundMapping key_mappings[512]; + struct { + char *press; + char *release; + } multi_key_mappings[512]; + int is_multi; + SF_INFO sf_info; +} SoundPack; + +typedef struct { + int key_code; + int thread_id; + int is_pressed; +} PlaybackData; + +extern SoundPack g_sound_pack; +extern SoundPack g_mouse_sound_pack; +extern float g_volume; +extern float g_mouse_volume; +extern int g_verbose; +extern int g_mute; +extern int g_keyboard_mute; +extern int g_mouse_mute; + +#endif // KEYVIBE_AUDIO_TYPES_H diff --git a/include/mute.h b/include/common/mute.h similarity index 99% rename from include/mute.h rename to include/common/mute.h index cfeecd6..d51cfc8 100644 --- a/include/mute.h +++ b/include/common/mute.h @@ -7,5 +7,3 @@ void write_runtime_keyboard_mute_file(int mute); void write_runtime_mouse_mute_file(int mute); #endif // KEYVIBE_MUTE_H - - diff --git a/include/utils.h b/include/common/utils.h similarity index 94% rename from include/utils.h rename to include/common/utils.h index 65e6471..6d9bd9e 100644 --- a/include/utils.h +++ b/include/common/utils.h @@ -7,7 +7,8 @@ // Returns 1 on success, 0 on truncation/error int safe_snprintf(char *dst, size_t dst_sz, const char *fmt, ...); -// fprintf to stderr with printf-style formatting. Returns 1 for convenient `return errorf(...)` usage. +// fprintf to stderr with printf-style formatting. Returns 1 for convenient +// `return errorf(...)` usage. int errorf(const char *fmt, ...); // Common runtime helpers @@ -24,4 +25,3 @@ void write_runtime_keyboard_mute_file(int mute); void write_runtime_mouse_mute_file(int mute); #endif // KEYVIBE_UTILS_H - diff --git a/include/config.h b/include/config.h index 9c2c9ff..1347bde 100644 --- a/include/config.h +++ b/include/config.h @@ -18,5 +18,3 @@ #define KeyVibe_BIN_DIR PACKAGE_PREFIX "/bin" #endif - - diff --git a/include/soundpacks.h b/include/soundpacks.h index 871b91a..b1115cf 100644 --- a/include/soundpacks.h +++ b/include/soundpacks.h @@ -2,14 +2,18 @@ #define KEYVIBE_SOUNDPACKS_H #include -int resolve_keyboard_sound_base_dir(const char *sound_name, char *out_basedir, size_t out_sz); -int resolve_mouse_sound_base_dir(const char *sound_name, char *out_basedir, size_t out_sz); -int build_paths_for_keyboard_sound(const char *sound_name, char *out_config_path, size_t out_config_sz, char *out_sound_dir, size_t out_sound_sz); -int build_paths_for_mouse_sound(const char *sound_name, char *out_config_path, size_t out_config_sz, char *out_sound_dir, size_t out_sound_sz); +int resolve_keyboard_sound_base_dir(const char *sound_name, char *out_basedir, + size_t out_sz); +int resolve_mouse_sound_base_dir(const char *sound_name, char *out_basedir, + size_t out_sz); +int build_paths_for_keyboard_sound(const char *sound_name, + char *out_config_path, size_t out_config_sz, + char *out_sound_dir, size_t out_sound_sz); +int build_paths_for_mouse_sound(const char *sound_name, char *out_config_path, + size_t out_config_sz, char *out_sound_dir, + size_t out_sound_sz); int validate_keyboard_sound_pack(const char *sound_name); int validate_mouse_sound_pack(const char *sound_name); int list_sound_packs(void); #endif // KEYVIBE_SOUNDPACKS_H - - diff --git a/include/config_io.h b/include/user_config.h similarity index 66% rename from include/config_io.h rename to include/user_config.h index 58def05..f89a799 100644 --- a/include/config_io.h +++ b/include/user_config.h @@ -6,11 +6,13 @@ char *get_user_config_path(char *buffer, size_t buflen); // Write ~/.keyvibe.json in new format -int write_user_config(const char *path, const char *keyboard_sound, const char *mouse_sound, int keyboard_volume, int mouse_volume); +int write_user_config(const char *path, const char *keyboard_sound, + const char *mouse_sound, int keyboard_volume, + int mouse_volume); // Read ~/.keyvibe.json supporting new and legacy formats -int read_user_config(const char *path, char **out_keyboard_sound, char **out_mouse_sound, int *out_keyboard_volume, int *out_mouse_volume); +int read_user_config(const char *path, char **out_keyboard_sound, + char **out_mouse_sound, int *out_keyboard_volume, + int *out_mouse_volume); #endif // KEYVIBE_CONFIG_IO_H - - diff --git a/src/app/process.c b/src/app/process.c new file mode 100644 index 0000000..44c1ca8 --- /dev/null +++ b/src/app/process.c @@ -0,0 +1,164 @@ +#define _POSIX_C_SOURCE 200809L +#include "app/process.h" +#include "common/utils.h" +#include "config.h" +#include +#include +#include +#include +#include +#include +#include + +#define MAX_PATH_LENGTH 1024 + +pid_t keyboard_pid = 0; +pid_t sound_pid = 0; +char pidfile_path[MAX_PATH_LENGTH] = {0}; +int is_daemon = 0; +volatile sig_atomic_t reload_requested = 0; + +void cleanup_processes(int sig) { + (void)sig; + if (!is_daemon) { + printf("\nShutting down KeyVibe...\n"); + } + if (sound_pid > 0) { + kill(sound_pid, SIGTERM); + waitpid(sound_pid, NULL, 0); + } + if (keyboard_pid > 0) { + kill(keyboard_pid, SIGTERM); + waitpid(keyboard_pid, NULL, 0); + } + if (pidfile_path[0] != '\0') { + unlink(pidfile_path); + } + exit(0); +} + +int require_running_pid(pid_t *out_pid) { + build_pidfile_path(pidfile_path, sizeof(pidfile_path)); + pid_t running_pid = 0; + if (!read_pidfile(pidfile_path, &running_pid) || + !process_is_running(running_pid)) { + fprintf(stderr, "KeyVibe: not running.\n"); + return 0; + } + *out_pid = running_pid; + return 1; +} + +void daemonize_self(void) { + pid_t pid = fork(); + if (pid < 0) + exit(1); + if (pid > 0) + exit(0); + if (setsid() < 0) + exit(1); + pid = fork(); + if (pid < 0) + exit(1); + if (pid > 0) + exit(0); + umask(0); + chdir("/"); + int fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) + close(fd); + } +} + +void stop_children(void) { + if (sound_pid > 0) { + kill(sound_pid, SIGTERM); + waitpid(sound_pid, NULL, 0); + sound_pid = 0; + } + if (keyboard_pid > 0) { + kill(keyboard_pid, SIGTERM); + waitpid(keyboard_pid, NULL, 0); + keyboard_pid = 0; + } +} + +int start_children(const char *sound_dir, const char *config_path, int volume, + int verbose, int mute, const char *mouse_sound_dir, + const char *mouse_config_path, int mouse_volume, + int keyboard_mute, int mouse_mute) { + (void)config_path; + (void)mouse_sound_dir; + int pipefd[2]; + if (pipe(pipefd) == -1) { + perror("pipe"); + return 0; + } + sound_pid = fork(); + if (sound_pid == -1) { + perror("fork"); + close(pipefd[0]); + close(pipefd[1]); + return 0; + } + char sound_player_path[MAX_PATH_LENGTH]; + snprintf(sound_player_path, sizeof(sound_player_path), "%s/keyvibe-audio", + KeyVibe_BIN_DIR); + if (sound_pid == 0) { + close(pipefd[1]); + dup2(pipefd[0], STDIN_FILENO); + close(pipefd[0]); + if (chdir(sound_dir) != 0) { + perror("chdir"); + exit(1); + } + char volume_str[32]; + snprintf(volume_str, sizeof(volume_str), "%d", volume); + char mouse_volume_str[32]; + snprintf(mouse_volume_str, sizeof(mouse_volume_str), "%d", mouse_volume); + char verbose_str[8]; + snprintf(verbose_str, sizeof(verbose_str), "%d", verbose); + char mute_str[8]; + snprintf(mute_str, sizeof(mute_str), "%d", mute); + char keyboard_mute_str[8]; + snprintf(keyboard_mute_str, sizeof(keyboard_mute_str), "%d", keyboard_mute); + char mouse_mute_str[8]; + snprintf(mouse_mute_str, sizeof(mouse_mute_str), "%d", mouse_mute); + execl(sound_player_path, "keyvibe-audio", "config.json", volume_str, + verbose_str, mute_str, mouse_config_path, mouse_volume_str, + keyboard_mute_str, mouse_mute_str, (char *)NULL); + perror("execl keyvibe-audio"); + exit(1); + } + keyboard_pid = fork(); + if (keyboard_pid == -1) { + perror("fork"); + kill(sound_pid, SIGTERM); + close(pipefd[0]); + close(pipefd[1]); + return 0; + } + char get_key_presses_path[MAX_PATH_LENGTH]; + snprintf(get_key_presses_path, sizeof(get_key_presses_path), + "%s/keyvibe-input", KeyVibe_BIN_DIR); + if (keyboard_pid == 0) { + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + close(pipefd[1]); + execl(get_key_presses_path, "keyvibe-input", (char *)NULL); + perror("execl keyvibe-input"); + exit(1); + } + close(pipefd[0]); + close(pipefd[1]); + return 1; +} + +void handle_sighup(int sig) { + (void)sig; + reload_requested = 1; +} diff --git a/src/app/reload.c b/src/app/reload.c new file mode 100644 index 0000000..6a9b1a9 --- /dev/null +++ b/src/app/reload.c @@ -0,0 +1,95 @@ +#include "app/reload.h" +#include "app/process.h" +#include "soundpacks.h" +#include "user_config.h" +#include +#include +#include + +int handle_reload(const char *user_cfg_path, char *current_sound_name, + char *current_mouse_sound_name, int *current_keyboard_volume, + int *current_mouse_volume, char *current_config_path, + char *current_sound_dir, char *current_mouse_config_path, + char *current_mouse_sound_dir, int current_mute, + int current_verbose, int current_mouse_volume_arg, + int current_keyboard_mute, int current_mouse_mute) { + char *cfg_keyboard_sound = NULL; + char *cfg_mouse_sound = NULL; + int cfg_keyboard_volume = *current_keyboard_volume; + int cfg_mouse_volume = *current_mouse_volume; + if (read_user_config(user_cfg_path, &cfg_keyboard_sound, &cfg_mouse_sound, + &cfg_keyboard_volume, &cfg_mouse_volume)) { + printf("Read config - keyboard:%s mouse:%s kvol:%d mvol:%d (current " + "kvol:%d mvol:%d)\n", + cfg_keyboard_sound ? cfg_keyboard_sound : "NULL", + cfg_mouse_sound ? cfg_mouse_sound : "NULL", cfg_keyboard_volume, + cfg_mouse_volume, *current_keyboard_volume, *current_mouse_volume); + int keyboard_changed = 0, mouse_changed = 0, keyboard_volume_changed = 0, + mouse_volume_changed = 0; + if (cfg_keyboard_sound && + strcmp(current_sound_name, cfg_keyboard_sound) != 0) { + keyboard_changed = 1; + strncpy(current_sound_name, cfg_keyboard_sound, 1023); + current_sound_name[1023] = '\0'; + } + if (cfg_mouse_sound && + strcmp(current_mouse_sound_name, cfg_mouse_sound) != 0) { + mouse_changed = 1; + strncpy(current_mouse_sound_name, cfg_mouse_sound, 1023); + current_mouse_sound_name[1023] = '\0'; + } + free(cfg_keyboard_sound); + free(cfg_mouse_sound); + if (cfg_keyboard_volume != *current_keyboard_volume) { + keyboard_volume_changed = 1; + *current_keyboard_volume = cfg_keyboard_volume; + } + if (cfg_mouse_volume != *current_mouse_volume) { + mouse_volume_changed = 1; + *current_mouse_volume = cfg_mouse_volume; + } + printf("Config changed - keyboard:%d mouse:%d kvol:%d mvol:%d\n", + keyboard_changed, mouse_changed, keyboard_volume_changed, + mouse_volume_changed); + if (keyboard_changed || mouse_changed || keyboard_volume_changed || + mouse_volume_changed) { + int keyboard_valid = validate_keyboard_sound_pack(current_sound_name); + int mouse_valid = validate_mouse_sound_pack(current_mouse_sound_name); + if (keyboard_valid && mouse_valid) { + if (build_paths_for_keyboard_sound(current_sound_name, + current_config_path, 1024, + current_sound_dir, 1024)) { + if (build_paths_for_mouse_sound(current_mouse_sound_name, + current_mouse_config_path, 1024, + current_mouse_sound_dir, 1024)) { + stop_children(); + start_children(current_sound_dir, current_config_path, + *current_keyboard_volume, current_verbose, + current_mute, current_mouse_sound_dir, + current_mouse_config_path, current_mouse_volume_arg, + current_keyboard_mute, current_mouse_mute); + printf("Reloaded successfully: keyboard=%s, mouse=%s, kvol=%d, " + "mvol=%d\n", + current_sound_name, current_mouse_sound_name, + *current_keyboard_volume, *current_mouse_volume); + return 1; + } else { + printf("Failed to build mouse sound paths\n"); + } + } else { + printf("Failed to build keyboard sound paths\n"); + } + } else { + if (!keyboard_valid) + printf("Reload failed: invalid keyboard sound pack '%s'\n", + current_sound_name); + if (!mouse_valid) + printf("Reload failed: invalid mouse sound pack '%s'\n", + current_mouse_sound_name); + } + } + } else { + printf("Failed to read user config\n"); + } + return 0; +} diff --git a/src/app/watch.c b/src/app/watch.c new file mode 100644 index 0000000..9e5d0e9 --- /dev/null +++ b/src/app/watch.c @@ -0,0 +1,67 @@ +#include "app/watch.h" +#include "app/process.h" +#include +#include +#include +#include +#include +#include +#include + +struct inotify_thread_args { + char path[1024]; +}; + +static void *inotify_thread_fn(void *arg) { + struct inotify_thread_args *a = (struct inotify_thread_args *)arg; + int fd = inotify_init1(IN_NONBLOCK); + if (fd < 0) { + printf("Failed to initialize inotify: %s\n", strerror(errno)); + return NULL; + } + int wd = inotify_add_watch( + fd, a->path, IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB | IN_MODIFY); + if (wd < 0) { + printf("Failed to add inotify watch for %s: %s\n", a->path, + strerror(errno)); + close(fd); + return NULL; + } + printf("Watching config file: %s\n", a->path); + char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event)))); + while (1) { + ssize_t len = read(fd, buf, sizeof(buf)); + if (len <= 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 100000000L; + nanosleep(&ts, NULL); + continue; + } + printf("inotify read error: %s\n", strerror(errno)); + break; + } + printf("Config file changed, requesting reload...\n"); + handle_sighup(0); + } + inotify_rm_watch(fd, wd); + close(fd); + return NULL; +} + +int start_config_watcher(const char *path) { + static pthread_t inotify_thread; + static struct inotify_thread_args in_args; + strncpy(in_args.path, path, sizeof(in_args.path) - 1); + in_args.path[sizeof(in_args.path) - 1] = '\0'; + if (access(in_args.path, F_OK) == 0) { + pthread_create(&inotify_thread, NULL, inotify_thread_fn, &in_args); + pthread_detach(inotify_thread); + return 1; + } else { + printf("Warning: Config file %s does not exist, file watching disabled\n", + in_args.path); + return 0; + } +} diff --git a/src/audio.c b/src/audio.c deleted file mode 100644 index 2b3beaa..0000000 --- a/src/audio.c +++ /dev/null @@ -1,787 +0,0 @@ -#define _GNU_SOURCE -#include "utils.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MAX_LINE_LENGTH 1024 -#define MAX_CONCURRENT_SOUNDS 10 - -typedef struct { - int start_ms; - int duration_ms; -} SoundMapping; -typedef struct { - char press_file[256]; - char release_file[256]; - char generic_press_files[5][256]; - int num_generic_press_files; - char sound_file[256]; - SoundMapping key_mappings[512]; - struct { - char *press; - char *release; - } multi_key_mappings[512]; - int is_multi; - SF_INFO sf_info; -} SoundPack; -typedef struct { - int key_code; - int thread_id; - int is_pressed; -} PlaybackData; - -SoundPack g_sound_pack = {0}; -SoundPack g_mouse_sound_pack = {0}; -float g_volume = 1.0f; -float g_mouse_volume = 1.0f; -int g_verbose = 0; -int g_mute = 0; -int g_keyboard_mute = 0; -int g_mouse_mute = 0; -pthread_t sound_threads[MAX_CONCURRENT_SOUNDS]; -volatile int thread_active[MAX_CONCURRENT_SOUNDS] = {0}; -pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; - -static void get_full_path(char *buffer, size_t buffer_size, - const char *base_dir, const char *filename) { - if (!filename || !base_dir) { - buffer[0] = '\0'; - return; - } - if (filename[0] == '/') { - strncpy(buffer, filename, buffer_size - 1); - buffer[buffer_size - 1] = '\0'; - } else { - if (!safe_snprintf(buffer, buffer_size, "%s/%s", base_dir, filename)) { - buffer[0] = '\0'; - } - } -} - -int load_sound_config(const char *config_path) { - FILE *file = fopen(config_path, "r"); - if (!file) { - fprintf(stderr, "Error: Cannot open config file: %s\n", config_path); - perror("fopen"); - return -1; - } - char config_path_copy[MAX_LINE_LENGTH]; - strncpy(config_path_copy, config_path, sizeof(config_path_copy) - 1); - config_path_copy[sizeof(config_path_copy) - 1] = '\0'; - char *config_dir = dirname(config_path_copy); - fseek(file, 0, SEEK_END); - long size = ftell(file); - if (size < 0) { - fclose(file); - fprintf(stderr, "Error: ftell failed while reading %s\n", config_path); - return -1; - } - errno = 0; - rewind(file); - if (errno != 0) { - fclose(file); - fprintf(stderr, "Error: rewind failed while reading %s\n", config_path); - return -1; - } - char *json_string = malloc((size_t)size + 1); - if (!json_string) { - fclose(file); - fprintf(stderr, "Error: Memory allocation failed\n"); - return -1; - } - fread(json_string, 1, size, file); - json_string[size] = '\0'; - fclose(file); - json_object *root = json_tokener_parse(json_string); - free(json_string); - if (!root) { - fprintf(stderr, "Error: Invalid JSON in config file\n"); - return -1; - } - const char *key_type = "single"; - json_object *obj; - if (json_object_object_get_ex(root, "key_define_type", &obj)) - key_type = json_object_get_string(obj); - g_sound_pack.is_multi = strcmp(key_type, "multi") == 0; - if (g_verbose) { - printf("Config loaded: Using %s mode\n", - g_sound_pack.is_multi ? "multi" : "single"); - } - if (g_sound_pack.is_multi) { - g_sound_pack.num_generic_press_files = 0; - if (json_object_object_get_ex(root, "sound", &obj)) { - const char *pattern = json_object_get_string(obj); - if (g_verbose) - printf("Sound pattern: %s\n", pattern); - if (strstr(pattern, "%d") || strstr(pattern, "{")) { - for (int i = 0; i <= 4; i++) { - char temp_filename[256]; - if (strstr(pattern, "{")) { - char temp_pattern[256]; - strncpy(temp_pattern, pattern, sizeof(temp_pattern) - 1); - temp_pattern[sizeof(temp_pattern) - 1] = '\0'; - char *brace_start = strstr(temp_pattern, "{"); - char *brace_end = strstr(temp_pattern, "}"); - if (brace_start && brace_end) { - *brace_start = '%'; - *(brace_start + 1) = 'd'; - memmove(brace_start + 2, brace_end + 1, - strlen(brace_end + 1) + 1); - } - snprintf(temp_filename, sizeof(temp_filename), temp_pattern, i); - } else { - snprintf(temp_filename, sizeof(temp_filename), pattern, i); - } - get_full_path(g_sound_pack.generic_press_files[i], - sizeof(g_sound_pack.generic_press_files[i]), config_dir, - temp_filename); - if (access(g_sound_pack.generic_press_files[i], R_OK) == 0) { - g_sound_pack.num_generic_press_files = i + 1; - } else { - if (g_verbose) - printf("Generic sound file not found: %s\n", - g_sound_pack.generic_press_files[i]); - break; - } - } - } else { - get_full_path(g_sound_pack.generic_press_files[0], - sizeof(g_sound_pack.generic_press_files[0]), config_dir, - pattern); - if (access(g_sound_pack.generic_press_files[0], R_OK) == 0) { - g_sound_pack.num_generic_press_files = 1; - if (g_verbose) - printf("Found single generic sound file: %s\n", - g_sound_pack.generic_press_files[0]); - } - } - if (g_verbose) - printf("Total generic press sound files: %d\n", - g_sound_pack.num_generic_press_files); - } - if (json_object_object_get_ex(root, "soundup", &obj)) { - char temp_release_file[256]; - strncpy(temp_release_file, json_object_get_string(obj), - sizeof(temp_release_file) - 1); - temp_release_file[sizeof(temp_release_file) - 1] = '\0'; - get_full_path(g_sound_pack.release_file, - sizeof(g_sound_pack.release_file), config_dir, - temp_release_file); - if (g_verbose) - printf("Release sound file: %s\n", g_sound_pack.release_file); - } - if (json_object_object_get_ex(root, "defines", &obj)) { - json_object_object_foreach(obj, key, val) { - int key_code; - int is_release = 0; - - // Handle mouse key names first - if (strcmp(key, "MouseLeft") == 0) { - key_code = 272; - } else if (strcmp(key, "MouseRight") == 0) { - key_code = 273; - } else if (strcmp(key, "MouseMiddle") == 0) { - key_code = 274; - } else { - // Handle regular key codes with -up suffix - char *dash = strstr(key, "-up"); - if (dash) { - char key_copy[16]; - strncpy(key_copy, key, dash - key); - key_copy[dash - key] = '\0'; - key_code = atoi(key_copy); - is_release = 1; - } else { - key_code = atoi(key); - } - } - - if (key_code >= 0 && key_code < 512) { - const char *filename_relative = json_object_get_string(val); - char full_filename[MAX_LINE_LENGTH]; - get_full_path(full_filename, sizeof(full_filename), config_dir, - filename_relative); - if (is_release) { - if (g_sound_pack.multi_key_mappings[key_code].release) - free(g_sound_pack.multi_key_mappings[key_code].release); - g_sound_pack.multi_key_mappings[key_code].release = - strdup(full_filename); - } else { - if (g_sound_pack.multi_key_mappings[key_code].press) - free(g_sound_pack.multi_key_mappings[key_code].press); - g_sound_pack.multi_key_mappings[key_code].press = - strdup(full_filename); - } - } - } - } - } else { - // Try "sound" first, then "audio_file" for mouse configs - if (json_object_object_get_ex(root, "sound", &obj) || - json_object_object_get_ex(root, "audio_file", &obj)) { - char temp_sound_file[256]; - strncpy(temp_sound_file, json_object_get_string(obj), - sizeof(temp_sound_file) - 1); - temp_sound_file[sizeof(temp_sound_file) - 1] = '\0'; - get_full_path(g_sound_pack.sound_file, sizeof(g_sound_pack.sound_file), - config_dir, temp_sound_file); - if (g_verbose) - printf("Single mode sound file: %s\n", g_sound_pack.sound_file); - } - // Try "defines" first, then "definitions" for mouse configs - json_object *defines_obj = NULL; - if (json_object_object_get_ex(root, "defines", &defines_obj) || - json_object_object_get_ex(root, "definitions", &defines_obj)) { - json_object_object_foreach(defines_obj, key, val) { - int key_code; - - // Handle mouse key names - if (strcmp(key, "MouseLeft") == 0) { - key_code = 272; - } else if (strcmp(key, "MouseRight") == 0) { - key_code = 273; - } else if (strcmp(key, "MouseMiddle") == 0) { - key_code = 274; - } else { - key_code = atoi(key); - } - - if (key_code >= 0 && key_code < 512) { - // Handle different config formats - if (json_object_is_type(val, json_type_array)) { - // Standard format: [start_ms, duration_ms] - if (json_object_array_length(val) >= 2) { - g_sound_pack.key_mappings[key_code].start_ms = - json_object_get_int(json_object_array_get_idx(val, 0)); - g_sound_pack.key_mappings[key_code].duration_ms = - json_object_get_int(json_object_array_get_idx(val, 1)); - } - } else if (json_object_is_type(val, json_type_object)) { - // Mouse format: {"timing": [[start1, duration1], [start2, - // duration2]]} - json_object *timing_array; - if (json_object_object_get_ex(val, "timing", &timing_array) && - json_object_is_type(timing_array, json_type_array) && - json_object_array_length(timing_array) > 0) { - // Use the first timing entry - json_object *first_timing = - json_object_array_get_idx(timing_array, 0); - if (json_object_is_type(first_timing, json_type_array) && - json_object_array_length(first_timing) >= 2) { - g_sound_pack.key_mappings[key_code].start_ms = - json_object_get_int( - json_object_array_get_idx(first_timing, 0)); - g_sound_pack.key_mappings[key_code].duration_ms = - json_object_get_int( - json_object_array_get_idx(first_timing, 1)); - } - } - } - } - } - } - } - json_object_put(root); - return 0; -} - -int init_audio() { - if (g_sound_pack.is_multi) - return 0; - if (strlen(g_sound_pack.sound_file) == 0) { - fprintf(stderr, "Error: No sound file specified in config\n"); - return -1; - } - if (access(g_sound_pack.sound_file, R_OK) != 0) { - fprintf(stderr, "Sound file not accessible: %s\n", g_sound_pack.sound_file); - perror("access"); - return -1; - } - SNDFILE *test_sf = - sf_open(g_sound_pack.sound_file, SFM_READ, &g_sound_pack.sf_info); - if (!test_sf) { - fprintf(stderr, "Could not open sound file: %s\n", g_sound_pack.sound_file); - fprintf(stderr, "libsndfile error: %s\n", sf_strerror(NULL)); - return -1; - } - sf_close(test_sf); - if (g_verbose) { - printf("Sound file info: %ld frames, %d channels, %d Hz\n", - g_sound_pack.sf_info.frames, g_sound_pack.sf_info.channels, - g_sound_pack.sf_info.samplerate); - } - return 0; -} - -void *play_sound_thread(void *arg) { - PlaybackData *data = (PlaybackData *)arg; - int key_code = data->key_code; - int thread_id = data->thread_id; - int is_pressed = data->is_pressed; - const char *file_to_play = NULL; - - // Determine if this is a mouse event (key codes 272, 273, 274) - int is_mouse_event = (key_code == 272 || key_code == 273 || key_code == 274); - SoundPack *sound_pack = is_mouse_event ? &g_mouse_sound_pack : &g_sound_pack; - float volume = is_mouse_event ? g_mouse_volume : g_volume; - int mute_state = is_mouse_event ? g_mouse_mute : g_keyboard_mute; - - if (g_verbose) { - printf("Thread %d: Playing %s sound for key %d (%s)\n", thread_id, - is_mouse_event ? "mouse" : "keyboard", key_code, - is_pressed ? "press" : "release"); - } - - // Check if this type of sound is muted - if (g_mute || mute_state) { - if (g_verbose) { - printf("Thread %d: %s sound is muted\n", thread_id, - is_mouse_event ? "Mouse" : "Keyboard"); - } - goto exit_cleanup; - } - if (sound_pack->is_multi) { - if (is_pressed && sound_pack->multi_key_mappings[key_code].press) - file_to_play = sound_pack->multi_key_mappings[key_code].press; - else if (!is_pressed && sound_pack->multi_key_mappings[key_code].release) - file_to_play = sound_pack->multi_key_mappings[key_code].release; - else if (is_pressed && sound_pack->num_generic_press_files > 0) { - int idx = rand() % sound_pack->num_generic_press_files; - file_to_play = sound_pack->generic_press_files[idx]; - } else if (!is_pressed && strlen(sound_pack->release_file) > 0) - file_to_play = sound_pack->release_file; - if (!file_to_play) { - if (g_verbose) { - printf("Thread %d: No sound file found for key %d (%s)\n", thread_id, - key_code, is_pressed ? "press" : "release"); - } - goto exit_cleanup; - } - if (g_verbose) { - printf("Thread %d: Using sound file: %s\n", thread_id, file_to_play); - } - SF_INFO sf_info = {0}; - SNDFILE *sf = sf_open(file_to_play, SFM_READ, &sf_info); - if (!sf) { - fprintf(stderr, "Could not open sound file: %s (Error: %s)\n", - file_to_play, sf_strerror(NULL)); - goto exit_cleanup; - } - pa_sample_spec ss = {.format = PA_SAMPLE_S16LE, - .rate = sf_info.samplerate, - .channels = sf_info.channels}; - int pa_error; - pa_simple *pa_handle = - pa_simple_new(NULL, "KeyboardSounds", PA_STREAM_PLAYBACK, NULL, - "playback", &ss, NULL, NULL, &pa_error); - if (!pa_handle) { - fprintf(stderr, "Could not initialize PulseAudio: %s\n", - pa_strerror(pa_error)); - sf_close(sf); - goto exit_cleanup; - } - int frames = 2048; - short *buffer = malloc(frames * sf_info.channels * sizeof(short)); - if (!buffer) { - pa_simple_free(pa_handle); - sf_close(sf); - goto exit_cleanup; - } - sf_count_t read; - while ((read = sf_readf_short(sf, buffer, frames)) > 0) { - for (sf_count_t i = 0; i < read * sf_info.channels; i++) { - buffer[i] = (short)(buffer[i] * volume); - } - int pa_write_error; - if (pa_simple_write(pa_handle, buffer, - read * sf_info.channels * sizeof(short), - &pa_write_error) < 0) { - fprintf(stderr, "PulseAudio write error: %s\n", - pa_strerror(pa_write_error)); - break; - } - } - int pa_drain_error; - pa_simple_drain(pa_handle, &pa_drain_error); - pa_simple_free(pa_handle); - sf_close(sf); - free(buffer); - } else { - if (key_code >= 512 || - sound_pack->key_mappings[key_code].duration_ms == 0) { - if (g_verbose) { - printf("Thread %d: No mapping for key %d\n", thread_id, key_code); - } - goto exit_cleanup; - } - SoundMapping *mapping = &sound_pack->key_mappings[key_code]; - SF_INFO sf_info = sound_pack->sf_info; - SNDFILE *sf = sf_open(sound_pack->sound_file, SFM_READ, &sf_info); - if (!sf) { - fprintf(stderr, "Thread: Could not open sound file\n"); - goto exit_cleanup; - } - pa_sample_spec ss = {.format = PA_SAMPLE_S16LE, - .rate = sf_info.samplerate, - .channels = sf_info.channels}; - int pa_error; - pa_simple *pa_handle = - pa_simple_new(NULL, "KeyboardSounds", PA_STREAM_PLAYBACK, NULL, - "playback", &ss, NULL, NULL, &pa_error); - if (!pa_handle) { - sf_close(sf); - fprintf(stderr, "Thread: Could not initialize PulseAudio: %s\n", - pa_strerror(pa_error)); - goto exit_cleanup; - } - sf_count_t start_frame = (mapping->start_ms * sf_info.samplerate) / 1000; - sf_count_t duration_frames = - (mapping->duration_ms * sf_info.samplerate) / 1000; - sf_seek(sf, start_frame, SEEK_SET); - short *buffer = malloc(duration_frames * sf_info.channels * sizeof(short)); - if (!buffer) { - pa_simple_free(pa_handle); - sf_close(sf); - goto exit_cleanup; - } - sf_count_t frames_read = sf_readf_short(sf, buffer, duration_frames); - for (sf_count_t i = 0; i < frames_read * sf_info.channels; i++) { - buffer[i] = (short)(buffer[i] * volume); - } - int pa_write_error, pa_drain_error; - pa_simple_write(pa_handle, buffer, - frames_read * sf_info.channels * sizeof(short), - &pa_write_error); - pa_simple_drain(pa_handle, &pa_drain_error); - free(buffer); - pa_simple_free(pa_handle); - sf_close(sf); - } -exit_cleanup: - pthread_mutex_lock(&thread_mutex); - thread_active[thread_id] = 0; - pthread_mutex_unlock(&thread_mutex); - free(data); - return NULL; -} - -int find_available_thread_slot() { - pthread_mutex_lock(&thread_mutex); - for (int i = 0; i < MAX_CONCURRENT_SOUNDS; i++) { - if (!thread_active[i]) { - pthread_mutex_unlock(&thread_mutex); - return i; - } - } - pthread_mutex_unlock(&thread_mutex); - return -1; -} - -void play_sound_segment(int key_code, int is_pressed) { - if (g_mute) { - if (g_verbose) { - printf("Sound muted - ignoring key %d (%s)\n", key_code, - is_pressed ? "press" : "release"); - } - return; - } - if (!g_sound_pack.is_multi && !is_pressed) { - if (g_verbose) { - printf("Single mode: Ignoring key release for key %d\n", key_code); - } - return; - } - int slot = find_available_thread_slot(); - if (slot == -1) { - if (g_verbose) { - printf("Warning: No available thread slots\n"); - } - return; - } - PlaybackData *data = malloc(sizeof(PlaybackData)); - if (!data) - return; - data->key_code = key_code; - data->thread_id = slot; - data->is_pressed = is_pressed; - pthread_mutex_lock(&thread_mutex); - thread_active[slot] = 1; - if (pthread_create(&sound_threads[slot], NULL, play_sound_thread, data) != - 0) { - thread_active[slot] = 0; - free(data); - fprintf(stderr, "Failed to create sound thread\n"); - } else { - pthread_detach(sound_threads[slot]); - } - pthread_mutex_unlock(&thread_mutex); -} - -int parse_keyboard_event(const char *json_line, int *key_code, - int *is_pressed) { - char *line_copy = strdup(json_line); - if (!line_copy) - return -1; - char *newline = strchr(line_copy, '\n'); - if (newline) - *newline = '\0'; - if (g_verbose) { - printf("Parsing JSON: %s\n", line_copy); - } - json_object *root = json_tokener_parse(line_copy); - free(line_copy); - if (!root) { - fprintf(stderr, "Failed to parse JSON: %s\n", json_line); - return -1; - } - json_object *key_code_obj, *state_code_obj; - if (json_object_object_get_ex(root, "key_code", &key_code_obj) && - json_object_object_get_ex(root, "state_code", &state_code_obj)) { - *key_code = json_object_get_int(key_code_obj); - *is_pressed = json_object_get_int(state_code_obj); - if (g_verbose) { - printf("Parsed key event: key_code=%d, is_pressed=%d\n", *key_code, - *is_pressed); - } - json_object_put(root); - return 0; - } - json_object_put(root); - return -1; -} - -void cleanup() { - if (g_verbose) - printf("Cleaning up...\n"); - usleep(500000); - for (int i = 0; i < 512; i++) { - if (g_sound_pack.multi_key_mappings[i].press) { - free(g_sound_pack.multi_key_mappings[i].press); - g_sound_pack.multi_key_mappings[i].press = NULL; - } - if (g_sound_pack.multi_key_mappings[i].release) { - free(g_sound_pack.multi_key_mappings[i].release); - g_sound_pack.multi_key_mappings[i].release = NULL; - } - } - pthread_mutex_lock(&thread_mutex); - for (int i = 0; i < MAX_CONCURRENT_SOUNDS; i++) { - thread_active[i] = 0; - } - pthread_mutex_unlock(&thread_mutex); -} - -static int read_mute_state() { - char mute_file[MAX_LINE_LENGTH]; - const char *rd = getenv("XDG_RUNTIME_DIR"); - if (!rd || strlen(rd) == 0) { - rd = "/tmp"; - } - if (!safe_snprintf(mute_file, sizeof(mute_file), "%s/keyvibe-mute-%d", rd, - (int)getuid())) { - return 0; - } - - FILE *f = fopen(mute_file, "r"); - if (!f) { - return 0; // Default to unmuted if file doesn't exist - } - - int mute = 0; - if (fscanf(f, "%d", &mute) == 1) { - fclose(f); - return mute; - } - fclose(f); - return 0; -} - -static int read_keyboard_mute_state() { - char mute_file[MAX_LINE_LENGTH]; - const char *rd = getenv("XDG_RUNTIME_DIR"); - if (!rd || strlen(rd) == 0) { - rd = "/tmp"; - } - if (!safe_snprintf(mute_file, sizeof(mute_file), "%s/keyvibe-kbd-mute-%d", rd, - (int)getuid())) { - return 0; - } - FILE *f = fopen(mute_file, "r"); - if (!f) { - return 0; - } - int mute = 0; - if (fscanf(f, "%d", &mute) == 1) { - fclose(f); - return mute; - } - fclose(f); - return 0; -} - -static int read_mouse_mute_state() { - char mute_file[MAX_LINE_LENGTH]; - const char *rd = getenv("XDG_RUNTIME_DIR"); - if (!rd || strlen(rd) == 0) { - rd = "/tmp"; - } - if (!safe_snprintf(mute_file, sizeof(mute_file), "%s/keyvibe-mouse-mute-%d", - rd, (int)getuid())) { - return 0; - } - FILE *f = fopen(mute_file, "r"); - if (!f) { - return 0; - } - int mute = 0; - if (fscanf(f, "%d", &mute) == 1) { - fclose(f); - return mute; - } - fclose(f); - return 0; -} - -int main(int argc, char *argv[]) { - if (argc < 2 || argc > 9) { - fprintf(stderr, - "Usage: %s [volume] [verbose] [mute] [mouse_config] " - "[mouse_volume] [keyboard_mute] [mouse_mute]\n", - argv[0]); - fprintf(stderr, " volume: 0-100 (default: 50)\n"); - fprintf(stderr, " verbose: 1 to enable verbose output (default: 0)\n"); - fprintf(stderr, " mute: 1 to mute all sound (default: 0)\n"); - fprintf(stderr, " mouse_config: path to mouse config.json (optional)\n"); - fprintf(stderr, " mouse_volume: 0-100 for mouse volume (optional)\n"); - fprintf(stderr, " keyboard_mute: 1 to mute keyboard sounds (optional)\n"); - fprintf(stderr, " mouse_mute: 1 to mute mouse sounds (optional)\n"); - return 1; - } - if (argc >= 3) { - int volume_percent = atoi(argv[2]); - if (volume_percent < 0) - volume_percent = 0; - if (volume_percent > 100) - volume_percent = 100; - g_volume = volume_percent / 100.0f; - if (g_verbose) - printf("Volume set to: %d%%\n", volume_percent); - } else { - if (g_verbose) - printf("Volume set to: 50%% (default)\n"); - } - if (argc >= 4) { - g_verbose = atoi(argv[3]); - if (g_verbose) { - printf("Verbose mode enabled\n"); - } - } - if (argc >= 5) { - g_mute = atoi(argv[4]); - if (g_mute) { - printf("Sound muted\n"); - } - } - - // Parse mouse config and volume if provided - if (argc >= 6) { - if (load_sound_config(argv[5]) != 0) { - fprintf(stderr, "Failed to load mouse sound configuration\n"); - return 1; - } - // Copy the loaded config to mouse sound pack - g_mouse_sound_pack = g_sound_pack; - if (g_verbose) { - printf("Mouse sound pack loaded from: %s\n", argv[5]); - } - } - - if (argc >= 7) { - int mouse_volume_percent = atoi(argv[6]); - if (mouse_volume_percent < 0) - mouse_volume_percent = 0; - if (mouse_volume_percent > 100) - mouse_volume_percent = 100; - g_mouse_volume = mouse_volume_percent / 100.0f; - if (g_verbose) - printf("Mouse volume set to: %d%%\n", mouse_volume_percent); - } - - if (argc >= 8) { - g_keyboard_mute = atoi(argv[7]); - if (g_verbose) - printf("Keyboard mute: %s\n", g_keyboard_mute ? "enabled" : "disabled"); - } - - if (argc >= 9) { - g_mouse_mute = atoi(argv[8]); - if (g_verbose) - printf("Mouse mute: %s\n", g_mouse_mute ? "enabled" : "disabled"); - } - - if (load_sound_config(argv[1]) != 0) { - fprintf(stderr, "Failed to load keyboard sound configuration\n"); - return 1; - } - if (init_audio() != 0) { - fprintf(stderr, "Failed to initialize audio\n"); - return 1; - } - // Remove signal handlers - we'll use file-based approach instead - fd_set readfds; - struct timeval timeout; - char line[MAX_LINE_LENGTH]; - while (1) { - // Check mute state from files periodically - g_mute = read_mute_state(); - g_keyboard_mute = read_keyboard_mute_state(); - g_mouse_mute = read_mouse_mute_state(); - - FD_ZERO(&readfds); - FD_SET(STDIN_FILENO, &readfds); - timeout.tv_sec = 1; - timeout.tv_usec = 0; - int ready = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout); - if (ready == -1) { - if (errno == EINTR) { - // Signal interrupted select, continue - continue; - } - perror("select"); - break; - } else if (ready == 0) { - if (g_verbose) { - printf("Waiting for input...\n"); - } - continue; - } - if (FD_ISSET(STDIN_FILENO, &readfds)) { - if (fgets(line, sizeof(line), stdin) == NULL) { - if (feof(stdin)) { - if (g_verbose) - printf("EOF reached on stdin\n"); - } else { - perror("fgets"); - } - break; - } - int key_code, is_pressed; - if (parse_keyboard_event(line, &key_code, &is_pressed) == 0) { - play_sound_segment(key_code, is_pressed); - } - } - } - cleanup(); - return 0; -} diff --git a/src/audio/config.c b/src/audio/config.c new file mode 100644 index 0000000..4ff53b1 --- /dev/null +++ b/src/audio/config.c @@ -0,0 +1,256 @@ +#include "audio/types.h" +#include "common/utils.h" +#include +#include +#include +#include +#include +#include +#include + +SoundPack g_sound_pack = {0}; +SoundPack g_mouse_sound_pack = {0}; +float g_volume = 1.0f; +float g_mouse_volume = 1.0f; +int g_verbose = 0; +int g_mute = 0; +int g_keyboard_mute = 0; +int g_mouse_mute = 0; + +static void get_full_path(char *buffer, size_t buffer_size, + const char *base_dir, const char *filename) { + if (!filename || !base_dir) { + buffer[0] = '\0'; + return; + } + if (filename[0] == '/') { + strncpy(buffer, filename, buffer_size - 1); + buffer[buffer_size - 1] = '\0'; + } else { + if (!safe_snprintf(buffer, buffer_size, "%s/%s", base_dir, filename)) { + buffer[0] = '\0'; + } + } +} + +static char *xstrdup(const char *s) { + if (!s) return NULL; + size_t n = strlen(s) + 1; + char *p = (char *)malloc(n); + if (!p) return NULL; + memcpy(p, s, n); + return p; +} + +int load_sound_config(const char *config_path) { + FILE *file = fopen(config_path, "r"); + if (!file) { + fprintf(stderr, "Error: Cannot open config file: %s\n", config_path); + perror("fopen"); + return -1; + } + char config_path_copy[1024]; + strncpy(config_path_copy, config_path, sizeof(config_path_copy) - 1); + config_path_copy[sizeof(config_path_copy) - 1] = '\0'; + char *config_dir = dirname(config_path_copy); + fseek(file, 0, SEEK_END); + long size = ftell(file); + if (size < 0) { + fclose(file); + fprintf(stderr, "Error: ftell failed while reading %s\n", config_path); + return -1; + } + errno = 0; + rewind(file); + if (errno != 0) { + fclose(file); + fprintf(stderr, "Error: rewind failed while reading %s\n", config_path); + return -1; + } + char *json_string = malloc((size_t)size + 1); + if (!json_string) { + fclose(file); + fprintf(stderr, "Error: Memory allocation failed\n"); + return -1; + } + fread(json_string, 1, size, file); + json_string[size] = '\0'; + fclose(file); + json_object *root = json_tokener_parse(json_string); + free(json_string); + if (!root) { + fprintf(stderr, "Error: Invalid JSON in config file\n"); + return -1; + } + const char *key_type = "single"; + json_object *obj; + if (json_object_object_get_ex(root, "key_define_type", &obj)) + key_type = json_object_get_string(obj); + g_sound_pack.is_multi = strcmp(key_type, "multi") == 0; + if (g_verbose) { + printf("Config loaded: Using %s mode\n", + g_sound_pack.is_multi ? "multi" : "single"); + } + if (g_sound_pack.is_multi) { + g_sound_pack.num_generic_press_files = 0; + if (json_object_object_get_ex(root, "sound", &obj)) { + const char *pattern = json_object_get_string(obj); + if (g_verbose) + printf("Sound pattern: %s\n", pattern); + if (strstr(pattern, "%d") || strstr(pattern, "{")) { + for (int i = 0; i <= 4; i++) { + char temp_filename[256]; + if (strstr(pattern, "{")) { + char temp_pattern[256]; + strncpy(temp_pattern, pattern, sizeof(temp_pattern) - 1); + temp_pattern[sizeof(temp_pattern) - 1] = '\0'; + char *brace_start = strstr(temp_pattern, "{"); + char *brace_end = strstr(temp_pattern, "}"); + if (brace_start && brace_end) { + *brace_start = '%'; + *(brace_start + 1) = 'd'; + memmove(brace_start + 2, brace_end + 1, + strlen(brace_end + 1) + 1); + } + snprintf(temp_filename, sizeof(temp_filename), temp_pattern, i); + } else { + snprintf(temp_filename, sizeof(temp_filename), pattern, i); + } + get_full_path(g_sound_pack.generic_press_files[i], + sizeof(g_sound_pack.generic_press_files[i]), config_dir, + temp_filename); + if (access(g_sound_pack.generic_press_files[i], R_OK) == 0) { + g_sound_pack.num_generic_press_files = i + 1; + } else { + if (g_verbose) + printf("Generic sound file not found: %s\n", + g_sound_pack.generic_press_files[i]); + break; + } + } + } else { + get_full_path(g_sound_pack.generic_press_files[0], + sizeof(g_sound_pack.generic_press_files[0]), config_dir, + pattern); + if (access(g_sound_pack.generic_press_files[0], R_OK) == 0) { + g_sound_pack.num_generic_press_files = 1; + if (g_verbose) + printf("Found single generic sound file: %s\n", + g_sound_pack.generic_press_files[0]); + } + } + if (g_verbose) + printf("Total generic press sound files: %d\n", + g_sound_pack.num_generic_press_files); + } + if (json_object_object_get_ex(root, "soundup", &obj)) { + char temp_release_file[256]; + strncpy(temp_release_file, json_object_get_string(obj), + sizeof(temp_release_file) - 1); + temp_release_file[sizeof(temp_release_file) - 1] = '\0'; + get_full_path(g_sound_pack.release_file, + sizeof(g_sound_pack.release_file), config_dir, + temp_release_file); + if (g_verbose) + printf("Release sound file: %s\n", g_sound_pack.release_file); + } + if (json_object_object_get_ex(root, "defines", &obj)) { + json_object_object_foreach(obj, key, val) { + int key_code; + int is_release = 0; + if (strcmp(key, "MouseLeft") == 0) { + key_code = 272; + } else if (strcmp(key, "MouseRight") == 0) { + key_code = 273; + } else if (strcmp(key, "MouseMiddle") == 0) { + key_code = 274; + } else { + char *dash = strstr(key, "-up"); + if (dash) { + char key_copy[16]; + strncpy(key_copy, key, dash - key); + key_copy[dash - key] = '\0'; + key_code = atoi(key_copy); + is_release = 1; + } else { + key_code = atoi(key); + } + } + if (key_code >= 0 && key_code < 512) { + const char *filename_relative = json_object_get_string(val); + char full_filename[1024]; + get_full_path(full_filename, sizeof(full_filename), config_dir, + filename_relative); + if (is_release) { + if (g_sound_pack.multi_key_mappings[key_code].release) + free(g_sound_pack.multi_key_mappings[key_code].release); + g_sound_pack.multi_key_mappings[key_code].release = + xstrdup(full_filename); + } else { + if (g_sound_pack.multi_key_mappings[key_code].press) + free(g_sound_pack.multi_key_mappings[key_code].press); + g_sound_pack.multi_key_mappings[key_code].press = + xstrdup(full_filename); + } + } + } + } + } else { + if (json_object_object_get_ex(root, "sound", &obj) || + json_object_object_get_ex(root, "audio_file", &obj)) { + char temp_sound_file[256]; + strncpy(temp_sound_file, json_object_get_string(obj), + sizeof(temp_sound_file) - 1); + temp_sound_file[sizeof(temp_sound_file) - 1] = '\0'; + get_full_path(g_sound_pack.sound_file, sizeof(g_sound_pack.sound_file), + config_dir, temp_sound_file); + if (g_verbose) + printf("Single mode sound file: %s\n", g_sound_pack.sound_file); + } + json_object *defines_obj = NULL; + if (json_object_object_get_ex(root, "defines", &defines_obj) || + json_object_object_get_ex(root, "definitions", &defines_obj)) { + json_object_object_foreach(defines_obj, key, val) { + int key_code; + if (strcmp(key, "MouseLeft") == 0) { + key_code = 272; + } else if (strcmp(key, "MouseRight") == 0) { + key_code = 273; + } else if (strcmp(key, "MouseMiddle") == 0) { + key_code = 274; + } else { + key_code = atoi(key); + } + if (key_code >= 0 && key_code < 512) { + if (json_object_is_type(val, json_type_array)) { + if (json_object_array_length(val) >= 2) { + g_sound_pack.key_mappings[key_code].start_ms = + json_object_get_int(json_object_array_get_idx(val, 0)); + g_sound_pack.key_mappings[key_code].duration_ms = + json_object_get_int(json_object_array_get_idx(val, 1)); + } + } else if (json_object_is_type(val, json_type_object)) { + json_object *timing_array; + if (json_object_object_get_ex(val, "timing", &timing_array) && + json_object_is_type(timing_array, json_type_array) && + json_object_array_length(timing_array) > 0) { + json_object *first_timing = + json_object_array_get_idx(timing_array, 0); + if (json_object_is_type(first_timing, json_type_array) && + json_object_array_length(first_timing) >= 2) { + g_sound_pack.key_mappings[key_code].start_ms = + json_object_get_int( + json_object_array_get_idx(first_timing, 0)); + g_sound_pack.key_mappings[key_code].duration_ms = + json_object_get_int( + json_object_array_get_idx(first_timing, 1)); + } + } + } + } + } + } + } + json_object_put(root); + return 0; +} diff --git a/src/audio/main.c b/src/audio/main.c new file mode 100644 index 0000000..07b4192 --- /dev/null +++ b/src/audio/main.c @@ -0,0 +1,204 @@ +#include "audio/playback.h" +#include "audio/types.h" +#include "common/utils.h" +#include +#include +#include +#include +#include +#include +#include +#include + +int load_sound_config(const char *config_path); + +static int read_mute_state() { + char mute_file[1024]; + const char *rd = getenv("XDG_RUNTIME_DIR"); + if (!rd || strlen(rd) == 0) { + rd = "/tmp"; + } + if (!safe_snprintf(mute_file, sizeof(mute_file), "%s/keyvibe-mute-%d", rd, + (int)getuid())) { + return 0; + } + FILE *f = fopen(mute_file, "r"); + if (!f) { + return 0; + } + int mute = 0; + if (fscanf(f, "%d", &mute) == 1) { + fclose(f); + return mute; + } + fclose(f); + return 0; +} + +static int read_keyboard_mute_state() { + char mute_file[1024]; + const char *rd = getenv("XDG_RUNTIME_DIR"); + if (!rd || strlen(rd) == 0) { + rd = "/tmp"; + } + if (!safe_snprintf(mute_file, sizeof(mute_file), "%s/keyvibe-kbd-mute-%d", rd, + (int)getuid())) { + return 0; + } + FILE *f = fopen(mute_file, "r"); + if (!f) { + return 0; + } + int mute = 0; + if (fscanf(f, "%d", &mute) == 1) { + fclose(f); + return mute; + } + fclose(f); + return 0; +} + +static int read_mouse_mute_state() { + char mute_file[1024]; + const char *rd = getenv("XDG_RUNTIME_DIR"); + if (!rd || strlen(rd) == 0) { + rd = "/tmp"; + } + if (!safe_snprintf(mute_file, sizeof(mute_file), "%s/keyvibe-mouse-mute-%d", + rd, (int)getuid())) { + return 0; + } + FILE *f = fopen(mute_file, "r"); + if (!f) { + return 0; + } + int mute = 0; + if (fscanf(f, "%d", &mute) == 1) { + fclose(f); + return mute; + } + fclose(f); + return 0; +} + +int main(int argc, char *argv[]) { + if (argc < 2 || argc > 9) { + fprintf(stderr, + "Usage: %s [volume] [verbose] [mute] [mouse_config] " + "[mouse_volume] [keyboard_mute] [mouse_mute]\n", + argv[0]); + fprintf(stderr, " volume: 0-100 (default: 50)\n"); + fprintf(stderr, " verbose: 1 to enable verbose output (default: 0)\n"); + fprintf(stderr, " mute: 1 to mute all sound (default: 0)\n"); + fprintf(stderr, " mouse_config: path to mouse config.json (optional)\n"); + fprintf(stderr, " mouse_volume: 0-100 for mouse volume (optional)\n"); + fprintf(stderr, " keyboard_mute: 1 to mute keyboard sounds (optional)\n"); + fprintf(stderr, " mouse_mute: 1 to mute mouse sounds (optional)\n"); + return 1; + } + if (argc >= 3) { + int volume_percent = atoi(argv[2]); + if (volume_percent < 0) + volume_percent = 0; + if (volume_percent > 100) + volume_percent = 100; + g_volume = volume_percent / 100.0f; + if (g_verbose) + printf("Volume set to: %d%%\n", volume_percent); + } else { + if (g_verbose) + printf("Volume set to: 50%% (default)\n"); + } + if (argc >= 4) { + g_verbose = atoi(argv[3]); + if (g_verbose) { + printf("Verbose mode enabled\n"); + } + } + if (argc >= 5) { + g_mute = atoi(argv[4]); + if (g_mute) { + printf("Sound muted\n"); + } + } + if (argc >= 6) { + if (load_sound_config(argv[5]) != 0) { + fprintf(stderr, "Failed to load mouse sound configuration\n"); + return 1; + } + g_mouse_sound_pack = g_sound_pack; + if (g_verbose) { + printf("Mouse sound pack loaded from: %s\n", argv[5]); + } + } + if (argc >= 7) { + int mouse_volume_percent = atoi(argv[6]); + if (mouse_volume_percent < 0) + mouse_volume_percent = 0; + if (mouse_volume_percent > 100) + mouse_volume_percent = 100; + g_mouse_volume = mouse_volume_percent / 100.0f; + if (g_verbose) + printf("Mouse volume set to: %d%%\n", mouse_volume_percent); + } + if (argc >= 8) { + g_keyboard_mute = atoi(argv[7]); + if (g_verbose) + printf("Keyboard mute: %s\n", g_keyboard_mute ? "enabled" : "disabled"); + } + if (argc >= 9) { + g_mouse_mute = atoi(argv[8]); + if (g_verbose) + printf("Mouse mute: %s\n", g_mouse_mute ? "enabled" : "disabled"); + } + if (load_sound_config(argv[1]) != 0) { + fprintf(stderr, "Failed to load keyboard sound configuration\n"); + return 1; + } + if (init_audio() != 0) { + fprintf(stderr, "Failed to initialize audio\n"); + return 1; + } + fd_set readfds; + struct timeval timeout; + char line[1024]; + while (1) { + g_mute = read_mute_state(); + g_keyboard_mute = read_keyboard_mute_state(); + g_mouse_mute = read_mouse_mute_state(); + + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + timeout.tv_sec = 1; + timeout.tv_usec = 0; + int ready = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout); + if (ready == -1) { + if (errno == EINTR) { + continue; + } + perror("select"); + break; + } else if (ready == 0) { + if (g_verbose) { + printf("Waiting for input...\n"); + } + continue; + } + if (FD_ISSET(STDIN_FILENO, &readfds)) { + if (fgets(line, sizeof(line), stdin) == NULL) { + if (feof(stdin)) { + if (g_verbose) + printf("EOF reached on stdin\n"); + } else { + perror("fgets"); + } + break; + } + int key_code, is_pressed; + if (parse_keyboard_event(line, &key_code, &is_pressed) == 0) { + play_sound_segment(key_code, is_pressed); + } + } + } + return 0; +} diff --git a/src/audio/playback.c b/src/audio/playback.c new file mode 100644 index 0000000..c8a4437 --- /dev/null +++ b/src/audio/playback.c @@ -0,0 +1,289 @@ +#include "audio/playback.h" +#include "audio/types.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +pthread_t sound_threads[MAX_CONCURRENT_SOUNDS]; +volatile int thread_active[MAX_CONCURRENT_SOUNDS] = {0}; +pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; + +int init_audio() { + if (g_sound_pack.is_multi) + return 0; + if (strlen(g_sound_pack.sound_file) == 0) { + fprintf(stderr, "Error: No sound file specified in config\n"); + return -1; + } + if (access(g_sound_pack.sound_file, R_OK) != 0) { + fprintf(stderr, "Sound file not accessible: %s\n", g_sound_pack.sound_file); + perror("access"); + return -1; + } + SNDFILE *test_sf = + sf_open(g_sound_pack.sound_file, SFM_READ, &g_sound_pack.sf_info); + if (!test_sf) { + fprintf(stderr, "Could not open sound file: %s\n", g_sound_pack.sound_file); + fprintf(stderr, "libsndfile error: %s\n", sf_strerror(NULL)); + return -1; + } + sf_close(test_sf); + if (g_verbose) { + printf("Sound file info: %ld frames, %d channels, %d Hz\n", + g_sound_pack.sf_info.frames, g_sound_pack.sf_info.channels, + g_sound_pack.sf_info.samplerate); + } + return 0; +} + +void *play_sound_thread(void *arg) { + PlaybackData *data = (PlaybackData *)arg; + int key_code = data->key_code; + int thread_id = data->thread_id; + int is_pressed = data->is_pressed; + const char *file_to_play = NULL; + + int is_mouse_event = (key_code == 272 || key_code == 273 || key_code == 274); + SoundPack *sound_pack = is_mouse_event ? &g_mouse_sound_pack : &g_sound_pack; + float volume = is_mouse_event ? g_mouse_volume : g_volume; + int mute_state = is_mouse_event ? g_mouse_mute : g_keyboard_mute; + + if (g_verbose) { + printf("Thread %d: Playing %s sound for key %d (%s)\n", thread_id, + is_mouse_event ? "mouse" : "keyboard", key_code, + is_pressed ? "press" : "release"); + } + + if (g_mute || mute_state) { + if (g_verbose) { + printf("Thread %d: %s sound is muted\n", thread_id, + is_mouse_event ? "Mouse" : "Keyboard"); + } + goto exit_cleanup; + } + if (sound_pack->is_multi) { + if (is_pressed && sound_pack->multi_key_mappings[key_code].press) + file_to_play = sound_pack->multi_key_mappings[key_code].press; + else if (!is_pressed && sound_pack->multi_key_mappings[key_code].release) + file_to_play = sound_pack->multi_key_mappings[key_code].release; + else if (is_pressed && sound_pack->num_generic_press_files > 0) { + int idx = rand() % sound_pack->num_generic_press_files; + file_to_play = sound_pack->generic_press_files[idx]; + } else if (!is_pressed && strlen(sound_pack->release_file) > 0) + file_to_play = sound_pack->release_file; + if (!file_to_play) { + if (g_verbose) { + printf("Thread %d: No sound file found for key %d (%s)\n", thread_id, + key_code, is_pressed ? "press" : "release"); + } + goto exit_cleanup; + } + if (g_verbose) { + printf("Thread %d: Using sound file: %s\n", thread_id, file_to_play); + } + SF_INFO sf_info = {0}; + SNDFILE *sf = sf_open(file_to_play, SFM_READ, &sf_info); + if (!sf) { + fprintf(stderr, "Could not open sound file: %s (Error: %s)\n", + file_to_play, sf_strerror(NULL)); + goto exit_cleanup; + } + pa_sample_spec ss = {.format = PA_SAMPLE_S16LE, + .rate = sf_info.samplerate, + .channels = sf_info.channels}; + int pa_error; + pa_simple *pa_handle = + pa_simple_new(NULL, "KeyboardSounds", PA_STREAM_PLAYBACK, NULL, + "playback", &ss, NULL, NULL, &pa_error); + if (!pa_handle) { + fprintf(stderr, "Could not initialize PulseAudio: %s\n", + pa_strerror(pa_error)); + sf_close(sf); + goto exit_cleanup; + } + int frames = 2048; + short *buffer = malloc(frames * sf_info.channels * sizeof(short)); + if (!buffer) { + pa_simple_free(pa_handle); + sf_close(sf); + goto exit_cleanup; + } + sf_count_t read; + while ((read = sf_readf_short(sf, buffer, frames)) > 0) { + for (sf_count_t i = 0; i < read * sf_info.channels; i++) { + buffer[i] = (short)(buffer[i] * volume); + } + int pa_write_error; + if (pa_simple_write(pa_handle, buffer, + read * sf_info.channels * sizeof(short), + &pa_write_error) < 0) { + fprintf(stderr, "PulseAudio write error: %s\n", + pa_strerror(pa_write_error)); + break; + } + } + int pa_drain_error; + pa_simple_drain(pa_handle, &pa_drain_error); + pa_simple_free(pa_handle); + sf_close(sf); + free(buffer); + } else { + if (key_code >= 512 || + sound_pack->key_mappings[key_code].duration_ms == 0) { + if (g_verbose) { + printf("Thread %d: No mapping for key %d\n", thread_id, key_code); + } + goto exit_cleanup; + } + SoundMapping *mapping = &sound_pack->key_mappings[key_code]; + SF_INFO sf_info = sound_pack->sf_info; + SNDFILE *sf = sf_open(sound_pack->sound_file, SFM_READ, &sf_info); + if (!sf) { + fprintf(stderr, "Thread: Could not open sound file\n"); + goto exit_cleanup; + } + pa_sample_spec ss = {.format = PA_SAMPLE_S16LE, + .rate = sf_info.samplerate, + .channels = sf_info.channels}; + int pa_error; + pa_simple *pa_handle = + pa_simple_new(NULL, "KeyboardSounds", PA_STREAM_PLAYBACK, NULL, + "playback", &ss, NULL, NULL, &pa_error); + if (!pa_handle) { + sf_close(sf); + fprintf(stderr, "Thread: Could not initialize PulseAudio: %s\n", + pa_strerror(pa_error)); + goto exit_cleanup; + } + sf_count_t start_frame = (mapping->start_ms * sf_info.samplerate) / 1000; + sf_count_t duration_frames = + (mapping->duration_ms * sf_info.samplerate) / 1000; + sf_seek(sf, start_frame, SEEK_SET); + short *buffer = malloc(duration_frames * sf_info.channels * sizeof(short)); + if (!buffer) { + pa_simple_free(pa_handle); + sf_close(sf); + goto exit_cleanup; + } + sf_count_t frames_read = sf_readf_short(sf, buffer, duration_frames); + for (sf_count_t i = 0; i < frames_read * sf_info.channels; i++) { + buffer[i] = (short)(buffer[i] * volume); + } + int pa_write_error, pa_drain_error; + pa_simple_write(pa_handle, buffer, + frames_read * sf_info.channels * sizeof(short), + &pa_write_error); + pa_simple_drain(pa_handle, &pa_drain_error); + free(buffer); + pa_simple_free(pa_handle); + sf_close(sf); + } +exit_cleanup: + pthread_mutex_lock(&thread_mutex); + thread_active[thread_id] = 0; + pthread_mutex_unlock(&thread_mutex); + free(data); + return NULL; +} + +int find_available_thread_slot() { + pthread_mutex_lock(&thread_mutex); + for (int i = 0; i < MAX_CONCURRENT_SOUNDS; i++) { + if (!thread_active[i]) { + pthread_mutex_unlock(&thread_mutex); + return i; + } + } + pthread_mutex_unlock(&thread_mutex); + return -1; +} + +void play_sound_segment(int key_code, int is_pressed) { + if (g_mute) { + if (g_verbose) { + printf("Sound muted - ignoring key %d (%s)\n", key_code, + is_pressed ? "press" : "release"); + } + return; + } + if (!g_sound_pack.is_multi && !is_pressed) { + if (g_verbose) { + printf("Single mode: Ignoring key release for key %d\n", key_code); + } + return; + } + int slot = find_available_thread_slot(); + if (slot == -1) { + if (g_verbose) { + printf("Warning: No available thread slots\n"); + } + return; + } + PlaybackData *data = malloc(sizeof(PlaybackData)); + if (!data) + return; + data->key_code = key_code; + data->thread_id = slot; + data->is_pressed = is_pressed; + pthread_mutex_lock(&thread_mutex); + thread_active[slot] = 1; + if (pthread_create(&sound_threads[slot], NULL, play_sound_thread, data) != + 0) { + thread_active[slot] = 0; + free(data); + fprintf(stderr, "Failed to create sound thread\n"); + } else { + pthread_detach(sound_threads[slot]); + } + pthread_mutex_unlock(&thread_mutex); +} + +static char *xstrdup2(const char *s) { + if (!s) + return NULL; + size_t n = strlen(s) + 1; + char *p = (char *)malloc(n); + if (!p) + return NULL; + memcpy(p, s, n); + return p; +} + +int parse_keyboard_event(const char *json_line, int *key_code, + int *is_pressed) { + char *line_copy = xstrdup2(json_line); + if (!line_copy) + return -1; + char *newline = strchr(line_copy, '\n'); + if (newline) + *newline = '\0'; + if (g_verbose) { + printf("Parsing JSON: %s\n", line_copy); + } + json_object *root = json_tokener_parse(line_copy); + free(line_copy); + if (!root) { + fprintf(stderr, "Failed to parse JSON: %s\n", json_line); + return -1; + } + json_object *key_code_obj, *state_code_obj; + if (json_object_object_get_ex(root, "key_code", &key_code_obj) && + json_object_object_get_ex(root, "state_code", &state_code_obj)) { + *key_code = json_object_get_int(key_code_obj); + *is_pressed = json_object_get_int(state_code_obj); + if (g_verbose) { + printf("Parsed key event: key_code=%d, is_pressed=%d\n", *key_code, + *is_pressed); + } + json_object_put(root); + return 0; + } + json_object_put(root); + return -1; +} diff --git a/src/cli.c b/src/cli.c new file mode 100644 index 0000000..8d193e4 --- /dev/null +++ b/src/cli.c @@ -0,0 +1,158 @@ +#include "app/cli.h" +#include +#include +#include +#include + +void print_usage(const char *program_name) { + printf("KeyVibe - Mechanical Keyboard Sound Simulator\n\n"); + printf("Usage: %s [OPTIONS]\n\n", program_name); + printf("Options:\n"); + printf(" -S, --sound SOUND_NAME Select keyboard sound pack (default: " + "eg-oreo)\n"); + printf( + " -M, --mouse SOUND_NAME Select mouse sound pack (default: ping)\n"); + printf(" -V, --volume VOLUME Set volume [0-100] for both keyboard and " + "mouse (default: 50)\n"); + printf(" -K, --keyboard-volume VOLUME Set keyboard volume [0-100] " + "(default: 50)\n"); + printf(" -O, --mouse-volume VOLUME Set mouse volume [0-100] (default: " + "50)\n"); + printf(" -l, --list List available sound packs\n"); + printf(" -d, --daemon Run in background (write PID file)\n"); + printf(" -s, --stop Stop background daemon\n"); + printf(" -m, --mute[=DEVICE] Mute sounds (keyboard|mouse|both, " + "default: both)\n"); + printf(" -u, --unmute[=DEVICE] Unmute sounds (keyboard|mouse|both, " + "default: both)\n"); + printf(" -h, --help Show this help message\n"); + printf(" -v, --verbose Enable verbose output\n"); + printf(" In daemon mode, editing ~/.keyvibe.json will auto-reload.\n"); +} + +int parse_cli(int argc, char **argv, CliOptions *out) { + memset(out, 0, sizeof(*out)); + out->volume = -1; + out->keyboard_volume = -1; + out->mouse_volume = -1; + out->keyboard_mute = -1; + out->mouse_mute = -1; + static struct option long_options[] = { + {"sound", required_argument, 0, 'S'}, + {"mouse", required_argument, 0, 'M'}, + {"volume", required_argument, 0, 'V'}, + {"keyboard-volume", required_argument, 0, 'K'}, + {"mouse-volume", required_argument, 0, 'O'}, + {"mute", optional_argument, 0, 'm'}, + {"unmute", optional_argument, 0, 'u'}, + {"list", no_argument, 0, 'l'}, + {"daemon", no_argument, 0, 'd'}, + {"stop", no_argument, 0, 's'}, + {"help", no_argument, 0, 'h'}, + {"verbose", no_argument, 0, 'v'}, + {0, 0, 0, 0}}; + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] == '-') { + const char *a = argv[i] + 2; + const char *e = strchr(a, '='); + size_t n = e ? (size_t)(e - a) : strlen(a); + if (!((n == 5 && strncmp(a, "sound", 5) == 0) || + (n == 5 && strncmp(a, "mouse", 5) == 0) || + (n == 6 && strncmp(a, "volume", 6) == 0) || + (n == 4 && strncmp(a, "list", 4) == 0) || + (n == 6 && strncmp(a, "daemon", 6) == 0) || + (n == 4 && strncmp(a, "stop", 4) == 0) || + (n == 4 && strncmp(a, "mute", 4) == 0) || + (n == 6 && strncmp(a, "unmute", 6) == 0) || + (n == 4 && strncmp(a, "help", 4) == 0) || + (n == 7 && strncmp(a, "verbose", 7) == 0))) { + fprintf(stderr, "Unknown option: %s (use full option name)\n", argv[i]); + return 1; + } + } + } + optind = 1; + int opt; + while ((opt = getopt_long(argc, argv, "S:M:V:K:O:lhdsm::u::v", long_options, + NULL)) != -1) { + switch (opt) { + case 'd': + out->daemon_flag = 1; + break; + case 's': + out->stop_flag = 1; + break; + case 'S': + out->sound = optarg; + break; + case 'M': + out->mouse_sound = optarg; + break; + case 'V': + out->volume = atoi(optarg); + if (out->volume < 0) + out->volume = 0; + if (out->volume > 100) + out->volume = 100; + break; + case 'K': + out->keyboard_volume = atoi(optarg); + if (out->keyboard_volume < 0) + out->keyboard_volume = 0; + if (out->keyboard_volume > 100) + out->keyboard_volume = 100; + break; + case 'O': + out->mouse_volume = atoi(optarg); + if (out->mouse_volume < 0) + out->mouse_volume = 0; + if (out->mouse_volume > 100) + out->mouse_volume = 100; + break; + case 'm': + if (optarg == NULL || strcmp(optarg, "both") == 0) { + out->keyboard_mute = 1; + out->mouse_mute = 1; + } else if (strcmp(optarg, "keyboard") == 0) { + out->keyboard_mute = 1; + } else if (strcmp(optarg, "mouse") == 0) { + out->mouse_mute = 1; + } else { + fprintf(stderr, + "Invalid mute option: %s. Use 'keyboard', 'mouse', or 'both'\n", + optarg); + return 1; + } + break; + case 'u': + if (optarg == NULL || strcmp(optarg, "both") == 0) { + out->keyboard_mute = 0; + out->mouse_mute = 0; + } else if (strcmp(optarg, "keyboard") == 0) { + out->keyboard_mute = 0; + } else if (strcmp(optarg, "mouse") == 0) { + out->mouse_mute = 0; + } else { + fprintf( + stderr, + "Invalid unmute option: %s. Use 'keyboard', 'mouse', or 'both'\n", + optarg); + return 1; + } + break; + case 'l': + out->list_flag = 1; + break; + case 'h': + print_usage(argv[0]); + return 2; // signal printed help + case 'v': + out->verbose = 1; + break; + default: + print_usage(argv[0]); + return 1; + } + } + return 0; +} diff --git a/src/utils.c b/src/common/utils.c similarity index 98% rename from src/utils.c rename to src/common/utils.c index 9427388..c66f192 100644 --- a/src/utils.c +++ b/src/common/utils.c @@ -1,5 +1,5 @@ #define _POSIX_C_SOURCE 200809L -#include "utils.h" +#include "common/utils.h" #include #include #include @@ -8,7 +8,6 @@ #include #include -// Mute helpers moved here for re-use int read_runtime_mute_file() { char mute_file[1024]; const char *rd = getenv("XDG_RUNTIME_DIR"); diff --git a/src/config_io.c b/src/config.c similarity index 98% rename from src/config_io.c rename to src/config.c index f01911a..96a0c07 100644 --- a/src/config_io.c +++ b/src/config.c @@ -1,5 +1,5 @@ -/* #include "config_io.h" */ -#include "utils.h" +#include "common/utils.h" +#include "user_config.h" #include #include #include diff --git a/src/input.c b/src/input.c index 01825e1..3e505f7 100644 --- a/src/input.c +++ b/src/input.c @@ -1,4 +1,4 @@ -#include "utils.h" +#include "common/utils.h" #include #include #include diff --git a/src/main.c b/src/main.c index 6b052d8..54ee093 100644 --- a/src/main.c +++ b/src/main.c @@ -1,10 +1,14 @@ #define _POSIX_C_SOURCE 200809L +#include "app/cli.h" +#include "app/process.h" +#include "app/reload.h" +#include "app/watch.h" +#include "common/mute.h" +#include "common/utils.h" #include "config.h" -#include "config_io.h" -#include "mute.h" +#include "user_config.h" #include "soundpacks.h" -#include "utils.h" #include #include #include @@ -32,15 +36,11 @@ #define USER_MOUSE_AUDIO_SUBPATH "/.local/share/keyvibe/soundpacks/mouse" #define USER_CONFIG_FILENAME ".keyvibe.json" -// Global variables for cleanup -pid_t keyboard_pid = 0; -pid_t sound_pid = 0; -static char pidfile_path[MAX_PATH_LENGTH] = {0}; -static int is_daemon = 0; -static volatile sig_atomic_t reload_requested = 0; -static volatile sig_atomic_t keyboard_reload_requested = 0; -static volatile sig_atomic_t mouse_reload_requested = 0; -static volatile sig_atomic_t volume_reload_requested = 0; +extern pid_t keyboard_pid; +extern pid_t sound_pid; +extern char pidfile_path[]; +extern int is_daemon; +extern volatile sig_atomic_t reload_requested; static int current_keyboard_volume = 50; static int current_mouse_volume = 50; static char current_sound_name[MAX_PATH_LENGTH] = {0}; @@ -54,273 +54,9 @@ static char current_mouse_config_path[MAX_PATH_LENGTH] = {0}; static char current_mouse_sound_dir[MAX_PATH_LENGTH] = {0}; static int current_mute = 0; -void print_usage(const char *program_name) { - printf("KeyVibe - Mechanical Keyboard Sound Simulator\n\n"); - printf("Usage: %s [OPTIONS]\n\n", program_name); - printf("Options:\n"); - printf(" -S, --sound SOUND_NAME Select keyboard sound pack (default: " - "eg-oreo)\n"); - printf( - " -M, --mouse SOUND_NAME Select mouse sound pack (default: ping)\n"); - printf(" -V, --volume VOLUME Set volume [0-100] for both keyboard and " - "mouse (default: 50)\n"); - printf(" -K, --keyboard-volume VOLUME Set keyboard volume [0-100] " - "(default: 50)\n"); - printf(" -O, --mouse-volume VOLUME Set mouse volume [0-100] (default: " - "50)\n"); - printf(" -l, --list List available sound packs\n"); - printf(" -d, --daemon Run in background (write PID file)\n"); - printf(" -s, --stop Stop background daemon\n"); - printf(" -m, --mute[=DEVICE] Mute sounds (keyboard|mouse|both, " - "default: both)\n"); - printf(" -u, --unmute[=DEVICE] Unmute sounds (keyboard|mouse|both, " - "default: both)\n"); - printf(" -h, --help Show this help message\n"); - printf(" -v, --verbose Enable verbose output\n"); - printf(" In daemon mode, editing ~/.keyvibe.json will auto-reload.\n"); -} - -// moved to soundpacks.c -int list_sound_packs(void); - -// moved to soundpacks.c -void get_user_keyboard_audio_dir(char *buffer, size_t buflen); - -// moved to soundpacks.c -void get_user_mouse_audio_dir(char *buffer, size_t buflen); - -// moved to soundpacks.c -int resolve_keyboard_sound_base_dir(const char *sound_name, char *out_basedir, - size_t out_sz); - -// moved to soundpacks.c -int resolve_mouse_sound_base_dir(const char *sound_name, char *out_basedir, - size_t out_sz); - -// moved to soundpacks.c -int validate_keyboard_sound_pack(const char *sound_name); - -// moved to soundpacks.c -int validate_mouse_sound_pack(const char *sound_name); - -void cleanup_processes(int sig) { - (void)sig; - if (!is_daemon) { - printf("\nShutting down KeyVibe...\n"); - } - if (sound_pid > 0) { - kill(sound_pid, SIGTERM); - waitpid(sound_pid, NULL, 0); - } - if (keyboard_pid > 0) { - kill(keyboard_pid, SIGTERM); - waitpid(keyboard_pid, NULL, 0); - } - if (pidfile_path[0] != '\0') { - unlink(pidfile_path); - } - exit(0); -} - -// moved to config_io.c -char *get_user_config_path(char *buffer, size_t buflen); - -// moved to soundpacks.c -int build_paths_for_keyboard_sound(const char *sound_name, - char *out_config_path, size_t out_config_sz, - char *out_sound_dir, size_t out_sound_sz); - -// moved to soundpacks.c -int build_paths_for_mouse_sound(const char *sound_name, char *out_config_path, - size_t out_config_sz, char *out_sound_dir, - size_t out_sound_sz); - -// Helper: ensure daemon is running, return its pid or print a consistent error -static int require_running_pid(pid_t *out_pid) { - build_pidfile_path(pidfile_path, sizeof(pidfile_path)); - pid_t running_pid = 0; - if (!read_pidfile(pidfile_path, &running_pid) || - !process_is_running(running_pid)) { - fprintf(stderr, "KeyVibe: not running.\n"); - return 0; - } - *out_pid = running_pid; - return 1; -} - -static void daemonize_self() { - pid_t pid = fork(); - if (pid < 0) - exit(1); - if (pid > 0) - exit(0); - if (setsid() < 0) - exit(1); - pid = fork(); - if (pid < 0) - exit(1); - if (pid > 0) - exit(0); - umask(0); - chdir("/"); - int fd = open("/dev/null", O_RDWR); - if (fd >= 0) { - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - if (fd > 2) - close(fd); - } -} - -static void stop_children() { - if (sound_pid > 0) { - kill(sound_pid, SIGTERM); - waitpid(sound_pid, NULL, 0); - sound_pid = 0; - } - if (keyboard_pid > 0) { - kill(keyboard_pid, SIGTERM); - waitpid(keyboard_pid, NULL, 0); - keyboard_pid = 0; - } -} - -static int start_children(const char *sound_dir, const char *config_path, - int volume, int verbose, int mute, - const char *mouse_sound_dir, - const char *mouse_config_path, int mouse_volume, - int keyboard_mute, int mouse_mute) { - (void)config_path; - (void)mouse_sound_dir; - int pipefd[2]; - if (pipe(pipefd) == -1) { - perror("pipe"); - return 0; - } - sound_pid = fork(); - if (sound_pid == -1) { - perror("fork"); - close(pipefd[0]); - close(pipefd[1]); - return 0; - } - char sound_player_path[MAX_PATH_LENGTH]; - snprintf(sound_player_path, sizeof(sound_player_path), "%s/keyvibe-audio", - KeyVibe_BIN_DIR); - if (sound_pid == 0) { - close(pipefd[1]); - dup2(pipefd[0], STDIN_FILENO); - close(pipefd[0]); - if (chdir(sound_dir) != 0) { - perror("chdir"); - exit(1); - } - char volume_str[32]; - snprintf(volume_str, sizeof(volume_str), "%d", volume); - char mouse_volume_str[32]; - snprintf(mouse_volume_str, sizeof(mouse_volume_str), "%d", mouse_volume); - char verbose_str[8]; - snprintf(verbose_str, sizeof(verbose_str), "%d", verbose); - char mute_str[8]; - snprintf(mute_str, sizeof(mute_str), "%d", mute); - char keyboard_mute_str[8]; - snprintf(keyboard_mute_str, sizeof(keyboard_mute_str), "%d", keyboard_mute); - char mouse_mute_str[8]; - snprintf(mouse_mute_str, sizeof(mouse_mute_str), "%d", mouse_mute); - execl(sound_player_path, "keyvibe-audio", "config.json", volume_str, - verbose_str, mute_str, mouse_config_path, mouse_volume_str, - keyboard_mute_str, mouse_mute_str, (char *)NULL); - perror("execl keyvibe-audio"); - exit(1); - } - keyboard_pid = fork(); - if (keyboard_pid == -1) { - perror("fork"); - kill(sound_pid, SIGTERM); - close(pipefd[0]); - close(pipefd[1]); - return 0; - } - char get_key_presses_path[MAX_PATH_LENGTH]; - snprintf(get_key_presses_path, sizeof(get_key_presses_path), - "%s/keyvibe-input", KeyVibe_BIN_DIR); - if (keyboard_pid == 0) { - close(pipefd[0]); - dup2(pipefd[1], STDOUT_FILENO); - close(pipefd[1]); - execl(get_key_presses_path, "keyvibe-input", (char *)NULL); - perror("execl keyvibe-input"); - exit(1); - } - close(pipefd[0]); - close(pipefd[1]); - return 1; -} - -static void handle_sighup(int sig) { - (void)sig; - reload_requested = 1; -} - -struct inotify_thread_args { - char path[MAX_PATH_LENGTH]; -}; - -static void *inotify_thread_fn(void *arg) { - struct inotify_thread_args *a = (struct inotify_thread_args *)arg; - int fd = inotify_init1(IN_NONBLOCK); - if (fd < 0) { - printf("Failed to initialize inotify: %s\n", strerror(errno)); - return NULL; - } - int wd = inotify_add_watch( - fd, a->path, IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB | IN_MODIFY); - if (wd < 0) { - printf("Failed to add inotify watch for %s: %s\n", a->path, - strerror(errno)); - close(fd); - return NULL; - } - printf("Watching config file: %s\n", a->path); - - char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event)))); - while (1) { - ssize_t len = read(fd, buf, sizeof(buf)); - if (len <= 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = 100000000L; // 100ms - nanosleep(&ts, NULL); - continue; - } - printf("inotify read error: %s\n", strerror(errno)); - break; - } - printf("Config file changed, requesting reload...\n"); - reload_requested = 1; - } - inotify_rm_watch(fd, wd); - close(fd); - return NULL; -} - -// moved to config_io.c -int write_user_config(const char *path, const char *keyboard_sound, - const char *mouse_sound, int keyboard_volume, - int mouse_volume); - -// moved to config_io.c -int read_user_config(const char *path, char **out_keyboard_sound, - char **out_mouse_sound, int *out_keyboard_volume, - int *out_mouse_volume); - int main(int argc, char *argv[]) { - char *sound_name = - strdup("eg-oreo"); // may be replaced by strdup; track for free - char *mouse_sound_name = - strdup("ping"); // may be replaced by strdup; track for free + char *sound_name = strdup("eg-oreo"); + char *mouse_sound_name = strdup("ping"); int sound_name_owned = 1; int mouse_sound_name_owned = 1; int verbose = 0; @@ -362,19 +98,17 @@ int main(int argc, char *argv[]) { if (sound_name_owned) { free(sound_name); } - sound_name = cfg_keyboard_sound; // now owned, must free on exit paths + sound_name = cfg_keyboard_sound; sound_name_owned = 1; } if (cfg_mouse_sound) { if (mouse_sound_name_owned) { free(mouse_sound_name); } - mouse_sound_name = - cfg_mouse_sound; // now owned, must free on exit paths + mouse_sound_name = cfg_mouse_sound; mouse_sound_name_owned = 1; } - volume = cfg_keyboard_volume; // Use keyboard volume as main volume for - // CLI compatibility + volume = cfg_keyboard_volume; } } } @@ -437,7 +171,6 @@ int main(int argc, char *argv[]) { break; case 'm': if (optarg == NULL) { - // No argument provided, mute both cli_keyboard_mute = 1; cli_mouse_mute = 1; current_mute = 1; // global mute @@ -460,7 +193,6 @@ int main(int argc, char *argv[]) { break; case 'u': if (optarg == NULL) { - // No argument provided, unmute both cli_keyboard_mute = 0; cli_mouse_mute = 0; current_mute = 0; // clear global mute @@ -485,7 +217,6 @@ int main(int argc, char *argv[]) { case 'l': list_sounds = 1; break; - /* removed legacy long mute/unmute cases; use -m/-u or --mute/--unmute */ case 'h': print_usage(argv[0]); if (sound_name_owned) { @@ -510,7 +241,7 @@ int main(int argc, char *argv[]) { } return rc; } - build_pidfile_path(pidfile_path, sizeof(pidfile_path)); + build_pidfile_path(pidfile_path, 1024); if (flag_stop) { pid_t running_pid = 0; if (!require_running_pid(&running_pid)) @@ -530,17 +261,15 @@ int main(int argc, char *argv[]) { } return errorf("KeyVibe: process did not stop in time\n"); } - // Initialize global mute from runtime file so child inherits correct state current_mute = read_runtime_mute_file(); - // Apply CLI options and update config file if needed int config_updated = 0; if (cli_sound != NULL) { if (sound_name_owned) { free(sound_name); } sound_name = cli_sound; - sound_name_owned = 0; // cli_sound is not owned by us + sound_name_owned = 0; config_updated = 1; } if (cli_mouse_sound != NULL) { @@ -548,7 +277,7 @@ int main(int argc, char *argv[]) { free(mouse_sound_name); } mouse_sound_name = cli_mouse_sound; - mouse_sound_name_owned = 0; // cli_mouse_sound is not owned by us + mouse_sound_name_owned = 0; config_updated = 1; } if (cli_volume >= 0) { @@ -572,13 +301,10 @@ int main(int argc, char *argv[]) { config_updated = 1; } - // Persist runtime mute state so audio child can pick it up write_runtime_mute_file(current_mute); write_runtime_keyboard_mute_file(current_keyboard_mute); write_runtime_mouse_mute_file(current_mouse_mute); - // If only CLI control options were provided and not daemon/list/stop, exit - // quietly if (config_updated && !flag_daemon && !list_sounds && !flag_stop) { if (sound_name_owned) { free(sound_name); @@ -589,7 +315,6 @@ int main(int argc, char *argv[]) { return 0; } - // Update config file if CLI options were used if (config_updated && get_user_config_path(user_cfg_path, sizeof(user_cfg_path))) { if (!write_user_config(user_cfg_path, sound_name, mouse_sound_name, @@ -600,7 +325,7 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Updated config file %s\n", user_cfg_path); } } - // Create default config file if it doesn't exist and no CLI options were used + if (!config_updated && get_user_config_path(user_cfg_path, sizeof(user_cfg_path))) { if (access(user_cfg_path, F_OK) != 0) { @@ -726,23 +451,11 @@ int main(int argc, char *argv[]) { int status; pid_t finished_pid; signal(SIGHUP, handle_sighup); - pthread_t inotify_thread; - struct inotify_thread_args in_args; - // Enable live reloading for both daemon and non-daemon modes + char user_cfg_path2[MAX_PATH_LENGTH]; if (get_user_config_path(user_cfg_path2, sizeof(user_cfg_path2))) { - strncpy(in_args.path, user_cfg_path2, sizeof(in_args.path) - 1); - in_args.path[sizeof(in_args.path) - 1] = '\0'; - printf("Setting up file watcher for: %s\n", in_args.path); - - // Check if the config file exists - if (access(in_args.path, F_OK) == 0) { - pthread_create(&inotify_thread, NULL, inotify_thread_fn, &in_args); - pthread_detach(inotify_thread); - } else { - printf("Warning: Config file %s does not exist, file watching disabled\n", - in_args.path); - } + printf("Setting up file watcher for: %s\n", user_cfg_path2); + start_config_watcher(user_cfg_path2); } else { printf("Warning: Could not get user config path for file watching\n"); } @@ -750,116 +463,14 @@ int main(int argc, char *argv[]) { if (reload_requested) { printf("Reload requested, processing config changes...\n"); reload_requested = 0; - char user_cfg_path3[MAX_PATH_LENGTH]; if (get_user_config_path(user_cfg_path3, sizeof(user_cfg_path3))) { - char *cfg_keyboard_sound = NULL; - char *cfg_mouse_sound = NULL; - int cfg_keyboard_volume = current_keyboard_volume; - int cfg_mouse_volume = current_mouse_volume; - - if (read_user_config(user_cfg_path3, &cfg_keyboard_sound, - &cfg_mouse_sound, &cfg_keyboard_volume, - &cfg_mouse_volume)) { - printf("Read config - keyboard:%s mouse:%s kvol:%d mvol:%d (current " - "kvol:%d mvol:%d)\n", - cfg_keyboard_sound ? cfg_keyboard_sound : "NULL", - cfg_mouse_sound ? cfg_mouse_sound : "NULL", - cfg_keyboard_volume, cfg_mouse_volume, current_keyboard_volume, - current_mouse_volume); - - // Check what changed - int keyboard_changed = 0; - int mouse_changed = 0; - int keyboard_volume_changed = 0; - int mouse_volume_changed = 0; - - if (cfg_keyboard_sound && - strcmp(current_sound_name, cfg_keyboard_sound) != 0) { - keyboard_changed = 1; - strncpy(current_sound_name, cfg_keyboard_sound, - sizeof(current_sound_name) - 1); - current_sound_name[sizeof(current_sound_name) - 1] = '\0'; - } - free(cfg_keyboard_sound); - - if (cfg_mouse_sound && - strcmp(current_mouse_sound_name, cfg_mouse_sound) != 0) { - mouse_changed = 1; - strncpy(current_mouse_sound_name, cfg_mouse_sound, - sizeof(current_mouse_sound_name) - 1); - current_mouse_sound_name[sizeof(current_mouse_sound_name) - 1] = - '\0'; - } - free(cfg_mouse_sound); - - if (cfg_keyboard_volume != current_keyboard_volume) { - keyboard_volume_changed = 1; - current_keyboard_volume = cfg_keyboard_volume; - } - - if (cfg_mouse_volume != current_mouse_volume) { - mouse_volume_changed = 1; - current_mouse_volume = cfg_mouse_volume; - } - - printf("Config changed - keyboard:%d mouse:%d kvol:%d mvol:%d\n", - keyboard_changed, mouse_changed, keyboard_volume_changed, - mouse_volume_changed); - - // Handle the changes immediately - if (keyboard_changed || mouse_changed || keyboard_volume_changed || - mouse_volume_changed) { - // Validate both sound packs - int keyboard_valid = - validate_keyboard_sound_pack(current_sound_name); - int mouse_valid = - validate_mouse_sound_pack(current_mouse_sound_name); - - if (keyboard_valid && mouse_valid) { - // Update keyboard paths - if (build_paths_for_keyboard_sound( - current_sound_name, current_config_path, - sizeof(current_config_path), current_sound_dir, - sizeof(current_sound_dir))) { - // Update mouse paths - if (build_paths_for_mouse_sound( - current_mouse_sound_name, current_mouse_config_path, - sizeof(current_mouse_config_path), - current_mouse_sound_dir, - sizeof(current_mouse_sound_dir))) { - // Restart children with new config (including volume changes) - stop_children(); - start_children(current_sound_dir, current_config_path, - current_keyboard_volume, current_verbose, - current_mute, current_mouse_sound_dir, - current_mouse_config_path, - current_mouse_volume, current_keyboard_mute, - current_mouse_mute); - printf("Reloaded successfully: keyboard=%s, mouse=%s, " - "kvol=%d, mvol=%d\n", - current_sound_name, current_mouse_sound_name, - current_keyboard_volume, current_mouse_volume); - } else { - printf("Failed to build mouse sound paths\n"); - } - } else { - printf("Failed to build keyboard sound paths\n"); - } - } else { - if (!keyboard_valid) { - printf("Reload failed: invalid keyboard sound pack '%s'\n", - current_sound_name); - } - if (!mouse_valid) { - printf("Reload failed: invalid mouse sound pack '%s'\n", - current_mouse_sound_name); - } - } - } - } else { - printf("Failed to read user config\n"); - } + handle_reload( + user_cfg_path3, current_sound_name, current_mouse_sound_name, + ¤t_keyboard_volume, ¤t_mouse_volume, + current_config_path, current_sound_dir, current_mouse_config_path, + current_mouse_sound_dir, current_mute, current_verbose, + current_mouse_volume, current_keyboard_mute, current_mouse_mute); } else { printf("Failed to get user config path\n"); } diff --git a/src/soundpacks.c b/src/soundpacks.c index f051297..d701c1c 100644 --- a/src/soundpacks.c +++ b/src/soundpacks.c @@ -1,6 +1,6 @@ -/* #include "soundpacks.h" */ +#include "soundpacks.h" +#include "common/utils.h" #include "config.h" -#include "utils.h" #include #include #include