/***
This file is part of gnome-speaker-setup.
Copyright 2009 Lennart Poettering
gnome-speaker-setup is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
gnome-speaker-setup 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 Lesser General Public
License along with gnome-speaker-setup; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
using Gtk;
using PulseAudio;
using Gee;
using Canberra;
int64 default_card_index;
int64 default_device_index;
bool lock_selection;
public class BoldLabel : Label {
public BoldLabel(string? text = null) {
if (text != null)
set_markup("%s".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(pretty_position());
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(PROP_MEDIA_ROLE, "test");
p.sets(PROP_MEDIA_NAME, pretty_position());
p.sets(PROP_CANBERRA_FORCE_CHANNEL, position.to_string());
p.sets(PROP_CANBERRA_ENABLE, "1");
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 unowned string pretty_position() {
if (position == ChannelPosition.LFE)
return "Subwoofer";
return position.to_pretty_string();
}
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 PulseAudio.Context context;
private PulseAudio.GLibMainLoop main_loop;
private Canberra.Context canberra;
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 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 device_lookup;
private HashMap 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");
canberra.change_props(PROP_APPLICATION_NAME, "Speaker Setup");
canberra.change_props(PROP_APPLICATION_ID, "org.gnome.SpeakerSetup");
canberra.change_props(PROP_APPLICATION_ICON_NAME, "audio-card");
device_lookup = new HashMap();
card_lookup = new HashMap();
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("Please select a sound card and output to configure.");
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(12);
channel_table.set_row_spacings(12);
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;
CellRenderer r = new CellRendererText();
card_combo_box.pack_start(r, true);
card_combo_box.set_attributes(r, "markup", 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;
destroy += main_quit;
TreeIter i;
cards.append(out i);
cards.set(i, 0, "Independent Devices", 1, INVALID_INDEX);
main_loop = new PulseAudio.GLibMainLoop();
context = new PulseAudio.Context(main_loop.get_api(), "Speaker Test");
if (context == null) {
stderr.printf("Cannot allocate connection.\n");
main_quit();
} else {
context.set_state_callback(context_state_cb);
context.set_subscribe_callback(context_subscribe_cb);
if (context.connect() < 0) {
stderr.printf("Cannot initiate connection: %s\n", PulseAudio.strerror(context.errno()));
main_quit();
}
}
vbox.show_all();
}
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("Cannot 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;
}
}
bool select_card(uint32 index) {
TreeIter iter;
if (!find_card_iter(index, out iter))
return false;
card_combo_box.set_active_iter(iter);
return true;
}
bool select_device(uint32 index) {
if (!(index in device_lookup))
return false;
DeviceData *d = device_lookup[index];
ListStore *devices;
if (d->card != null) {
if (!select_card(d->card->index))
return false;
devices = d->card->devices;
} else {
card_combo_box.set_active(0); /* that's the independant devices entry */
devices = unowned_devices;
}
TreeIter iter;
if (!find_device_iter(devices, index, out iter))
return false;
device_combo_box.set_active_iter(iter);
return true;
}
bool pick_initial_selection() {
if (finished_startup)
return true;
finished_startup = true;
if (default_device_index != INVALID_INDEX) {
if (!select_device((uint32) default_device_index)) {
stderr.printf("Cannot find device.\n");
main_quit();
return false;
}
profile_combo_box.set_sensitive(!lock_selection);
card_combo_box.set_sensitive(!lock_selection);
device_combo_box.set_sensitive(!lock_selection);
} else if (default_card_index != INVALID_INDEX) {
if (!select_card((uint32) default_card_index)) {
stderr.printf("Cannot find card.\n");
main_quit();
return false;
}
card_combo_box.set_sensitive(!lock_selection);
} else {
if (card_combo_box.get_active() >= 0)
return true;
/* Select a device if there is one */
MapIterator diter = device_lookup.map_iterator();
if (diter.first()) {
DeviceData *d = diter.get_value();
if (select_device(d->index))
return true;
}
/* No device, so let's startup with a card if there is one */
MapIterator citer = card_lookup.map_iterator();
if (citer.first()) {
CardData *d = citer.get_value();
if (select_card(d->index))
return true;
}
card_combo_box.set_active(0);
}
return true;
}
void context_card_info_cb(PulseAudio.Context c, CardInfo? info, int eol) {
if (info == null)
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) {
if (pick_initial_selection())
show();
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() {
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);
}
}
static const OptionEntry entries[] = {
{ "card", 0, 0, OptionArg.INT64, out default_card_index, "Show this card by default", "INDEX" },
{ "device", 0, 0, OptionArg.INT64, out default_device_index, "Show this device by default", "INDEX" },
{ "lock", 0, 0, OptionArg.NONE, out lock_selection, "Don't allow other selection", null },
{ null }
};
int main (string[] args) {
Gtk.init(ref args);
default_card_index = INVALID_INDEX;
default_device_index = INVALID_INDEX;
lock_selection = false;
OptionContext context = new OptionContext("- Speaker Setup Tool");
context.add_main_entries(entries, null);
context.add_group(Gtk.get_option_group(false));
try {
context.parse(ref args);
} catch (GLib.OptionError e) {
stderr.printf("Failed to parse command line: %s\n", e.message);
return 1;
}
new SpeakerSetupWindow();
Gtk.main();
return 0;
}