From 5902bf3ebbaf69029112195ce69d1bf995d845da Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Wed, 8 Oct 2025 12:35:18 +0545 Subject: [PATCH] feat: add mute functionality via command-line option and runtime file check --- src/keyvibe_audio.c | 52 +++++++++++++- src/keyvibe_main.c | 169 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 188 insertions(+), 33 deletions(-) diff --git a/src/keyvibe_audio.c b/src/keyvibe_audio.c index 25d19e3..46b9b14 100644 --- a/src/keyvibe_audio.c +++ b/src/keyvibe_audio.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE +#include #include #include #include @@ -43,6 +44,7 @@ typedef struct { SoundPack g_sound_pack = {0}; float g_volume = 1.0f; int g_verbose = 0; +int g_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; @@ -390,6 +392,13 @@ int find_available_thread_slot() { } 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); @@ -476,11 +485,36 @@ void cleanup() { 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"; + } + snprintf(mute_file, sizeof(mute_file), "%s/keyvibe-mute-%d", rd, + (int)getuid()); + + 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; +} + int main(int argc, char *argv[]) { - if (argc < 2 || argc > 4) { - fprintf(stderr, "Usage: %s [volume] [verbose]\n", argv[0]); + if (argc < 2 || argc > 5) { + fprintf(stderr, "Usage: %s [volume] [verbose] [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 sound (default: 0)\n"); return 1; } if (argc >= 3) { @@ -500,6 +534,12 @@ int main(int argc, char *argv[]) { printf("Verbose mode enabled\n"); } } + if (argc >= 5) { + g_mute = atoi(argv[4]); + if (g_mute) { + printf("Sound muted\n"); + } + } if (load_sound_config(argv[1]) != 0) { fprintf(stderr, "Failed to load sound configuration\n"); return 1; @@ -508,16 +548,24 @@ int main(int argc, char *argv[]) { 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 file periodically + g_mute = read_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) { diff --git a/src/keyvibe_main.c b/src/keyvibe_main.c index b589523..d013a45 100644 --- a/src/keyvibe_main.c +++ b/src/keyvibe_main.c @@ -21,6 +21,7 @@ #define MAX_PATH_LENGTH 512 #define AUDIO_BASE_DIR KeyVibe_DATA_DIR "/audio" +#define USER_AUDIO_SUBPATH "/.local/share/keyvibe/audio" #define USER_CONFIG_FILENAME ".keyvibe.json" // Global variables for cleanup @@ -34,6 +35,7 @@ static char current_sound_name[MAX_PATH_LENGTH] = {0}; static int current_verbose = 0; static char current_config_path[MAX_PATH_LENGTH] = {0}; static char current_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"); @@ -46,6 +48,8 @@ void print_usage(const char *program_name) { printf(" --daemon Run in background (write PID file)\n"); printf(" --stop Stop background daemon\n"); printf(" --reload Send reload signal to running daemon\n"); + printf(" --mute Mute sound\n"); + printf(" --unmute Unmute sound\n"); printf(" -h, --help Show this help message\n"); printf(" -v, --verbose Enable verbose output\n"); printf("\nConfiguration:\n"); @@ -68,44 +72,100 @@ int list_sound_packs() { char path[MAX_PATH_LENGTH]; struct stat st; - dir = opendir(AUDIO_BASE_DIR); - if (dir == NULL) { - fprintf(stderr, "Error: Cannot open audio directory '%s'\n", - AUDIO_BASE_DIR); - fprintf(stderr, "Make sure you're running from the correct directory.\n"); - return 1; + // Determine user audio dir + char user_audio_dir[MAX_PATH_LENGTH] = {0}; + const char *home = getenv("HOME"); + if (!home || strlen(home) == 0) { + struct passwd *pw = getpwuid(getuid()); + if (pw && pw->pw_dir) + home = pw->pw_dir; + } + if (home) { + snprintf(user_audio_dir, sizeof(user_audio_dir), "%s%s", home, + USER_AUDIO_SUBPATH); } printf("Available sound packs:\n"); printf("======================\n"); - while ((entry = readdir(dir)) != NULL) { - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - snprintf(path, sizeof(path), "%s/%s", AUDIO_BASE_DIR, entry->d_name); - if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { - printf(" %s\n", entry->d_name); + // List user packs first (if available) + if (user_audio_dir[0] && (dir = opendir(user_audio_dir)) != NULL) { + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + snprintf(path, sizeof(path), "%s/%s", user_audio_dir, entry->d_name); + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + printf(" %s (user)\n", entry->d_name); + } } + closedir(dir); } - closedir(dir); + // Then list system packs + dir = opendir(AUDIO_BASE_DIR); + if (dir != NULL) { + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + snprintf(path, sizeof(path), "%s/%s", AUDIO_BASE_DIR, entry->d_name); + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + printf(" %s (system)\n", entry->d_name); + } + } + closedir(dir); + } + + return 0; +} + +static void get_user_audio_dir(char *buffer, size_t buflen) { + buffer[0] = '\0'; + const char *home = getenv("HOME"); + if (!home || strlen(home) == 0) { + struct passwd *pw = getpwuid(getuid()); + if (pw && pw->pw_dir) + home = pw->pw_dir; + } + if (home) + snprintf(buffer, buflen, "%s%s", home, USER_AUDIO_SUBPATH); +} + +static int resolve_sound_base_dir(const char *sound_name, char *out_basedir, + size_t out_sz) { + char user_audio_dir[MAX_PATH_LENGTH]; + get_user_audio_dir(user_audio_dir, sizeof(user_audio_dir)); + if (user_audio_dir[0]) { + char p[MAX_PATH_LENGTH]; + snprintf(p, sizeof(p), "%s/%s/config.json", user_audio_dir, sound_name); + if (access(p, R_OK) == 0) { + strncpy(out_basedir, user_audio_dir, out_sz - 1); + out_basedir[out_sz - 1] = '\0'; + return 1; + } + } + // Fallback to system dir + char p2[MAX_PATH_LENGTH]; + snprintf(p2, sizeof(p2), "%s/%s/config.json", AUDIO_BASE_DIR, sound_name); + if (access(p2, R_OK) == 0) { + strncpy(out_basedir, AUDIO_BASE_DIR, out_sz - 1); + out_basedir[out_sz - 1] = '\0'; + return 1; + } return 0; } int validate_sound_pack(const char *sound_name) { - char config_path[MAX_PATH_LENGTH]; - char sound_path[MAX_PATH_LENGTH]; - - snprintf(config_path, sizeof(config_path), "%s/%s/config.json", - AUDIO_BASE_DIR, sound_name); - snprintf(sound_path, sizeof(sound_path), "%s/%s", AUDIO_BASE_DIR, sound_name); - - if (access(sound_path, F_OK) != 0) { - fprintf(stderr, "Error: Sound pack '%s' not found in %s/\n", sound_name, - AUDIO_BASE_DIR); + char basedir[MAX_PATH_LENGTH]; + if (!resolve_sound_base_dir(sound_name, basedir, sizeof(basedir))) { + fprintf(stderr, + "Error: Sound pack '%s' not found in user or system dirs.\n", + sound_name); fprintf(stderr, "Use --list to see available sound packs.\n"); return 0; } + char config_path[MAX_PATH_LENGTH]; + snprintf(config_path, sizeof(config_path), "%s/%s/config.json", basedir, + sound_name); if (access(config_path, R_OK) != 0) { fprintf(stderr, "Error: Config file not found: %s\n", config_path); return 0; @@ -174,7 +234,11 @@ static const char *get_runtime_dir() { static int build_paths_for_sound(const char *sound_name, char *out_config_path, size_t out_config_sz, char *out_sound_dir, size_t out_sound_sz) { - size_t base_len = strlen(AUDIO_BASE_DIR); + char basedir[MAX_PATH_LENGTH]; + if (!resolve_sound_base_dir(sound_name, basedir, sizeof(basedir))) { + return 0; + } + size_t base_len = strlen(basedir); size_t name_len = strlen(sound_name); const char *cfg_suffix = "/config.json"; size_t cfg_suffix_len = strlen(cfg_suffix); @@ -186,9 +250,9 @@ static int build_paths_for_sound(const char *sound_name, char *out_config_path, fprintf(stderr, "Error: sound pack name too long for directory buffer.\n"); return 0; } - snprintf(out_config_path, out_config_sz, "%s/%s%s", AUDIO_BASE_DIR, - sound_name, cfg_suffix); - snprintf(out_sound_dir, out_sound_sz, "%s/%s", AUDIO_BASE_DIR, sound_name); + snprintf(out_config_path, out_config_sz, "%s/%s%s", basedir, sound_name, + cfg_suffix); + snprintf(out_sound_dir, out_sound_sz, "%s/%s", basedir, sound_name); return 1; } @@ -267,7 +331,7 @@ static void stop_children() { } static int start_children(const char *sound_dir, const char *config_path, - int volume, int verbose) { + int volume, int verbose, int mute) { (void)config_path; int pipefd[2]; if (pipe(pipefd) == -1) { @@ -296,8 +360,10 @@ static int start_children(const char *sound_dir, const char *config_path, snprintf(volume_str, sizeof(volume_str), "%d", 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); execl(sound_player_path, "keyvibe-audio", "config.json", volume_str, - verbose_str, (char *)NULL); + verbose_str, mute_str, (char *)NULL); perror("execl keyvibe-audio"); exit(1); } @@ -330,6 +396,19 @@ static void handle_sighup(int sig) { reload_requested = 1; } +static void write_mute_state(int mute) { + char mute_file[MAX_PATH_LENGTH]; + const char *rd = get_runtime_dir(); + snprintf(mute_file, sizeof(mute_file), "%s/keyvibe-mute-%d", rd, + (int)getuid()); + + FILE *f = fopen(mute_file, "w"); + if (f) { + fprintf(f, "%d\n", mute); + fclose(f); + } +} + struct inotify_thread_args { char path[MAX_PATH_LENGTH]; }; @@ -428,6 +507,8 @@ int main(int argc, char *argv[]) { {"daemon", no_argument, 0, 1000}, {"stop", no_argument, 0, 1001}, {"reload", no_argument, 0, 1002}, + {"mute", no_argument, 0, 1003}, + {"unmute", no_argument, 0, 1004}, {"help", no_argument, 0, 'h'}, {"verbose", no_argument, 0, 'v'}, {0, 0, 0, 0}}; @@ -489,6 +570,32 @@ int main(int argc, char *argv[]) { printf("KeyVibe reload signal sent.\n"); return 0; } + case 1003: { + 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 1; + } + // Write mute state to file instead of using signals + write_mute_state(1); + printf("KeyVibe muted.\n"); + return 0; + } + case 1004: { + 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 1; + } + // Write mute state to file instead of using signals + write_mute_state(0); + printf("KeyVibe unmuted.\n"); + return 0; + } case 'h': print_usage(argv[0]); return 0; @@ -607,7 +714,7 @@ int main(int argc, char *argv[]) { printf("Press Ctrl+C to exit.\n"); } } - if (!start_children(sound_dir, config_path, volume, verbose)) { + if (!start_children(sound_dir, config_path, volume, verbose, current_mute)) { return 1; } int status; @@ -649,7 +756,7 @@ int main(int argc, char *argv[]) { } stop_children(); start_children(current_sound_dir, current_config_path, current_volume, - current_verbose); + current_verbose, current_mute); } continue; }