summaryrefslogtreecommitdiffstats
path: root/gnome-speaker-setup.vala
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2010-02-20 01:22:14 +0100
committerLennart Poettering <lennart@poettering.net>2010-02-20 01:22:14 +0100
commitfbd7bca5fcb8758ce5cd774f5dc6805763ec824b (patch)
tree667d639e10872f7977c62104c071b8649070f280 /gnome-speaker-setup.vala
initial checkin
Diffstat (limited to 'gnome-speaker-setup.vala')
-rw-r--r--gnome-speaker-setup.vala840
1 files changed, 840 insertions, 0 deletions
diff --git a/gnome-speaker-setup.vala b/gnome-speaker-setup.vala
new file mode 100644
index 0000000..343acfc
--- /dev/null
+++ b/gnome-speaker-setup.vala
@@ -0,0 +1,840 @@
+using Gtk;
+using PulseAudio;
+using Gee;
+using Canberra;
+
+public class BoldLabel : Label {
+ public BoldLabel(string? text = null) {
+ if (text != null)
+ set_markup("<b>%s</b>".printf(text));
+ set_alignment(0, 0.5f);
+ }
+}
+
+public class FieldLabel : Label {
+ public FieldLabel(string? text = null) {
+ if (text != null)
+ set_markup_with_mnemonic(text);
+ set_alignment(0, 0.5f);
+ }
+}
+
+public class ChannelControl : VBox {
+
+ private Label label;
+ private Image image;
+ private Button test_button;
+ private ChannelPosition position;
+ private Canberra.Context *canberra;
+ private bool playing;
+
+ public ChannelControl(Canberra.Context? canberra, ChannelPosition p) {
+ position = p;
+ set_spacing(6);
+
+ image = new Image.from_icon_name("audio-volume-medium", IconSize.DIALOG);
+ pack_start(image, false, false, 0);
+
+ label = new Label(p.to_pretty_string());
+ pack_start(label, false, false, 0);
+
+ test_button = new Button.with_label("Test");
+ test_button.clicked += on_test_button_clicked;
+
+ Box box = new HBox(false, 0);
+ box.pack_start(test_button, true, false, 0);
+ pack_start(box, false, false, 0);
+
+ this.canberra = canberra;
+ }
+
+ public void on_test_button_clicked() {
+ Canberra.Proplist p;
+
+ canberra->cancel(1);
+
+ if (playing)
+ playing = false;
+ else {
+ Canberra.Proplist.create(out p);
+ p.sets("canberra.force_channel", position.to_string());
+
+ unowned string? name = sound_name();
+ if (name != null) {
+ p.sets(PROP_EVENT_ID, name);
+ playing = canberra->play_full(1, p, finish_cb) >= 0;
+ }
+
+ if (!playing) {
+ p.sets(PROP_EVENT_ID, "audio-test-signal");
+ playing = canberra->play_full(1, p, finish_cb) >= 0;
+ }
+
+ if (!playing) {
+ p.sets(PROP_EVENT_ID, "bell-window-system");
+ playing = canberra->play_full(1, p, finish_cb) >= 0;
+ }
+ }
+
+ update_button();
+ }
+
+ public void update_button() {
+ test_button.set_label(playing ? "Stop" : "Test");
+ }
+
+ public void finish_cb(Canberra.Context c, uint32 id, int code) {
+
+ /* This is called in the background thread, hence
+ * forward to main thread via idle callback */
+ Idle.add(idle_cb);
+ }
+
+ public bool idle_cb() {
+ playing = false;
+ update_button();
+ return false;
+ }
+
+ public unowned string? sound_name() {
+ switch (position) {
+ case ChannelPosition.FRONT_LEFT:
+ return "audio-channel-front-left";
+
+ case ChannelPosition.FRONT_RIGHT:
+ return "audio-channel-front-right";
+
+ case ChannelPosition.FRONT_CENTER:
+ return "audio-channel-front-center";
+
+ case ChannelPosition.REAR_LEFT:
+ return "audio-channel-rear-left";
+
+ case ChannelPosition.REAR_RIGHT:
+ return "audio-channel-rear-right";
+
+ case ChannelPosition.REAR_CENTER:
+ return "audio-channel-rear-center";
+
+ case ChannelPosition.LFE:
+ return "audio-channel-lfe";
+
+ case ChannelPosition.SIDE_LEFT:
+ return "audio-channel-side-left";
+
+ case ChannelPosition.SIDE_RIGHT:
+ return "audio-channel-side-right";
+
+ default:
+ return null;
+ }
+ }
+}
+
+public class SpeakerSetupWindow : Window {
+
+ private Label card_title_label;
+ private Label card_label;
+ private Label profile_label;
+
+ private Alignment device_title_alignment;
+ private Label device_title_label;
+ private Label device_label;
+ private Label port_label;
+
+ private ComboBox card_combo_box;
+ private ComboBox profile_combo_box;
+ private ComboBox device_combo_box;
+ private ComboBox port_combo_box;
+
+ private ChannelControl[] channel_controls = new ChannelControl[ChannelPosition.MAX];
+
+ private Table channel_table;
+ private Notebook channel_notebook;
+
+ private Button close_button;
+
+ private PulseAudio.Context context;
+ private PulseAudio.GLibMainLoop main_loop;
+
+ private Canberra.Context canberra;
+
+ private bool suppress_feedback;
+ private bool finished_startup;
+
+ [Compact]
+ class CardData {
+ public uint32 index;
+ public string description;
+
+ public int active_profile;
+
+ public ListStore profiles;
+ public ListStore devices;
+ }
+
+ [Compact]
+ class DeviceData {
+ public uint32 index;
+ public string name;
+ public string description;
+ public ChannelMap channel_map;
+ public int active_port;
+
+ public ListStore ports;
+ public CardData* card;
+ }
+
+ private ListStore unowned_devices;
+ private ListStore cards;
+
+ private ListStore *current_profiles;
+ private ListStore *current_devices;
+ private ListStore *current_ports;
+
+ private HashMap<uint32,DeviceData*> device_lookup;
+ private HashMap<uint32,CardData*> card_lookup;
+
+ public SpeakerSetupWindow() {
+
+ title = "Speaker Setup";
+ icon_name = "audio-card";
+ position = WindowPosition.CENTER;
+ border_width = 12;
+
+ Canberra.Context.create(out canberra);
+ canberra.set_driver("pulse");
+
+ device_lookup = new HashMap<uint32, DeviceData*>();
+ card_lookup = new HashMap<uint32, CardData*>();
+
+ unowned_devices = new ListStore(2, typeof(string), typeof(uint32));
+ cards = new ListStore(2, typeof(string), typeof(uint32));
+
+ Box vbox = new VBox(false, 0);
+ add(vbox);
+
+ Table upper_table = new Table(7, 2, false);
+ vbox.pack_start(upper_table, false, true, 0);
+
+ upper_table.set_col_spacings(6);
+ upper_table.set_row_spacings(0);
+
+ card_title_label = new BoldLabel("Hardware");
+ Alignment a = new Alignment(0, 0, 1, 1);
+ a.set_padding(0, 3, 0, 0);
+ a.add(card_title_label);
+ upper_table.attach(a, 0, 2, 0, 1, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ card_label = new FieldLabel("Sound _Card:");
+ upper_table.attach(card_label, 0, 1, 1, 2, AttachOptions.FILL, AttachOptions.FILL, 0, 3);
+ card_combo_box = new ComboBox.with_model(cards);
+ upper_table.attach(card_combo_box, 1, 2, 1, 2, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 3);
+ profile_label = new FieldLabel("_Profile:");
+ upper_table.attach(profile_label, 0, 1, 2, 3, AttachOptions.FILL, AttachOptions.FILL, 0, 3);
+ profile_combo_box = new ComboBox();
+ upper_table.attach(profile_combo_box, 1, 2, 2, 3, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 3);
+
+ device_title_label = new BoldLabel("Output");
+ device_title_alignment = new Alignment(0, 0, 1, 1);
+ device_title_alignment.set_padding(15, 3, 0, 0);
+ device_title_alignment.add(device_title_label);
+ upper_table.attach(device_title_alignment, 0, 2, 3, 4, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+ device_label = new FieldLabel("Sound _Output:");
+ upper_table.attach(device_label, 0, 1, 4, 5, AttachOptions.FILL, AttachOptions.FILL, 0, 3);
+ device_combo_box = new ComboBox();
+ upper_table.attach(device_combo_box, 1, 2, 4, 5, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 3);
+ port_label = new FieldLabel("_Connector:");
+ upper_table.attach(port_label, 0, 1, 5, 6, AttachOptions.FILL, AttachOptions.FILL, 0, 3);
+ port_combo_box = new ComboBox();
+ upper_table.attach(port_combo_box, 1, 2, 5, 6, AttachOptions.EXPAND|AttachOptions.FILL, AttachOptions.FILL, 0, 3);
+
+ Label label = new BoldLabel("Speaker Placement and Testing");
+ a = new Alignment(0, 0, 1, 1);
+ a.set_padding(15, 6, 0, 0);
+ a.add(label);
+ upper_table.attach(a, 0, 2, 6, 7, AttachOptions.FILL, AttachOptions.FILL, 0, 0);
+
+ Frame frame = new Frame(null);
+ frame.shadow_type = ShadowType.OUT;
+ vbox.pack_start(frame, true, true, 0);
+
+ channel_notebook = new Notebook();
+ channel_notebook.set_show_border(false);
+ channel_notebook.set_show_tabs(false);
+ frame.add(channel_notebook);
+
+ Label empty_label = new Label(null);
+ empty_label.set_markup("<i>Please select a sound card and output to configure.</i>");
+ channel_notebook.append_page(empty_label, null);
+
+ channel_table = new Table(3, 5, true);
+ channel_notebook.append_page(channel_table, null);
+
+ channel_table.set_col_spacings(6);
+ channel_table.set_row_spacings(36);
+ channel_table.set_border_width(36);
+
+ create_channel_controls();
+
+ channel_table.attach(new Image.from_icon_name("face-smile", IconSize.DIALOG), 2, 3, 1, 2,
+ AttachOptions.EXPAND, AttachOptions.EXPAND, 0, 0);
+
+ ButtonBox button_box = new HButtonBox();
+ button_box.set_layout(ButtonBoxStyle.END);
+ close_button = new Button.from_stock(STOCK_CLOSE);
+ button_box.pack_start(close_button, false, false, 0);
+
+ a = new Alignment(0, 0, 1, 1);
+ a.set_padding(12, 0, 0, 0);
+ a.add(button_box);
+ vbox.pack_start(a, false, false, 0);
+ close_button.clicked += on_close_button_clicked;
+
+ destroy += Gtk.main_quit;
+
+ main_loop = new PulseAudio.GLibMainLoop();
+ context = new PulseAudio.Context(main_loop.get_api(), "Speaker Test");
+
+ context.connect();
+
+ context.set_state_callback(context_state_cb);
+ context.set_subscribe_callback(context_subscribe_cb);
+
+ CellRenderer r = new CellRendererText();
+ card_combo_box.pack_start(r, true);
+ card_combo_box.set_attributes(r, "text", 0, null);
+
+ r = new CellRendererText();
+ profile_combo_box.pack_start(r, true);
+ profile_combo_box.set_attributes(r, "text", 0, null);
+
+ r = new CellRendererText();
+ device_combo_box.pack_start(r, true);
+ device_combo_box.set_attributes(r, "text", 0, null);
+
+ r = new CellRendererText();
+ port_combo_box.pack_start(r, true);
+ port_combo_box.set_attributes(r, "text", 0, null);
+
+ card_combo_box.changed += on_card_combo_box_changed;
+ profile_combo_box.changed += on_profile_combo_box_changed;
+ device_combo_box.changed += on_device_combo_box_changed;
+ port_combo_box.changed += on_port_combo_box_changed;
+
+ device_combo_box.set_model(unowned_devices);
+ current_devices = unowned_devices;
+
+ TreeIter i;
+ cards.append(out i);
+ cards.set(i, 0, "Non-Hardware", 1, INVALID_INDEX);
+ }
+
+ private const int position_table[] = {
+ /* Position, X, Y */
+ ChannelPosition.FRONT_LEFT, 0, 0,
+ ChannelPosition.FRONT_LEFT_OF_CENTER, 1, 0,
+ ChannelPosition.FRONT_CENTER, 2, 0,
+ ChannelPosition.MONO, 2, 0,
+ ChannelPosition.FRONT_RIGHT_OF_CENTER, 3, 0,
+ ChannelPosition.FRONT_RIGHT, 4, 0,
+ ChannelPosition.SIDE_LEFT, 0, 1,
+ ChannelPosition.SIDE_RIGHT, 4, 1,
+ ChannelPosition.REAR_LEFT, 0, 2,
+ ChannelPosition.REAR_CENTER, 2, 2,
+ ChannelPosition.REAR_RIGHT, 4, 2,
+ ChannelPosition.LFE, 3, 2
+ };
+
+ void create_channel_controls() {
+
+ for (int i = 0; i < position_table.length; i += 3) {
+ channel_controls[position_table[i]] = new ChannelControl(canberra, (ChannelPosition) position_table[i]);
+ channel_table.attach(channel_controls[position_table[i]],
+ position_table[i+1],
+ position_table[i+1]+1,
+ position_table[i+2],
+ position_table[i+2]+1,
+ AttachOptions.EXPAND, AttachOptions.EXPAND, 0, 0);
+ }
+ }
+
+ void update_channel_map(ChannelMap? m) {
+
+ if (m == null)
+ channel_notebook.set_current_page(0);
+ else {
+ for (int i = 0; i < position_table.length; i += 3)
+ channel_controls[position_table[i]].set_visible(
+ m != null && m.has_position((ChannelPosition) position_table[i]));
+
+ channel_notebook.set_current_page(1);
+ }
+ }
+
+ void context_state_cb(PulseAudio.Context c) {
+
+ PulseAudio.Context.State state = c.get_state();
+
+ if (!state.IS_GOOD()) {
+ stderr.printf("Couldn't establish connection: %s\n", PulseAudio.strerror(c.errno()));
+ Gtk.main_quit();
+ return;
+ };
+
+ if (state == PulseAudio.Context.State.READY) {
+ /* Connection is ready, let's query cards and sinks */
+ context.subscribe(PulseAudio.Context.SubscriptionMask.SINK | PulseAudio.Context.SubscriptionMask.CARD);
+ c.get_card_info_list(context_card_info_cb);
+ c.get_sink_info_list(context_sink_info_cb);
+ }
+ }
+
+ bool find_card_iter(uint32 idx, out TreeIter iter) {
+
+ if (!cards.get_iter_first(out iter))
+ return false;
+
+ do {
+ uint32 j;
+ cards.get(iter, 1, out j);
+
+ if (j == idx)
+ return true;
+
+ } while (cards.iter_next(ref iter));
+
+ return false;
+ }
+
+ bool find_device_iter(ListStore devices, uint32 idx, out TreeIter iter) {
+
+ if (!devices.get_iter_first(out iter))
+ return false;
+
+ do {
+ uint32 j;
+ devices.get(iter, 1, out j);
+
+ if (j == idx)
+ return true;
+
+ } while (devices.iter_next(ref iter));
+
+ return false;
+ }
+
+ void context_subscribe_cb(PulseAudio.Context c, PulseAudio.Context.SubscriptionEventType t, uint32 idx) {
+
+ switch (t & PulseAudio.Context.SubscriptionEventType.FACILITY_MASK) {
+
+ case PulseAudio.Context.SubscriptionEventType.CARD:
+
+ if ((t & PulseAudio.Context.SubscriptionEventType.TYPE_MASK) ==
+ PulseAudio.Context.SubscriptionEventType.REMOVE) {
+
+ if (idx in card_lookup) {
+ CardData *d = card_lookup[idx];
+
+ TreeIter iter;
+ if (find_card_iter(idx, out iter))
+ cards.remove(iter);
+
+ if (current_devices == d->devices)
+ current_devices = null;
+
+ card_lookup.remove(idx);
+ delete d;
+
+ /* Make sure the UI is properly updated when the last device of a card is gone */
+ on_card_combo_box_changed();
+ }
+ } else
+ context.get_card_info_by_index(idx, context_card_info_cb);
+
+ break;
+
+ case PulseAudio.Context.SubscriptionEventType.SINK:
+
+ if ((t & PulseAudio.Context.SubscriptionEventType.TYPE_MASK) ==
+ PulseAudio.Context.SubscriptionEventType.REMOVE) {
+
+ if (idx in device_lookup) {
+ DeviceData *d = device_lookup[idx];
+
+ TreeIter iter;
+ if (d->card != null) {
+ if (find_device_iter(d->card->devices, idx, out iter))
+ d->card->devices.remove(iter);
+ } else
+ if (find_device_iter(unowned_devices, idx, out iter))
+ unowned_devices.remove(iter);
+
+ device_lookup.remove(idx);
+ delete d;
+
+ /* Make sure the UI is properly updated when the last device of a card is gone */
+ on_card_combo_box_changed();
+ }
+
+ } else
+ context.get_sink_info_by_index(idx, context_sink_info_cb);
+
+ break;
+ }
+ }
+
+ void pick_initial_card() {
+ if (finished_startup)
+ return;
+
+ finished_startup = true;
+
+ if (card_combo_box.get_active() >= 0)
+ return;
+
+ TreeIter i;
+ if (cards.get_iter_first(out i) &&
+ cards.iter_next(ref i))
+ card_combo_box.set_active(1);
+ else
+ card_combo_box.set_active(0);
+ }
+
+ void context_card_info_cb(PulseAudio.Context c, CardInfo? info, int eol) {
+
+ if (info == null) {
+ pick_initial_card();
+ return;
+ }
+
+ CardData *d;
+ TreeIter iter;
+
+ if (info.index in card_lookup) {
+
+ d = card_lookup[info.index];
+ d->profiles.clear();
+
+ find_card_iter(d->index, out iter);
+ } else {
+ d = new CardData();
+
+ d->devices = new ListStore(2, typeof(string), typeof(uint32));
+ d->profiles = new ListStore(2, typeof(string), typeof(string));
+ d->index = info.index;
+
+ card_lookup[d->index] = d;
+ cards.append(out iter);
+ }
+
+ d->description = info.proplist.gets(PulseAudio.Proplist.PROP_DEVICE_DESCRIPTION);
+ if (d->description == null)
+ d->description = info.name;
+
+ cards.set(iter, 0, d->description, 1, d->index);
+
+ d->active_profile = -1;
+ for (int j = 0; j < info.n_profiles; j++) {
+ d->profiles.append(out iter);
+ d->profiles.set(iter,
+ 0, info.profiles[j].description,
+ 1, info.profiles[j].name);
+
+ if (info.active_profile == &info.profiles[j])
+ d->active_profile = j;
+ }
+
+ /* The card we are showing might have changed, so
+ * let's make sure to show all fields correctly */
+ on_card_combo_box_changed();
+ }
+
+ void context_sink_info_cb(PulseAudio.Context c, SinkInfo? info, int eol) {
+
+ if (info == null)
+ return;
+
+ DeviceData *d;
+ TreeIter iter;
+ ListStore *devices;
+
+ if (info.index in device_lookup) {
+
+ d = device_lookup[info.index];
+ d->ports.clear();
+
+ if (d->card == null)
+ devices = unowned_devices;
+ else
+ devices = d->card->devices;
+
+ find_device_iter(devices, d->index, out iter);
+
+ } else {
+
+ d = new DeviceData();
+ d->ports = new ListStore(2, typeof(string), typeof(string));
+ d->index = info.index;
+ d->name = info.name;
+
+ if (info.card == INVALID_INDEX) {
+ d->card = null;
+ devices = unowned_devices;
+ } else {
+ d->card = card_lookup[info.card];
+ devices = d->card->devices;
+ }
+
+ device_lookup[info.index] = d;
+ devices->append(out iter);
+ }
+
+ d->channel_map = info.channel_map;
+ d->description = info.proplist.gets(PulseAudio.Proplist.PROP_DEVICE_DESCRIPTION);
+ if (d->description == null)
+ d->description = info.name;
+
+ devices->set(iter, 0, d->description, 1, d->index);
+
+ d->active_port = -1;
+ for (int j = 0; j < info.n_ports; j++) {
+ d->ports.append(out iter);
+ d->ports.set(iter,
+ 0, info.ports[j]->description,
+ 1, info.ports[j]->name);
+
+ if (info.active_port == info.ports[j])
+ d->active_port = j;
+ }
+
+ /* A card might just have added a sink, so let's make
+ * sure the sink dropdown is visible */
+ on_card_combo_box_changed();
+ }
+
+ void on_close_button_clicked() {
+ Gtk.main_quit();
+ }
+
+ unowned CardData? get_current_card() {
+ TreeIter i;
+ uint32 index;
+
+ if (!card_combo_box.get_active_iter(out i))
+ return null;
+
+ cards.get(i, 1, out index);
+ if (index in card_lookup)
+ return card_lookup[index];
+
+ return null;
+ }
+
+ unowned string? get_current_profile() {
+ TreeIter i;
+ unowned string name;
+
+ if (current_profiles == null)
+ return null;
+
+ if (!profile_combo_box.get_active_iter(out i))
+ return null;
+
+ current_profiles->get(i, 1, out name);
+ return name;
+ }
+
+ unowned DeviceData? get_current_device() {
+ TreeIter i;
+ uint32 index;
+
+ if (current_devices == null)
+ return null;
+
+ if (!device_combo_box.get_active_iter(out i))
+ return null;
+
+ current_devices->get(i, 1, out index);
+ if (index in device_lookup)
+ return device_lookup[index];
+
+ return null;
+ }
+
+ unowned string? get_current_port() {
+ TreeIter i;
+ unowned string name;
+
+ if (current_ports == null)
+ return null;
+
+ if (!port_combo_box.get_active_iter(out i))
+ return null;
+
+ current_ports->get(i, 1, out name);
+ return name;
+ }
+
+ void show_ports(ListStore? ports, int active_port) {
+ TreeIter i;
+
+ if (ports != null && ports.get_iter_first(out i)) {
+ current_ports = ports;
+ port_combo_box.set_model(current_ports);
+ port_label.set_visible(true);
+ port_combo_box.set_visible(true);
+
+ if (active_port >= 0)
+ port_combo_box.set_active(active_port);
+ } else {
+ current_ports = null;
+ port_label.set_visible(false);
+ port_combo_box.set_visible(false);
+ port_combo_box.set_model(current_ports);
+ }
+ }
+
+ void show_profiles(ListStore? profiles, int active_profile) {
+ TreeIter i;
+
+ if (profiles != null && profiles.get_iter_first(out i)) {
+ current_profiles = profiles;
+ profile_combo_box.set_model(current_profiles);
+ profile_label.set_visible(true);
+ profile_combo_box.set_visible(true);
+
+ if (active_profile >= 0)
+ profile_combo_box.set_active(active_profile);
+ } else {
+ current_profiles = null;
+ profile_label.set_visible(false);
+ profile_combo_box.set_visible(false);
+ profile_combo_box.set_model(current_profiles);
+ }
+ }
+
+ void show_devices(ListStore? devices) {
+ TreeIter i;
+
+ if (devices != null && devices.get_iter_first(out i)) {
+ current_devices = devices;
+ device_combo_box.set_model(current_devices);
+ device_title_label.set_visible(true);
+ device_title_alignment.set_visible(true);
+ device_label.set_visible(true);
+ device_combo_box.set_visible(true);
+ } else {
+ current_devices = null;
+ device_title_label.set_visible(false);
+ device_title_alignment.set_visible(false);
+ device_label.set_visible(false);
+ device_combo_box.set_visible(false);
+ device_combo_box.set_model(current_devices);
+ show_ports(null, -1);
+ update_channel_map(null);
+ }
+ }
+
+ void select_something(ComboBox combo_box) {
+ TreeIter iter;
+ unowned TreeModel m;
+
+ if (combo_box.get_active_iter(out iter))
+ return;
+
+ m = combo_box.get_model();
+ if (m == null)
+ return;
+
+ if (!m.get_iter_first(out iter))
+ return;
+
+ device_combo_box.set_active_iter(iter);
+ }
+
+ void on_card_combo_box_changed() {
+ unowned CardData info;
+
+ suppress_feedback = true;
+
+ info = get_current_card();
+ if (info == null) {
+ show_profiles(null, -1);
+ show_devices(unowned_devices);
+ } else {
+ show_profiles(info.profiles, info.active_profile);
+ show_devices(info.devices);
+ }
+
+ select_something(device_combo_box);
+ suppress_feedback = false;
+
+ on_device_combo_box_changed();
+ }
+
+ void on_profile_combo_box_changed() {
+ unowned CardData? info;
+ unowned string? profile;
+
+ if (suppress_feedback)
+ return;
+
+ info = get_current_card();
+ if (info == null)
+ return;
+
+ profile = get_current_profile();
+ if (profile == null)
+ return;
+
+ context.set_card_profile_by_index(info.index, profile);
+ }
+
+ void on_device_combo_box_changed() {
+ unowned DeviceData info;
+
+ suppress_feedback = true;
+
+ info = get_current_device();
+ if (info == null) {
+ show_ports(null, -1);
+
+ update_channel_map(null);
+ } else {
+ show_ports(info.ports, info.active_port);
+ canberra.change_device(info.name);
+ update_channel_map(info.channel_map);
+ }
+
+ suppress_feedback = false;
+ }
+
+ void on_port_combo_box_changed() {
+ unowned DeviceData? info;
+ unowned string? port;
+
+ if (suppress_feedback)
+ return;
+
+ info = get_current_device();
+ if (info == null)
+ return;
+
+ port = get_current_port();
+ if (port == null)
+ return;
+
+ context.set_sink_port_by_index(info.index, port);
+ }
+}
+
+int main (string[] args) {
+ Gtk.init (ref args);
+
+ var window = new SpeakerSetupWindow();
+ window.show_all();
+
+ Gtk.main();
+
+ return 0;
+}