diff options
-rw-r--r-- | src/Makefile.am | 14 | ||||
-rw-r--r-- | src/cardwidget.cc | 94 | ||||
-rw-r--r-- | src/cardwidget.h | 67 | ||||
-rw-r--r-- | src/channelwidget.cc | 132 | ||||
-rw-r--r-- | src/channelwidget.h | 57 | ||||
-rw-r--r-- | src/mainwindow.cc | 834 | ||||
-rw-r--r-- | src/mainwindow.h | 99 | ||||
-rw-r--r-- | src/minimalstreamwidget.cc | 122 | ||||
-rw-r--r-- | src/minimalstreamwidget.h | 56 | ||||
-rw-r--r-- | src/pavucontrol.cc | 1835 | ||||
-rw-r--r-- | src/pavucontrol.glade | 107 | ||||
-rw-r--r-- | src/pavucontrol.h | 67 | ||||
-rw-r--r-- | src/rolewidget.cc | 72 | ||||
-rw-r--r-- | src/rolewidget.h | 40 | ||||
-rw-r--r-- | src/sinkinputwidget.cc | 132 | ||||
-rw-r--r-- | src/sinkinputwidget.h | 70 | ||||
-rw-r--r-- | src/sinkwidget.cc | 85 | ||||
-rw-r--r-- | src/sinkwidget.h | 46 | ||||
-rw-r--r-- | src/sourceoutputwidget.cc | 106 | ||||
-rw-r--r-- | src/sourceoutputwidget.h | 68 | ||||
-rw-r--r-- | src/sourcewidget.cc | 85 | ||||
-rw-r--r-- | src/sourcewidget.h | 46 | ||||
-rw-r--r-- | src/streamwidget.cc | 115 | ||||
-rw-r--r-- | src/streamwidget.h | 56 |
24 files changed, 2482 insertions, 1923 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index a990de3..629219c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,7 +24,19 @@ glade_DATA=pavucontrol.glade desktop_in_files=pavucontrol.desktop.in desktop_DATA=$(desktop_in_files:.desktop.in=.desktop) -pavucontrol_SOURCES=pavucontrol.cc i18n.h +pavucontrol_SOURCES= \ + minimalstreamwidget.h minimalstreamwidget.cc \ + channelwidget.h channelwidget.cc \ + streamwidget.h streamwidget.cc \ + cardwidget.h cardwidget.cc \ + sinkwidget.h sinkwidget.cc \ + sourcewidget.h sourcewidget.cc \ + sinkinputwidget.h sinkinputwidget.cc \ + sourceoutputwidget.h sourceoutputwidget.cc \ + rolewidget.h rolewidget.cc \ + mainwindow.h mainwindow.cc \ + pavucontrol.h pavucontrol.cc \ + i18n.h pavucontrol_LDADD=$(AM_LDADD) $(GUILIBS_LIBS) $(PULSE_LIBS) pavucontrol_CXXFLAGS=$(AM_CXXFLAGS) $(GUILIBS_CFLAGS) $(PULSE_CFLAGS) -DLOCALEDIR=\"$(localedir)\" diff --git a/src/cardwidget.cc b/src/cardwidget.cc new file mode 100644 index 0000000..ea4b208 --- /dev/null +++ b/src/cardwidget.cc @@ -0,0 +1,94 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "cardwidget.h" + +#include "i18n.h" + +/*** CardWidget ***/ +CardWidget::CardWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + Gtk::VBox(cobject) { + + x->get_widget("nameLabel", nameLabel); + x->get_widget("profileList", profileList); + x->get_widget("iconImage", iconImage); + + treeModel = Gtk::ListStore::create(profileModel); + profileList->set_model(treeModel); + profileList->pack_start(profileModel.desc); + + profileList->signal_changed().connect( sigc::mem_fun(*this, &CardWidget::onProfileChange)); +} + +CardWidget* CardWidget::create() { + CardWidget* w; + Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "cardWidget"); + x->get_widget_derived("cardWidget", w); + return w; +} + + +void CardWidget::prepareMenu() { + int idx = 0; + int active_idx = -1; + + treeModel->clear(); + /* Fill the ComboBox's Tree Model */ + for (std::map<Glib::ustring, Glib::ustring>::iterator i = profiles.begin(); i != profiles.end(); ++i) { + Gtk::TreeModel::Row row = *(treeModel->append()); + row[profileModel.name] = i->first; + row[profileModel.desc] = i->second; + if (i->first == activeProfile) + active_idx = idx; + idx++; + } + + if (active_idx >= 0) + profileList->set_active(active_idx); +} + +void CardWidget::onProfileChange() { + Gtk::TreeModel::iterator iter; + + if (updating) + return; + + iter = profileList->get_active(); + if (iter) + { + Gtk::TreeModel::Row row = *iter; + if (row) + { + pa_operation* o; + Glib::ustring profile = row[profileModel.name]; + + if (!(o = pa_context_set_card_profile_by_index(get_context(), index, profile.c_str(), NULL, NULL))) { + show_error(_("pa_context_set_card_profile_by_index() failed")); + return; + } + + pa_operation_unref(o); + } + } +} diff --git a/src/cardwidget.h b/src/cardwidget.h new file mode 100644 index 0000000..d420c83 --- /dev/null +++ b/src/cardwidget.h @@ -0,0 +1,67 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef cardwidget_h +#define cardwidget_h + +#include "pavucontrol.h" + +class CardWidget : public Gtk::VBox { +public: + CardWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + static CardWidget* create(); + + Gtk::Label *nameLabel; + Gtk::ToggleButton *streamToggleButton; + Gtk::Menu menu; + Gtk::Image *iconImage; + Glib::ustring name; + uint32_t index; + bool updating; + + std::map<Glib::ustring,Glib::ustring> profiles; + Glib::ustring activeProfile; + bool hasSinks; + bool hasSources; + + void prepareMenu(); + +protected: + virtual void onProfileChange(); + + /* Tree model columns */ + class ModelColumns : public Gtk::TreeModel::ColumnRecord + { + public: + + ModelColumns() + { add(name); add(desc); } + + Gtk::TreeModelColumn<Glib::ustring> name; + Gtk::TreeModelColumn<Glib::ustring> desc; + }; + + ModelColumns profileModel; + + Gtk::ComboBox *profileList; + Glib::RefPtr<Gtk::ListStore> treeModel; +}; + +#endif diff --git a/src/channelwidget.cc b/src/channelwidget.cc new file mode 100644 index 0000000..eac41ad --- /dev/null +++ b/src/channelwidget.cc @@ -0,0 +1,132 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "channelwidget.h" +#include "streamwidget.h" + +#include "i18n.h" + +static bool show_decibel = true; + +/*** ChannelWidget ***/ + +ChannelWidget::ChannelWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + Gtk::EventBox(cobject), + volumeScaleEnabled(true) { + + x->get_widget("channelLabel", channelLabel); + x->get_widget("volumeLabel", volumeLabel); + x->get_widget("volumeScale", volumeScale); + + volumeScale->set_value(100.0); + volumeScale->set_increments(100.0/PA_VOLUME_NORM, 100.0/PA_VOLUME_NORM); + + volumeScale->signal_value_changed().connect(sigc::mem_fun(*this, &ChannelWidget::onVolumeScaleValueChanged)); +} + +ChannelWidget* ChannelWidget::create() { + ChannelWidget* w; + Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "channelWidget"); + x->get_widget_derived("channelWidget", w); + return w; +} + +void ChannelWidget::setVolume(pa_volume_t volume) { + double v; + char txt[64]; + + v = ((gdouble) volume * 100) / PA_VOLUME_NORM; + + if (can_decibel && show_decibel) { + double dB = pa_sw_volume_to_dB(volume); + + if (dB > PA_DECIBEL_MININFTY) { + snprintf(txt, sizeof(txt), "%0.2f dB", dB); + volumeLabel->set_tooltip_text(txt); + } else + volumeLabel->set_tooltip_markup("-∞dB"); + volumeLabel->set_has_tooltip(TRUE); + } else + volumeLabel->set_has_tooltip(FALSE); + + snprintf(txt, sizeof(txt), "%0.0f%%", v); + volumeLabel->set_text(txt); + + volumeScaleEnabled = false; + volumeScale->set_value(v > 100 ? 100 : v); + volumeScaleEnabled = true; +} + +void ChannelWidget::onVolumeScaleValueChanged() { + + if (!volumeScaleEnabled) + return; + + if (streamWidget->updating) + return; + + pa_volume_t volume = (pa_volume_t) ((volumeScale->get_value() * PA_VOLUME_NORM) / 100); + streamWidget->updateChannelVolume(channel, volume); + + if (beepDevice != "") { + ca_context_change_device(ca_gtk_context_get(), beepDevice.c_str()); + + ca_context_cancel(ca_gtk_context_get(), 2); + + ca_gtk_play_for_widget(GTK_WIDGET(volumeScale->gobj()), + 2, + CA_PROP_EVENT_DESCRIPTION, _("Volume Control Feedback Sound"), + CA_PROP_EVENT_ID, "audio-volume-change", + CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", + CA_PROP_CANBERRA_VOLUME, "0", + CA_PROP_CANBERRA_ENABLE, "1", + NULL); + + ca_context_change_device(ca_gtk_context_get(), NULL); + } +} + +void ChannelWidget::set_sensitive(bool enabled) { + Gtk::EventBox::set_sensitive(enabled); + + channelLabel->set_sensitive(enabled); + volumeLabel->set_sensitive(enabled); + volumeScale->set_sensitive(enabled); +} + +void ChannelWidget::setBaseVolume(pa_volume_t v) { + + gtk_scale_add_mark(GTK_SCALE(volumeScale->gobj()), 0.0, (GtkPositionType) GTK_POS_BOTTOM, _("<small>Silence</small>")); + gtk_scale_add_mark(GTK_SCALE(volumeScale->gobj()), 100.0, (GtkPositionType) GTK_POS_BOTTOM, _("<small>Max</small>")); + + if (v > PA_VOLUME_MUTED && v < PA_VOLUME_NORM) { + double p = ((double) v * 100) / PA_VOLUME_NORM; + gtk_scale_add_mark(GTK_SCALE(volumeScale->gobj()), p, (GtkPositionType) GTK_POS_BOTTOM, _("<small><i>Base</i></small>")); + } + +} + +void ChannelWidget::setSteps(unsigned n) { + volumeScale->set_increments(100.0/(n-1), 100.0/(n-1)); +} diff --git a/src/channelwidget.h b/src/channelwidget.h new file mode 100644 index 0000000..1de22d0 --- /dev/null +++ b/src/channelwidget.h @@ -0,0 +1,57 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef channelwidget_h +#define channelwidget_h + +#include "pavucontrol.h" + +#include <canberra-gtk.h> + +class StreamWidget; + +class ChannelWidget : public Gtk::EventBox { +public: + ChannelWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + static ChannelWidget* create(); + + void setVolume(pa_volume_t volume); + + Gtk::Label *channelLabel; + Gtk::Label *volumeLabel; + Gtk::HScale *volumeScale; + + int channel; + StreamWidget *streamWidget; + + void onVolumeScaleValueChanged(); + + bool can_decibel; + bool volumeScaleEnabled; + + Glib::ustring beepDevice; + + virtual void set_sensitive(bool enabled); + virtual void setBaseVolume(pa_volume_t); + virtual void setSteps(unsigned n); +}; + + +#endif diff --git a/src/mainwindow.cc b/src/mainwindow.cc new file mode 100644 index 0000000..0277bb0 --- /dev/null +++ b/src/mainwindow.cc @@ -0,0 +1,834 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "mainwindow.h" + +#include "cardwidget.h" +#include "sinkwidget.h" +#include "sourcewidget.h" +#include "sinkinputwidget.h" +#include "sourceoutputwidget.h" +#include "rolewidget.h" + +#include "i18n.h" + +/*** MainWindow ***/ + +MainWindow::MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + Gtk::Window(cobject), + showSinkInputType(SINK_INPUT_CLIENT), + showSinkType(SINK_ALL), + showSourceOutputType(SOURCE_OUTPUT_CLIENT), + showSourceType(SOURCE_NO_MONITOR), + eventRoleWidget(NULL){ + + x->get_widget("cardsVBox", cardsVBox); + x->get_widget("streamsVBox", streamsVBox); + x->get_widget("recsVBox", recsVBox); + x->get_widget("sinksVBox", sinksVBox); + x->get_widget("sourcesVBox", sourcesVBox); + x->get_widget("noCardsLabel", noCardsLabel); + x->get_widget("noStreamsLabel", noStreamsLabel); + x->get_widget("noRecsLabel", noRecsLabel); + x->get_widget("noSinksLabel", noSinksLabel); + x->get_widget("noSourcesLabel", noSourcesLabel); + x->get_widget("sinkInputTypeComboBox", sinkInputTypeComboBox); + x->get_widget("sourceOutputTypeComboBox", sourceOutputTypeComboBox); + x->get_widget("sinkTypeComboBox", sinkTypeComboBox); + x->get_widget("sourceTypeComboBox", sourceTypeComboBox); + x->get_widget("notebook", notebook); + + cardsVBox->set_reallocate_redraws(true); + sourcesVBox->set_reallocate_redraws(true); + streamsVBox->set_reallocate_redraws(true); + recsVBox->set_reallocate_redraws(true); + sinksVBox->set_reallocate_redraws(true); + + sinkInputTypeComboBox->set_active((int) showSinkInputType); + sourceOutputTypeComboBox->set_active((int) showSourceOutputType); + sinkTypeComboBox->set_active((int) showSinkType); + sourceTypeComboBox->set_active((int) showSourceType); + + sinkInputTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSinkInputTypeComboBoxChanged)); + sourceOutputTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSourceOutputTypeComboBoxChanged)); + sinkTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSinkTypeComboBoxChanged)); + sourceTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSourceTypeComboBoxChanged)); +} + +MainWindow* MainWindow::create() { + MainWindow* w; + Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "mainWindow"); + x->get_widget_derived("mainWindow", w); + return w; +} + +void MainWindow::on_realize() { + Gtk::Window::on_realize(); + + get_window()->set_cursor(Gdk::Cursor(Gdk::WATCH)); +} + +MainWindow::~MainWindow() { + while (!clientNames.empty()) { + std::map<uint32_t, char*>::iterator i = clientNames.begin(); + g_free(i->second); + clientNames.erase(i); + } +} + +static void set_icon_name_fallback(Gtk::Image *i, const char *name, Gtk::IconSize size) { + Glib::RefPtr<Gtk::IconTheme> theme; + Glib::RefPtr<Gdk::Pixbuf> pixbuf; + gint width = 24, height = 24; + + Gtk::IconSize::lookup(size, width, height); + theme = Gtk::IconTheme::get_default(); + pixbuf = theme->load_icon(name, width, Gtk::ICON_LOOKUP_GENERIC_FALLBACK); + + if (pixbuf) + i->set(pixbuf); + else + i->set(name); +} + +void MainWindow::updateCard(const pa_card_info &info) { + CardWidget *w; + bool is_new = false; + const char *description, *icon; + + if (cardWidgets.count(info.index)) + w = cardWidgets[info.index]; + else { + cardWidgets[info.index] = w = CardWidget::create(); + cardsVBox->pack_start(*w, false, false, 0); + w->index = info.index; + is_new = true; + } + + w->updating = true; + + description = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_DESCRIPTION); + w->name = description ? description : info.name; + w->nameLabel->set_markup(w->name.c_str()); + + icon = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_ICON_NAME); + set_icon_name_fallback(w->iconImage, icon ? icon : "audio-card", Gtk::ICON_SIZE_SMALL_TOOLBAR); + + w->hasSinks = w->hasSources = false; + w->profiles.clear(); + for (uint32_t i=0; i<info.n_profiles; ++i) { + w->hasSinks = w->hasSinks || (info.profiles[i].n_sinks > 0); + w->hasSources = w->hasSources || (info.profiles[i].n_sources > 0); + w->profiles.insert(std::pair<Glib::ustring,Glib::ustring>(info.profiles[i].name, info.profiles[i].description)); + } + w->activeProfile = info.active_profile->name; + + w->updating = false; + + w->prepareMenu(); + + if (is_new) + updateDeviceVisibility(); +} + +void MainWindow::updateSink(const pa_sink_info &info) { + SinkWidget *w; + bool is_new = false; + const char *icon; + + if (sinkWidgets.count(info.index)) + w = sinkWidgets[info.index]; + else { + sinkWidgets[info.index] = w = SinkWidget::create(); + w->beepDevice = info.name; + w->setChannelMap(info.channel_map, !!(info.flags & PA_SINK_DECIBEL_VOLUME)); + sinksVBox->pack_start(*w, false, false, 0); + w->index = info.index; + w->monitor_index = info.monitor_source; + is_new = true; + + w->setBaseVolume(info.base_volume); + w->setSteps(info.n_volume_steps); + } + + w->updating = true; + + w->card_index = info.card; + w->name = info.name; + w->description = info.description; + w->type = info.flags & PA_SINK_HARDWARE ? SINK_HARDWARE : SINK_VIRTUAL; + + w->boldNameLabel->set_text(""); + gchar *txt; + w->nameLabel->set_markup(txt = g_markup_printf_escaped("%s", info.description)); + g_free(txt); + + icon = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_ICON_NAME); + set_icon_name_fallback(w->iconImage, icon ? icon : "audio-card", Gtk::ICON_SIZE_SMALL_TOOLBAR); + + w->setVolume(info.volume); + w->muteToggleButton->set_active(info.mute); + + w->defaultMenuItem.set_active(w->name == defaultSinkName); + + w->updating = false; + + if (is_new) + updateDeviceVisibility(); +} + +static void suspended_callback(pa_stream *s, void *userdata) { + MainWindow *w = static_cast<MainWindow*>(userdata); + + if (pa_stream_is_suspended(s)) + w->updateVolumeMeter(pa_stream_get_device_index(s), PA_INVALID_INDEX, -1); +} + +static void read_callback(pa_stream *s, size_t length, void *userdata) { + MainWindow *w = static_cast<MainWindow*>(userdata); + const void *data; + double v; + + if (pa_stream_peek(s, &data, &length) < 0) { + show_error(_("Failed to read data from stream")); + return; + } + + assert(length > 0); + assert(length % sizeof(float) == 0); + + v = ((const float*) data)[length / sizeof(float) -1]; + + pa_stream_drop(s); + + if (v < 0) + v = 0; + if (v > 1) + v = 1; + + w->updateVolumeMeter(pa_stream_get_device_index(s), pa_stream_get_monitor_stream(s), v); +} + +void MainWindow::createMonitorStreamForSource(uint32_t source_idx) { + pa_stream *s; + char t[16]; + pa_buffer_attr attr; + pa_sample_spec ss; + return; + + ss.channels = 1; + ss.format = PA_SAMPLE_FLOAT32; + ss.rate = 25; + + memset(&attr, 0, sizeof(attr)); + attr.fragsize = sizeof(float); + attr.maxlength = (uint32_t) -1; + + snprintf(t, sizeof(t), "%u", source_idx); + + if (!(s = pa_stream_new(get_context(), _("Peak detect"), &ss, NULL))) { + show_error(_("Failed to create monitoring stream")); + return; + } + + pa_stream_set_read_callback(s, read_callback, this); + pa_stream_set_suspended_callback(s, suspended_callback, this); + + if (pa_stream_connect_record(s, t, &attr, (pa_stream_flags_t) (PA_STREAM_DONT_MOVE|PA_STREAM_PEAK_DETECT|PA_STREAM_ADJUST_LATENCY)) < 0) { + show_error(_("Failed to connect monitoring stream")); + pa_stream_unref(s); + return; + } +} + +void MainWindow::createMonitorStreamForSinkInput(uint32_t sink_input_idx, uint32_t sink_idx) { + pa_stream *s; + char t[16]; + pa_buffer_attr attr; + pa_sample_spec ss; + uint32_t monitor_source_idx; + return; + + ss.channels = 1; + ss.format = PA_SAMPLE_FLOAT32; + ss.rate = 25; + + if (!sinkWidgets.count(sink_idx)) + return; + + monitor_source_idx = sinkWidgets[sink_idx]->monitor_index; + + memset(&attr, 0, sizeof(attr)); + attr.fragsize = sizeof(float); + attr.maxlength = (uint32_t) -1; + + snprintf(t, sizeof(t), "%u", monitor_source_idx); + + if (!(s = pa_stream_new(get_context(), _("Peak detect"), &ss, NULL))) { + show_error(_("Failed to create monitoring stream")); + return; + } + + pa_stream_set_monitor_stream(s, sink_input_idx); + pa_stream_set_read_callback(s, read_callback, this); + pa_stream_set_suspended_callback(s, suspended_callback, this); + + if (pa_stream_connect_record(s, t, &attr, (pa_stream_flags_t) (PA_STREAM_DONT_MOVE|PA_STREAM_PEAK_DETECT|PA_STREAM_ADJUST_LATENCY)) < 0) { + show_error(_("Failed to connect monitoring stream")); + pa_stream_unref(s); + return; + } +} + +void MainWindow::updateSource(const pa_source_info &info) { + SourceWidget *w; + bool is_new = false; + const char *icon; + + if (sourceWidgets.count(info.index)) + w = sourceWidgets[info.index]; + else { + sourceWidgets[info.index] = w = SourceWidget::create(); + w->setChannelMap(info.channel_map, !!(info.flags & PA_SOURCE_DECIBEL_VOLUME)); + sourcesVBox->pack_start(*w, false, false, 0); + w->index = info.index; + is_new = true; + + w->setBaseVolume(info.base_volume); + w->setSteps(info.n_volume_steps); + + if (pa_context_get_server_protocol_version(get_context()) >= 13) + createMonitorStreamForSource(info.index); + } + + w->updating = true; + + w->card_index = info.card; + w->name = info.name; + w->description = info.description; + w->type = info.monitor_of_sink != PA_INVALID_INDEX ? SOURCE_MONITOR : (info.flags & PA_SOURCE_HARDWARE ? SOURCE_HARDWARE : SOURCE_VIRTUAL); + + w->boldNameLabel->set_text(""); + gchar *txt; + w->nameLabel->set_markup(txt = g_markup_printf_escaped("%s", info.description)); + g_free(txt); + + icon = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_ICON_NAME); + set_icon_name_fallback(w->iconImage, icon ? icon : "audio-input-microphone", Gtk::ICON_SIZE_SMALL_TOOLBAR); + + w->setVolume(info.volume); + w->muteToggleButton->set_active(info.mute); + + w->defaultMenuItem.set_active(w->name == defaultSourceName); + + w->updating = false; + + if (is_new) + updateDeviceVisibility(); +} + +void MainWindow::setIconFromProplist(Gtk::Image *icon, pa_proplist *l, const char *def) { + const char *t; + + if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME))) + goto finish; + + if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME))) + goto finish; + + if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME))) + goto finish; + + if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE))) { + + if (strcmp(t, "video") == 0 || + strcmp(t, "phone") == 0) + goto finish; + + if (strcmp(t, "music") == 0) { + t = "audio"; + goto finish; + } + + if (strcmp(t, "game") == 0) { + t = "applications-games"; + goto finish; + } + + if (strcmp(t, "event") == 0) { + t = "dialog-information"; + goto finish; + } + } + + t = def; + +finish: + + icon->set_from_icon_name(t, Gtk::ICON_SIZE_SMALL_TOOLBAR); +} + +void MainWindow::updateSinkInput(const pa_sink_input_info &info) { + SinkInputWidget *w; + bool is_new = false; + + if (sinkInputWidgets.count(info.index)) + w = sinkInputWidgets[info.index]; + else { + sinkInputWidgets[info.index] = w = SinkInputWidget::create(); + w->setChannelMap(info.channel_map, true); + streamsVBox->pack_start(*w, false, false, 0); + w->index = info.index; + w->clientIndex = info.client; + w->mainWindow = this; + is_new = true; + + if (pa_context_get_server_protocol_version(get_context()) >= 13) + createMonitorStreamForSinkInput(info.index, info.sink); + } + + w->updating = true; + + w->type = info.client != PA_INVALID_INDEX ? SINK_INPUT_CLIENT : SINK_INPUT_VIRTUAL; + + w->sinkIndex = info.sink; + + char *txt; + if (clientNames.count(info.client)) { + w->boldNameLabel->set_markup(txt = g_markup_printf_escaped("<b>%s</b>", clientNames[info.client])); + g_free(txt); + w->nameLabel->set_markup(txt = g_markup_printf_escaped(": %s", info.name)); + g_free(txt); + } else { + w->boldNameLabel->set_text(""); + w->nameLabel->set_label(info.name); + } + + setIconFromProplist(w->iconImage, info.proplist, "audio-card"); + + w->setVolume(info.volume); + w->muteToggleButton->set_active(info.mute); + + w->updating = false; + + if (is_new) + updateDeviceVisibility(); +} + +void MainWindow::updateSourceOutput(const pa_source_output_info &info) { + SourceOutputWidget *w; + const char *app; + bool is_new = false; + + if ((app = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID))) + if (strcmp(app, "org.PulseAudio.pavucontrol") == 0) + return; + + if (sourceOutputWidgets.count(info.index)) + w = sourceOutputWidgets[info.index]; + else { + sourceOutputWidgets[info.index] = w = SourceOutputWidget::create(); + recsVBox->pack_start(*w, false, false, 0); + w->index = info.index; + w->clientIndex = info.client; + w->mainWindow = this; + } + + w->updating = true; + + w->type = info.client != PA_INVALID_INDEX ? SOURCE_OUTPUT_CLIENT : SOURCE_OUTPUT_VIRTUAL; + + w->sourceIndex = info.source; + + char *txt; + if (clientNames.count(info.client)) { + w->boldNameLabel->set_markup(txt = g_markup_printf_escaped("<b>%s</b>", clientNames[info.client])); + g_free(txt); + w->nameLabel->set_markup(txt = g_markup_printf_escaped(": %s", info.name)); + g_free(txt); + } else { + w->boldNameLabel->set_text(""); + w->nameLabel->set_label(info.name); + } + + setIconFromProplist(w->iconImage, info.proplist, "audio-input-microphone"); + + w->updating = false; + + if (is_new) + updateDeviceVisibility(); +} + +void MainWindow::updateClient(const pa_client_info &info) { + + g_free(clientNames[info.index]); + clientNames[info.index] = g_strdup(info.name); + + for (std::map<uint32_t, SinkInputWidget*>::iterator i = sinkInputWidgets.begin(); i != sinkInputWidgets.end(); ++i) { + SinkInputWidget *w = i->second; + + if (!w) + continue; + + if (w->clientIndex == info.index) { + gchar *txt; + w->boldNameLabel->set_markup(txt = g_markup_printf_escaped("<b>%s</b>", info.name)); + g_free(txt); + } + } +} + +void MainWindow::updateServer(const pa_server_info &info) { + + defaultSourceName = info.default_source_name ? info.default_source_name : ""; + defaultSinkName = info.default_sink_name ? info.default_sink_name : ""; + + for (std::map<uint32_t, SinkWidget*>::iterator i = sinkWidgets.begin(); i != sinkWidgets.end(); ++i) { + SinkWidget *w = i->second; + + if (!w) + continue; + + w->updating = true; + w->defaultMenuItem.set_active(w->name == defaultSinkName); + w->updating = false; + } + + for (std::map<uint32_t, SourceWidget*>::iterator i = sourceWidgets.begin(); i != sourceWidgets.end(); ++i) { + SourceWidget *w = i->second; + + if (!w) + continue; + + w->updating = true; + w->defaultMenuItem.set_active(w->name == defaultSourceName); + w->updating = false; + } +} + +bool MainWindow::createEventRoleWidget() { + if (eventRoleWidget) + return FALSE; + + pa_channel_map cm = { + 1, { PA_CHANNEL_POSITION_MONO } + }; + + eventRoleWidget = RoleWidget::create(); + streamsVBox->pack_start(*eventRoleWidget, false, false, 0); + eventRoleWidget->role = "sink-input-by-media-role:event"; + eventRoleWidget->setChannelMap(cm, true); + + eventRoleWidget->boldNameLabel->set_text(""); + eventRoleWidget->nameLabel->set_label(_("System Sounds")); + + eventRoleWidget->iconImage->set_from_icon_name("multimedia-volume-control", Gtk::ICON_SIZE_SMALL_TOOLBAR); + + eventRoleWidget->device = ""; + + eventRoleWidget->updating = true; + + pa_cvolume volume; + volume.channels = 1; + volume.values[0] = PA_VOLUME_NORM; + + eventRoleWidget->setVolume(volume); + eventRoleWidget->muteToggleButton->set_active(false); + + eventRoleWidget->updating = false; + + return TRUE; +} + +void MainWindow::deleteEventRoleWidget() { + + if (eventRoleWidget) + delete eventRoleWidget; + + eventRoleWidget = NULL; +} + +void MainWindow::updateRole(const pa_ext_stream_restore_info &info) { + pa_cvolume volume; + bool is_new = false; + + if (strcmp(info.name, "sink-input-by-media-role:event") != 0) + return; + + is_new = createEventRoleWidget(); + + eventRoleWidget->updating = true; + + eventRoleWidget->device = info.device ? info.device : ""; + + volume.channels = 1; + volume.values[0] = pa_cvolume_max(&info.volume); + + eventRoleWidget->setVolume(volume); + eventRoleWidget->muteToggleButton->set_active(info.mute); + + eventRoleWidget->updating = false; + + if (is_new) + updateDeviceVisibility(); +} + +void MainWindow::updateVolumeMeter(uint32_t source_index, uint32_t sink_input_idx, double v) { + + if (sink_input_idx != PA_INVALID_INDEX) { + SinkInputWidget *w; + + if (sinkInputWidgets.count(sink_input_idx)) { + w = sinkInputWidgets[sink_input_idx]; + w->updatePeak(v); + } + + } else { + + for (std::map<uint32_t, SinkWidget*>::iterator i = sinkWidgets.begin(); i != sinkWidgets.end(); ++i) { + SinkWidget* w = i->second; + + if (w->monitor_index == source_index) + w->updatePeak(v); + } + + for (std::map<uint32_t, SourceWidget*>::iterator i = sourceWidgets.begin(); i != sourceWidgets.end(); ++i) { + SourceWidget* w = i->second; + + if (w->index == source_index) + w->updatePeak(v); + } + + for (std::map<uint32_t, SourceOutputWidget*>::iterator i = sourceOutputWidgets.begin(); i != sourceOutputWidgets.end(); ++i) { + SourceOutputWidget* w = i->second; + + if (w->sourceIndex == source_index) + w->updatePeak(v); + } + } +} + +static guint idle_source = 0; + +gboolean idle_cb(gpointer data) { + ((MainWindow*) data)->reallyUpdateDeviceVisibility(); + idle_source = 0; + return FALSE; +} + +void MainWindow::updateDeviceVisibility() { + + if (idle_source) + return; + + idle_source = g_idle_add(idle_cb, this); +} + +void MainWindow::reallyUpdateDeviceVisibility() { + bool is_empty = true; + + for (std::map<uint32_t, SinkInputWidget*>::iterator i = sinkInputWidgets.begin(); i != sinkInputWidgets.end(); ++i) { + SinkInputWidget* w = i->second; + + if (showSinkInputType == SINK_INPUT_ALL || w->type == showSinkInputType) { + w->show(); + is_empty = false; + } else + w->hide(); + } + + if (eventRoleWidget) + is_empty = false; + + if (is_empty) + noStreamsLabel->show(); + else + noStreamsLabel->hide(); + + is_empty = true; + + for (std::map<uint32_t, SourceOutputWidget*>::iterator i = sourceOutputWidgets.begin(); i != sourceOutputWidgets.end(); ++i) { + SourceOutputWidget* w = i->second; + + if (showSourceOutputType == SOURCE_OUTPUT_ALL || w->type == showSourceOutputType) { + w->show(); + is_empty = false; + } else + w->hide(); + } + + if (is_empty) + noRecsLabel->show(); + else + noRecsLabel->hide(); + + is_empty = true; + + for (std::map<uint32_t, SinkWidget*>::iterator i = sinkWidgets.begin(); i != sinkWidgets.end(); ++i) { + SinkWidget* w = i->second; + + if (showSinkType == SINK_ALL || w->type == showSinkType) { + w->show(); + is_empty = false; + } else + w->hide(); + } + + if (is_empty) + noSinksLabel->show(); + else + noSinksLabel->hide(); + + is_empty = true; + + for (std::map<uint32_t, CardWidget*>::iterator i = cardWidgets.begin(); i != cardWidgets.end(); ++i) { + CardWidget* w = i->second; + + w->show(); + is_empty = false; + } + + if (is_empty) + noCardsLabel->show(); + else + noCardsLabel->hide(); + + is_empty = true; + + for (std::map<uint32_t, SourceWidget*>::iterator i = sourceWidgets.begin(); i != sourceWidgets.end(); ++i) { + SourceWidget* w = i->second; + + if (showSourceType == SOURCE_ALL || + w->type == showSourceType || + (showSourceType == SOURCE_NO_MONITOR && w->type != SOURCE_MONITOR)) { + w->show(); + is_empty = false; + } else + w->hide(); + } + + if (is_empty) + noSourcesLabel->show(); + else + noSourcesLabel->hide(); + + /* Hmm, if I don't call hide()/show() here some widgets will never + * get their proper space allocated */ + sinksVBox->hide(); + sinksVBox->show(); + sourcesVBox->hide(); + sourcesVBox->show(); + streamsVBox->hide(); + streamsVBox->show(); + recsVBox->hide(); + recsVBox->show(); + cardsVBox->hide(); + cardsVBox->show(); +} + +void MainWindow::removeCard(uint32_t index) { + if (!cardWidgets.count(index)) + return; + + delete cardWidgets[index]; + cardWidgets.erase(index); + updateDeviceVisibility(); +} + +void MainWindow::removeSink(uint32_t index) { + if (!sinkWidgets.count(index)) + return; + + delete sinkWidgets[index]; + sinkWidgets.erase(index); + updateDeviceVisibility(); +} + +void MainWindow::removeSource(uint32_t index) { + if (!sourceWidgets.count(index)) + return; + + delete sourceWidgets[index]; + sourceWidgets.erase(index); + updateDeviceVisibility(); +} + +void MainWindow::removeSinkInput(uint32_t index) { + if (!sinkInputWidgets.count(index)) + return; + + delete sinkInputWidgets[index]; + sinkInputWidgets.erase(index); + updateDeviceVisibility(); +} + +void MainWindow::removeSourceOutput(uint32_t index) { + if (!sourceOutputWidgets.count(index)) + return; + + delete sourceOutputWidgets[index]; + sourceOutputWidgets.erase(index); + updateDeviceVisibility(); +} + +void MainWindow::removeClient(uint32_t index) { + g_free(clientNames[index]); + clientNames.erase(index); +} + +void MainWindow::onSinkTypeComboBoxChanged() { + showSinkType = (SinkType) sinkTypeComboBox->get_active_row_number(); + + if (showSinkType == (SinkType) -1) + sinkTypeComboBox->set_active((int) SINK_ALL); + + updateDeviceVisibility(); +} + +void MainWindow::onSourceTypeComboBoxChanged() { + showSourceType = (SourceType) sourceTypeComboBox->get_active_row_number(); + + if (showSourceType == (SourceType) -1) + sourceTypeComboBox->set_active((int) SOURCE_NO_MONITOR); + + updateDeviceVisibility(); +} + +void MainWindow::onSinkInputTypeComboBoxChanged() { + showSinkInputType = (SinkInputType) sinkInputTypeComboBox->get_active_row_number(); + + if (showSinkInputType == (SinkInputType) -1) + sinkInputTypeComboBox->set_active((int) SINK_INPUT_CLIENT); + + updateDeviceVisibility(); +} + +void MainWindow::onSourceOutputTypeComboBoxChanged() { + showSourceOutputType = (SourceOutputType) sourceOutputTypeComboBox->get_active_row_number(); + + if (showSourceOutputType == (SourceOutputType) -1) + sourceOutputTypeComboBox->set_active((int) SOURCE_OUTPUT_CLIENT); + + updateDeviceVisibility(); +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..a9f0f64 --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,99 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef mainwindow_h +#define mainwindow_h + +#include "pavucontrol.h" +#include <pulse/ext-stream-restore.h> + + +class CardWidget; +class SinkWidget; +class SourceWidget; +class SinkInputWidget; +class SourceOutputWidget; +class RoleWidget; + +class MainWindow : public Gtk::Window { +public: + MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + static MainWindow* create(); + virtual ~MainWindow(); + + void updateCard(const pa_card_info &info); + void updateSink(const pa_sink_info &info); + void updateSource(const pa_source_info &info); + void updateSinkInput(const pa_sink_input_info &info); + void updateSourceOutput(const pa_source_output_info &info); + void updateClient(const pa_client_info &info); + void updateServer(const pa_server_info &info); + void updateVolumeMeter(uint32_t source_index, uint32_t sink_input_index, double v); + void updateRole(const pa_ext_stream_restore_info &info); + + void removeCard(uint32_t index); + void removeSink(uint32_t index); + void removeSource(uint32_t index); + void removeSinkInput(uint32_t index); + void removeSourceOutput(uint32_t index); + void removeClient(uint32_t index); + + Gtk::Notebook *notebook; + Gtk::VBox *streamsVBox, *recsVBox, *sinksVBox, *sourcesVBox, *cardsVBox; + Gtk::Label *noStreamsLabel, *noRecsLabel, *noSinksLabel, *noSourcesLabel, *noCardsLabel; + Gtk::ComboBox *sinkInputTypeComboBox, *sourceOutputTypeComboBox, *sinkTypeComboBox, *sourceTypeComboBox; + + std::map<uint32_t, CardWidget*> cardWidgets; + std::map<uint32_t, SinkWidget*> sinkWidgets; + std::map<uint32_t, SourceWidget*> sourceWidgets; + std::map<uint32_t, SinkInputWidget*> sinkInputWidgets; + std::map<uint32_t, SourceOutputWidget*> sourceOutputWidgets; + std::map<uint32_t, char*> clientNames; + + SinkInputType showSinkInputType; + SinkType showSinkType; + SourceOutputType showSourceOutputType; + SourceType showSourceType; + + virtual void onSinkInputTypeComboBoxChanged(); + virtual void onSourceOutputTypeComboBoxChanged(); + virtual void onSinkTypeComboBoxChanged(); + virtual void onSourceTypeComboBoxChanged(); + + void updateDeviceVisibility(); + void reallyUpdateDeviceVisibility(); + void createMonitorStreamForSource(uint32_t source_idx); + void createMonitorStreamForSinkInput(uint32_t sink_input_idx, uint32_t sink_idx); + + void setIconFromProplist(Gtk::Image *icon, pa_proplist *l, const char *name); + + RoleWidget *eventRoleWidget; + + bool createEventRoleWidget(); + void deleteEventRoleWidget(); + + Glib::ustring defaultSinkName, defaultSourceName; + +protected: + virtual void on_realize(); +}; + + +#endif diff --git a/src/minimalstreamwidget.cc b/src/minimalstreamwidget.cc new file mode 100644 index 0000000..1044d7a --- /dev/null +++ b/src/minimalstreamwidget.cc @@ -0,0 +1,122 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "minimalstreamwidget.h" + +/*** MinimalStreamWidget ***/ +MinimalStreamWidget::MinimalStreamWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + Gtk::VBox(cobject), + peakProgressBar(), + lastPeak(0), + updating(false), + volumeMeterEnabled(false) { + + x->get_widget("channelsVBox", channelsVBox); + x->get_widget("nameLabel", nameLabel); + x->get_widget("boldNameLabel", boldNameLabel); + x->get_widget("streamToggle", streamToggleButton); + x->get_widget("iconImage", iconImage); + + peakProgressBar.set_size_request(-1, 10); + channelsVBox->pack_end(peakProgressBar, false, false); + + streamToggleButton->set_active(false); + streamToggleButton->signal_clicked().connect(sigc::mem_fun(*this, &MinimalStreamWidget::onStreamToggleButton)); + menu.signal_deactivate().connect(sigc::mem_fun(*this, &MinimalStreamWidget::onMenuDeactivated)); + + peakProgressBar.hide(); +} + +void MinimalStreamWidget::prepareMenu(void) { +} + +void MinimalStreamWidget::onMenuDeactivated(void) { + streamToggleButton->set_active(false); +} + +void MinimalStreamWidget::popupMenuPosition(int& x, int& y, bool& push_in G_GNUC_UNUSED) { + Gtk::Requisition r; + + streamToggleButton->get_window()->get_origin(x, y); + r = menu.size_request(); + + /* Align the right side of the menu with the right side of the togglebutton */ + x += streamToggleButton->get_allocation().get_x(); + x += streamToggleButton->get_allocation().get_width(); + x -= r.width; + + /* Align the top of the menu with the buttom of the togglebutton */ + y += streamToggleButton->get_allocation().get_y(); + y += streamToggleButton->get_allocation().get_height(); +} + +void MinimalStreamWidget::onStreamToggleButton(void) { + if (streamToggleButton->get_active()) { + prepareMenu(); + menu.popup(sigc::mem_fun(*this, &MinimalStreamWidget::popupMenuPosition), 0, gtk_get_current_event_time()); + } +} + +bool MinimalStreamWidget::on_button_press_event (GdkEventButton* event) { + if (Gtk::VBox::on_button_press_event(event)) + return TRUE; + + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + prepareMenu(); + menu.popup(0, event->time); + return TRUE; + } + + return FALSE; +} + +#define DECAY_STEP .04 + +void MinimalStreamWidget::updatePeak(double v) { + + if (lastPeak >= DECAY_STEP) + if (v < lastPeak - DECAY_STEP) + v = lastPeak - DECAY_STEP; + + lastPeak = v; + + if (v >= 0) { + peakProgressBar.set_sensitive(TRUE); + peakProgressBar.set_fraction(v); + } else { + peakProgressBar.set_sensitive(FALSE); + peakProgressBar.set_fraction(0); + } + + enableVolumeMeter(); +} + +void MinimalStreamWidget::enableVolumeMeter() { + if (volumeMeterEnabled) + return; + + volumeMeterEnabled = true; + peakProgressBar.show(); +} + diff --git a/src/minimalstreamwidget.h b/src/minimalstreamwidget.h new file mode 100644 index 0000000..af5c9b5 --- /dev/null +++ b/src/minimalstreamwidget.h @@ -0,0 +1,56 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef minimalstreamwidget_h +#define minimalstreamwidget_h + +#include "pavucontrol.h" + +class MinimalStreamWidget : public Gtk::VBox { +public: + MinimalStreamWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + + Gtk::VBox *channelsVBox; + Gtk::Label *nameLabel, *boldNameLabel; + Gtk::ToggleButton *streamToggleButton; + Gtk::Menu menu; + Gtk::Image *iconImage; + Gtk::ProgressBar peakProgressBar; + double lastPeak; + + bool updating; + + void onStreamToggleButton(); + void onMenuDeactivated(); + void popupMenuPosition(int& x, int& y, bool& push_in); + + virtual void prepareMenu(void); + + bool volumeMeterEnabled; + void enableVolumeMeter(); + void updatePeak(double v); + + Glib::ustring beepDevice; + +protected: + virtual bool on_button_press_event(GdkEventButton* event); +}; + +#endif diff --git a/src/pavucontrol.cc b/src/pavucontrol.cc index 7fef582..423c44b 100644 --- a/src/pavucontrol.cc +++ b/src/pavucontrol.cc @@ -22,366 +22,25 @@ #include <config.h> #endif -#include <signal.h> -#include <string.h> +#include "pavucontrol.h" -#include <gtkmm.h> -#include <libglademm.h> -#include <libintl.h> - -#include <canberra-gtk.h> - -#include <pulse/pulseaudio.h> #include <pulse/glib-mainloop.h> #include <pulse/ext-stream-restore.h> #include "i18n.h" - -#ifndef GLADE_FILE -#define GLADE_FILE "pavucontrol.glade" -#endif +#include "minimalstreamwidget.h" +#include "channelwidget.h" +#include "streamwidget.h" +#include "cardwidget.h" +#include "sinkwidget.h" +#include "sourcewidget.h" +#include "sinkinputwidget.h" +#include "sourceoutputwidget.h" +#include "rolewidget.h" +#include "mainwindow.h" static pa_context *context = NULL; static int n_outstanding = 0; -static bool show_decibel = true; - -enum SinkInputType { - SINK_INPUT_ALL, - SINK_INPUT_CLIENT, - SINK_INPUT_VIRTUAL -}; - -enum SinkType { - SINK_ALL, - SINK_HARDWARE, - SINK_VIRTUAL, -}; - -enum SourceOutputType { - SOURCE_OUTPUT_ALL, - SOURCE_OUTPUT_CLIENT, - SOURCE_OUTPUT_VIRTUAL -}; - -enum SourceType{ - SOURCE_ALL, - SOURCE_NO_MONITOR, - SOURCE_HARDWARE, - SOURCE_VIRTUAL, - SOURCE_MONITOR, -}; - -class StreamWidget; -class MainWindow; - -class ChannelWidget : public Gtk::EventBox { -public: - ChannelWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - static ChannelWidget* create(); - - void setVolume(pa_volume_t volume); - - Gtk::Label *channelLabel; - Gtk::Label *volumeLabel; - Gtk::HScale *volumeScale; - - int channel; - StreamWidget *streamWidget; - - void onVolumeScaleValueChanged(); - - bool can_decibel; - bool volumeScaleEnabled; - - Glib::ustring beepDevice; - - virtual void set_sensitive(bool enabled); -}; - -class MinimalStreamWidget : public Gtk::VBox { -public: - MinimalStreamWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - - Gtk::VBox *channelsVBox; - Gtk::Label *nameLabel, *boldNameLabel; - Gtk::ToggleButton *streamToggleButton; - Gtk::Menu menu; - Gtk::Image *iconImage; - Gtk::ProgressBar peakProgressBar; - double lastPeak; - - bool updating; - - void onStreamToggleButton(); - void onMenuDeactivated(); - void popupMenuPosition(int& x, int& y, bool& push_in); - - virtual void prepareMenu(void); - - bool volumeMeterEnabled; - void enableVolumeMeter(); - void updatePeak(double v); - - Glib::ustring beepDevice; - -protected: - virtual bool on_button_press_event(GdkEventButton* event); -}; - -class StreamWidget : public MinimalStreamWidget { -public: - StreamWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - - void setChannelMap(const pa_channel_map &m, bool can_decibel); - void setVolume(const pa_cvolume &volume, bool force); - virtual void updateChannelVolume(int channel, pa_volume_t v); - - Gtk::ToggleButton *lockToggleButton, *muteToggleButton; - - pa_channel_map channelMap; - pa_cvolume volume; - - ChannelWidget *channelWidgets[PA_CHANNELS_MAX]; - - virtual void onMuteToggleButton(); - - sigc::connection timeoutConnection; - - bool timeoutEvent(); - - virtual void executeVolumeUpdate(); -}; - -class CardWidget : public Gtk::VBox { -public: - CardWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - static CardWidget* create(); - - Gtk::Label *nameLabel; - Gtk::ToggleButton *streamToggleButton; - Gtk::Menu menu; - Gtk::Image *iconImage; - Glib::ustring name; - uint32_t index; - bool updating; - - std::map<Glib::ustring,Glib::ustring> profiles; - Glib::ustring activeProfile; - bool hasSinks; - bool hasSources; - - void prepareMenu(); - -protected: - virtual void onProfileChange(); - - //Tree model columns: - class ModelColumns : public Gtk::TreeModel::ColumnRecord - { - public: - - ModelColumns() - { add(name); add(desc); } - - Gtk::TreeModelColumn<Glib::ustring> name; - Gtk::TreeModelColumn<Glib::ustring> desc; - }; - - ModelColumns profileModel; - - //Child widgets: - Gtk::ComboBox *profileList; - Glib::RefPtr<Gtk::ListStore> treeModel; -}; - -class SinkWidget : public StreamWidget { -public: - SinkWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - static SinkWidget* create(); - - SinkType type; - Glib::ustring description; - Glib::ustring name; - uint32_t index, monitor_index, card_index; - bool can_decibel; - - Gtk::CheckMenuItem defaultMenuItem; - - virtual void onMuteToggleButton(); - virtual void executeVolumeUpdate(); - virtual void onDefaultToggle(); -}; - -class SourceWidget : public StreamWidget { -public: - SourceWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - static SourceWidget* create(); - - SourceType type; - Glib::ustring name; - Glib::ustring description; - uint32_t index, card_index; - bool can_decibel; - - Gtk::CheckMenuItem defaultMenuItem; - - virtual void onMuteToggleButton(); - virtual void executeVolumeUpdate(); - virtual void onDefaultToggle(); -}; - -class SinkInputWidget : public StreamWidget { -public: - SinkInputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - static SinkInputWidget* create(); - virtual ~SinkInputWidget(); - - SinkInputType type; - - uint32_t index, clientIndex, sinkIndex; - virtual void executeVolumeUpdate(); - virtual void onMuteToggleButton(); - virtual void onKill(); - virtual void prepareMenu(); - - MainWindow *mainWindow; - Gtk::Menu submenu; - Gtk::MenuItem titleMenuItem, killMenuItem; - - struct SinkMenuItem { - SinkMenuItem(SinkInputWidget *w, const char *label, uint32_t i, bool active) : - widget(w), - menuItem(label), - index(i) { - menuItem.set_active(active); - menuItem.set_draw_as_radio(true); - menuItem.signal_toggled().connect(sigc::mem_fun(*this, &SinkMenuItem::onToggle)); - } - - SinkInputWidget *widget; - Gtk::CheckMenuItem menuItem; - uint32_t index; - void onToggle(); - }; - - std::map<uint32_t, SinkMenuItem*> sinkMenuItems; - - void clearMenu(); - void buildMenu(); -}; - -class SourceOutputWidget : public MinimalStreamWidget { -public: - SourceOutputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - static SourceOutputWidget* create(); - virtual ~SourceOutputWidget(); - - SourceOutputType type; - - uint32_t index, clientIndex, sourceIndex; - virtual void onKill(); - - MainWindow *mainWindow; - Gtk::Menu submenu; - Gtk::MenuItem titleMenuItem, killMenuItem; - - struct SourceMenuItem { - SourceMenuItem(SourceOutputWidget *w, const char *label, uint32_t i, bool active) : - widget(w), - menuItem(label), - index(i) { - menuItem.set_active(active); - menuItem.set_draw_as_radio(true); - menuItem.signal_toggled().connect(sigc::mem_fun(*this, &SourceMenuItem::onToggle)); - } - - SourceOutputWidget *widget; - Gtk::CheckMenuItem menuItem; - uint32_t index; - void onToggle(); - }; - - std::map<uint32_t, SourceMenuItem*> sourceMenuItems; - - void clearMenu(); - void buildMenu(); - virtual void prepareMenu(); -}; - -class RoleWidget : public StreamWidget { -public: - RoleWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - static RoleWidget* create(); - - Glib::ustring role; - Glib::ustring device; - - virtual void onMuteToggleButton(); - virtual void executeVolumeUpdate(); -}; - -class MainWindow : public Gtk::Window { -public: - MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); - static MainWindow* create(); - virtual ~MainWindow(); - - void updateCard(const pa_card_info &info); - void updateSink(const pa_sink_info &info); - void updateSource(const pa_source_info &info); - void updateSinkInput(const pa_sink_input_info &info); - void updateSourceOutput(const pa_source_output_info &info); - void updateClient(const pa_client_info &info); - void updateServer(const pa_server_info &info); - void updateVolumeMeter(uint32_t source_index, uint32_t sink_input_index, double v); - void updateRole(const pa_ext_stream_restore_info &info); - - void removeCard(uint32_t index); - void removeSink(uint32_t index); - void removeSource(uint32_t index); - void removeSinkInput(uint32_t index); - void removeSourceOutput(uint32_t index); - void removeClient(uint32_t index); - - Gtk::Notebook *notebook; - Gtk::VBox *streamsVBox, *recsVBox, *sinksVBox, *sourcesVBox, *cardsVBox; - Gtk::Label *noStreamsLabel, *noRecsLabel, *noSinksLabel, *noSourcesLabel, *noCardsLabel; - Gtk::ComboBox *sinkInputTypeComboBox, *sourceOutputTypeComboBox, *sinkTypeComboBox, *sourceTypeComboBox; - - std::map<uint32_t, CardWidget*> cardWidgets; - std::map<uint32_t, SinkWidget*> sinkWidgets; - std::map<uint32_t, SourceWidget*> sourceWidgets; - std::map<uint32_t, SinkInputWidget*> sinkInputWidgets; - std::map<uint32_t, SourceOutputWidget*> sourceOutputWidgets; - std::map<uint32_t, char*> clientNames; - - SinkInputType showSinkInputType; - SinkType showSinkType; - SourceOutputType showSourceOutputType; - SourceType showSourceType; - - virtual void onSinkInputTypeComboBoxChanged(); - virtual void onSourceOutputTypeComboBoxChanged(); - virtual void onSinkTypeComboBoxChanged(); - virtual void onSourceTypeComboBoxChanged(); - - void updateDeviceVisibility(); - void reallyUpdateDeviceVisibility(); - void createMonitorStreamForSource(uint32_t source_idx); - void createMonitorStreamForSinkInput(uint32_t sink_input_idx, uint32_t sink_idx); - - void setIconFromProplist(Gtk::Image *icon, pa_proplist *l, const char *name); - - RoleWidget *eventRoleWidget; - - bool createEventRoleWidget(); - void deleteEventRoleWidget(); - - Glib::ustring defaultSinkName, defaultSourceName; - -protected: - virtual void on_realize(); -}; void show_error(const char *txt) { char buf[256]; @@ -394,1466 +53,6 @@ void show_error(const char *txt) { Gtk::Main::quit(); } -/*** ChannelWidget ***/ - -ChannelWidget::ChannelWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - Gtk::EventBox(cobject), - volumeScaleEnabled(true) { - - x->get_widget("channelLabel", channelLabel); - x->get_widget("volumeLabel", volumeLabel); - x->get_widget("volumeScale", volumeScale); - - volumeScale->set_value(100); - - volumeScale->signal_value_changed().connect(sigc::mem_fun(*this, &ChannelWidget::onVolumeScaleValueChanged)); -} - -ChannelWidget* ChannelWidget::create() { - ChannelWidget* w; - Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "channelWidget"); - x->get_widget_derived("channelWidget", w); - return w; -} - -void ChannelWidget::setVolume(pa_volume_t volume) { - double v; - char txt[64]; - - v = ((gdouble) volume * 100) / PA_VOLUME_NORM; - - if (can_decibel && show_decibel) { - double dB = pa_sw_volume_to_dB(volume); - - if (dB > PA_DECIBEL_MININFTY) { - snprintf(txt, sizeof(txt), "%0.2f dB", dB); - volumeLabel->set_tooltip_text(txt); - } else - volumeLabel->set_tooltip_markup("-∞dB"); - volumeLabel->set_has_tooltip(TRUE); - } else - volumeLabel->set_has_tooltip(FALSE); - - snprintf(txt, sizeof(txt), "%0.0f%%", v); - volumeLabel->set_text(txt); - - volumeScaleEnabled = false; - volumeScale->set_value(v > 100 ? 100 : v); - volumeScaleEnabled = true; -} - -void ChannelWidget::onVolumeScaleValueChanged() { - - if (!volumeScaleEnabled) - return; - - if (streamWidget->updating) - return; - - pa_volume_t volume = (pa_volume_t) ((volumeScale->get_value() * PA_VOLUME_NORM) / 100); - streamWidget->updateChannelVolume(channel, volume); - - if (beepDevice != "") { - ca_context_change_device(ca_gtk_context_get(), beepDevice.c_str()); - - ca_context_cancel(ca_gtk_context_get(), 2); - - ca_gtk_play_for_widget(GTK_WIDGET(volumeScale->gobj()), - 2, - CA_PROP_EVENT_DESCRIPTION, _("Volume Control Feedback Sound"), - CA_PROP_EVENT_ID, "audio-volume-change", - CA_PROP_CANBERRA_CACHE_CONTROL, "permanent", - CA_PROP_CANBERRA_VOLUME, "0", - CA_PROP_CANBERRA_ENABLE, "1", - NULL); - - ca_context_change_device(ca_gtk_context_get(), NULL); - } -} - -void ChannelWidget::set_sensitive(bool enabled) { - Gtk::EventBox::set_sensitive(enabled); - - channelLabel->set_sensitive(enabled); - volumeLabel->set_sensitive(enabled); - volumeScale->set_sensitive(enabled); -} - - - -/*** CardWidget ***/ -CardWidget::CardWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - Gtk::VBox(cobject) { - - x->get_widget("nameLabel", nameLabel); - x->get_widget("profileList", profileList); - x->get_widget("iconImage", iconImage); - - treeModel = Gtk::ListStore::create(profileModel); - profileList->set_model(treeModel); - profileList->pack_start(profileModel.desc); - - profileList->signal_changed().connect( sigc::mem_fun(*this, &CardWidget::onProfileChange)); -} - -CardWidget* CardWidget::create() { - CardWidget* w; - Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "cardWidget"); - x->get_widget_derived("cardWidget", w); - return w; -} - - -void CardWidget::prepareMenu() { - int idx = 0; - int active_idx = -1; - - treeModel->clear(); - //Fill the ComboBox's Tree Model: - for (std::map<Glib::ustring, Glib::ustring>::iterator i = profiles.begin(); i != profiles.end(); ++i) { - Gtk::TreeModel::Row row = *(treeModel->append()); - row[profileModel.name] = i->first; - row[profileModel.desc] = i->second; - if (i->first == activeProfile) - active_idx = idx; - idx++; - } - - if (active_idx >= 0) - profileList->set_active(active_idx); -} - -void CardWidget::onProfileChange() { - Gtk::TreeModel::iterator iter; - - if (updating) - return; - - iter = profileList->get_active(); - if (iter) - { - Gtk::TreeModel::Row row = *iter; - if (row) - { - pa_operation* o; - Glib::ustring profile = row[profileModel.name]; - - if (!(o = pa_context_set_card_profile_by_index(context, index, profile.c_str(), NULL, NULL))) { - show_error(_("pa_context_set_card_profile_by_index() failed")); - return; - } - - pa_operation_unref(o); - } - } -} - - -/*** MinimalStreamWidget ***/ -MinimalStreamWidget::MinimalStreamWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - Gtk::VBox(cobject), - peakProgressBar(), - lastPeak(0), - updating(false), - volumeMeterEnabled(false) { - - x->get_widget("channelsVBox", channelsVBox); - x->get_widget("nameLabel", nameLabel); - x->get_widget("boldNameLabel", boldNameLabel); - x->get_widget("streamToggle", streamToggleButton); - x->get_widget("iconImage", iconImage); - - peakProgressBar.set_size_request(-1, 10); - channelsVBox->pack_end(peakProgressBar, false, false); - - streamToggleButton->set_active(false); - streamToggleButton->signal_clicked().connect(sigc::mem_fun(*this, &MinimalStreamWidget::onStreamToggleButton)); - menu.signal_deactivate().connect(sigc::mem_fun(*this, &MinimalStreamWidget::onMenuDeactivated)); - - peakProgressBar.hide(); -} - -void MinimalStreamWidget::prepareMenu(void) { -} - -void MinimalStreamWidget::onMenuDeactivated(void) { - streamToggleButton->set_active(false); -} - -void MinimalStreamWidget::popupMenuPosition(int& x, int& y, bool& push_in G_GNUC_UNUSED) { - Gtk::Requisition r; - - streamToggleButton->get_window()->get_origin(x, y); - r = menu.size_request(); - - /* Align the right side of the menu with the right side of the togglebutton */ - x += streamToggleButton->get_allocation().get_x(); - x += streamToggleButton->get_allocation().get_width(); - x -= r.width; - - /* Align the top of the menu with the buttom of the togglebutton */ - y += streamToggleButton->get_allocation().get_y(); - y += streamToggleButton->get_allocation().get_height(); -} - -void MinimalStreamWidget::onStreamToggleButton(void) { - if (streamToggleButton->get_active()) { - prepareMenu(); - menu.popup(sigc::mem_fun(*this, &MinimalStreamWidget::popupMenuPosition), 0, gtk_get_current_event_time()); - } -} - -bool MinimalStreamWidget::on_button_press_event (GdkEventButton* event) { - if (Gtk::VBox::on_button_press_event(event)) - return TRUE; - - if (event->type == GDK_BUTTON_PRESS && event->button == 3) { - prepareMenu(); - menu.popup(0, event->time); - return TRUE; - } - - return FALSE; -} - -#define DECAY_STEP .04 - -void MinimalStreamWidget::updatePeak(double v) { - - if (lastPeak >= DECAY_STEP) - if (v < lastPeak - DECAY_STEP) - v = lastPeak - DECAY_STEP; - - lastPeak = v; - - if (v >= 0) { - peakProgressBar.set_sensitive(TRUE); - peakProgressBar.set_fraction(v); - } else { - peakProgressBar.set_sensitive(FALSE); - peakProgressBar.set_fraction(0); - } - - enableVolumeMeter(); -} - -void MinimalStreamWidget::enableVolumeMeter() { - if (volumeMeterEnabled) - return; - - volumeMeterEnabled = true; - peakProgressBar.show(); -} - -/*** StreamWidget ***/ - -StreamWidget::StreamWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - MinimalStreamWidget(cobject, x) { - - x->get_widget("lockToggleButton", lockToggleButton); - x->get_widget("muteToggleButton", muteToggleButton); - - muteToggleButton->signal_clicked().connect(sigc::mem_fun(*this, &StreamWidget::onMuteToggleButton)); - - for (unsigned i = 0; i < PA_CHANNELS_MAX; i++) - channelWidgets[i] = NULL; -} - -void StreamWidget::setChannelMap(const pa_channel_map &m, bool can_decibel) { - channelMap = m; - - for (int i = 0; i < m.channels; i++) { - ChannelWidget *cw = channelWidgets[i] = ChannelWidget::create(); - cw->beepDevice = beepDevice; - cw->channel = i; - cw->can_decibel = can_decibel; - cw->streamWidget = this; - char text[64]; - snprintf(text, sizeof(text), "<b>%s</b>", pa_channel_position_to_pretty_string(m.map[i])); - cw->channelLabel->set_markup(text); - channelsVBox->pack_start(*cw, false, false, 0); - } - - lockToggleButton->set_sensitive(m.channels > 1); -} - -void StreamWidget::setVolume(const pa_cvolume &v, bool force = false) { - g_assert(v.channels == channelMap.channels); - - volume = v; - - if (timeoutConnection.empty() || force) { /* do not update the volume when a volume change is still in flux */ - for (int i = 0; i < volume.channels; i++) - channelWidgets[i]->setVolume(volume.values[i]); - } -} - -void StreamWidget::updateChannelVolume(int channel, pa_volume_t v) { - pa_cvolume n; - g_assert(channel < volume.channels); - - n = volume; - if (lockToggleButton->get_active()) { - for (int i = 0; i < n.channels; i++) - n.values[i] = v; - } else - n.values[channel] = v; - - setVolume(n, true); - - if (timeoutConnection.empty()) - timeoutConnection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &StreamWidget::timeoutEvent), 100); -} - -void StreamWidget::onMuteToggleButton() { - - lockToggleButton->set_sensitive(!muteToggleButton->get_active()); - - for (int i = 0; i < channelMap.channels; i++) - channelWidgets[i]->set_sensitive(!muteToggleButton->get_active()); -} - -bool StreamWidget::timeoutEvent() { - executeVolumeUpdate(); - return false; -} - -void StreamWidget::executeVolumeUpdate() { -} - -SinkWidget::SinkWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - StreamWidget(cobject, x), - defaultMenuItem("_Default", true){ - - add_events(Gdk::BUTTON_PRESS_MASK); - - defaultMenuItem.set_active(false); - defaultMenuItem.signal_toggled().connect(sigc::mem_fun(*this, &SinkWidget::onDefaultToggle)); - menu.append(defaultMenuItem); - menu.show_all(); -} - -SinkWidget* SinkWidget::create() { - SinkWidget* w; - Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); - x->get_widget_derived("streamWidget", w); - return w; -} - -void SinkWidget::executeVolumeUpdate() { - pa_operation* o; - - if (!(o = pa_context_set_sink_volume_by_index(context, index, &volume, NULL, NULL))) { - show_error(_("pa_context_set_sink_volume_by_index() failed")); - return; - } - - pa_operation_unref(o); -} - -void SinkWidget::onMuteToggleButton() { - StreamWidget::onMuteToggleButton(); - - if (updating) - return; - - pa_operation* o; - if (!(o = pa_context_set_sink_mute_by_index(context, index, muteToggleButton->get_active(), NULL, NULL))) { - show_error(_("pa_context_set_sink_mute_by_index() failed")); - return; - } - - pa_operation_unref(o); -} - -void SinkWidget::onDefaultToggle() { - pa_operation* o; - - if (updating) - return; - - if (!(o = pa_context_set_default_sink(context, name.c_str(), NULL, NULL))) { - show_error(_("pa_context_set_default_sink() failed")); - return; - } - pa_operation_unref(o); -} - -SourceWidget::SourceWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - StreamWidget(cobject, x), - defaultMenuItem(_("_Default"), true){ - - add_events(Gdk::BUTTON_PRESS_MASK); - - defaultMenuItem.set_active(false); - defaultMenuItem.signal_toggled().connect(sigc::mem_fun(*this, &SourceWidget::onDefaultToggle)); - menu.append(defaultMenuItem); - menu.show_all(); -} - -SourceWidget* SourceWidget::create() { - SourceWidget* w; - Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); - x->get_widget_derived("streamWidget", w); - return w; -} - -void SourceWidget::executeVolumeUpdate() { - pa_operation* o; - - if (!(o = pa_context_set_source_volume_by_index(context, index, &volume, NULL, NULL))) { - show_error(_("pa_context_set_source_volume_by_index() failed")); - return; - } - - pa_operation_unref(o); -} - -void SourceWidget::onMuteToggleButton() { - StreamWidget::onMuteToggleButton(); - - if (updating) - return; - - pa_operation* o; - if (!(o = pa_context_set_source_mute_by_index(context, index, muteToggleButton->get_active(), NULL, NULL))) { - show_error(_("pa_context_set_source_mute_by_index() failed")); - return; - } - - pa_operation_unref(o); -} - -void SourceWidget::onDefaultToggle() { - pa_operation* o; - - if (updating) - return; - - if (!(o = pa_context_set_default_source(context, name.c_str(), NULL, NULL))) { - show_error(_("pa_context_set_default_source() failed")); - return; - } - pa_operation_unref(o); -} - -SinkInputWidget::SinkInputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - StreamWidget(cobject, x), - mainWindow(NULL), - titleMenuItem(_("_Move Stream..."), true), - killMenuItem(_("_Terminate Stream"), true) { - - add_events(Gdk::BUTTON_PRESS_MASK); - - menu.append(titleMenuItem); - titleMenuItem.set_submenu(submenu); - - menu.append(killMenuItem); - killMenuItem.signal_activate().connect(sigc::mem_fun(*this, &SinkInputWidget::onKill)); -} - -SinkInputWidget::~SinkInputWidget() { - clearMenu(); -} - -SinkInputWidget* SinkInputWidget::create() { - SinkInputWidget* w; - Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); - x->get_widget_derived("streamWidget", w); - return w; -} - -void SinkInputWidget::executeVolumeUpdate() { - pa_operation* o; - - if (!(o = pa_context_set_sink_input_volume(context, index, &volume, NULL, NULL))) { - show_error(_("pa_context_set_sink_input_volume() failed")); - return; - } - - pa_operation_unref(o); -} - -void SinkInputWidget::onMuteToggleButton() { - StreamWidget::onMuteToggleButton(); - - if (updating) - return; - - pa_operation* o; - if (!(o = pa_context_set_sink_input_mute(context, index, muteToggleButton->get_active(), NULL, NULL))) { - show_error(_("pa_context_set_sink_input_mute() failed")); - return; - } - - pa_operation_unref(o); -} - -void SinkInputWidget::prepareMenu() { - clearMenu(); - buildMenu(); -} - -void SinkInputWidget::clearMenu() { - - while (!sinkMenuItems.empty()) { - std::map<uint32_t, SinkMenuItem*>::iterator i = sinkMenuItems.begin(); - delete i->second; - sinkMenuItems.erase(i); - } -} - -void SinkInputWidget::buildMenu() { - for (std::map<uint32_t, SinkWidget*>::iterator i = mainWindow->sinkWidgets.begin(); i != mainWindow->sinkWidgets.end(); ++i) { - SinkMenuItem *m; - sinkMenuItems[i->second->index] = m = new SinkMenuItem(this, i->second->description.c_str(), i->second->index, i->second->index == sinkIndex); - submenu.append(m->menuItem); - } - - menu.show_all(); -} - -void SinkInputWidget::onKill() { - pa_operation* o; - if (!(o = pa_context_kill_sink_input(context, index, NULL, NULL))) { - show_error(_("pa_context_kill_sink_input() failed")); - return; - } - - pa_operation_unref(o); -} - -void SinkInputWidget::SinkMenuItem::onToggle() { - - if (widget->updating) - return; - - if (!menuItem.get_active()) - return; - - pa_operation* o; - if (!(o = pa_context_move_sink_input_by_index(context, widget->index, index, NULL, NULL))) { - show_error(_("pa_context_move_sink_input_by_index() failed")); - return; - } - - pa_operation_unref(o); -} - -SourceOutputWidget::SourceOutputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - MinimalStreamWidget(cobject, x), - mainWindow(NULL), - titleMenuItem(_("_Move Stream..."), true), - killMenuItem(_("_Terminate Stream"), true) { - - add_events(Gdk::BUTTON_PRESS_MASK); - - menu.append(titleMenuItem); - titleMenuItem.set_submenu(submenu); - - menu.append(killMenuItem); - killMenuItem.signal_activate().connect(sigc::mem_fun(*this, &SourceOutputWidget::onKill)); -} - -SourceOutputWidget::~SourceOutputWidget() { - clearMenu(); -} - -SourceOutputWidget* SourceOutputWidget::create() { - SourceOutputWidget* w; - Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); - x->get_widget_derived("streamWidget", w); - return w; -} - -void SourceOutputWidget::onKill() { - pa_operation* o; - if (!(o = pa_context_kill_source_output(context, index, NULL, NULL))) { - show_error(_("pa_context_kill_source_output() failed")); - return; - } - - pa_operation_unref(o); -} - -void SourceOutputWidget::clearMenu() { - - while (!sourceMenuItems.empty()) { - std::map<uint32_t, SourceMenuItem*>::iterator i = sourceMenuItems.begin(); - delete i->second; - sourceMenuItems.erase(i); - } -} - -void SourceOutputWidget::buildMenu() { - for (std::map<uint32_t, SourceWidget*>::iterator i = mainWindow->sourceWidgets.begin(); i != mainWindow->sourceWidgets.end(); ++i) { - SourceMenuItem *m; - sourceMenuItems[i->second->index] = m = new SourceMenuItem(this, i->second->description.c_str(), i->second->index, i->second->index == sourceIndex); - submenu.append(m->menuItem); - } - - menu.show_all(); -} - -void SourceOutputWidget::prepareMenu(void) { - clearMenu(); - buildMenu(); -} - -void SourceOutputWidget::SourceMenuItem::onToggle() { - - if (widget->updating) - return; - - if (!menuItem.get_active()) - return; - - pa_operation* o; - if (!(o = pa_context_move_source_output_by_index(context, widget->index, index, NULL, NULL))) { - show_error(_("pa_context_move_source_output_by_index() failed")); - return; - } - - pa_operation_unref(o); -} - -RoleWidget::RoleWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - StreamWidget(cobject, x) { - - lockToggleButton->hide(); - streamToggleButton->hide(); -} - -RoleWidget* RoleWidget::create() { - RoleWidget* w; - Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); - x->get_widget_derived("streamWidget", w); - return w; -} - -void RoleWidget::onMuteToggleButton() { - StreamWidget::onMuteToggleButton(); - - executeVolumeUpdate(); -} - -void RoleWidget::executeVolumeUpdate() { - pa_ext_stream_restore_info info; - - if (updating) - return; - - info.name = role.c_str(); - info.channel_map.channels = 1; - info.channel_map.map[0] = PA_CHANNEL_POSITION_MONO; - info.volume = volume; - info.device = device == "" ? NULL : device.c_str(); - info.mute = muteToggleButton->get_active(); - - pa_operation* o; - if (!(o = pa_ext_stream_restore_write(context, PA_UPDATE_REPLACE, &info, 1, TRUE, NULL, NULL))) { - show_error(_("pa_ext_stream_restore_write() failed")); - return; - } - - pa_operation_unref(o); -} - -/*** MainWindow ***/ - -MainWindow::MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : - Gtk::Window(cobject), - showSinkInputType(SINK_INPUT_CLIENT), - showSinkType(SINK_ALL), - showSourceOutputType(SOURCE_OUTPUT_CLIENT), - showSourceType(SOURCE_NO_MONITOR), - eventRoleWidget(NULL){ - - x->get_widget("cardsVBox", cardsVBox); - x->get_widget("streamsVBox", streamsVBox); - x->get_widget("recsVBox", recsVBox); - x->get_widget("sinksVBox", sinksVBox); - x->get_widget("sourcesVBox", sourcesVBox); - x->get_widget("noCardsLabel", noCardsLabel); - x->get_widget("noStreamsLabel", noStreamsLabel); - x->get_widget("noRecsLabel", noRecsLabel); - x->get_widget("noSinksLabel", noSinksLabel); - x->get_widget("noSourcesLabel", noSourcesLabel); - x->get_widget("sinkInputTypeComboBox", sinkInputTypeComboBox); - x->get_widget("sourceOutputTypeComboBox", sourceOutputTypeComboBox); - x->get_widget("sinkTypeComboBox", sinkTypeComboBox); - x->get_widget("sourceTypeComboBox", sourceTypeComboBox); - x->get_widget("notebook", notebook); - - cardsVBox->set_reallocate_redraws(true); - sourcesVBox->set_reallocate_redraws(true); - streamsVBox->set_reallocate_redraws(true); - recsVBox->set_reallocate_redraws(true); - sinksVBox->set_reallocate_redraws(true); - - sinkInputTypeComboBox->set_active((int) showSinkInputType); - sourceOutputTypeComboBox->set_active((int) showSourceOutputType); - sinkTypeComboBox->set_active((int) showSinkType); - sourceTypeComboBox->set_active((int) showSourceType); - - sinkInputTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSinkInputTypeComboBoxChanged)); - sourceOutputTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSourceOutputTypeComboBoxChanged)); - sinkTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSinkTypeComboBoxChanged)); - sourceTypeComboBox->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::onSourceTypeComboBoxChanged)); -} - -MainWindow* MainWindow::create() { - MainWindow* w; - Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "mainWindow"); - x->get_widget_derived("mainWindow", w); - return w; -} - -void MainWindow::on_realize() { - Gtk::Window::on_realize(); - - get_window()->set_cursor(Gdk::Cursor(Gdk::WATCH)); -} - -MainWindow::~MainWindow() { - while (!clientNames.empty()) { - std::map<uint32_t, char*>::iterator i = clientNames.begin(); - g_free(i->second); - clientNames.erase(i); - } -} - -static void set_icon_name_fallback(Gtk::Image *i, const char *name, Gtk::IconSize size) { - Glib::RefPtr<Gtk::IconTheme> theme; - Glib::RefPtr<Gdk::Pixbuf> pixbuf; - gint width = 24, height = 24; - - Gtk::IconSize::lookup(size, width, height); - theme = Gtk::IconTheme::get_default(); - pixbuf = theme->load_icon(name, width, Gtk::ICON_LOOKUP_GENERIC_FALLBACK); - - if (pixbuf) - i->set(pixbuf); - else - i->set(name); -} - -void MainWindow::updateCard(const pa_card_info &info) { - CardWidget *w; - bool is_new = false; - const char *description, *icon; - - if (cardWidgets.count(info.index)) - w = cardWidgets[info.index]; - else { - cardWidgets[info.index] = w = CardWidget::create(); - cardsVBox->pack_start(*w, false, false, 0); - w->index = info.index; - is_new = true; - } - - w->updating = true; - - description = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_DESCRIPTION); - w->name = description ? description : info.name; - w->nameLabel->set_markup(w->name.c_str()); - - icon = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_ICON_NAME); - set_icon_name_fallback(w->iconImage, icon ? icon : "audio-card", Gtk::ICON_SIZE_SMALL_TOOLBAR); - - w->hasSinks = w->hasSources = false; - w->profiles.clear(); - for (uint32_t i=0; i<info.n_profiles; ++i) { - w->hasSinks = w->hasSinks || (info.profiles[i].n_sinks > 0); - w->hasSources = w->hasSources || (info.profiles[i].n_sources > 0); - w->profiles.insert(std::pair<Glib::ustring,Glib::ustring>(info.profiles[i].name, info.profiles[i].description)); - } - w->activeProfile = info.active_profile->name; - //w->defaultMenuItem.set_active(w->name == defaultSinkName); - - w->updating = false; - - w->prepareMenu(); - - if (is_new) - updateDeviceVisibility(); -} - -void MainWindow::updateSink(const pa_sink_info &info) { - SinkWidget *w; - bool is_new = false; - const char *icon; - - if (sinkWidgets.count(info.index)) - w = sinkWidgets[info.index]; - else { - sinkWidgets[info.index] = w = SinkWidget::create(); - w->beepDevice = info.name; - w->setChannelMap(info.channel_map, !!(info.flags & PA_SINK_DECIBEL_VOLUME)); - sinksVBox->pack_start(*w, false, false, 0); - w->index = info.index; - w->monitor_index = info.monitor_source; - is_new = true; - } - - w->updating = true; - - w->card_index = info.card; - w->name = info.name; - w->description = info.description; - w->type = info.flags & PA_SINK_HARDWARE ? SINK_HARDWARE : SINK_VIRTUAL; - - w->boldNameLabel->set_text(""); - gchar *txt; - w->nameLabel->set_markup(txt = g_markup_printf_escaped("%s", info.description)); - g_free(txt); - - icon = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_ICON_NAME); - set_icon_name_fallback(w->iconImage, icon ? icon : "audio-card", Gtk::ICON_SIZE_SMALL_TOOLBAR); - - w->setVolume(info.volume); - w->muteToggleButton->set_active(info.mute); - - w->defaultMenuItem.set_active(w->name == defaultSinkName); - - w->updating = false; - - if (is_new) - updateDeviceVisibility(); -} - -static void suspended_callback(pa_stream *s, void *userdata) { - MainWindow *w = static_cast<MainWindow*>(userdata); - - if (pa_stream_is_suspended(s)) - w->updateVolumeMeter(pa_stream_get_device_index(s), PA_INVALID_INDEX, -1); -} - -static void read_callback(pa_stream *s, size_t length, void *userdata) { - MainWindow *w = static_cast<MainWindow*>(userdata); - const void *data; - double v; - - if (pa_stream_peek(s, &data, &length) < 0) { - show_error(_("Failed to read data from stream")); - return; - } - - assert(length > 0); - assert(length % sizeof(float) == 0); - - v = ((const float*) data)[length / sizeof(float) -1]; - - pa_stream_drop(s); - - if (v < 0) - v = 0; - if (v > 1) - v = 1; - - w->updateVolumeMeter(pa_stream_get_device_index(s), pa_stream_get_monitor_stream(s), v); -} - -void MainWindow::createMonitorStreamForSource(uint32_t source_idx) { - pa_stream *s; - char t[16]; - pa_buffer_attr attr; - pa_sample_spec ss; - return; - - ss.channels = 1; - ss.format = PA_SAMPLE_FLOAT32; - ss.rate = 25; - - memset(&attr, 0, sizeof(attr)); - attr.fragsize = sizeof(float); - attr.maxlength = (uint32_t) -1; - - snprintf(t, sizeof(t), "%u", source_idx); - - if (!(s = pa_stream_new(context, _("Peak detect"), &ss, NULL))) { - show_error(_("Failed to create monitoring stream")); - return; - } - - pa_stream_set_read_callback(s, read_callback, this); - pa_stream_set_suspended_callback(s, suspended_callback, this); - - if (pa_stream_connect_record(s, t, &attr, (pa_stream_flags_t) (PA_STREAM_DONT_MOVE|PA_STREAM_PEAK_DETECT|PA_STREAM_ADJUST_LATENCY)) < 0) { - show_error(_("Failed to connect monitoring stream")); - pa_stream_unref(s); - return; - } -} - -void MainWindow::createMonitorStreamForSinkInput(uint32_t sink_input_idx, uint32_t sink_idx) { - pa_stream *s; - char t[16]; - pa_buffer_attr attr; - pa_sample_spec ss; - uint32_t monitor_source_idx; - return; - - ss.channels = 1; - ss.format = PA_SAMPLE_FLOAT32; - ss.rate = 25; - - if (!sinkWidgets.count(sink_idx)) - return; - - monitor_source_idx = sinkWidgets[sink_idx]->monitor_index; - - memset(&attr, 0, sizeof(attr)); - attr.fragsize = sizeof(float); - attr.maxlength = (uint32_t) -1; - - snprintf(t, sizeof(t), "%u", monitor_source_idx); - - if (!(s = pa_stream_new(context, _("Peak detect"), &ss, NULL))) { - show_error(_("Failed to create monitoring stream")); - return; - } - - pa_stream_set_monitor_stream(s, sink_input_idx); - pa_stream_set_read_callback(s, read_callback, this); - pa_stream_set_suspended_callback(s, suspended_callback, this); - - if (pa_stream_connect_record(s, t, &attr, (pa_stream_flags_t) (PA_STREAM_DONT_MOVE|PA_STREAM_PEAK_DETECT|PA_STREAM_ADJUST_LATENCY)) < 0) { - show_error(_("Failed to connect monitoring stream")); - pa_stream_unref(s); - return; - } -} - -void MainWindow::updateSource(const pa_source_info &info) { - SourceWidget *w; - bool is_new = false; - const char *icon; - - if (sourceWidgets.count(info.index)) - w = sourceWidgets[info.index]; - else { - sourceWidgets[info.index] = w = SourceWidget::create(); - w->setChannelMap(info.channel_map, !!(info.flags & PA_SOURCE_DECIBEL_VOLUME)); - sourcesVBox->pack_start(*w, false, false, 0); - w->index = info.index; - is_new = true; - - if (pa_context_get_server_protocol_version(context) >= 13) - createMonitorStreamForSource(info.index); - } - - w->updating = true; - - w->card_index = info.card; - w->name = info.name; - w->description = info.description; - w->type = info.monitor_of_sink != PA_INVALID_INDEX ? SOURCE_MONITOR : (info.flags & PA_SOURCE_HARDWARE ? SOURCE_HARDWARE : SOURCE_VIRTUAL); - - w->boldNameLabel->set_text(""); - gchar *txt; - w->nameLabel->set_markup(txt = g_markup_printf_escaped("%s", info.description)); - g_free(txt); - - icon = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_ICON_NAME); - set_icon_name_fallback(w->iconImage, icon ? icon : "audio-input-microphone", Gtk::ICON_SIZE_SMALL_TOOLBAR); - - w->setVolume(info.volume); - w->muteToggleButton->set_active(info.mute); - - w->defaultMenuItem.set_active(w->name == defaultSourceName); - - w->updating = false; - - if (is_new) - updateDeviceVisibility(); -} - -void MainWindow::setIconFromProplist(Gtk::Image *icon, pa_proplist *l, const char *def) { - const char *t; - - if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME))) - goto finish; - - if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME))) - goto finish; - - if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME))) - goto finish; - - if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE))) { - - if (strcmp(t, "video") == 0 || - strcmp(t, "phone") == 0) - goto finish; - - if (strcmp(t, "music") == 0) { - t = "audio"; - goto finish; - } - - if (strcmp(t, "game") == 0) { - t = "applications-games"; - goto finish; - } - - if (strcmp(t, "event") == 0) { - t = "dialog-information"; - goto finish; - } - } - - t = def; - -finish: - - icon->set_from_icon_name(t, Gtk::ICON_SIZE_SMALL_TOOLBAR); -} - -void MainWindow::updateSinkInput(const pa_sink_input_info &info) { - SinkInputWidget *w; - bool is_new = false; - - if (sinkInputWidgets.count(info.index)) - w = sinkInputWidgets[info.index]; - else { - sinkInputWidgets[info.index] = w = SinkInputWidget::create(); - w->setChannelMap(info.channel_map, true); - streamsVBox->pack_start(*w, false, false, 0); - w->index = info.index; - w->clientIndex = info.client; - w->mainWindow = this; - is_new = true; - - if (pa_context_get_server_protocol_version(context) >= 13) - createMonitorStreamForSinkInput(info.index, info.sink); - } - - w->updating = true; - - w->type = info.client != PA_INVALID_INDEX ? SINK_INPUT_CLIENT : SINK_INPUT_VIRTUAL; - - w->sinkIndex = info.sink; - - char *txt; - if (clientNames.count(info.client)) { - w->boldNameLabel->set_markup(txt = g_markup_printf_escaped("<b>%s</b>", clientNames[info.client])); - g_free(txt); - w->nameLabel->set_markup(txt = g_markup_printf_escaped(": %s", info.name)); - g_free(txt); - } else { - w->boldNameLabel->set_text(""); - w->nameLabel->set_label(info.name); - } - - setIconFromProplist(w->iconImage, info.proplist, "audio-card"); - - w->setVolume(info.volume); - w->muteToggleButton->set_active(info.mute); - - w->updating = false; - - if (is_new) - updateDeviceVisibility(); -} - -void MainWindow::updateSourceOutput(const pa_source_output_info &info) { - SourceOutputWidget *w; - const char *app; - bool is_new = false; - - if ((app = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID))) - if (strcmp(app, "org.PulseAudio.pavucontrol") == 0) - return; - - if (sourceOutputWidgets.count(info.index)) - w = sourceOutputWidgets[info.index]; - else { - sourceOutputWidgets[info.index] = w = SourceOutputWidget::create(); - recsVBox->pack_start(*w, false, false, 0); - w->index = info.index; - w->clientIndex = info.client; - w->mainWindow = this; - } - - w->updating = true; - - w->type = info.client != PA_INVALID_INDEX ? SOURCE_OUTPUT_CLIENT : SOURCE_OUTPUT_VIRTUAL; - - w->sourceIndex = info.source; - - char *txt; - if (clientNames.count(info.client)) { - w->boldNameLabel->set_markup(txt = g_markup_printf_escaped("<b>%s</b>", clientNames[info.client])); - g_free(txt); - w->nameLabel->set_markup(txt = g_markup_printf_escaped(": %s", info.name)); - g_free(txt); - } else { - w->boldNameLabel->set_text(""); - w->nameLabel->set_label(info.name); - } - - setIconFromProplist(w->iconImage, info.proplist, "audio-input-microphone"); - - w->updating = false; - - if (is_new) - updateDeviceVisibility(); -} - -void MainWindow::updateClient(const pa_client_info &info) { - - g_free(clientNames[info.index]); - clientNames[info.index] = g_strdup(info.name); - - for (std::map<uint32_t, SinkInputWidget*>::iterator i = sinkInputWidgets.begin(); i != sinkInputWidgets.end(); ++i) { - SinkInputWidget *w = i->second; - - if (!w) - continue; - - if (w->clientIndex == info.index) { - gchar *txt; - w->boldNameLabel->set_markup(txt = g_markup_printf_escaped("<b>%s</b>", info.name)); - g_free(txt); - } - } -} - -void MainWindow::updateServer(const pa_server_info &info) { - - defaultSourceName = info.default_source_name ? info.default_source_name : ""; - defaultSinkName = info.default_sink_name ? info.default_sink_name : ""; - - for (std::map<uint32_t, SinkWidget*>::iterator i = sinkWidgets.begin(); i != sinkWidgets.end(); ++i) { - SinkWidget *w = i->second; - - if (!w) - continue; - - w->updating = true; - w->defaultMenuItem.set_active(w->name == defaultSinkName); - w->updating = false; - } - - for (std::map<uint32_t, SourceWidget*>::iterator i = sourceWidgets.begin(); i != sourceWidgets.end(); ++i) { - SourceWidget *w = i->second; - - if (!w) - continue; - - w->updating = true; - w->defaultMenuItem.set_active(w->name == defaultSourceName); - w->updating = false; - } -} - -bool MainWindow::createEventRoleWidget() { - if (eventRoleWidget) - return FALSE; - - pa_channel_map cm = { - 1, { PA_CHANNEL_POSITION_MONO } - }; - - eventRoleWidget = RoleWidget::create(); - streamsVBox->pack_start(*eventRoleWidget, false, false, 0); - eventRoleWidget->role = "sink-input-by-media-role:event"; - eventRoleWidget->setChannelMap(cm, true); - - eventRoleWidget->boldNameLabel->set_text(""); - eventRoleWidget->nameLabel->set_label(_("System Sounds")); - - eventRoleWidget->iconImage->set_from_icon_name("multimedia-volume-control", Gtk::ICON_SIZE_SMALL_TOOLBAR); - - eventRoleWidget->device = ""; - - eventRoleWidget->updating = true; - - pa_cvolume volume; - volume.channels = 1; - volume.values[0] = PA_VOLUME_NORM; - - eventRoleWidget->setVolume(volume); - eventRoleWidget->muteToggleButton->set_active(false); - - eventRoleWidget->updating = false; - - return TRUE; -} - -void MainWindow::deleteEventRoleWidget() { - - if (eventRoleWidget) - delete eventRoleWidget; - - eventRoleWidget = NULL; -} - -void MainWindow::updateRole(const pa_ext_stream_restore_info &info) { - pa_cvolume volume; - bool is_new = false; - - if (strcmp(info.name, "sink-input-by-media-role:event") != 0) - return; - - is_new = createEventRoleWidget(); - - eventRoleWidget->updating = true; - - eventRoleWidget->device = info.device ? info.device : ""; - - volume.channels = 1; - volume.values[0] = pa_cvolume_max(&info.volume); - - eventRoleWidget->setVolume(volume); - eventRoleWidget->muteToggleButton->set_active(info.mute); - - eventRoleWidget->updating = false; - - if (is_new) - updateDeviceVisibility(); -} - -void MainWindow::updateVolumeMeter(uint32_t source_index, uint32_t sink_input_idx, double v) { - - if (sink_input_idx != PA_INVALID_INDEX) { - SinkInputWidget *w; - - if (sinkInputWidgets.count(sink_input_idx)) { - w = sinkInputWidgets[sink_input_idx]; - w->updatePeak(v); - } - - } else { - - for (std::map<uint32_t, SinkWidget*>::iterator i = sinkWidgets.begin(); i != sinkWidgets.end(); ++i) { - SinkWidget* w = i->second; - - if (w->monitor_index == source_index) - w->updatePeak(v); - } - - for (std::map<uint32_t, SourceWidget*>::iterator i = sourceWidgets.begin(); i != sourceWidgets.end(); ++i) { - SourceWidget* w = i->second; - - if (w->index == source_index) - w->updatePeak(v); - } - - for (std::map<uint32_t, SourceOutputWidget*>::iterator i = sourceOutputWidgets.begin(); i != sourceOutputWidgets.end(); ++i) { - SourceOutputWidget* w = i->second; - - if (w->sourceIndex == source_index) - w->updatePeak(v); - } - } -} - -static guint idle_source = 0; - -gboolean idle_cb(gpointer data) { - ((MainWindow*) data)->reallyUpdateDeviceVisibility(); - idle_source = 0; - return FALSE; -} - -void MainWindow::updateDeviceVisibility() { - - if (idle_source) - return; - - idle_source = g_idle_add(idle_cb, this); -} - -void MainWindow::reallyUpdateDeviceVisibility() { - bool is_empty = true; - - for (std::map<uint32_t, SinkInputWidget*>::iterator i = sinkInputWidgets.begin(); i != sinkInputWidgets.end(); ++i) { - SinkInputWidget* w = i->second; - - if (showSinkInputType == SINK_INPUT_ALL || w->type == showSinkInputType) { - w->show(); - is_empty = false; - } else - w->hide(); - } - - if (eventRoleWidget) - is_empty = false; - - if (is_empty) - noStreamsLabel->show(); - else - noStreamsLabel->hide(); - - is_empty = true; - - for (std::map<uint32_t, SourceOutputWidget*>::iterator i = sourceOutputWidgets.begin(); i != sourceOutputWidgets.end(); ++i) { - SourceOutputWidget* w = i->second; - - if (showSourceOutputType == SOURCE_OUTPUT_ALL || w->type == showSourceOutputType) { - w->show(); - is_empty = false; - } else - w->hide(); - } - - if (is_empty) - noRecsLabel->show(); - else - noRecsLabel->hide(); - - is_empty = true; - - for (std::map<uint32_t, SinkWidget*>::iterator i = sinkWidgets.begin(); i != sinkWidgets.end(); ++i) { - SinkWidget* w = i->second; - - if (showSinkType == SINK_ALL || w->type == showSinkType) { - w->show(); - is_empty = false; - } else - w->hide(); - } - - if (is_empty) - noSinksLabel->show(); - else - noSinksLabel->hide(); - - is_empty = true; - - for (std::map<uint32_t, CardWidget*>::iterator i = cardWidgets.begin(); i != cardWidgets.end(); ++i) { - CardWidget* w = i->second; - - w->show(); - is_empty = false; - } - - if (is_empty) - noCardsLabel->show(); - else - noCardsLabel->hide(); - - is_empty = true; - - for (std::map<uint32_t, SourceWidget*>::iterator i = sourceWidgets.begin(); i != sourceWidgets.end(); ++i) { - SourceWidget* w = i->second; - - if (showSourceType == SOURCE_ALL || - w->type == showSourceType || - (showSourceType == SOURCE_NO_MONITOR && w->type != SOURCE_MONITOR)) { - w->show(); - is_empty = false; - } else - w->hide(); - } - - if (is_empty) - noSourcesLabel->show(); - else - noSourcesLabel->hide(); - - /* Hmm, if I don't call hide()/show() here some widgets will never - * get their proper space allocated */ - sinksVBox->hide(); - sinksVBox->show(); - sourcesVBox->hide(); - sourcesVBox->show(); - streamsVBox->hide(); - streamsVBox->show(); - recsVBox->hide(); - recsVBox->show(); - cardsVBox->hide(); - cardsVBox->show(); -} - -void MainWindow::removeCard(uint32_t index) { - if (!cardWidgets.count(index)) - return; - - delete cardWidgets[index]; - cardWidgets.erase(index); - updateDeviceVisibility(); -} - -void MainWindow::removeSink(uint32_t index) { - if (!sinkWidgets.count(index)) - return; - - delete sinkWidgets[index]; - sinkWidgets.erase(index); - updateDeviceVisibility(); -} - -void MainWindow::removeSource(uint32_t index) { - if (!sourceWidgets.count(index)) - return; - - delete sourceWidgets[index]; - sourceWidgets.erase(index); - updateDeviceVisibility(); -} - -void MainWindow::removeSinkInput(uint32_t index) { - if (!sinkInputWidgets.count(index)) - return; - - delete sinkInputWidgets[index]; - sinkInputWidgets.erase(index); - updateDeviceVisibility(); -} - -void MainWindow::removeSourceOutput(uint32_t index) { - if (!sourceOutputWidgets.count(index)) - return; - - delete sourceOutputWidgets[index]; - sourceOutputWidgets.erase(index); - updateDeviceVisibility(); -} - -void MainWindow::removeClient(uint32_t index) { - g_free(clientNames[index]); - clientNames.erase(index); -} - -void MainWindow::onSinkTypeComboBoxChanged() { - showSinkType = (SinkType) sinkTypeComboBox->get_active_row_number(); - - if (showSinkType == (SinkType) -1) - sinkTypeComboBox->set_active((int) SINK_ALL); - - updateDeviceVisibility(); -} - -void MainWindow::onSourceTypeComboBoxChanged() { - showSourceType = (SourceType) sourceTypeComboBox->get_active_row_number(); - - if (showSourceType == (SourceType) -1) - sourceTypeComboBox->set_active((int) SOURCE_NO_MONITOR); - - updateDeviceVisibility(); -} - -void MainWindow::onSinkInputTypeComboBoxChanged() { - showSinkInputType = (SinkInputType) sinkInputTypeComboBox->get_active_row_number(); - - if (showSinkInputType == (SinkInputType) -1) - sinkInputTypeComboBox->set_active((int) SINK_INPUT_CLIENT); - - updateDeviceVisibility(); -} - -void MainWindow::onSourceOutputTypeComboBoxChanged() { - showSourceOutputType = (SourceOutputType) sourceOutputTypeComboBox->get_active_row_number(); - - if (showSourceOutputType == (SourceOutputType) -1) - sourceOutputTypeComboBox->set_active((int) SOURCE_OUTPUT_CLIENT); - - updateDeviceVisibility(); -} - static void dec_outstanding(MainWindow *w) { if (n_outstanding <= 0) return; @@ -2012,7 +211,8 @@ void ext_stream_restore_read_cb( MainWindow *w = static_cast<MainWindow*>(userdata); if (eol < 0) { - g_debug(_("Failed to initialized stream_restore extension: %s"), pa_strerror(pa_context_errno(context))); + dec_outstanding(w); + g_debug(_("Failed to initialize stream_restore extension: %s"), pa_strerror(pa_context_errno(context))); w->deleteEventRoleWidget(); return; } @@ -2164,7 +364,7 @@ void context_state_callback(pa_context *c, void *userdata) { } pa_operation_unref(o); - // Keep track of the outstanding callbacks for UI tweaks + /* Keep track of the outstanding callbacks for UI tweaks */ n_outstanding = 0; if (!(o = pa_context_get_server_info(c, server_info_cb, w))) { @@ -2227,7 +427,8 @@ void context_state_callback(pa_context *c, void *userdata) { pa_operation_unref(o); } else - g_debug(_("Failed to initialized stream_restore extension: %s"), pa_strerror(pa_context_errno(context))); + g_debug(_("Failed to initialize stream_restore extension: %s"), pa_strerror(pa_context_errno(context))); + break; } @@ -2243,6 +444,10 @@ void context_state_callback(pa_context *c, void *userdata) { } } +pa_context* get_context(void) { + return context; +} + int main(int argc, char *argv[]) { /* Initialize the i18n stuff */ diff --git a/src/pavucontrol.glade b/src/pavucontrol.glade index 5e09f8a..ff4f00e 100644 --- a/src/pavucontrol.glade +++ b/src/pavucontrol.glade @@ -703,113 +703,6 @@ Monitors</property> </widget> </child> </widget> - <widget class="GtkWindow" id="minimalStreamWindow"> - <property name="visible">True</property> - <property name="title" translatable="yes">window1</property> - <child> - <widget class="GtkEventBox" id="minimalStreamWidget"> - <property name="visible">True</property> - <child> - <widget class="GtkVBox" id="streamWidget7"> - <property name="visible">True</property> - <child> - <widget class="GtkVBox" id="vbox7"> - <property name="visible">True</property> - <property name="border_width">12</property> - <property name="spacing">6</property> - <child> - <widget class="GtkHBox" id="hbox9"> - <property name="visible">True</property> - <property name="spacing">6</property> - <child> - <widget class="GtkImage" id="iconImage"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="stock">gtk-missing-image</property> - <property name="icon-size">4</property> - </widget> - <packing> - <property name="expand">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <widget class="GtkHBox" id="hbox11"> - <property name="visible">True</property> - <child> - <widget class="GtkLabel" id="boldNameLabel"> - <property name="visible">True</property> - <property name="use_markup">True</property> - </widget> - <packing> - <property name="expand">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="nameLabel"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">Stream Title</property> - <property name="use_markup">True</property> - <property name="ellipsize">middle</property> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> - <child> - <widget class="GtkToggleButton" id="streamToggle"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="relief">none</property> - <child> - <widget class="GtkArrow" id="arrow1"> - <property name="visible">True</property> - <property name="arrow_type">down</property> - </widget> - </child> - </widget> - <packing> - <property name="expand">False</property> - <property name="position">2</property> - </packing> - </child> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <widget class="GtkHSeparator" id="hseparator5"> - <property name="visible">True</property> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </widget> - </child> - </widget> - </child> - </widget> <widget class="GtkWindow" id="cardWindow"> <property name="visible">True</property> <property name="title" translatable="yes">window1</property> diff --git a/src/pavucontrol.h b/src/pavucontrol.h new file mode 100644 index 0000000..0e0f6bd --- /dev/null +++ b/src/pavucontrol.h @@ -0,0 +1,67 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef pavucontrol_h +#define pavucontrol_h + +#include <signal.h> +#include <string.h> + +#include <libintl.h> + +#include <gtkmm.h> +#include <libglademm.h> + +#include <pulse/pulseaudio.h> + +#ifndef GLADE_FILE +#define GLADE_FILE "pavucontrol.glade" +#endif + +enum SinkInputType { + SINK_INPUT_ALL, + SINK_INPUT_CLIENT, + SINK_INPUT_VIRTUAL +}; + +enum SinkType { + SINK_ALL, + SINK_HARDWARE, + SINK_VIRTUAL, +}; + +enum SourceOutputType { + SOURCE_OUTPUT_ALL, + SOURCE_OUTPUT_CLIENT, + SOURCE_OUTPUT_VIRTUAL +}; + +enum SourceType { + SOURCE_ALL, + SOURCE_NO_MONITOR, + SOURCE_HARDWARE, + SOURCE_VIRTUAL, + SOURCE_MONITOR, +}; + +pa_context* get_context(void); +void show_error(const char *txt); + +#endif diff --git a/src/rolewidget.cc b/src/rolewidget.cc new file mode 100644 index 0000000..e86ba3d --- /dev/null +++ b/src/rolewidget.cc @@ -0,0 +1,72 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "rolewidget.h" + +#include <pulse/ext-stream-restore.h> + +#include "i18n.h" + +RoleWidget::RoleWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + StreamWidget(cobject, x) { + + lockToggleButton->hide(); + streamToggleButton->hide(); +} + +RoleWidget* RoleWidget::create() { + RoleWidget* w; + Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); + x->get_widget_derived("streamWidget", w); + return w; +} + +void RoleWidget::onMuteToggleButton() { + StreamWidget::onMuteToggleButton(); + + executeVolumeUpdate(); +} + +void RoleWidget::executeVolumeUpdate() { + pa_ext_stream_restore_info info; + + if (updating) + return; + + info.name = role.c_str(); + info.channel_map.channels = 1; + info.channel_map.map[0] = PA_CHANNEL_POSITION_MONO; + info.volume = volume; + info.device = device == "" ? NULL : device.c_str(); + info.mute = muteToggleButton->get_active(); + + pa_operation* o; + if (!(o = pa_ext_stream_restore_write(get_context(), PA_UPDATE_REPLACE, &info, 1, TRUE, NULL, NULL))) { + show_error(_("pa_ext_stream_restore_write() failed")); + return; + } + + pa_operation_unref(o); +} + diff --git a/src/rolewidget.h b/src/rolewidget.h new file mode 100644 index 0000000..bbd39d6 --- /dev/null +++ b/src/rolewidget.h @@ -0,0 +1,40 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef rolewidget_h +#define rolewidget_h + +#include "pavucontrol.h" + +#include "streamwidget.h" + +class RoleWidget : public StreamWidget { +public: + RoleWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + static RoleWidget* create(); + + Glib::ustring role; + Glib::ustring device; + + virtual void onMuteToggleButton(); + virtual void executeVolumeUpdate(); +}; + +#endif diff --git a/src/sinkinputwidget.cc b/src/sinkinputwidget.cc new file mode 100644 index 0000000..e5443f1 --- /dev/null +++ b/src/sinkinputwidget.cc @@ -0,0 +1,132 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "sinkinputwidget.h" +#include "mainwindow.h" +#include "sinkwidget.h" + +#include "i18n.h" + +SinkInputWidget::SinkInputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + StreamWidget(cobject, x), + mainWindow(NULL), + titleMenuItem(_("_Move Stream..."), true), + killMenuItem(_("_Terminate Stream"), true) { + + add_events(Gdk::BUTTON_PRESS_MASK); + + menu.append(titleMenuItem); + titleMenuItem.set_submenu(submenu); + + menu.append(killMenuItem); + killMenuItem.signal_activate().connect(sigc::mem_fun(*this, &SinkInputWidget::onKill)); +} + +SinkInputWidget::~SinkInputWidget() { + clearMenu(); +} + +SinkInputWidget* SinkInputWidget::create() { + SinkInputWidget* w; + Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); + x->get_widget_derived("streamWidget", w); + return w; +} + +void SinkInputWidget::executeVolumeUpdate() { + pa_operation* o; + + if (!(o = pa_context_set_sink_input_volume(get_context(), index, &volume, NULL, NULL))) { + show_error(_("pa_context_set_sink_input_volume() failed")); + return; + } + + pa_operation_unref(o); +} + +void SinkInputWidget::onMuteToggleButton() { + StreamWidget::onMuteToggleButton(); + + if (updating) + return; + + pa_operation* o; + if (!(o = pa_context_set_sink_input_mute(get_context(), index, muteToggleButton->get_active(), NULL, NULL))) { + show_error(_("pa_context_set_sink_input_mute() failed")); + return; + } + + pa_operation_unref(o); +} + +void SinkInputWidget::prepareMenu() { + clearMenu(); + buildMenu(); +} + +void SinkInputWidget::clearMenu() { + + while (!sinkMenuItems.empty()) { + std::map<uint32_t, SinkMenuItem*>::iterator i = sinkMenuItems.begin(); + delete i->second; + sinkMenuItems.erase(i); + } +} + +void SinkInputWidget::buildMenu() { + for (std::map<uint32_t, SinkWidget*>::iterator i = mainWindow->sinkWidgets.begin(); i != mainWindow->sinkWidgets.end(); ++i) { + SinkMenuItem *m; + sinkMenuItems[i->second->index] = m = new SinkMenuItem(this, i->second->description.c_str(), i->second->index, i->second->index == sinkIndex); + submenu.append(m->menuItem); + } + + menu.show_all(); +} + +void SinkInputWidget::onKill() { + pa_operation* o; + if (!(o = pa_context_kill_sink_input(get_context(), index, NULL, NULL))) { + show_error(_("pa_context_kill_sink_input() failed")); + return; + } + + pa_operation_unref(o); +} + +void SinkInputWidget::SinkMenuItem::onToggle() { + + if (widget->updating) + return; + + if (!menuItem.get_active()) + return; + + pa_operation* o; + if (!(o = pa_context_move_sink_input_by_index(get_context(), widget->index, index, NULL, NULL))) { + show_error(_("pa_context_move_sink_input_by_index() failed")); + return; + } + + pa_operation_unref(o); +} diff --git a/src/sinkinputwidget.h b/src/sinkinputwidget.h new file mode 100644 index 0000000..6db504b --- /dev/null +++ b/src/sinkinputwidget.h @@ -0,0 +1,70 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef sinkinputwidget_h +#define sinkinputwidget_h + +#include "pavucontrol.h" + +#include "streamwidget.h" + +class MainWindow; + +class SinkInputWidget : public StreamWidget { +public: + SinkInputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + static SinkInputWidget* create(); + virtual ~SinkInputWidget(); + + SinkInputType type; + + uint32_t index, clientIndex, sinkIndex; + virtual void executeVolumeUpdate(); + virtual void onMuteToggleButton(); + virtual void onKill(); + virtual void prepareMenu(); + + MainWindow *mainWindow; + Gtk::Menu submenu; + Gtk::MenuItem titleMenuItem, killMenuItem; + + struct SinkMenuItem { + SinkMenuItem(SinkInputWidget *w, const char *label, uint32_t i, bool active) : + widget(w), + menuItem(label), + index(i) { + menuItem.set_active(active); + menuItem.set_draw_as_radio(true); + menuItem.signal_toggled().connect(sigc::mem_fun(*this, &SinkMenuItem::onToggle)); + } + + SinkInputWidget *widget; + Gtk::CheckMenuItem menuItem; + uint32_t index; + void onToggle(); + }; + + std::map<uint32_t, SinkMenuItem*> sinkMenuItems; + + void clearMenu(); + void buildMenu(); +}; + +#endif diff --git a/src/sinkwidget.cc b/src/sinkwidget.cc new file mode 100644 index 0000000..8d21bb0 --- /dev/null +++ b/src/sinkwidget.cc @@ -0,0 +1,85 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "sinkwidget.h" + +#include "i18n.h" + +SinkWidget::SinkWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + StreamWidget(cobject, x), + defaultMenuItem("_Default", true){ + + add_events(Gdk::BUTTON_PRESS_MASK); + + defaultMenuItem.set_active(false); + defaultMenuItem.signal_toggled().connect(sigc::mem_fun(*this, &SinkWidget::onDefaultToggle)); + menu.append(defaultMenuItem); + menu.show_all(); +} + +SinkWidget* SinkWidget::create() { + SinkWidget* w; + Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); + x->get_widget_derived("streamWidget", w); + return w; +} + +void SinkWidget::executeVolumeUpdate() { + pa_operation* o; + + if (!(o = pa_context_set_sink_volume_by_index(get_context(), index, &volume, NULL, NULL))) { + show_error(_("pa_context_set_sink_volume_by_index() failed")); + return; + } + + pa_operation_unref(o); +} + +void SinkWidget::onMuteToggleButton() { + StreamWidget::onMuteToggleButton(); + + if (updating) + return; + + pa_operation* o; + if (!(o = pa_context_set_sink_mute_by_index(get_context(), index, muteToggleButton->get_active(), NULL, NULL))) { + show_error(_("pa_context_set_sink_mute_by_index() failed")); + return; + } + + pa_operation_unref(o); +} + +void SinkWidget::onDefaultToggle() { + pa_operation* o; + + if (updating) + return; + + if (!(o = pa_context_set_default_sink(get_context(), name.c_str(), NULL, NULL))) { + show_error(_("pa_context_set_default_sink() failed")); + return; + } + pa_operation_unref(o); +} diff --git a/src/sinkwidget.h b/src/sinkwidget.h new file mode 100644 index 0000000..d5ce315 --- /dev/null +++ b/src/sinkwidget.h @@ -0,0 +1,46 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef sinkwidget_h +#define sinkwidget_h + +#include "pavucontrol.h" + +#include "streamwidget.h" + +class SinkWidget : public StreamWidget { +public: + SinkWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + static SinkWidget* create(); + + SinkType type; + Glib::ustring description; + Glib::ustring name; + uint32_t index, monitor_index, card_index; + bool can_decibel; + + Gtk::CheckMenuItem defaultMenuItem; + + virtual void onMuteToggleButton(); + virtual void executeVolumeUpdate(); + virtual void onDefaultToggle(); +}; + +#endif diff --git a/src/sourceoutputwidget.cc b/src/sourceoutputwidget.cc new file mode 100644 index 0000000..eafe620 --- /dev/null +++ b/src/sourceoutputwidget.cc @@ -0,0 +1,106 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "sourceoutputwidget.h" +#include "mainwindow.h" +#include "sourcewidget.h" + +#include "i18n.h" + +SourceOutputWidget::SourceOutputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + MinimalStreamWidget(cobject, x), + mainWindow(NULL), + titleMenuItem(_("_Move Stream..."), true), + killMenuItem(_("_Terminate Stream"), true) { + + add_events(Gdk::BUTTON_PRESS_MASK); + + menu.append(titleMenuItem); + titleMenuItem.set_submenu(submenu); + + menu.append(killMenuItem); + killMenuItem.signal_activate().connect(sigc::mem_fun(*this, &SourceOutputWidget::onKill)); +} + +SourceOutputWidget::~SourceOutputWidget() { + clearMenu(); +} + +SourceOutputWidget* SourceOutputWidget::create() { + SourceOutputWidget* w; + Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); + x->get_widget_derived("streamWidget", w); + return w; +} + +void SourceOutputWidget::onKill() { + pa_operation* o; + if (!(o = pa_context_kill_source_output(get_context(), index, NULL, NULL))) { + show_error(_("pa_context_kill_source_output() failed")); + return; + } + + pa_operation_unref(o); +} + +void SourceOutputWidget::clearMenu() { + + while (!sourceMenuItems.empty()) { + std::map<uint32_t, SourceMenuItem*>::iterator i = sourceMenuItems.begin(); + delete i->second; + sourceMenuItems.erase(i); + } +} + +void SourceOutputWidget::buildMenu() { + for (std::map<uint32_t, SourceWidget*>::iterator i = mainWindow->sourceWidgets.begin(); i != mainWindow->sourceWidgets.end(); ++i) { + SourceMenuItem *m; + sourceMenuItems[i->second->index] = m = new SourceMenuItem(this, i->second->description.c_str(), i->second->index, i->second->index == sourceIndex); + submenu.append(m->menuItem); + } + + menu.show_all(); +} + +void SourceOutputWidget::prepareMenu(void) { + clearMenu(); + buildMenu(); +} + +void SourceOutputWidget::SourceMenuItem::onToggle() { + + if (widget->updating) + return; + + if (!menuItem.get_active()) + return; + + pa_operation* o; + if (!(o = pa_context_move_source_output_by_index(get_context(), widget->index, index, NULL, NULL))) { + show_error(_("pa_context_move_source_output_by_index() failed")); + return; + } + + pa_operation_unref(o); +} diff --git a/src/sourceoutputwidget.h b/src/sourceoutputwidget.h new file mode 100644 index 0000000..cdaf28a --- /dev/null +++ b/src/sourceoutputwidget.h @@ -0,0 +1,68 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef sourceoutputwidget_h +#define sourceoutputwidget_h + +#include "pavucontrol.h" + +#include "minimalstreamwidget.h" + +class MainWindow; + +class SourceOutputWidget : public MinimalStreamWidget { +public: + SourceOutputWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + static SourceOutputWidget* create(); + virtual ~SourceOutputWidget(); + + SourceOutputType type; + + uint32_t index, clientIndex, sourceIndex; + virtual void onKill(); + + MainWindow *mainWindow; + Gtk::Menu submenu; + Gtk::MenuItem titleMenuItem, killMenuItem; + + struct SourceMenuItem { + SourceMenuItem(SourceOutputWidget *w, const char *label, uint32_t i, bool active) : + widget(w), + menuItem(label), + index(i) { + menuItem.set_active(active); + menuItem.set_draw_as_radio(true); + menuItem.signal_toggled().connect(sigc::mem_fun(*this, &SourceMenuItem::onToggle)); + } + + SourceOutputWidget *widget; + Gtk::CheckMenuItem menuItem; + uint32_t index; + void onToggle(); + }; + + std::map<uint32_t, SourceMenuItem*> sourceMenuItems; + + void clearMenu(); + void buildMenu(); + virtual void prepareMenu(); +}; + +#endif diff --git a/src/sourcewidget.cc b/src/sourcewidget.cc new file mode 100644 index 0000000..dc13f7f --- /dev/null +++ b/src/sourcewidget.cc @@ -0,0 +1,85 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "sourcewidget.h" + +#include "i18n.h" + +SourceWidget::SourceWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + StreamWidget(cobject, x), + defaultMenuItem(_("_Default"), true){ + + add_events(Gdk::BUTTON_PRESS_MASK); + + defaultMenuItem.set_active(false); + defaultMenuItem.signal_toggled().connect(sigc::mem_fun(*this, &SourceWidget::onDefaultToggle)); + menu.append(defaultMenuItem); + menu.show_all(); +} + +SourceWidget* SourceWidget::create() { + SourceWidget* w; + Glib::RefPtr<Gnome::Glade::Xml> x = Gnome::Glade::Xml::create(GLADE_FILE, "streamWidget"); + x->get_widget_derived("streamWidget", w); + return w; +} + +void SourceWidget::executeVolumeUpdate() { + pa_operation* o; + + if (!(o = pa_context_set_source_volume_by_index(get_context(), index, &volume, NULL, NULL))) { + show_error(_("pa_context_set_source_volume_by_index() failed")); + return; + } + + pa_operation_unref(o); +} + +void SourceWidget::onMuteToggleButton() { + StreamWidget::onMuteToggleButton(); + + if (updating) + return; + + pa_operation* o; + if (!(o = pa_context_set_source_mute_by_index(get_context(), index, muteToggleButton->get_active(), NULL, NULL))) { + show_error(_("pa_context_set_source_mute_by_index() failed")); + return; + } + + pa_operation_unref(o); +} + +void SourceWidget::onDefaultToggle() { + pa_operation* o; + + if (updating) + return; + + if (!(o = pa_context_set_default_source(get_context(), name.c_str(), NULL, NULL))) { + show_error(_("pa_context_set_default_source() failed")); + return; + } + pa_operation_unref(o); +} diff --git a/src/sourcewidget.h b/src/sourcewidget.h new file mode 100644 index 0000000..2fd137e --- /dev/null +++ b/src/sourcewidget.h @@ -0,0 +1,46 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef sourcewidget_h +#define sourcewidget_h + +#include "pavucontrol.h" + +#include "streamwidget.h" + +class SourceWidget : public StreamWidget { +public: + SourceWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + static SourceWidget* create(); + + SourceType type; + Glib::ustring name; + Glib::ustring description; + uint32_t index, card_index; + bool can_decibel; + + Gtk::CheckMenuItem defaultMenuItem; + + virtual void onMuteToggleButton(); + virtual void executeVolumeUpdate(); + virtual void onDefaultToggle(); +}; + +#endif diff --git a/src/streamwidget.cc b/src/streamwidget.cc new file mode 100644 index 0000000..fae605f --- /dev/null +++ b/src/streamwidget.cc @@ -0,0 +1,115 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "streamwidget.h" +#include "channelwidget.h" + +/*** StreamWidget ***/ + +StreamWidget::StreamWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x) : + MinimalStreamWidget(cobject, x) { + + x->get_widget("lockToggleButton", lockToggleButton); + x->get_widget("muteToggleButton", muteToggleButton); + + muteToggleButton->signal_clicked().connect(sigc::mem_fun(*this, &StreamWidget::onMuteToggleButton)); + + for (unsigned i = 0; i < PA_CHANNELS_MAX; i++) + channelWidgets[i] = NULL; +} + +void StreamWidget::setChannelMap(const pa_channel_map &m, bool can_decibel) { + channelMap = m; + + for (int i = 0; i < m.channels; i++) { + ChannelWidget *cw = channelWidgets[i] = ChannelWidget::create(); + cw->beepDevice = beepDevice; + cw->channel = i; + cw->can_decibel = can_decibel; + cw->streamWidget = this; + char text[64]; + snprintf(text, sizeof(text), "<b>%s</b>", pa_channel_position_to_pretty_string(m.map[i])); + cw->channelLabel->set_markup(text); + channelsVBox->pack_start(*cw, false, false, 0); + } + + lockToggleButton->set_sensitive(m.channels > 1); +} + +void StreamWidget::setVolume(const pa_cvolume &v, bool force) { + g_assert(v.channels == channelMap.channels); + + volume = v; + + if (timeoutConnection.empty() || force) { /* do not update the volume when a volume change is still in flux */ + for (int i = 0; i < volume.channels; i++) + channelWidgets[i]->setVolume(volume.values[i]); + } +} + +void StreamWidget::updateChannelVolume(int channel, pa_volume_t v) { + pa_cvolume n; + g_assert(channel < volume.channels); + + n = volume; + if (lockToggleButton->get_active()) { + for (int i = 0; i < n.channels; i++) + n.values[i] = v; + } else + n.values[channel] = v; + + setVolume(n, true); + + if (timeoutConnection.empty()) + timeoutConnection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &StreamWidget::timeoutEvent), 100); +} + +void StreamWidget::onMuteToggleButton() { + + lockToggleButton->set_sensitive(!muteToggleButton->get_active()); + + for (int i = 0; i < channelMap.channels; i++) + channelWidgets[i]->set_sensitive(!muteToggleButton->get_active()); +} + +bool StreamWidget::timeoutEvent() { + executeVolumeUpdate(); + return false; +} + +void StreamWidget::executeVolumeUpdate() { +} + + +void StreamWidget::setBaseVolume(pa_volume_t v) { + + if (channelMap.channels > 0) + channelWidgets[channelMap.channels-1]->setBaseVolume(v); +} + +void StreamWidget::setSteps(unsigned n) { + + for (int i = 0; i < channelMap.channels; i++) + channelWidgets[channelMap.channels-1]->setSteps(n); +} diff --git a/src/streamwidget.h b/src/streamwidget.h new file mode 100644 index 0000000..af5f0b9 --- /dev/null +++ b/src/streamwidget.h @@ -0,0 +1,56 @@ +/*** + This file is part of pavucontrol. + + Copyright 2006-2008 Lennart Poettering + Copyright 2009 Colin Guthrie + + pavucontrol 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. + + pavucontrol 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with pavucontrol. If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef streamwidget_h +#define streamwidget_h + +#include "pavucontrol.h" + +#include "minimalstreamwidget.h" + +class ChannelWidget; + +class StreamWidget : public MinimalStreamWidget { +public: + StreamWidget(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& x); + + void setChannelMap(const pa_channel_map &m, bool can_decibel); + void setVolume(const pa_cvolume &volume, bool force = false); + virtual void updateChannelVolume(int channel, pa_volume_t v); + + Gtk::ToggleButton *lockToggleButton, *muteToggleButton; + + pa_channel_map channelMap; + pa_cvolume volume; + + ChannelWidget *channelWidgets[PA_CHANNELS_MAX]; + + virtual void onMuteToggleButton(); + + sigc::connection timeoutConnection; + + bool timeoutEvent(); + + virtual void executeVolumeUpdate(); + virtual void setBaseVolume(pa_volume_t v); + virtual void setSteps(unsigned n); +}; + +#endif |