diff options
Diffstat (limited to 'panels/sound/cc-level-bar.c')
-rw-r--r-- | panels/sound/cc-level-bar.c | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/panels/sound/cc-level-bar.c b/panels/sound/cc-level-bar.c new file mode 100644 index 0000000..8636132 --- /dev/null +++ b/panels/sound/cc-level-bar.c @@ -0,0 +1,290 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Canonical Ltd. + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "cc-level-bar.h" +#include "cc-sound-enums.h" +#include "gvc-mixer-stream-private.h" + +struct _CcLevelBar +{ + GtkWidget parent_instance; + + CcStreamType type; + pa_stream *level_stream; + gdouble last_input_peak; + + gdouble value; +}; + +G_DEFINE_TYPE (CcLevelBar, cc_level_bar, GTK_TYPE_WIDGET) + +#define LED_WIDTH 12 +#define LED_HEIGHT 3 +#define LED_SPACING 4 + +#define DECAY_STEP .15 + +static void +set_peak (CcLevelBar *self, + gdouble value) +{ + if (value < 0) + value = 0; + if (value > 1) + value = 1; + + if (self->last_input_peak >= DECAY_STEP && + value < self->last_input_peak - DECAY_STEP) + value = self->last_input_peak - DECAY_STEP; + self->last_input_peak = value; + + self->value = value; + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +read_cb (pa_stream *stream, + size_t length, + void *userdata) +{ + CcLevelBar *self = userdata; + const void *data; + gdouble value; + + if (pa_stream_peek (stream, &data, &length) < 0) + { + g_warning ("Failed to read data from stream"); + return; + } + + if (!data) + { + pa_stream_drop (stream); + return; + } + + assert (length > 0); + assert (length % sizeof (float) == 0); + + value = ((const float *) data)[length / sizeof (float) -1]; + + pa_stream_drop (stream); + + set_peak (self, value); +} + +static void +suspended_cb (pa_stream *stream, + void *userdata) +{ + CcLevelBar *self = userdata; + + if (pa_stream_is_suspended (stream)) + { + g_debug ("Stream suspended"); + self->value = 0.0; + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +static void +cc_level_bar_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + if (orientation == GTK_ORIENTATION_VERTICAL) + { + *minimum = *natural = LED_HEIGHT; + } + else + { + GTK_WIDGET_CLASS (cc_level_bar_parent_class)->measure (widget, + orientation, + for_size, + minimum, + natural, + minimum_baseline, + natural_baseline); + } +} + +static void +cc_level_bar_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + CcLevelBar *self = CC_LEVEL_BAR (widget); + GdkRGBA inactive_color, active_color; + int i, n_leds; + double level; + double spacing, x_offset = 0.0; + + n_leds = gtk_widget_get_width (widget) / (LED_WIDTH + LED_SPACING); + spacing = (double) (gtk_widget_get_width (widget) - (n_leds * LED_WIDTH)) / (n_leds - 1); + level = self->value * n_leds; + + gdk_rgba_parse (&inactive_color, "#C0C0C0"); + switch (self->type) + { + default: + case CC_STREAM_TYPE_OUTPUT: + gdk_rgba_parse (&active_color, "#4a90d9"); + break; + case CC_STREAM_TYPE_INPUT: + gdk_rgba_parse (&active_color, "#ff0000"); + break; + } + + for (i = 0; i < n_leds; i++) + { + GdkRGBA blended_color; + double led_level; + + led_level = level - i; + if (led_level < 0.0) + led_level = 0.0; + else if (led_level > 1.0) + led_level = 1.0; + + blended_color = (GdkRGBA) { + .red = (1.0 - led_level) * inactive_color.red + led_level * active_color.red, + .green = (1.0 - led_level) * inactive_color.green + led_level * active_color.green, + .blue = (1.0 - led_level) * inactive_color.blue + led_level * active_color.blue, + .alpha = 1.0, + }; + + gtk_snapshot_append_color (snapshot, + &blended_color, + &GRAPHENE_RECT_INIT (x_offset, 0, + LED_WIDTH, + gtk_widget_get_height (widget))); + x_offset += LED_WIDTH + spacing; + } +} + +static void +close_stream (pa_stream *stream) +{ + if (stream == NULL) + return; + + /* Stop receiving data */ + pa_stream_set_read_callback (stream, NULL, NULL); + pa_stream_set_suspended_callback (stream, NULL, NULL); + + /* Disconnect from the stream */ + pa_stream_disconnect (stream); +} + +static void +cc_level_bar_dispose (GObject *object) +{ + CcLevelBar *self = CC_LEVEL_BAR (object); + + close_stream (self->level_stream); + g_clear_pointer (&self->level_stream, pa_stream_unref); + + G_OBJECT_CLASS (cc_level_bar_parent_class)->dispose (object); +} + +void +cc_level_bar_class_init (CcLevelBarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = cc_level_bar_dispose; + + widget_class->measure = cc_level_bar_measure; + widget_class->snapshot = cc_level_bar_snapshot; +} + +void +cc_level_bar_init (CcLevelBar *self) +{ +} + +void +cc_level_bar_set_stream (CcLevelBar *self, + GvcMixerStream *stream, + CcStreamType type) +{ + pa_context *context; + pa_sample_spec sample_spec; + pa_proplist *proplist; + pa_buffer_attr attr; + g_autofree gchar *device = NULL; + + g_return_if_fail (CC_IS_LEVEL_BAR (self)); + + close_stream (self->level_stream); + g_clear_pointer (&self->level_stream, pa_stream_unref); + + self->type = type; + + if (stream == NULL) + { + self->value = 0.0; + gtk_widget_queue_draw (GTK_WIDGET (self)); + return; + } + + context = gvc_mixer_stream_get_pa_context (stream); + + if (pa_context_get_server_protocol_version (context) < 13) + { + g_warning ("Unsupported version of PulseAudio"); + return; + } + + sample_spec.channels = 1; + sample_spec.format = PA_SAMPLE_FLOAT32; + sample_spec.rate = 25; + + proplist = pa_proplist_new (); + pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "org.gnome.VolumeControl"); + self->level_stream = pa_stream_new_with_proplist (context, "Peak detect", &sample_spec, NULL, proplist); + pa_proplist_free (proplist); + if (self->level_stream == NULL) + { + g_warning ("Failed to create monitoring stream"); + return; + } + + pa_stream_set_read_callback (self->level_stream, read_cb, self); + pa_stream_set_suspended_callback (self->level_stream, suspended_cb, self); + + memset (&attr, 0, sizeof (attr)); + attr.fragsize = sizeof (float); + attr.maxlength = (uint32_t) -1; + device = g_strdup_printf ("%u", gvc_mixer_stream_get_index (stream)); + if (pa_stream_connect_record (self->level_stream, + device, + &attr, + (pa_stream_flags_t) (PA_STREAM_DONT_MOVE | + PA_STREAM_PEAK_DETECT | + PA_STREAM_ADJUST_LATENCY)) < 0) + { + g_warning ("Failed to connect monitoring stream"); + } + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} |