/* -*- 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 . */ #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)); }