1#include <pulse/pulseaudio.h>
2#include <string.h>
3#include "glitch.h"
4
5extern WindowManager wm;
6
7static void trigger_redraw(void) {
8 if (!wm.dpy || wm.root == None) return;
9
10 XLockDisplay(wm.dpy);
11 XEvent ev = {0};
12 ev.type = Expose;
13 ev.xexpose.window = wm.root;
14 ev.xexpose.x = 0;
15 ev.xexpose.y = 0;
16 ev.xexpose.width = 1;
17 ev.xexpose.height = 1;
18 ev.xexpose.count = 0;
19
20 XSendEvent(wm.dpy, wm.root, False, ExposureMask, &ev);
21 XFlush(wm.dpy);
22 XUnlockDisplay(wm.dpy);
23}
24
25static void source_info_callback(pa_context *c, const pa_source_info *i, int eol, void *userdata) {
26 (void)c;
27 (void)userdata;
28 if (eol > 0 || !i) return;
29
30 // Check if this is the default source or matches our criteria
31 // For simplicity, we can just track the mute state of the default source
32 // Pulseaudio usually calls this for the specific source we requested
33 int muted = i->mute;
34 if (wm.mic_muted != muted) {
35 wm.mic_muted = muted;
36 trigger_redraw();
37 }
38}
39
40static void update_mic_state(pa_context *c) {
41 pa_operation *o = pa_context_get_source_info_by_name(c, "@DEFAULT_SOURCE@", source_info_callback, NULL);
42 if (o) pa_operation_unref(o);
43}
44
45static void subscribe_callback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
46 (void)idx;
47 (void)userdata;
48 if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) {
49 update_mic_state(c);
50 }
51}
52
53static void context_state_callback(pa_context *c, void *userdata) {
54 (void)userdata;
55 switch (pa_context_get_state(c)) {
56 case PA_CONTEXT_READY:
57 pa_context_set_subscribe_callback(c, subscribe_callback, NULL);
58 pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL);
59 update_mic_state(c);
60 break;
61 case PA_CONTEXT_FAILED:
62 case PA_CONTEXT_TERMINATED:
63 break;
64 case PA_CONTEXT_UNCONNECTED:
65 case PA_CONTEXT_CONNECTING:
66 case PA_CONTEXT_AUTHORIZING:
67 case PA_CONTEXT_SETTING_NAME:
68 break;
69 }
70}
71
72void init_audio(void) {
73 wm.pa_mainloop = pa_threaded_mainloop_new();
74 if (!wm.pa_mainloop) return;
75
76 wm.pa_ctx = pa_context_new(pa_threaded_mainloop_get_api(wm.pa_mainloop), "glitch-wm");
77 if (!wm.pa_ctx) return;
78
79 pa_context_set_state_callback(wm.pa_ctx, context_state_callback, NULL);
80
81 if (pa_context_connect(wm.pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) {
82 return;
83 }
84
85 if (pa_threaded_mainloop_start(wm.pa_mainloop) < 0) {
86 return;
87 }
88}
89
90void deinit_audio(void) {
91 if (wm.pa_mainloop) pa_threaded_mainloop_stop(wm.pa_mainloop);
92 if (wm.pa_ctx) pa_context_unref(wm.pa_ctx);
93 if (wm.pa_mainloop) pa_threaded_mainloop_free(wm.pa_mainloop);
94}
95
96void toggle_mic_mute(const Arg *arg) {
97 (void)arg;
98 if (!wm.pa_ctx || pa_context_get_state(wm.pa_ctx) != PA_CONTEXT_READY) return;
99
100 // Instant UI feedback
101 wm.mic_muted = !wm.mic_muted;
102 trigger_redraw();
103
104 pa_threaded_mainloop_lock(wm.pa_mainloop);
105 pa_operation *o = pa_context_set_source_mute_by_name(wm.pa_ctx, "@DEFAULT_SOURCE@", wm.mic_muted, NULL, NULL);
106 if (o) pa_operation_unref(o);
107 pa_threaded_mainloop_unlock(wm.pa_mainloop);
108}