diff options
Diffstat (limited to 'data/hfconsole.in')
-rwxr-xr-x | data/hfconsole.in | 1542 |
1 files changed, 1207 insertions, 335 deletions
diff --git a/data/hfconsole.in b/data/hfconsole.in index feb65f1..4573982 100755 --- a/data/hfconsole.in +++ b/data/hfconsole.in @@ -114,17 +114,204 @@ class GtkAlerter: if not self.removed: self.complete() +# This is really _really_ stupid +# If there was a vCard parser that was part of the standard +# Python distribution, it would be used here. But there isn't +# and the job isn't very complicated. +class VCard: + """A hacky vCard representation""" + +class VCardSet: + """A hacky vCard container/manager object""" + + def __init__(self): + self._file_line = 0 + self._save_line = None + self._current_object = None + pass + + def _isalpha(self, c): + x = ord(c) + return ((x >= ord('a') and x <= ord('z')) or + (x >= ord('A') and x <= ord('Z'))) + def _isnum(self, c): + x = ord(c) + return (x >= ord('0') and x <= ord('9')) + def _iswsp(self, c): + return c == ' ' or c == '\t' + def _issafe(self, c): + x = ord(c) + return (self._iswsp(c) + or x == 0x21 or + (x >= 0x23 and x <= 0x2b) or + (x >= 0x2d and x <= 0x39) or + (x >= 0x3c and x <= 0x7e) or + x > 0x7f) + def _isqsafe(self, c): + x = ord(c) + return (_iswsp(c) + or x == 0x21 or + (x >= 0x23 and x <= 0x7e) or + x > 0x7f) + + def _process_line(self, name, groups, params, value): + print "Name:%s groups:%s value:%s" % (name, groups, value) + for param in params: + print "\t%s=%s" % (param, params[param]) + + def _parse_error(self, message): + raise Exception('Line %d: %s' % (self._file_line, message)) + def _parse_name_groups(self, line): + groups = [] + name = None + sep = 0 + while True: + if len(line) > sep: + x = line[sep] + if (self._isalpha(x) or + self._isnum(x) or + x == '-'): + sep += 1 + continue + elif x == '.': + if not sep: + self._parse_error( + _('Empty group definition')) + groups.append(line[:sep]) + line = line[(sep + 1):] + sep = 0 + continue + if not sep: + self._parse_error(_("Empty content name")) + name = line[:sep] + line = line[sep:] + return (groups, name, line) + def _parse_param_value(self, line): + sep = 0 + if line[0] == '"': + line = line[1:] + while True: + if len(line) <= sep: + self._parse_error( + _("Malformed quoted " + "parameter value")) + x = line[sep] + if self._isqsafe(x): + sep += 1 + continue + elif x == '"': + break + else: + self._parse_error( + _("Malformed quoted " + "parameter value")) + + value = '' + if sep: + value = line[:sep] + line = line[(sep + 1):] + return (value, line) + + while True: + if len(line) > sep: + x = line[sep] + if self._issafe(x): + sep += 1 + continue + break + value = '' + if sep: + value = line[:sep] + line = line[sep:] + return (value, line) + + def _parse_param(self, line): + name = None + values = [] + sep = 0 + while True: + if len(line) <= sep: + self._parse_error(_("Malformed parameter")) + x = line[sep] + if self._isalpha(x) or self._isnum(x) or x == '-': + sep += 1 + else: + if not sep: + self._parse_error( + _("Empty parameter name")) + name = line[:sep] + if x == '=': + line = line[(sep + 1):] + break + line = line[sep:] + return (name, values, line) + + while True: + (x, line) = self._parse_param_value(line) + values.append(x) + if not len(line): + return (name, values, line) + x = line[0] + if x != ',': + return (name, values, line) + line = line[1:] + def _parse_line(self, line): + print "Line:%s" % line + (groups, name, line) = self._parse_name_groups(line) + params = {} + if not line: + self._parse_error(_('Malformed content line')) + while line[0] == ';': + (param, values, line) = self._parse_param(line[1:]) + if param in params: + params[param].extend(values) + else: + params[param] = values + if line[0] != ':': + self._parse_error(_('Malformed content line')) + self._process_line(name, groups, params, line[1:]) + + def _feed_line(self, line): + if not line or not self._iswsp(line[0]): + if self._save_line: + self._parse_line(self._save_line) + self._save_line = line + elif self._save_line: + self._save_line += line[1:] + else: + self._save_line = line[1:] + self._file_line += 1 + def _feed_done(self): + self._feed_line(None) + if self._current_object: + self._parse_error(_('Unterminated object in stream')) + def import_string(self, str): + self._file_line = 0 + line = '' + for x in str.splitlines(): + self._feed_line(x) + self._feed_done() + def import_file(self, path): + f = open(path, 'r') + contents = f.read() + f.close() + self.import_string(contents) + + class HfConsole: """This is a user interface for the hfpd Bluetooth hands-free profile server""" + DBUS_SERVICE_NAME = 'org.freedesktop.DBus' DBUS_INTERFACE_DBUS = 'org.freedesktop.DBus' DBUS_INTERFACE_PROPERTIES = 'org.freedesktop.DBus.Properties' + DBUS_BUS_OBJECT = '/org/freedesktop/DBus' HFPD_HANDSFREE_INTERFACE_NAME = 'net.sf.nohands.hfpd.HandsFree' HFPD_SOUNDIO_INTERFACE_NAME = 'net.sf.nohands.hfpd.SoundIo' HFPD_AUDIOGATEWAY_INTERFACE_NAME = 'net.sf.nohands.hfpd.AudioGateway' HFPD_SERVICE_NAME = 'net.sf.nohands.hfpd' HFPD_HANDSFREE_OBJECT = '/net/sf/nohands/hfpd' + HFPD_SOUNDIO_OBJECT = '/net/sf/nohands/hfpd/soundio' def addr_transform(self, addr): val = (str(addr[0:2]) + str(addr[3:5]) + str(addr[6:8]) + @@ -160,38 +347,77 @@ class HfConsole: # Create our dictionay and connect it dic = { # Callbacks for the main window + "AgSelector_button_press_event_cb" : + self.ag_selector_open, + "AgSelector_toggled_cb" : self.ag_selector_toggle, + "AgScan_clicked_cb" : self.bar_ag_scan_clicked, + "AgConnect_clicked_cb" : self.bar_ag_connect_clicked, + "BarDialPad_clicked_cb" : self.bar_dialpad_clicked, + "BarContacts_clicked_cb" : self.bar_contacts_clicked, + "AgAudioToggle_toggled_cb" : self.bar_ag_audio_toggled, + "AgHangUp_clicked_cb" : self.bar_ag_hangup_clicked, + "AgHoldCall_clicked_cb" : self.bar_ag_swaphold_clicked, + "AgSwapCall_clicked_cb" : self.bar_ag_swaphold_clicked, + "AgDial_clicked_cb" : self.bar_ag_dial_clicked, + "AgRedial_clicked_cb" : self.bar_ag_redial_clicked, + "ConfigOpen_clicked_cb" : self.bar_config_clicked, "DigitButton_pressed_cb" : self.digit_button_pressed, "DigitButton_clicked_cb" : self.digit_button_clicked, "PhoneNumBs_clicked_cb" : self.phone_num_bs, - "DialPadDial_clicked_cb" : self.dial_dial_clicked, - "DialPadRedial_clicked_cb" : self.dial_redial_clicked, - "DialPadXfer_activate_cb" : self.dial_xfer_clicked, - "ScanButton_clicked_cb" : self.scan_open, - "ConfigButton_clicked_cb" : self.config_open, "Mute_toggled_cb" : self.mute_toggled, "on_MainWindow_destroy" : gtk.main_quit, - # Callbacks for the scan dialog - "on_ScanDialog_close" : gtk.main_quit, - "ScanOK_clicked_cb" : self.scan_ok_clicked, - "ScanCancel_clicked_cb" : self.scan_cancel_clicked, + # Known Devices tab + "DevicesClose_clicked_cb" : self.devices_close_clicked, + "DevicesAdd_clicked_cb" : self.devices_add_clicked, + "DevicesDelete_clicked_cb" : + self.devices_delete_clicked, + "DevicesConnect_clicked_cb" : + self.devices_connect_clicked, + "DevicesDisconnect_clicked_cb" : + self.devices_disconnect_clicked, + "DevicesAutoConnectNever_toggled_cb" : + self.devices_autoconnect_never_toggled, + "DevicesAutoConnectDisabled_toggled_cb" : + self.devices_autoconnect_disabled_toggled, + "DevicesAutoConnectEnabled_toggled_cb" : + self.devices_autoconnect_enabled_toggled, + + # New Device tab + "NewDeviceClose_clicked_cb" : + self.newdevice_close_clicked, + "NewDeviceAdd_clicked_cb" : + self.newdevice_add_clicked, + "NewDeviceClear_clicked_cb" : + self.newdevice_clear_clicked, + + # Callbacks for the scan tab + "ScanAdd_clicked_cb" : self.scan_add_clicked, + "ScanClose_clicked_cb" : self.scan_close_clicked, "ScanHistory_clicked_cb" : self.scan_history_clicked, "ScanRestart_clicked_cb" : self.scan_restart_clicked, - # Callbacks for the history dialog + # Callbacks for the history tab "HistoryOK_clicked_cb" : self.history_ok_clicked, "HistoryCancel_clicked_cb" : self.history_cancel_clicked, - # Callbacks for the config dialog - "ConfigOk_clicked_cb" : self.config_ok_clicked, + # Callbacks for the config tab + "ConfigOK_clicked_cb" : self.config_ok_clicked, "ConfigCancel_clicked_cb" : self.config_cancel_clicked, + "ConfigInit_clicked_cb" : self.config_init_clicked, + "ConfigDevices_clicked_cb" : + self.config_devices_clicked, "ConfigDriver_changed_cb" : self.config_driver_changed, "ConfigPacketIntervalHint_toggled_cb" : self.config_packet_interval_toggled, "ConfigPacketInterval_value_changed_cb" : self.config_packet_interval_changed, + "ConfigJitterWindowHint_toggled_cb" : + self.config_jitter_window_toggled, + "ConfigJitterWindow_value_changed_cb" : + self.config_jitter_window_changed, "ConfigMinOutBufferHint_toggled_cb" : self.config_min_out_buffer_toggled, "ConfigMinOutBuffer_value_changed_cb" : @@ -217,22 +443,88 @@ class HfConsole: "ConfigDspTest_clicked_cb" : self.config_dsptest_open, "on_ConfigDialog_close" : gtk.main_quit, - # Callbacks for the DSP test assistant - "DspTestAssistant_prepare_cb" : - self.config_dsptest_prepare, - "DspTestAssistant_cancel_cb" : - self.config_dsptest_cancel, - "DspTestAssistant_close_cb" : + # Callbacks for the DSP test + "DspTestForward_clicked_cb" : + self.config_dsptest_forward, + "DspTestBack_clicked_cb" : + self.config_dsptest_back, + "DspTestClose_clicked_cb" : self.config_dsptest_close, + "DspTestNotebook_switch_page_cb" : + self.config_dsptest_switch_page, "DspTestRecStart1_toggled_cb" : self.config_dsptest_recstart1_toggled, "DspTestRecStart2_toggled_cb" : self.config_dsptest_recstart2_toggled, "DspTestPlayStart_toggled_cb" : self.config_dsptest_playstart_toggled, + + # Callbacks for the initial configuration page + "InitConfDone_clicked_cb" : self.initconf_done_clicked, + "InitConfBack_clicked_cb" : self.initconf_back_clicked, + "InitConfForward_clicked_cb" : + self.initconf_forward_clicked, + "InitConfNotebook_switch_page_cb" : + self.initconf_switch_page, + } self.widgets.signal_autoconnect(dic) + self.ag_list = gtk.Menu() + self.ag_list_separator = gtk.SeparatorMenuItem() + self.ag_list_separator.set_no_show_all(True) + self.ag_list.append(self.ag_list_separator) + self.ag_list_scan = gtk.MenuItem(_('Search for Device...')) + self.ag_list_scan.connect('activate', + self.ag_selector_scan_activate) + self.ag_list.append(self.ag_list_scan) + self.ag_list_manage = gtk.MenuItem(_('Manage Devices...')) + self.ag_list_manage.connect('activate', + self.ag_selector_manage_activate) + self.ag_list.append(self.ag_list_manage) + self.ag_list.connect('selection-done', self.ag_selector_close) + self.ag_list.show_all() + + self.ag_list_detail = gtk.ListStore(gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_PYOBJECT) + self['DevicesList'].set_model(self.ag_list_detail) + tvcolumn = gtk.TreeViewColumn(_('Name')) + self['DevicesList'].append_column(tvcolumn) + cell = gtk.CellRendererText() + tvcolumn.pack_start(cell, True) + tvcolumn.add_attribute(cell, 'text', 0) + tvcolumn = gtk.TreeViewColumn(_('Address')) + self['DevicesList'].append_column(tvcolumn) + cell = gtk.CellRendererText() + tvcolumn.pack_start(cell, True) + tvcolumn.add_attribute(cell, 'text', 1) + self['DevicesList'].get_selection().connect('changed', + self.devices_select) + self.devices_ag = None + + self.ag_list_new = gtk.ListStore(gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_PYOBJECT) + self['NewDeviceList'].set_model(self.ag_list_new) + tvcolumn = gtk.TreeViewColumn(_('Name')) + self['NewDeviceList'].append_column(tvcolumn) + cell = gtk.CellRendererText() + tvcolumn.pack_start(cell, True) + tvcolumn.add_attribute(cell, 'text', 0) + tvcolumn = gtk.TreeViewColumn(_('Address')) + self['NewDeviceList'].append_column(tvcolumn) + cell = gtk.CellRendererText() + tvcolumn.pack_start(cell, True) + tvcolumn.add_attribute(cell, 'text', 1) + self['NewDeviceList'].get_selection().connect('changed', + self.newdevice_select) + self.ag_list_new.connect('row-inserted', + self.newdevice_row_added) + self.ag_list_new.connect('row-deleted', + self.newdevice_row_removed) + self.newdevice_ag = None + self['ConfigDriverDevList'].set_model( gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)) @@ -249,7 +541,7 @@ class HfConsole: gobject.TYPE_UINT, gobject.TYPE_STRING) self['ScanResults'].set_model(self.scanresults) - tvcolumn = gtk.TreeViewColumn('Device') + tvcolumn = gtk.TreeViewColumn(_('Device')) self['ScanResults'].append_column(tvcolumn) cell = gtk.CellRendererText() tvcolumn.pack_start(cell, True) @@ -261,7 +553,7 @@ class HfConsole: gobject.TYPE_UINT, gobject.TYPE_STRING) self['HistoryResults'].set_model(self.historyresults) - tvcolumn = gtk.TreeViewColumn('Device') + tvcolumn = gtk.TreeViewColumn(_('Device')) self['HistoryResults'].append_column(tvcolumn) cell = gtk.CellRendererText() tvcolumn.pack_start(cell, True) @@ -276,9 +568,14 @@ class HfConsole: self.ags = {} self.ags_added = {} self.soundmonitor = False - self.autoreconnect = True self.selected_ag = None + self.selected_ag_lock = False self.command_audio_attach_state = False + self.nobt = False + self.nested = False + + self.frontpage = 0 #1 + self.frontnotebook_set(self.frontpage) self.alerter_factory = (lambda x: GtkAlerter(self.gladefile, x)) @@ -296,24 +593,24 @@ class HfConsole: p = subprocess.Popen(exc, shell=True) def getprop_hfpd(self, prop): - return self.hfpd_props.Get('net.sf.nohands.hfpd.HandsFree', + return self.hfpd_props.Get(self.HFPD_HANDSFREE_INTERFACE_NAME, prop) def getprop_soundio(self, prop): - return self.soundio_props.Get('net.sf.nohands.hfpd.SoundIo', + return self.soundio_props.Get(self.HFPD_SOUNDIO_INTERFACE_NAME, prop) def setprop_hfpd(self, name, value): - self.hfpd_props.Set('net.sf.nohands.hfpd.HandsFree', + self.hfpd_props.Set(self.HFPD_HANDSFREE_INTERFACE_NAME, name, value) def setprop_soundio(self, name, value): - self.soundio_props.Set('net.sf.nohands.hfpd.SoundIo', + self.soundio_props.Set(self.HFPD_SOUNDIO_INTERFACE_NAME, name, value) def Start(self): try: self.busctl = dbus.Interface( - self.dbus.get_object('org.freedesktop.DBus', - '/org/freedesktop/DBus'), - dbus_interface='org.freedesktop.DBus') + self.dbus.get_object(self.DBUS_SERVICE_NAME, + self.DBUS_BUS_OBJECT), + dbus_interface=self.DBUS_INTERFACE_DBUS) except dbus.exceptions.DBusException, (ex): self.fatal(_('Could not connect to D-Bus:\n%s') % str(ex)) @@ -321,21 +618,23 @@ class HfConsole: try: self.hfpd = dbus.Interface( - self.dbus.get_object('net.sf.nohands.hfpd', - '/net/sf/nohands/hfpd'), - dbus_interface='net.sf.nohands.hfpd.HandsFree') + self.dbus.get_object(self.HFPD_SERVICE_NAME, + self.HFPD_HANDSFREE_OBJECT), + dbus_interface = + self.HFPD_HANDSFREE_INTERFACE_NAME) self.hfpd_props = dbus.Interface( - self.dbus.get_object('net.sf.nohands.hfpd', - '/net/sf/nohands/hfpd'), - dbus_interface='org.freedesktop.DBus.Properties') + self.dbus.get_object(self.HFPD_SERVICE_NAME, + self.HFPD_HANDSFREE_OBJECT), + dbus_interface=self.DBUS_INTERFACE_PROPERTIES) self.soundio = dbus.Interface( - self.dbus.get_object('net.sf.nohands.hfpd', - '/net/sf/nohands/hfpd/soundio'), - dbus_interface='net.sf.nohands.hfpd.SoundIo') + self.dbus.get_object(self.HFPD_SERVICE_NAME, + self.HFPD_SOUNDIO_OBJECT), + dbus_interface = + self.HFPD_SOUNDIO_INTERFACE_NAME) self.soundio_props = dbus.Interface( - self.dbus.get_object('net.sf.nohands.hfpd', - '/net/sf/nohands/hfpd/soundio'), - dbus_interface='org.freedesktop.DBus.Properties') + self.dbus.get_object(self.HFPD_SERVICE_NAME, + self.HFPD_SOUNDIO_OBJECT), + dbus_interface=self.DBUS_INTERFACE_PROPERTIES) except dbus.exceptions.DBusException, (ex): self.fatal(_('Could not connect to hfpd:\n%s\n\n' 'Ensure that hfpd and its D-Bus ' @@ -351,7 +650,7 @@ class HfConsole: except: v = 0 - my_version = 2 + my_version = 3 if v < my_version: self.fatal(_('Version mismatch with hfpd!\n' 'hfpd version: %(hfpdver)d\n' @@ -393,7 +692,7 @@ class HfConsole: loadags = self.configfile.items('devices') for x in loadags: x = self.addr_untransform(x[0]) - self.add_audiogateway(x) + self.add_audiogateway(x, False) self.busctl.connect_to_signal("NameOwnerChanged", self.hfpd_lost) @@ -426,20 +725,24 @@ class HfConsole: self.soundio_status_ctx = self['StatusBar'].get_context_id( 'SoundIoStatus') self.soundio_skew_monitor = None - st = self.getprop_hfpd('SystemState') - self.hfpd_system_state(st) - if not st: + self.nobt = not self.getprop_hfpd('SystemState') + self.hfpd_system_state(not self.nobt) + if self.nobt: try: self.hfpd.Start() + self.nobt = False except dbus.exceptions.DBusException, (ex): if (ex.get_dbus_name() != 'net.sf.nohands.hfpd.Error'): - self.fatal(ex.get_dbus_message()) + self.fatal(str(ex)) return self.setprop_hfpd('AutoRestart', True) ags = self.getprop_hfpd('AudioGateways') for x in ags: self.hfpd_ag_added(str(x)) + if not self.selected_ag: + self.ag_none_selected() + self.soundio_state_changed(self.getprop_soundio('State')) hwg['MainWindow'].show() @@ -449,13 +752,12 @@ class HfConsole: def error_closed(self, dlg, response): dlg.hide() - def error_message(self, msg, window=None): + def error_message(self, msg): dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg) - if not window: - window = self['MainWindow'] + window = self['MainWindow'] dlg.set_title(window.get_title()) dlg.set_transient_for(window) dlg.set_position(gtk.WIN_POS_CENTER_ON_PARENT) @@ -471,75 +773,36 @@ class HfConsole: dlg.connect('response', self.fatal_closed) class AudioGateway: - def image_button(self, toggle, stock, tip): - if toggle: - but = gtk.ToggleButton() - else: - but = gtk.Button() - img = gtk.Image() - img.set_from_stock(stock, - gtk.ICON_SIZE_LARGE_TOOLBAR) - but.add(img) - img.show() - but.set_no_show_all(True) - - # Fail gracefully if not available - try: but.set_tooltip_text(tip) - except: pass - return but - + def reset_indicators(self): + self.signal = -1 + self.battery = -1 + self.message = False + self.vm = False + self.noservice = False def __init__(self, hfc, sbus, hfpd, agpath, box): self.hfc = hfc self.path = agpath self.hfpd = hfpd self.alerter = None self.caller_id = None + self.reset_indicators() + self.autoreconnect = 1 + self.autoreconnect_flag = False - frame = gtk.Frame('') - box.pack_start(frame, expand=False) - vbox = gtk.VBox(spacing = 8) - vbox.set_border_width(8) - frame.add(vbox) - hbox = gtk.HBox(spacing = 8) - vbox.pack_start(hbox) - lab = gtk.Label('State Here') - lab.set_alignment(0.0, 0.5) - hbox.pack_start(lab, expand=True) - self.statelab = lab - but = self.image_button(False, gtk.STOCK_DELETE, - _('Remove Device')) - hbox.pack_end(but, expand=False) - but.connect('clicked', self.remove_clicked) - self.remove_but = but - but = self.image_button(False, gtk.STOCK_CONNECT, - _('Connect Now')) - hbox.pack_end(but, expand=False) - but.connect('clicked', self.connect_clicked) - self.connect_but = but - but = self.image_button(True, gtk.STOCK_APPLY, - _('Set Device as Primary')) - hbox.pack_end(but, expand=False) - but.connect('toggled', self.select_toggled) - self.select_but = but - but = self.image_button(False, gtk.STOCK_ADD, - _('Remember This Device')) - hbox.pack_end(but, expand=False) - but.connect('clicked', self.add_clicked) - self.add_but = but - self.frame = frame self.state = 0 self.callstate = 0 self.audiostate = 0 - self.noservice = False + self.disp = hfc.ag_disp_new(self) - self.dbusobj = sbus.get_object('net.sf.nohands.hfpd', - agpath) + self.dbusobj = sbus.get_object( + hfc.HFPD_SERVICE_NAME, + agpath) self.ag = dbus.Interface(self.dbusobj, dbus_interface = - 'net.sf.nohands.hfpd.AudioGateway') + hfc.HFPD_AUDIOGATEWAY_INTERFACE_NAME) self.agprops = dbus.Interface(self.dbusobj, - dbus_interface = - 'org.freedesktop.DBus.Properties') + dbus_interface = + hfc.DBUS_INTERFACE_PROPERTIES) self.sigs = [] sig = self.ag.connect_to_signal("NameResolved", @@ -554,6 +817,9 @@ class HfConsole: sig = self.ag.connect_to_signal("AudioStateChanged", self.audio_state_changed) self.sigs.append(sig) + sig = self.ag.connect_to_signal("AutoReconnectChanged", + self.autoreconnect_changed) + self.sigs.append(sig) sig = self.ag.connect_to_signal("IndicatorChanged", self.indicator_changed) self.sigs.append(sig) @@ -562,111 +828,168 @@ class HfConsole: self.sigs.append(sig) self.addr = self.getprop('Address') - name = self.getprop('Name') - self.frame.set_label(name) + self.name = self.getprop('Name') + if self.name == self.addr: + self.name = self.hfc.ag_history_getname( + self.addr) self.state = self.getprop('State') self.callstate = self.getprop('CallState') self.audiostate = self.getprop('AudioState') self.state_changed(self.state, False) self.call_state_changed(self.callstate) self.audio_state_changed(self.audiostate) - self.frame.show_all() + self.disp.set_name(self.name) self.hfc.ag_update_history(self.addr, None) - def set_state_labels(self): - state = self.state - callstate = self.callstate - lab = 'Invalid State' - self.remove_but.show() - if state != 4: - if state == 1: - lab = _('Destroyed') + '\n' - if state == 2: - lab = _('Detached') + '\n' - self.connect_but.show() - self.connect_but.set_sensitive(True) - if state == 3: - lab = _('Attaching') + '\n' - self.connect_but.show() - self.connect_but.set_sensitive(False) - self.statelab.set_markup(lab) - self.select_but.hide() - self.hfc.ag_state_changed(self) - return - self.connect_but.hide() - self.select_but.show() - if callstate == 1: - lab = _('Ready') + '\n' - if self.noservice: - lab = _('No Service') + '\n' - if callstate == 2: - lab = _('Dialing') + '\n' - if callstate == 3: - lab = _('Call Established') + '\n' - if callstate == 4: - lab = _('Ringing') + '\n' - if callstate == 5: - lab = _('Waiting Call') + '\n' - if self.has_audio(): - lab += _('Audio Open') - self.statelab.set_markup(lab) - self.hfc.ag_state_changed(self) - def has_feature(self, feat): return (hasattr(self, 'features') and self.features[feat]) + def is_connecting(self): + return self.state == 3 + def is_connected(self): + return self.state == 4 def can_dial(self): - return self.state == 4 and (self.callstate == 1 or - self.callstate == 4) + return self.is_connected() and (self.callstate == 1 or + self.callstate == 4) def can_dtmf(self): - return self.state == 4 and (self.callstate == 2 or - self.callstate == 3 or - self.callstate == 5) + return self.is_connected() and (self.callstate == 2 or + self.callstate == 3 or + self.callstate == 5) + def can_hold(self): + return (self.is_connected() and + self.has_feature('SwapHoldActive') and + ((self.callstate == 3) or + (self.callstate == 5))) + def has_any_call(self): + return self.is_connected() and (self.callstate != 1) + def has_waiting_call(self): + return self.is_connected() and (self.callstate == 4 or + self.callstate == 5) + def has_message(self): + return self.is_connected() and (self.message or self.vm) def can_drop_waiting(self): return self.has_feature('RejectCall') def can_drop_held_udub(self): return self.has_feature('DropHeldUdub') def selectable(self): - return self.state == 4 + return self.is_mine() + def select_priority(self): + if self.has_audio(): + return 4 + if self.has_any_call(): + return 3 + if self.has_service(): + return 2 + if self.is_connected(): + return 1 + return 0 + def is_mine(self): + return self.hfc.ags_added.has_key(self.addr) + def load_config(self): + ar = True + if self.hfc.ag_config_has_option(self.addr, + 'autoreconnect'): + ar = self.hfc.ag_config_get_option(self.addr, + 'autoreconnect') + try: ar = int(ar) + except: ar = 2 + if ar != self.autoreconnect: + self.autoreconnect = ar + if ar == 2: self.set_autoreconnect_flag(True) + else: self.set_autoreconnect_flag(False) + def save_config(self): + self.hfc.ag_config_set_option(self.addr, + 'autoreconnect', + self.autoreconnect) + def update_mine(self): + self.disp.set_name(None) + self.disp.set_name(self.name) + def get_autoreconnect(self): + return self.autoreconnect + def set_autoreconnect(self, value): + if value == self.autoreconnect: + return + self.autoreconnect = value + if value == 2: self.set_autoreconnect_flag(True) + else: self.set_autoreconnect_flag(False) + self.save_config() + self.disp.set_state() + def get_autoreconnect_flag(self): + return self.autoreconnect_flag + def set_autoreconnect_flag(self, value): + value = bool(value) + if value != self.autoreconnect_flag: + self.setprop('AutoReconnect', + dbus.Boolean(value)) + self.autoreconnect_flag = value + def has_service(self): + return self.is_connected() and not self.noservice + def signal_level(self): + return self.signal + def battery_level(self): + return self.battery + def audio_connecting(self): + return self.audiostate == 2 def has_audio(self): return self.audiostate == 3 def wants_audio(self): return (self.callstate == 2 or self.callstate == 3 or self.callstate == 5) - def select(self, state): - if state and not self.selectable(): - return False - self.nested = True - self.select_but.set_active(state) - del self.nested - return True def name_resolved(self, name): - self.frame.set_label(name) - self.hfc.ag_update_history(self.addr, name) + self.name = name + self.disp.set_name(self.name) + self.hfc.ag_update_history(self.addr, self.name) def state_changed(self, state, voluntary): self.state = int(state) - if self.state == 4: + if self.is_connected(): self.features = self.getprop('Features') + if (self.is_mine() and + self.get_autoreconnect() == 1): + self.set_autoreconnect(2) else: + self.reset_indicators() self.call_state_changed(1) if hasattr(self, 'features'): del self.features - self.set_state_labels() + self.disp.set_state() def call_state_changed(self, state): self.callstate = int(state) - self.set_state_labels() + self.disp.set_state() if (self.callstate != 4 and self.callstate != 5): self.alert_close() def audio_state_changed(self, state): self.audiostate = int(state) - self.set_state_labels() + self.disp.set_state() + def soundio_state_changed(self): + self.disp.set_state() + def autoreconnect_changed(self, value): + value = bool(value) + if self.autoreconnect_flag != value: + self.autoreconnect_flag = value + if value and self.get_autoreconnect() == 0: + self.set_autoreconnect(2) + elif not value and self.get_autoreconnect() ==2: + self.set_autoreconnect(1) def indicator_changed(self, name, val): + #print "Indicator %s -> %d" % (name, int(val)) if name == 'service': self.noservice = not val - self.set_state_labels() + self.disp.set_state() + elif name == 'signal': + self.signal = int(val) + self.disp.set_state() + elif name == 'battchg': + self.battery = int(val) + self.disp.set_state() + elif name == 'voice mail': + self.vm = int(val) + self.disp.set_state() + elif name == 'message': + self.vm = int(val) + self.disp.set_state() def alert_close(self): if self.alerter: @@ -685,20 +1008,14 @@ class HfConsole: def waiting_caller_info(self): return (self.caller_id, self.caller_phbook) - def connect_clicked(self, widget): + def connect(self): self.ag.Connect() - def not_mine(self): - self.add_but.show() - def add_clicked(self, widget): - self.hfc.ag_add_to_config(self.addr) - widget.hide() - def select_toggled(self, widget): - if hasattr(self, 'nested'): - return - self.hfc.ag_select(self, widget.get_active()) - + def disconnect(self): + self.ag.Disconnect() def command_failed(self, reason): print _("Command failed: %s") % reason + def open_audio(self): + self.ag.OpenAudio() def close_audio(self): self.ag.CloseAudio() def dial(self, number): @@ -733,32 +1050,239 @@ class HfConsole: self.ag.SendDtmf(char[0], reply_handler=lambda : None, error_handler=self.command_failed) - def remove_clicked(self, widget): - if self.hfc.configfile.remove_option('devices', - self.hfc.addr_transform(self.addr)): - self.hfc.configfile.write(open( - self.hfc.configfile_name, 'w+')) - self.setprop('Known', False) + def do_remove(self): + self.hfc.ag_remove_from_config(self.addr) + if self.hfc.ags_added.has_key(self.addr): + del self.hfc.ags_added[self.addr] + x = self.setprop('Known', False) self.hfpd.RemoveDevice(self.addr) def remove(self): - self.frame.get_parent().remove(self.frame) - del self.frame + self.disp.remove() + del self.disp while self.sigs: sig = self.sigs.pop() sig.remove() def getprop(self, prop): return self.agprops.Get( - 'net.sf.nohands.hfpd.AudioGateway', + self.hfc.HFPD_AUDIOGATEWAY_INTERFACE_NAME, prop) def setprop(self, prop, val): return self.agprops.Set( - 'net.sf.nohands.hfpd.AudioGateway', + self.hfc.HFPD_AUDIOGATEWAY_INTERFACE_NAME, prop, val) + class AgStateDisplay: + def __init__(self, hf, ag): + self.hf = hf + self.ag = ag + self.item = None + self.row_detail = None + self.row_new = None + def selector_toggle(self, widget): + if widget.get_active(): + self.hf.ag_selector_changed(self.ag) + def remove_item(self): + if self.item: + self.hf.ag_list.remove(self.item) + self.item = None + kids = self.hf.ag_list.get_children() + count = len(kids) + if count == 3: + self.hf.ag_list_separator.hide() + def set_name(self, name): + if not name: + self.remove_item() + if self.row_detail: + self.hf.ag_list_detail.remove( + self.row_detail) + self.row_detail = None + if self.row_new: + self.hf.ag_list_new.remove( + self.row_new) + self.row_new = None + return + + if not self.ag.is_mine(): + if not self.row_new: + self.row_new = (self.hf.ag_list_new. + append( + row=[name, self.ag.addr, self])) + else: + self.hf.ag_list_new.set(self.row_new, + 0, name) + return + + self.remove_item() + group = None + kids = self.hf.ag_list.get_children() + count = len(kids) + if count > 3: + group = kids[0] + self.hf.ag_list_separator.show() + self.item = gtk.RadioMenuItem(group, name) + self.item.connect('toggled', self.selector_toggle) + self.item.show_all() + if self.ag == self.hf.selected_ag: + self.item.set_active(True) + self.hf.ag_list.insert(self.item, count - 3) + if self.ag == self.hf.selected_ag: + self.hf['AgSelectorLabel'].set_text(name) + + if not self.row_detail: + self.row_detail = (self.hf.ag_list_detail. + append( + row=[name, self.ag.addr, self])) + else: + self.hf.ag_list_detail.set(self.row_detail, + 0, name) + def remove(self): + if self.ag == self.hf.selected_ag: + self.hf.selected_ag = None + self.hf.ag_none_selected() + self.ag = None + self.set_name(None) + + def do_mainwindow_state(self, string): + self.hf['AudioGatewayStateLabel'].set_markup( + string) + def mainwindow_state(self): + if self.hf.selected_ag != self.ag: + return + state = self.ag.state + callstate = self.ag.callstate + lab = 'Invalid State' + if not self.ag.is_connected(): + if state == 1: + lab = _('Destroyed') + if state == 2: + lab = _('Detached') + if state == 3: + lab = _('Attaching') + lab += '\n' + else: + if callstate == 1: + lab = _('Ready') + if not self.ag.has_service(): + lab = _('No Service') + if callstate == 2: + lab = _('Dialing') + if callstate == 3: + lab = _('Call Established') + if callstate == 4: + lab = _('Ringing') + if callstate == 5: + lab = _('Waiting Call') + lab += '\n' + if self.ag.has_audio(): + lab += _('Audio Open') + elif self.ag.audio_connecting(): + lab += _('Audio Connecting') + self.do_mainwindow_state(lab) + self.hf.ag_configure_gui(self.ag) + + def devices_state(self): + if self.hf.devices_ag != self.ag: + return + self.hf.nested = True + self.hf['DevicesProperties'].set_sensitive(True) + self.hf['DevicesDevAddr'].set_text(self.ag.addr) + v = self.ag.get_autoreconnect() + if v == 0: v = 'DevicesAutoConnectNever' + elif v == 1: v = 'DevicesAutoConnectDisabled' + else: v = 'DevicesAutoConnectEnabled' + self.hf[v].set_active(True) + if self.ag.is_connected(): + self.hf['DevicesConnect'].hide() + self.hf['DevicesDisconnect'].show() + else: + self.hf['DevicesConnect'].show() + self.hf['DevicesConnect'].set_sensitive( + not self.ag.is_connecting()) + self.hf['DevicesDisconnect'].hide() + self.hf.nested = False + + def update_state(self): + self.mainwindow_state() + self.devices_state() + + def set_state(self): + self.hf.ag_state_changed(self.ag) + self.update_state() + + def ag_disp_new(self, ag): + disp = self.AgStateDisplay(self, ag) + return disp + + def ag_selector_set(self, ag): + do_arrow = False + if ag: + lab = ag.name + ag.disp.item.set_active(True) + self['AgSelectorArrow'].show() + else: + lab = _('Search for Device...') + self['AgSelectorArrow'].hide() + self['AgSelectorLabel'].set_text(lab) + def ag_selector_menu_position(self, menu): + widget = self['AgSelector'] + alloc = widget.get_allocation() + (x, y) = widget.get_parent_window().get_origin() + x += alloc.x + y += alloc.y + alloc.height + return (x, y, True) + def ag_selector_open(self, widget, event): + kids = self.ag_list.get_children() + count = len(kids) + if count == 3: + return + self.nested = True + self['AgSelector'].set_active(True) + self.nested = False + self.ag_list.popup(None, None, + self.ag_selector_menu_position, + event.button, + event.get_time()) + + def ag_selector_close(self, widget): + self.nested = True + self['AgSelector'].set_active(False) + self.nested = False + def ag_selector_toggle(self, widget): + if self.nested: + return + self.nested = True + if widget.get_active(): + widget.set_active(False) + self.scan_open() + self.nested = False + def ag_selector_scan_activate(self, widget): + self.scan_open() + def ag_selector_manage_activate(self, widget): + self.devices_open() + def ag_selector_changed(self, ag): + if not ag.selectable(): + ag = None + if self.selected_ag and self.selected_ag.selectable(): + self.selected_ag.item.set_active(True) + ag = self.selected_ag + self.ag_selector_set(ag) + return + + # If the user explicitly picks an audio gateway of lesser + # priority than the currently selected AG, consider the + # selection to be locked to that audio gateway. + lock = False + if self.selected_ag and (self.selected_ag.select_priority() > + ag.select_priority()): + lock = True + self.ag_select_changed(ag, lock) + def hfpd_lost(self, name, old_owner, new_owner): - if name != 'net.sf.nohands.hfpd' or new_owner != '': + if name != self.HFPD_SERVICE_NAME or new_owner != '': return - self.fatal(_('HFPD Process Disconnected')) + msg = _('HFPD Process Disconnected') + print msg + self.fatal(msg) def hfpd_ag_added(self, agpath): if agpath not in self.ags: @@ -768,21 +1292,36 @@ class HfConsole: agpath, self['AudioGatewayBox']) self.ags[agpath] = ag - self.ag_mine(ag) + if ag.is_mine(): + ag.load_config() + else: + self.newdevice_open() + if (self.ags_added.has_key(ag.addr) and + self.ags_added[ag.addr] == 2): + if (not self.selected_ag or + not self.selected_ag.wants_audio()): + self.ags_added[ag.addr] = 1 + self.ag_select_changed(ag) def hfpd_ag_removed(self, agpath): if agpath in self.ags: self.ags[agpath].remove() del self.ags[agpath] - def ag_select_search(self): - for xag in self.ags.values(): - if xag.has_audio() and xag.select(True): - self.ag_select_changed(xag) - return + if not self.selected_ag: + self.ag_none_selected() + def ag_none_selected(self): + (ag, prio) = self.ag_best() + self.ag_select_changed(ag) + def ag_best(self): + best_ag = None + best_prio = -1 for xag in self.ags.values(): - if xag.select(True): - self.ag_select_changed(xag) - return + if xag.selectable() and ( + not best_ag or (best_prio < + xag.select_priority())): + best_ag = xag + best_prio = xag.select_priority() + return (best_ag, best_prio) def ag_stop_audio_unselected(self): for xag in self.ags.values(): if xag != self.selected_ag and xag.has_audio(): @@ -808,58 +1347,66 @@ class HfConsole: self.selected_ag.close_audio() self.ag_stop_audio_unselected() def ag_state_changed(self, ag): - if ((not ag.selectable() and ag == self.selected_ag) or - (ag.has_audio() and self.selected_ag and - not self.selected_ag.has_audio())): - ag.select(False) - self.ag_select_changed(None) - - if not self.selected_ag: - # Find another AG and select it - self.ag_select_search() - # If we still didn't find one, stop audio. - if not self.selected_ag: - self.ag_stop_audio_unselected() - elif ag == self.selected_ag: - # Maybe update buttons to reflect state - self.ag_select_changed(ag) - - def ag_select(self, ag, state): - if ((not state and ag == self.selected_ag) or - (state and ag != self.selected_ag)): - ag.select(False) - self.ag_select_changed(None) - if state and ag.select(True): - self.ag_select_changed(ag) + #print "AG state changed %s %slock" % (ag.name, self.selected_ag_lock) + if ag and ag == self.selected_ag: + self.ag_push_audio() + if self.selected_ag_lock and self.selected_ag.selectable(): + self.ag_stop_audio_unselected() + return + # Evaluate all AGs and maybe change selection + (best, prio) = self.ag_best() + if (not self.selected_ag or + not self.selected_ag.selectable() or + (self.selected_ag.select_priority() < prio)): + self.ag_select_changed(best) if not self.selected_ag: - self.ag_select_search() + self.ag_stop_audio_unselected() - def ag_select_changed(self, ag): - if self.selected_ag: - if self.selected_ag != ag: - self.selected_ag.select(False) - self.selected_ag = ag + def ag_select_changed(self, ag, lock = False): if not ag: - self.command_mode(0) - elif ag.can_dial(): - self.command_mode(1) - elif ag.can_dtmf(): - self.command_mode(2) + lock = False + elif not lock and self.selected_ag == ag: + lock = self.selected_ag_lock + self.selected_ag = ag + self.selected_ag_lock = lock + self.ag_selector_set(ag) + if ag: + ag.disp.update_state() else: - self.command_mode(0) + self.ag_configure_gui(None) if ag: self.ag_push_audio() - def ag_mine(self, ag): - if self.ags_added.has_key(ag.addr): - ag.setprop('AutoReconnect', self.autoreconnect) - else: - ag.not_mine() + def ag_config_has_option(self, addr, name): + section = 'ag_' + self.addr_transform(addr) + if not self.configfile.has_section(section): + return False + return self.configfile.has_option(section, name) + + def ag_config_get_option(self, addr, name): + section = 'ag_' + self.addr_transform(addr) + if not self.configfile.has_section(section): + return None + return self.configfile.get(section, name, raw=True) + + def ag_config_set_option(self, addr, name, value): + section = 'ag_' + self.addr_transform(addr) + if not self.configfile.has_section(section): + self.configfile.add_section(section) + self.configfile.set(section, name, value) + self.configfile.write(open(self.configfile_name, 'w+')) def ag_add_to_config(self, addr): self.configfile.set('devices', self.addr_transform(addr), True) self.configfile.write(open(self.configfile_name, 'w+')) + def ag_remove_from_config(self, addr): + self.configfile.remove_option('devices', + self.addr_transform(addr)) + section = 'ag_' + self.addr_transform(addr) + self.configfile.remove_section(section) + self.configfile.write(open(self.configfile_name, 'w+')) + def ag_update_history(self, addr, name): addrx = self.addr_transform(addr) has = self.configfile.has_option('history', addrx) @@ -872,9 +1419,19 @@ class HfConsole: self.configfile.set('history', addrx, name) self.configfile.write(open(self.configfile_name, 'w+')) - def add_audiogateway(self, addr): + def ag_history_getname(self, addr): + addrx = self.addr_transform(addr) + if not self.configfile.has_option('history', addrx): + return None + return self.configfile.get('history', addrx) + + def add_audiogateway(self, addr, selectit): + agpath = None try: - self.ags_added[addr] = True + if selectit: + self.ags_added[addr] = 2 + else: + self.ags_added[addr] = 1 # We will use the accompanying AudioGatewayAdded # signal to construct the AudioGateway object above agpath = self.hfpd.AddDevice(addr, False) @@ -910,42 +1467,179 @@ class HfConsole: def phone_num_bs(self, but): entry = self['PhoneNumEntry'] entry.set_text(entry.get_text()[:-1]) - def command_mode(self, val): - if val == 0: - self['DigitButtonBox'].set_sensitive(False) - self['PhoneNumEntryBox'].set_sensitive(False) - self['DialCommandBox'].set_sensitive(False) - self['DialPadDial'].set_label('Dial') - elif val == 1: - self['DigitButtonBox'].set_sensitive(True) - self['PhoneNumEntryBox'].set_sensitive(True) - self['DialCommandBox'].set_sensitive(True) - self['DialPadRedial'].set_sensitive(True) - self['DialPadDial'].set_label('Dial') - elif val == 2: - self['DigitButtonBox'].set_sensitive(True) - self['PhoneNumEntryBox'].set_sensitive(False) - self['DialCommandBox'].set_sensitive(True) - self['DialPadRedial'].set_sensitive(False) - self['DialPadDial'].set_label('Hang Up') - def dial_dial_clicked(self, widget): + def ag_configure_gui(self, ag): + sel_dis = False + connect = False + connect_ip = False + digits = False + do_notebook = False + dial = False + hold = False + swap = False + hangup = False + audio = False + audio_ip = False + audio_dn = False + record = False + record_dn = False + mainstatus = False + status_page = 0 + battstatus = False + batt_page = 0 + message = False + if ag: + mainstatus = True + if not ag.is_connected(): + connect = True + connect_ip = ag.is_connecting() or self.nobt + status_page = 8 + else: + status_page = 7 + if ag.has_service(): + status_page = ag.signal_level() + if status_page < 0 or status_page > 5: + status_page = 6 + digits = ag.can_dial() or ag.can_dtmf() + dial = ag.can_dial() + hold = ag.can_hold() + do_notebook = not ag.can_dial() + hangup = not dial + if hold: + swap = ag.has_waiting_call() + hold = not swap + audio = True + if ag.has_audio(): + audio_dn = True + elif ag.audio_connecting(): + audio_ip = True + message = ag.has_message() + batt_page = ag.battery_level() + battstatus = True + if batt_page < 0 or batt_page > 5: + batt_page = 0 + battstatus = False + + # Display the "no bluetooth" icon if BT is disconnected + # and the AG is not connected + if self.nobt and (not ag or (not ag.is_connected() and + not ag.is_connecting())): + mainstatus = True + status_page = 9 + sel_dis = True + + def set_vis(widget, visible): + if visible: widget.show() + else: widget.hide() + + self['AgSelector'].set_sensitive(not sel_dis) + self['DigitButtonBox'].set_sensitive(digits) + self['PhoneNumEntryBox'].set_sensitive(dial) + set_vis(self['AgConnect'], connect) + self['AgConnect'].set_sensitive(not connect_ip) + set_vis(self['AgHangUp'], hangup) + set_vis(self['AgHoldCall'], hold) + set_vis(self['AgSwapCall'], swap) + set_vis(self['AgDial'], dial) + set_vis(self['AgRedial'], dial) + set_vis(self['AgAudioToggle'], audio) + self['AgAudioToggle'].set_active(audio_dn) + self['AgAudioToggle'].set_sensitive(not audio_ip) + set_vis(self['RecordCall'], record) + self['RecordCall'].set_active(record_dn) + set_vis(self['AgMainStatus'], mainstatus) + self['AgMainStatus'].set_current_page(status_page) + set_vis(self['AgBatteryStatus'], battstatus) + self['AgBatteryStatus'].set_current_page(batt_page) + set_vis(self['MessageIndicator'], message) + if do_notebook: + self.frontnotebook_set(0) + else: + self.frontnotebook_set(self.frontpage) + + def bar_ag_dial_clicked(self, widget): ag = self.selected_ag entry = self['PhoneNumEntry'] if not ag: return if not ag.can_dial(): - ag.hangup() return num = entry.get_text() entry.set_text('') ag.dial(num) - def dial_redial_clicked(self, widget): + + def bar_ag_scan_clicked(self, widget): + self.scan_open() + def bar_ag_connect_clicked(self, widget): + try: + if self.selected_ag: + self.selected_ag.connect() + except dbus.exceptions.DBusException, (ex): + self.error_message( + _('Could not attach Audio Gateway: %s') % + str(ex)) + + def frontnotebook_set(self, page): + dialpad_but = False + contacts_but = False + if page == 0: + #contacts_but = True + if (self.selected_ag and + self.selected_ag.is_connected() and + not self.selected_ag.can_dial()): + contacts_but = False + if page == 1: + dialpad_but = True + + def set_vis(widget, visible): + if visible: widget.show() + else: widget.hide() + + self['FrontNotebook'].set_current_page(page) + set_vis(self['BarDialPad'], dialpad_but) + set_vis(self['BarContacts'], contacts_but) + + def bar_dialpad_clicked(self, widget): + self.frontpage = 0 + self.frontnotebook_set(0) + def bar_contacts_clicked(self, widget): + self.frontpage = 1 + self.frontnotebook_set(1) + + def bar_ag_hangup_clicked(self, widget): + ag = self.selected_ag + if not ag: + return + ag.hangup() + def bar_ag_swaphold_clicked(self, widget): + ag = self.selected_ag + if not ag or not ag.can_hold(): + return + ag.swap_hold_active() + def bar_ag_redial_clicked(self, widget): ag = self.selected_ag if not ag or not ag.can_dial(): return ag.redial() - def dial_xfer_clicked(self, widget): - pass + def bar_ag_audio_toggled(self, widget): + if not self.selected_ag: + return + if not widget.get_active(): + if (self.selected_ag.has_audio() or + self.selected_ag.audio_connecting()): + self.selected_ag.close_audio() + return + if (self.selected_ag.has_audio() or + self.selected_ag.audio_connecting()): + return + try: + self.selected_ag.open_audio() + except dbus.exceptions.DBusException, (ex): + self.error_message( + _('Could not open audio connection: %s') % + str(ex)) + widget.set_active(False) + def bar_config_clicked(self, widget): + self.config_open() def hfpd_system_msg_clear(self): if hasattr(self, 'system_msg'): self['StatusBar'].remove(self.system_ctx, @@ -954,6 +1648,7 @@ class HfConsole: if hasattr(self, 'system_msg_timeout'): gobject.source_remove(self.system_msg_timeout) del self.system_msg_timeout + return False def hfpd_system_msg(self, msg, timeout): self.hfpd_system_msg_clear() self.system_msg = self['StatusBar'].push(self.system_ctx, msg) @@ -963,59 +1658,173 @@ class HfConsole: def hfpd_system_state(self, state): if not state: + self.nobt = True self.hfpd_system_msg(_('Bluetooth Unavailable'), None) else: + self.nobt = False self.hfpd_system_msg_clear() + self.ag_configure_gui(self.selected_ag) + + def topnotebook_set(self, page): + nb = self['HiddenNotebook'] + if not hasattr(self, 'notebook_stack'): + self.notebook_stack = [] + if page: + self.notebook_stack.append(nb.get_current_page()) + nb.set_current_page(nb.page_num(page)) + elif len(self.notebook_stack): + nb.set_current_page(self.notebook_stack.pop()) + else: + nb.set_current_page(0) + def topnotebook_get(self): + nb = self['HiddenNotebook'] + return nb.get_nth_page(nb.get_current_page()) + + def devices_open(self): + self.topnotebook_set(self['DevicesTab']) + def devices_close_clicked(self, widget): + self.topnotebook_set(None) + def devices_add_clicked(self, widget): + self.scan_open() + def devices_delete_clicked(self, widget): + if self.devices_ag: + self.ag_remove_from_config(self.devices_ag.addr) + self.devices_ag.do_remove() + def devices_connect_clicked(self, widget): + if self.devices_ag: + self.devices_ag.connect() + def devices_disconnect_clicked(self, widget): + if self.devices_ag: + self.devices_ag.disconnect() + def devices_autoconnect_toggled(self, widget, value): + if self.nested or not widget.get_active(): + return + if self.devices_ag: + self.devices_ag.set_autoreconnect(value) + def devices_autoconnect_never_toggled(self, widget): + self.devices_autoconnect_toggled(widget, 0) + def devices_autoconnect_disabled_toggled(self, widget): + self.devices_autoconnect_toggled(widget, 1) + def devices_autoconnect_enabled_toggled(self, widget): + self.devices_autoconnect_toggled(widget, 2) + def devices_select(self, selection): + if not selection.count_selected_rows(): + self.devices_ag = None + self['DevicesDevAddr'].set_text('') + self['DevicesConnect'].hide() + self['DevicesDisconnect'].show() + self['DevicesProperties'].set_sensitive(False) + return + (store, iter) = selection.get_selected() + disp = store.get_value(iter, 2) + self.devices_ag = disp.ag + disp.devices_state() + self['DevicesProperties'].show() + + def newdevice_open(self): + tab = self['NewDeviceTab'] + if self.topnotebook_get() == tab: + return + itx = self.ag_list_new.get_iter_root() + if itx: + self['NewDeviceList'].get_selection().select_iter(itx) + self.topnotebook_set(tab) + def newdevice_close_clicked(self, widget): + self.topnotebook_set(None) + def newdevice_add_clicked(self, widget): + ag = self.newdevice_ag + self.add_audiogateway(ag.addr, False) + ag.update_mine() + self.ag_add_to_config(ag.addr) + ag.set_autoreconnect(2) + self.ag_select_changed(ag) + def newdevice_clear_clicked(self, widget): + xiter = self.ag_list_new.get_iter_root() + while xiter: + disp = self.ag_list_new.get_value(xiter, 2) + disp.ag.do_remove() + xiter = self.ag_list_new.iter_next(xiter) + def newdevice_select(self, selection): + if not selection.count_selected_rows(): + self['NewDeviceAdd'].set_sensitive(False) + return + (store, iter) = selection.get_selected() + disp = store.get_value(iter, 2) + self.newdevice_ag = disp.ag + self['NewDeviceAdd'].set_sensitive(True) + def newdevice_row_added(self, list, path, iter): + self['NewDeviceClear'].set_sensitive(True) + def newdevice_row_removed(self, list, path): + if not list.get_iter_root(): + self['NewDeviceClear'].set_sensitive(False) + tab = self['NewDeviceTab'] + if self.topnotebook_get() == tab: + self.topnotebook_set(None) + + def inquiry_show_activity(self): + self['ScanActivityIndicator'].pulse() + return True + def inquiry_started(self): + self.inquiry = True + self['ScanRestart'].set_sensitive(False) + self['ScanActivityIndicator'].set_fraction(0) + self['ScanActivity'].show() + self.inquiry_animation_timeout = gobject.timeout_add( + 175, self.inquiry_show_activity) + + def inquiry_stopped(self): + self.inquiry = False + self['ScanActivity'].hide() + self['ScanRestart'].set_sensitive(True) + if hasattr(self, 'inquiry_animation_timeout'): + gobject.source_remove(self.inquiry_animation_timeout) + del self.inquiry_animation_timeout def scan_restart(self): self.scanresults.clear() - self['ScanOK'].set_sensitive(False) + self['ScanAdd'].set_sensitive(False) try: self.hfpd.StartInquiry() except: return False - self.inquiry = True - self['ScanRestart'].set_sensitive(False) + self.inquiry_started() return True - def scan_open(self, but): - dlg = self['ScanDialog'] + def scan_open(self): if not self.scan_restart(): - self.error_message(_('Could not start inquiry'), - self['ScanDialog']) + self.error_message(_('Could not start inquiry')) return - dlg.show() + self.topnotebook_set(self['ScanTab']) def scan_close(self): - dlg = self['ScanDialog'] - dlg.hide() + self.topnotebook_set(None) if self.inquiry: try: self.hfpd.StopInquiry() except: pass - self.inquiry = False + self.inquiry_stopped() def scan_create_session(self, store, iter): # Create a session for the selected bdaddr self.scan_close() assert store and iter addr = store.get_value(iter, 2) self.ag_add_to_config(addr) - self.add_audiogateway(addr) - def scan_ok_clicked(self, widget): + self.ag_config_set_option(addr, 'autoreconnect', 2) + self.add_audiogateway(addr, True) + def scan_add_clicked(self, widget): assert (self.scanselect.count_selected_rows() <= 1) (store, iter) = self.scanselect.get_selected() self.scan_create_session(store, iter) - def scan_cancel_clicked(self,widget): + def scan_close_clicked(self,widget): self.scan_close() def scan_history_clicked(self, widget): self.history_open() def scan_restart_clicked(self, widget): if not self.scan_restart(): - self.error_message(_('Could not start inquiry'), - self['ScanDialog']) + self.error_message(_('Could not start inquiry')) def scan_select(self, selection): if not selection.count_selected_rows(): - self['ScanOK'].set_sensitive(False) + self['ScanAdd'].set_sensitive(False) else: - self['ScanOK'].set_sensitive(True) + self['ScanAdd'].set_sensitive(True) class NameResolveReq: def __init__(self, hfc, addr, store): self.addr = addr @@ -1047,8 +1856,7 @@ class HfConsole: return if not self.inquiry: return - self.inquiry = False - self['ScanRestart'].set_sensitive(True) + self.inquiry_stopped() ix = self.scanresults.get_iter_first() while ix: addr = self.scanresults.get_value(ix, 2) @@ -1060,7 +1868,6 @@ class HfConsole: # History box def history_open(self): - dlg = self['HistoryDialog'] self.historyresults.clear() self['HistoryOK'].set_sensitive(False) @@ -1074,10 +1881,9 @@ class HfConsole: else: name = "%s (%s)" % (name, addr) self.historyresults.append(row=[name, 0, addr]) - dlg.show() + self.topnotebook_set(self['HistoryTab']) def history_close(self): - dlg = self['HistoryDialog'] - dlg.hide() + self.topnotebook_set(None) def history_ok_clicked(self, widget): self.history_close() assert self.historyselect.count_selected_rows() <= 1 @@ -1092,10 +1898,10 @@ class HfConsole: self['HistoryOK'].set_sensitive(True) # Configuration dialog related methods - def config_open(self, widget): - dlg = self['ConfigDialog'] + def config_open(self): savecfg = self.config_get_vals() savecfg['packetintervalsave'] = savecfg['packetinterval'] + savecfg['jitterwindowsave'] = savecfg['jitterwindow'] savecfg['minbufferfillsave'] = savecfg['minbufferfill'] savecfg['echocancelsave'] = savecfg['echocancel'] if savecfg['echocancelsave'] == 0: @@ -1113,8 +1919,6 @@ class HfConsole: savecfg['acceptunknown']) self['ConfigVoicePersist'].set_active( savecfg['voicepersist']) - self['ConfigAutoReconnect'].set_active( - savecfg['autoreconnect']) self['ConfigDriver'].get_model().clear() for x in savecfg['drivers']: self['ConfigDriver'].append_text(x) @@ -1122,6 +1926,7 @@ class HfConsole: self['ConfigDriverDevList'].child.set_text( savecfg['driveropts']) self.config_packet_interval(savecfg['packetinterval']) + self.config_jitter_window(savecfg['jitterwindow']) self.config_min_out_buffer(savecfg['minbufferfill']) self['ConfigDenoise'].set_active(savecfg['denoise']) self.config_echo_cancel(savecfg['echocancel']) @@ -1149,35 +1954,40 @@ class HfConsole: if cmd: self['ConfigAudioDetachCommand'].set_text(cmd) self.config_savecfg = savecfg - dlg.show() + self.topnotebook_set(self['ConfigTab']) def config_close(self): - dlg = self['ConfigDialog'] - dlg.hide() + self['ConfigFeedbackTest'].set_active(False) del self.config if hasattr(self, 'loopback_active'): del self.loopback_active if hasattr(self, 'config_savecfg'): del self.config_savecfg + self.topnotebook_set(None) def config_ok_clicked(self, widget): try: self.config_save_vals() except dbus.exceptions.DBusException, (ex): - self.error_message(ex.get_dbus_message()) + self.error_message(str(ex)) self.config_close() def config_cancel_clicked(self, widget): # Restore the save values try: self.config_restore_vals(self.config_savecfg) except dbus.exceptions.DBusException, (ex): - self.error_message(ex.get_dbus_message()) + self.error_message(str(ex)) self.config_close() + def config_init_clicked(self, widget): + self.initconf_open() + def config_devices_clicked(self, widget): + self.devices_open() def config_get_vals(self): - spr = self.soundio_props.GetAll('net.sf.nohands.hfpd.SoundIo') - hfpr = self.hfpd_props.GetAll('net.sf.nohands.hfpd.HandsFree') + spr = self.soundio_props.GetAll( + self.HFPD_SOUNDIO_INTERFACE_NAME) + hfpr = self.hfpd_props.GetAll( + self.HFPD_HANDSFREE_INTERFACE_NAME) vals = dict() vals['secmode'] = int(hfpr['SecMode']) vals['acceptunknown'] = hfpr['AcceptUnknown'] vals['voicepersist'] = hfpr['VoicePersist'] - vals['autoreconnect'] = self.autoreconnect vals['drivers'] = [] vals['driver'] = 0 index = 0 @@ -1189,6 +1999,7 @@ class HfConsole: index += 1 vals['driveropts'] = str(spr['DriverOpts']) vals['packetinterval'] = int(spr['PacketIntervalHint']) + vals['jitterwindow'] = spr['JitterWindowHint'] vals['minbufferfill'] = spr['MinBufferFillHint'] vals['denoise'] = spr['Denoise'] vals['echocancel'] = int(spr['EchoCancelTail']) @@ -1214,7 +2025,6 @@ class HfConsole: self['ConfigDriverDevList'].child. get_text()) self.hfpd.SaveSettings() - self.autoreconnect = self['ConfigAutoReconnect'].get_active() cmd = self['ConfigAudioAttachCommand'].get_text() self.command_audio_attach_acmd = cmd self.configfile.set('options', 'attach_command', cmd) @@ -1229,6 +2039,8 @@ class HfConsole: self.config['driveropts']) self.setprop_soundio('PacketIntervalHint', dbus.UInt32(vals['packetinterval'])) + self.setprop_soundio('JitterWindowHint', + dbus.UInt32(vals['jitterwindow'])) self.setprop_soundio('MinBufferFillHint', dbus.UInt32(vals['minbufferfill'])) self.setprop_soundio('Denoise', dbus.Boolean(vals['denoise'])) @@ -1304,6 +2116,41 @@ class HfConsole: value = self.value_transform(widget.get_value(), 3, 5, 1000) if not self.nested: self.config_packet_interval(value) + def config_jitter_window(self, value): + self.nested = True + if value != 0: + value = self.constrain(value, 5, 1000) + self['ConfigJitterWindowHint'].set_active(True) + self['ConfigJitterWindowLabel'].set_text( + self.format_ms(value)) + self['ConfigJitterWindow'].set_sensitive(True) + self['ConfigJitterWindow'].set_value( + self.value_untransform(value, 3, 5, 1000)) + else: + value = 0 + self['ConfigJitterWindowHint'].set_active(False) + self['ConfigJitterWindowLabel'].set_text('') + self['ConfigJitterWindow'].set_sensitive(False) + self['ConfigJitterWindow'].set_value(0) + if self.config['jitterwindow'] != value: + self.config['jitterwindow'] = value + self.setprop_soundio('JitterWindowHint', + dbus.UInt32(value)) + self.nested = False + def config_jitter_window_toggled(self, widget): + if widget.get_active(): + value = self.config['jitterwindowsave'] + value = self.constrain(value, 5, 1000) + else: + self.config['jitterwindowsave'] = ( + self.config['jitterwindow']) + value = 0 + if not self.nested: + self.config_jitter_window(value) + def config_jitter_window_changed(self, widget): + value = self.value_transform(widget.get_value(), 3, 5, 1000) + if not self.nested: + self.config_jitter_window(value) def config_min_out_buffer(self, value): self.nested = True if value != 0: @@ -1353,8 +2200,7 @@ class HfConsole: except: widget.set_active(False) self.error_message(_('Cannot Apply ' - 'Sound Driver Settings'), - self['ConfigDialog']) + 'Sound Driver Settings')) return try: @@ -1366,8 +2212,7 @@ class HfConsole: widget.set_active(False) self.error_message(_('Could not open ' 'Audio Device:\n' - '%s') % str(ex), - self['ConfigDialog']) + '%s') % str(ex)) return self.loopback_active = True @@ -1391,6 +2236,7 @@ class HfConsole: if hasattr(self, 'config_skew_timeout'): gobject.source_remove(self.config_skew_timeout) del self.config_skew_timeout + return False def config_skew_notify(self, skewtype, count): timeout = 1500 if skewtype == 0: @@ -1505,7 +2351,7 @@ class HfConsole: def config_dsptest_pageid(self, widget): - return int(widget.get_name()[11:]) + return widget.get_current_page() def config_dsptest_open(self, widget): self['ConfigFeedbackTest'].set_active(False) try: @@ -1515,12 +2361,12 @@ class HfConsole: get_text()) except: self.error_message(_('Cannot Apply Sound Driver ' - 'Settings'), - self['ConfigDialog']) + 'Settings')) return - assist = self['DspTestAssistant'] - assist.set_current_page(0) - assist.show() + self['DspTestNotebook'].set_current_page(1) + self['DspTestNotebook'].set_current_page(0) + self.topnotebook_set(self['DspTestTab']) + self['DspTestForward'].grab_focus() def connect_soundmonitor(self, to): if self.soundmonitor: self.soundmonitor.remove() @@ -1540,47 +2386,52 @@ class HfConsole: pow(float(amp) / 65535, 0.33)) def config_dsptest_mon3(self, pos, amp): self['DspTestPlayPosition'].set_fraction(float(pos)/80000) - def config_dsptest_prepare(self, widget, page): - assist = self['DspTestAssistant'] - pageid = self.config_dsptest_pageid(page) + def config_dsptest_set_buttons(self, back, fwd): + self['DspTestBack'].set_sensitive(back) + self['DspTestForward'].set_sensitive(fwd) + def config_dsptest_back(self, widget): + self['DspTestNotebook'].prev_page() + def config_dsptest_forward(self, widget): + self['DspTestNotebook'].next_page() + def config_dsptest_setup(self, pageid): if not hasattr(self, 'membuf_active'): self.membuf_active = False if self.membuf_active: self.soundio.Stop() if pageid == 0: self.maxvol = 0 - assist.set_page_complete(page, True) + self.config_dsptest_set_buttons(False, True) elif pageid == 1: - assist.set_page_complete(page, False) + self.config_dsptest_set_buttons(True, False) self['DspTestRecPosition1'].set_fraction(0.0) self.connect_soundmonitor(self.config_dsptest_mon1) self['DspTestRecStart1'].set_active(True) elif pageid == 2: + self.config_dsptest_set_buttons(True, True) self['DspTestRecPosition2'].set_fraction(0.0) - assist.set_page_complete(page, True) self.connect_soundmonitor(self.config_dsptest_mon2) elif pageid == 3: + self.config_dsptest_set_buttons(True, True) self['DspTestPlayPosition'].set_fraction(0.0) - assist.set_page_complete(page, True) self.connect_soundmonitor(self.config_dsptest_mon3) self['DspTestPlayStart'].set_active(True) - def config_dsptest_cancel(self, widget): - assist = self['DspTestAssistant'] - assist.hide() - if self.membuf_active: - self.soundio.Stop() - self.connect_soundmonitor(None) - if hasattr(self, 'maxvol'): - del self.maxvol + else: + self.config_dsptest_set_buttons(True, False) + def config_dsptest_close(self, widget): + self['DspTestRecStart1'].set_active(False) + self['DspTestRecStart2'].set_active(False) + self['DspTestPlayStart'].set_active(False) + self.topnotebook_set(None) if hasattr(self, 'membuf_active'): + if self.membuf_active: + self.soundio.Stop() del self.membuf_active - def config_dsptest_close(self, widget): - assist = self['DspTestAssistant'] - assist.hide() self.connect_soundmonitor(None) - print "DSP close" + if hasattr(self, 'maxvol'): + del self.maxvol + def config_dsptest_switch_page(self, widget, page, page_num): + self.config_dsptest_setup(page_num) def config_dsptest_xxx_toggled(self, widget, page, doin, doout): - assist = self['DspTestAssistant'] if widget.get_active(): if doin and not doout: self.soundio.MembufClear() @@ -1593,12 +2444,11 @@ class HfConsole: except: widget.set_active(False) self.error_message(_('Could not open ' - 'Audio Device'), - self['DspTestAssistant']) + 'Audio Device')) return self.membuf_active = True - assist.set_page_complete(page, True) + self.config_dsptest_set_buttons(True, True) return if self.membuf_active: try: @@ -1642,7 +2492,7 @@ class HfConsole: if agpath: ag = self.ags[agpath] if ag: - self.ag_select(ag, True) + ag.soundio_state_changed() def soundio_stream_aborted(self, excode, descr): if (excode == 'net.sf.nohands.hfpd.Error.SoundIoSoundCardFailed'): @@ -1667,6 +2517,7 @@ class HfConsole: del self.soundio_status_timeout if hasattr(self, 'soundio_skew_type'): del self.soundio_skew_type + return False def soundio_status_msg(self, msg, timeout): self.soundio_status_msg_clear() self.soundio_status_msgid = self['StatusBar'].push( @@ -1695,8 +2546,29 @@ class HfConsole: if self.soundio_skew_monitor: self.soundio_skew_monitor(skewtype, count) + def initconf_open(self): + self['InitConfNotebook'].set_current_page(1) + self['InitConfNotebook'].set_current_page(0) + self.topnotebook_set(self['InitConfTab']) + self['InitConfForward'].grab_focus() + def initconf_close(self): + self.topnotebook_set(None) + + def initconf_set_buttons(self, back, fwd): + self['InitConfBack'].set_sensitive(back) + self['InitConfForward'].set_sensitive(fwd) + def initconf_done_clicked(self, widget): + self.initconf_close() + def initconf_back_clicked(self, widget): + self['InitConfNotebook'].prev_page() + def initconf_forward_clicked(self, widget): + self['InitConfNotebook'].next_page() + def initconf_switch_page(self, widget, page, page_num): + if page_num == 0: + self.initconf_set_buttons(False, True) + elif page_num == 1: + self.initconf_set_buttons(True, False) if __name__ == "__main__": hwg = HfConsole() - hwg.Start() gtk.main() |