Compare commits

12 Commits
Author SHA1 Message Date
sankalp 69b6ecd2d5 feat: update gitignore 2026-02-22 03:22:00 +05:45
sankalp 373e64fcad feat(include/common): add thread‑safe logging macros and error handling
- New Features
  - Wrap log output with flockfile/funlockfile to guarantee atomic writes.
  - Add LOG_PERROR macro to report messages with errno details.
- Enhancements
  - Include <errno.h> and <string.h> for robust error handling.
  - Remove unused errorf declaration.
- Bug Fixes
  - None.
2026-02-22 02:35:26 +05:45
sankalp 21cada4969 feat(src/common): replace errorf with LOG_ERROR and add verbose flag
New Features
- None

Enhancements
- Use LOG_ERROR for consistent error logging; add g_verbose flag.

Bug Fixes
- Remove deprecated errorf function.
2026-02-22 02:35:22 +05:45
sankalp 563ebdbbab feat(src): improve logging and refactor console output
Enhancements
- Replace printf and LOG_ERROR with LOG_INFO, LOG_WARN, LOG_PERROR throughout src/app/process.c, src/app/watch.c, and src/audio/config.c
- Use LOG_INFO for headings and formatted tables in sound‑pack listing
- Add missing includes and clean up header organization

Bug Fixes
- Use LOG_PERROR for pipe, fork, and execl failures to log errno correctly
- Ensure proper newline handling in config‑watcher log messages

New Features
- Introduce structured logging for configuration file changes and reload notifications
- Provide clearer usage guidance via LOG_INFO
2026-02-22 02:35:17 +05:45
sankalp a776be0bf3 feat(include/audio): Add sound configuration loading and extended sound pack structures
Introduce API to load sound configurations and expand SoundPack with richer audio data for multi‑key mappings and generic press handling, enabling more flexible audio management.

New Features
- Added `load_sound_config` prototype in `include/audio/config.h`.

Enhancements
- Extended `SoundPack` with generic press arrays, release data fields, and multi‑key mapping structs.
- Added `AudioVoice` struct and `MAX_MIXER_VOICES` for voice management.
- Added `generic_press_data`, `generic_press_samples`, `release_data`, and `release_samples` fields.
2026-02-22 02:35:11 +05:45
sankalp f7a2a7270b feat(root): add POSIX source definition and new audio/input/vbx modules
- New Features: Added audio, input, and vbx modules
- Enhancements: Updated Makefile to include POSIX source definition
- Bug Fixes: None
2026-02-22 02:35:03 +05:45
sankalp 95255580b1 feat(src/common): add write_runtime_system_volume_following_file
The new function writes a runtime state file to track the system volume following setting.

- New Features
  - Added `write_runtime_system_volume_following_file` to persist system volume following state.
2026-02-22 02:08:51 +05:45
sankalp f70e13dd70 feat(include): add system volume following support
Implement configurable system volume following and replace JSON IPC with binary struct.

- New Feature: Introduce `system_volume_following` CLI and runtime option.
- Enhancement: Extend CLI options, reload, and user‑config APIs to propagate the new flag.
- Enhancement: Add `VbxEvent` binary IPC struct and logging macros for better performance.
- Enhancement: Add helper to persist system volume following state.
- Bug Fix: Remove stale `parse_keyboard_event` prototype.
2026-02-22 02:08:46 +05:45
sankalp c44ae01226 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
2026-02-22 02:08:42 +05:45
sankalp ffef8d7eaf feat(root): add system-volume-following flag and multiplexer functionality
The flag enables automatic scaling of vbx output to match the host system's volume, improving user experience without manual adjustments.

- New Feature: Added `--system-volume-following=0/1` option to enable dynamic tracking.
- Enhancement: Implemented intelligent volume scaling that maintains punchiness across different OS volume levels.
- Enhancement: Updated documentation with detailed explanation of volume mechanics.
2026-02-22 02:08:38 +05:45
sankalp 6658d6561b feat(src): Add comprehensive logging and parameterize system volume following
Replace numerous printf statements with LOG_* macros, introduce system_volume_following parameter, and improve error handling throughout the daemon.

New Features
- Introduce LOG_INFO, LOG_ERROR, LOG_WARN for unified diagnostics.
- Add support for system_volume_following when spawning child processes.
- Log detailed startup information instead of raw printf.

Enhancements
- Refactor start_children signature to accept the new parameter.
- Use strerror(errno) in error logs for clearer messages.
- Improve signal handling output and reload request logging.

Bug Fixes
- Correct error formatting for pipe, fork, chdir, and execl failures.
- Ensure proper handling of system_volume_following_str initialization.
- Replace daemon‑specific prints with appropriate log levels.
2026-02-22 02:08:31 +05:45
sankalp 93491af2e2 . 2025-10-19 11:53:14 +05:45
26 changed files with 712 additions and 690 deletions

No files matched your search

+3
View File
@@ -3,6 +3,9 @@
/keyvibe
/keyvibe-audio
/keyvibe-input
/audio
/input
/vbx
# Object and static/shared library files
*.o
+1 -1
View File
@@ -1,6 +1,6 @@
CC = gcc
# Updated include paths for modular headers
CFLAGS = -Wall -Wextra -std=c99 -Iinclude -Iinclude/app -Iinclude/audio -Iinclude/common -Iinclude/sound -Iinclude/config
CFLAGS = -Wall -Wextra -std=c99 -D_POSIX_C_SOURCE=200809L -Iinclude -Iinclude/app -Iinclude/audio -Iinclude/common -Iinclude/sound -Iinclude/config
PREFIX ?= /usr
# Pass PACKAGE_PREFIX macro for config.h
+15 -1
View File
@@ -72,14 +72,28 @@ sudo make install
-u --unmute Unmute sound
-- enbale/disable DEVICE_NAME Enable/disable a device
-l, --list List available sound packs
-f, --system-volume-following=0/1 Enable/disable dynamic tracking of system volume (default: 0)
-h, --help Show this help message
-v, --verbose Enable verbose output
Notes:
- First run creates `~/.vbx.json`. Subsequent runs use it unless `-c` is supplied.
- First run creates `~/.vbx.json`. Subsequent runs use it unless `--config` is supplied.
- In daemon mode, editing `~/.vbx.json` will automatically reload.
## 🔊 Volume Mechanics
vbx lets you control your volume in three ways:
1. **Keyboard Volume** (`--volume`): Sets the base volume for your keyboard sounds (from 0 to 100).
2. **Mouse Volume** (`--mouse-volume`): Sets the base volume for your mouse clicks (from 0 to 100).
3. **System Volume Multiplexer** (`--system-volume-following=1`): When turned on, `vbx` automatically scales the app's sound to match your computer's main volume slider.
**How the multiplexer works:**
Instead of just cutting the volume linearly (which makes sounds too quiet at 50% OS volume), the multiplexer uses a gentle boost curve. This means if you lower your computer's volume, your typing sounds will still be punchy and easy to hear without ever being too loud!
**Example:**
If you set your keyboard volume to `50%` (`vbx --volume 50 --system-volume-following 1`), and your computer's master volume is currently at `80%`, `vbx` will intelligently scale its own output down to securely match your OS levels, without you having to change your `vbx` settings every time you watch a quiet video!
## 🎵 Sound Packs
System packs are installed under: `/usr/share/vbx/soundpacks/`
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

