/* * Copyright (C) 2018 Canonical Ltd. * Copyright (C) 2023 Marco Melorio * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include "cc-stream-row.h" #include "cc-volume-levels-window.h" struct _CcVolumeLevelsWindow { GtkWindow parent_instance; GtkListBox *listbox; GtkSizeGroup *label_size_group; GvcMixerControl *mixer_control; GListStore *stream_list; }; G_DEFINE_TYPE (CcVolumeLevelsWindow, cc_volume_levels_window, GTK_TYPE_WINDOW) static gint sort_stream (gconstpointer a, gconstpointer b, gpointer user_data) { CcVolumeLevelsWindow *self = user_data; GvcMixerStream *stream_a, *stream_b, *event_sink; g_autofree gchar *name_a = NULL; g_autofree gchar *name_b = NULL; stream_a = GVC_MIXER_STREAM (a); stream_b = GVC_MIXER_STREAM (b); /* Put the system sound events control at the top */ event_sink = gvc_mixer_control_get_event_sink_input (self->mixer_control); if (stream_a == event_sink) return -1; else if (stream_b == event_sink) return 1; name_a = g_utf8_casefold (gvc_mixer_stream_get_name (stream_a), -1); name_b = g_utf8_casefold (gvc_mixer_stream_get_name (stream_b), -1); return g_strcmp0 (name_a, name_b); } static gboolean filter_stream (gpointer item, gpointer user_data) { GvcMixerStream *stream = item; const gchar *app_id = gvc_mixer_stream_get_application_id (stream); /* Filter out master volume controls */ if (g_strcmp0 (app_id, "org.gnome.VolumeControl") == 0 || g_strcmp0 (app_id, "org.PulseAudio.pavucontrol") == 0) { return FALSE; } /* Filter out streams that aren't volume controls */ if (GVC_IS_MIXER_SOURCE (stream) || GVC_IS_MIXER_SINK (stream) || gvc_mixer_stream_is_virtual (stream) || gvc_mixer_stream_is_event_stream (stream)) { return FALSE; } return TRUE; } static GtkWidget * create_stream_row (gpointer item, gpointer user_data) { CcVolumeLevelsWindow *self = user_data; GvcMixerStream *stream = item; guint id; CcStreamRow *row; id = gvc_mixer_stream_get_id (stream); row = cc_stream_row_new (self->label_size_group, stream, id, CC_STREAM_TYPE_OUTPUT, self->mixer_control); return GTK_WIDGET (row); } static void stream_added_cb (CcVolumeLevelsWindow *self, guint id) { GvcMixerStream *stream = gvc_mixer_control_lookup_stream_id (self->mixer_control, id); if (stream == NULL) return; g_list_store_append (self->stream_list, G_OBJECT (stream)); } static void stream_removed_cb (CcVolumeLevelsWindow *self, guint id) { guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self->stream_list)); for (guint i = 0; i < n_items; i++) { g_autoptr(GObject) item; guint stream_id; item = g_list_model_get_item (G_LIST_MODEL (self->stream_list), i); stream_id = gvc_mixer_stream_get_id (GVC_MIXER_STREAM (item)); if (id == stream_id) { g_list_store_remove (self->stream_list, i); return; } } } static void add_stream (gpointer data, gpointer user_data) { CcVolumeLevelsWindow *self = user_data; GvcMixerStream *stream = data; g_list_store_append (self->stream_list, G_OBJECT (stream)); } static void cc_volume_levels_window_dispose (GObject *object) { CcVolumeLevelsWindow *self = CC_VOLUME_LEVELS_WINDOW (object); g_clear_object (&self->mixer_control); G_OBJECT_CLASS (cc_volume_levels_window_parent_class)->dispose (object); } void cc_volume_levels_window_class_init (CcVolumeLevelsWindowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = cc_volume_levels_window_dispose; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/sound/cc-volume-levels-window.ui"); gtk_widget_class_bind_template_child (widget_class, CcVolumeLevelsWindow, listbox); gtk_widget_class_bind_template_child (widget_class, CcVolumeLevelsWindow, label_size_group); } void cc_volume_levels_window_init (CcVolumeLevelsWindow *self) { GtkFilter *filter; GtkFilterListModel *filter_model; GtkSorter *sorter; GtkSortListModel *sort_model; gtk_widget_init_template (GTK_WIDGET (self)); self->stream_list = g_list_store_new (GVC_TYPE_MIXER_STREAM); filter = GTK_FILTER (gtk_custom_filter_new (filter_stream, NULL, NULL)); filter_model = gtk_filter_list_model_new (G_LIST_MODEL (self->stream_list), filter); sorter = GTK_SORTER (gtk_custom_sorter_new (sort_stream, self, NULL)); sort_model = gtk_sort_list_model_new (G_LIST_MODEL (filter_model), sorter); gtk_list_box_bind_model (self->listbox, G_LIST_MODEL (sort_model), create_stream_row, self, NULL); } CcVolumeLevelsWindow * cc_volume_levels_window_new (GvcMixerControl *mixer_control) { CcVolumeLevelsWindow *self; g_autoptr(GSList) streams = NULL; self = g_object_new (CC_TYPE_VOLUME_LEVELS_WINDOW, NULL); self->mixer_control = g_object_ref (mixer_control); streams = gvc_mixer_control_get_streams (self->mixer_control); g_slist_foreach (streams, add_stream, self); g_signal_connect_object (self->mixer_control, "stream-added", G_CALLBACK (stream_added_cb), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->mixer_control, "stream-removed", G_CALLBACK (stream_removed_cb), self, G_CONNECT_SWAPPED); return self; }