/* * Copyright (C) 2014 Michal Ratajsky * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the licence, or (at your option) any later version. * * This library 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 Lesser General Public * License along with this library; if not, see . */ #include #include #include #include #include #include "pulse-connection.h" #include "pulse-helpers.h" #include "pulse-monitor.h" #include "pulse-stream-control.h" struct _PulseStreamControlPrivate { guint32 index; guint volume; pa_cvolume cvolume; pa_volume_t base_volume; pa_channel_map channel_map; PulseConnection *connection; PulseMonitor *monitor; MateMixerAppInfo *app_info; }; enum { PROP_0, PROP_INDEX, PROP_CONNECTION, N_PROPERTIES }; static GParamSpec *properties[N_PROPERTIES] = { NULL, }; static void pulse_stream_control_class_init (PulseStreamControlClass *klass); static void pulse_stream_control_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void pulse_stream_control_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void pulse_stream_control_init (PulseStreamControl *control); static void pulse_stream_control_dispose (GObject *object); static void pulse_stream_control_finalize (GObject *object); G_DEFINE_ABSTRACT_TYPE (PulseStreamControl, pulse_stream_control, MATE_MIXER_TYPE_STREAM_CONTROL) static MateMixerAppInfo * pulse_stream_control_get_app_info (MateMixerStreamControl *mmsc); static gboolean pulse_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute); static guint pulse_stream_control_get_num_channels (MateMixerStreamControl *mmsc); static guint pulse_stream_control_get_volume (MateMixerStreamControl *mmsc); static gboolean pulse_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume); static gdouble pulse_stream_control_get_decibel (MateMixerStreamControl *mmsc); static gboolean pulse_stream_control_set_decibel (MateMixerStreamControl *mmsc, gdouble decibel); static guint pulse_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, guint channel); static gboolean pulse_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, guint channel, guint volume); static gdouble pulse_stream_control_get_channel_decibel (MateMixerStreamControl *mmsc, guint channel); static gboolean pulse_stream_control_set_channel_decibel (MateMixerStreamControl *mmsc, guint channel, gdouble decibel); static MateMixerChannelPosition pulse_stream_control_get_channel_position (MateMixerStreamControl *mmsc, guint channel); static gboolean pulse_stream_control_has_channel_position (MateMixerStreamControl *mmsc, MateMixerChannelPosition position); static gboolean pulse_stream_control_set_balance (MateMixerStreamControl *mmsc, gfloat balance); static gboolean pulse_stream_control_set_fade (MateMixerStreamControl *mmsc, gfloat fade); static gboolean pulse_stream_control_get_monitor_enabled (MateMixerStreamControl *mmsc); static gboolean pulse_stream_control_set_monitor_enabled (MateMixerStreamControl *mmsc, gboolean enabled); static guint pulse_stream_control_get_min_volume (MateMixerStreamControl *mmsc); static guint pulse_stream_control_get_max_volume (MateMixerStreamControl *mmsc); static guint pulse_stream_control_get_normal_volume (MateMixerStreamControl *mmsc); static guint pulse_stream_control_get_base_volume (MateMixerStreamControl *mmsc); static void on_monitor_value (PulseMonitor *monitor, gdouble value, PulseStreamControl *control); static void set_balance_fade (PulseStreamControl *control); static gboolean set_cvolume (PulseStreamControl *control, pa_cvolume *cvolume); static void pulse_stream_control_class_init (PulseStreamControlClass *klass) { GObjectClass *object_class; MateMixerStreamControlClass *control_class; object_class = G_OBJECT_CLASS (klass); object_class->dispose = pulse_stream_control_dispose; object_class->finalize = pulse_stream_control_finalize; object_class->get_property = pulse_stream_control_get_property; object_class->set_property = pulse_stream_control_set_property; control_class = MATE_MIXER_STREAM_CONTROL_CLASS (klass); control_class->get_app_info = pulse_stream_control_get_app_info; control_class->set_mute = pulse_stream_control_set_mute; control_class->get_num_channels = pulse_stream_control_get_num_channels; control_class->get_volume = pulse_stream_control_get_volume; control_class->set_volume = pulse_stream_control_set_volume; control_class->get_decibel = pulse_stream_control_get_decibel; control_class->set_decibel = pulse_stream_control_set_decibel; control_class->get_channel_volume = pulse_stream_control_get_channel_volume; control_class->set_channel_volume = pulse_stream_control_set_channel_volume; control_class->get_channel_decibel = pulse_stream_control_get_channel_decibel; control_class->set_channel_decibel = pulse_stream_control_set_channel_decibel; control_class->get_channel_position = pulse_stream_control_get_channel_position; control_class->has_channel_position = pulse_stream_control_has_channel_position; control_class->set_balance = pulse_stream_control_set_balance; control_class->set_fade = pulse_stream_control_set_fade; control_class->get_monitor_enabled = pulse_stream_control_get_monitor_enabled; control_class->set_monitor_enabled = pulse_stream_control_set_monitor_enabled; control_class->get_min_volume = pulse_stream_control_get_min_volume; control_class->get_max_volume = pulse_stream_control_get_max_volume; control_class->get_normal_volume = pulse_stream_control_get_normal_volume; control_class->get_base_volume = pulse_stream_control_get_base_volume; properties[PROP_INDEX] = g_param_spec_uint ("index", "Index", "Index of the stream control", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); properties[PROP_CONNECTION] = g_param_spec_object ("connection", "Connection", "PulseAudio connection", PULSE_TYPE_CONNECTION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPERTIES, properties); g_type_class_add_private (object_class, sizeof (PulseStreamControlPrivate)); } static void pulse_stream_control_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec) { PulseStreamControl *control; control = PULSE_STREAM_CONTROL (object); switch (param_id) { case PROP_INDEX: g_value_set_uint (value, control->priv->index); break; case PROP_CONNECTION: g_value_set_object (value, control->priv->connection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void pulse_stream_control_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec) { PulseStreamControl *control; control = PULSE_STREAM_CONTROL (object); switch (param_id) { case PROP_INDEX: control->priv->index = g_value_get_uint (value); break; case PROP_CONNECTION: control->priv->connection = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } static void pulse_stream_control_init (PulseStreamControl *control) { control->priv = G_TYPE_INSTANCE_GET_PRIVATE (control, PULSE_TYPE_STREAM_CONTROL, PulseStreamControlPrivate); /* Initialize empty volume and channel map structures, they will be used * if the stream does not support volume */ pa_cvolume_init (&control->priv->cvolume); pa_channel_map_init (&control->priv->channel_map); } static void pulse_stream_control_dispose (GObject *object) { PulseStreamControl *control; control = PULSE_STREAM_CONTROL (object); g_clear_object (&control->priv->monitor); g_clear_object (&control->priv->connection); G_OBJECT_CLASS (pulse_stream_control_parent_class)->dispose (object); } static void pulse_stream_control_finalize (GObject *object) { PulseStreamControl *control; control = PULSE_STREAM_CONTROL (object); if (control->priv->app_info != NULL) _mate_mixer_app_info_free (control->priv->app_info); G_OBJECT_CLASS (pulse_stream_control_parent_class)->finalize (object); } guint32 pulse_stream_control_get_index (PulseStreamControl *control) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), 0); return control->priv->index; } PulseConnection * pulse_stream_control_get_connection (PulseStreamControl *control) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), NULL); return control->priv->connection; } PulseMonitor * pulse_stream_control_get_monitor (PulseStreamControl *control) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), NULL); return control->priv->monitor; } const pa_cvolume * pulse_stream_control_get_cvolume (PulseStreamControl *control) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), NULL); return &control->priv->cvolume; } const pa_channel_map * pulse_stream_control_get_channel_map (PulseStreamControl *control) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (control), NULL); return &control->priv->channel_map; } void pulse_stream_control_set_app_info (PulseStreamControl *control, MateMixerAppInfo *info) { g_return_if_fail (PULSE_IS_STREAM_CONTROL (control)); if G_UNLIKELY (control->priv->app_info) _mate_mixer_app_info_free (control->priv->app_info); control->priv->app_info = info; } void pulse_stream_control_set_channel_map (PulseStreamControl *control, const pa_channel_map *map) { MateMixerStreamControlFlags flags; g_return_if_fail (PULSE_IS_STREAM_CONTROL (control)); g_return_if_fail (map != NULL); flags = mate_mixer_stream_control_get_flags (MATE_MIXER_STREAM_CONTROL (control)); if (pa_channel_map_valid (map)) { if (pa_channel_map_can_balance (map)) flags |= MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; else flags &= ~MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; if (pa_channel_map_can_fade (map)) flags |= MATE_MIXER_STREAM_CONTROL_CAN_FADE; else flags &= ~MATE_MIXER_STREAM_CONTROL_CAN_FADE; control->priv->channel_map = *map; } else { flags &= ~(MATE_MIXER_STREAM_CONTROL_CAN_BALANCE | MATE_MIXER_STREAM_CONTROL_CAN_FADE); /* If the channel map is not valid, create an empty channel map, which * also won't validate, but at least we know what it is */ pa_channel_map_init (&control->priv->channel_map); } _mate_mixer_stream_control_set_flags (MATE_MIXER_STREAM_CONTROL (control), flags); } void pulse_stream_control_set_cvolume (PulseStreamControl *control, const pa_cvolume *cvolume, pa_volume_t base_volume) { MateMixerStreamControlFlags flags; g_return_if_fail (PULSE_IS_STREAM_CONTROL (control)); /* The base volume is not a property */ control->priv->base_volume = base_volume; flags = mate_mixer_stream_control_get_flags (MATE_MIXER_STREAM_CONTROL (control)); g_object_freeze_notify (G_OBJECT (control)); if (cvolume != NULL && pa_cvolume_valid (cvolume)) { /* Decibel volume and volume settability flags must be provided by * the implementation */ flags |= MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE; if (pa_cvolume_equal (&control->priv->cvolume, cvolume) == 0) { control->priv->cvolume = *cvolume; control->priv->volume = (guint) pa_cvolume_max (&control->priv->cvolume); g_object_notify (G_OBJECT (control), "volume"); } } else { flags &= ~(MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE | MATE_MIXER_STREAM_CONTROL_VOLUME_WRITABLE | MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL); /* If the cvolume is not valid, create an empty cvolume, which also * won't validate, but at least we know what it is */ pa_cvolume_init (&control->priv->cvolume); if (control->priv->volume != (guint) PA_VOLUME_MUTED) { control->priv->volume = (guint) PA_VOLUME_MUTED; g_object_notify (G_OBJECT (control), "volume"); } } _mate_mixer_stream_control_set_flags (MATE_MIXER_STREAM_CONTROL (control), flags); /* Changing volume may change the balance and fade values as well */ set_balance_fade (control); g_object_thaw_notify (G_OBJECT (control)); } static MateMixerAppInfo * pulse_stream_control_get_app_info (MateMixerStreamControl *mmsc) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), NULL); return PULSE_STREAM_CONTROL (mmsc)->priv->app_info; } static gboolean pulse_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); return PULSE_STREAM_CONTROL_GET_CLASS (mmsc)->set_mute (PULSE_STREAM_CONTROL (mmsc), mute); } static guint pulse_stream_control_get_num_channels (MateMixerStreamControl *mmsc) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), 0); return PULSE_STREAM_CONTROL (mmsc)->priv->channel_map.channels; } static guint pulse_stream_control_get_volume (MateMixerStreamControl *mmsc) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); return PULSE_STREAM_CONTROL (mmsc)->priv->volume; } static gboolean pulse_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume) { PulseStreamControl *control; pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); control = PULSE_STREAM_CONTROL (mmsc); cvolume = control->priv->cvolume; if (pa_cvolume_scale (&cvolume, (pa_volume_t) volume) == NULL) return FALSE; return set_cvolume (control, &cvolume); } static gdouble pulse_stream_control_get_decibel (MateMixerStreamControl *mmsc) { gdouble value; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), -MATE_MIXER_INFINITY); value = pa_sw_volume_to_dB (pulse_stream_control_get_volume (mmsc)); /* PA_VOLUME_MUTED is converted to PA_DECIBEL_MININFTY */ return (value == PA_DECIBEL_MININFTY) ? -MATE_MIXER_INFINITY : value; } static gboolean pulse_stream_control_set_decibel (MateMixerStreamControl *mmsc, gdouble decibel) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); return pulse_stream_control_set_volume (mmsc, pa_sw_volume_from_dB (decibel)); } static guint pulse_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, guint channel) { PulseStreamControl *control; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); control = PULSE_STREAM_CONTROL (mmsc); if (channel >= control->priv->cvolume.channels) return (guint) PA_VOLUME_MUTED; return (guint) control->priv->cvolume.values[channel]; } static gboolean pulse_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, guint channel, guint volume) { PulseStreamControl *control; pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); control = PULSE_STREAM_CONTROL (mmsc); if (channel >= control->priv->cvolume.channels) return FALSE; /* This is safe, because the cvolume is validated by set_cvolume() */ cvolume = control->priv->cvolume; cvolume.values[channel] = (pa_volume_t) volume; return set_cvolume (control, &cvolume); } static gdouble pulse_stream_control_get_channel_decibel (MateMixerStreamControl *mmsc, guint channel) { PulseStreamControl *control; gdouble value; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), -MATE_MIXER_INFINITY); control = PULSE_STREAM_CONTROL (mmsc); if (channel >= control->priv->cvolume.channels) return -MATE_MIXER_INFINITY; value = pa_sw_volume_to_dB (control->priv->cvolume.values[channel]); return (value == PA_DECIBEL_MININFTY) ? -MATE_MIXER_INFINITY : value; } static gboolean pulse_stream_control_set_channel_decibel (MateMixerStreamControl *mmsc, guint channel, gdouble decibel) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); return pulse_stream_control_set_channel_volume (mmsc, channel, pa_sw_volume_from_dB (decibel)); } static MateMixerChannelPosition pulse_stream_control_get_channel_position (MateMixerStreamControl *mmsc, guint channel) { PulseStreamControl *control; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), MATE_MIXER_CHANNEL_UNKNOWN); control = PULSE_STREAM_CONTROL (mmsc); if (channel >= control->priv->channel_map.channels) return MATE_MIXER_CHANNEL_UNKNOWN; return pulse_convert_position_from_pulse (control->priv->channel_map.map[channel]); } static gboolean pulse_stream_control_has_channel_position (MateMixerStreamControl *mmsc, MateMixerChannelPosition position) { PulseStreamControl *control; pa_channel_position_t p; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); control = PULSE_STREAM_CONTROL (mmsc); /* Handle invalid position as a special case, otherwise this function would * return TRUE for e.g. unknown index in a default channel map */ p = pulse_convert_position_to_pulse (position); if (p == PA_CHANNEL_POSITION_INVALID) return FALSE; if (pa_channel_map_has_position (&control->priv->channel_map, p) != 0) return TRUE; else return FALSE; } static gboolean pulse_stream_control_set_balance (MateMixerStreamControl *mmsc, gfloat balance) { PulseStreamControl *control; pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); control = PULSE_STREAM_CONTROL (mmsc); cvolume = control->priv->cvolume; if (pa_cvolume_set_balance (&cvolume, &control->priv->channel_map, balance) == NULL) return FALSE; return set_cvolume (control, &cvolume); } static gboolean pulse_stream_control_set_fade (MateMixerStreamControl *mmsc, gfloat fade) { PulseStreamControl *control; pa_cvolume cvolume; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); control = PULSE_STREAM_CONTROL (mmsc); cvolume = control->priv->cvolume; if (pa_cvolume_set_fade (&cvolume, &control->priv->channel_map, fade) == NULL) return FALSE; return set_cvolume (control, &cvolume); } static gboolean pulse_stream_control_get_monitor_enabled (MateMixerStreamControl *mmsc) { PulseStreamControl *control; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); control = PULSE_STREAM_CONTROL (mmsc); if (control->priv->monitor != NULL) return pulse_monitor_get_enabled (control->priv->monitor); return FALSE; } static gboolean pulse_stream_control_set_monitor_enabled (MateMixerStreamControl *mmsc, gboolean enabled) { PulseStreamControl *control; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), FALSE); control = PULSE_STREAM_CONTROL (mmsc); if (enabled == TRUE) { if (control->priv->monitor == NULL) { control->priv->monitor = PULSE_STREAM_CONTROL_GET_CLASS (control)->create_monitor (control); if G_UNLIKELY (control->priv->monitor == NULL) return FALSE; g_signal_connect (G_OBJECT (control->priv->monitor), "value", G_CALLBACK (on_monitor_value), control); } } else { if (control->priv->monitor == NULL) return FALSE; } return pulse_monitor_set_enabled (control->priv->monitor, enabled); } static guint pulse_stream_control_get_min_volume (MateMixerStreamControl *mmsc) { return (guint) PA_VOLUME_MUTED; } static guint pulse_stream_control_get_max_volume (MateMixerStreamControl *mmsc) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); return (guint) PA_VOLUME_UI_MAX; } static guint pulse_stream_control_get_normal_volume (MateMixerStreamControl *mmsc) { g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); return (guint) PA_VOLUME_NORM; } static guint pulse_stream_control_get_base_volume (MateMixerStreamControl *mmsc) { PulseStreamControl *control; g_return_val_if_fail (PULSE_IS_STREAM_CONTROL (mmsc), (guint) PA_VOLUME_MUTED); control = PULSE_STREAM_CONTROL (mmsc); if (control->priv->base_volume > 0) return control->priv->base_volume; else return (guint) PA_VOLUME_NORM; } static void on_monitor_value (PulseMonitor *monitor, gdouble value, PulseStreamControl *control) { g_signal_emit_by_name (G_OBJECT (control), "monitor-value", value); } static void set_balance_fade (PulseStreamControl *control) { gfloat value; /* PulseAudio returns the default 0.0f value on error, so skip checking validity * of the channel map and cvolume */ value = pa_cvolume_get_balance (&control->priv->cvolume, &control->priv->channel_map); _mate_mixer_stream_control_set_balance (MATE_MIXER_STREAM_CONTROL (control), value); value = pa_cvolume_get_fade (&control->priv->cvolume, &control->priv->channel_map); _mate_mixer_stream_control_set_fade (MATE_MIXER_STREAM_CONTROL (control), value); } static gboolean set_cvolume (PulseStreamControl *control, pa_cvolume *cvolume) { PulseStreamControlClass *klass; if (pa_cvolume_valid (cvolume) == 0) return FALSE; if (pa_cvolume_equal (cvolume, &control->priv->cvolume) != 0) return TRUE; klass = PULSE_STREAM_CONTROL_GET_CLASS (control); if (klass->set_volume (control, cvolume) == FALSE) return FALSE; control->priv->cvolume = *cvolume; control->priv->volume = (guint) pa_cvolume_max (cvolume); g_object_notify (G_OBJECT (control), "volume"); /* Changing volume may change the balance and fade values as well */ set_balance_fade (control); return TRUE; }