+1
View File
@@ -15,6 +15,7 @@ typedef struct {
int mouse_mute; // -1 unchanged, 0 unmute, 1 mute
int keyboard_enabled; // -1 unchanged, 0 disabled, 1 enabled
int mouse_enabled; // -1 unchanged, 0 disabled, 1 enabled
int system_volume_following; // -1 unchanged, 0 disabled, 1 enabled
} CliOptions;
void print_usage(const char *program_name);
+2 -1
View File
@@ -17,7 +17,8 @@ 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, int keyboard_enabled, int mouse_enabled);
int keyboard_mute, int mouse_mute, int keyboard_enabled,
int mouse_enabled, int system_volume_following);
void handle_sighup(int sig);
#endif // VBX_PROCESS_H
+3 -2
View File
@@ -7,8 +7,9 @@ int handle_reload(const char *user_cfg_path,
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, int *current_keyboard_enabled, int *current_mouse_enabled);
int current_keyboard_mute, int current_mouse_mute,
int *current_keyboard_enabled, int *current_mouse_enabled,
int *current_system_volume_following);
#endif // VBX_RELOAD_H
+8
View File
@@ -0,0 +1,8 @@
#ifndef VBX_AUDIO_CONFIG_H
#define VBX_AUDIO_CONFIG_H
#include "audio/types.h"
int load_sound_config(const char *config_path, SoundPack *pack);
#endif // VBX_AUDIO_CONFIG_H
-1
View File
@@ -2,7 +2,6 @@
#define VBX_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 // VBX_AUDIO_PLAYBACK_H
+22
View File
@@ -8,23 +8,43 @@
typedef struct {
int start_ms;
int duration_ms;
short *pcm_data;
uint32_t num_samples;
} SoundMapping;
typedef struct {
char press_file[256];
char release_file[256];
char generic_press_files[5][256];
short *generic_press_data[5];
uint32_t generic_press_samples[5];
int num_generic_press_files;
char sound_file[256];
short *release_data;
uint32_t release_samples;
SoundMapping key_mappings[512];
struct {
char *press;
char *release;
short *press_data;
uint32_t press_samples;
short *release_data;
uint32_t release_samples;
} multi_key_mappings[512];
int is_multi;
SF_INFO sf_info;
} SoundPack;
typedef struct {
short *data;
uint32_t total_samples;
uint32_t current_pos;
float volume;
int active;
} AudioVoice;
#define MAX_MIXER_VOICES 32
typedef struct {
int key_code;
int thread_id;
@@ -41,5 +61,7 @@ extern int g_keyboard_mute;
extern int g_mouse_mute;
extern int g_keyboard_enabled;
extern int g_mouse_enabled;
extern int g_system_volume_following;
extern float g_system_volume_multiplier;
#endif // VBX_AUDIO_TYPES_H
+13
View File
@@ -0,0 +1,13 @@
#ifndef VBX_IPC_H
#define VBX_IPC_H
#include <stdint.h>
// Lightweight binary structure for IPC between vbx-input and vbx-audio
// Replaces the heavy JSON parsing
typedef struct {
uint32_t key_code;
uint8_t is_pressed;
} VbxEvent;
#endif // VBX_IPC_H
+34
View File
@@ -0,0 +1,34 @@
#ifndef VBX_LOG_H
#define VBX_LOG_H
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <string.h>
// Assuming g_verbose is defined globally or we pass it
extern int g_verbose;
extern int is_daemon;
#define LOG_LEVEL_DEBUG 0
#define LOG_LEVEL_INFO 1
#define LOG_LEVEL_WARN 2
#define LOG_LEVEL_ERROR 3
#define LOG_MACRO(level, prefix, out_stream, ...) do { \
if (level == LOG_LEVEL_DEBUG && !g_verbose) break; \
flockfile(out_stream); \
fprintf(out_stream, "[VBX-%s] ", prefix); \
fprintf(out_stream, __VA_ARGS__); \
fprintf(out_stream, "\n"); \
fflush(out_stream); \
funlockfile(out_stream); \
} while(0)
#define LOG_DEBUG(...) LOG_MACRO(LOG_LEVEL_DEBUG, "DEBUG", stdout, __VA_ARGS__)
#define LOG_INFO(...) LOG_MACRO(LOG_LEVEL_INFO, "INFO", stdout, __VA_ARGS__)
#define LOG_WARN(...) LOG_MACRO(LOG_LEVEL_WARN, "WARN", stderr, __VA_ARGS__)
#define LOG_ERROR(...) LOG_MACRO(LOG_LEVEL_ERROR, "ERROR", stderr, __VA_ARGS__)
#define LOG_PERROR(msg) LOG_MACRO(LOG_LEVEL_ERROR, "ERROR", stderr, "%s: %s", msg, strerror(errno))
#endif // VBX_LOG_H
+3 -4
View File
@@ -8,10 +8,6 @@
// 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.
int errorf(const char *fmt, ...);
// Common runtime helpers
const char *get_runtime_dir(void);
void build_pidfile_path(char *buffer, size_t buflen);
@@ -29,6 +25,9 @@ void write_runtime_mouse_mute_file(int mute);
int write_runtime_keyboard_enabled_file(int enabled);
int write_runtime_mouse_enabled_file(int enabled);
// System volume following helper
int write_runtime_system_volume_following_file(int enabled);
// Helper functions
void int_to_str(char *buffer, size_t size, int value);
char *xstrdup(const char *s);
+4 -2
View File
@@ -8,11 +8,13 @@ char *get_user_config_path(char *buffer, size_t buflen);
// Write ~/.vbx.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 keyboard_enabled, int mouse_enabled);
int mouse_volume, int keyboard_enabled, int mouse_enabled,
int system_volume_following);
// Read ~/.vbx.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 *out_keyboard_enabled, int *out_mouse_enabled);
int *out_mouse_volume, int *out_keyboard_enabled, int *out_mouse_enabled,
int *out_system_volume_following);
#endif // VBX_CONFIG_IO_H
+16 -10
View File
@@ -1,11 +1,14 @@
#define _POSIX_C_SOURCE 200809L
#include "app/process.h"
#include "common/log.h"
#include "common/utils.h"
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -21,7 +24,7 @@ volatile sig_atomic_t reload_requested = 0;
void cleanup_processes(int sig) {
(void)sig;
if (!is_daemon) {
printf("\nShutting down VBX daemon...\n");
LOG_INFO("Shutting down VBX daemon...");
}
if (sound_pid > 0) {
kill(sound_pid, SIGTERM);
@@ -42,7 +45,7 @@ int require_running_pid(pid_t *out_pid) {
pid_t running_pid = 0;
if (!read_pidfile(pidfile_path, &running_pid) ||
!process_is_running(running_pid)) {
fprintf(stderr, "VBX daemon is not running.\n");
LOG_ERROR("VBX daemon is not running.");
return 0;
}
*out_pid = running_pid;
@@ -92,17 +95,17 @@ 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, int keyboard_enabled,
int mouse_enabled) {
int mouse_enabled, int system_volume_following) {
(void)config_path;
(void)mouse_sound_dir;
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
LOG_PERROR("pipe");
return 0;
}
sound_pid = fork();
if (sound_pid == -1) {
perror("fork");
LOG_PERROR("fork");
close(pipefd[0]);
close(pipefd[1]);
return 0;
@@ -115,12 +118,13 @@ int start_children(const char *sound_dir, const char *config_path, int volume,
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
if (chdir(sound_dir) != 0) {
perror("chdir");
LOG_ERROR("chdir: %s", strerror(errno));
exit(1);
}
char volume_str[32], mouse_volume_str[32], verbose_str[8], mute_str[8];
char keyboard_mute_str[8], mouse_mute_str[8];
char keyboard_enabled_str[8], mouse_enabled_str[8];
char system_volume_following_str[8];
int_to_str(volume_str, sizeof(volume_str), volume);
int_to_str(mouse_volume_str, sizeof(mouse_volume_str), mouse_volume);
@@ -130,17 +134,19 @@ int start_children(const char *sound_dir, const char *config_path, int volume,
int_to_str(mouse_mute_str, sizeof(mouse_mute_str), mouse_mute);
int_to_str(keyboard_enabled_str, sizeof(keyboard_enabled_str), keyboard_enabled);
int_to_str(mouse_enabled_str, sizeof(mouse_enabled_str), mouse_enabled);
int_to_str(system_volume_following_str, sizeof(system_volume_following_str),
system_volume_following);
execl(sound_player_path, "vbx-audio", "config.json", volume_str,
verbose_str, mute_str, mouse_config_path, mouse_volume_str,
keyboard_mute_str, mouse_mute_str, keyboard_enabled_str,
mouse_enabled_str, (char *)NULL);
perror("execl vbx-audio");
mouse_enabled_str, system_volume_following_str, (char *)NULL);
LOG_PERROR("execl vbx-audio");
exit(1);
}
keyboard_pid = fork();
if (keyboard_pid == -1) {
perror("fork");
LOG_PERROR("fork");
kill(sound_pid, SIGTERM);
close(pipefd[0]);
close(pipefd[1]);
@@ -154,7 +160,7 @@ int start_children(const char *sound_dir, const char *config_path, int volume,
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[1]);
execl(get_key_presses_path, "vbx-input", (char *)NULL);
perror("execl vbx-input");
LOG_PERROR("execl vbx-input");
exit(1);
}
close(pipefd[0]);
+28 -19
View File
@@ -1,5 +1,6 @@
#include "app/reload.h"
#include "app/process.h"
#include "common/log.h"
#include "common/utils.h"
#include "soundpacks.h"
#include "user_config.h"
@@ -12,26 +13,28 @@ int handle_reload(const char *user_cfg_path, char *current_sound_name,
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,
int *current_keyboard_enabled, int *current_mouse_enabled) {
int current_verbose, int current_keyboard_mute,
int current_mouse_mute, int *current_keyboard_enabled,
int *current_mouse_enabled, int *current_system_volume_following) {
int g_verbose = current_verbose;
char *cfg_keyboard_sound = NULL;
char *cfg_mouse_sound = NULL;
int cfg_keyboard_volume = *current_keyboard_volume;
int cfg_mouse_volume = *current_mouse_volume;
int cfg_keyboard_enabled = *current_keyboard_enabled;
int cfg_mouse_enabled = *current_mouse_enabled;
int cfg_system_volume_following = *current_system_volume_following;
if (read_user_config(user_cfg_path, &cfg_keyboard_sound, &cfg_mouse_sound,
&cfg_keyboard_volume, &cfg_mouse_volume,
&cfg_keyboard_enabled, &cfg_mouse_enabled)) {
printf("Read config - keyboard:%s mouse:%s kvol:%d mvol:%d kenabled:%d "
"menabled:%d\n",
&cfg_keyboard_enabled, &cfg_mouse_enabled, &cfg_system_volume_following)) {
LOG_DEBUG("Read config - keyboard:%s mouse:%s kvol:%d mvol:%d kenabled:%d "
"menabled:%d",
cfg_keyboard_sound ? cfg_keyboard_sound : "NULL",
cfg_mouse_sound ? cfg_mouse_sound : "NULL", cfg_keyboard_volume,
cfg_mouse_volume, cfg_keyboard_enabled, cfg_mouse_enabled);
int keyboard_changed = 0, mouse_changed = 0, keyboard_volume_changed = 0,
mouse_volume_changed = 0, keyboard_enabled_changed = 0,
mouse_enabled_changed = 0;
mouse_enabled_changed = 0, sysvol_following_changed = 0;
if (cfg_keyboard_sound &&
strcmp(current_sound_name, cfg_keyboard_sound) != 0) {
keyboard_changed = 1;
@@ -62,18 +65,23 @@ int handle_reload(const char *user_cfg_path, char *current_sound_name,
mouse_enabled_changed = 1;
*current_mouse_enabled = cfg_mouse_enabled;
}
printf("Config changed - keyboard:%d mouse:%d kvol:%d mvol:%d kenabled:%d "
"menabled:%d\n",
if (cfg_system_volume_following != *current_system_volume_following) {
sysvol_following_changed = 1;
*current_system_volume_following = cfg_system_volume_following;
}
LOG_DEBUG("Config changed - keyboard:%d mouse:%d kvol:%d mvol:%d kenabled:%d "
"menabled:%d",
keyboard_changed, mouse_changed, keyboard_volume_changed,
mouse_volume_changed, keyboard_enabled_changed,
mouse_enabled_changed);
if (keyboard_changed || mouse_changed || keyboard_volume_changed ||
mouse_volume_changed || keyboard_enabled_changed ||
mouse_enabled_changed) {
mouse_enabled_changed || sysvol_following_changed) {
// Persist enabled state to runtime files so the audio process picks it up
// immediately
write_runtime_keyboard_enabled_file(*current_keyboard_enabled);
write_runtime_mouse_enabled_file(*current_mouse_enabled);
write_runtime_system_volume_following_file(*current_system_volume_following);
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) {
@@ -87,31 +95,32 @@ int handle_reload(const char *user_cfg_path, char *current_sound_name,
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_mouse_config_path, *current_mouse_volume,
current_keyboard_mute, current_mouse_mute,
*current_keyboard_enabled, *current_mouse_enabled);
printf("Reloaded successfully: keyboard=%s, mouse=%s, kvol=%d, "
"mvol=%d\n",
*current_keyboard_enabled, *current_mouse_enabled,
*current_system_volume_following);
LOG_INFO("Reloaded successfully: keyboard=%s, mouse=%s, kvol=%d, "
"mvol=%d",
current_sound_name, current_mouse_sound_name,
*current_keyboard_volume, *current_mouse_volume);
return 1;
} else {
printf("Failed to build mouse sound paths\n");
LOG_ERROR("Failed to build mouse sound paths");
}
} else {
printf("Failed to build keyboard sound paths\n");
LOG_ERROR("Failed to build keyboard sound paths");
}
} else {
if (!keyboard_valid)
printf("Reload failed: invalid keyboard sound pack '%s'\n",
LOG_ERROR("Reload failed: invalid keyboard sound pack '%s'",
current_sound_name);
if (!mouse_valid)
printf("Reload failed: invalid mouse sound pack '%s'\n",
LOG_ERROR("Reload failed: invalid mouse sound pack '%s'",
current_mouse_sound_name);
}
}
} else {
printf("Failed to read user config\n");
LOG_ERROR("Failed to read user config");
}
return 0;
}
+8 -8
View File
@@ -1,6 +1,7 @@
#include "app/watch.h"
#include "common/utils.h"
#include "app/process.h"
#include "common/log.h"
#include "common/utils.h"
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
@@ -17,18 +18,17 @@ 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));
LOG_PERROR("Failed to initialize inotify");
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));
LOG_PERROR("Failed to add inotify watch");
close(fd);
return NULL;
}
printf("Watching config file: %s\n", a->path);
LOG_INFO("Watching config file: %s", a->path);
char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
while (1) {
ssize_t len = read(fd, buf, sizeof(buf));
@@ -40,10 +40,10 @@ static void *inotify_thread_fn(void *arg) {
nanosleep(&ts, NULL);
continue;
}
printf("inotify read error: %s\n", strerror(errno));
LOG_PERROR("inotify read error");
break;
}
printf("Config file changed, requesting reload...\n");
LOG_INFO("Config file changed, requesting reload...");
handle_sighup(0);
}
inotify_rm_watch(fd, wd);
@@ -60,7 +60,7 @@ int start_config_watcher(const char *path) {
pthread_detach(inotify_thread);
return 1;
} else {
printf("Warning: Config file %s does not exist, file watching disabled\n",
LOG_WARN("Warning: Config file %s does not exist, file watching disabled",
in_args.path);
return 0;
}
+110 -131
View File
@@ -1,4 +1,6 @@
#include "audio/config.h"
#include "audio/types.h"
#include "common/log.h"
#include "common/utils.h"
#include <errno.h>
#include <json-c/json.h>
@@ -12,7 +14,6 @@ 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;
@@ -32,12 +33,36 @@ static void get_full_path(char *buffer, size_t buffer_size,
}
}
static short *load_pcm_data(const char *path, uint32_t *out_samples, SF_INFO *out_info) {
if (!path || strlen(path) == 0) return NULL;
SF_INFO sf_info = {0};
SNDFILE *sf = sf_open(path, SFM_READ, &sf_info);
if (!sf) return NULL;
uint32_t total_samples = (uint32_t)(sf_info.frames * sf_info.channels);
short *buffer = malloc(total_samples * sizeof(short));
if (!buffer) {
sf_close(sf);
return NULL;
}
sf_count_t read = sf_readf_short(sf, buffer, sf_info.frames);
if (read < sf_info.frames) {
// Partial read or error
}
sf_close(sf);
if (out_samples) *out_samples = total_samples;
if (out_info) *out_info = sf_info;
return buffer;
}
int load_sound_config(const char *config_path) {
int load_sound_config(const char *config_path, SoundPack *pack) {
memset(pack, 0, sizeof(SoundPack));
FILE *file = fopen(config_path, "r");
if (!file) {
safe_fprintf(stderr, "Error: Cannot open sound pack config: %s\n", config_path);
perror("fopen");
LOG_ERROR("Error: Cannot open sound pack config: %s", config_path);
LOG_PERROR("fopen");
return -1;
}
char config_path_copy[1024];
@@ -47,20 +72,20 @@ int load_sound_config(const char *config_path) {
long size = ftell(file);
if (size < 0) {
fclose(file);
safe_fprintf(stderr, "Error: ftell failed while reading %s\n", config_path);
LOG_ERROR("Error: ftell failed while reading %s", config_path);
return -1;
}
errno = 0;
rewind(file);
if (errno != 0) {
fclose(file);
safe_fprintf(stderr, "Error: rewind failed while reading %s\n", config_path);
LOG_ERROR("Error: rewind failed while reading %s", config_path);
return -1;
}
char *json_string = malloc((size_t)size + 1);
if (!json_string) {
fclose(file);
safe_fprintf(stderr, "Error: Memory allocation failed\n");
LOG_ERROR("Error: Memory allocation failed");
return -1;
}
fread(json_string, 1, size, file);
@@ -69,170 +94,124 @@ int load_sound_config(const char *config_path) {
json_object *root = json_tokener_parse(json_string);
free(json_string);
if (!root) {
safe_fprintf(stderr, "Error: Invalid JSON in sound pack config\n");
safe_fprintf(stderr, "Check that the config.json file is properly formatted.\n");
LOG_ERROR("Error: Invalid JSON in sound pack config");
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;
pack->is_multi = strcmp(key_type, "multi") == 0;
LOG_DEBUG("Config loaded: Using %s mode", pack->is_multi ? "multi" : "single");
if (pack->is_multi) {
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];
safe_strncpy(temp_pattern, pattern, sizeof(temp_pattern));
char *brace_start = strstr(temp_pattern, "{");
char *brace_end = strstr(temp_pattern, "}");
if (brace_start && brace_end) {
*brace_start = '%';
*(brace_start + 1) = 'd';
safe_memmove(brace_start + 2, brace_end + 1,
strlen(brace_end + 1) + 1);
}
safe_snprintf_wrapper(temp_filename, sizeof(temp_filename), temp_pattern, i);
// Simple pattern replacement for {0}, {1}, etc.
safe_snprintf(temp_filename, sizeof(temp_filename), "%s", pattern);
char *brace = strstr(temp_filename, "{");
if (brace) {
*brace = '0' + i;
safe_memmove(brace + 1, brace + 3, strlen(brace + 3) + 1);
}
} else {
safe_snprintf_wrapper(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;
safe_snprintf(temp_filename, sizeof(temp_filename), pattern, i);
}
get_full_path(pack->generic_press_files[i], sizeof(pack->generic_press_files[i]), config_dir, temp_filename);
if (access(pack->generic_press_files[i], R_OK) == 0) {
pack->num_generic_press_files = i + 1;
pack->generic_press_data[i] = load_pcm_data(pack->generic_press_files[i], &pack->generic_press_samples[i], NULL);
} else 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]);
get_full_path(pack->generic_press_files[0], sizeof(pack->generic_press_files[0]), config_dir, pattern);
if (access(pack->generic_press_files[0], R_OK) == 0) {
pack->num_generic_press_files = 1;
pack->generic_press_data[0] = load_pcm_data(pack->generic_press_files[0], &pack->generic_press_samples[0], NULL);
LOG_DEBUG("Generic sound cached: %s", 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];
safe_strncpy(temp_release_file, json_object_get_string(obj),
sizeof(temp_release_file));
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);
get_full_path(pack->release_file, sizeof(pack->release_file), config_dir, json_object_get_string(obj));
pack->release_data = load_pcm_data(pack->release_file, &pack->release_samples, NULL);
LOG_DEBUG("Release sound cached: %s", pack->release_file);
}
if (json_object_object_get_ex(root, "defines", &obj)) {
json_object_object_foreach(obj, key, val) {
int key_code;
int key_code = -1;
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];
safe_strncpy(key_copy, key, dash - key + 1);
key_copy[dash - key] = '\0';
key_code = atoi(key_copy);
is_release = 1;
} else {
key_code = atoi(key);
}
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 *up = strstr(key, "-up");
if (up) { is_release = 1; key_code = atoi(key); }
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);
char full_path[1024];
get_full_path(full_path, sizeof(full_path), config_dir, json_object_get_string(val));
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);
pack->multi_key_mappings[key_code].release = xstrdup(full_path);
pack->multi_key_mappings[key_code].release_data = load_pcm_data(full_path, &pack->multi_key_mappings[key_code].release_samples, NULL);
LOG_DEBUG("Key %d-up cached: %s", key_code, full_path);
} 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);
pack->multi_key_mappings[key_code].press = xstrdup(full_path);
pack->multi_key_mappings[key_code].press_data = load_pcm_data(full_path, &pack->multi_key_mappings[key_code].press_samples, NULL);
LOG_DEBUG("Key %d cached: %s", key_code, full_path);
}
}
}
}
} else {
if (json_object_object_get_ex(root, "sound", &obj) ||
json_object_object_get_ex(root, "audio_file", &obj)) {
char temp_sound_file[256];
safe_strncpy(temp_sound_file, json_object_get_string(obj),
sizeof(temp_sound_file));
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);
if (json_object_object_get_ex(root, "sound", &obj) || json_object_object_get_ex(root, "audio_file", &obj)) {
get_full_path(pack->sound_file, sizeof(pack->sound_file), config_dir, json_object_get_string(obj));
uint32_t s;
short *d = load_pcm_data(pack->sound_file, &s, &pack->sf_info);
if (d) free(d);
LOG_DEBUG("Single mode sound file: %s (metadata loaded)", 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)) {
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);
}
int key_code = -1;
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));
}
pack->key_mappings[key_code].start_ms = json_object_get_int(json_object_array_get_idx(val, 0));
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 *timings;
if (json_object_object_get_ex(val, "timing", &timings) && json_object_array_length(timings) > 0) {
json_object *t = json_object_array_get_idx(timings, 0);
pack->key_mappings[key_code].start_ms = json_object_get_int(json_object_array_get_idx(t, 0));
pack->key_mappings[key_code].duration_ms = json_object_get_int(json_object_array_get_idx(t, 1));
}
}
if (pack->key_mappings[key_code].duration_ms > 0) {
uint32_t sr = pack->sf_info.samplerate;
uint32_t ch = pack->sf_info.channels;
uint32_t start_frame = (pack->key_mappings[key_code].start_ms * sr) / 1000;
uint32_t duration_frames = (pack->key_mappings[key_code].duration_ms * sr) / 1000;
SNDFILE *sf = sf_open(pack->sound_file, SFM_READ, &pack->sf_info);
if (sf) {
sf_seek(sf, start_frame, SEEK_SET);
pack->key_mappings[key_code].num_samples = duration_frames * ch;
pack->key_mappings[key_code].pcm_data = malloc(pack->key_mappings[key_code].num_samples * sizeof(short));
if (pack->key_mappings[key_code].pcm_data) {
sf_readf_short(sf, pack->key_mappings[key_code].pcm_data, duration_frames);
}
sf_close(sf);
}
}
}
+94 -64
View File
@@ -1,19 +1,23 @@
#define _XOPEN_SOURCE 500
#include "audio/config.h"
#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 load_sound_config(const char *config_path);
int g_system_volume_following = 0;
float g_system_volume_multiplier = 1.0f;
// Generic function to read runtime state files
static int read_runtime_state(const char *filename_suffix, int default_value) {
@@ -59,101 +63,133 @@ 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;
}
struct timespec ts = {0, 500000000L};
nanosleep(&ts, NULL); // 500ms
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc < 2 || argc > 11) {
safe_fprintf(stderr,
"Usage: %s <config.json> [volume] [verbose] [mute] [mouse_config] "
"[mouse_volume] [keyboard_mute] [mouse_mute] [keyboard_enabled] "
"[mouse_enabled]\n",
argv[0]);
safe_fprintf(stderr, " volume: 0-100 (default: 50)\n");
safe_fprintf(stderr, " verbose: 1 to enable verbose output (default: 0)\n");
safe_fprintf(stderr, " mute: 1 to mute all sound (default: 0)\n");
safe_fprintf(stderr, " mouse_config: path to mouse config.json (optional)\n");
safe_fprintf(stderr, " mouse_volume: 0-100 for mouse volume (optional)\n");
safe_fprintf(stderr, " keyboard_mute: 1 to mute keyboard sounds (optional)\n");
safe_fprintf(stderr, " mouse_mute: 1 to mute mouse sounds (optional)\n");
safe_fprintf(stderr,
" keyboard_enabled: 1 to enable keyboard sounds (optional)\n");
safe_fprintf(stderr, " mouse_enabled: 1 to enable mouse sounds (optional)\n");
if (argc < 2 || argc > 12) {
LOG_ERROR("Usage: %s <config.json> [volume] [verbose] [mute] [mouse_config] "
"[mouse_volume] [keyboard_mute] [mouse_mute] [keyboard_enabled] "
"[mouse_enabled] [system_volume_following]",
argv[0]);
LOG_ERROR(" volume: 0-100 (default: 50)");
LOG_ERROR(" verbose: 1 to enable verbose output (default: 0)");
LOG_ERROR(" mute: 1 to mute all sound (default: 0)");
LOG_ERROR(" mouse_config: path to mouse config.json (optional)");
LOG_ERROR(" mouse_volume: 0-100 for mouse volume (optional)");
LOG_ERROR(" keyboard_mute: 1 to mute keyboard sounds (optional)");
LOG_ERROR(" mouse_mute: 1 to mute mouse sounds (optional)");
LOG_ERROR(" keyboard_enabled: 1 to enable keyboard sounds (optional)");
LOG_ERROR(" mouse_enabled: 1 to enable mouse sounds (optional)");
return 1;
}
if (argc >= 3) {
int volume_percent = validate_volume(atoi(argv[2]));
g_volume = volume_percent / 100.0f;
if (g_verbose)
printf("Volume set to: %d%%\n", volume_percent);
LOG_DEBUG("Volume set to: %d%%", volume_percent);
} else {
if (g_verbose)
printf("Volume set to: 50%% (default)\n");
LOG_DEBUG("Volume set to: 50%% (default)");
}
if (argc >= 4) {
g_verbose = atoi(argv[3]);
if (g_verbose) {
printf("Verbose mode enabled\n");
LOG_INFO("Verbose mode enabled");
}
}
if (argc >= 5) {
g_mute = atoi(argv[4]);
if (g_mute) {
printf("Sound muted\n");
LOG_INFO("Sound muted");
}
}
if (argc >= 6) {
if (load_sound_config(argv[5]) != 0) {
safe_fprintf(stderr, "Failed to load mouse sound configuration\n");
if (load_sound_config(argv[5], &g_mouse_sound_pack) != 0) {
LOG_ERROR("Failed to load mouse sound configuration");
return 1;
}
g_mouse_sound_pack = g_sound_pack;
if (g_verbose) {
printf("Mouse sound pack loaded from: %s\n", argv[5]);
}
LOG_DEBUG("Mouse sound pack loaded from: %s", argv[5]);
}
if (argc >= 7) {
int mouse_volume_percent = validate_volume(atoi(argv[6]));
g_mouse_volume = mouse_volume_percent / 100.0f;
if (g_verbose)
printf("Mouse volume set to: %d%%\n", mouse_volume_percent);
LOG_DEBUG("Mouse volume set to: %d%%", 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");
LOG_DEBUG("Keyboard mute: %s", 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");
LOG_DEBUG("Mouse mute: %s", g_mouse_mute ? "enabled" : "disabled");
}
if (argc >= 10) {
g_keyboard_enabled = atoi(argv[9]);
if (g_verbose)
printf("Keyboard enabled: %s\n", g_keyboard_enabled ? "yes" : "no");
LOG_DEBUG("Keyboard enabled: %s", g_keyboard_enabled ? "yes" : "no");
}
if (argc >= 11) {
g_mouse_enabled = atoi(argv[10]);
if (g_verbose)
printf("Mouse enabled: %s\n", g_mouse_enabled ? "yes" : "no");
LOG_DEBUG("Mouse enabled: %s", g_mouse_enabled ? "yes" : "no");
}
if (load_sound_config(argv[1]) != 0) {
safe_fprintf(stderr, "Failed to load keyboard sound configuration\n");
if (argc >= 12) {
g_system_volume_following = atoi(argv[11]);
LOG_DEBUG("System volume following: %s", g_system_volume_following ? "yes" : "no");
}
if (load_sound_config(argv[1], &g_sound_pack) != 0) {
LOG_ERROR("Failed to load keyboard sound configuration");
return 1;
}
if (init_audio() != 0) {
safe_fprintf(stderr, "Failed to initialize audio\n");
LOG_ERROR("Failed to initialize audio");
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;
@@ -163,28 +199,22 @@ int main(int argc, char *argv[]) {
if (errno == EINTR) {
continue;
}
perror("select");
LOG_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) {
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");
LOG_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;
+137 -273
View File
@@ -1,7 +1,8 @@
#define _DEFAULT_SOURCE
#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>
@@ -10,297 +11,160 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
pthread_t sound_threads[MAX_CONCURRENT_SOUNDS];
volatile int thread_active[MAX_CONCURRENT_SOUNDS] = {0};
pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER;
static pa_simple *g_pa_handle = NULL;
static AudioVoice g_mixer_voices[MAX_MIXER_VOICES] = {0};
static pthread_mutex_t g_mixer_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_t g_mixer_thread;
static volatile int g_mixer_running = 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 sound pack config\n");
fprintf(stderr, "Check that your sound pack has a valid config.json file.\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;
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);
}
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");
void *mixer_thread_func(void *arg) {
(void)arg;
const int chunk_frames = 512;
const int channels = g_sound_pack.sf_info.channels ? g_sound_pack.sf_info.channels : 2;
short *mix_buffer = malloc(chunk_frames * channels * sizeof(short));
while (g_mixer_running) {
memset(mix_buffer, 0, chunk_frames * channels * sizeof(short));
int any_active = 0;
pthread_mutex_lock(&g_mixer_mutex);
float sys_vol = get_boosted_system_volume(g_system_volume_multiplier);
for (int i = 0; i < MAX_MIXER_VOICES; i++) {
if (!g_mixer_voices[i].active) continue;
any_active = 1;
uint32_t samples_to_copy = chunk_frames * channels;
if (g_mixer_voices[i].current_pos + samples_to_copy > g_mixer_voices[i].total_samples) {
samples_to_copy = g_mixer_voices[i].total_samples - g_mixer_voices[i].current_pos;
}
float vol = g_mixer_voices[i].volume * sys_vol;
for (uint32_t s = 0; s < samples_to_copy; s++) {
int32_t mixed = mix_buffer[s] + (int32_t)(g_mixer_voices[i].data[g_mixer_voices[i].current_pos + s] * vol);
// Clip
if (mixed > 32767) mixed = 32767;
else if (mixed < -32768) mixed = -32768;
mix_buffer[s] = (short)mixed;
}
g_mixer_voices[i].current_pos += samples_to_copy;
if (g_mixer_voices[i].current_pos >= g_mixer_voices[i].total_samples) {
g_mixer_voices[i].active = 0;
}
}
pthread_mutex_unlock(&g_mixer_mutex);
if (any_active) {
int pa_error;
if (pa_simple_write(g_pa_handle, mix_buffer, chunk_frames * channels * sizeof(short), &pa_error) < 0) {
LOG_ERROR("PulseAudio write error: %s", pa_strerror(pa_error));
}
} else {
// Nothing playing, sleep a bit to avoid CPU spin
struct timespec ts = {0, 10000000L};
nanosleep(&ts, NULL); // 10ms
}
}
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, "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");
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);
free(mix_buffer);
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;
}
int init_audio() {
// Use keyboard pack as reference for samplerate/channels
pa_sample_spec ss = {
.format = PA_SAMPLE_S16LE,
.rate = g_sound_pack.sf_info.samplerate ? g_sound_pack.sf_info.samplerate : 44100,
.channels = g_sound_pack.sf_info.channels ? g_sound_pack.sf_info.channels : 2
};
int pa_error;
g_pa_handle = pa_simple_new(NULL, "vbx", PA_STREAM_PLAYBACK, NULL, "clicks", &ss, NULL, NULL, &pa_error);
if (!g_pa_handle) {
LOG_ERROR("Could not initialize PulseAudio stream: %s", pa_strerror(pa_error));
return -1;
}
pthread_mutex_unlock(&thread_mutex);
return -1;
g_mixer_running = 1;
if (pthread_create(&g_mixer_thread, NULL, mixer_thread_func, NULL) != 0) {
LOG_ERROR("Failed to create mixer thread");
return -1;
}
return 0;
}
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_mute) return;
int is_mouse = (key_code == 272 || key_code == 273 || key_code == 274);
int device_enabled = is_mouse ? g_mouse_enabled : g_keyboard_enabled;
if (!device_enabled) return;
// Check if this is a mouse event and if mouse is disabled
int is_mouse_event = (key_code == 272 || key_code == 273 || key_code == 274);
if (is_mouse_event) {
// For mouse events, we need to check if mouse is enabled
// This will be handled by the main loop passing enabled state
// 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);
SoundPack *pack = is_mouse ? &g_mouse_sound_pack : &g_sound_pack;
float volume = is_mouse ? g_mouse_volume : g_volume;
int device_mute = is_mouse ? g_mouse_mute : g_keyboard_mute;
if (device_mute) return;
short *pcm_data = NULL;
uint32_t num_samples = 0;
if (pack->is_multi) {
if (is_pressed) {
if (pack->multi_key_mappings[key_code].press_data) {
pcm_data = pack->multi_key_mappings[key_code].press_data;
num_samples = pack->multi_key_mappings[key_code].press_samples;
} else if (pack->num_generic_press_files > 0) {
int idx = rand() % pack->num_generic_press_files;
pcm_data = pack->generic_press_data[idx];
num_samples = pack->generic_press_samples[idx];
}
} else {
if (pack->multi_key_mappings[key_code].release_data) {
pcm_data = pack->multi_key_mappings[key_code].release_data;
num_samples = pack->multi_key_mappings[key_code].release_samples;
} else if (pack->release_data) {
pcm_data = pack->release_data;
num_samples = pack->release_samples;
}
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);
}
return;
if (is_pressed && key_code < 512) {
pcm_data = pack->key_mappings[key_code].pcm_data;
num_samples = pack->key_mappings[key_code].num_samples;
}
}
if (!g_sound_pack.is_multi && !is_pressed) {
if (g_verbose) {
printf("Single mode: Ignoring key release for key %d\n", key_code);
if (!pcm_data || num_samples == 0) return;
// Find a slot in the mixer
pthread_mutex_lock(&g_mixer_mutex);
int found = 0;
for (int i = 0; i < MAX_MIXER_VOICES; i++) {
if (!g_mixer_voices[i].active) {
g_mixer_voices[i].data = pcm_data;
g_mixer_voices[i].total_samples = num_samples;
g_mixer_voices[i].current_pos = 0;
g_mixer_voices[i].volume = volume;
g_mixer_voices[i].active = 1;
found = 1;
break;
}
return;
}
int slot = find_available_thread_slot();
if (slot == -1) {
if (g_verbose) {
printf("Warning: No available thread slots\n");
}
return;
pthread_mutex_unlock(&g_mixer_mutex);
if (!found) {
LOG_WARN("Mixer full, dropping sound");
}
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 = 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;
}
+15 -9
View File
@@ -1,4 +1,5 @@
#include "app/cli.h"
#include "common/log.h"
#include "common/utils.h"
#include "config.h"
#include <getopt.h>
@@ -18,8 +19,8 @@ static int parse_device_argument(const char *optarg, const char *action,
} else if (strcmp(optarg, "mouse") == 0) {
*mouse_value = value;
} else {
safe_fprintf(stderr, "Error: Invalid device '%s' for %s\n", optarg, action);
safe_fprintf(stderr, "Valid options: keyboard, mouse, or both\n");
LOG_ERROR("Error: Invalid device '%s' for %s", optarg, action);
LOG_ERROR("Valid options: keyboard, mouse, or both");
return 1;
}
return 0;
@@ -59,7 +60,8 @@ void print_usage(const char *program_name) {
printf(" --enable[=DEVICE] Enable device sounds "
"(keyboard|mouse|both)\n");
printf(" --disable[=DEVICE] Disable device sounds "
"(keyboard|mouse|both)\n\n");
"(keyboard|mouse|both)\n");
printf(" --system-volume-following=0/1 Enable or disable tracking Linux system volume dynamically\n\n");
printf("DAEMON MODE:\n");
printf(" -d, --daemon Run in background with auto-reload\n");
@@ -102,6 +104,7 @@ int parse_cli(int argc, char **argv, CliOptions *out) {
out->mouse_mute = -1;
out->keyboard_enabled = -1;
out->mouse_enabled = -1;
out->system_volume_following = -1;
static struct option long_options[] = {
{"sound", required_argument, 0, 'S'},
{"mouse", required_argument, 0, 'M'},
@@ -117,6 +120,7 @@ int parse_cli(int argc, char **argv, CliOptions *out) {
{"stop", no_argument, 0, 's'},
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"system-volume-following", required_argument, 0, 'f'},
{0, 0, 0, 0}};
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-' && argv[i][1] == '-') {
@@ -133,18 +137,17 @@ int parse_cli(int argc, char **argv, CliOptions *out) {
(n == 6 && strncmp(a, "unmute", 6) == 0) ||
(n == 6 && strncmp(a, "enable", 6) == 0) ||
(n == 7 && strncmp(a, "disable", 7) == 0) ||
(n == 4 && strncmp(a, "help", 4) == 0) ||
(n == 7 && strncmp(a, "verbose", 7) == 0))) {
safe_fprintf(stderr, "Error: Unknown option '%s'\n", argv[i]);
safe_fprintf(stderr, "Run '%s --help' to see available options.\n",
argv[0]);
(n == 7 && strncmp(a, "verbose", 7) == 0) ||
(n == 23 && strncmp(a, "system-volume-following", 23) == 0))) {
LOG_ERROR("Error: Unknown option '%s'", argv[i]);
LOG_ERROR("Run '%s --help' to see available options.", argv[0]);
return 1;
}
}
}
optind = 1;
int opt;
while ((opt = getopt_long(argc, argv, "S:M:V:K:O:lhdsm::u::v", long_options,
while ((opt = getopt_long(argc, argv, "S:M:V:K:O:f:lhdsm::u::v", long_options,
NULL)) != -1) {
switch (opt) {
case 'd':
@@ -180,6 +183,9 @@ int parse_cli(int argc, char **argv, CliOptions *out) {
return 1;
}
break;
case 'f':
out->system_volume_following = atoi(optarg);
break;
case 1000: // --enable
case 1001: // --disable
{
+9 -9
View File
@@ -1,5 +1,6 @@
#define _POSIX_C_SOURCE 200809L
#include "common/utils.h"
#include "common/log.h"
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
@@ -8,6 +9,8 @@
#include <string.h>
#include <unistd.h>
int g_verbose = 0;
int read_runtime_mute_file() {
char mute_file[1024];
const char *rd = getenv("XDG_RUNTIME_DIR");
@@ -66,14 +69,6 @@ int safe_snprintf(char *dst, size_t dst_sz, const char *fmt, ...) {
return (n >= 0 && (size_t)n < dst_sz);
}
int errorf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
return 1;
}
const char *get_runtime_dir(void) {
const char *rd = getenv("XDG_RUNTIME_DIR");
if (rd && strlen(rd) > 0)
@@ -125,6 +120,11 @@ int write_runtime_mouse_enabled_file(int enabled) {
return 1;
}
int write_runtime_system_volume_following_file(int enabled) {
write_runtime_state_file("sysvol-following", enabled);
return 1;
}
void int_to_str(char *buffer, size_t size, int value) {
safe_snprintf_wrapper(buffer, size, "%d", value);
@@ -171,7 +171,7 @@ void safe_strncpy(char *dest, const char *src, size_t dest_size) {
// Helper to print error and close file
void print_error_and_close_file(FILE *file, const char *error_msg, const char *filename) {
if (file) fclose(file);
safe_fprintf(stderr, "Error: %s %s\n", error_msg, filename ? filename : "");
LOG_ERROR("Error: %s %s", error_msg, filename ? filename : "");
}
// Safe wrapper for fscanf with bounds checking
+30 -3
View File
@@ -21,8 +21,31 @@ char *get_user_config_path(char *buffer, size_t buflen) {
int write_user_config(const char *path, const char *keyboard_sound,
const char *mouse_sound, int keyboard_volume,
int mouse_volume, int keyboard_enabled,
int mouse_enabled) {
json_object *root = json_object_new_object();
int mouse_enabled, int system_volume_following) {
json_object *root = NULL;
FILE *in_f = fopen(path, "r");
if (in_f) {
fseek(in_f, 0, SEEK_END);
long sz = ftell(in_f);
if (sz > 0) {
rewind(in_f);
char *buf = malloc((size_t)sz + 1);
if (buf) {
if (fread(buf, 1, sz, in_f) == (size_t)sz) {
buf[sz] = '\0';
root = json_tokener_parse(buf);
}
free(buf);
}
}
fclose(in_f);
}
if (!root || !json_object_is_type(root, json_type_object)) {
if (root) json_object_put(root);
root = json_object_new_object();
}
if (!root)
return 0;
@@ -43,6 +66,9 @@ int write_user_config(const char *path, const char *keyboard_sound,
json_object_object_add(mouse, "volume", json_object_new_int(mouse_volume));
json_object_object_add(root, "mouse", mouse);
json_object_object_add(root, "system_volume_following",
json_object_new_boolean(system_volume_following));
const char *json_str =
json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY);
FILE *f = fopen(path, "w");
@@ -59,7 +85,7 @@ int write_user_config(const char *path, const char *keyboard_sound,
int read_user_config(const char *path, char **out_keyboard_sound,
char **out_mouse_sound, int *out_keyboard_volume,
int *out_mouse_volume, int *out_keyboard_enabled,
int *out_mouse_enabled) {
int *out_mouse_enabled, int *out_system_volume_following) {
FILE *f = fopen(path, "r");
if (!f)
return 0;
@@ -135,6 +161,7 @@ int read_user_config(const char *path, char **out_keyboard_sound,
*out_keyboard_volume = legacy_volume;
*out_mouse_volume = legacy_volume;
}
*out_system_volume_following = 0;
}
json_object_put(root);
return 1;
+49 -44
View File
@@ -1,4 +1,8 @@
#define _POSIX_C_SOURCE 200809L
#include "common/utils.h"
#include "common/ipc.h"
#include "common/log.h"
#include <time.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
@@ -49,7 +53,7 @@ static int open_restricted(const char *path, int flags, void *user_data) {
(void)user_data;
int fd = open(path, flags);
if (fd < 0)
errorf("Failed to open %s because of %s.\n", path, strerror(errno));
LOG_PERROR("Failed to open path");
return fd < 0 ? -errno : fd;
}
@@ -61,41 +65,27 @@ static void close_restricted(int fd, void *user_data) {
static const struct libinput_interface interface = {
.open_restricted = open_restricted, .close_restricted = close_restricted};
static int print_key_event(struct libinput_event *event) {
struct libinput_event_keyboard *keyboard =
libinput_event_get_keyboard_event(event);
enum libinput_event_type event_type = libinput_event_get_type(event);
uint32_t time_stamp = libinput_event_keyboard_get_time(keyboard);
uint32_t key_code = libinput_event_keyboard_get_key(keyboard);
const char *key_name = libevdev_event_code_get_name(EV_KEY, key_code);
key_name = key_name ? key_name : "null";
enum libinput_key_state state_code =
libinput_event_keyboard_get_key_state(keyboard);
const char *state_name =
state_code == LIBINPUT_KEY_STATE_PRESSED ? "PRESSED" : "RELEASED";
return printf("{\"event_name\": \"KEYBOARD_KEY\", \"event_type\": %d, "
"\"time_stamp\": %d, \"key_name\": \"%s\", \"key_code\": %d, "
"\"state_name\": \"%s\", \"state_code\": %d}\n",
event_type, time_stamp, key_name, key_code, state_name,
state_code);
static void send_event(uint32_t key_code, uint8_t is_pressed) {
VbxEvent ev = { .key_code = key_code, .is_pressed = is_pressed };
fwrite(&ev, sizeof(VbxEvent), 1, stdout);
}
static int print_button_event(struct libinput_event *event) {
static void send_key_event(struct libinput_event *event) {
struct libinput_event_keyboard *keyboard =
libinput_event_get_keyboard_event(event);
uint32_t key_code = libinput_event_keyboard_get_key(keyboard);
enum libinput_key_state state_code =
libinput_event_keyboard_get_key_state(keyboard);
send_event(key_code, state_code == LIBINPUT_KEY_STATE_PRESSED ? 1 : 0);
}
static void send_button_event(struct libinput_event *event) {
struct libinput_event_pointer *pointer =
libinput_event_get_pointer_event(event);
enum libinput_event_type event_type = libinput_event_get_type(event);
uint32_t time_stamp = libinput_event_pointer_get_time(pointer);
uint32_t button_code = libinput_event_pointer_get_button(pointer);
const char *button_name = libevdev_event_code_get_name(EV_KEY, button_code);
enum libinput_button_state state_code =
libinput_event_pointer_get_button_state(pointer);
const char *state_name =
state_code == LIBINPUT_BUTTON_STATE_PRESSED ? "PRESSED" : "RELEASED";
return printf("{\"event_name\": \"POINTER_BUTTON\", \"event_type\": %d, "
"\"time_stamp\": %d, \"key_name\": \"%s\", \"key_code\": %d, "
"\"state_name\": \"%s\", \"state_code\": %d}\n",
event_type, time_stamp, button_name, button_code, state_name,
state_code);
send_event(button_code, state_code == LIBINPUT_BUTTON_STATE_PRESSED ? 1 : 0);
}
static int handle_events(struct libinput *libinput) {
@@ -103,21 +93,28 @@ static int handle_events(struct libinput *libinput) {
struct libinput_event *event;
if (libinput_dispatch(libinput) < 0)
return result;
bool flushed = false;
while ((event = libinput_get_event(libinput)) != NULL) {
switch (libinput_event_get_type(event)) {
case LIBINPUT_EVENT_KEYBOARD_KEY:
print_key_event(event);
send_key_event(event);
flushed = true;
break;
case LIBINPUT_EVENT_POINTER_BUTTON:
print_button_event(event);
send_button_event(event);
flushed = true;
break;
default:
break;
}
fflush(stdout);
libinput_event_destroy(event);
result = 0;
}
if (flushed) {
fflush(stdout);
}
return result;
}
@@ -126,15 +123,17 @@ static int run_mainloop(struct libinput *libinput) {
fd.fd = libinput_get_fd(libinput);
fd.events = POLLIN;
fd.revents = 0;
if (handle_events(libinput) != 0)
return errorf("Expected device added events on startup but got none. Maybe "
"you don't have the right permissions?\n");
if (handle_events(libinput) != 0) {
LOG_ERROR("Expected device added events on startup but got none. Maybe you don't have the right permissions?");
return -1;
}
while (1) {
int pr = poll(&fd, 1, -1);
int pr = poll(&fd, 1, 10);
if (pr < 0) {
if (errno == EINTR)
continue;
return errorf("poll failed: %s\n", strerror(errno));
LOG_PERROR("poll failed");
return -1;
}
handle_events(libinput);
}
@@ -153,6 +152,8 @@ void print_help(char *program_name) {
"will run this.\n");
}
int is_daemon = 0;
int main(int argc, char *argv[]) {
setvbuf(stdout, NULL, _IOLBF, 0);
const struct option long_options[] = {{"version", no_argument, 0, 'v'},
@@ -174,19 +175,23 @@ int main(int argc, char *argv[]) {
case '?':
break;
default:
errorf("%s: Invalid option `-%c`.\n", argv[0], opt);
LOG_ERROR("Invalid option `-%c`.", opt);
break;
}
}
struct udev *udev = udev_new();
if (udev == NULL)
return errorf("Failed to initialize udev.\n");
if (udev == NULL) {
LOG_ERROR("Failed to initialize udev.");
return 1;
}
struct libinput *libinput =
libinput_udev_create_context(&interface, NULL, udev);
if (!libinput)
return errorf("Failed to initialize libinput from udev.\n");
if (!libinput) {
LOG_ERROR("Failed to initialize libinput from udev.");
return 1;
}
if (libinput_udev_assign_seat(libinput, "seat0") != 0) {
errorf("Failed to set seat.\n");
LOG_ERROR("Failed to set seat.");
libinput_unref(libinput);
udev_unref(udev);
return SEAT_FAILED;
@@ -195,7 +200,7 @@ int main(int argc, char *argv[]) {
struct input_handler_data input_handler_data = {udev, libinput};
if (pthread_create(&input_handler, NULL, handle_input, &input_handler_data) !=
0) {
errorf("Failed to create input handler thread.\n");
LOG_ERROR("Failed to create input handler thread.");
} else {
pthread_detach(input_handler);
}
+70 -70
View File
@@ -4,6 +4,7 @@
#include "app/process.h"
#include "app/reload.h"
#include "app/watch.h"
#include "common/log.h"
#include "common/mute.h"
#include "common/utils.h"
#include "config.h"
@@ -50,6 +51,7 @@ static int current_keyboard_mute = 0;
static int current_mouse_mute = 0;
static int current_keyboard_enabled = 1;
static int current_mouse_enabled = 1;
static int current_system_volume_following = 0;
static char current_config_path[MAX_PATH_LENGTH] = {0};
static char current_sound_dir[MAX_PATH_LENGTH] = {0};
static char current_mouse_config_path[MAX_PATH_LENGTH] = {0};
@@ -65,23 +67,22 @@ int main(int argc, char *argv[]) {
int list_sounds = 0;
int flag_daemon = 0;
int flag_stop = 0;
int volume = 50;
CliOptions cli_opts;
char user_cfg_path[MAX_PATH_LENGTH];
if (get_user_config_path(user_cfg_path, sizeof(user_cfg_path))) {
if (access(user_cfg_path, R_OK) == 0) {
if (verbose) {
printf("Loading configuration from: %s\n", user_cfg_path);
}
LOG_DEBUG("Loading configuration from: %s", user_cfg_path);
char *cfg_keyboard_sound = NULL;
char *cfg_mouse_sound = NULL;
int cfg_keyboard_volume = volume;
int cfg_mouse_volume = volume;
int cfg_keyboard_volume = current_keyboard_volume;
int cfg_mouse_volume = current_mouse_volume;
int cfg_keyboard_enabled = 1;
int cfg_mouse_enabled = 1;
int cfg_system_volume_following = 0;
if (read_user_config(user_cfg_path, &cfg_keyboard_sound, &cfg_mouse_sound,
&cfg_keyboard_volume, &cfg_mouse_volume,
&cfg_keyboard_enabled, &cfg_mouse_enabled)) {
&cfg_keyboard_enabled, &cfg_mouse_enabled,
&cfg_system_volume_following)) {
if (cfg_keyboard_sound) {
if (sound_name_owned) {
free(sound_name);
@@ -96,9 +97,11 @@ int main(int argc, char *argv[]) {
mouse_sound_name = cfg_mouse_sound;
mouse_sound_name_owned = 1;
}
volume = cfg_keyboard_volume;
current_keyboard_volume = cfg_keyboard_volume;
current_mouse_volume = cfg_mouse_volume;
current_keyboard_enabled = cfg_keyboard_enabled;
current_mouse_enabled = cfg_mouse_enabled;
current_system_volume_following = cfg_system_volume_following;
}
}
}
@@ -139,10 +142,9 @@ int main(int argc, char *argv[]) {
// Show welcome message for first-time users
if (access(user_cfg_path, F_OK) != 0 && !flag_daemon) {
printf("\nWelcome to VBX!\n");
printf("This appears to be your first time running VBX.\n");
printf("A default configuration will be created at: %s\n", user_cfg_path);
printf("\n");
LOG_INFO("Welcome to VBX!");
LOG_INFO("This appears to be your first time running VBX.");
LOG_INFO("A default configuration will be created at: %s", user_cfg_path);
}
build_pidfile_path(pidfile_path, 1024);
if (flag_stop) {
@@ -163,12 +165,13 @@ int main(int argc, char *argv[]) {
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
return (perror("kill"), 1);
LOG_PERROR("kill");
return 1;
}
for (int i = 0; i < 30; i++) {
if (!process_is_running(running_pid)) {
unlink(pidfile_path);
printf("VBX daemon stopped successfully.\n");
LOG_INFO("VBX daemon stopped successfully.");
if (sound_name_owned) {
free(sound_name);
}
@@ -188,8 +191,8 @@ int main(int argc, char *argv[]) {
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
return errorf(
"VBX: daemon did not stop in time. Try running 'vbx --stop' again.\n");
LOG_ERROR("VBX: daemon did not stop in time. Try running 'vbx --stop' again.");
return 1;
}
current_mute = read_runtime_mute_file();
@@ -211,7 +214,8 @@ int main(int argc, char *argv[]) {
config_updated = 1;
}
if (cli_opts.volume >= 0) {
volume = cli_opts.volume;
current_keyboard_volume = cli_opts.volume;
current_mouse_volume = cli_opts.volume;
config_updated = 1;
}
if (cli_opts.keyboard_volume >= 0) {
@@ -226,36 +230,36 @@ int main(int argc, char *argv[]) {
current_keyboard_mute = cli_opts.keyboard_mute;
config_updated = 1;
if (current_keyboard_mute) {
printf("Keyboard sounds muted.\n");
LOG_INFO("Keyboard sounds muted.");
} else {
printf("Keyboard sounds unmuted.\n");
LOG_INFO("Keyboard sounds unmuted.");
}
}
if (cli_opts.mouse_mute >= 0) {
current_mouse_mute = cli_opts.mouse_mute;
config_updated = 1;
if (current_mouse_mute) {
printf("Mouse sounds muted.\n");
LOG_INFO("Mouse sounds muted.");
} else {
printf("Mouse sounds unmuted.\n");
LOG_INFO("Mouse sounds unmuted.");
}
}
if (cli_opts.keyboard_enabled >= 0) {
current_keyboard_enabled = cli_opts.keyboard_enabled;
config_updated = 1;
if (current_keyboard_enabled) {
printf("Keyboard sounds enabled.\n");
LOG_INFO("Keyboard sounds enabled.");
} else {
printf("Keyboard sounds disabled.\n");
LOG_INFO("Keyboard sounds disabled.");
}
}
if (cli_opts.mouse_enabled >= 0) {
current_mouse_enabled = cli_opts.mouse_enabled;
config_updated = 1;
if (current_mouse_enabled) {
printf("Mouse sounds enabled.\n");
LOG_INFO("Mouse sounds enabled.");
} else {
printf("Mouse sounds disabled.\n");
LOG_INFO("Mouse sounds disabled.");
}
}
@@ -264,17 +268,18 @@ int main(int argc, char *argv[]) {
write_runtime_mouse_mute_file(current_mouse_mute);
write_runtime_keyboard_enabled_file(current_keyboard_enabled);
write_runtime_mouse_enabled_file(current_mouse_enabled);
write_runtime_system_volume_following_file(current_system_volume_following);
if (config_updated &&
get_user_config_path(user_cfg_path, sizeof(user_cfg_path))) {
if (!write_user_config(user_cfg_path, sound_name, mouse_sound_name,
current_keyboard_volume, current_mouse_volume,
current_keyboard_enabled, current_mouse_enabled)) {
safe_fprintf(stderr, "Warning: Failed to update config file %s\n",
user_cfg_path);
safe_fprintf(stderr, "Your settings will not be saved for next time.\n");
} else if (verbose) {
safe_fprintf(stderr, "Updated config file %s\n", user_cfg_path);
current_keyboard_enabled, current_mouse_enabled,
current_system_volume_following)) {
LOG_WARN("Failed to update config file %s", user_cfg_path);
LOG_WARN("Your settings will not be saved for next time.");
} else {
LOG_DEBUG("Updated config file %s", user_cfg_path);
}
}
@@ -293,10 +298,11 @@ int main(int argc, char *argv[]) {
if (access(user_cfg_path, F_OK) != 0) {
if (!write_user_config(user_cfg_path, sound_name, mouse_sound_name,
current_keyboard_volume, current_mouse_volume,
current_keyboard_enabled, current_mouse_enabled)) {
safe_fprintf(stderr, "Warning: Failed to write %s\n", user_cfg_path);
current_keyboard_enabled, current_mouse_enabled,
current_system_volume_following)) {
LOG_WARN("Failed to write %s", user_cfg_path);
} else if (verbose) {
printf("Created default configuration: %s\n", user_cfg_path);
LOG_INFO("Created default configuration: %s", user_cfg_path);
}
}
}
@@ -332,8 +338,8 @@ int main(int argc, char *argv[]) {
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
return errorf("Error: Cannot find or execute required binaries in %s\n",
VBX_BIN_DIR);
LOG_ERROR("Error: Cannot find or execute required binaries in %s", VBX_BIN_DIR);
return 1;
}
signal(SIGINT, cleanup_processes);
signal(SIGTERM, cleanup_processes);
@@ -346,10 +352,7 @@ int main(int argc, char *argv[]) {
if (mouse_sound_name_owned) {
free(mouse_sound_name);
}
safe_fprintf(stderr,
"VBX daemon is already running (PID: %ld). Use 'vbx --stop' "
"to stop it.\n",
(long)existing);
LOG_WARN("VBX daemon is already running (PID: %ld). Use 'vbx --stop' to stop it.", (long)existing);
return 1;
}
daemonize_self();
@@ -382,8 +385,6 @@ int main(int argc, char *argv[]) {
}
return 1;
}
current_keyboard_volume = volume;
current_mouse_volume = volume;
current_verbose = verbose;
safe_strncpy(current_sound_name, sound_name, sizeof(current_sound_name));
safe_strncpy(current_mouse_sound_name, mouse_sound_name,
@@ -395,30 +396,29 @@ int main(int argc, char *argv[]) {
safe_strncpy(current_mouse_sound_dir, mouse_sound_dir,
sizeof(current_mouse_sound_dir));
if (verbose && !is_daemon) {
printf("Starting VBX daemon...\n");
printf("Keyboard sound pack: %s\n", sound_name);
printf("Mouse sound pack: %s\n", mouse_sound_name);
printf("Keyboard config file: %s\n", config_path);
printf("Mouse config file: %s\n", mouse_config_path);
printf("Keyboard working directory: %s\n", sound_dir);
printf("Mouse working directory: %s\n", mouse_sound_dir);
printf("Press Ctrl+C to exit.\n\n");
LOG_INFO("Starting VBX daemon...");
LOG_INFO("Keyboard sound pack: %s", sound_name);
LOG_INFO("Mouse sound pack: %s", mouse_sound_name);
LOG_INFO("Keyboard config file: %s", config_path);
LOG_INFO("Mouse config file: %s", mouse_config_path);
LOG_INFO("Keyboard working directory: %s", sound_dir);
LOG_INFO("Mouse working directory: %s", mouse_sound_dir);
LOG_DEBUG("Press Ctrl+C to exit.");
} else {
if (!is_daemon) {
printf("VBX daemon started successfully!\n");
printf(" Keyboard: %s (volume: %d%%)\n", sound_name,
current_keyboard_volume);
printf(" Mouse: %s (volume: %d%%)\n", mouse_sound_name,
current_mouse_volume);
printf(" Config: ~/.vbx.json (auto-reload enabled)\n");
printf("\nUse 'vbx --stop' to stop the daemon.\n");
printf("Press Ctrl+C to exit.\n");
LOG_INFO("VBX daemon started successfully!");
LOG_INFO(" Keyboard: %s (volume: %d%%)", sound_name, current_keyboard_volume);
LOG_INFO(" Mouse: %s (volume: %d%%)", mouse_sound_name, current_mouse_volume);
LOG_INFO(" Config: ~/.vbx.json (auto-reload enabled)");
LOG_INFO("Use 'vbx --stop' to stop the daemon.");
LOG_INFO("Press Ctrl+C to exit.");
}
}
if (!start_children(sound_dir, config_path, volume, verbose, current_mute,
if (!start_children(sound_dir, config_path, current_keyboard_volume, verbose, current_mute,
mouse_sound_dir, mouse_config_path, current_mouse_volume,
current_keyboard_mute, current_mouse_mute,
current_keyboard_enabled, current_mouse_enabled)) {
current_keyboard_enabled, current_mouse_enabled,
current_system_volume_following)) {
if (sound_name_owned) {
free(sound_name);
}
@@ -433,14 +433,14 @@ int main(int argc, char *argv[]) {
char user_cfg_path2[MAX_PATH_LENGTH];
if (get_user_config_path(user_cfg_path2, sizeof(user_cfg_path2))) {
printf("Setting up file watcher for: %s\n", user_cfg_path2);
LOG_INFO("Setting up file watcher for: %s", user_cfg_path2);
start_config_watcher(user_cfg_path2);
} else {
printf("Warning: Could not get user config path for file watching\n");
LOG_WARN("Could not get user config path for file watching");
}
while (1) {
if (reload_requested) {
printf("Reload requested, processing config changes...\n");
LOG_INFO("Reload requested, processing config changes...");
reload_requested = 0;
char user_cfg_path3[MAX_PATH_LENGTH];
if (get_user_config_path(user_cfg_path3, sizeof(user_cfg_path3))) {
@@ -449,10 +449,11 @@ int main(int argc, char *argv[]) {
&current_keyboard_volume, &current_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,
&current_keyboard_enabled, &current_mouse_enabled);
current_keyboard_mute, current_mouse_mute,
&current_keyboard_enabled, &current_mouse_enabled,
&current_system_volume_following);
} else {
printf("Failed to get user config path\n");
LOG_ERROR("Failed to get user config path");
}
}
finished_pid = waitpid(-1, &status, WNOHANG);
@@ -465,14 +466,13 @@ int main(int argc, char *argv[]) {
} else if (finished_pid > 0) {
if (finished_pid == keyboard_pid) {
if (!is_daemon)
printf("Keyboard listener exited with status %d\n",
WEXITSTATUS(status));
LOG_WARN("Keyboard listener exited with status %d", WEXITSTATUS(status));
keyboard_pid = 0;
if (sound_pid > 0)
kill(sound_pid, SIGTERM);
} else if (finished_pid == sound_pid) {
if (!is_daemon)
printf("Sound player exited with status %d\n", WEXITSTATUS(status));
LOG_WARN("Sound player exited with status %d", WEXITSTATUS(status));
sound_pid = 0;
if (keyboard_pid > 0)
kill(keyboard_pid, SIGTERM);
@@ -484,7 +484,7 @@ int main(int argc, char *argv[]) {
}
}
if (!is_daemon) {
printf("VBX daemon exited.\n");
LOG_INFO("VBX daemon exited.");
}
if (sound_name_owned) {
free(sound_name);
+37 -38
View File
@@ -1,4 +1,5 @@
#include "soundpacks.h"
#include "common/log.h"
#include "common/utils.h"
#include "config.h"
#include <dirent.h>
@@ -129,24 +130,21 @@ int build_paths_for_mouse_sound(const char *sound_name, char *out_config_path,
int validate_keyboard_sound_pack(const char *sound_name) {
char basedir[1024];
if (!resolve_keyboard_sound_base_dir(sound_name, basedir, sizeof(basedir))) {
fprintf(
stderr,
"Error: Keyboard sound pack '%s' not found in user or system dirs.\n",
sound_name);
fprintf(stderr, "Use --list to see available sound packs.\n");
LOG_ERROR("Error: Keyboard sound pack '%s' not found in user or system dirs.",
sound_name);
LOG_ERROR("Use --list to see available sound packs.");
return 0;
}
char config_path[1024];
if ((size_t)snprintf(config_path, sizeof(config_path), "%s/%s/config.json",
basedir, sound_name) >= sizeof(config_path)) {
fprintf(stderr, "Error: Sound pack path too long: %s/%s\n", basedir,
sound_name);
fprintf(stderr, "Try using a shorter sound pack name.\n");
LOG_ERROR("Error: Sound pack path too long: %s/%s", basedir, sound_name);
LOG_ERROR("Try using a shorter sound pack name.");
return 0;
}
if (access(config_path, R_OK) != 0) {
fprintf(stderr, "Error: Sound pack config not found: %s\n", config_path);
fprintf(stderr, "Make sure the sound pack directory contains a valid config.json file.\n");
LOG_ERROR("Error: Sound pack config not found: %s", config_path);
LOG_ERROR("Make sure the sound pack directory contains a valid config.json file.");
return 0;
}
return 1;
@@ -155,23 +153,21 @@ int validate_keyboard_sound_pack(const char *sound_name) {
int validate_mouse_sound_pack(const char *sound_name) {
char basedir[1024];
if (!resolve_mouse_sound_base_dir(sound_name, basedir, sizeof(basedir))) {
fprintf(stderr,
"Error: Mouse sound pack '%s' not found in user or system dirs.\n",
sound_name);
fprintf(stderr, "Use --list to see available sound packs.\n");
LOG_ERROR("Error: Mouse sound pack '%s' not found in user or system dirs.",
sound_name);
LOG_ERROR("Use --list to see available sound packs.");
return 0;
}
char config_path[1024];
if ((size_t)snprintf(config_path, sizeof(config_path), "%s/%s/config.json",
basedir, sound_name) >= sizeof(config_path)) {
fprintf(stderr, "Error: Sound pack path too long: %s/%s\n", basedir,
sound_name);
fprintf(stderr, "Try using a shorter sound pack name.\n");
LOG_ERROR("Error: Sound pack path too long: %s/%s", basedir, sound_name);
LOG_ERROR("Try using a shorter sound pack name.");
return 0;
}
if (access(config_path, R_OK) != 0) {
fprintf(stderr, "Error: Sound pack config not found: %s\n", config_path);
fprintf(stderr, "Make sure the sound pack directory contains a valid config.json file.\n");
LOG_ERROR("Error: Sound pack config not found: %s", config_path);
LOG_ERROR("Make sure the sound pack directory contains a valid config.json file.");
return 0;
}
return 1;
@@ -198,24 +194,25 @@ int list_sound_packs(void) {
USER_MOUSE_AUDIO_SUBPATH);
}
printf("Available Sound Packs\n");
printf("=====================\n\n");
printf("\nKEYBOARD SOUND PACKS:\n");
printf("======================\n");
printf("%-30s %-15s\n", "Pack Name", "Source");
printf("%-30s %-15s\n", "---------", "------");
LOG_INFO("Available Sound Packs");
LOG_INFO("=====================");
LOG_INFO(" ");
LOG_INFO("KEYBOARD SOUND PACKS:");
LOG_INFO("======================");
LOG_INFO("%-30s %-15s", "Pack Name", "Source");
LOG_INFO("%-30s %-15s", "---------", "------");
if (user_keyboard_dir[0] && (dir = opendir(user_keyboard_dir)) != NULL) {
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
if ((size_t)snprintf(path, sizeof(path), "%s/%s", user_keyboard_dir,
entry->d_name) >= sizeof(path)) {
fprintf(stderr, "Warning: Path too long, skipping: %s/%s\n",
LOG_WARN("Warning: Path too long, skipping: %s/%s",
user_keyboard_dir, entry->d_name);
continue;
}
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
printf("%-30s %-15s\n", entry->d_name, "user");
LOG_INFO("%-30s %-15s", entry->d_name, "user");
}
}
closedir(dir);
@@ -227,32 +224,33 @@ int list_sound_packs(void) {
continue;
if ((size_t)snprintf(path, sizeof(path), "%s/%s", KEYBOARD_AUDIO_DIR,
entry->d_name) >= sizeof(path)) {
fprintf(stderr, "Warning: Path too long, skipping: %s/%s\n",
LOG_WARN("Warning: Path too long, skipping: %s/%s",
KEYBOARD_AUDIO_DIR, entry->d_name);
continue;
}
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
printf("%-30s %-15s\n", entry->d_name, "system");
LOG_INFO("%-30s %-15s", entry->d_name, "system");
}
}
closedir(dir);
}
printf("\nMOUSE SOUND PACKS:\n");
printf("==================\n");
printf("%-30s %-15s\n", "Pack Name", "Source");
printf("%-30s %-15s\n", "---------", "------");
LOG_INFO(" ");
LOG_INFO("MOUSE SOUND PACKS:");
LOG_INFO("==================");
LOG_INFO("%-30s %-15s", "Pack Name", "Source");
LOG_INFO("%-30s %-15s", "---------", "------");
if (user_mouse_dir[0] && (dir = opendir(user_mouse_dir)) != NULL) {
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
if ((size_t)snprintf(path, sizeof(path), "%s/%s", user_mouse_dir,
entry->d_name) >= sizeof(path)) {
fprintf(stderr, "Warning: Path too long, skipping: %s/%s\n",
LOG_WARN("Warning: Path too long, skipping: %s/%s",
user_mouse_dir, entry->d_name);
continue;
}
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
printf("%-30s %-15s\n", entry->d_name, "user");
LOG_INFO("%-30s %-15s", entry->d_name, "user");
}
}
closedir(dir);
@@ -264,16 +262,17 @@ int list_sound_packs(void) {
continue;
if ((size_t)snprintf(path, sizeof(path), "%s/%s", MOUSE_AUDIO_DIR,
entry->d_name) >= sizeof(path)) {
fprintf(stderr, "Warning: Path too long, skipping: %s/%s\n",
LOG_WARN("Warning: Path too long, skipping: %s/%s",
MOUSE_AUDIO_DIR, entry->d_name);
continue;
}
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
printf("%-30s %-15s\n", entry->d_name, "system");
LOG_INFO("%-30s %-15s", entry->d_name, "system");
}
}
closedir(dir);
}
printf("\nUsage: vbx -S <pack-name> for keyboard or vbx -M <pack-name> for mouse\n");
LOG_INFO(" ");
LOG_INFO("Usage: vbx -S <pack-name> for keyboard or vbx -M <pack-name> for mouse");
return 0;
}