Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ am__make_running_with_option = \
test $$has_opt = yes
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
pkgdatadir = $(datadir)/volmix
pkgincludedir = $(includedir)/volmix
pkglibdir = $(libdir)/volmix
pkglibexecdir = $(libexecdir)/volmix
Expand Down Expand Up @@ -167,13 +166,14 @@ am__define_uniq_tagged_files = \
done | $(am__uniquify_input)`
am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
ACLOCAL = ${SHELL} '/home/cwage/git/cwage/volume/missing' aclocal-1.16
pkgdatadir = ${datadir}/volmix
ACLOCAL = ${SHELL} '/home/runner/work/volmix/volmix/missing' aclocal-1.16
AMTAR = $${TAR-tar}
AM_DEFAULT_VERBOSITY = 1
AUTOCONF = ${SHELL} '/home/cwage/git/cwage/volume/missing' autoconf
AUTOHEADER = ${SHELL} '/home/cwage/git/cwage/volume/missing' autoheader
AUTOMAKE = ${SHELL} '/home/cwage/git/cwage/volume/missing' automake-1.16
AWK = mawk
AUTOCONF = ${SHELL} '/home/runner/work/volmix/volmix/missing' autoconf
AUTOHEADER = ${SHELL} '/home/runner/work/volmix/volmix/missing' autoheader
AUTOMAKE = ${SHELL} '/home/runner/work/volmix/volmix/missing' automake-1.16
AWK = gawk
CC = gcc
CCDEPMODE = depmode=gcc3
CFLAGS = -g -O2
Expand All @@ -188,10 +188,10 @@ ECHO_N = -n
ECHO_T =
ETAGS = etags
EXEEXT =
GLIB_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include
GLIB_LIBS = -lglib-2.0
GTK_CFLAGS = -pthread -I/usr/include/gtk-3.0 -I/usr/include/at-spi2-atk/2.0 -I/usr/include/at-spi-2.0 -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -I/usr/include/gtk-3.0 -I/usr/include/gio-unix-2.0 -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/harfbuzz -I/usr/include/pango-1.0 -I/usr/include/fribidi -I/usr/include/harfbuzz -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/uuid -I/usr/include/freetype2 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libpng16 -I/usr/include/x86_64-linux-gnu -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include
GTK_LIBS = -lgtk-3 -lgdk-3 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0
GLIB_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include
GLIB_LIBS = -lglib-2.0
GTK_CFLAGS = -I/usr/include/gtk-3.0 -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/harfbuzz -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/fribidi -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/x86_64-linux-gnu -I/usr/include/webp -I/usr/include/gio-unix-2.0 -I/usr/include/atk-1.0 -I/usr/include/at-spi2-atk/2.0 -I/usr/include/at-spi-2.0 -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -pthread
GTK_LIBS = -lgtk-3 -lgdk-3 -lz -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0
INSTALL = /usr/bin/install -c
INSTALL_DATA = ${INSTALL} -m 644
INSTALL_PROGRAM = ${INSTALL}
Expand All @@ -201,7 +201,7 @@ LDFLAGS =
LIBOBJS =
LIBS =
LTLIBOBJS =
MAKEINFO = ${SHELL} '/home/cwage/git/cwage/volume/missing' makeinfo
MAKEINFO = ${SHELL} '/home/runner/work/volmix/volmix/missing' makeinfo
MKDIR_P = /usr/bin/mkdir -p
OBJEXT = o
PACKAGE = volmix
Expand All @@ -215,16 +215,16 @@ PATH_SEPARATOR = :
PKG_CONFIG = /usr/bin/pkg-config
PKG_CONFIG_LIBDIR =
PKG_CONFIG_PATH =
PULSE_CFLAGS = -D_REENTRANT
PULSE_LIBS = -lpulse -pthread
PULSE_CFLAGS = -D_REENTRANT
PULSE_LIBS = -lpulse -pthread
SET_MAKE =
SHELL = /bin/bash
STRIP =
VERSION = 0.1.0
abs_builddir = /home/cwage/git/cwage/volume/src
abs_srcdir = /home/cwage/git/cwage/volume/src
abs_top_builddir = /home/cwage/git/cwage/volume
abs_top_srcdir = /home/cwage/git/cwage/volume
abs_builddir = /home/runner/work/volmix/volmix/src
abs_srcdir = /home/runner/work/volmix/volmix/src
abs_top_builddir = /home/runner/work/volmix/volmix
abs_top_srcdir = /home/runner/work/volmix/volmix
ac_ct_CC = gcc
am__include = include
am__leading_dot = .
Expand All @@ -243,7 +243,7 @@ host_alias =
htmldir = ${docdir}
includedir = ${prefix}/include
infodir = ${datarootdir}/info
install_sh = ${SHELL} /home/cwage/git/cwage/volume/install-sh
install_sh = ${SHELL} /home/runner/work/volmix/volmix/install-sh
libdir = ${exec_prefix}/lib
libexecdir = ${exec_prefix}/libexec
localedir = ${datarootdir}/locale
Expand All @@ -265,7 +265,7 @@ top_build_prefix = ../
top_builddir = ..
top_srcdir = ..
volmix_SOURCES = volmix.c pulse_client.c pulse_client.h
volmix_CFLAGS = $(GTK_CFLAGS) $(PULSE_CFLAGS) $(GLIB_CFLAGS)
volmix_CFLAGS = $(GTK_CFLAGS) $(PULSE_CFLAGS) $(GLIB_CFLAGS) -DDATADIR=\"$(datadir)\"
volmix_LDADD = $(GTK_LIBS) $(PULSE_LIBS) $(GLIB_LIBS)
all: all-am

Expand Down
4 changes: 2 additions & 2 deletions src/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ am__make_running_with_option = \
test $$has_opt = yes
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
pkgdatadir = $(datadir)/@PACKAGE@
pkgincludedir = $(includedir)/@PACKAGE@
pkglibdir = $(libdir)/@PACKAGE@
pkglibexecdir = $(libexecdir)/@PACKAGE@
Expand Down Expand Up @@ -167,6 +166,7 @@ am__define_uniq_tagged_files = \
done | $(am__uniquify_input)`
am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
pkgdatadir = @pkgdatadir@
ACLOCAL = @ACLOCAL@
AMTAR = @AMTAR@
AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
Expand Down Expand Up @@ -265,7 +265,7 @@ top_build_prefix = @top_build_prefix@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
volmix_SOURCES = volmix.c pulse_client.c pulse_client.h
volmix_CFLAGS = $(GTK_CFLAGS) $(PULSE_CFLAGS) $(GLIB_CFLAGS)
volmix_CFLAGS = $(GTK_CFLAGS) $(PULSE_CFLAGS) $(GLIB_CFLAGS) -DDATADIR=\"$(datadir)\"
volmix_LDADD = $(GTK_LIBS) $(PULSE_LIBS) $(GLIB_LIBS)
all: all-am

