oops many changes
This commit is contained in:
25 files changed
+1413
-1234
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef KEYVIBE_PROCESS_H
|
||||
#define KEYVIBE_PROCESS_H
|
||||
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef KEYVIBE_WATCH_H
|
||||
#define KEYVIBE_WATCH_H
|
||||
|
||||
int start_config_watcher(const char *path);
|
||||
|
||||
#endif // KEYVIBE_WATCH_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
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef KEYVIBE_AUDIO_TYPES_H
|
||||
#define KEYVIBE_AUDIO_TYPES_H
|
||||
|
||||
#include <sndfile.h>
|
||||
|
||||
#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
|
||||
@@ -7,5 +7,3 @@ void write_runtime_keyboard_mute_file(int mute);
|
||||
void write_runtime_mouse_mute_file(int mute);
|
||||
|
||||
#endif // KEYVIBE_MUTE_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
|
||||
|
||||
@@ -18,5 +18,3 @@
|
||||
#define KeyVibe_BIN_DIR PACKAGE_PREFIX "/bin"
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
+10
-6
@@ -2,14 +2,18 @@
|
||||
#define KEYVIBE_SOUNDPACKS_H
|
||||
#include <stddef.h>
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "app/process.h"
|
||||
#include "common/utils.h"
|
||||
#include "config.h"
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "app/reload.h"
|
||||
#include "app/process.h"
|
||||
#include "soundpacks.h"
|
||||
#include "user_config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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) {
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "app/watch.h"
|
||||
#include "app/process.h"
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
-787
@@ -1,787 +0,0 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "utils.h"
|
||||
#include <errno.h>
|
||||
#include <json-c/json.h>
|
||||
#include <libgen.h>
|
||||
#include <pthread.h>
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/simple.h>
|
||||
#include <sndfile.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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 <config.json> [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;
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
#include "audio/types.h"
|
||||
#include "common/utils.h"
|
||||
#include <json-c/json.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
#include "audio/playback.h"
|
||||
#include "audio/types.h"
|
||||
#include "common/utils.h"
|
||||
#include <errno.h>
|
||||
#include <json-c/json.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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 <config.json> [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;
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
#include "audio/playback.h"
|
||||
#include "audio/types.h"
|
||||
#include <json-c/json.h>
|
||||
#include <pthread.h>
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/simple.h>
|
||||
#include <sndfile.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
#include "app/cli.h"
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "utils.h"
|
||||
#include "common/utils.h"
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Mute helpers moved here for re-use
|
||||
int read_runtime_mute_file() {
|
||||
char mute_file[1024];
|
||||
const char *rd = getenv("XDG_RUNTIME_DIR");
|
||||
@@ -1,5 +1,5 @@
|
||||
/* #include "config_io.h" */
|
||||
#include "utils.h"
|
||||
#include "common/utils.h"
|
||||
#include "user_config.h"
|
||||
#include <errno.h>
|
||||
#include <json-c/json.h>
|
||||
#include <pwd.h>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "utils.h"
|
||||
#include "common/utils.h"
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
+30
-419
@@ -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 <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
/* #include "soundpacks.h" */
|
||||
#include "soundpacks.h"
|
||||
#include "common/utils.h"
|
||||
#include "config.h"
|
||||
#include "utils.h"
|
||||
#include <dirent.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
Reference in new issue
Block a user