-
Notifications
You must be signed in to change notification settings - Fork 0
Fix application volume sliders becoming non-functional after extended runtime #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ static void context_state_callback(pa_context *c, void *userdata); | |||||
| static void sink_info_callback(pa_context *c, const pa_sink_info *info, int eol, void *userdata); | ||||||
| static void server_info_callback(pa_context *c, const pa_server_info *info, void *userdata); | ||||||
| static void sink_input_info_callback(pa_context *c, const pa_sink_input_info *info, int eol, void *userdata); | ||||||
| static void subscription_callback(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata); | ||||||
|
|
||||||
| gboolean pulse_client_init(pulse_client_t *client) | ||||||
| { | ||||||
|
|
@@ -17,6 +18,7 @@ gboolean pulse_client_init(pulse_client_t *client) | |||||
|
|
||||||
| memset(client, 0, sizeof(pulse_client_t)); | ||||||
| client->audio_apps = NULL; | ||||||
| client->sink_inputs_changed = FALSE; | ||||||
|
|
||||||
| // Create mainloop | ||||||
| client->mainloop = pa_mainloop_new(); | ||||||
|
|
@@ -112,6 +114,22 @@ gboolean pulse_client_connect(pulse_client_t *client) | |||||
| client->connected = TRUE; | ||||||
| printf("Connected to PulseAudio server\n"); | ||||||
|
|
||||||
| // Subscribe to sink input events to detect when applications start/stop audio | ||||||
| pa_context_set_subscribe_callback(client->context, subscription_callback, client); | ||||||
| client->operation = pa_context_subscribe(client->context, | ||||||
| PA_SUBSCRIPTION_MASK_SINK_INPUT, | ||||||
| NULL, NULL); | ||||||
| if (!client->operation) { | ||||||
| printf("Failed to subscribe to PulseAudio events\n"); | ||||||
| return FALSE; | ||||||
| } | ||||||
|
|
||||||
| while (pa_operation_get_state(client->operation) == PA_OPERATION_RUNNING) { | ||||||
| pa_mainloop_iterate(client->mainloop, 1, NULL); | ||||||
| } | ||||||
| pa_operation_unref(client->operation); | ||||||
| client->operation = NULL; | ||||||
|
|
||||||
| // Get server info to find default sink | ||||||
| client->operation = pa_context_get_server_info(client->context, | ||||||
| server_info_callback, client); | ||||||
|
|
@@ -258,6 +276,17 @@ void pulse_client_iterate(pulse_client_t *client) | |||||
| pa_mainloop_iterate(client->mainloop, 0, &retval); | ||||||
| } | ||||||
|
|
||||||
| gboolean pulse_client_sink_inputs_changed(pulse_client_t *client) | ||||||
| { | ||||||
| if (!client) { | ||||||
| return FALSE; | ||||||
| } | ||||||
|
|
||||||
| gboolean changed = client->sink_inputs_changed; | ||||||
| client->sink_inputs_changed = FALSE; // Reset the flag | ||||||
| return changed; | ||||||
| } | ||||||
|
|
||||||
| // Callback functions | ||||||
| static void context_state_callback(pa_context *c, void *userdata) | ||||||
| { | ||||||
|
|
@@ -511,4 +540,19 @@ static void sink_input_info_callback(pa_context *c, const pa_sink_input_info *in | |||||
| app->name, app->process_name, app->index, | ||||||
| app_audio_get_volume_percent(app), | ||||||
| app->muted ? "yes" : "no"); | ||||||
| } | ||||||
|
|
||||||
| // Subscription callback to handle PulseAudio events | ||||||
| static void subscription_callback(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) | ||||||
| { | ||||||
| pulse_client_t *client = (pulse_client_t *)userdata; | ||||||
|
|
||||||
| // Check if this is a sink input event | ||||||
| if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { | ||||||
| // Mark that sink inputs have changed - this will trigger UI update | ||||||
| client->sink_inputs_changed = TRUE; | ||||||
| printf("Sink input event detected (index=%u, type=%s)\n", index, | ||||||
|
||||||
| printf("Sink input event detected (index=%u, type=%s)\n", index, | |
| g_debug("Sink input event detected (index=%u, type=%s)", index, |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,6 +25,10 @@ typedef struct { | |||||||||||||||||
|
|
||||||||||||||||||
| static volmix_app_t app_data; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Forward declaration | ||||||||||||||||||
| static void update_slider_indexes(volmix_app_t *app); | ||||||||||||||||||
| static void update_sliders_recursive(GList *widgets, GList *apps); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Asynchronous callback for PulseAudio processing | ||||||||||||||||||
| static gboolean async_iterate_callback(gpointer user_data) | ||||||||||||||||||
| { | ||||||||||||||||||
|
|
@@ -160,8 +164,12 @@ static void build_volume_window(volmix_app_t *app) | |||||||||||||||||
| uint32_t *index_ptr = g_malloc(sizeof(uint32_t)); | ||||||||||||||||||
| *index_ptr = audio_app->index; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Store application name for index updates | ||||||||||||||||||
| char *app_name = g_strdup(audio_app->name); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Use g_object_set_data_full to ensure memory is freed when widget is destroyed | ||||||||||||||||||
| g_object_set_data_full(G_OBJECT(slider), "sink_input_index", index_ptr, g_free); | ||||||||||||||||||
| g_object_set_data_full(G_OBJECT(slider), "app_name", app_name, g_free); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Connect slider signal using the stored data instead of direct pointer | ||||||||||||||||||
| g_signal_connect(slider, "value-changed", G_CALLBACK(on_app_volume_changed), | ||||||||||||||||||
|
|
@@ -375,9 +383,86 @@ static gboolean pulse_client_timer_callback(gpointer user_data) | |||||||||||||||||
| // Process PulseAudio events | ||||||||||||||||||
| pulse_client_iterate(&app->pulse_client); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Check if sink inputs have changed and window is visible | ||||||||||||||||||
| if (pulse_client_sink_inputs_changed(&app->pulse_client) && | ||||||||||||||||||
| app->volmix_window && gtk_widget_get_visible(app->volmix_window)) { | ||||||||||||||||||
| printf("Sink inputs changed, updating slider indexes...\n"); | ||||||||||||||||||
|
||||||||||||||||||
| printf("Sink inputs changed, updating slider indexes...\n"); | |
| g_debug("Sink inputs changed, updating slider indexes..."); |
Copilot
AI
Aug 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic numbers MAX_REFRESH_TIMEOUT and DELAY_5_MS_USEC are used but not defined in this file. These constants should be defined at the top of the file or in a header to improve code maintainability.
Copilot
AI
Aug 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug printf statements should be replaced with proper logging or removed from production code. Consider using a logging framework or conditional compilation for debug output.
| if (*stored_index != app->index) { | |
| printf("Updating slider for '%s': index %u -> %u\n", | |
| stored_app_name, *stored_index, app->index); | |
| if (*stored_index != app->index) { | |
| #ifdef DEBUG | |
| printf("Updating slider for '%s': index %u -> %u\n", | |
| stored_app_name, *stored_index, app->index); | |
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is unclear about the consequences of the subscription failure. Consider providing more context about what this means for the application's functionality.