Expand Down
44 changes: 44 additions & 0 deletions src/pulse_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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();
Expand Down Expand Up @@ -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");
Copy link

Copilot AI Aug 9, 2025

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.

Suggested change
printf("Failed to subscribe to PulseAudio events\n");
printf("Failed to subscribe to PulseAudio events. Audio application monitoring will be disabled and the client will not detect when applications start or stop audio playback.\n");

Copilot uses AI. Check for mistakes.
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);
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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,
Copy link

Copilot AI Aug 9, 2025

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.

Suggested change
printf("Sink input event detected (index=%u, type=%s)\n", index,
g_debug("Sink input event detected (index=%u, type=%s)", index,

Copilot uses AI. Check for mistakes.
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW ? "NEW" :
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ? "REMOVE" : "CHANGE");
}
}
4 changes: 4 additions & 0 deletions src/pulse_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ typedef struct {
pa_cvolume default_sink_volume;
gboolean default_sink_muted;
GList *audio_apps; // List of app_audio_t
gboolean sink_inputs_changed; // Flag to indicate sink inputs have changed
} pulse_client_t;

// Initialize PulseAudio client
Expand Down Expand Up @@ -55,6 +56,9 @@ gboolean pulse_client_toggle_master_mute(pulse_client_t *client);
// Process PulseAudio events (call periodically)
void pulse_client_iterate(pulse_client_t *client);

// Check if sink inputs have changed since last check
gboolean pulse_client_sink_inputs_changed(pulse_client_t *client);

// Application management functions
void pulse_client_refresh_apps(pulse_client_t *client);
GList* pulse_client_get_apps(pulse_client_t *client);
Expand Down
85 changes: 85 additions & 0 deletions src/volmix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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");
Copy link

Copilot AI Aug 9, 2025

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.

Suggested change
printf("Sink inputs changed, updating slider indexes...\n");
g_debug("Sink inputs changed, updating slider indexes...");

Copilot uses AI. Check for mistakes.
update_slider_indexes(app);
}

return G_SOURCE_CONTINUE; // Keep the timer running
}

// Function to update slider widget data with current sink input indexes
static void update_slider_indexes(volmix_app_t *app)
{
if (!app->volmix_window || !gtk_widget_get_visible(app->volmix_window)) {
return;
}

// Refresh the sink input list
pulse_client_refresh_apps(&app->pulse_client);

// Process the refresh operation
Copy link

Copilot AI Aug 9, 2025

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 uses AI. Check for mistakes.
if (app->pulse_client.operation) {
int timeout_count = 0;
while (pa_operation_get_state(app->pulse_client.operation) == PA_OPERATION_RUNNING &&
timeout_count < MAX_REFRESH_TIMEOUT) {
pulse_client_iterate(&app->pulse_client);
g_usleep(DELAY_5_MS_USEC);
timeout_count++;
}
pa_operation_unref(app->pulse_client.operation);
app->pulse_client.operation = NULL;
}

// Get current apps list
GList *apps = pulse_client_get_apps(&app->pulse_client);

// Find all slider widgets and update their stored sink input indexes
GList *widgets = gtk_container_get_children(GTK_CONTAINER(app->volmix_window));
update_sliders_recursive(widgets, apps);
g_list_free(widgets);
}

// Recursive function to find and update slider widgets
static void update_sliders_recursive(GList *widgets, GList *apps)
{
while (widgets) {
GtkWidget *widget = GTK_WIDGET(widgets->data);

if (GTK_IS_SCALE(widget)) {
// This is a volume slider - check if it has stored app data
uint32_t *stored_index = (uint32_t *)g_object_get_data(G_OBJECT(widget), "sink_input_index");
char *stored_app_name = (char *)g_object_get_data(G_OBJECT(widget), "app_name");

if (stored_index && stored_app_name) {
// Find the current index for this app name
GList *app_item = apps;
while (app_item) {
app_audio_t *app = (app_audio_t *)app_item->data;
if (strcmp(app->name, stored_app_name) == 0) {
if (*stored_index != app->index) {
printf("Updating slider for '%s': index %u -> %u\n",
stored_app_name, *stored_index, app->index);
Comment on lines +445 to +447
Copy link

Copilot AI Aug 9, 2025

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
*stored_index = app->index;
}
break;
}
app_item = app_item->next;
}
}
} else if (GTK_IS_CONTAINER(widget)) {
// Recursively check container widgets
GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
update_sliders_recursive(children, apps);
g_list_free(children);
}

widgets = widgets->next;
}
}

int main(int argc, char *argv[])
{
// Initialize GTK
Expand Down