aboutsummaryrefslogtreecommitdiff
path: root/daemons/volume/volumed.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemons/volume/volumed.c')
-rw-r--r--daemons/volume/volumed.c171
1 files changed, 171 insertions, 0 deletions
diff --git a/daemons/volume/volumed.c b/daemons/volume/volumed.c
new file mode 100644
index 0000000..3681b60
--- /dev/null
+++ b/daemons/volume/volumed.c
@@ -0,0 +1,171 @@
+// bar-volumed — volume daemon for bar (no permissions needed)
+//
+// Uses CoreAudio to listen for system volume/mute changes instead of
+// a CGEvent tap, so no Accessibility permission is required.
+//
+// When the volume changes, it posts a Darwin notification that tells
+// bar to refresh the "volume" block.
+//
+// ── Setup ──
+//
+// 1. Add a volume block to Config (no interval needed):
+// Block(name: "volume", placement: .right, command: "<your-volume-script>")
+//
+// 2. Compile and run the daemon:
+// cc -o ~/.local/bin/bar-volumed daemons/volume/volumed.c -framework CoreAudio
+// ~/.local/bin/bar-volumed &
+//
+// 3. Or install as a luanch agent:
+// cp daemons/volume/house.panini.bar.volumed.plist ~/Library/LaunchAgents/
+// # Make sure to update the ProgramArguments with the correct path to the program
+// launchctl load ~/Library/LaunchAgents/house.panini.bar.volumed.plist
+//
+// Compile:
+// cc -o bar-volumed daemons/volume/volumed.c -framework CoreAudio
+
+#include <CoreAudio/CoreAudio.h>
+#include <notify.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static volatile int keep_running = 1;
+
+static void handle_signal(int sig) {
+ (void)sig;
+ keep_running = 0;
+}
+
+// ── Volume change callback ───────────────────────────────────────────
+
+static OSStatus volume_changed(
+ AudioObjectID object,
+ UInt32 num_addresses,
+ const AudioObjectPropertyAddress addresses[],
+ void *context
+) {
+ (void)object; (void)num_addresses; (void)addresses; (void)context;
+ notify_post("bar.touch.volume");
+ return noErr;
+}
+
+// ── Device change callback ───────────────────────────────────────────
+// Re-registers the volume listener when the output device changes
+// (e.g. headphones plugged in).
+
+static AudioDeviceID current_device = kAudioObjectUnknown;
+static void register_volume_listener(AudioDeviceID device);
+
+static OSStatus device_changed(
+ AudioObjectID object,
+ UInt32 num_addresses,
+ const AudioObjectPropertyAddress addresses[],
+ void *context
+) {
+ (void)object; (void)num_addresses; (void)addresses; (void)context;
+
+ AudioObjectPropertyAddress addr = {
+ kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMain
+ };
+ AudioDeviceID new_device = kAudioObjectUnknown;
+ UInt32 size = sizeof(new_device);
+ AudioObjectGetPropertyData(
+ kAudioObjectSystemObject, &addr, 0, NULL, &size, &new_device);
+
+ if (new_device != current_device && new_device != kAudioObjectUnknown) {
+ register_volume_listener(new_device);
+ }
+ return noErr;
+}
+
+// ── Register listener on a device ────────────────────────────────────
+
+static void register_volume_listener(AudioDeviceID device) {
+ // Remove old listener if any
+ if (current_device != kAudioObjectUnknown) {
+ AudioObjectPropertyAddress old_addr = {
+ kAudioDevicePropertyVolumeScalar,
+ kAudioDevicePropertyScopeOutput,
+ 1 // channel 1
+ };
+ AudioObjectRemovePropertyListener(
+ current_device, &old_addr, volume_changed, NULL);
+ }
+
+ current_device = device;
+
+ // Get the preferred stereo channel (usually 1)
+ UInt32 channel = 1;
+ AudioObjectPropertyAddress chan = {
+ kAudioDevicePropertyPreferredChannelsForStereo,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMain
+ };
+ UInt32 channels[2] = {0, 0};
+ UInt32 size = sizeof(channels);
+ if (AudioObjectGetPropertyData(device, &chan, 0, NULL, &size, &channels) == noErr) {
+ channel = channels[0];
+ }
+
+ // Listen for volume changes on the main output channel
+ AudioObjectPropertyAddress vol_addr = {
+ kAudioDevicePropertyVolumeScalar,
+ kAudioDevicePropertyScopeOutput,
+ channel
+ };
+ AudioObjectAddPropertyListener(device, &vol_addr, volume_changed, NULL);
+
+ // Also listen for mute changes
+ AudioObjectPropertyAddress mute_addr = {
+ kAudioDevicePropertyMute,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMain
+ };
+ AudioObjectAddPropertyListener(device, &mute_addr, volume_changed, NULL);
+}
+
+// ── Main ─────────────────────────────────────────────────────────────
+
+int main(void) {
+ signal(SIGINT, handle_signal);
+ signal(SIGTERM, handle_signal);
+
+ // Listen for output device changes
+ AudioObjectPropertyAddress dev_addr = {
+ kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMain
+ };
+ AudioObjectAddPropertyListener(
+ kAudioObjectSystemObject, &dev_addr, device_changed, NULL);
+
+ // Register on the current device
+ UInt32 size = sizeof(current_device);
+ AudioObjectGetPropertyData(
+ kAudioObjectSystemObject, &dev_addr, 0, NULL, &size, &current_device);
+
+ if (current_device != kAudioObjectUnknown) {
+ register_volume_listener(current_device);
+ }
+
+ // Run until SIGINT/SIGTERM
+ while (keep_running) {
+ pause();
+ }
+
+ // Cleanup
+ if (current_device != kAudioObjectUnknown) {
+ AudioObjectPropertyAddress vol_addr = {
+ kAudioDevicePropertyVolumeScalar,
+ kAudioDevicePropertyScopeOutput,
+ 1 // channel 1
+ };
+ AudioObjectRemovePropertyListener(
+ current_device, &vol_addr, volume_changed, NULL);
+ }
+
+ return 0;
+}