feat(src/audio): add system volume following and mute handling
New Features: - Added command-line option for system volume following and a background thread that polls the default audio sink volume - Integrated the polled volume multiplier into playback volume calculations - Introduced new runtime states and flags for system volume tracking Enhancements: - Replaced scattered printf debugging with standardized LOG_ERROR/DEBUG/WARN macros - Pre‑read all runtime states before spawning threads, simplifying the main loop - Detached the poller thread to run continuously without blocking the main loop Bug Fixes: - Extended argument count validation to accommodate the new flag - Updated thread creation error logging to use LOG_ERROR instead of fprintf - Fixed potential race by initializing the volume multiplier before playback threads start
This commit is contained in:
2 files changed
+108
-111
No files matched your search
+65
-19
@@ -1,17 +1,22 @@
|
||||
#define _XOPEN_SOURCE 500
|
||||
#include "audio/playback.h"
|
||||
#include "audio/types.h"
|
||||
#include "common/utils.h"
|
||||
#include "common/ipc.h"
|
||||
#include "common/log.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>
|
||||
#include <pthread.h>
|
||||
|
||||
int g_keyboard_enabled = 1;
|
||||
int g_mouse_enabled = 1;
|
||||
int g_system_volume_following = 0;
|
||||
float g_system_volume_multiplier = 1.0f;
|
||||
|
||||
int load_sound_config(const char *config_path);
|
||||
|
||||
@@ -59,12 +64,46 @@ static int read_mouse_enabled_state() {
|
||||
return read_runtime_state("mouse-enabled", 1);
|
||||
}
|
||||
|
||||
static int read_system_volume_following_state() {
|
||||
return read_runtime_state("sysvol-following", 0);
|
||||
}
|
||||
|
||||
void *system_volume_poller_thread(void *arg) {
|
||||
(void)arg;
|
||||
while (1) {
|
||||
g_mute = read_mute_state();
|
||||
g_keyboard_mute = read_keyboard_mute_state();
|
||||
g_mouse_mute = read_mouse_mute_state();
|
||||
g_keyboard_enabled = read_keyboard_enabled_state();
|
||||
g_mouse_enabled = read_mouse_enabled_state();
|
||||
g_system_volume_following = read_system_volume_following_state();
|
||||
|
||||
if (g_system_volume_following) {
|
||||
char buffer[128];
|
||||
FILE *fp = popen("wpctl get-volume @DEFAULT_AUDIO_SINK@", "r");
|
||||
if (fp != NULL) {
|
||||
if (fgets(buffer, sizeof(buffer), fp) != NULL) {
|
||||
float vol = 1.0f;
|
||||
if (sscanf(buffer, "Volume: %f", &vol) == 1) {
|
||||
g_system_volume_multiplier = vol;
|
||||
}
|
||||
}
|
||||
pclose(fp);
|
||||
}
|
||||
} else {
|
||||
g_system_volume_multiplier = 1.0f;
|
||||
}
|
||||
usleep(500000); // 500ms
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2 || argc > 11) {
|
||||
if (argc < 2 || argc > 12) {
|
||||
safe_fprintf(stderr,
|
||||
"Usage: %s <config.json> [volume] [verbose] [mute] [mouse_config] "
|
||||
"[mouse_volume] [keyboard_mute] [mouse_mute] [keyboard_enabled] "
|
||||
"[mouse_enabled]\n",
|
||||
"[mouse_enabled] [system_volume_following]\n",
|
||||
argv[0]);
|
||||
safe_fprintf(stderr, " volume: 0-100 (default: 50)\n");
|
||||
safe_fprintf(stderr, " verbose: 1 to enable verbose output (default: 0)\n");
|
||||
@@ -135,6 +174,11 @@ int main(int argc, char *argv[]) {
|
||||
if (g_verbose)
|
||||
printf("Mouse enabled: %s\n", g_mouse_enabled ? "yes" : "no");
|
||||
}
|
||||
if (argc >= 12) {
|
||||
g_system_volume_following = atoi(argv[11]);
|
||||
if (g_verbose)
|
||||
printf("System volume following: %s\n", g_system_volume_following ? "yes" : "no");
|
||||
}
|
||||
if (load_sound_config(argv[1]) != 0) {
|
||||
safe_fprintf(stderr, "Failed to load keyboard sound configuration\n");
|
||||
return 1;
|
||||
@@ -143,17 +187,22 @@ int main(int argc, char *argv[]) {
|
||||
safe_fprintf(stderr, "Failed to initialize audio\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read states initially before starting threads and loops
|
||||
g_mute = read_mute_state();
|
||||
g_keyboard_mute = read_keyboard_mute_state();
|
||||
g_mouse_mute = read_mouse_mute_state();
|
||||
g_keyboard_enabled = read_keyboard_enabled_state();
|
||||
g_mouse_enabled = read_mouse_enabled_state();
|
||||
g_system_volume_following = read_system_volume_following_state();
|
||||
|
||||
pthread_t poller_thread;
|
||||
pthread_create(&poller_thread, NULL, system_volume_poller_thread, NULL);
|
||||
pthread_detach(poller_thread);
|
||||
|
||||
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();
|
||||
|
||||
g_keyboard_enabled = read_keyboard_enabled_state();
|
||||
g_mouse_enabled = read_mouse_enabled_state();
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(STDIN_FILENO, &readfds);
|
||||
timeout.tv_sec = 1;
|
||||
@@ -172,19 +221,16 @@ int main(int argc, char *argv[]) {
|
||||
continue;
|
||||
}
|
||||
if (FD_ISSET(STDIN_FILENO, &readfds)) {
|
||||
if (fgets(line, sizeof(line), stdin) == NULL) {
|
||||
VbxEvent ev;
|
||||
if (fread(&ev, sizeof(VbxEvent), 1, stdin) != 1) {
|
||||
if (feof(stdin)) {
|
||||
if (g_verbose)
|
||||
printf("EOF reached on stdin\n");
|
||||
LOG_DEBUG("EOF reached on stdin");
|
||||
} else {
|
||||
perror("fgets");
|
||||
perror("fread");
|
||||
}
|
||||
break;
|
||||
}
|
||||
int key_code, is_pressed;
|
||||
if (parse_keyboard_event(line, &key_code, &is_pressed) == 0) {
|
||||
play_sound_segment(key_code, is_pressed);
|
||||
}
|
||||
play_sound_segment(ev.key_code, ev.is_pressed);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
+43
-92
@@ -1,7 +1,7 @@
|
||||
#include "audio/playback.h"
|
||||
#include "audio/types.h"
|
||||
#include "common/utils.h"
|
||||
#include <json-c/json.h>
|
||||
#include "common/log.h"
|
||||
#include <pthread.h>
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/simple.h>
|
||||
@@ -15,32 +15,37 @@ pthread_t sound_threads[MAX_CONCURRENT_SOUNDS];
|
||||
volatile int thread_active[MAX_CONCURRENT_SOUNDS] = {0};
|
||||
pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static inline float get_boosted_system_volume(float vol_multiplier) {
|
||||
if (vol_multiplier >= 1.0f) return vol_multiplier;
|
||||
if (vol_multiplier <= 0.0f) return 0.0f;
|
||||
float inv = 1.0f - vol_multiplier;
|
||||
return 1.0f - (inv * inv * inv);
|
||||
}
|
||||
|
||||
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 sound pack config\n");
|
||||
fprintf(stderr, "Check that your sound pack has a valid config.json file.\n");
|
||||
LOG_ERROR("No sound file specified in sound pack config");
|
||||
LOG_ERROR("Check that your sound pack has a valid config.json file.");
|
||||
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);
|
||||
LOG_ERROR("Sound file not accessible: %s", 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));
|
||||
LOG_ERROR("Could not open sound file: %s", g_sound_pack.sound_file);
|
||||
LOG_ERROR("libsndfile error: %s", 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);
|
||||
}
|
||||
LOG_DEBUG("Sound file info: %ld frames, %d channels, %d Hz",
|
||||
(long)g_sound_pack.sf_info.frames, g_sound_pack.sf_info.channels,
|
||||
g_sound_pack.sf_info.samplerate);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -56,17 +61,13 @@ void *play_sound_thread(void *arg) {
|
||||
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");
|
||||
}
|
||||
LOG_DEBUG("Thread %d: Playing %s sound for key %d (%s)", 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");
|
||||
}
|
||||
LOG_DEBUG("Thread %d: %s sound is muted", thread_id,
|
||||
is_mouse_event ? "Mouse" : "Keyboard");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
if (sound_pack->is_multi) {
|
||||
@@ -80,21 +81,17 @@ void *play_sound_thread(void *arg) {
|
||||
} 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");
|
||||
}
|
||||
LOG_DEBUG("Thread %d: No sound file found for key %d (%s)", 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);
|
||||
}
|
||||
LOG_DEBUG("Thread %d: Using sound file: %s", 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, "Error: Could not open sound file: %s\n", file_to_play);
|
||||
fprintf(stderr, "Details: %s\n", sf_strerror(NULL));
|
||||
fprintf(stderr, "Check that the audio file exists and is readable.\n");
|
||||
LOG_ERROR("Could not open sound file: %s", file_to_play);
|
||||
LOG_ERROR("Details: %s", sf_strerror(NULL));
|
||||
LOG_ERROR("Check that the audio file exists and is readable.");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
pa_sample_spec ss = {.format = PA_SAMPLE_S16LE,
|
||||
@@ -105,8 +102,7 @@ void *play_sound_thread(void *arg) {
|
||||
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));
|
||||
LOG_ERROR("Could not initialize PulseAudio: %s", pa_strerror(pa_error));
|
||||
sf_close(sf);
|
||||
goto exit_cleanup;
|
||||
}
|
||||
@@ -119,15 +115,15 @@ void *play_sound_thread(void *arg) {
|
||||
}
|
||||
sf_count_t read;
|
||||
while ((read = sf_readf_short(sf, buffer, frames)) > 0) {
|
||||
float vol_mult = volume * get_boosted_system_volume(g_system_volume_multiplier);
|
||||
for (sf_count_t i = 0; i < read * sf_info.channels; i++) {
|
||||
buffer[i] = (short)(buffer[i] * volume);
|
||||
buffer[i] = (short)(buffer[i] * vol_mult);
|
||||
}
|
||||
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));
|
||||
LOG_ERROR("PulseAudio write error: %s", pa_strerror(pa_write_error));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -139,16 +135,14 @@ void *play_sound_thread(void *arg) {
|
||||
} 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);
|
||||
}
|
||||
LOG_DEBUG("Thread %d: No mapping for key %d", 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");
|
||||
LOG_ERROR("Thread: Could not open sound file");
|
||||
goto exit_cleanup;
|
||||
}
|
||||
pa_sample_spec ss = {.format = PA_SAMPLE_S16LE,
|
||||
@@ -160,8 +154,7 @@ void *play_sound_thread(void *arg) {
|
||||
"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));
|
||||
LOG_ERROR("Thread: Could not initialize PulseAudio: %s", pa_strerror(pa_error));
|
||||
goto exit_cleanup;
|
||||
}
|
||||
sf_count_t start_frame = (mapping->start_ms * sf_info.samplerate) / 1000;
|
||||
@@ -175,8 +168,9 @@ void *play_sound_thread(void *arg) {
|
||||
goto exit_cleanup;
|
||||
}
|
||||
sf_count_t frames_read = sf_readf_short(sf, buffer, duration_frames);
|
||||
float vol_mult = volume * get_boosted_system_volume(g_system_volume_multiplier);
|
||||
for (sf_count_t i = 0; i < frames_read * sf_info.channels; i++) {
|
||||
buffer[i] = (short)(buffer[i] * volume);
|
||||
buffer[i] = (short)(buffer[i] * vol_mult);
|
||||
}
|
||||
int pa_write_error, pa_drain_error;
|
||||
pa_simple_write(pa_handle, buffer,
|
||||
@@ -209,10 +203,8 @@ 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");
|
||||
}
|
||||
LOG_DEBUG("Sound muted - ignoring key %d (%s)", key_code,
|
||||
is_pressed ? "press" : "release");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -224,32 +216,24 @@ void play_sound_segment(int key_code, int is_pressed) {
|
||||
// For now, we'll add a simple check here
|
||||
extern int g_mouse_enabled;
|
||||
if (!g_mouse_enabled) {
|
||||
if (g_verbose) {
|
||||
printf("Mouse sounds disabled - ignoring mouse event %d\n", key_code);
|
||||
}
|
||||
LOG_DEBUG("Mouse sounds disabled - ignoring mouse event %d", key_code);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// For keyboard events, check if keyboard is enabled
|
||||
extern int g_keyboard_enabled;
|
||||
if (!g_keyboard_enabled) {
|
||||
if (g_verbose) {
|
||||
printf("Keyboard sounds disabled - ignoring key %d\n", key_code);
|
||||
}
|
||||
LOG_DEBUG("Keyboard sounds disabled - ignoring key %d", key_code);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!g_sound_pack.is_multi && !is_pressed) {
|
||||
if (g_verbose) {
|
||||
printf("Single mode: Ignoring key release for key %d\n", key_code);
|
||||
}
|
||||
LOG_DEBUG("Single mode: Ignoring key release for key %d", key_code);
|
||||
return;
|
||||
}
|
||||
int slot = find_available_thread_slot();
|
||||
if (slot == -1) {
|
||||
if (g_verbose) {
|
||||
printf("Warning: No available thread slots\n");
|
||||
}
|
||||
LOG_WARN("No available thread slots");
|
||||
return;
|
||||
}
|
||||
PlaybackData *data = malloc(sizeof(PlaybackData));
|
||||
@@ -264,43 +248,10 @@ void play_sound_segment(int key_code, int is_pressed) {
|
||||
0) {
|
||||
thread_active[slot] = 0;
|
||||
free(data);
|
||||
fprintf(stderr, "Failed to create sound thread\n");
|
||||
LOG_ERROR("Failed to create sound thread");
|
||||
} 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 = xstrdup(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;
|
||||
}
|
||||
Reference in new issue
Block a user