Files
vbx/src/main.c
T
2025-10-13 21:35:35 +05:45

906 lines
30 KiB
C

#define _POSIX_C_SOURCE 200809L
#include "config.h"
#include "config_io.h"
#include "mute.h"
#include "soundpacks.h"
#include "utils.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <json-c/json.h>
#include <pthread.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#define MAX_PATH_LENGTH 1024
#define AUDIO_BASE_DIR KeyVibe_DATA_DIR "/soundpacks"
#define KEYBOARD_AUDIO_DIR AUDIO_BASE_DIR "/keyboard"
#define MOUSE_AUDIO_DIR AUDIO_BASE_DIR "/mouse"
#define USER_KEYBOARD_AUDIO_SUBPATH "/.local/share/keyvibe/soundpacks/keyboard"
#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;
static int current_keyboard_volume = 50;
static int current_mouse_volume = 50;
static char current_sound_name[MAX_PATH_LENGTH] = {0};
static char current_mouse_sound_name[MAX_PATH_LENGTH] = {0};
static int current_verbose = 0;
static int current_keyboard_mute = 0;
static int current_mouse_mute = 0;
static char current_config_path[MAX_PATH_LENGTH] = {0};
static char current_sound_dir[MAX_PATH_LENGTH] = {0};
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
int sound_name_owned = 1;
int mouse_sound_name_owned = 1;
int verbose = 0;
int list_sounds = 0;
int flag_daemon = 0;
int flag_stop = 0;
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}};
int volume = 50;
char *cli_sound = NULL;
char *cli_mouse_sound = NULL;
int cli_volume = -1;
int cli_keyboard_volume = -1;
int cli_mouse_volume = -1;
int cli_keyboard_mute = -1;
int cli_mouse_mute = -1;
char user_cfg_path[MAX_PATH_LENGTH];
if (get_user_config_path(user_cfg_path, sizeof(user_cfg_path))) {
if (access(user_cfg_path, R_OK) == 0) {
char *cfg_keyboard_sound = NULL;
char *cfg_mouse_sound = NULL;
int cfg_keyboard_volume = volume;
int cfg_mouse_volume = volume;
if (read_user_config(user_cfg_path, &cfg_keyboard_sound, &cfg_mouse_sound,
&cfg_keyboard_volume, &cfg_mouse_volume)) {
if (cfg_keyboard_sound) {
if (sound_name_owned) {
free(sound_name);
}
sound_name = cfg_keyboard_sound; // now owned, must free on exit paths
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_owned = 1;
}
volume = cfg_keyboard_volume; // Use keyboard volume as main volume for
// CLI compatibility
}
}
}
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;
}
}
}
int opt;
while ((opt = getopt_long(argc, argv, "S:M:V:K:O:lhdsm::u::v", long_options,
NULL)) != -1) {
switch (opt) {
case 'd':
flag_daemon = 1;
break;
case 's':
flag_stop = 1;
break;
case 'S':
cli_sound = optarg;
break;
case 'M':
cli_mouse_sound = optarg;
break;
case 'V':
cli_volume = atoi(optarg);
if (cli_volume < 0)
cli_volume = 0;
if (cli_volume > 100)
cli_volume = 100;
break;
case 'K':
cli_keyboard_volume = atoi(optarg);
if (cli_keyboard_volume < 0)
cli_keyboard_volume = 0;
if (cli_keyboard_volume > 100)
cli_keyboard_volume = 100;
break;
case 'O':
cli_mouse_volume = atoi(optarg);
if (cli_mouse_volume < 0)
cli_mouse_volume = 0;
if (cli_mouse_volume > 100)
cli_mouse_volume = 100;
break;
case 'm':
if (optarg == NULL) {
// No argument provided, mute both
cli_keyboard_mute = 1;
cli_mouse_mute = 1;
current_mute = 1; // global mute
} else if (strcmp(optarg, "keyboard") == 0) {
cli_keyboard_mute = 1;
write_runtime_keyboard_mute_file(1);
} else if (strcmp(optarg, "mouse") == 0) {
cli_mouse_mute = 1;
write_runtime_mouse_mute_file(1);
} else if (strcmp(optarg, "both") == 0) {
cli_keyboard_mute = 1;
cli_mouse_mute = 1;
current_mute = 1; // global mute
} else {
fprintf(stderr,
"Invalid mute option: %s. Use 'keyboard', 'mouse', or 'both'\n",
optarg);
return 1;
}
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
} else if (strcmp(optarg, "keyboard") == 0) {
cli_keyboard_mute = 0;
write_runtime_keyboard_mute_file(0);
} else if (strcmp(optarg, "mouse") == 0) {
cli_mouse_mute = 0;
write_runtime_mouse_mute_file(0);
} else if (strcmp(optarg, "both") == 0) {
cli_keyboard_mute = 0;
cli_mouse_mute = 0;
current_mute = 0; // clear global mute
} else {
fprintf(
stderr,
"Invalid unmute option: %s. Use 'keyboard', 'mouse', or 'both'\n",
optarg);
return 1;
}
break;
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) {
free(sound_name);
}
return 0;
case 'v':
verbose = 1;
break;
default:
print_usage(argv[0]);
if (sound_name_owned) {
free(sound_name);
}
return 1;
}
}
if (list_sounds) {
int rc = list_sound_packs();
if (sound_name_owned) {
free(sound_name);
}
return rc;
}
build_pidfile_path(pidfile_path, sizeof(pidfile_path));
if (flag_stop) {
pid_t running_pid = 0;
if (!require_running_pid(&running_pid))
return 1;
if (kill(running_pid, SIGTERM) != 0)
return (perror("kill"), 1);
for (int i = 0; i < 30; i++) {
if (!process_is_running(running_pid)) {
unlink(pidfile_path);
printf("KeyVibe stopped.\n");
return 0;
}
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 100000000L;
nanosleep(&ts, NULL);
}
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
config_updated = 1;
}
if (cli_mouse_sound != NULL) {
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
mouse_sound_name = cli_mouse_sound;
mouse_sound_name_owned = 0; // cli_mouse_sound is not owned by us
config_updated = 1;
}
if (cli_volume >= 0) {
volume = cli_volume;
config_updated = 1;
}
if (cli_keyboard_volume >= 0) {
current_keyboard_volume = cli_keyboard_volume;
config_updated = 1;
}
if (cli_mouse_volume >= 0) {
current_mouse_volume = cli_mouse_volume;
config_updated = 1;
}
if (cli_keyboard_mute >= 0) {
current_keyboard_mute = cli_keyboard_mute;
config_updated = 1;
}
if (cli_mouse_mute >= 0) {
current_mouse_mute = cli_mouse_mute;
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);
}
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
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,
current_keyboard_volume, current_mouse_volume)) {
fprintf(stderr, "Warning: Failed to update config file %s\n",
user_cfg_path);
} else if (verbose) {
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) {
if (!write_user_config(user_cfg_path, sound_name, mouse_sound_name,
current_keyboard_volume, current_mouse_volume)) {
fprintf(stderr, "Warning: Failed to write %s\n", user_cfg_path);
} else if (verbose) {
fprintf(stderr, "Created default config %s\n", user_cfg_path);
}
}
}
if (!validate_keyboard_sound_pack(sound_name)) {
if (sound_name_owned) {
free(sound_name);
}
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
return 1;
}
if (!validate_mouse_sound_pack(mouse_sound_name)) {
if (sound_name_owned) {
free(sound_name);
}
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
return 1;
}
char get_key_presses_path[MAX_PATH_LENGTH];
char sound_player_path[MAX_PATH_LENGTH];
snprintf(get_key_presses_path, sizeof(get_key_presses_path),
"%s/keyvibe-input", KeyVibe_BIN_DIR);
snprintf(sound_player_path, sizeof(sound_player_path), "%s/keyvibe-audio",
KeyVibe_BIN_DIR);
if (access(get_key_presses_path, X_OK) != 0 ||
access(sound_player_path, X_OK) != 0)
return errorf("Error: Cannot find or execute required binaries in %s\n",
KeyVibe_BIN_DIR);
signal(SIGINT, cleanup_processes);
signal(SIGTERM, cleanup_processes);
if (flag_daemon) {
pid_t existing = 0;
if (read_pidfile(pidfile_path, &existing) && process_is_running(existing)) {
fprintf(stderr,
"KeyVibe already running (pid %ld). Use --stop to stop it.\n",
(long)existing);
return 1;
}
daemonize_self();
is_daemon = 1;
write_pidfile(pidfile_path, getpid());
}
char config_path[MAX_PATH_LENGTH];
char sound_dir[MAX_PATH_LENGTH];
char mouse_config_path[MAX_PATH_LENGTH];
char mouse_sound_dir[MAX_PATH_LENGTH];
if (!build_paths_for_keyboard_sound(sound_name, config_path,
sizeof(config_path), sound_dir,
sizeof(sound_dir))) {
if (sound_name_owned) {
free(sound_name);
}
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
return 1;
}
if (!build_paths_for_mouse_sound(mouse_sound_name, mouse_config_path,
sizeof(mouse_config_path), mouse_sound_dir,
sizeof(mouse_sound_dir))) {
if (sound_name_owned) {
free(sound_name);
}
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
return 1;
}
current_keyboard_volume = volume;
current_mouse_volume = volume;
current_verbose = verbose;
strncpy(current_sound_name, sound_name, sizeof(current_sound_name) - 1);
current_sound_name[sizeof(current_sound_name) - 1] = '\0';
strncpy(current_mouse_sound_name, mouse_sound_name,
sizeof(current_mouse_sound_name) - 1);
current_mouse_sound_name[sizeof(current_mouse_sound_name) - 1] = '\0';
strncpy(current_config_path, config_path, sizeof(current_config_path) - 1);
current_config_path[sizeof(current_config_path) - 1] = '\0';
strncpy(current_sound_dir, sound_dir, sizeof(current_sound_dir) - 1);
current_sound_dir[sizeof(current_sound_dir) - 1] = '\0';
strncpy(current_mouse_config_path, mouse_config_path,
sizeof(current_mouse_config_path) - 1);
current_mouse_config_path[sizeof(current_mouse_config_path) - 1] = '\0';
strncpy(current_mouse_sound_dir, mouse_sound_dir,
sizeof(current_mouse_sound_dir) - 1);
current_mouse_sound_dir[sizeof(current_mouse_sound_dir) - 1] = '\0';
if (verbose && !is_daemon) {
printf("KeyVibe starting...\n");
printf("Keyboard sound pack: %s\n", sound_name);
printf("Mouse sound pack: %s\n", mouse_sound_name);
printf("Keyboard config file: %s\n", config_path);
printf("Mouse config file: %s\n", mouse_config_path);
printf("Keyboard working directory: %s\n", sound_dir);
printf("Mouse working directory: %s\n", mouse_sound_dir);
printf("Press Ctrl+C to exit.\n\n");
} else {
if (!is_daemon) {
printf("KeyVibe started with keyboard sound pack: %s, mouse sound pack: "
"%s\n",
sound_name, mouse_sound_name);
printf("Press Ctrl+C to exit.\n");
}
}
if (!start_children(sound_dir, config_path, volume, verbose, current_mute,
mouse_sound_dir, mouse_config_path, current_mouse_volume,
current_keyboard_mute, current_mouse_mute)) {
if (sound_name_owned) {
free(sound_name);
}
return 1;
}
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);
}
} else {
printf("Warning: Could not get user config path for file watching\n");
}
while (1) {
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");
}
} else {
printf("Failed to get user config path\n");
}
}
finished_pid = waitpid(-1, &status, WNOHANG);
if (finished_pid == 0) {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 100000000L;
nanosleep(&ts, NULL);
continue;
} else if (finished_pid > 0) {
if (finished_pid == keyboard_pid) {
if (!is_daemon)
printf("Keyboard listener exited with status %d\n",
WEXITSTATUS(status));
keyboard_pid = 0;
if (sound_pid > 0)
kill(sound_pid, SIGTERM);
} else if (finished_pid == sound_pid) {
if (!is_daemon)
printf("Sound player exited with status %d\n", WEXITSTATUS(status));
sound_pid = 0;
if (keyboard_pid > 0)
kill(keyboard_pid, SIGTERM);
}
break;
} else {
if (errno == ECHILD)
break;
}
}
if (!is_daemon) {
printf("KeyVibe exited.\n");
}
if (sound_name_owned) {
free(sound_name);
}
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
return 0;
}