diff options
author | samr7 <samr7@126591fb-c623-4b62-a76d-97a8e4f34109> | 2009-01-09 10:10:52 +0000 |
---|---|---|
committer | samr7 <samr7@126591fb-c623-4b62-a76d-97a8e4f34109> | 2009-01-09 10:10:52 +0000 |
commit | 3b1c199304d10df53db10ffe974976796eeced81 (patch) | |
tree | 74dad0dff1b5b4c6f728cd2bf46109a57b1299a8 | |
parent | 9ac76264349a78631c0dfb4f1a0c48d7c9c58318 (diff) | |
download | nohands-3b1c199304d10df53db10ffe974976796eeced81.tar.gz |
Checkpoint of large change set, including:
- Minor HFP state machine overhaul
- Move auto-reconnect handling to RfcommService/Session
- Add timeouts for RFCOMM connection and HFP commands
- Support CNUM, CLCC, and COPS commands in libhfp/hfpd
- CmdCallDropActive -> CmdCallDropIndex
- Add CallDropIndex() and CallPrivateConsult() to hfpd
- Support voice recognition activation in libhfp/hfpd/hfconsole
- Propagate in-band ring tone state to hfpd/hfconsole
- Add '+' international prefix symbol to hfconsole keypad
git-svn-id: http://nohands.svn.sourceforge.net/svnroot/nohands/trunk@80 126591fb-c623-4b62-a76d-97a8e4f34109
-rw-r--r-- | data/hfconsole.glade | 1492 | ||||
-rwxr-xr-x | data/hfconsole.in | 592 | ||||
-rw-r--r-- | data/hfpd-dbus-doc.cs | 473 | ||||
-rw-r--r-- | hfpd/objects.cpp | 340 | ||||
-rw-r--r-- | hfpd/objects.h | 34 | ||||
-rw-r--r-- | include/libhfp/bt.h | 2 | ||||
-rw-r--r-- | include/libhfp/hfp.h | 394 | ||||
-rw-r--r-- | include/libhfp/rfcomm.h | 60 | ||||
-rw-r--r-- | libhfp/hfp.cpp | 1210 | ||||
-rw-r--r-- | libhfp/rfcomm.cpp | 301 |
10 files changed, 3592 insertions, 1306 deletions
diff --git a/data/hfconsole.glade b/data/hfconsole.glade index 8df3a37..81dc971 100644 --- a/data/hfconsole.glade +++ b/data/hfconsole.glade @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> -<!--Generated with glade3 3.4.5 on Tue Dec 23 12:42:43 2008 --> +<!--Generated with glade3 3.4.5 on Fri Jan 9 01:42:09 2009 --> <glade-interface> <widget class="GtkWindow" id="MainWindow"> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> @@ -68,7 +68,7 @@ <child> <widget class="GtkImage" id="image22"> <property name="visible">True</property> - <property name="tooltip" translatable="yes">Has Service</property> + <property name="tooltip" translatable="yes">Poor Reception</property> <property name="pixbuf">signal-0.png</property> </widget> </child> @@ -193,6 +193,7 @@ <child> <widget class="GtkImage" id="image29"> <property name="visible">True</property> + <property name="tooltip" translatable="yes">No Phone</property> <property name="pixbuf">noag.png</property> </widget> <packing> @@ -208,6 +209,7 @@ <child> <widget class="GtkImage" id="NoBluetoothIndicator"> <property name="visible">True</property> + <property name="tooltip" translatable="yes">Bluetooth Unavailable</property> <property name="pixbuf">nobt.png</property> </widget> <packing> @@ -236,6 +238,7 @@ <child> <widget class="GtkImage" id="image34"> <property name="visible">True</property> + <property name="tooltip" translatable="yes">Low Battery</property> <property name="pixbuf">battery-0.png</property> </widget> </child> @@ -248,6 +251,7 @@ <child> <widget class="GtkImage" id="image33"> <property name="visible">True</property> + <property name="tooltip" translatable="yes">Battery Charge</property> <property name="pixbuf">battery-1.png</property> </widget> <packing> @@ -263,6 +267,7 @@ <child> <widget class="GtkImage" id="image32"> <property name="visible">True</property> + <property name="tooltip" translatable="yes">Battery Charge</property> <property name="pixbuf">battery-2.png</property> </widget> <packing> @@ -278,6 +283,7 @@ <child> <widget class="GtkImage" id="image31"> <property name="visible">True</property> + <property name="tooltip" translatable="yes">Battery Charge</property> <property name="pixbuf">battery-3.png</property> </widget> <packing> @@ -293,6 +299,7 @@ <child> <widget class="GtkImage" id="image17"> <property name="visible">True</property> + <property name="tooltip" translatable="yes">Battery Charge</property> <property name="pixbuf">battery-4.png</property> </widget> <packing> @@ -308,6 +315,7 @@ <child> <widget class="GtkImage" id="image8"> <property name="visible">True</property> + <property name="tooltip" translatable="yes">Battery Charge</property> <property name="pixbuf">battery-5.png</property> </widget> <packing> @@ -377,6 +385,7 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> + <property name="tooltip" translatable="yes">Show Keypad</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> <signal name="clicked" handler="BarDialPad_clicked_cb"/> @@ -393,6 +402,7 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> + <property name="tooltip" translatable="yes">Show Contacts</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> <signal name="clicked" handler="BarContacts_clicked_cb"/> @@ -518,6 +528,24 @@ <property name="position">7</property> </packing> </child> + <child> + <widget class="GtkToggleButton" id="AgVoiceRecogToggle"> + <property name="visible">True</property> + <property name="tooltip" translatable="yes">Voice Recognition</property> + <property name="focus_on_click">False</property> + <property name="response_id">0</property> + <signal name="toggled" handler="AgVoiceRecogToggle_toggled_cb"/> + <child> + <widget class="GtkImage" id="image41"> + <property name="visible">True</property> + <property name="pixbuf">vr.png</property> + </widget> + </child> + </widget> + <packing> + <property name="position">8</property> + </packing> + </child> </widget> <packing> <property name="expand">False</property> @@ -558,6 +586,7 @@ <property name="has_focus">True</property> <property name="is_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <signal name="changed" handler="PhoneNumEntry_changed_cb"/> </widget> </child> <child> @@ -596,96 +625,100 @@ <property name="row_spacing">8</property> <property name="homogeneous">True</property> <child> - <widget class="GtkButton" id="DigitButtonAst"> + <widget class="GtkButton" id="DigitButton3"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="*"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="*"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="3"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="3"/> <child> - <widget class="GtkLabel" id="DigitLabelAst"> + <widget class="GtkLabel" id="DigitLabel3"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">*</span> -</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">3</span> +def</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> </child> </widget> <packing> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="left_attach">2</property> + <property name="right_attach">3</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton7"> + <widget class="GtkButton" id="DigitButton6"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="7"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="7"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="6"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="6"/> <child> - <widget class="GtkLabel" id="DigitLabel7"> + <widget class="GtkLabel" id="DigitLabel6"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">7</span> -pqrs</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">6</span> +mno</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> </child> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton4"> + <widget class="GtkButton" id="DigitButton9"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="4"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="4"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="9"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="9"/> <child> - <widget class="GtkLabel" id="DigitLabel4"> + <widget class="GtkLabel" id="DigitLabel9"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">4</span> -ghi</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">9</span> +wxyz</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> </child> </widget> <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton1"> + <widget class="GtkButton" id="DigitButtonPnd"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="1"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="1"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="#"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="#"/> <child> - <widget class="GtkLabel" id="DigitLabel1"> + <widget class="GtkLabel" id="DigitLabelPnd"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">1</span> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">#</span> </property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> @@ -693,24 +726,29 @@ ghi</property> </child> </widget> <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton2"> + <widget class="GtkButton" id="DigitButton0"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="2"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="2"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="0"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="0"/> + <signal name="released" handler="DigitButton_released_cb"/> <child> - <widget class="GtkLabel" id="DigitLabel2"> + <widget class="GtkLabel" id="DigitLabel0"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">2</span> -abc</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">0</span> ++</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> @@ -719,24 +757,26 @@ abc</property> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton5"> + <widget class="GtkButton" id="DigitButton8"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="5"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="5"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="8"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="8"/> <child> - <widget class="GtkLabel" id="DigitLabel5"> + <widget class="GtkLabel" id="DigitLabel8"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">5</span> -jkl</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">8</span> +tuv</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> @@ -745,26 +785,26 @@ jkl</property> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton8"> + <widget class="GtkButton" id="DigitButton5"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="8"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="8"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="5"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="5"/> <child> - <widget class="GtkLabel" id="DigitLabel8"> + <widget class="GtkLabel" id="DigitLabel5"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">8</span> -tuv</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">5</span> +jkl</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> @@ -773,26 +813,26 @@ tuv</property> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton0"> + <widget class="GtkButton" id="DigitButton2"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="0"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="0"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="2"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="2"/> <child> - <widget class="GtkLabel" id="DigitLabel0"> + <widget class="GtkLabel" id="DigitLabel2"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">0</span> -</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">2</span> +abc</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> @@ -801,25 +841,23 @@ tuv</property> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButtonPnd"> + <widget class="GtkButton" id="DigitButton1"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="#"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="#"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="1"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="1"/> <child> - <widget class="GtkLabel" id="DigitLabelPnd"> + <widget class="GtkLabel" id="DigitLabel1"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">#</span> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">1</span> </property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> @@ -827,92 +865,84 @@ tuv</property> </child> </widget> <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton9"> + <widget class="GtkButton" id="DigitButton4"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="9"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="9"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="4"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="4"/> <child> - <widget class="GtkLabel" id="DigitLabel9"> + <widget class="GtkLabel" id="DigitLabel4"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">9</span> -wxyz</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">4</span> +ghi</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> </child> </widget> <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton6"> + <widget class="GtkButton" id="DigitButton7"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="6"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="6"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="7"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="7"/> <child> - <widget class="GtkLabel" id="DigitLabel6"> + <widget class="GtkLabel" id="DigitLabel7"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">6</span> -mno</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">7</span> +pqrs</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> </child> </widget> <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> - <widget class="GtkButton" id="DigitButton3"> + <widget class="GtkButton" id="DigitButtonAst"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="pressed" handler="DigitButton_pressed_cb" object="3"/> - <signal name="clicked" handler="DigitButton_clicked_cb" object="3"/> + <signal name="pressed" handler="DigitButton_pressed_cb" object="*"/> + <signal name="clicked" handler="DigitButton_clicked_cb" object="*"/> <child> - <widget class="GtkLabel" id="DigitLabel3"> + <widget class="GtkLabel" id="DigitLabelAst"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><span size="xx-large" weight="heavy">3</span> -def</property> + <property name="label" translatable="yes"><span size="xx-large" weight="heavy">*</span> +</property> <property name="use_markup">True</property> <property name="justify">GTK_JUSTIFY_CENTER</property> </widget> </child> </widget> <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property> </packing> @@ -981,6 +1011,7 @@ def</property> <property name="label">gtk-delete</property> <property name="use_stock">True</property> <property name="response_id">0</property> + <signal name="clicked" handler="ContactsDelete_clicked_cb"/> </widget> </child> <child> @@ -991,6 +1022,7 @@ def</property> <property name="label">gtk-new</property> <property name="use_stock">True</property> <property name="response_id">0</property> + <signal name="clicked" handler="ContactsNew_clicked_cb"/> </widget> <packing> <property name="position">1</property> @@ -1004,6 +1036,7 @@ def</property> <property name="label">gtk-edit</property> <property name="use_stock">True</property> <property name="response_id">0</property> + <signal name="clicked" handler="ContactsEdit_clicked_cb"/> </widget> <packing> <property name="position">2</property> @@ -1080,42 +1113,42 @@ def</property> </packing> </child> <child> - <widget class="GtkToggleButton" id="RecordCall"> + <widget class="GtkButton" id="ConfigOpen"> <property name="visible">True</property> - <property name="receives_default">True</property> - <property name="tooltip" translatable="yes">Record Call</property> + <property name="tooltip" translatable="yes">Open Preferences</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> + <signal name="clicked" handler="ConfigOpen_clicked_cb"/> <child> - <widget class="GtkImage" id="image11"> + <widget class="GtkImage" id="image9"> <property name="visible">True</property> - <property name="stock">gtk-media-record</property> + <property name="stock">gtk-preferences</property> <property name="icon_size">6</property> </widget> </child> </widget> <packing> <property name="position">2</property> + <property name="secondary">True</property> </packing> </child> <child> - <widget class="GtkButton" id="ConfigOpen"> + <widget class="GtkToggleButton" id="RecordCall"> <property name="visible">True</property> - <property name="tooltip" translatable="yes">Open Preferences</property> + <property name="receives_default">True</property> + <property name="tooltip" translatable="yes">Record Call</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> - <signal name="clicked" handler="ConfigOpen_clicked_cb"/> <child> - <widget class="GtkImage" id="image9"> + <widget class="GtkImage" id="image11"> <property name="visible">True</property> - <property name="stock">gtk-preferences</property> + <property name="stock">gtk-media-record</property> <property name="icon_size">6</property> </widget> </child> </widget> <packing> <property name="position">3</property> - <property name="secondary">True</property> </packing> </child> </widget> @@ -1774,55 +1807,52 @@ At least one unknown device attempted to connect. Would you like to add a devic <property name="column_spacing">8</property> <property name="row_spacing">8</property> <child> - <widget class="GtkLabel" id="label4"> + <widget class="GtkLabel" id="label3"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Mobile:</property> + <property name="label" translatable="yes">Name:</property> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkEntry" id="ContactMobile"> + <widget class="GtkEntry" id="ContactName"> <property name="visible">True</property> <property name="can_focus">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkEntry" id="ContactHome"> + <widget class="GtkLabel" id="label44"> <property name="visible">True</property> - <property name="can_focus">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>Telephone Numbers</b></property> + <property name="use_markup">True</property> </widget> <packing> - <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label36"> + <widget class="GtkLabel" id="label42"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Home:</property> + <property name="label" translatable="yes">Work:</property> </widget> <packing> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> @@ -1842,52 +1872,55 @@ At least one unknown device attempted to connect. Would you like to add a devic </packing> </child> <child> - <widget class="GtkLabel" id="label42"> + <widget class="GtkLabel" id="label36"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Work:</property> + <property name="label" translatable="yes">Home:</property> </widget> <packing> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label44"> + <widget class="GtkEntry" id="ContactHome"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>Telephone Numbers</b></property> - <property name="use_markup">True</property> + <property name="can_focus">True</property> </widget> <packing> + <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkEntry" id="ContactName"> + <widget class="GtkEntry" id="ContactMobile"> <property name="visible">True</property> <property name="can_focus">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label3"> + <widget class="GtkLabel" id="label4"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Name:</property> + <property name="label" translatable="yes">Mobile:</property> </widget> <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> @@ -1991,12 +2024,13 @@ The contact you wish to call has multiple telephone numbers. Select the number <property name="spacing">8</property> <property name="layout_style">GTK_BUTTONBOX_START</property> <child> - <widget class="GtkButton" id="button1"> + <widget class="GtkButton" id="ChooseNumberMobile"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> + <signal name="clicked" handler="ChooseNumberMobile_clicked_cb"/> <child> <widget class="GtkHBox" id="hbox18"> <property name="visible">True</property> @@ -2013,12 +2047,25 @@ The contact you wish to call has multiple telephone numbers. Select the number </packing> </child> <child> - <widget class="GtkLabel" id="label64"> + <widget class="GtkVBox" id="vbox13"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>Mobile</b> -</property> - <property name="use_markup">True</property> + <child> + <widget class="GtkLabel" id="label64"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>Mobile</b></property> + <property name="use_markup">True</property> + </widget> + </child> + <child> + <widget class="GtkLabel" id="ChooseNumberMobileLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> </widget> <packing> <property name="position">1</property> @@ -2032,12 +2079,13 @@ The contact you wish to call has multiple telephone numbers. Select the number </packing> </child> <child> - <widget class="GtkButton" id="button2"> + <widget class="GtkButton" id="ChooseNumberHome"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> + <signal name="clicked" handler="ChooseNumberHome_clicked_cb"/> <child> <widget class="GtkHBox" id="hbox19"> <property name="visible">True</property> @@ -2054,12 +2102,25 @@ The contact you wish to call has multiple telephone numbers. Select the number </packing> </child> <child> - <widget class="GtkLabel" id="label68"> + <widget class="GtkVBox" id="vbox14"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>Home</b> -</property> - <property name="use_markup">True</property> + <child> + <widget class="GtkLabel" id="label68"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>Home</b></property> + <property name="use_markup">True</property> + </widget> + </child> + <child> + <widget class="GtkLabel" id="ChooseNumberHomeLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> </widget> <packing> <property name="position">1</property> @@ -2074,12 +2135,13 @@ The contact you wish to call has multiple telephone numbers. Select the number </packing> </child> <child> - <widget class="GtkButton" id="button3"> + <widget class="GtkButton" id="ChooseNumberWork"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="focus_on_click">False</property> <property name="response_id">0</property> + <signal name="clicked" handler="ChooseNumberWork_clicked_cb"/> <child> <widget class="GtkHBox" id="hbox20"> <property name="visible">True</property> @@ -2096,12 +2158,25 @@ The contact you wish to call has multiple telephone numbers. Select the number </packing> </child> <child> - <widget class="GtkLabel" id="label69"> + <widget class="GtkVBox" id="vbox15"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>Work</b> -</property> - <property name="use_markup">True</property> + <child> + <widget class="GtkLabel" id="label69"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>Work</b></property> + <property name="use_markup">True</property> + </widget> + </child> + <child> + <widget class="GtkLabel" id="ChooseNumberWorkLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> </widget> <packing> <property name="position">1</property> @@ -2130,9 +2205,10 @@ The contact you wish to call has multiple telephone numbers. Select the number <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> - <property name="label" translatable="yes">gtk-cancel</property> + <property name="label">gtk-cancel</property> <property name="use_stock">True</property> <property name="response_id">0</property> + <signal name="clicked" handler="ChooseNumberCancel_clicked_cb"/> </widget> </child> </widget> @@ -2307,114 +2383,185 @@ The contact you wish to call has multiple telephone numbers. Select the number <property name="column_spacing">8</property> <property name="row_spacing">8</property> <child> - <widget class="GtkLabel" id="ConfigMinOutBufferLabel"> + <widget class="GtkFrame" id="frame2"> <property name="visible">True</property> - <property name="xalign">0</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label_xalign">0</property> + <child> + <widget class="GtkAlignment" id="alignment2"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="bottom_padding">8</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkVBox" id="vbox10"> + <property name="visible">True</property> + <property name="spacing">8</property> + <child> + <widget class="GtkHBox" id="hbox8"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="border_width">10</property> + <property name="spacing">10</property> + <child> + <widget class="GtkToggleButton" id="ConfigFeedbackTest"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Feedback Test</property> + <property name="response_id">0</property> + <signal name="toggled" handler="ConfigFeedbackTest_toggled_cb"/> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label21"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Real Packet Size:</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="ConfigRealPacketSizeLabel"> + <property name="width_request">64</property> + <property name="visible">True</property> + <property name="xalign">0</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="position">2</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkHBox" id="hbox111"> + <property name="visible">True</property> + <property name="spacing">8</property> + <child> + <widget class="GtkImage" id="ConfigSkewNotice"> + <property name="no_show_all">True</property> + <property name="stock">gtk-dialog-warning</property> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="ConfigSkewNoticeLabel"> + <property name="no_show_all">True</property> + <property name="xalign">0</property> + <property name="wrap">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="padding">4</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label20"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes"><b>Test</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> </widget> <packing> - <property name="left_attach">2</property> <property name="right_attach">3</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkHScale" id="ConfigMinOutBuffer"> + <widget class="GtkComboBox" id="ConfigDriver"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">1 0 1 0.01 0.10000000000000001 0</property> - <property name="draw_value">False</property> - <property name="value_pos">GTK_POS_RIGHT</property> - <signal name="value_changed" handler="ConfigMinOutBuffer_value_changed_cb"/> + <property name="items" translatable="yes"></property> + <signal name="changed" handler="ConfigDriver_changed_cb"/> </widget> <packing> <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="ConfigJitterWindowLabel"> - <property name="visible">True</property> - <property name="xalign">0</property> - </widget> - <packing> - <property name="left_attach">2</property> <property name="right_attach">3</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkCheckButton" id="ConfigJitterWindowHint"> + <widget class="GtkLabel" id="label10"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Jitter Window</property> <property name="xalign">0</property> - <property name="response_id">0</property> - <property name="draw_indicator">True</property> - <signal name="toggled" handler="ConfigJitterWindowHint_toggled_cb"/> + <property name="label" translatable="yes">Device / Options</property> </widget> <packing> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkComboBoxEntry" id="ConfigDriverDevList"> + <widget class="GtkHScale" id="ConfigJitterWindow"> <property name="visible">True</property> - <child internal-child="entry"> - <widget class="GtkEntry" id="comboboxentry-entry2"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </widget> - </child> + <property name="can_focus">True</property> + <property name="adjustment">1 0 1 0.01 0.10000000000000001 0</property> + <property name="draw_value">False</property> + <property name="value_pos">GTK_POS_RIGHT</property> + <signal name="value_changed" handler="ConfigJitterWindow_value_changed_cb"/> </widget> <packing> <property name="left_attach">1</property> - <property name="right_attach">3</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">GTK_FILL</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkCheckButton" id="ConfigMinOutBufferHint"> + <widget class="GtkLabel" id="label7"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Output Buffer</property> <property name="xalign">0</property> - <property name="response_id">0</property> - <property name="draw_indicator">True</property> - <signal name="toggled" handler="ConfigMinOutBufferHint_toggled_cb"/> + <property name="label" translatable="yes">Driver</property> </widget> <packing> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkCheckButton" id="ConfigPacketIntervalHint"> + <widget class="GtkLabel" id="ConfigPacketIntervalLabel"> + <property name="width_request">64</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Packet Interval</property> <property name="xalign">0</property> - <property name="response_id">0</property> - <property name="draw_indicator">True</property> - <signal name="toggled" handler="ConfigPacketIntervalHint_toggled_cb"/> </widget> <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> <property name="top_attach">2</property> <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> @@ -2442,14 +2589,16 @@ The contact you wish to call has multiple telephone numbers. Select the number </packing> </child> <child> - <widget class="GtkLabel" id="ConfigPacketIntervalLabel"> - <property name="width_request">64</property> + <widget class="GtkCheckButton" id="ConfigPacketIntervalHint"> <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Packet Interval</property> <property name="xalign">0</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="ConfigPacketIntervalHint_toggled_cb"/> </widget> <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> <property name="top_attach">2</property> <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> @@ -2457,172 +2606,99 @@ The contact you wish to call has multiple telephone numbers. Select the number </packing> </child> <child> - <widget class="GtkLabel" id="label7"> + <widget class="GtkCheckButton" id="ConfigMinOutBufferHint"> <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Output Buffer</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Driver</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="ConfigMinOutBufferHint_toggled_cb"/> </widget> <packing> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkHScale" id="ConfigJitterWindow"> + <widget class="GtkComboBoxEntry" id="ConfigDriverDevList"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="adjustment">1 0 1 0.01 0.10000000000000001 0</property> - <property name="draw_value">False</property> - <property name="value_pos">GTK_POS_RIGHT</property> - <signal name="value_changed" handler="ConfigJitterWindow_value_changed_cb"/> + <child internal-child="entry"> + <widget class="GtkEntry" id="comboboxentry-entry2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </widget> + </child> </widget> <packing> <property name="left_attach">1</property> - <property name="right_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="ConfigJitterWindowHint"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Jitter Window</property> + <property name="xalign">0</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="ConfigJitterWindowHint_toggled_cb"/> + </widget> + <packing> <property name="top_attach">3</property> <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label10"> + <widget class="GtkLabel" id="ConfigJitterWindowLabel"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Device / Options</property> </widget> <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkComboBox" id="ConfigDriver"> + <widget class="GtkHScale" id="ConfigMinOutBuffer"> <property name="visible">True</property> - <property name="items" translatable="yes"></property> - <signal name="changed" handler="ConfigDriver_changed_cb"/> + <property name="can_focus">True</property> + <property name="adjustment">1 0 1 0.01 0.10000000000000001 0</property> + <property name="draw_value">False</property> + <property name="value_pos">GTK_POS_RIGHT</property> + <signal name="value_changed" handler="ConfigMinOutBuffer_value_changed_cb"/> </widget> <packing> <property name="left_attach">1</property> - <property name="right_attach">3</property> - <property name="x_options">GTK_FILL</property> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkFrame" id="frame2"> + <widget class="GtkLabel" id="ConfigMinOutBufferLabel"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label_xalign">0</property> - <child> - <widget class="GtkAlignment" id="alignment2"> - <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="bottom_padding">8</property> - <property name="left_padding">12</property> - <child> - <widget class="GtkVBox" id="vbox10"> - <property name="visible">True</property> - <property name="spacing">8</property> - <child> - <widget class="GtkHBox" id="hbox8"> - <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="border_width">10</property> - <property name="spacing">10</property> - <child> - <widget class="GtkToggleButton" id="ConfigFeedbackTest"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes">Feedback Test</property> - <property name="response_id">0</property> - <signal name="toggled" handler="ConfigFeedbackTest_toggled_cb"/> - </widget> - </child> - <child> - <widget class="GtkLabel" id="label21"> - <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes">Real Packet Size:</property> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="ConfigRealPacketSizeLabel"> - <property name="width_request">64</property> - <property name="visible">True</property> - <property name="xalign">0</property> - </widget> - <packing> - <property name="expand">False</property> - <property name="position">2</property> - </packing> - </child> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <widget class="GtkHBox" id="hbox111"> - <property name="visible">True</property> - <property name="spacing">8</property> - <child> - <widget class="GtkImage" id="ConfigSkewNotice"> - <property name="no_show_all">True</property> - <property name="stock">gtk-dialog-warning</property> - </widget> - <packing> - <property name="expand">False</property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="ConfigSkewNoticeLabel"> - <property name="no_show_all">True</property> - <property name="xalign">0</property> - <property name="wrap">True</property> - </widget> - <packing> - <property name="expand">False</property> - <property name="padding">4</property> - <property name="position">1</property> - </packing> - </child> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </widget> - </child> - </widget> - </child> - <child> - <widget class="GtkLabel" id="label20"> - <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><b>Test</b></property> - <property name="use_markup">True</property> - </widget> - <packing> - <property name="type">label_item</property> - </packing> - </child> + <property name="xalign">0</property> </widget> <packing> + <property name="left_attach">2</property> <property name="right_attach">3</property> - <property name="top_attach">5</property> - <property name="bottom_attach">6</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> @@ -2661,87 +2737,91 @@ The contact you wish to call has multiple telephone numbers. Select the number <property name="column_spacing">10</property> <property name="row_spacing">10</property> <child> - <widget class="GtkLabel" id="ConfigDereverbDecayLabel"> + <widget class="GtkLabel" id="ConfigEchoCancelTailLabel"> + <property name="width_request">64</property> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label">0</property> </widget> <packing> <property name="left_attach">2</property> <property name="right_attach">3</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="ConfigDereverbValueLabel"> + <widget class="GtkHScale" id="ConfigEchoCancelTail"> <property name="visible">True</property> + <property name="can_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label">0</property> + <property name="adjustment">0 0 1 0.01 0.10000000000000001 0</property> + <property name="draw_value">False</property> + <signal name="value_changed" handler="ConfigEchoCancelTail_value_changed_cb"/> </widget> <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> </packing> </child> <child> - <widget class="GtkCheckButton" id="ConfigDenoise"> + <widget class="GtkCheckButton" id="ConfigEchoCancel"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes">Noise Reduction</property> - <property name="xalign">0</property> + <property name="label" translatable="yes">Echo Cancelation</property> <property name="response_id">0</property> <property name="draw_indicator">True</property> - <signal name="toggled" handler="ConfigDenoise_toggled_cb"/> + <signal name="toggled" handler="ConfigEchoCancel_toggled_cb"/> </widget> <packing> - <property name="right_attach">3</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label34"> + <widget class="GtkLabel" id="ConfigAutoGainLabel"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Auto Gain</property> + <property name="label">0</property> </widget> <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> <property name="top_attach">1</property> <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkCheckButton" id="ConfigDereverb"> + <widget class="GtkHScale" id="ConfigAutoGain"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes">Dereverb</property> - <property name="response_id">0</property> - <property name="draw_indicator">True</property> - <signal name="toggled" handler="ConfigDereverb_toggled_cb"/> + <property name="adjustment">0 0 20 1 10 0</property> + <property name="digits">0</property> + <property name="draw_value">False</property> + <property name="value_pos">GTK_POS_RIGHT</property> + <signal name="value_changed" handler="ConfigAutoGain_value_changed_cb"/> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">4</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkHScale" id="ConfigDereverbValue"> + <widget class="GtkHScale" id="ConfigDereverbDecay"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> @@ -2749,18 +2829,18 @@ The contact you wish to call has multiple telephone numbers. Select the number <property name="digits">2</property> <property name="draw_value">False</property> <property name="value_pos">GTK_POS_RIGHT</property> - <signal name="value_changed" handler="ConfigDereverbValue_value_changed_cb"/> + <signal name="value_changed" handler="ConfigDereverbDecay_value_changed_cb"/> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkHScale" id="ConfigDereverbDecay"> + <widget class="GtkHScale" id="ConfigDereverbValue"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> @@ -2768,96 +2848,92 @@ The contact you wish to call has multiple telephone numbers. Select the number <property name="digits">2</property> <property name="draw_value">False</property> <property name="value_pos">GTK_POS_RIGHT</property> - <signal name="value_changed" handler="ConfigDereverbDecay_value_changed_cb"/> + <signal name="value_changed" handler="ConfigDereverbValue_value_changed_cb"/> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkHScale" id="ConfigAutoGain"> + <widget class="GtkCheckButton" id="ConfigDereverb"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="adjustment">0 0 20 1 10 0</property> - <property name="digits">0</property> - <property name="draw_value">False</property> - <property name="value_pos">GTK_POS_RIGHT</property> - <signal name="value_changed" handler="ConfigAutoGain_value_changed_cb"/> + <property name="label" translatable="yes">Dereverb</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="ConfigDereverb_toggled_cb"/> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options">GTK_FILL</property> + <property name="top_attach">2</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="ConfigAutoGainLabel"> + <widget class="GtkLabel" id="label34"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label">0</property> + <property name="label" translatable="yes">Auto Gain</property> </widget> <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> <property name="top_attach">1</property> <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkCheckButton" id="ConfigEchoCancel"> + <widget class="GtkCheckButton" id="ConfigDenoise"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes">Echo Cancelation</property> + <property name="label" translatable="yes">Noise Reduction</property> + <property name="xalign">0</property> <property name="response_id">0</property> <property name="draw_indicator">True</property> - <signal name="toggled" handler="ConfigEchoCancel_toggled_cb"/> + <signal name="toggled" handler="ConfigDenoise_toggled_cb"/> </widget> <packing> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> + <property name="right_attach">3</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkHScale" id="ConfigEchoCancelTail"> + <widget class="GtkLabel" id="ConfigDereverbValueLabel"> <property name="visible">True</property> - <property name="can_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="adjustment">0 0 1 0.01 0.10000000000000001 0</property> - <property name="draw_value">False</property> - <signal name="value_changed" handler="ConfigEchoCancelTail_value_changed_cb"/> + <property name="xalign">0</property> + <property name="label">0</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="ConfigEchoCancelTailLabel"> - <property name="width_request">64</property> + <widget class="GtkLabel" id="ConfigDereverbDecayLabel"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> + <property name="label">0</property> </widget> <packing> <property name="left_attach">2</property> <property name="right_attach">3</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> @@ -2914,40 +2990,25 @@ The contact you wish to call has multiple telephone numbers. Select the number <property name="column_spacing">5</property> <property name="row_spacing">5</property> <child> - <widget class="GtkEntry" id="ConfigAudioDetachCommand"> + <widget class="GtkFileChooserButton" id="ConfigRingerFile"> <property name="visible">True</property> - <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <signal name="file_set" handler="ConfigRingerFile_file_set_cb"/> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkEntry" id="ConfigAudioAttachCommand"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options">GTK_FILL</property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="label38"> + <widget class="GtkLabel" id="label39"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Audio Detach Command:</property> + <property name="label" translatable="yes">Ringer Sound:</property> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> @@ -2966,26 +3027,41 @@ The contact you wish to call has multiple telephone numbers. Select the number </packing> </child> <child> - <widget class="GtkLabel" id="label39"> + <widget class="GtkLabel" id="label38"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Ringer Sound:</property> + <property name="label" translatable="yes">Audio Detach Command:</property> </widget> <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkFileChooserButton" id="ConfigRingerFile"> + <widget class="GtkEntry" id="ConfigAudioAttachCommand"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <signal name="file_set" handler="ConfigRingerFile_file_set_cb"/> + <property name="can_focus">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="ConfigAudioDetachCommand"> + <property name="visible">True</property> + <property name="can_focus">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="y_options">GTK_FILL</property> </packing> </child> @@ -3105,52 +3181,57 @@ The contact you wish to call has multiple telephone numbers. Select the number <property name="column_spacing">8</property> <property name="row_spacing">8</property> <child> - <widget class="GtkLabel" id="label24"> + <widget class="GtkLabel" id="label14"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes">The first step will begin when you click <i>Forward</i>.</property> + <property name="label" translatable="yes"><b>1. Record a sound clip of spoken language + <i>Try to leave pauses and quiet moments during + this clip.</i></b></property> <property name="use_markup">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel2"> - <property name="visible">True</property> - </widget> - <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">5</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="emptylabel"> + <widget class="GtkLabel" id="label11"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">To test your signal processing settings, we will perform +three steps:</property> + <property name="use_markup">True</property> </widget> <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkArrow" id="arrow1"> + <widget class="GtkLabel" id="label17"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>2.</b> Play back the sound clip while recording a second clip + <i>This will simulate a full duplex conversation, with the clip + recorded in step one as coming from the remote party.</i></property> + <property name="use_markup">True</property> </widget> <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> @@ -3173,55 +3254,50 @@ The contact you wish to call has multiple telephone numbers. Select the number </packing> </child> <child> - <widget class="GtkLabel" id="label17"> + <widget class="GtkArrow" id="arrow1"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>2.</b> Play back the sound clip while recording a second clip - <i>This will simulate a full duplex conversation, with the clip - recorded in step one as coming from the remote party.</i></property> - <property name="use_markup">True</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label11"> + <widget class="GtkLabel" id="emptylabel"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">To test your signal processing settings, we will perform -three steps:</property> - <property name="use_markup">True</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label14"> + <widget class="GtkLabel" id="emptylabel2"> + <property name="visible">True</property> + </widget> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">5</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label24"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes"><b>1. Record a sound clip of spoken language - <i>Try to leave pauses and quiet moments during - this clip.</i></b></property> + <property name="label" translatable="yes">The first step will begin when you click <i>Forward</i>.</property> <property name="use_markup">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> @@ -3248,51 +3324,68 @@ three steps:</property> <property name="column_spacing">8</property> <property name="row_spacing">8</property> <child> - <widget class="GtkLabel" id="label8"> + <widget class="GtkToggleButton" id="DspTestRecStart1"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"> Click <i>Record</i> to begin</property> - <property name="use_markup">True</property> - <property name="ellipsize">PANGO_ELLIPSIZE_END</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label">gtk-media-record</property> + <property name="use_stock">True</property> + <property name="response_id">0</property> + <signal name="toggled" handler="DspTestRecStart1_toggled_cb"/> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> + <property name="x_options"></property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkProgressBar" id="DspTestRecPosition1"> + <widget class="GtkLabel" id="label12"> <property name="visible">True</property> - <property name="show_text">True</property> - <property name="text" translatable="yes">Record Progress</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>1. Record a sound clip of spoken language + <i>Try to leave pauses and quiet moments during + this clip.</i></b></property> + <property name="use_markup">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel4"> + <widget class="GtkLabel" id="label22"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">To test your signal processing settings, we will perform +three steps:</property> + <property name="use_markup">True</property> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">6</property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel1"> + <widget class="GtkArrow" id="arrow2"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> </widget> <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> @@ -3312,70 +3405,53 @@ three steps:</property> </packing> </child> <child> - <widget class="GtkArrow" id="arrow2"> + <widget class="GtkLabel" id="emptylabel1"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> </widget> <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label22"> + <widget class="GtkLabel" id="emptylabel4"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">To test your signal processing settings, we will perform -three steps:</property> - <property name="use_markup">True</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">6</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label12"> + <widget class="GtkProgressBar" id="DspTestRecPosition1"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>1. Record a sound clip of spoken language - <i>Try to leave pauses and quiet moments during - this clip.</i></b></property> - <property name="use_markup">True</property> + <property name="show_text">True</property> + <property name="text" translatable="yes">Record Progress</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkToggleButton" id="DspTestRecStart1"> + <widget class="GtkLabel" id="label8"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label">gtk-media-record</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> - <signal name="toggled" handler="DspTestRecStart1_toggled_cb"/> + <property name="xalign">0</property> + <property name="label" translatable="yes"> Click <i>Record</i> to begin</property> + <property name="use_markup">True</property> + <property name="ellipsize">PANGO_ELLIPSIZE_END</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">5</property> - <property name="bottom_attach">6</property> - <property name="x_options"></property> - <property name="y_options"></property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> </packing> </child> </widget> @@ -3404,68 +3480,51 @@ three steps:</property> <property name="column_spacing">8</property> <property name="row_spacing">8</property> <child> - <widget class="GtkLabel" id="label9"> + <widget class="GtkLabel" id="label26"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes"><b>2. Play back the sound clip while recording a second clip - <i>This will simulate a full duplex conversation, with - the clip recorded in step one as coming from the - remote party.</i></b></property> + <property name="label" translatable="yes"> Click <i>Record</i> to begin</property> <property name="use_markup">True</property> + <property name="ellipsize">PANGO_ELLIPSIZE_END</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> </packing> </child> <child> - <widget class="GtkToggleButton" id="DspTestRecStart2"> + <widget class="GtkProgressBar" id="DspTestRecPosition2"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label">gtk-media-record</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> - <signal name="toggled" handler="DspTestRecStart2_toggled_cb"/> + <property name="show_text">True</property> + <property name="text" translatable="yes">Record Progress</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">5</property> - <property name="bottom_attach">6</property> - <property name="x_options"></property> - <property name="y_options"></property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label25"> + <widget class="GtkLabel" id="emptylabel8"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">You are currently on Step 2 of the digital signal processing test:</property> - <property name="use_markup">True</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">6</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkArrow" id="arrow4"> + <widget class="GtkLabel" id="emptylabel7"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> </widget> <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> @@ -3485,53 +3544,70 @@ three steps:</property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel7"> + <widget class="GtkArrow" id="arrow4"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> </widget> <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel8"> + <widget class="GtkLabel" id="label25"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">You are currently on Step 2 of the digital signal processing test:</property> + <property name="use_markup">True</property> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">6</property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkProgressBar" id="DspTestRecPosition2"> + <widget class="GtkToggleButton" id="DspTestRecStart2"> <property name="visible">True</property> - <property name="show_text">True</property> - <property name="text" translatable="yes">Record Progress</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label">gtk-media-record</property> + <property name="use_stock">True</property> + <property name="response_id">0</property> + <signal name="toggled" handler="DspTestRecStart2_toggled_cb"/> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> + <property name="x_options"></property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label26"> + <widget class="GtkLabel" id="label9"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes"> Click <i>Record</i> to begin</property> + <property name="label" translatable="yes"><b>2. Play back the sound clip while recording a second clip + <i>This will simulate a full duplex conversation, with + the clip recorded in step one as coming from the + remote party.</i></b></property> <property name="use_markup">True</property> - <property name="ellipsize">PANGO_ELLIPSIZE_END</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> </widget> @@ -3560,119 +3636,119 @@ three steps:</property> <property name="column_spacing">8</property> <property name="row_spacing">8</property> <child> - <widget class="GtkToggleButton" id="DspTestPlayStart"> + <widget class="GtkLabel" id="label23"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label">gtk-media-play</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> - <signal name="toggled" handler="DspTestPlayStart_toggled_cb"/> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>3. Play back the second clip + <i>This is what the remote party would hear for the + conversation as simulated in step two.</i></b></property> + <property name="use_markup">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> - <property name="x_options"></property> - <property name="y_options">GTK_EXPAND</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label16"> + <widget class="GtkLabel" id="label13"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes">You are currently on Step 3 of the digital signal processing test:</property> + <property name="label" translatable="yes"> Click <i>Play</i> to begin</property> <property name="use_markup">True</property> + <property name="ellipsize">PANGO_ELLIPSIZE_END</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> </packing> </child> <child> - <widget class="GtkArrow" id="arrow3"> + <widget class="GtkProgressBar" id="DspTestPlayPosition"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="show_text">True</property> + <property name="text" translatable="yes">Playback Progress</property> </widget> <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel6"> + <widget class="GtkLabel" id="emptylabel5"> <property name="visible">True</property> </widget> <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">5</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel5"> + <widget class="GtkLabel" id="emptylabel6"> <property name="visible">True</property> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">5</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkProgressBar" id="DspTestPlayPosition"> + <widget class="GtkArrow" id="arrow3"> <property name="visible">True</property> - <property name="show_text">True</property> - <property name="text" translatable="yes">Playback Progress</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label13"> + <widget class="GtkLabel" id="label16"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes"> Click <i>Play</i> to begin</property> + <property name="label" translatable="yes">You are currently on Step 3 of the digital signal processing test:</property> <property name="use_markup">True</property> - <property name="ellipsize">PANGO_ELLIPSIZE_END</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label23"> + <widget class="GtkToggleButton" id="DspTestPlayStart"> <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>3. Play back the second clip - <i>This is what the remote party would hear for the - conversation as simulated in step two.</i></b></property> - <property name="use_markup">True</property> + <property name="label">gtk-media-play</property> + <property name="use_stock">True</property> + <property name="response_id">0</property> + <signal name="toggled" handler="DspTestPlayStart_toggled_cb"/> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="x_options"></property> + <property name="y_options">GTK_EXPAND</property> </packing> </child> </widget> @@ -3836,40 +3912,53 @@ three steps:</property> <property name="column_spacing">8</property> <property name="row_spacing">8</property> <child> - <widget class="GtkLabel" id="label52"> + <widget class="GtkLabel" id="label56"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes">The first step will begin when you click <i>Forward</i>.</property> + <property name="label" translatable="yes"><b>1. Configure and test your local sound card</b></property> <property name="use_markup">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel3"> + <widget class="GtkLabel" id="label55"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">To set up HFP for Linux for the first time, the following steps +will be taken:</property> + <property name="use_markup">True</property> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">4</property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel9"> + <widget class="GtkLabel" id="label54"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>2.</b> Configure your Bluetooth mobile phone</property> + <property name="use_markup">True</property> </widget> <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> @@ -3885,51 +3974,38 @@ three steps:</property> </packing> </child> <child> - <widget class="GtkLabel" id="label54"> + <widget class="GtkLabel" id="emptylabel9"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>2.</b> Configure your Bluetooth mobile phone</property> - <property name="use_markup">True</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label55"> + <widget class="GtkLabel" id="emptylabel3"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">To set up HFP for Linux for the first time, the following steps -will be taken:</property> - <property name="use_markup">True</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label56"> + <widget class="GtkLabel" id="label52"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes"><b>1. Configure and test your local sound card</b></property> + <property name="label" translatable="yes">The first step will begin when you click <i>Forward</i>.</property> <property name="use_markup">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> @@ -3956,40 +4032,53 @@ will be taken:</property> <property name="column_spacing">8</property> <property name="row_spacing">8</property> <child> - <widget class="GtkLabel" id="label60"> + <widget class="GtkLabel" id="label63"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes">The first step will begin when you click <i>Forward</i>.</property> + <property name="label" translatable="yes"><b>1. Configure and test your local sound card</b></property> <property name="use_markup">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel10"> + <widget class="GtkLabel" id="label62"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">To set up HFP for Linux for the first time, the following steps +will be taken:</property> + <property name="use_markup">True</property> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">4</property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="emptylabel11"> + <widget class="GtkLabel" id="label61"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>2.</b> Configure your Bluetooth mobile phone</property> + <property name="use_markup">True</property> </widget> <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> @@ -4005,51 +4094,38 @@ will be taken:</property> </packing> </child> <child> - <widget class="GtkLabel" id="label61"> + <widget class="GtkLabel" id="emptylabel11"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>2.</b> Configure your Bluetooth mobile phone</property> - <property name="use_markup">True</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label62"> + <widget class="GtkLabel" id="emptylabel10"> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">To set up HFP for Linux for the first time, the following steps -will be taken:</property> - <property name="use_markup">True</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> - <property name="y_options"></property> + <property name="y_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label63"> + <widget class="GtkLabel" id="label60"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="xalign">0</property> - <property name="label" translatable="yes"><b>1. Configure and test your local sound card</b></property> + <property name="label" translatable="yes">The first step will begin when you click <i>Forward</i>.</property> <property name="use_markup">True</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> diff --git a/data/hfconsole.in b/data/hfconsole.in index 4573982..7977ea9 100755 --- a/data/hfconsole.in +++ b/data/hfconsole.in @@ -15,6 +15,7 @@ pygtk.require("2.0") import gtk import gtk.glade import gobject +import pango import gettext _ = gettext.gettext @@ -47,7 +48,10 @@ class GtkAlerter: self['CallAlertReject'].show() else: self['CallAlertReject'].hide() - if ag.can_drop_held_udub(): + # The UDUB feature seems to be implemented very + # inconsistently across phones and handset makers. + # Presenting it to the user is almost silly. + if False and ag.can_drop_held_udub(): self['CallAlertBusy'].show() else: self['CallAlertBusy'].hide() @@ -114,21 +118,206 @@ class GtkAlerter: if not self.removed: self.complete() +# Telephone number processing is a serious pain in the rear. +# In order to be able to compare telephone numbers, we must convert them +# to a normalized form, which will be international form. +class TelNormalizer: + """A hacky telephone number normalizer""" + def __init__(self): + pass + def _isnum(self, c): + x = ord(c) + return (x >= ord('0') and x <= ord('9')) + def _isalpha(self, c): + x = ord(c) + return ((x >= ord('a') and x <= ord('z')) or + (x >= ord('A') and x <= ord('Z'))) + def _alpha_convert(self, c): + x = ord(c) + if x >= ord('a') and x <= ord('z'): + c = c.upper() + x = ord(c) + if x >= ord('A') and x <= ord('C'): + c = '2' + elif x >= ord('D') and x <= ord('F'): + c = '3' + elif x >= ord('G') and x <= ord('I'): + c = '4' + elif x >= ord('J') and x <= ord('L'): + c = '5' + elif x >= ord('M') and x <= ord('O'): + c = '6' + elif x >= ord('P') and x <= ord('S'): + c = '7' + elif x >= ord('T') and x <= ord('V'): + c = '8' + elif x >= ord('W') and x <= ord('Z'): + c = '9' + return c + def _simplify(self, tel): + new = '' + if not tel: + return new + if tel[0] == '+': + new = '+' + tel = tel[1:] + for c in tel: + if self._isnum(c) or c == '#' or c == '*': + new = new + c + elif self._isalpha(c): + new = new + self._tel_alpha_convert(c) + elif (self._iswsp(c) or c == '-' or c == '.' or + c == '(' or c == ')' or c == '~' or c == '/'): + continue + else: + print "Odd character \"%s\" in tel" % c + return new + def normalize(self, tel, teltype): + return self._simplify(tel) + def compare(self, tel1, tel2): + # FIXME: this is a horrible hack! + if ((tel1[0] == '+' and tel2[0] == '+') or + (tel1[0] != '+' and tel2[0] != '+')): + return tel1 == tel2 + if tel2[0] == '+': + tmp = tel1 + tel1 = tel2 + tel2 = tmp + if len(tel1) < len(tel2): + return False + start = len(tel1) - len(tel2) + return tel1[start:] == tel2 + + # 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""" + def __init__(self, fillbasic): + self.vtype = 'vcard' + self.attribs = [] + if fillbasic: + self.attribs.append(['version', None, None, '3.0']) + self.attribs.append(['n', None, None, ';;;;']) + self.attribs.append(['fn', None, None, '']) + def findattrs(self, name): + opts = [] + for x in self.attribs: + if x[0] == name: + opts.append(x) + return opts + def findtype(self, attrs, xtype): + opts = [] + for x in attrs: + if not xtype: + opts.append(x) + elif 'type' in x[2]: + types = x[2]['type'] + for y in types: + if y == xtype: + opts.append(x) + return opts + def findtels(self, teltype): + return self.findtype(self.findattrs('tel'), teltype) + def getuid(self): + opts = self.findattrs('uid') + if opts: + return opts[0][3] + return None + def getname(self): + opts = self.findattrs('name') + if not opts: + opts = self.findattrs('fn') + opt = opts[0] + # fixme: decode the name! + return opt[3] + def setname(self, name): + opts = self.findattrs('name') + if not opts: + opts = self.findattrs('fn') + opt = opts[0] + # fixme: encode the name! + opt[3] = name + def gettel(self, teltype): + opts = self.findtels(teltype) + if opts: + return opts[0][3] + return '' + def settel(self, teltype, num): + opts = self.findtels(teltype) + if opts: + opts[0][3] = num + else: + self.attribs.append( + ['tel', None, {'type': teltype}, num]) + def remove(self): + if hasattr(self, '_container'): + self._container.remove(self) + def merge(self, object): + # TODO: merge changes to attributes + print "Merge entry %s" % self.getuid() + def _parse_error(self, message): + raise Exception('VCARD object: %s' % message) + def _parse_case_insensitive_param(self, name): + return (name == 'type' or + name == 'charset' or + name == 'encoding') + def _parse_case_list(self, list): + out = [] + for x in list: + out.append(x.lower()) + return out + def _parse_add_content(self, name, groups, params, value): + self.attribs.append([name, groups, params, value]) + #print 'Name:%s groups:%s Value:%s' % (name, groups, value) + for x in params: + if self._parse_case_insensitive_param(x): + params[x] = self._parse_case_list(params[x]) + #print '\tParam: %s Value:%s' % (x, params[x]) + def _parse_get_single(self, attr, reqd): + al = self.findattrs(attr) + v = None + if not len(al): + if not reqd: + return None + self._parse_error(_('Missing \"%s\" attribute') % attr) + elif len(al) > 1: + self._parse_error(_('Multiple \"%s\" attributes') % + attr) + else: + v = al[0][3] + return v + def _parse_end(self): + v = self._parse_get_single('n', True) + v = self._parse_get_single('fn', True) + v = self._parse_get_single('version', True) + if v != '2.1' and v != '3.0': + self._parse_error(_('Unrecognized version \'%s\'') % v) + v = self._parse_get_single('uid', False) class VCardSet: """A hacky vCard container/manager object""" + # This module implements a parser for vCard 2.1 and 3.0. + # It does not parse each format strictly, as there are low-level + # differences in the following areas: + # - Whitespace is permitted between content tokens in 2.1, not 3.0 + # - All parameters must be explicitly named in 3.0, not in 2.1 + # + # Input that claims to be of one standard but does not strictly + # comply with that standard is silently accepted. + def __init__(self): self._file_line = 0 self._save_line = None self._current_object = None - pass + self._objects = [] + self._uids = {} + self._object_list = gtk.ListStore(gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING) + self.normalizer = TelNormalizer() def _isalpha(self, c): x = ord(c) @@ -141,26 +330,83 @@ class VCardSet: return c == ' ' or c == '\t' def _issafe(self, c): x = ord(c) - return (self._iswsp(c) - or x == 0x21 or + 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 + return (_iswsp(c) or + x == 0x21 or (x >= 0x23 and x <= 0x7e) or x > 0x7f) - + def _create_object(self, vtype): + if vtype == 'vcard': + return VCard(False) + else: + self._parse_error(_('Unknown value type %s') % vtype) + def _add_object(self, object): + if not hasattr(object, '_container'): + uid = object.getuid() + if uid and uid in self._uids: + mergeit = self._uids[uid] + mergeit.merge(object) + else: + object._container = self + self._objects.append(object) + self._uids[uid] = object + iterx = self._object_list.append( + row = [object, object.getname()]) + object._iter = iterx + def _remove_object(self, object): + if hasattr(object, '_container'): + i = 0 + while i < len(self._objects): + if self._objects[i] == object: + del self._objects[i] + else: + i += 1 + uid = object.getuid() + if uid: + del self._uids[uid] + del object._container + self._object_list.remove(object._iter) + del object._iter 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]) + if name == 'begin': + if self._current_object: + self._parse_error(_('Nested directory object')) + else: + self._current_object = ( + self._create_object(value.lower())) + elif name == 'end': + if not self._current_object: + self._parse_error(_('Mismatched directory ' + 'object END')) + elif self._current_object.vtype != value.lower(): + self._parse_error(_('Mismatched directory ' + 'object type for END')) + else: + self._current_object._parse_end() + self._add_object(self._current_object) + self._current_object = None + elif not self._current_object: + self._parse_error(_('Content line outside ' + 'directory object')) + else: + self._current_object._parse_add_content(name, groups, + params, value) def _parse_error(self, message): - raise Exception('Line %d: %s' % (self._file_line, message)) + raise Exception(_('Line %d: %s') % (self._file_line, message)) + + def _parse_skip_ws(self, line): + pos = 0 + while pos < len(line) and self._iswsp(line[pos]): + pos += 1 + return line[pos:] def _parse_name_groups(self, line): groups = [] name = None @@ -185,7 +431,7 @@ class VCardSet: self._parse_error(_("Empty content name")) name = line[:sep] line = line[sep:] - return (groups, name, line) + return (groups, name.lower(), line) def _parse_param_value(self, line): sep = 0 if line[0] == '"': @@ -240,36 +486,51 @@ class VCardSet: self._parse_error( _("Empty parameter name")) name = line[:sep] + # vCard 2.1 hack + line = self._parse_skip_ws(line[sep:]) if x == '=': - line = line[(sep + 1):] + # vCard 2.1 hack + line = self._parse_skip_ws(line[1:]) break - line = line[sep:] - return (name, values, line) + # vCard 2.1 hack + # Property names without '=' are equivalent + # to type=name + values.append(name) + return ('type', values, line) + + name = name.lower() while True: (x, line) = self._parse_param_value(line) values.append(x) if not len(line): return (name, values, line) + # vCard 2.1 hack + line = self._parse_skip_ws(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')) + # vCard 2.1 hack + line = self._parse_skip_ws(line) while line[0] == ';': (param, values, line) = self._parse_param(line[1:]) if param in params: params[param].extend(values) else: params[param] = values + # vCard 2.1 hack + line = self._parse_skip_ws(line) if line[0] != ':': self._parse_error(_('Malformed content line')) - self._process_line(name, groups, params, line[1:]) + # vCard 2.1 hack + line = self._parse_skip_ws(line[1:]) + self._process_line(name, groups, params, line) def _feed_line(self, line): if not line or not self._iswsp(line[0]): @@ -292,11 +553,32 @@ class VCardSet: self._feed_line(x) self._feed_done() def import_file(self, path): - f = open(path, 'r') + f = open(os.path.expanduser(path), 'r') contents = f.read() f.close() self.import_string(contents) + def search_tel(self, num_norm): + print "Search for %s" % num_norm + if not num_norm: + return (None, None) + for vc in self._objects: + if vc.vtype != 'vcard': + continue + nums = vc.findtels(None) + for ent in nums: + xnum = self.normalizer.normalize(ent[3], 129) + print "Check: %s" % xnum + if self.normalizer.compare(num_norm, xnum): + types = None + if 'type' in ent[2]: + types = ent[2]['type'] + return (vc, types) + return (None, None) + + def get_gtk_model(self): + return self._object_list + class HfConsole: """This is a user interface for the hfpd Bluetooth hands-free @@ -322,6 +604,12 @@ class HfConsole: addr[6:8] + ':' + addr[8:10] + ':' + addr[10:12]) return val.upper() + def create_cellrenderertext(self): + cell = gtk.CellRendererText() + cell.set_property('scale-set', True) + cell.set_property('scale', pango.SCALE_X_LARGE) + return cell + def __init__(self): # Set the Glade file @@ -355,6 +643,8 @@ class HfConsole: "BarDialPad_clicked_cb" : self.bar_dialpad_clicked, "BarContacts_clicked_cb" : self.bar_contacts_clicked, "AgAudioToggle_toggled_cb" : self.bar_ag_audio_toggled, + "AgVoiceRecogToggle_toggled_cb" : + self.bar_ag_voice_recognition_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, @@ -362,10 +652,16 @@ class HfConsole: "AgRedial_clicked_cb" : self.bar_ag_redial_clicked, "ConfigOpen_clicked_cb" : self.bar_config_clicked, "DigitButton_pressed_cb" : self.digit_button_pressed, + "DigitButton_released_cb" : self.digit_button_released, "DigitButton_clicked_cb" : self.digit_button_clicked, + "PhoneNumEntry_changed_cb" : self.phone_num_changed, "PhoneNumBs_clicked_cb" : self.phone_num_bs, "Mute_toggled_cb" : self.mute_toggled, "on_MainWindow_destroy" : gtk.main_quit, + "ContactsDelete_clicked_cb" : + self.contacts_delete_clicked, + "ContactsNew_clicked_cb" : self.contacts_new_clicked, + "ContactsEdit_clicked_cb" : self.contacts_edit_clicked, # Known Devices tab "DevicesClose_clicked_cb" : self.devices_close_clicked, @@ -402,6 +698,16 @@ class HfConsole: "HistoryCancel_clicked_cb" : self.history_cancel_clicked, + # Callbacks for the choose number tab + "ChooseNumberCancel_clicked_cb" : + self.choose_number_cancel_clicked, + "ChooseNumberMobile_clicked_cb" : + self.choose_number_mobile_clicked, + "ChooseNumberHome_clicked_cb" : + self.choose_number_home_clicked, + "ChooseNumberWork_clicked_cb" : + self.choose_number_work_clicked, + # Callbacks for the config tab "ConfigOK_clicked_cb" : self.config_ok_clicked, "ConfigCancel_clicked_cb" : self.config_cancel_clicked, @@ -491,12 +797,12 @@ class HfConsole: self['DevicesList'].set_model(self.ag_list_detail) tvcolumn = gtk.TreeViewColumn(_('Name')) self['DevicesList'].append_column(tvcolumn) - cell = gtk.CellRendererText() + cell = self.create_cellrenderertext() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 0) tvcolumn = gtk.TreeViewColumn(_('Address')) self['DevicesList'].append_column(tvcolumn) - cell = gtk.CellRendererText() + cell = self.create_cellrenderertext() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 1) self['DevicesList'].get_selection().connect('changed', @@ -509,12 +815,12 @@ class HfConsole: self['NewDeviceList'].set_model(self.ag_list_new) tvcolumn = gtk.TreeViewColumn(_('Name')) self['NewDeviceList'].append_column(tvcolumn) - cell = gtk.CellRendererText() + cell = self.create_cellrenderertext() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 0) tvcolumn = gtk.TreeViewColumn(_('Address')) self['NewDeviceList'].append_column(tvcolumn) - cell = gtk.CellRendererText() + cell = self.create_cellrenderertext() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 1) self['NewDeviceList'].get_selection().connect('changed', @@ -543,7 +849,7 @@ class HfConsole: self['ScanResults'].set_model(self.scanresults) tvcolumn = gtk.TreeViewColumn(_('Device')) self['ScanResults'].append_column(tvcolumn) - cell = gtk.CellRendererText() + cell = self.create_cellrenderertext() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 0) self.scanselect = self['ScanResults'].get_selection() @@ -555,7 +861,7 @@ class HfConsole: self['HistoryResults'].set_model(self.historyresults) tvcolumn = gtk.TreeViewColumn(_('Device')) self['HistoryResults'].append_column(tvcolumn) - cell = gtk.CellRendererText() + cell = self.create_cellrenderertext() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 0) self.historyselect = self['HistoryResults'].get_selection() @@ -574,12 +880,33 @@ class HfConsole: self.nobt = False self.nested = False - self.frontpage = 0 #1 + self.frontpage = 1 self.frontnotebook_set(self.frontpage) self.alerter_factory = (lambda x: GtkAlerter(self.gladefile, x)) + self.vcards = VCardSet() + sort = gtk.TreeModelSort(self.vcards.get_gtk_model()) + def name_sort(model, iter1, iter2): + n1 = model.get_value(iter1, 1) + n2 = model.get_value(iter2, 1) + if n1 < n2: + return -1 + if n1 == n2: + return 0 + return 1 + sort.set_sort_func(0, name_sort) + sort.set_sort_column_id(0, gtk.SORT_ASCENDING) + self['ContactsList'].set_model(sort) + tvcolumn = gtk.TreeViewColumn(_('Name')) + self['ContactsList'].append_column(tvcolumn) + cell = self.create_cellrenderertext() + tvcolumn.pack_start(cell, True) + tvcolumn.add_attribute(cell, 'text', 1) + contactselect = self['ContactsList'].get_selection() + contactselect.connect('changed', self.contacts_select) + def command_audio_attach(self, state): if state == self.command_audio_attach_state: return @@ -650,7 +977,7 @@ class HfConsole: except: v = 0 - my_version = 3 + my_version = 4 if v < my_version: self.fatal(_('Version mismatch with hfpd!\n' 'hfpd version: %(hfpdver)d\n' @@ -694,6 +1021,12 @@ class HfConsole: x = self.addr_untransform(x[0]) self.add_audiogateway(x, False) + try: + pass + #self.vcards.import_file('~/.hfconsole.vcf') + except Exception, (ex): + print 'Load contacts: %s' % str(ex) + self.busctl.connect_to_signal("NameOwnerChanged", self.hfpd_lost) self.hfpd.connect_to_signal("SystemStateChanged", @@ -779,6 +1112,8 @@ class HfConsole: self.message = False self.vm = False self.noservice = False + self.voicerecog = False + self.inbandringtone = False def __init__(self, hfc, sbus, hfpd, agpath, box): self.hfc = hfc self.path = agpath @@ -788,6 +1123,7 @@ class HfConsole: self.reset_indicators() self.autoreconnect = 1 self.autoreconnect_flag = False + self.normalizer = TelNormalizer() self.state = 0 self.callstate = 0 @@ -826,6 +1162,14 @@ class HfConsole: sig = self.ag.connect_to_signal("Ring", self.ring_notify) self.sigs.append(sig) + sig = self.ag.connect_to_signal( + "VoiceRecognitionActiveChanged", + self.voice_recognition_changed) + self.sigs.append(sig) + sig = self.ag.connect_to_signal( + "InBandRingToneEnableChanged", + self.inbandringtone_changed) + self.sigs.append(sig) self.addr = self.getprop('Address') self.name = self.getprop('Name') @@ -835,6 +1179,10 @@ class HfConsole: self.state = self.getprop('State') self.callstate = self.getprop('CallState') self.audiostate = self.getprop('AudioState') + self.voicerecog = self.getprop( + 'VoiceRecognitionActive') + self.inbandringtone = self.getprop( + 'InBandRingToneEnable') self.state_changed(self.state, False) self.call_state_changed(self.callstate) self.audio_state_changed(self.audiostate) @@ -872,6 +1220,18 @@ class HfConsole: return self.has_feature('RejectCall') def can_drop_held_udub(self): return self.has_feature('DropHeldUdub') + def has_voice_recognition(self): + return (self.is_connected() and + self.has_feature('VoiceRecognition')) + def get_voice_recognition(self): + return self.voicerecog + def set_voice_recognition(self, st): + self.ag.SetVoiceRecognition(dbus.Boolean(st), + reply_handler=lambda : None, + error_handler=self.command_failed) + def voice_recognition_changed(self, st): + self.voicerecog = st + self.disp.set_state() def selectable(self): return self.is_mine() def select_priority(self): @@ -965,6 +1325,11 @@ class HfConsole: self.disp.set_state() def soundio_state_changed(self): self.disp.set_state() + def inbandringtone_changed(self, st): + self.inbandringtone = st + # So far we don't really care about this, + # because audio gateways get priority for the + # sound card. def autoreconnect_changed(self, value): value = bool(value) if self.autoreconnect_flag != value: @@ -1001,9 +1366,16 @@ class HfConsole: if not self.alerter: self.alerter = self.hfc.alerter_factory(self) self.alerter.update_state() - def ring_notify(self, caller_id, phbook_ent): - self.caller_id = caller_id - self.caller_phbook = phbook_ent + def ring_notify(self, number, number_type, subaddr, satype, + phbook_ent): + if self.caller_id != number: + self.caller_id = number + if not phbook_ent: + num_norm = self.normalizer.normalize( + number, number_type) + phbook_ent = self.hfc.contacts_lookup( + num_norm) + self.caller_phbook = phbook_ent self.alert_open() def waiting_caller_info(self): return (self.caller_id, self.caller_phbook) @@ -1451,19 +1823,47 @@ class HfConsole: text = '#' return text + def digit_button_output_char(self, char): + entry = self['PhoneNumEntry'] + entry.set_text(entry.get_text() + char) + def digit_button_plus_timeout_cancel(self): + if hasattr(self, 'digit_plus_timeout'): + gobject.source_remove(self.digit_plus_timeout) + del self.digit_plus_timeout + def digit_button_plus_timeout_set(self, timeout): + self.digit_plus_timeout = gobject.timeout_add( + timeout, self.digit_button_plus_timeout) + def digit_button_plus_timeout(self): + self.digit_button_plus_timeout_cancel() + self.nested = True + self['DigitButton0'].released() + self.nested = False + self.digit_button_output_char('+') + return False def digit_button_pressed(self, but): ag = self.selected_ag + text = self.digit_button_char(but) if not ag or not ag.can_dtmf(): + if text == '0' and ag and ag.can_dial(): + self.digit_button_plus_timeout_set(1000) return - text = self.digit_button_char(but) ag.dtmf(text) + def digit_button_released(self, but): + if self.nested: + return + text = self.digit_button_char(but) + if text == '0': + self.digit_button_plus_timeout_cancel() def digit_button_clicked(self, but): + if self.nested: + return ag = self.selected_ag if not ag or not ag.can_dial(): return text = self.digit_button_char(but) - entry = self['PhoneNumEntry'] - entry.set_text(entry.get_text() + text) + self.digit_button_output_char(text) + def phone_num_changed(self, widget): + self.ag_configure_gui(self.selected_ag) def phone_num_bs(self, but): entry = self['PhoneNumEntry'] entry.set_text(entry.get_text()[:-1]) @@ -1474,12 +1874,15 @@ class HfConsole: digits = False do_notebook = False dial = False + dial_gray = False hold = False swap = False hangup = False audio = False audio_ip = False audio_dn = False + voicerecog = False + voicerecog_dn = False record = False record_dn = False mainstatus = False @@ -1504,9 +1907,9 @@ class HfConsole: hold = ag.can_hold() do_notebook = not ag.can_dial() hangup = not dial - if hold: - swap = ag.has_waiting_call() - hold = not swap + if hold and ag.has_waiting_call(): + swap = True + hold = False audio = True if ag.has_audio(): audio_dn = True @@ -1518,6 +1921,11 @@ class HfConsole: if batt_page < 0 or batt_page > 5: batt_page = 0 battstatus = False + if (ag.has_voice_recognition() and + ag.can_dial()): + voicerecog = True + voicerecog_dn = ( + ag.get_voice_recognition()) # Display the "no bluetooth" icon if BT is disconnected # and the AG is not connected @@ -1527,10 +1935,24 @@ class HfConsole: status_page = 9 sel_dis = True + # Gray out the dial button if the UI does not have a + # phone number entered or a contact selected + if dial: + pad = True + if not do_notebook and self.frontpage == 1: + pad = False + if pad: + dial_gray = not bool(self['PhoneNumEntry']. + get_text()) + else: + sel = self['ContactsList'].get_selection() + dial_gray = (sel.count_selected_rows() == 0) + def set_vis(widget, visible): if visible: widget.show() else: widget.hide() + self.nested = True self['AgSelector'].set_sensitive(not sel_dis) self['DigitButtonBox'].set_sensitive(digits) self['PhoneNumEntryBox'].set_sensitive(dial) @@ -1540,10 +1962,13 @@ class HfConsole: set_vis(self['AgHoldCall'], hold) set_vis(self['AgSwapCall'], swap) set_vis(self['AgDial'], dial) + self['AgDial'].set_sensitive(not dial_gray) 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['AgVoiceRecogToggle'], voicerecog) + self['AgVoiceRecogToggle'].set_active(voicerecog_dn) set_vis(self['RecordCall'], record) self['RecordCall'].set_active(record_dn) set_vis(self['AgMainStatus'], mainstatus) @@ -1551,6 +1976,7 @@ class HfConsole: set_vis(self['AgBatteryStatus'], battstatus) self['AgBatteryStatus'].set_current_page(batt_page) set_vis(self['MessageIndicator'], message) + self.nested = False if do_notebook: self.frontnotebook_set(0) else: @@ -1558,14 +1984,32 @@ class HfConsole: def bar_ag_dial_clicked(self, widget): ag = self.selected_ag - entry = self['PhoneNumEntry'] if not ag: return if not ag.can_dial(): return - num = entry.get_text() - entry.set_text('') - ag.dial(num) + if self.frontpage == 0: + entry = self['PhoneNumEntry'] + num = entry.get_text() + entry.set_text('') + if not num: + return + ag.dial(num) + else: + sel = self['ContactsList'].get_selection() + (model, iterx) = sel.get_selected() + if not iterx: + return + vcard = model.get_value(iterx, 0) + nums = vcard.findtels(None) + if not len(nums): + pass + elif len(nums) == 1: + num = vcard.gettel(None) + ag.dial(num) + else: + # Ask the user to pick a number + self.choose_number_open(vcard) def bar_ag_scan_clicked(self, widget): self.scan_open() @@ -1581,13 +2025,15 @@ class HfConsole: def frontnotebook_set(self, page): dialpad_but = False contacts_but = False + page = 0 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: + pass + elif page == 1: dialpad_but = True def set_vis(widget, visible): @@ -1600,10 +2046,11 @@ class HfConsole: def bar_dialpad_clicked(self, widget): self.frontpage = 0 - self.frontnotebook_set(0) + self.ag_configure_gui(self.selected_ag) + def bar_contacts_clicked(self, widget): self.frontpage = 1 - self.frontnotebook_set(1) + self.ag_configure_gui(self.selected_ag) def bar_ag_hangup_clicked(self, widget): ag = self.selected_ag @@ -1638,8 +2085,68 @@ class HfConsole: _('Could not open audio connection: %s') % str(ex)) widget.set_active(False) + def bar_ag_voice_recognition_toggled(self, widget): + if (self.nested or + not self.selected_ag or + not self.selected_ag.has_voice_recognition()): + return + self.selected_ag.set_voice_recognition(widget.get_active()) def bar_config_clicked(self, widget): self.config_open() + + def contacts_new_clicked(self, widget): + pass + def contacts_edit_clicked(self, widget): + pass + def contacts_delete_clicked(self, widget): + pass + def contacts_select(self, widget): + self.ag_configure_gui(self.selected_ag) + def contacts_lookup(self, num_norm): + (vcard, types) = self.vcards.search_tel(num_norm) + if not vcard: + return None + nfmt = _('%s (Home)') + for x in types: + if x == 'cell': + nfmt = _('%s (Mobile)') + elif x == 'work': + nfmt = _('%s (Work)') + return nfmt % vcard.getname() + + def choose_number_open(self, vcard): + tel_cell = vcard.gettel('cell') + tel_home = vcard.gettel('home') + tel_work = vcard.gettel('work') + + def fix_button(prefix, tel): + prefix = 'ChooseNumber' + prefix + if tel: + self[prefix + 'Label'].set_text(tel) + self[prefix].show() + else: + self[prefix].hide() + fix_button('Mobile', tel_cell) + fix_button('Home', tel_home) + fix_button('Work', tel_work) + self.topnotebook_set(self['ChooseNumberTab']) + def choose_number_done(self): + self.topnotebook_set(None) + def choose_number_cancel_clicked(self, widget): + self.choose_number_done() + def choose_number_finish(self, num): + self.choose_number_done() + if self.selected_ag and self.selected_ag.can_dial(): + self.selected_ag.dial(num) + def choose_number_mobile_clicked(self, widget): + num = self['ChooseNumberMobileLabel'].get_text() + self.choose_number_finish(num) + def choose_number_home_clicked(self, widget): + num = self['ChooseNumberHomeLabel'].get_text() + self.choose_number_finish(num) + def choose_number_work_clicked(self, widget): + num = self['ChooseNumberWorkLabel'].get_text() + self.choose_number_finish(num) def hfpd_system_msg_clear(self): if hasattr(self, 'system_msg'): self['StatusBar'].remove(self.system_ctx, @@ -2568,6 +3075,7 @@ class HfConsole: self.initconf_set_buttons(False, True) elif page_num == 1: self.initconf_set_buttons(True, False) + if __name__ == "__main__": hwg = HfConsole() hwg.Start() diff --git a/data/hfpd-dbus-doc.cs b/data/hfpd-dbus-doc.cs index ff81138..d4f8457 100644 --- a/data/hfpd-dbus-doc.cs +++ b/data/hfpd-dbus-doc.cs @@ -152,7 +152,7 @@ * - Set the net.sf.nohands.hfpd.AudioGateway.AutoReconnect property * to @c true. * - Register to receive the - * net.sf.nohands.hfpd.AudioGateway.AudiotateChanged signal, and use + * net.sf.nohands.hfpd.AudioGateway.AudioStateChanged signal, and use * the signal handler to start streaming audio to and from the audio * gateway device, using * net.sf.nohands.hfpd.SoundIo.AudioGatewayStart(), or failing that, @@ -395,7 +395,7 @@ namespace net.sf.nohands.hfpd { * finds an unexpected value, it should fail and warn about * a version mismatch. * - * This document describes version 2 of the HFPD D-Bus + * This document describes version 4 of the HFPD D-Bus * interface. * * A version number value in this field covers all HFPD @@ -1663,10 +1663,10 @@ namespace net.sf.nohands.hfpd { * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public Dial(in string phone_num); @@ -1686,10 +1686,10 @@ namespace net.sf.nohands.hfpd { * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public Redial(); @@ -1712,10 +1712,10 @@ namespace net.sf.nohands.hfpd { * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public HangUp(); @@ -1738,36 +1738,200 @@ namespace net.sf.nohands.hfpd { * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public SendDtmf(in byte code); /** - * @brief Send an answer call command to the audio gateway + * @brief Request the audio gateway answer the incoming call * * This method causes an answer call command to be sent to * the audio gateway. If the audio gateway has an * incoming, incomplete, ringing call, the call will be * answered and will become the active call. - * + * @throw net.sf.nohands.hfpd.Error Thrown on any * sort of error, unspecific of the reason of failure. * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public Answer(); /** + * @brief Query the telephone number of the audio gateway + * + * This method queries the telephone number of the + * audio gateway and returns the result. This request is + * described in GSM 07.07 7.1. + * + * @param[out] number The telephone number of the audio + * gateway on the public telephone network. The format + * of this number, e.g. organizational, local, or + * international, is described by the @em type parameter. + * @param[out] type The format of the @em number + * parameter. See GSM 04.10 section 10.5.4.7 for more + * information on the meaning. Some common values include: + * - 129: Unknown type of number in E.164 numbering plan + * - 145: International number in E.164 numbering plan + * @param[out] service Service related to the number. + * Values are described in GSM 07.07 section 7.1, and + * include: + * - 4: voice + * - 5: fax + * + * @throw net.sf.nohands.hfpd.Error Thrown on any + * sort of error, unspecific of the reason of failure. + * + * @note This method will not return until the audio gateway + * has responded to the command. An audio gateway that has + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete + * quickly and should invoke it asynchronously. + */ + public QueryNumber(out string number, out int32 type, + out int32 service); + + /** Structure used for AudioGateway.QueryCurrentCalls() */ + struct CallInfo { + /** @brief Unique identifier of the call */ + int32 idx; + /** @brief Direction: 0=outgoing, 1=incoming */ + int32 dir; + /** + * @brief State of the call + * - 0: active + * - 1: held + * - 2: dialing outgoing call + * - 3: remotely ringing outgoing call + * - 4: incoming unanswered call + * - 5: waiting (on hold) + */ + int32 stat; + /** + * @brief bearer/teleservice + * - 0: voice + * - 1: data + * - 2: fax + * - 3: voice followed by data, voice mode + * - 4: alternating voice/data, voice mode + * - 5: alternating voice/fax, voice mode + * - 6: voice followed by data, data mode + * - 7: alternating voice/data, data mode + * - 8: alternating voice/fax, fax mode + * - 9: unknown + */ + int32 mode; + /** @brief Multiparty: 0=not linked, 1=linked */ + int32 mpty; + /** @brief Telephone number of remote party */ + string number; + /** @brief Telephone number representation type */ + int32 type; + /** @brief Phone book formatted name of remote party */ + string alpha; + }; + + /** + * @brief Query the current calls of the audio gateway + * + * This method queries the current calls on the audio + * gateway, and returns the result. This request is + * described in GSM 07.07 7.17. The audio gateway is not + * required to support this request, but is likely to + * support it if the @c "EnhancedCallStatus" feature in + * the AudioGateway.Features property is set to @c true. + * + * @param[out] calls An array of CallInfo structures, one + * per active call being handled by the audio gateway. + * If no calls are present, the array will be empty. The + * index members may be used + * + * @throw net.sf.nohands.hfpd.Error Thrown on any + * sort of error, unspecific of the reason of failure. + * + * @note This method will not return until the audio gateway + * has responded to the command. An audio gateway that has + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete + * quickly and should invoke it asynchronously. + */ + public QueryCurrentCalls(out CallInfo[] calls); + + /** + * @brief Query the operator of the audio gateway + * + * This method queries the current operator information + * from the audio gateway and returns the result. This + * request is described in GSM 07.07 7.3, but as per + * Bluetooth HFP 1.5, only the long alphanumeric form of + * the operator information is available, e.g. "T-Mobile" + * or "Verizon". + * + * @param[out] mode Operator selection mode. See GSM 07.07 + * @param[out] format Operator display format, always 0. + * @param[out] oper Operator name as an alphanumeric + * string, e.g. "AT&T". + * + * @throw net.sf.nohands.hfpd.Error Thrown on any + * sort of error, unspecific of the reason of failure. + * + * @note This method will not return until the audio gateway + * has responded to the command. An audio gateway that has + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete + * quickly and should invoke it asynchronously. + */ + public QueryOperator(out int32 mode, out int32 format, + out string oper); + + /** + * @brief Request activation or deactivation of voice + * recognition on the audio gateway + * + * Some audio gateway devices support voice recognition, + * and can use the audio link and the microphone of the + * hands-free for voice recognition. Support for voice + * recognition is indicated by the @c "VoiceRecognition" + * feature in the AudioGateway.Features property being + * @c true. + * + * @param[in] activate Set to @c true to request + * activation of voice recognition, @c false to request + * deactivation. + * + * The current state of voice recognition can be retrieved + * from the AudioGateway.VoiceRecognitionActive property. + * Changes to the state, including changes induced by calls + * to this method, are reported by the + * VoiceRecognitionActiveChanged() signal. + * + * @note This method will not return until the audio gateway + * has responded to the command. An audio gateway that has + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete + * quickly and should invoke it asynchronously. + */ + public SetVoiceRecognition(in bool activate); + + /** * @brief Send a drop held / UDUB command to the audio gateway * * This method causes an oddly designed command to be @@ -1779,19 +1943,25 @@ namespace net.sf.nohands.hfpd { * - Otherwise, if there is a call that is on hold, the call * will be terminated. * - * Support for this command is optional, and is indicated - * by the @c "DropHeldUdub" feature in the - * AudioGateway.Features property being @c true. + * Support for this command by the audio gateway is + * optional, and is indicated by the @c "DropHeldUdub" + * feature in the AudioGateway.Features property being @c true. + * + * Many audio gateways do not perform a unique action when + * this request is issued, and will often reject or ignore + * the incoming call. In general, the UDUB request is + * implemented so inconsistently that it is not worth + * presenting to the user. * * @throw net.sf.nohands.hfpd.Error Thrown on any * sort of error, unspecific of the reason of failure. * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public CallDropHeldUdub(); @@ -1807,24 +1977,53 @@ namespace net.sf.nohands.hfpd { * incoming, unanswered call, it will be connected and * will become the active call. * - * Support for this command is optional, and is indicated - * by the @c "SwapDropActive" feature in the - * AudioGateway.Features property being @c true. + * Support for this command by the audio gateway is + * optional, and is indicated by the @c "SwapDropActive" + * feature in the AudioGateway.Features property being @c true. * * @throw net.sf.nohands.hfpd.Error Thrown on any * sort of error, unspecific of the reason of failure. * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public CallSwapDropActive(); /** + * @brief Send a drop specific call command to the audio + * gateway + * + * This method causes a command to be sent to the audio + * gateway requesting that it drop a specific call. + * This method is only useful when the audio gateway has the + * @c "EnhancedCallStatus" feature, in which case call index + * numbers can be retrieved via QueryCurrentCalls(). + * Additionally, the audio gateway is only likely to support + * the command if it has the @c "EnhancedCallControl" and + * @c "DropIndex" features in the AudioGateway.Features + * property being @c true. + * + * @param[in] idx Index of the call to be dropped. + * + * @throw net.sf.nohands.hfpd.Error Thrown on any + * sort of error, unspecific of the reason of failure. + * + * @note This method will not return until the audio gateway + * has responded to the command. An audio gateway that has + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete + * quickly and should invoke it asynchronously. + */ + public CallDropIndex(int idx); + + /** * @brief Send a hold active call command to the audio gateway * * This method causes a command to be sent to the audio @@ -1835,19 +2034,19 @@ namespace net.sf.nohands.hfpd { * incoming, unanswered call, it will be connected and * will become the active call. * - * Support for this command is optional, and is indicated - * by the @c "SwapHoldActive" feature in the - * AudioGateway.Features property being @c true. + * Support for this command by the audio gateway is + * optional, and is indicated by the @c "SwapHoldActive" + * feature in the AudioGateway.Features property being @c true. * * @throw net.sf.nohands.hfpd.Error Thrown on any * sort of error, unspecific of the reason of failure. * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public CallSwapHoldActive(); @@ -1858,10 +2057,10 @@ namespace net.sf.nohands.hfpd { * This method causes a link calls command to be sent to the * audio gateway. If the audio gateway has an active call, * and a call placed on hold, the calls will be linked - * into a single three-way call. + * into a single three-way multiparty call. * - * Support for this command is optional, and is indicated - * by the @c "Link" feature in the + * Support for this command by the audio gateway is + * optional, and is indicated by the @c "Link" feature in the * AudioGateway.Features property being @c true. * * @throw net.sf.nohands.hfpd.Error Thrown on any @@ -1869,15 +2068,47 @@ namespace net.sf.nohands.hfpd { * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public CallLink(); /** + * @brief Send a private consult command to the audio + * gateway + * + * This method causes a command to be sent to the audio + * gateway requesting that it remove all calls from the + * multiparty call except for a specific call. All calls + * removed from the multiparty will be placed on hold. + * This method is only useful when the audio gateway has the + * @c "EnhancedCallStatus" feature, in which case call index + * numbers can be retrieved via QueryCurrentCalls(). + * Additionally, the audio gateway is only likely to support + * the command if it has the @c "EnhancedCallControl" and + * @c "PrivateConsult" features in the AudioGateway.Features + * property set to @c true. + * + * @param[in] idx Index of the call to go to private + * consult mode. + * + * @throw net.sf.nohands.hfpd.Error Thrown on any + * sort of error, unspecific of the reason of failure. + * + * @note This method will not return until the audio gateway + * has responded to the command. An audio gateway that has + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete + * quickly and should invoke it asynchronously. + */ + public CallPrivateConsult(int idx); + + /** * @brief Send a transfer call command to the audio gateway * * This method causes a transfer call command to be sent to @@ -1885,19 +2116,19 @@ namespace net.sf.nohands.hfpd { * call, and a call placed on hold, the calls will be linked * to each other, and disconnected from the audio gateway. * - * Support for this command is optional, and is indicated - * by the @c "Transfer" feature in the - * AudioGateway.Features property being @c true. + * Support for this command by the audio gateway is + * optional, and is indicated by the @c "Transfer" feature + * in the AudioGateway.Features property being @c true. * * @throw net.sf.nohands.hfpd.Error Thrown on any * sort of error, unspecific of the reason of failure. * * @note This method will not return until the audio gateway * has responded to the command. An audio gateway that has - * moved out of radio range may not be identified as being - * inaccessible for several seconds, and a poorly designed - * audio gateway may fail to respond altogether. D-Bus - * clients should not expect this method to complete + * moved out of Bluetooth radio range may not be identified + * as being inaccessible for several seconds, and a poorly + * designed audio gateway may fail to respond altogether. + * D-Bus clients should not expect this method to complete * quickly and should invoke it asynchronously. */ public CallTransfer(); @@ -1975,6 +2206,61 @@ namespace net.sf.nohands.hfpd { const byte AudioState; /** + * @brief Voice recognition state of the device + * + * This property can be accessed using the + * @ref property "standard D-Bus property interface". + * + * This read-only property describes whether the audio + * gateway has its voice recognition feature activated. + * When activated, the audio gateway will use the + * audio link to feed its built-in voice recogintion + * mechanism, and recognize commands spoken into the + * microphone of the hands-free. + * + * This property is present on all AudioGateway objects, + * but is only useful with devices that support voice + * recogintion. Such devices will have the + * @c "VoiceRecognition" feature in AudioGateway.Features + * set to @c true. + * + * Changes to this value are reported by the + * VoiceRecognitionActiveChanged() signal. + * + * This property is read-only, but changes may be + * effected by SetVoiceRecognition(). + */ + const bool VoiceRecognitionActive; + + /** + * @brief In-Band ring tone state of the device + * + * This property can be accessed using the + * @ref property "standard D-Bus property interface". + * + * This read-only property describes whether the audio + * gateway has its in-band ring tone feature enabled. + * When in-band ring tones are enabled, the audio gateway + * will use the audio link to play its normal alerting + * ring tone, which will be played over the speakers of + * the hands-free. + * + * Changes to this value are reported by the + * InBandRingToneEnableChanged() signal. + * + * This property is read-only. Changes may only be + * effected by changing the setting on the audio gateway + * itself, typically through an option in the user + * interface of the audio gateway. + * + * This setting can be used to inhibit playback of alert + * sounds in situations where alert sounds take priority + * over the audio gateway audio link, or coexist with + * the audio link. + */ + const bool InBandRingToneEnable; + + /** * @brief D-Bus ownership state of the device * * This property can be accessed using the @@ -2113,12 +2399,13 @@ namespace net.sf.nohands.hfpd { * - EnhancedCallControl * - DropHeldUdub * - SwapDropActive - * - DropActive + * - DropIndex * - SwapHoldActive * - PrivateConsult * - Link * - Transfer * - CallSetupIndicator + * - CallHeldIndicator * - SignalStrengthIndicator * - RoamingIndicator * - BatteryChargeIndicator @@ -2239,15 +2526,28 @@ namespace net.sf.nohands.hfpd { * AudioGateway should use this signal to start alerting * the user and possibly start playing a ring tone. * - * @param[out] caller_id The caller line identification - * reported by the audio gateway, typically a set of digits - * occasionally prefixed by a "+". The D-Bus client may - * use this value as a key for a phone book search. + * @param[out] number The caller line identification + * reported by the audio gateway. The format of this + * number, e.g. organizational, local, or international, + * is described by the @em number_type parameter. + * The D-Bus client may use this value as a key for a + * phone book search. + * @param[out] number_type The format of the @em number + * parameter. See GSM 04.10 section 10.5.4.7 for more + * information on the meaning. Some common values include: + * - 129: Unknown type of number in E.164 numbering plan + * - 145: International number in E.164 numbering plan + * @param[out] subaddr The ISDN subaddress of the caller. + * This is unused in most situations. For more information, + * see ITU-T recommendation I.330. + * @param[out] subaddr_type The format of the @em subaddr + * parameter, in the same format as the @em number_type + * parameter. * @param[out] caller_name Some audio gateway devices * have built-in phone books, which may have an entry for * the phone number of the caller. In such cases, this - * parameter contains the caller name reported by the - * audio gateway. + * parameter contains the contact name associated with the + * phone number as reported by the audio gateway. * * The D-Bus client has a number of options for dealing * with the incoming call, although some are optionally @@ -2256,7 +2556,10 @@ namespace net.sf.nohands.hfpd { * - Reject the call with HangUp(). * - Report User Determined User Busy with CallDropHeldUdub(). */ - public signal Ring(out string caller_id, + public signal Ring(out string number, + out int32 number_type, + out string subaddr, + out int32 subaddr_type, out string caller_name); /** @@ -2298,5 +2601,41 @@ namespace net.sf.nohands.hfpd { * gateway device. */ public signal NameResolved(out string name); + + /** + * @brief Notification of voice recognition activatation or + * deactivatation + * + * When the audio gateway activates or deactivates its + * built-in voice recognition feature, this signal will + * be emitted as a notification. + * + * @param[out] state Set to @em true if voice recognition + * is being activated, @em false if deactivated. + * + * The voice recognition state is also available from + * the AudioGateway.VoiceRecognitionActive property. + */ + public signal VoiceRecognitionActiveChanged(out bool state); + + /** + * @brief Notification of change to the in-band ring tone + * setting of the audio gateway + * + * When the audio gateway's in-band ring tone setting is + * changed, this signal will be emitted as a notification. + * + * @param[out] state The new value of the in-band ring tone + * setting. Set to @em true if the in-band ring tones are + * enabled, @em false if disabled. + * + * The in-band ring tone state is also available from + * the AudioGateway.InBandRingToneEnable property. + * + * The in-band ring tone setting may only be changed by + * the audio gateway itself, typically through an option + * in the user interface of the audio gateway. + */ + public signal InBandRingToneEnableChanged(out bool state); } } diff --git a/hfpd/objects.cpp b/hfpd/objects.cpp index 30c98a7..294d042 100644 --- a/hfpd/objects.cpp +++ b/hfpd/objects.cpp @@ -197,13 +197,15 @@ SendReplyErrorInfo(DBusMessage *msgp, libhfp::ErrorInfo &error) } struct AgPendingCommand { - ListItem m_links; - DBusMessage *m_msg; - HfpPendingCommand *m_pend; - AudioGateway *m_ag; + ListItem m_links; + DBusMessage *m_msg; + HfpPendingCommand *m_pend; + AudioGateway *m_ag; + + Callback<void, AgPendingCommand *, void *> cb_success; void HfpCommandResult(HfpPendingCommand *pendp, - ErrorInfo *error, const char *info) { + ErrorInfo *error, void *info) { assert(pendp == m_pend); assert(m_msg); assert(m_ag); @@ -213,7 +215,11 @@ struct AgPendingCommand { * structure here. */ if (!error) { - (void) m_ag->SendReplyArgs(m_msg, DBUS_TYPE_INVALID); + if (cb_success.Registered()) + cb_success(this, info); + else + (void) m_ag->SendReplyArgs(m_msg, + DBUS_TYPE_INVALID); } else { (void) m_ag->SendReplyErrorInfo(m_msg, *error); } @@ -268,6 +274,11 @@ AudioGateway(HandsFree *hfp, HfpSession *sessp, char *name) sessp->cb_NotifyCall.Register(this, &AudioGateway::NotifyCall); sessp->cb_NotifyIndicator.Register(this, &AudioGateway::NotifyIndicator); + sessp->cb_NotifyVoiceRecog.Register(this, + &AudioGateway::NotifyVoiceRecog); + sessp->cb_NotifyVolume.Register(this, &AudioGateway::NotifyVolume); + sessp->cb_NotifyInBandRingTone.Register(this, + &AudioGateway::NotifyInBandRingTone); sessp->cb_NotifyDestroy.Register(this, &AudioGateway::NotifyDestroy); } @@ -525,22 +536,31 @@ NotifyCall(libhfp::HfpSession *sessp, bool act, bool waiting, bool ring) UpdateCallState(CallState()); if (ring) { - const char *num = 0, *alpha = 0; - const GsmClipPhoneNumber *clip; + const char *num = 0, *sa = 0, *alpha = 0; + const GsmClipResult *clip; + int32_t numtype = 0, satype = 0; clip = sessp->WaitingCallIdentity(); - if (clip) { + if (clip && (clip->cli_validity == 0)) { num = clip->number; + numtype = clip->type; + sa = clip->subaddr; + satype = clip->satype; alpha = clip->alpha; } if (!num) num = ""; + if (!sa) + sa = ""; if (!alpha) alpha = ""; SendSignalArgs(HFPD_AUDIOGATEWAY_INTERFACE_NAME, "Ring", DBUS_TYPE_STRING, &num, + DBUS_TYPE_INT32, &numtype, + DBUS_TYPE_STRING, &sa, + DBUS_TYPE_INT32, &satype, DBUS_TYPE_STRING, &alpha, DBUS_TYPE_INVALID); } @@ -601,6 +621,31 @@ NotifyAudioConnection(libhfp::HfpSession *sessp, libhfp::ErrorInfo *error) } void AudioGateway:: +NotifyVoiceRecog(libhfp::HfpSession *sessp, bool active) +{ + dbus_bool_t st = active; + (void) SendSignalArgs(HFPD_AUDIOGATEWAY_INTERFACE_NAME, + "VoiceRecognitionActiveChanged", + DBUS_TYPE_BOOLEAN, &st, + DBUS_TYPE_INVALID); +} + +void AudioGateway:: +NotifyVolume(libhfp::HfpSession *sessp, bool mic, bool speaker) +{ +} + +void AudioGateway:: +NotifyInBandRingTone(libhfp::HfpSession *sessp, bool enabled) +{ + dbus_bool_t st = enabled; + (void) SendSignalArgs(HFPD_AUDIOGATEWAY_INTERFACE_NAME, + "InBandRingToneEnableChanged", + DBUS_TYPE_BOOLEAN, &st, + DBUS_TYPE_INVALID); +} + +void AudioGateway:: NotifyDestroy(BtManaged *objp) { const char *path; @@ -788,6 +833,216 @@ Answer(DBusMessage *msgp) m_sess->CmdAnswer(&error))); } +void AudioGateway:: +QueryNumberComplete(AgPendingCommand *agpcp, void *result) +{ + GsmCnumResult *res = (GsmCnumResult *) result; + int32_t type, service; + const char *number = 0; + + assert(res); + + number = res->number; + type = res->type; + service = res->service; + + if (!number) + number = ""; + (void) SendReplyArgs(agpcp->m_msg, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INT32, &type, + DBUS_TYPE_INT32, &service, + DBUS_TYPE_INVALID); + +} + +bool AudioGateway:: +QueryNumber(DBusMessage *msgp) +{ + AgPendingCommand *agpcp = 0; + ErrorInfo error; + + if (!CreatePendingCommand(msgp, agpcp)) + return false; + + agpcp->cb_success.Register(this, &AudioGateway::QueryNumberComplete); + return DoPendingCommand(agpcp, error, + m_sess->CmdQueryNumber(&error)); +} + +void AudioGateway:: +QueryOperatorComplete(AgPendingCommand *agpcp, void *result) +{ + GsmCopsResult *res = (GsmCopsResult *) result; + int32_t mode, format; + char ibuf[16]; + const char *oper = 0; + + assert(res); + + mode = res->mode; + format = res->format; + if (format == 2) { + sprintf(ibuf, "%d", res->nonalpha); + oper = ibuf; + } else { + oper = res->alpha; + } + + if (!oper) + oper = ""; + (void) SendReplyArgs(agpcp->m_msg, + DBUS_TYPE_INT32, &mode, + DBUS_TYPE_INT32, &format, + DBUS_TYPE_STRING, &oper, + DBUS_TYPE_INVALID); + +} + +bool AudioGateway:: +QueryOperator(DBusMessage *msgp) +{ + AgPendingCommand *agpcp = 0; + ErrorInfo error; + + if (!CreatePendingCommand(msgp, agpcp)) + return false; + + agpcp->cb_success.Register(this, &AudioGateway::QueryOperatorComplete); + return DoPendingCommand(agpcp, error, + m_sess->CmdQueryOperator(&error)); +} + +void AudioGateway:: +QueryCurrentCallsComplete(AgPendingCommand *agpcp, void *result) +{ + ListItem *headp, *listp; + GsmClccResult *resp; + DBusMessage *replyp; + ErrorInfo error; + DBusMessageIter mi, ami, smi; + + int32_t idx, dir, stat, mode, mpty, type; + const char *number, *alpha; + + headp = (ListItem *) result; + replyp = NewMethodReturn(agpcp->m_msg); + if (!replyp) + goto failed; + + dbus_message_iter_init_append(replyp, &mi); + if (!dbus_message_iter_open_container(&mi, + DBUS_TYPE_ARRAY, + DBUS_STRUCT_BEGIN_CHAR_AS_STRING + DBUS_TYPE_INT32_AS_STRING + DBUS_TYPE_INT32_AS_STRING + DBUS_TYPE_INT32_AS_STRING + DBUS_TYPE_INT32_AS_STRING + DBUS_TYPE_INT32_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_INT32_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_STRUCT_END_CHAR_AS_STRING, + &ami)) { + error.SetNoMem(); + goto failed; + } + + ListForEach(listp, headp) { + resp = GetContainer(listp, GsmClccResult, m_links); + + idx = resp->idx; + dir = resp->dir; + stat = resp->stat; + mode = resp->mode; + mpty = resp->mpty; + number = resp->number; + type = resp->type; + alpha = resp->alpha; + + if (!number) + number = ""; + if (!alpha) + alpha = ""; + + if (!dbus_message_iter_open_container(&ami, + DBUS_TYPE_STRUCT, + 0, + &smi) || + !dbus_message_iter_append_basic(&smi, + DBUS_TYPE_INT32, + &idx) || + !dbus_message_iter_append_basic(&smi, + DBUS_TYPE_INT32, + &dir) || + !dbus_message_iter_append_basic(&smi, + DBUS_TYPE_INT32, + &stat) || + !dbus_message_iter_append_basic(&smi, + DBUS_TYPE_INT32, + &mode) || + !dbus_message_iter_append_basic(&smi, + DBUS_TYPE_INT32, + &mpty) || + !dbus_message_iter_append_basic(&smi, + DBUS_TYPE_STRING, + &number) || + !dbus_message_iter_append_basic(&smi, + DBUS_TYPE_INT32, + &type) || + !dbus_message_iter_append_basic(&smi, + DBUS_TYPE_STRING, + &alpha) || + !dbus_message_iter_close_container(&ami, &smi)) { + error.SetNoMem(); + goto failed; + } + } + + if (!dbus_message_iter_close_container(&mi, &ami) || + !SendMessage(replyp)) + goto failed; + + return; + +failed: + SendReplyErrorInfo(agpcp->m_msg, error); +} + +bool AudioGateway:: +QueryCurrentCalls(DBusMessage *msgp) +{ + AgPendingCommand *agpcp = 0; + ErrorInfo error; + + if (!CreatePendingCommand(msgp, agpcp)) + return false; + + agpcp->cb_success.Register(this, + &AudioGateway::QueryCurrentCallsComplete); + return DoPendingCommand(agpcp, error, + m_sess->CmdQueryCurrentCalls(&error)); +} + +bool AudioGateway:: +SetVoiceRecognition(DBusMessage *msgp) +{ + DBusMessageIter mi; + bool res; + dbus_bool_t st; + AgPendingCommand *agpcp = 0; + ErrorInfo error; + + res = dbus_message_iter_init(msgp, &mi); + assert(res); + assert(dbus_message_iter_get_arg_type(&mi) == DBUS_TYPE_BOOLEAN); + dbus_message_iter_get_basic(&mi, &st); + + return (CreatePendingCommand(msgp, agpcp) && + DoPendingCommand(agpcp, error, + m_sess->CmdSetVoiceRecog(st, &error))); +} + bool AudioGateway:: CallDropHeldUdub(DBusMessage *msgp) { @@ -811,6 +1066,25 @@ CallSwapDropActive(DBusMessage *msgp) } bool AudioGateway:: +CallDropIndex(DBusMessage *msgp) +{ + DBusMessageIter mi; + int32_t index; + bool res; + AgPendingCommand *agpcp = 0; + ErrorInfo error; + + res = dbus_message_iter_init(msgp, &mi); + assert(res); + assert(dbus_message_iter_get_arg_type(&mi) == DBUS_TYPE_INT32); + dbus_message_iter_get_basic(&mi, &index); + + return (CreatePendingCommand(msgp, agpcp) && + DoPendingCommand(agpcp, error, + m_sess->CmdCallDropIndex(index, &error))); +} + +bool AudioGateway:: CallSwapHoldActive(DBusMessage *msgp) { AgPendingCommand *agpcp = 0; @@ -833,6 +1107,25 @@ CallLink(DBusMessage *msgp) } bool AudioGateway:: +CallPrivateConsult(DBusMessage *msgp) +{ + DBusMessageIter mi; + int32_t index; + bool res; + AgPendingCommand *agpcp = 0; + ErrorInfo error; + + res = dbus_message_iter_init(msgp, &mi); + assert(res); + assert(dbus_message_iter_get_arg_type(&mi) == DBUS_TYPE_INT32); + dbus_message_iter_get_basic(&mi, &index); + + return (CreatePendingCommand(msgp, agpcp) && + DoPendingCommand(agpcp, error, + m_sess->CmdCallPrivateConsult(index, &error))); +} + +bool AudioGateway:: CallTransfer(DBusMessage *msgp) { AgPendingCommand *agpcp = 0; @@ -866,6 +1159,20 @@ GetAudioState(DBusMessage *msgp, uint8_t &val) } bool AudioGateway:: +GetVoiceRecognitionActive(DBusMessage *msgp, bool &val) +{ + val = m_sess->GetVoiceRecogActive(); + return true; +} + +bool AudioGateway:: +GetInBandRingToneEnable(DBusMessage *msgp, bool &val) +{ + val = m_sess->GetInBandRingToneEnable(); + return true; +} + +bool AudioGateway:: GetClaimed(DBusMessage *msgp, bool &val) { val = (m_owner != 0); @@ -1003,8 +1310,8 @@ GetFeatures(DBusMessage *msgp, const DbusProperty *propp, m_sess->FeatureDropHeldUdub()) || !AddStringBool(ami, "SwapDropActive", m_sess->FeatureSwapDropActive()) || - !AddStringBool(ami, "DropActive", - m_sess->FeatureDropActive()) || + !AddStringBool(ami, "DropIndex", + m_sess->FeatureDropIndex()) || !AddStringBool(ami, "SwapHoldActive", m_sess->FeatureSwapHoldActive()) || !AddStringBool(ami, "PrivateConsult", @@ -1015,6 +1322,8 @@ GetFeatures(DBusMessage *msgp, const DbusProperty *propp, m_sess->FeatureTransfer()) || !AddStringBool(ami, "CallSetupIndicator", m_sess->FeatureIndCallSetup()) || + !AddStringBool(ami, "CallHeldIndicator", + m_sess->FeatureIndCallHeld()) || !AddStringBool(ami, "SignalStrengthIndicator", m_sess->FeatureIndSignalStrength()) || !AddStringBool(ami, "RoamingIndicator", @@ -1814,7 +2123,7 @@ done: bool HandsFree:: GetVersion(DBusMessage *msgp, dbus_uint32_t &val) { - val = 3; + val = 4; return true; } @@ -2070,7 +2379,12 @@ bool HandsFree:: SetReportCapabilities(DBusMessage *msgp, const dbus_uint32_t &val, bool &doreply) { - m_hfp->SetCaps(val); + ErrorInfo error; + + if (!m_hfp->SetCaps(val, &error)) { + doreply = false; + return SendReplyErrorInfo(msgp, error); + } return true; } diff --git a/hfpd/objects.h b/hfpd/objects.h index d17536e..037af82 100644 --- a/hfpd/objects.h +++ b/hfpd/objects.h @@ -85,6 +85,12 @@ class AudioGateway : public HfpdExportObject { bool DoSetAutoReconnect(bool value, libhfp::ErrorInfo *error = 0); + void QueryNumberComplete(class AgPendingCommand *agpcp, void *result); + void QueryOperatorComplete(class AgPendingCommand *agpcp, + void *result); + void QueryCurrentCallsComplete(class AgPendingCommand *agpcp, + void *result); + public: AudioGateway(HandsFree *hfp, libhfp::HfpSession *sessp, char *name); virtual ~AudioGateway(); @@ -114,6 +120,9 @@ public: int val); void NotifyAudioConnection(libhfp::HfpSession *sessp, libhfp::ErrorInfo *reason); + void NotifyVoiceRecog(libhfp::HfpSession *sessp, bool active); + void NotifyVolume(libhfp::HfpSession *sessp, bool mic, bool speaker); + void NotifyInBandRingTone(libhfp::HfpSession *sessp, bool enabled); void NotifyDestroy(libhfp::BtManaged *sessp); void NameResolved(void); @@ -127,16 +136,24 @@ public: bool HangUp(DBusMessage *msgp); bool SendDtmf(DBusMessage *msgp); bool Answer(DBusMessage *msgp); + bool QueryNumber(DBusMessage *msgp); + bool QueryOperator(DBusMessage *msgp); + bool QueryCurrentCalls(DBusMessage *msgp); + bool SetVoiceRecognition(DBusMessage *msgp); bool CallDropHeldUdub(DBusMessage *msgp); bool CallSwapDropActive(DBusMessage *msgp); + bool CallDropIndex(DBusMessage *msgp); bool CallSwapHoldActive(DBusMessage *msgp); bool CallLink(DBusMessage *msgp); + bool CallPrivateConsult(DBusMessage *msgp); bool CallTransfer(DBusMessage *msgp); /* D-Bus Property related methods */ bool GetState(DBusMessage *msgp, uint8_t &val); bool GetCallState(DBusMessage *msgp, uint8_t &val); bool GetAudioState(DBusMessage *msgp, uint8_t &val); + bool GetVoiceRecognitionActive(DBusMessage *msgp, bool &val); + bool GetInBandRingToneEnable(DBusMessage *msgp, bool &val); bool GetClaimed(DBusMessage *msgp, bool &val); bool GetVoluntaryDisconnect(DBusMessage *msgp, bool &val); bool GetAddress(DBusMessage *msgp, const DbusProperty *propp, @@ -163,10 +180,16 @@ static const DbusMethod g_AudioGateway_methods[] = { DbusMethodEntry(AudioGateway, HangUp, "", ""), DbusMethodEntry(AudioGateway, SendDtmf, "y", ""), DbusMethodEntry(AudioGateway, Answer, "", ""), + DbusMethodEntry(AudioGateway, QueryNumber, "", "sii"), + DbusMethodEntry(AudioGateway, QueryOperator, "", "iis"), + DbusMethodEntry(AudioGateway, QueryCurrentCalls, "", "a(iiiiisis)"), + DbusMethodEntry(AudioGateway, SetVoiceRecognition, "b", ""), DbusMethodEntry(AudioGateway, CallDropHeldUdub, "", ""), DbusMethodEntry(AudioGateway, CallSwapDropActive, "", ""), + DbusMethodEntry(AudioGateway, CallDropIndex, "i", ""), DbusMethodEntry(AudioGateway, CallSwapHoldActive, "", ""), DbusMethodEntry(AudioGateway, CallLink, "", ""), + DbusMethodEntry(AudioGateway, CallPrivateConsult, "i", ""), DbusMethodEntry(AudioGateway, CallTransfer, "", ""), { 0, } }; @@ -177,9 +200,12 @@ static const DbusMethod g_AudioGateway_signals[] = { DbusSignalEntry(AudioStateChanged, "y"), DbusSignalEntry(ClaimStateChanged, "b"), DbusSignalEntry(AutoReconnectChanged, "b"), - DbusSignalEntry(Ring, "ss"), + DbusSignalEntry(Ring, "sisis"), DbusSignalEntry(IndicatorChanged, "si"), DbusSignalEntry(NameResolved, "s"), + DbusSignalEntry(VoiceRecognitionActiveChanged, "b"), + DbusSignalEntry(InBandRingToneEnableChanged, "b"), + DbusSignalEntry(VolumeChanged, "yy"), { 0, } }; @@ -194,6 +220,12 @@ static const DbusProperty g_AudioGateway_properties[] = { GetCallState), DbusPropertyMarshallImmutable(uint8_t, AudioState, AudioGateway, GetAudioState), + DbusPropertyMarshallImmutable(bool, VoiceRecognitionActive, + AudioGateway, + GetVoiceRecognitionActive), + DbusPropertyMarshallImmutable(bool, InBandRingToneEnable, + AudioGateway, + GetInBandRingToneEnable), DbusPropertyMarshallImmutable(bool, Claimed, AudioGateway, GetClaimed), DbusPropertyMarshallImmutable(bool, VoluntaryDisconnect, AudioGateway, diff --git a/include/libhfp/bt.h b/include/libhfp/bt.h index 9065f15..8c0a2a2 100644 --- a/include/libhfp/bt.h +++ b/include/libhfp/bt.h @@ -172,6 +172,8 @@ enum { LIBHFP_ERROR_BT_NOT_CONNECTED_SCO, /** System or service has already been started */ LIBHFP_ERROR_BT_ALREADY_STARTED, + /** Operation timed out */ + LIBHFP_ERROR_BT_TIMEOUT, /** Protocol has been violated */ LIBHFP_ERROR_BT_PROTOCOL_VIOLATION, /** User-initiated disconnection of device */ diff --git a/include/libhfp/hfp.h b/include/libhfp/hfp.h index 5d67006..64167df 100644 --- a/include/libhfp/hfp.h +++ b/include/libhfp/hfp.h @@ -63,7 +63,7 @@ class HfpSession; * callbacks, e.g. HfpService::cb_HfpSessionFactory, read known devices * from a configuration file, and instantiate an HfpSession object for * each with GetSession(). The HfpSession objects can be marked - * auto-reconnect, via HfpSession::SetAutoReconnect(), so that they may + * auto-reconnect, via RfcommSession::SetAutoReconnect(), so that they may * be automatically connected when they enter radio range. * * Whenever Hands-Free Profile service is active, HfpService maintains @@ -95,26 +95,12 @@ private: char *m_svc_desc; sdp_record_t *m_sdp_rec; - void AutoReconnectTimeout(TimerNotifier*); - bool m_sco_enable; - ListItem m_autoreconnect_list; - int m_autoreconnect_timeout; - bool m_autoreconnect_set; - TimerNotifier *m_autoreconnect_timer; - - ListItem m_autoreconnect_now_list; - bool m_autoreconnect_now_set; - TimerNotifier *m_autoreconnect_now_timer; - bool m_complaint_sco_mtu; bool m_complaint_sco_vs; bool m_complaint_sco_listen; - void AddAutoReconnect(HfpSession *sessp, bool now = false); - void RemoveAutoReconnect(HfpSession *sessp); - bool ScoListen(ErrorInfo *error); void ScoCleanup(void); void ScoListenNotify(SocketNotifier *, int fh); @@ -132,7 +118,7 @@ private: public: - HfpService(int caps = 15); + HfpService(int caps = 63); virtual ~HfpService(); /** @@ -435,15 +421,24 @@ public: */ bool SetScoEnabled(bool sco_enable, ErrorInfo *error = 0); + /** Query advertised hands-free capabilities */ int GetCaps(void) const { return m_brsf_my_caps; } - void SetCaps(int caps) { m_brsf_my_caps = caps; } + /** Set advertised hands-free capabilities */ + bool SetCaps(int caps, ErrorInfo *error = 0); + + /** Query service name advertised in SDP record */ const char *GetServiceName(void) const { return m_svc_name ? m_svc_name : "Handsfree"; } + + /** Set service name advertised in SDP record */ bool SetServiceName(const char *desc, ErrorInfo *error = 0); + /** Query service description advertised in SDP record */ const char *GetServiceDesc(void) const { return m_svc_desc ? m_svc_desc : ""; } + + /** Set service description advertised in SDP record */ bool SetServiceDesc(const char *desc, ErrorInfo *error = 0); static bool IsDeviceClassHf(uint32_t devclass) { @@ -454,12 +449,16 @@ public: } }; -class GsmClipPhoneNumber { - void *operator new(size_t nb, size_t extra); +class GsmResult { +protected: + char *src_string; size_t extra; + void *operator new(size_t nb, const char *src_string, size_t len); +public: + void operator delete(void *mem); +}; - static GsmClipPhoneNumber *Create(const char *src); - +class GsmClipResult : public GsmResult { public: const char *number; int type; @@ -468,11 +467,47 @@ public: const char *alpha; int cli_validity; - void operator delete(void *mem); - static GsmClipPhoneNumber *Parse(const char *buffer); - static GsmClipPhoneNumber *ParseCcwa(const char *buffer); - bool Compare(const GsmClipPhoneNumber *clip) const; - GsmClipPhoneNumber *Duplicate(void) const; + static GsmClipResult *Parse(const char *buffer); + static GsmClipResult *ParseCcwa(const char *buffer); + bool Compare(const GsmClipResult *clip) const; + GsmClipResult *Duplicate(void) const; +}; + +class GsmCnumResult : public GsmResult { +public: + const char *alpha; + const char *number; + int type; + int speed; + int service; + + static GsmCnumResult *Parse(const char *buffer); +}; + +class GsmCopsResult : public GsmResult { +public: + int mode; + int format; + const char *alpha; + int nonalpha; + + static GsmCopsResult *Parse(const char *buffer); +}; + +class GsmClccResult : public GsmResult { +public: + ListItem m_links; + + int idx; + int dir; + int stat; + int mode; + int mpty; + const char *number; + int type; + const char *alpha; + + static GsmClccResult *Parse(const char *buffer); }; class AtCommand; @@ -508,7 +543,7 @@ class AtCommand; * successful call to the Cancel() method. */ class HfpPendingCommand - : public Callback<void, HfpPendingCommand*, ErrorInfo*, const char *> { + : public Callback<void, HfpPendingCommand*, ErrorInfo*, void *> { public: /** * @brief Request that the command be canceled and not sent @@ -558,7 +593,7 @@ public: * - Client reference count drops to zero. Put(). * - The HfpSession is in the Disconnected state. See IsConnected(), * IsConnecting(). - * - Auto-reconnect is not enabled. See SetAutoReconnect(). + * - Auto-reconnect is not enabled. See RfcommSession::SetAutoReconnect(). * - As with all @ref managed "managed objects," the actual destruction * is performed in the context of a timer event. * @@ -608,38 +643,44 @@ private: BTS_Handshaking, BTS_Connected } m_conn_state; - bool m_conn_autoreconnect; - ListItem m_autoreconnect_links; ListItem m_commands; + /* Timeout for completion of the topmost command */ + TimerNotifier *m_command_timer; + void CommandTimeout(TimerNotifier *notp); + int m_brsf; /* Methods reimplemented from RfcommSession */ virtual void __Disconnect(ErrorInfo *reason, bool voluntary = false); virtual void NotifyConnectionState(ErrorInfo *async_error); virtual void SdpSupportedFeatures(uint16_t features); + virtual void AutoReconnect(void); /* New methods for HFP */ - void AutoReconnect(void); bool HfpHandshake(ErrorInfo *error); void HfpHandshakeDone(void); void HfpDataReady(SocketNotifier *notp, int fh); size_t HfpConsume(char *buf, size_t len); void DeleteFirstCommand(bool do_start = true); - bool AppendCommand(AtCommand *cmdp, ErrorInfo *error); + bool AddCommand(AtCommand *cmdp, bool top, ErrorInfo *error); + bool SendCommand(const char *cmd, ErrorInfo *error); bool StartCommand(ErrorInfo *error); bool CancelCommand(AtCommand *cmdp); void ResponseDefault(char *buf); HfpPendingCommand *PendingCommand(AtCommand *cmdp, ErrorInfo *error); + friend class CopsCommand; friend class CindRCommand; + friend class CmerCommand; friend class AtdCommand; friend class AtCommandClearCallSetup; void UpdateIndicator(int inum, const char *ival); void UpdateCallSetup(int val, int ring = 0, - GsmClipPhoneNumber *clip = 0, + GsmClipResult *clip = 0, int timeout_ms = 0); + void ClearClip(void); friend class ChldTCommand; void SetSupportedHoldRange(int start, int end); @@ -653,7 +694,7 @@ private: m_chld_4: 1; friend class BrsfCommand; - void SetSupportedFeatures(int ag_features) { m_brsf = ag_features; }; + void SetSupportedFeatures(int ag_features); friend class CindTCommand; void SetIndicatorNum(int inum, const char *name, int namelen); @@ -667,6 +708,7 @@ private: int m_inum_service; int m_inum_call; int m_inum_callsetup; + int m_inum_callheld; int m_inum_signal; int m_inum_roam; int m_inum_battchg; @@ -679,7 +721,11 @@ private: void CleanupIndicators(void); void ExpandIndicators(int min_size); + bool VerifyIndicators(void); + friend class NrecCommand; + friend class BvraCommand; + void SetBvra(bool active); /* Call state trackers */ bool m_state_service; @@ -688,9 +734,13 @@ private: int m_state_signal; int m_state_roam; int m_state_battchg; + bool m_state_bvra; + bool m_state_bsir; + bool m_state_ecnr; + int m_state_vgm; + int m_state_vgs; enum { PHONENUM_MAX_LEN = 31, }; - GsmClipPhoneNumber *m_state_incomplete_clip; static bool ValidPhoneNumChar(char c, ErrorInfo *error); static bool ValidPhoneNum(const char *ph, ErrorInfo *error); @@ -724,15 +774,32 @@ private: bool ScoSocketExists(void) const { return (m_sco_sock >= 0); } /* Event handling stuff */ + bool m_callsetup_presumed; TimerNotifier *m_timer; void Timeout(TimerNotifier *notp); + /* + * Caller-ID timeout for RING events, to avoid reporting empty + * CLIP information. + */ + TimerNotifier *m_clip_timer; + enum { + CLIP_UNKNOWN, + CLIP_WAITING, + CLIP_FINAL, + } m_clip_state; + GsmClipResult *m_clip_value; + + void ClipTimeout(TimerNotifier *notp); + protected: /* Timeouts for dealing with devices without the callsetup indicator */ int m_timeout_ring; int m_timeout_ring_ccwa; int m_timeout_dial; + int m_timeout_clip; + int m_timeout_command; public: HfpService *GetService(void) const @@ -882,6 +949,51 @@ public: */ Callback<void, HfpSession *, const char *, int> cb_NotifyIndicator; + /** + * @brief Notification of a change to the audio gateway's + * voice recognition state + * + * @param bool Set to @em true if the audio gateway has activated + * voice recognition mode, @em false otherwise + * + * The last known voice recognition state can also be accessed + * via GetVoiceRecogActive(). Activation or deactivation can + * be requested via CmdSetVoiceRecog(). + * + * @sa Bluetooth HFP v1.5 section 4.25 + */ + Callback<void, HfpSession *, bool> cb_NotifyVoiceRecog; + + /** + * @brief Notification of a change to the negotiated speaker + * or microphone gain level + * + * @param bool1 Set to @em true if the microphone gain level has + * been changed by audio gateway + * @param bool2 Set to @em true if the speaker gain level has + * been changed by audio gateway + * + * The gain values can be accessed via GetVolumeMic() and + * GetVolumeSpeaker(). + * + * @sa Bluetooth HFP v1.5 section 4.28 + */ + Callback<void, HfpSession *, bool, bool> cb_NotifyVolume; + + /** + * @brief Notification of a change to the audio gateway's + * in-band ring tone setting + * + * @param bool Set to @em true if the audio gateway has enabled + * in-band ring tones, @em false otherwise + * + * The in-band ring tone setting can also be accessed via + * GetInBandRingToneEnable() + * + * @sa Bluetooth HFP v1.5 section 4.13.4 + */ + Callback<void, HfpSession *, bool> cb_NotifyInBandRingTone; + private: /* Response buffer */ enum { RFCOMM_MAX_LINELEN = 512 }; @@ -979,37 +1091,11 @@ public: * - There are no available Bluetooth HCIs. * - The RFCOMM socket could not be created, e.g. because part or * all of the bluetooth stack could not be loaded. - * @sa Disconnect(), SetAutoReconnect() + * @sa Disconnect(), RfcommSession::SetAutoReconnect() * @sa HfpSession::cb_NotifyConnection */ bool Connect(ErrorInfo *error = 0); - /** - * @brief Query whether the autoreconnect mechanism is enabled for - * this device - * - * @retval true Autoreconnect is enabled - * @retval false Autoreconnect is disabled. - * @sa SetAutoReconnect() - */ - bool IsAutoReconnect(void) const { return m_conn_autoreconnect; } - - /** - * @brief Enable or disable the autoreconnect mechanism for this - * device - * - * If enabled, whenever the device is disconnected, a reconnection - * attempt will be made periodically through a timer. - * Auto-reconnection is useful for phones that regularly move in - * and out of range. - * - * This function can affect the @ref aglifecycle "life cycle management" - * of the object it is called on. - * @param enable Set to true to enable, false to disable. - * @sa IsAutoReconnect(), Connect() - */ - void SetAutoReconnect(bool enable); - /* * Call state queries @@ -1027,7 +1113,7 @@ public: /** * @brief Query whether the device has an established call * - * @retval true Established call exists + * @retval true Established call, either active or on hold, exists * @retval false No established call exists */ bool HasEstablishedCall(void) const { @@ -1047,14 +1133,15 @@ public: * @brief Retrieve the caller ID value of the last incomplete call, * either incoming or outgoing * - * The string value may be used - * until the global event handler is invoked again or - * CmdDial() / CmdRedial() is invoked. - * @return String pointer to the remote identity of the last - * incomplete call, or NULL if the identity was not known. + * @return a GsmClipResult object containing the calling line + * identity, or NULL if the identity is not known, or there is no + * waiting or unanswered call. This object's contents are described + * in GSM 07.07 7.6. This object may be used until the global + * event handler is invoked again or CmdDial() / CmdRedial() is + * invoked. */ - const GsmClipPhoneNumber *WaitingCallIdentity(void) const { - return m_state_incomplete_clip; + const GsmClipResult *WaitingCallIdentity(void) const { + return m_clip_value; } @@ -1133,6 +1220,71 @@ public: */ int GetBatteryCharge(void) const { return m_state_battchg; } + /** + * @brief Query the voice recognition state + * + * If the audio gateway supports voice recognition, and is + * connected, it will use the microphone of the hands-free to + * listen for commands. Voice recognition can be activated + * directly on the audio gateway, or via CmdSetVoiceRecog(). + * Voice recognition can be deactivated when the audio gateway + * has recognized a voice command, when the audio gateway times + * out listening for a command, or via CmdSetVoiceRecog(). + * + * Audio gateway support for voice recognition can be tested + * via FeatureVoiceRecog(). + * + * @retval true Voice recognition activated + * @retval false Voice recognition deactivated or unsupported + * + * @note This function will only return meaningful values + * when the device is in the connected state. + */ + bool GetVoiceRecogActive(void) const { return m_state_bvra; } + + /** + * @brief Query the microphone gain level + * + * If the audio gateway supports remote volume control, this + * method can be used to retrieve the microphone volume level + * synchronized with the audio gateway. + * + * @return The synchronized microphone volume level, 0-15, + * or -1 if the microphone volume level is unsynchronized. + */ + int GetVolumeMic(void) const { return m_state_vgm; } + + /** + * @brief Query the speaker gain level + * + * If the audio gateway supports remote volume control, this + * method can be used to retrieve the speaker volume level + * synchronized with the audio gateway. + * + * @return The synchronized speaker volume level, 0-15, + * or -1 if the speaker volume level is unsynchronized. + */ + int GetVolumeSpeaker(void) const { return m_state_vgs; } + + /** + * @brief Query the in-band ring tone setting + * + * If the audio gateway supports in-band ring tones, it will + * send its in-band ring tone state on connection, and when it + * is changed. This accessor method can be used to query the + * last known value. + * + * The in-band ring tone setting cannot be changed from the + * hands-free; it can only be set on the audio gateway. + * + * @retval 0 In-band ring tones disabled or unsupported + * @retval 1 In-band ring tones enabled + * + * @note This function will only return meaningful values + * when the device is in the connected state. + */ + bool GetInBandRingToneEnable(void) const { return m_state_bsir; } + /* * Reported feature presence queries @@ -1181,7 +1333,6 @@ public: * * @note This information is only valid when the device is in the * connected state. - * @todo Voice recognition is not yet supported by libhfp. * * @retval true Voice recognition supported * @retval false Voice recognition not supported @@ -1225,10 +1376,12 @@ public: { return (m_brsf & 256) ? true : false; } bool FeatureIndCallSetup(void) const { return (m_inum_callsetup != 0); } + bool FeatureIndCallHeld(void) const + { return (m_inum_callheld != 0); } bool FeatureDropHeldUdub(void) const { return m_chld_0; } bool FeatureSwapDropActive(void) const { return m_chld_1; } - bool FeatureDropActive(void) const { return m_chld_1x; } + bool FeatureDropIndex(void) const { return m_chld_1x; } bool FeatureSwapHoldActive(void) const { return m_chld_2; } bool FeaturePrivateConsult(void) const { return m_chld_2x; } bool FeatureLink(void) const { return m_chld_3; } @@ -1293,8 +1446,93 @@ public: */ bool IsCommandPending(void) const { return !m_commands.Empty(); } - HfpPendingCommand *CmdSetVoiceRecog(bool enabled, + /** + * @brief Query the telephone number of the audio gateway + * + * @param[out] error Error information structure. If this method + * fails and returns @c 0, and @em error is not 0, @em error + * will be filled out with information on the cause of the failure. + * @return An HfpPendingCommand to receive a notification when + * the command completes, with the command status, or @c 0 if + * the command could not be queued, e.g. because the device + * connection was lost + * + * On success, the last parameter of the callback method to the + * resulting HfpPendingCommand object will be passed a + * GsmCnumResult structure. The object will be deleted after + * the callback method returns, so the callback method must + * duplicate the object if it wishes to reference it later. + */ + HfpPendingCommand *CmdQueryNumber(ErrorInfo *error = 0); + + /** + * @brief Query operator information from the audio gateway + * + * @param[out] error Error information structure. If this method + * fails and returns @c 0, and @em error is not 0, @em error + * will be filled out with information on the cause of the failure. + * @return An HfpPendingCommand to receive a notification when + * the command completes, with the command status, or @c 0 if + * the command could not be queued, e.g. because the device + * connection was lost + * + * On success, the last parameter of the callback method to the + * resulting HfpPendingCommand object will be passed a + * GsmCopsResult structure. The object will be deleted after + * the callback method returns, so the callback method must + * duplicate the object if it wishes to reference it later. + */ + HfpPendingCommand *CmdQueryOperator(ErrorInfo *error = 0); + + /** + * @brief Query current calls from the audio gateway + * + * @param[out] error Error information structure. If this method + * fails and returns @c 0, and @em error is not 0, @em error + * will be filled out with information on the cause of the failure. + * @return An HfpPendingCommand to receive a notification when + * the command completes, with the command status, or @c 0 if + * the command could not be queued, e.g. because the device + * connection was lost + * + * On success, the last parameter of the callback method to the + * resulting HfpPendingCommand object will be passed a pointer to + * a ListItem, which is the head of a list of GsmClccResult + * objects. The list head and all objects on the list will be + * deleted after the callback method returns, so the callback + * method must duplicate the objects that it wishes to reference + * later. + */ + HfpPendingCommand *CmdQueryCurrentCalls(ErrorInfo *error = 0); + + /** + * @brief Request that the audio gateway activate or deactivate + * voice recognition + * + * @note This command may only be expected to succeed if the device + * claims support for voice recognition, + * i.e. FeatureVoiceRecog() returns true. + * + * @param[in] active Set to @em true to request activation of + * voice recognition mode, @em false to request deactivation. + * @param[out] error Error information structure. If this method + * fails and returns @c 0, and @em error is not 0, @em error + * will be filled out with information on the cause of the failure. + * @return An HfpPendingCommand to receive a notification when + * the command completes, with the command status, or @c 0 if + * the command could not be queued, e.g. because the device + * connection was lost + * + * If successful, the cb_NotifyVoiceRecog callback will be + * invoked with the new voice recognition state. + */ + HfpPendingCommand *CmdSetVoiceRecog(bool active, ErrorInfo *error = 0); + + HfpPendingCommand *CmdSetVolumeMic(uint8_t vol, ErrorInfo *error = 0); + HfpPendingCommand *CmdSetVolumeSpeaker(uint8_t vol, + ErrorInfo *error = 0); + HfpPendingCommand *CmdSetEcnr(bool enabled, ErrorInfo *error = 0); /** @@ -1408,11 +1646,11 @@ public: HfpPendingCommand *CmdCallSwapDropActive(ErrorInfo *error = 0); /** - * @brief Drop a specific active call + * @brief Drop a specific call * * @note This command may only be expected to succeed if the - * device supports dropping of specific active calls, i.e. - * FeatureDropActive() returns true. + * device supports dropping of specific calls, i.e. + * FeatureDropIndex() returns true. * @param[in] actnum Call number to be dropped * @param[out] error Error information structure. If this method * fails and returns @c 0, and @em error is not 0, @em error @@ -1422,8 +1660,8 @@ public: * the command could not be queued, e.g. because the device * connection was lost */ - HfpPendingCommand *CmdCallDropActive(unsigned int actnum, - ErrorInfo *error = 0); + HfpPendingCommand *CmdCallDropIndex(unsigned int actnum, + ErrorInfo *error = 0); /** * @brief Request that the audio gateway hold the active call diff --git a/include/libhfp/rfcomm.h b/include/libhfp/rfcomm.h index 22b0150..c3a5218 100644 --- a/include/libhfp/rfcomm.h +++ b/include/libhfp/rfcomm.h @@ -105,9 +105,25 @@ protected: /* Call me from Stop() */ void RfcommCleanup(void); + ListItem m_autoreconnect_list; + int m_autoreconnect_timeout; + bool m_autoreconnect_set; + TimerNotifier *m_autoreconnect_timer; + + ListItem m_autoreconnect_now_list; + bool m_autoreconnect_now_set; + TimerNotifier *m_autoreconnect_now_timer; + + void AddAutoReconnect(RfcommSession *sessp, bool now = false); + void RemoveAutoReconnect(RfcommSession *sessp); + void AutoReconnectTimeout(TimerNotifier*); + RfcommService(uint16_t search_svclass_id = 0); virtual ~RfcommService(); + bool Start(ErrorInfo *error); + void Stop(void); + public: RfcommSession *GetSession(BtDevice *devp, bool create = true); RfcommSession *GetSession(bdaddr_t const &addr, bool create = true); @@ -178,11 +194,12 @@ protected: bool RfcommSdpLookupChannel(ErrorInfo *error); void RfcommSdpLookupChannelComplete(SdpTask *taskp); - bool RfcommConnect(uint8_t channel, ErrorInfo *error); + bool RfcommConnect(uint8_t channel, ErrorInfo *error, + int timeout = 15000); void RfcommConnectNotify(SocketNotifier *notp, int fh); /* This is the primary overload and handles SDP channel lookups */ - bool RfcommConnect(ErrorInfo *error); + bool RfcommConnect(ErrorInfo *error, int timeout = 30000); virtual bool RfcommAccept(int sock); @@ -190,6 +207,18 @@ protected: SocketNotifier *m_rfcomm_not; rfcomm_secmode_t m_rfcomm_secmode; + bool m_conn_autoreconnect; + ListItem m_autoreconnect_links; + + virtual void AutoReconnect(void) = 0; + + TimerNotifier *m_operation_timeout; + + bool RfcommSetOperationTimeout(int ms, ErrorInfo *error); + void RfcommOperationTimeout(TimerNotifier *); + + bool RfcommSend(const uint8_t *buf, size_t len, ErrorInfo *error); + RfcommSession(RfcommService *svcp, BtDevice *devp); virtual ~RfcommSession(); @@ -222,7 +251,6 @@ protected: return (err == ECONNRESET); } - public: RfcommService *GetService(void) const { return (RfcommService*) BtSession::GetService(); } @@ -256,6 +284,32 @@ public: rfcomm_secmode_t GetSecMode(void) const { return m_rfcomm_secmode; } /** + * @brief Query whether the autoreconnect mechanism is enabled for + * this device + * + * @retval true Autoreconnect is enabled + * @retval false Autoreconnect is disabled. + * @sa SetAutoReconnect() + */ + bool IsAutoReconnect(void) const { return m_conn_autoreconnect; } + + /** + * @brief Enable or disable the autoreconnect mechanism for this + * device + * + * If enabled, whenever the device is disconnected, a reconnection + * attempt will be made periodically through a timer. + * Auto-reconnection is useful for devices such as phones that + * regularly move in and out of range. + * + * This function can affect the @ref aglifecycle "life cycle management" + * of the object it is called on. + * @param enable Set to true to enable, false to disable. + * @sa IsAutoReconnect(), Connect() + */ + void SetAutoReconnect(bool enable); + + /** * @brief Query whether an in-progress or complete connection to the * device was initiated by the device * diff --git a/libhfp/hfp.cpp b/libhfp/hfp.cpp index f74cd98..11f0145 100644 --- a/libhfp/hfp.cpp +++ b/libhfp/hfp.cpp @@ -55,9 +55,6 @@ HfpService(int caps) m_sco_listen(-1), m_sco_listen_not(0), m_brsf_my_caps(caps), m_svc_name(0), m_svc_desc(0), m_sdp_rec(0), m_sco_enable(true), - m_autoreconnect_timeout(15000), m_autoreconnect_set(false), - m_autoreconnect_timer(0), - m_autoreconnect_now_set(false), m_autoreconnect_now_timer(0), m_complaint_sco_mtu(false), m_complaint_sco_vs(false), m_complaint_sco_listen(false) { @@ -74,85 +71,6 @@ HfpService:: free(m_svc_desc); m_svc_desc = 0; } - assert(!m_autoreconnect_timer); - assert(!m_autoreconnect_now_timer); -} - -void HfpService:: -AutoReconnectTimeout(TimerNotifier *timerp) -{ - ListItem retrylist; - - if (timerp == m_autoreconnect_timer) { - assert(m_autoreconnect_set); - m_autoreconnect_set = false; - retrylist.AppendItemsFrom(m_autoreconnect_list); - - } else { - assert(timerp == m_autoreconnect_now_timer); - assert(m_autoreconnect_now_set); - m_autoreconnect_now_set = false; - retrylist.AppendItemsFrom(m_autoreconnect_now_list); - } - - while (!retrylist.Empty()) { - HfpSession *sessp = GetContainer(retrylist.next, HfpSession, - m_autoreconnect_links); - sessp->m_autoreconnect_links.UnlinkOnly(); - - /* Always append to the delayed list */ - m_autoreconnect_list.AppendItem(sessp->m_autoreconnect_links); - - sessp->AutoReconnect(); - } - - if (!m_autoreconnect_list.Empty() && !m_autoreconnect_set) { - m_autoreconnect_set = true; - m_autoreconnect_timer->Set(m_autoreconnect_timeout); - } -} - -void HfpService:: -AddAutoReconnect(HfpSession *sessp, bool now) -{ - assert(sessp->m_autoreconnect_links.Empty()); - assert(!sessp->IsConnected() && !sessp->IsConnecting()); - - if (now) { - if (m_autoreconnect_now_timer && !m_autoreconnect_now_set) { - m_autoreconnect_now_set = true; - m_autoreconnect_now_timer->Set(0); - } - m_autoreconnect_now_list.AppendItem(sessp-> - m_autoreconnect_links); - } else { - if (m_autoreconnect_timer && !m_autoreconnect_set) { - m_autoreconnect_set = true; - m_autoreconnect_timer->Set(m_autoreconnect_timeout); - } - m_autoreconnect_list.AppendItem(sessp->m_autoreconnect_links); - } -} - -void HfpService:: -RemoveAutoReconnect(HfpSession *sessp) -{ - assert(!sessp->m_autoreconnect_links.Empty()); - sessp->m_autoreconnect_links.Unlink(); - - if (m_autoreconnect_timer && - m_autoreconnect_list.Empty() && - m_autoreconnect_set) { - m_autoreconnect_set = false; - m_autoreconnect_timer->Cancel(); - } - - if (m_autoreconnect_now_timer && - m_autoreconnect_now_list.Empty() && - m_autoreconnect_now_set) { - m_autoreconnect_now_set = false; - m_autoreconnect_now_timer->Cancel(); - } } bool HfpService:: @@ -454,7 +372,7 @@ SdpRegister(ErrorInfo *error) root_list = 0; /* Add one last required attribute */ - caps = m_brsf_my_caps; + caps = m_brsf_my_caps & 0x1f; if (sdp_attr_add_new(svcrec, SDP_ATTR_SUPPORTED_FEATURES, SDP_UINT16, &caps) < 0) goto nomem; @@ -491,26 +409,9 @@ bool HfpService:: Start(ErrorInfo *error) { assert(GetHub()); - assert(!m_autoreconnect_timer); - assert(!m_autoreconnect_now_timer); - - m_autoreconnect_timer = GetDi()->NewTimer(); - if (!m_autoreconnect_timer) { - if (error) - error->SetNoMem(); - return false; - } - m_autoreconnect_timer->Register(this, - &HfpService::AutoReconnectTimeout); - m_autoreconnect_now_timer = GetDi()->NewTimer(); - if (!m_autoreconnect_now_timer) { - if (error) - error->SetNoMem(); + if (!RfcommService::Start(error)) goto failed; - } - m_autoreconnect_now_timer->Register(this, - &HfpService::AutoReconnectTimeout); if (!RfcommListen(error)) goto failed; @@ -521,16 +422,6 @@ Start(ErrorInfo *error) if (!SdpRegister(error)) goto failed; - if (!m_autoreconnect_list.Empty() && !m_autoreconnect_set) { - m_autoreconnect_set = true; - m_autoreconnect_timer->Set(0); - } - - if (!m_autoreconnect_now_list.Empty() && !m_autoreconnect_now_set) { - m_autoreconnect_now_set = true; - m_autoreconnect_now_timer->Set(0); - } - return true; failed: @@ -546,16 +437,7 @@ Stop(void) SdpUnregister(); RfcommCleanup(); ScoCleanup(); - if (m_autoreconnect_timer) { - m_autoreconnect_set = false; - delete m_autoreconnect_timer; - m_autoreconnect_timer = 0; - } - if (m_autoreconnect_now_timer) { - m_autoreconnect_now_set = false; - delete m_autoreconnect_now_timer; - m_autoreconnect_now_timer = 0; - } + RfcommService::Stop(); } RfcommSession * HfpService:: @@ -649,6 +531,26 @@ SetScoEnabled(bool sco_enable, ErrorInfo *error) } bool HfpService:: +SetCaps(int caps, ErrorInfo *error) +{ + int old_caps = m_brsf_my_caps; + + m_brsf_my_caps = caps; + + if ((m_sdp_rec != 0) && ((old_caps ^ caps) & 0x1f)) { + SdpUnregister(); + if (!SdpRegister(error)) { + /* Now we're just screwed! */ + m_brsf_my_caps = old_caps; + (void) SdpRegister(0); + return false; + } + } + + return true; +} + +bool HfpService:: SetServiceName(const char *val, ErrorInfo *error) { char *oldval, *newval = 0; @@ -667,14 +569,16 @@ SetServiceName(const char *val, ErrorInfo *error) } } - SdpUnregister(); - if ((m_sco_listen >= 0) && !SdpRegister(error)) { - /* Now we're just screwed! */ - m_svc_name = oldval; - if (newval) - free(newval); - (void) SdpRegister(0); - return false; + if (m_sdp_rec != 0) { + SdpUnregister(); + if (!SdpRegister(error)) { + /* Now we're just screwed! */ + m_svc_name = oldval; + if (newval) + free(newval); + (void) SdpRegister(0); + return false; + } } if (oldval) @@ -701,13 +605,15 @@ SetServiceDesc(const char *val, ErrorInfo *error) } } - SdpUnregister(); - if ((m_sco_listen >= 0) && !SdpRegister(error)) { - m_svc_desc = oldval; - if (newval) - free(newval); - (void) SdpRegister(0); - return false; + if (m_sdp_rec != 0) { + SdpUnregister(); + if (!SdpRegister(error)) { + m_svc_desc = oldval; + if (newval) + free(newval); + (void) SdpRegister(0); + return false; + } } if (oldval) @@ -718,20 +624,23 @@ SetServiceDesc(const char *val, ErrorInfo *error) HfpSession:: HfpSession(HfpService *svcp, BtDevice *devp) : RfcommSession(svcp, devp), m_conn_state(BTS_Disconnected), - m_conn_autoreconnect(false), + m_command_timer(0), m_chld_0(false), m_chld_1(false), m_chld_1x(false), m_chld_2(false), m_chld_2x(false),m_chld_3(false), m_chld_4(false), m_clip_enabled(false), m_ccwa_enabled(false), m_inum_service(0), m_inum_call(0), m_inum_callsetup(0), - m_inum_signal(0), m_inum_roam(0), m_inum_battchg(0), + m_inum_callheld(0), m_inum_signal(0), m_inum_roam(0), + m_inum_battchg(0), m_inum_names(NULL), m_inum_names_len(0), m_state_service(false), m_state_call(false), m_state_callsetup(0), m_state_signal(-1), m_state_roam(-1), m_state_battchg(-1), - m_state_incomplete_clip(0), + m_state_bvra(false), m_state_bsir(false), m_state_ecnr(false), + m_state_vgm(-1), m_state_vgs(-1), m_sco_state(BVS_Invalid), m_sco_sock(-1), m_sco_nonblock(false), - m_sco_not(0), m_timer(0), + m_sco_not(0), m_callsetup_presumed(false), m_timer(0), + m_clip_timer(0), m_clip_state(CLIP_UNKNOWN), m_clip_value(0), m_timeout_ring(5000), m_timeout_ring_ccwa(20000), - m_timeout_dial(20000), + m_timeout_dial(20000), m_timeout_clip(250), m_timeout_command(30000), m_rsp_start(0), m_rsp_len(0) { } @@ -739,7 +648,18 @@ HfpSession(HfpService *svcp, BtDevice *devp) HfpSession:: ~HfpSession() { - assert(!m_timer); + if (m_timer) { + delete m_timer; + m_timer = 0; + } + if (m_clip_timer) { + delete m_clip_timer; + m_clip_timer = 0; + } + if (m_command_timer) { + delete m_command_timer; + m_command_timer = 0; + } assert(m_conn_state == BTS_Disconnected); assert(m_commands.Empty()); assert(!m_inum_names); @@ -771,21 +691,36 @@ NotifyConnectionState(ErrorInfo *async_error) (m_conn_state == BTS_Disconnected)); m_conn_state = BTS_Handshaking; - if (!HfpHandshake(&local_error)) + if (!m_timer) { + m_timer = GetDi()->NewTimer(); + if (m_timer) + m_timer->Register(this, &HfpSession::Timeout); + } + if (!m_clip_timer) { + m_clip_timer = GetDi()->NewTimer(); + if (m_clip_timer) + m_clip_timer->Register(this, + &HfpSession::ClipTimeout); + } + if (!m_command_timer) { + m_command_timer = GetDi()->NewTimer(); + if (m_command_timer) + m_command_timer->Register(this, + &HfpSession::CommandTimeout); + } + + if (!m_timer || !m_clip_timer || !m_command_timer) { + local_error.SetNoMem(); __Disconnect(&local_error, false); + } - /* - * We might need to disable autoreconnect for this - * device if the state change is the result of a - * remotely initiated connection. - */ - if (!m_autoreconnect_links.Empty()) { - assert(m_conn_autoreconnect); - GetService()->RemoveAutoReconnect(this); + if (IsRfcommConnected() && !HfpHandshake(&local_error)) { + __Disconnect(&local_error, false); } - else if (IsConnectionRemoteInitiated() && - cb_NotifyConnection.Registered()) + if (IsRfcommConnected() && + IsConnectionRemoteInitiated() && + cb_NotifyConnection.Registered()) cb_NotifyConnection(this, 0); } @@ -794,6 +729,7 @@ NotifyConnectionState(ErrorInfo *async_error) assert(m_conn_state == BTS_Disconnected); assert(!IsConnectingAudio()); assert(!IsConnectedAudio()); + UpdateCallSetup(0); } if (async_error && cb_NotifyConnection.Registered()) @@ -825,25 +761,19 @@ __Disconnect(ErrorInfo *reason, bool voluntary) if (m_conn_state != BTS_Disconnected) { m_conn_state = BTS_Disconnected; - if (m_conn_autoreconnect) - GetService()->AddAutoReconnect(this); } m_rsp_start = m_rsp_len = 0; m_chld_0 = m_chld_1 = m_chld_1x = m_chld_2 = m_chld_2x = m_chld_3 = m_chld_4 = false; m_clip_enabled = false; m_ccwa_enabled = false; - m_inum_service = m_inum_call = m_inum_callsetup = 0; + m_inum_service = m_inum_call = m_inum_callsetup = m_inum_callheld = 0; m_inum_signal = m_inum_roam = m_inum_battchg = 0; m_state_service = false; m_state_call = false; - m_state_callsetup = 0; m_state_signal = m_state_roam = m_state_battchg = -1; - - if (m_state_incomplete_clip) { - delete m_state_incomplete_clip; - m_state_incomplete_clip = 0; - } + m_state_bvra = m_state_bsir = m_state_ecnr = false; + m_state_vgm = m_state_vgs = -1; RfcommSession::__Disconnect(reason, voluntary); } @@ -855,21 +785,16 @@ Connect(ErrorInfo *error) return true; m_conn_state = BTS_RfcommConnecting; - if (m_conn_autoreconnect) - GetService()->RemoveAutoReconnect(this); if (RfcommConnect(error)) return true; m_conn_state = BTS_Disconnected; - if (m_conn_autoreconnect) - GetService()->AddAutoReconnect(this); return false; } void HfpSession:: AutoReconnect(void) { - assert(m_conn_autoreconnect); assert(m_conn_state == BTS_Disconnected); if (Connect() && cb_NotifyConnection.Registered()) @@ -885,23 +810,6 @@ AutoReconnect(void) } void HfpSession:: -SetAutoReconnect(bool enable) -{ - if (enable && !m_conn_autoreconnect) { - Get(); - m_conn_autoreconnect = true; - if (m_conn_state == BTS_Disconnected) - GetService()->AddAutoReconnect(this, true); - } - else if (!enable && m_conn_autoreconnect) { - m_conn_autoreconnect = false; - if (m_conn_state == BTS_Disconnected) - GetService()->RemoveAutoReconnect(this); - Put(); - } -} - -void HfpSession:: SdpSupportedFeatures(uint16_t features) { assert(!IsConnected()); @@ -1505,8 +1413,7 @@ class AtCommand { bool iResponse(const char *buf) { if (!strcmp(buf, "OK")) { - OK(); - return true; + return OK(); } if (!strcmp(buf, "ERROR")) { ERROR(); @@ -1522,7 +1429,7 @@ class AtCommand { protected: HfpSession *GetSession(void) const { return m_sess; } DispatchInterface *GetDi(void) const { return m_sess->GetDi(); } - void CompletePending(ErrorInfo *error, const char *info) { + void CompletePending(ErrorInfo *error, void *info) { HfpPendingCommand *cmdp = m_pend; if (cmdp) { m_pend = 0; @@ -1533,8 +1440,9 @@ public: const char *m_command_text; virtual bool Response(const char *buf) { return false; }; - virtual void OK(void) { + virtual bool OK() { CompletePending(0, 0); + return true; } virtual void ERROR(void) { ErrorInfo simple_error; @@ -1808,7 +1716,8 @@ ParseGsmStringField(char *&buf, const char *&result) static bool ParseGsmIntField(char *&buf, int &result) { - char *xbuf, *endp; + char *xbuf; + char *endp; int value; xbuf = buf; @@ -1816,7 +1725,6 @@ ParseGsmIntField(char *&buf, int &result) if (!*xbuf) return false; - value = strtol(xbuf, &endp, 10); if (endp != xbuf) { result = value; @@ -1825,7 +1733,6 @@ ParseGsmIntField(char *&buf, int &result) while (*xbuf && IsWS(*xbuf)) { xbuf++; } if (*xbuf == ',') { - *xbuf = '\0'; xbuf++; } else if (*xbuf) { @@ -1836,45 +1743,43 @@ ParseGsmIntField(char *&buf, int &result) return true; } -void *GsmClipPhoneNumber:: -operator new(size_t nb, size_t extra) +void *GsmResult:: +operator new(size_t nb, const char *src_string, size_t extra) { - return malloc(nb + extra); + void *mem; + char *cbuf; + + if (!src_string) + src_string = ""; + if (!extra) + extra = strlen(src_string) + 1; + mem = malloc(nb + extra); + if (mem) { + memset(mem, 0, nb); + cbuf = ((char *) mem) + nb; + memcpy(cbuf, src_string, extra); + ((GsmResult *) mem)->src_string = cbuf; + ((GsmResult *) mem)->extra = extra; + } + return mem; } -void GsmClipPhoneNumber:: +void GsmResult:: operator delete(void *mem) { free(mem); } -GsmClipPhoneNumber *GsmClipPhoneNumber:: -Create(const char *src) -{ - GsmClipPhoneNumber *res; - size_t extra; - - extra = strlen(src) + 1; - res = new (extra) GsmClipPhoneNumber; - if (!res) - return 0; - - strcpy((char *)(res + 1), src); - memset(res, 0, sizeof(*res)); - res->extra = extra; - return res; -} - -GsmClipPhoneNumber *GsmClipPhoneNumber:: +GsmClipResult *GsmClipResult:: Parse(const char *clip) { - GsmClipPhoneNumber *res; + GsmClipResult *res; char *buf; - res = Create(clip); + res = new (clip, 0) GsmClipResult; if (!res) return 0; - buf = (char *) (res + 1); + buf = res->src_string; if (!ParseGsmStringField(buf, res->number)) goto bad; @@ -1905,17 +1810,17 @@ bad: return 0; } -GsmClipPhoneNumber *GsmClipPhoneNumber:: +GsmClipResult *GsmClipResult:: ParseCcwa(const char *clip) { - GsmClipPhoneNumber *res; + GsmClipResult *res; char *buf; int class_drop; - res = Create(clip); + res = new (clip, 0) GsmClipResult; if (!res) return 0; - buf = (char *) (res + 1); + buf = res->src_string; if (!ParseGsmStringField(buf, res->number)) goto bad; @@ -1942,10 +1847,10 @@ bad: return 0; } -bool GsmClipPhoneNumber:: -Compare(const GsmClipPhoneNumber *clip) const +bool GsmClipResult:: +Compare(const GsmClipResult *clip) const { - const GsmClipPhoneNumber *src = this; + const GsmClipResult *src = this; if (src->number || clip->number) { if (!src->number || !clip->number) return false; @@ -1971,20 +1876,21 @@ Compare(const GsmClipPhoneNumber *clip) const return true; } -GsmClipPhoneNumber *GsmClipPhoneNumber:: +GsmClipResult *GsmClipResult:: Duplicate(void) const { - const GsmClipPhoneNumber *src = this; - GsmClipPhoneNumber *res; + const GsmClipResult *src = this; + GsmClipResult *res; const char *from; char *buf; - res = new (src->extra) GsmClipPhoneNumber; + res = new (src->src_string, src->extra) GsmClipResult; if (!res) return 0; + buf = res->src_string; + from = src->src_string; *res = *src; - buf = (char *) (res + 1); - from = (const char *) (src + 1); + res->src_string = buf; memcpy(buf, from, src->extra); if (src->number) res->number = buf + (src->number - from); @@ -1995,10 +1901,145 @@ Duplicate(void) const return res; } +GsmCnumResult *GsmCnumResult:: +Parse(const char *cops) +{ + GsmCnumResult *res; + char *buf; + + res = new (cops, 0) GsmCnumResult; + if (!res) + return 0; + buf = res->src_string; + + if (!ParseGsmStringField(buf, res->alpha)) + goto bad; + if (!*buf) + goto bad; + if (!ParseGsmStringField(buf, res->number)) + goto bad; + if (!*buf) + goto bad; + if (!ParseGsmIntField(buf, res->type)) + goto bad; + if (!*buf) + return res; + if (!ParseGsmIntField(buf, res->speed)) + goto bad; + if (!*buf) + return res; + if (!ParseGsmIntField(buf, res->service)) + goto bad; + return res; + +bad: + delete res; + return 0; +} + +GsmCopsResult *GsmCopsResult:: +Parse(const char *cops) +{ + GsmCopsResult *res; + char *buf; + + res = new (cops, 0) GsmCopsResult; + if (!res) + return 0; + buf = res->src_string; + + if (!ParseGsmIntField(buf, res->mode)) + goto bad; + if (!*buf) + return res; + if (!ParseGsmIntField(buf, res->format)) + goto bad; + if (!*buf) + goto bad; + switch (res->format) { + case 0: + case 1: + if (!ParseGsmStringField(buf, res->alpha)) + goto bad; + break; + case 2: + if (!ParseGsmIntField(buf, res->nonalpha)) + goto bad; + break; + default: + goto bad; + } + return res; + +bad: + delete res; + return 0; +} + +GsmClccResult *GsmClccResult:: +Parse(const char *clcc) +{ + GsmClccResult *res; + char *buf; + + res = new (clcc, 0) GsmClccResult; + if (!res) + return 0; + buf = res->src_string; + + if (!ParseGsmIntField(buf, res->idx)) + goto bad; + if (!*buf) + goto bad; + if (!ParseGsmIntField(buf, res->dir)) + goto bad; + if (!*buf) + goto bad; + if (!ParseGsmIntField(buf, res->stat)) + goto bad; + if (!*buf) + goto bad; + if (!ParseGsmIntField(buf, res->mode)) + goto bad; + if (!*buf) + goto bad; + if (!ParseGsmIntField(buf, res->mpty)) + goto bad; + if (!*buf) + return res; + if (!ParseGsmStringField(buf, res->number)) + goto bad; + if (!*buf) + goto bad; + if (!ParseGsmIntField(buf, res->type)) + goto bad; + if (!*buf) + return res; + if (!ParseGsmStringField(buf, res->alpha)) + goto bad; + return res; + +bad: + delete res; + return 0; +} + + +void HfpSession:: +SetBvra(bool active) +{ + if (m_state_bvra != active) { + m_state_bvra = active; + if (cb_NotifyVoiceRecog.Registered()) + cb_NotifyVoiceRecog(this, active); + } +} + void HfpSession:: ResponseDefault(char *buf) { - GsmClipPhoneNumber *phnum; + char *end; + GsmClipResult *phnum; int indnum; if (!strncmp(buf, "+CIEV:", 6)) { @@ -2030,20 +2071,72 @@ ResponseDefault(char *buf) else if (!strncmp(buf, "+CLIP:", 6) && IsConnected()) { /* Line identification for incoming call */ - phnum = GsmClipPhoneNumber::Parse(buf + 6); + phnum = GsmClipResult::Parse(buf + 6); if (!phnum) GetDi()->LogWarn("Parse error on CLIP"); - UpdateCallSetup(1, 1, phnum, m_timeout_ring); + UpdateCallSetup(1, 0, phnum, m_timeout_ring); delete phnum; } else if (!strncmp(buf, "+CCWA:", 6) && IsConnected()) { /* Call waiting + line identification for call waiting */ - phnum = GsmClipPhoneNumber::ParseCcwa(buf + 6); + phnum = GsmClipResult::ParseCcwa(buf + 6); if (!phnum) GetDi()->LogWarn("Parse error on CCWA"); UpdateCallSetup(1, 2, phnum, m_timeout_ring_ccwa); } + + else if (!strncmp(buf, "+BVRA:", 6) && IsConnected()) { + buf += 6; + while (buf[0] && IsWS(buf[0])) { buf++; } + indnum = strtol(buf, &end, 0); + if ((end == buf) || (indnum < 0) || (indnum > 1)) { + GetDi()->LogWarn("Parse error on BVRA"); + } else { + SetBvra(indnum == 1); + } + } + + else if ((!strncmp(buf, "+VGM:", 5) || !strncmp(buf, "+VGM=", 5)) && + IsConnected()) { + buf += 5; + while (buf[0] && IsWS(buf[0])) { buf++; } + indnum = strtol(buf, &end, 0); + if ((end == buf) || (indnum < 0) || (indnum > 15)) { + GetDi()->LogWarn("Parse error on VGM"); + } else if (m_state_vgm != indnum) { + m_state_vgm = indnum; + if (cb_NotifyVolume.Registered()) + cb_NotifyVolume(this, true, false); + } + } + + else if ((!strncmp(buf, "+VGS:", 5) || !strncmp(buf, "+VGS=", 5)) && + IsConnected()) { + buf += 5; + while (buf[0] && IsWS(buf[0])) { buf++; } + indnum = strtol(buf, &end, 0); + if ((end == buf) || (indnum < 0) || (indnum > 15)) { + GetDi()->LogWarn("Parse error on VGS"); + } else if (m_state_vgs != indnum) { + m_state_vgs = indnum; + if (cb_NotifyVolume.Registered()) + cb_NotifyVolume(this, false, true); + } + } + + else if (!strncmp(buf, "+BSIR:", 6) && IsConnected()) { + buf += 6; + while (buf[0] && IsWS(buf[0])) { buf++; } + indnum = strtol(buf, &end, 0); + if ((end == buf) || (indnum < 0) || (indnum > 1)) { + GetDi()->LogWarn("Parse error on BSIR"); + } else if (m_state_bsir != indnum) { + m_state_bsir = (indnum == 1); + if (cb_NotifyInBandRingTone.Registered()) + cb_NotifyInBandRingTone(this, indnum == 1); + } + } } @@ -2056,6 +2149,19 @@ ResponseDefault(char *buf) */ void HfpSession:: +CommandTimeout(TimerNotifier *timerp) +{ + ErrorInfo error; + + assert(timerp == m_command_timer); + GetDi()->LogWarn(&error, + LIBHFP_ERROR_SUBSYS_BT, + LIBHFP_ERROR_BT_TIMEOUT, + "Audio Gateway Command Timeout"); + __Disconnect(&error, false); +} + +void HfpSession:: DeleteFirstCommand(bool do_start) { AtCommand *cmd; @@ -2065,12 +2171,13 @@ DeleteFirstCommand(bool do_start) cmd->m_links.Unlink(); delete cmd; + m_command_timer->Cancel(); if (do_start && !m_commands.Empty()) (void) StartCommand(0); } bool HfpSession:: -AppendCommand(AtCommand *cmdp, ErrorInfo *error) +AddCommand(AtCommand *cmdp, bool top, ErrorInfo *error) { bool was_empty; @@ -2091,7 +2198,14 @@ AppendCommand(AtCommand *cmdp, ErrorInfo *error) } was_empty = m_commands.Empty(); - m_commands.AppendItem(cmdp->m_links); + + if (top && !was_empty) { + AtCommand *topcmd; + topcmd = GetContainer(m_commands.next, AtCommand, m_links); + topcmd->m_links.PrependItem(cmdp->m_links); + } else { + m_commands.AppendItem(cmdp->m_links); + } if (!was_empty) return true; @@ -2126,7 +2240,7 @@ PendingCommand(AtCommand *cmdp, ErrorInfo *error) return 0; } - if (!AppendCommand(cmdp, error)) { + if (!AddCommand(cmdp, false, error)) { delete pendp; return 0; } @@ -2135,70 +2249,37 @@ PendingCommand(AtCommand *cmdp, ErrorInfo *error) } bool HfpSession:: -StartCommand(ErrorInfo *error) +SendCommand(const char *cmd, ErrorInfo *error) { - AtCommand *cmdp; - int cl, rl; - int err; - ErrorInfo local_error; + size_t cl; char stackbuf[64]; StringBuffer sb(stackbuf, sizeof(stackbuf)); - if (!IsRfcommConnected()) { - if (error) - error->Set(LIBHFP_ERROR_SUBSYS_BT, - LIBHFP_ERROR_BT_NOT_CONNECTED, - "Device is not connected"); - return false; - } - - if (m_commands.Empty()) - return true; - - cmdp = GetContainer(m_commands.next, AtCommand, m_links); - - GetDi()->LogDebug("<< %s", cmdp->m_command_text); - - if (!sb.AppendFmt("%s\r", cmdp->m_command_text)) { + if (!sb.AppendFmt("%s\r", cmd)) { if (error) error->SetNoMem(); return false; } - cl = strlen(sb.Contents()); - rl = send(m_rfcomm_sock, sb.Contents(), cl, MSG_NOSIGNAL); - if (rl < 0) { - err = errno; - /* Problems!! */ - GetDi()->LogDebug(&local_error, - LIBHFP_ERROR_SUBSYS_BT, - LIBHFP_ERROR_BT_SYSCALL, - "Write to RFCOMM socket: %s", - strerror(err)); - if (error) - *error = local_error; + GetDi()->LogDebug("<< %s", cmd); + return RfcommSend((const uint8_t *) sb.Contents(), cl, error); +} - __Disconnect(&local_error, ReadErrorVoluntary(err)); +bool HfpSession:: +StartCommand(ErrorInfo *error) +{ + AtCommand *cmdp; - return false; - } + if (m_commands.Empty()) + return true; - if (rl != cl) { - /* Short write!? */ - GetDi()->LogWarn(&local_error, - LIBHFP_ERROR_SUBSYS_BT, - LIBHFP_ERROR_BT_SYSCALL, - "Short write: expected:%d sent:%d, " - "command \"%s\"", - cl, rl, cmdp->m_command_text); - if (error) - *error = local_error; + cmdp = GetContainer(m_commands.next, AtCommand, m_links); - __Disconnect(&local_error); + if (!SendCommand(cmdp->m_command_text, error)) return false; - } + m_command_timer->Set(m_timeout_command); return true; } @@ -2350,13 +2431,13 @@ public: return false; } - void OK(void) { + bool OK(void) { GetSession()->SetSupportedFeatures(m_brsf); if (GetSession()->FeatureThreeWayCalling()) { - (void) GetSession()->AppendCommand( - new ChldTCommand(GetSession()), 0); + (void) GetSession()->AddCommand( + new ChldTCommand(GetSession()), false, 0); } - AtCommand::OK(); + return AtCommand::OK(); } void ERROR(void) { /* @@ -2369,6 +2450,21 @@ public: } }; +void HfpSession:: +SetSupportedFeatures(int ag_features) +{ + assert(IsConnecting()); + m_brsf = ag_features; + + /* Default: in-band ring tones enabled */ + if (FeatureInBandRingTone()) + m_state_bsir = true; + + /* Default: echo cancelation/noise reduction enabled on AG */ + if (FeatureEcnr()) + m_state_ecnr = true; +} + /* Cellular Indicator Test */ class CindTCommand : public AtCommand { public: @@ -2423,6 +2519,7 @@ public: buf++; } + GetSession()->VerifyIndicators(); } return false; } @@ -2533,12 +2630,20 @@ SetIndicatorNum(int indnum, const char *buf, int len) } else if (StrMatch("call", buf, len)) { m_inum_call = indnum; + + /* Don't propagate to clients */ + len = 0; } else if (StrMatch("callsetup", buf, len) || StrMatch("call_setup", buf, len)) { - m_inum_callsetup = indnum; - buf = "callsetup"; - len = strlen(buf); + if (m_inum_callsetup == 0) + m_inum_callsetup = indnum; + + /* Don't propagate to clients */ + len = 0; + } + else if (StrMatch("callheld", buf, len)) { + m_inum_callheld = indnum; } else if (StrMatch("signal", buf, len)) { m_inum_signal = indnum; @@ -2554,7 +2659,7 @@ SetIndicatorNum(int indnum, const char *buf, int len) if (indnum >= m_inum_names_len) { ExpandIndicators(indnum + 1); } - if (m_inum_names[indnum] == NULL) { + if (len && (m_inum_names[indnum] == NULL)) { char *ndup = (char*) malloc(len + 1); m_inum_names[indnum] = ndup; /* Convert to lower case while copying */ @@ -2565,6 +2670,23 @@ SetIndicatorNum(int indnum, const char *buf, int len) } } +bool HfpSession:: +VerifyIndicators(void) +{ + bool res = true; + + if (m_inum_service < 0) { + GetDi()->LogWarn("AG did not report indicator \"service\""); + res = false; + } + if (m_inum_call < 0) { + GetDi()->LogWarn("AG did not report indicator \"call\""); + res = false; + } + + return res; +} + void HfpSession:: UpdateIndicator(int indnum, const char *buf) { @@ -2589,18 +2711,25 @@ UpdateIndicator(int indnum, const char *buf) cb_NotifyCall(this, true, false, false); /* - * If the call state has changed, and we - * are emulating callsetup, we presume that - * the callsetup state has changed as well. + * If the call state has changed, and the + * callsetup state is presumed, we reset it. */ - if (IsConnected() && !FeatureIndCallSetup()) { + if (IsConnected() && + (newstate == 1) && + m_callsetup_presumed) UpdateCallSetup(0); - } } return; } if (indnum == m_inum_callsetup) { - UpdateCallSetup(val); + /* + * Certain Motorola RAZR V3 devices will send a + * callsetup=1 indicator, but will fail to send + * callsetup=0 if the call is remotely terminated + * before it is answered or rejected. + */ + if (val != 1) + UpdateCallSetup(val); return; } @@ -2610,11 +2739,7 @@ UpdateIndicator(int indnum, const char *buf) if (m_timer) m_timer->Cancel(); m_state_call = false; - m_state_callsetup = 0; - if (m_state_incomplete_clip) { - delete m_state_incomplete_clip; - m_state_incomplete_clip = 0; - } + UpdateCallSetup(0); } } else if (indnum == m_inum_signal) { @@ -2627,40 +2752,84 @@ UpdateIndicator(int indnum, const char *buf) m_state_battchg = val; } - if ((indnum >= m_inum_names_len) || - (m_inum_names[indnum] == NULL)) { + if ((indnum < 0) || (indnum >= m_inum_names_len)) { GetDi()->LogWarn("Undefined indicator %d", indnum); return; } + + if (!m_inum_names[indnum]) + /* Indicator not to be propagated to libhfp clients */ + return; + + /* Some AGs have both "callsetup" and "call_setup" indicators */ if (cb_NotifyIndicator.Registered()) cb_NotifyIndicator(this, m_inum_names[indnum], val); } -/* - * UpdateCallSetup supports emulation of the callsetup indicator for - * HFP 1.0 devices that don't support it. - */ void HfpSession:: Timeout(TimerNotifier *notp) { - assert(notp == m_timer); - /* - * Hack: deal with devices that don't provide call setup - * information. + * Presumptive call setup timeout has elapsed, + * reset the callsetup value to 0. */ - if (IsConnected() && !FeatureIndCallSetup() && - (m_state_callsetup == 1)) { - UpdateCallSetup(0); + + assert(notp == m_timer); + assert(IsConnected()); + assert(m_callsetup_presumed); + assert(m_state_callsetup != 0); + UpdateCallSetup(0); +} + +void HfpSession:: +ClipTimeout(TimerNotifier *notp) +{ + assert(IsConnected()); + assert(m_clip_state == CLIP_WAITING); + m_clip_state = CLIP_FINAL; + UpdateCallSetup(1, 1, 0, m_timeout_ring); +} + +void HfpSession:: +ClearClip(void) +{ + if (m_clip_state == CLIP_WAITING) + m_clip_timer->Cancel(); + if (m_clip_value) { + assert(m_clip_state == CLIP_FINAL); + delete m_clip_value; + m_clip_value = 0; } + m_clip_state = CLIP_UNKNOWN; } +/* + * UpdateCallSetup is a complicated function that handles specific + * state machine jobs related to the call, callsetup, and clip states. + * + * We track the callsetup state presumptively. When an event occurs that + * suggests the callsetup should be in a particular state, we presume + * it to be in that state regardless of receiving an explicit indicator + * update from the AG. For example, if we get a RING event, we know that + * there is an incoming call, even if the AG doesn't tell is so. If we + * presume an incomplete call, we associate a timeout with the presumption. + * The timeout will be canceled if the AG tells us explicitly what the + * callsetup state is, or we presume no incomplete call. This way, we can + * transparently support AGs that do not report callsetup state. This is + * important because callsetup reporting is not required per HFP 1.5. + * + * The AG informs us of ringing before it gives us the caller ID + * information, but it's much cleaner to present the ring notification to + * our client at the same time as the caller ID info. We use the CLIP + * timer to try to make this happen. + */ void HfpSession:: -UpdateCallSetup(int val, int ring, GsmClipPhoneNumber *clip, int timeout_ms) +UpdateCallSetup(int val, int ring, GsmClipResult *clip, int timeout_ms) { - bool upd_wc = false, upd_ac = false; - GsmClipPhoneNumber *cpy = 0; + bool upd_wc = false, upd_ac = false, do_ring = false; + GsmClipResult *cpy = 0; + ErrorInfo local_error; /* GetDi()->LogDebug("UpdateCallSetup: v:%d r:%d clip:\"%s\", to:%d", @@ -2669,60 +2838,140 @@ UpdateCallSetup(int val, int ring, GsmClipPhoneNumber *clip, int timeout_ms) : "[NONE]", timeout_ms); */ - if (!FeatureIndCallSetup() && m_timer) { - /* Reset the timer */ + if (m_callsetup_presumed && m_timer) m_timer->Cancel(); - if (val && timeout_ms) { - m_timer->Set(timeout_ms); - } + if (IsConnected() && timeout_ms && + (m_callsetup_presumed || (m_state_callsetup != val))) { + m_callsetup_presumed = true; + m_timer->Set(timeout_ms); + } else { + m_callsetup_presumed = false; } - if (!val) { + + switch (val) { + case 0: + /* No incomplete call */ assert(!clip); - if (m_state_incomplete_clip) { - delete m_state_incomplete_clip; - m_state_incomplete_clip = 0; + assert(!ring); + assert(!timeout_ms); + ClearClip(); + break; + case 1: + /* Incomplete incoming call */ + if ((m_state_callsetup != 0) && + (m_state_callsetup != 1)) + ClearClip(); + + /* Check the CLIP state, update it */ + switch (m_clip_state) { + case CLIP_UNKNOWN: + /* Was one provided? */ + assert(!m_clip_value); + if (clip) { + cpy = clip->Duplicate(); + if (cpy) { + m_clip_value = cpy; + m_clip_state = CLIP_FINAL; + } + } else if (ring) { + if (m_clip_enabled) { + /* + * We are now waiting for a CLIP, + * start the timer. + */ + m_clip_timer->Set(m_timeout_clip); + m_clip_state = CLIP_WAITING; + } else { + m_clip_state = CLIP_FINAL; + } + } + break; + case CLIP_WAITING: + assert(!m_clip_value); + if (clip) { + cpy = clip->Duplicate(); + if (cpy) { + m_clip_value = cpy; + m_clip_state = CLIP_FINAL; + m_clip_timer->Cancel(); + } + do_ring = true; + } + break; + case CLIP_FINAL: + if (clip && (!m_clip_value || + !m_clip_value->Compare(clip))) { + /* + * Did we miss an event here? + * A different caller ID value is being + * reported, + */ + cpy = clip->Duplicate(); + if (cpy) { + m_clip_value = cpy; + upd_wc = true; + } + } + break; + default: + abort(); } - } - else if (clip) { - assert(val == 1); - assert(ring != 0); - if (m_state_incomplete_clip && - !m_state_incomplete_clip->Compare(clip)) { - /* - * Did we miss an event here? - * A different caller ID value is being reported - */ - cpy = clip->Duplicate(); - if (cpy) - upd_wc = true; + if (ring) { + if ((ring == 1) && m_state_call) { + m_state_call = false; + upd_ac = true; + } + else if ((ring == 2) && !m_state_call) { + m_state_call = true; + upd_ac = true; + } + + if (!do_ring) + do_ring = (m_clip_state == CLIP_FINAL); + } + break; + case 2: + case 3: + /* Incomplete outgoing call */ + assert(!ring); + if ((m_state_callsetup != 0) && + (m_state_callsetup != 2) && + (m_state_callsetup != 3)) + ClearClip(); + + assert(m_clip_state != CLIP_WAITING); + if (m_clip_state != CLIP_FINAL) { + assert(!m_clip_value); + m_clip_state = CLIP_FINAL; } - else if (!m_state_incomplete_clip) { + if (clip && (!m_clip_value || + !m_clip_value->Compare(clip))) { cpy = clip->Duplicate(); + if (cpy) { + m_clip_value = cpy; + upd_wc = true; + } } + break; - if (cpy) { - if (m_state_incomplete_clip) - delete m_state_incomplete_clip; - m_state_incomplete_clip = cpy; - } + default: + GetDi()->LogWarn(&local_error, + LIBHFP_ERROR_SUBSYS_BT, + LIBHFP_ERROR_BT_PROTOCOL_VIOLATION, + "Unrecognized callsetup value from device"); + __Disconnect(&local_error, true); + return; } + if (val != m_state_callsetup) { m_state_callsetup = val; upd_wc = true; } - else if (ring) { - if ((ring == 1) && m_state_call) { - m_state_call = false; - upd_ac = true; - } - else if ((ring == 2) && !m_state_call) { - m_state_call = true; - upd_ac = true; - } - } - if ((upd_ac || upd_wc || ring) && cb_NotifyCall.Registered()) - cb_NotifyCall(this, upd_ac, upd_wc, (ring != 0)); + if (IsConnected() && + (upd_ac || upd_wc || do_ring) && + cb_NotifyCall.Registered()) + cb_NotifyCall(this, upd_ac, upd_wc, do_ring); } @@ -2732,8 +2981,13 @@ public: CmerCommand(HfpSession *sessp) : AtCommand(sessp, "AT+CMER=3,0,0,1") {} void ERROR(void) { - GetDi()->LogWarn("Could not enable AG event reporting"); - AtCommand::ERROR(); + ErrorInfo error; + /* This will render the HFP interface almost unusable */ + GetDi()->LogWarn(&error, + LIBHFP_ERROR_SUBSYS_BT, + LIBHFP_ERROR_BT_PROTOCOL_VIOLATION, + "Could not enable AG event reporting"); + GetSession()->__Disconnect(&error, true); } }; @@ -2741,19 +2995,20 @@ public: class ClipCommand : public AtCommand { public: ClipCommand(HfpSession *sessp) : AtCommand(sessp, "AT+CLIP=1") {} - void OK(void) { + bool OK(void) { GetSession()->m_clip_enabled = true; - AtCommand::OK(); + return AtCommand::OK(); } + /* It is not severely disruptive if this command fails */ }; /* Cellular Call Waiting */ class CcwaCommand : public AtCommand { public: CcwaCommand(HfpSession *sessp) : AtCommand(sessp, "AT+CCWA=1") {} - void OK(void) { + bool OK(void) { GetSession()->m_ccwa_enabled = true; - AtCommand::OK(); + return AtCommand::OK(); } }; @@ -2768,12 +3023,12 @@ HfpHandshake(ErrorInfo *error) m_rfcomm_not = GetDi()->NewSocket(m_rfcomm_sock, false); m_rfcomm_not->Register(this, &HfpSession::HfpDataReady); - if (!AppendCommand(new BrsfCommand(this, GetService()->m_brsf_my_caps), - error) || - !AppendCommand(new CindTCommand(this), error) || - !AppendCommand(new CmerCommand(this), error) || - !AppendCommand(new ClipCommand(this), error) || - !AppendCommand(new CcwaCommand(this), error)) + if (!AddCommand(new BrsfCommand(this, GetService()->m_brsf_my_caps), + false, error) || + !AddCommand(new CindTCommand(this), false, error) || + !AddCommand(new CmerCommand(this), false, error) || + !AddCommand(new ClipCommand(this), false, error) || + !AddCommand(new CcwaCommand(this), false, error)) return false; return true; @@ -2790,7 +3045,7 @@ HfpHandshakeDone(void) m_conn_state = BTS_Connected; /* Query current state */ - if (!AppendCommand(new CindRCommand(this), &error)) { + if (!AddCommand(new CindRCommand(this), false, &error)) { __Disconnect(&error, false); return; } @@ -2806,22 +3061,206 @@ HfpHandshakeDone(void) * Commands */ +/* Cellular Get Subscriber Number */ +class CnumCommand : public AtCommand { +public: + GsmCnumResult *m_res; + + CnumCommand(HfpSession *sessp) + : AtCommand(sessp, "AT+CNUM"), m_res(0) {} + ~CnumCommand() { + if (m_res) { + delete m_res; + m_res = 0; + } + } + + bool Response(const char *buf) { + ErrorInfo local_error; + if (!strncmp(buf, "+CNUM:", 6)) { + m_res = GsmCnumResult::Parse(buf + 6); + /* We could save the error code but we don't */ + } + return false; + } + + bool OK(void) { + ErrorInfo local_error; + if (!m_res) { + GetDi()->LogWarn(&local_error, + LIBHFP_ERROR_SUBSYS_BT, + LIBHFP_ERROR_BT_PROTOCOL_VIOLATION, + "Unacceptable response from device"); + CompletePending(&local_error, 0); + } else { + CompletePending(0, m_res); + } + return true; + } +}; + +HfpPendingCommand *HfpSession:: +CmdQueryNumber(ErrorInfo *error) +{ + return PendingCommand(new CnumCommand(this), error); +} + +/* Cellular Operator Status (two-stage) */ +class CopsCommand : public AtCommand { +public: + bool m_sent_fmt; + GsmCopsResult *m_res; + CopsCommand(HfpSession *sessp) + : AtCommand(sessp, "AT+COPS=3,0"), + m_sent_fmt(false), m_res(0) {} + ~CopsCommand() { + if (m_res) { + delete m_res; + m_res = 0; + } + } + bool Response(const char *buf) { + ErrorInfo local_error; + if (!m_sent_fmt) + return false; + if (!strncmp(buf, "+COPS:", 6)) { + m_res = GsmCopsResult::Parse(buf + 6); + /* We could save the error code but we don't */ + } + return false; + } + bool OK(void) { + ErrorInfo local_error; + if (!m_sent_fmt) { + /* Send the next stage of the command */ + if (!GetSession()->SendCommand("AT+COPS?", + &local_error)) { + CompletePending(&local_error, 0); + } else { + m_sent_fmt = true; + } + return false; + } + + if (!m_res) { + GetDi()->LogWarn(&local_error, + LIBHFP_ERROR_SUBSYS_BT, + LIBHFP_ERROR_BT_PROTOCOL_VIOLATION, + "Unacceptable response from device"); + CompletePending(&local_error, 0); + + } else { + CompletePending(0, m_res); + } + + return true; + } +}; + +HfpPendingCommand *HfpSession:: +CmdQueryOperator(ErrorInfo *error) +{ + return PendingCommand(new CopsCommand(this), error); +} + +/* Cellular List Current Calls */ +class ClccCommand : public AtCommand { +public: + ListItem m_results; + + ClccCommand(HfpSession *sessp) : AtCommand(sessp, "AT+CLCC") {} + ~ClccCommand() { + while (!m_results.Empty()) { + GsmClccResult *resp; + resp = GetContainer(m_results.next, + GsmClccResult, + m_links); + resp->m_links.Unlink(); + delete resp; + } + } + + bool Response(const char *buf) { + ErrorInfo local_error; + if (!strncmp(buf, "+CLCC:", 6)) { + GsmClccResult *resp; + resp = GsmClccResult::Parse(buf + 6); + if (resp) + m_results.AppendItem(resp->m_links); + } + return false; + } + + bool OK(void) { + CompletePending(0, &m_results); + return true; + } +}; + +HfpPendingCommand *HfpSession:: +CmdQueryCurrentCalls(ErrorInfo *error) +{ + return PendingCommand(new ClccCommand(this), error); +} + +/* Bluetooth Voice Recognition Activation */ +class BvraCommand : public AtCommand { +public: + bool m_enable; + BvraCommand(HfpSession *sessp, bool enable) + : AtCommand(sessp, enable ? "AT+BVRA=1" : "AT+BVRA=0"), + m_enable(enable) {} + ~BvraCommand() {} + + bool OK(void) { + GetSession()->SetBvra(m_enable); + return AtCommand::OK(); + } +}; + HfpPendingCommand *HfpSession:: CmdSetVoiceRecog(bool enabled, ErrorInfo *error) { + return PendingCommand(new BvraCommand(this, enabled), error); +} + +HfpPendingCommand *HfpSession:: +CmdSetVolumeMic(uint8_t vol, ErrorInfo *error) +{ char buf[32]; - sprintf(buf, "AT+BVRA=%d", enabled ? 1 : 0); + sprintf(buf, "AT+VGM=%d", vol); return PendingCommand(new AtCommand(this, buf), error); } HfpPendingCommand *HfpSession:: -CmdSetEcnr(bool enabled, ErrorInfo *error) +CmdSetVolumeSpeaker(uint8_t vol, ErrorInfo *error) { char buf[32]; - sprintf(buf, "AT+NREC=%d", enabled ? 1 : 0); + sprintf(buf, "AT+VGM=%d", vol); return PendingCommand(new AtCommand(this, buf), error); } +/* Noise Reduction and Echo Cancellation */ +class NrecCommand : public AtCommand { +public: + bool m_enable; + NrecCommand(HfpSession *sessp, bool enable) + : AtCommand(sessp, enable ? "AT+NREC=1" : "AT+NREC=0"), + m_enable(enable) {} + ~NrecCommand() {} + + bool OK(void) { + GetSession()->m_state_ecnr = m_enable; + return AtCommand::OK(); + } +}; + +HfpPendingCommand *HfpSession:: +CmdSetEcnr(bool enabled, ErrorInfo *error) +{ + return PendingCommand(new NrecCommand(this, enabled), error); +} + class AtdCommand : public AtCommand { public: AtdCommand(HfpSession *sessp, const char *phnum) : AtCommand(sessp) { @@ -2832,13 +3271,12 @@ public: /* Redial mode - Bluetooth Last Dialed Number */ AtdCommand(HfpSession *sessp) : AtCommand(sessp, "AT+BLDN") { } - void OK(void) { - if (!GetSession()->FeatureIndCallSetup() && - !GetSession()->HasConnectingCall()) { + bool OK(void) { + if (!GetSession()->HasConnectingCall()) { GetSession()->UpdateCallSetup(2, 0, NULL, GetSession()->m_timeout_dial); } - AtCommand::OK(); + return AtCommand::OK(); } }; @@ -2848,12 +3286,10 @@ public: : AtCommand(sessp, cmd) {} AtCommandClearCallSetup(HfpSession *sessp, const char *cmd) : AtCommand(sessp, cmd) {} - void OK(void) { + bool OK(void) { /* Emulate call setup change */ - if (!GetSession()->FeatureIndCallSetup()) { - GetSession()->UpdateCallSetup(0); - } - AtCommand::OK(); + GetSession()->UpdateCallSetup(0); + return AtCommand::OK(); } }; @@ -2891,7 +3327,7 @@ ValidPhoneNum(const char *ph, ErrorInfo *error) int len = 0; if (!ph) return false; - if (*ph == '+') { + if ((*ph == '+') || (*ph == '>')) { ph++; len++; } @@ -2959,12 +3395,12 @@ CmdCallSwapDropActive(ErrorInfo *error) } HfpPendingCommand *HfpSession:: -CmdCallDropActive(unsigned int actnum, ErrorInfo *error) +CmdCallDropIndex(unsigned int actnum, ErrorInfo *error) { char buf[32]; if (!m_chld_1x) { - GetDi()->LogWarn("Requested CmdCallDropActive(%d), " + GetDi()->LogWarn("Requested CmdCallActive(%d), " "but AG does not claim support", actnum); } sprintf(buf, "AT+CHLD=1%d", actnum); diff --git a/libhfp/rfcomm.cpp b/libhfp/rfcomm.cpp index b2ef2ad..0a6b5c8 100644 --- a/libhfp/rfcomm.cpp +++ b/libhfp/rfcomm.cpp @@ -97,7 +97,10 @@ RfcommService:: RfcommService(uint16_t search_svclass_id) : BtService(), m_rfcomm_listen(-1), m_rfcomm_listen_channel(0), m_rfcomm_listen_not(0), m_secmode(RFCOMM_SEC_NONE), - m_search_svclass_id(search_svclass_id), m_bt_master(true) + m_search_svclass_id(search_svclass_id), m_bt_master(true), + m_autoreconnect_timeout(15000), m_autoreconnect_set(false), + m_autoreconnect_timer(0), + m_autoreconnect_now_set(false), m_autoreconnect_now_timer(0) { } @@ -107,6 +110,9 @@ RfcommService:: /* Make sure that we have been cleaned up after */ assert(m_rfcomm_listen < 0); assert(m_rfcomm_listen_not == 0); + + assert(!m_autoreconnect_timer); + assert(!m_autoreconnect_now_timer); } bool RfcommService:: @@ -134,6 +140,140 @@ SetSecMode(rfcomm_secmode_t secmode, ErrorInfo *error) } void RfcommService:: +AutoReconnectTimeout(TimerNotifier *timerp) +{ + ListItem retrylist; + + if (timerp == m_autoreconnect_timer) { + assert(m_autoreconnect_set); + m_autoreconnect_set = false; + retrylist.AppendItemsFrom(m_autoreconnect_list); + + } else { + assert(timerp == m_autoreconnect_now_timer); + assert(m_autoreconnect_now_set); + m_autoreconnect_now_set = false; + retrylist.AppendItemsFrom(m_autoreconnect_now_list); + } + + while (!retrylist.Empty()) { + RfcommSession *sessp = GetContainer(retrylist.next, + RfcommSession, + m_autoreconnect_links); + sessp->m_autoreconnect_links.UnlinkOnly(); + + /* Always append to the delayed list */ + m_autoreconnect_list.AppendItem(sessp->m_autoreconnect_links); + + sessp->AutoReconnect(); + } + + if (!m_autoreconnect_list.Empty() && !m_autoreconnect_set) { + m_autoreconnect_set = true; + m_autoreconnect_timer->Set(m_autoreconnect_timeout); + } +} + +void RfcommService:: +AddAutoReconnect(RfcommSession *sessp, bool now) +{ + assert(sessp->m_autoreconnect_links.Empty()); + assert(!sessp->IsRfcommConnected() && !sessp->IsRfcommConnecting()); + + if (now) { + if (m_autoreconnect_now_timer && !m_autoreconnect_now_set) { + m_autoreconnect_now_set = true; + m_autoreconnect_now_timer->Set(0); + } + m_autoreconnect_now_list.AppendItem(sessp-> + m_autoreconnect_links); + } else { + if (m_autoreconnect_timer && !m_autoreconnect_set) { + m_autoreconnect_set = true; + m_autoreconnect_timer->Set(m_autoreconnect_timeout); + } + m_autoreconnect_list.AppendItem(sessp->m_autoreconnect_links); + } +} + +void RfcommService:: +RemoveAutoReconnect(RfcommSession *sessp) +{ + assert(!sessp->m_autoreconnect_links.Empty()); + sessp->m_autoreconnect_links.Unlink(); + + if (m_autoreconnect_timer && + m_autoreconnect_list.Empty() && + m_autoreconnect_set) { + m_autoreconnect_set = false; + m_autoreconnect_timer->Cancel(); + } + + if (m_autoreconnect_now_timer && + m_autoreconnect_now_list.Empty() && + m_autoreconnect_now_set) { + m_autoreconnect_now_set = false; + m_autoreconnect_now_timer->Cancel(); + } +} + +bool RfcommService:: +Start(ErrorInfo *error) +{ + assert(!m_autoreconnect_timer); + assert(!m_autoreconnect_now_timer); + + m_autoreconnect_timer = GetDi()->NewTimer(); + if (!m_autoreconnect_timer) { + if (error) + error->SetNoMem(); + return false; + } + m_autoreconnect_timer->Register(this, + &RfcommService::AutoReconnectTimeout); + + m_autoreconnect_now_timer = GetDi()->NewTimer(); + if (!m_autoreconnect_now_timer) { + if (error) + error->SetNoMem(); + goto failed; + } + m_autoreconnect_now_timer->Register(this, + &RfcommService::AutoReconnectTimeout); + + if (!m_autoreconnect_list.Empty() && !m_autoreconnect_set) { + m_autoreconnect_set = true; + m_autoreconnect_timer->Set(0); + } + + if (!m_autoreconnect_now_list.Empty() && !m_autoreconnect_now_set) { + m_autoreconnect_now_set = true; + m_autoreconnect_now_timer->Set(0); + } + + return true; + +failed: + Stop(); + return false; +} + +void RfcommService:: +Stop(void) +{ + if (m_autoreconnect_timer) { + m_autoreconnect_set = false; + delete m_autoreconnect_timer; + m_autoreconnect_timer = 0; + } + if (m_autoreconnect_now_timer) { + m_autoreconnect_now_set = false; + delete m_autoreconnect_now_timer; + m_autoreconnect_now_timer = 0; + } +} + +void RfcommService:: RfcommListenNotify(SocketNotifier *notp, int fh) { BtDevice *devp; @@ -415,8 +555,8 @@ RfcommSession(RfcommService *svcp, BtDevice *devp) : BtSession(svcp, devp), m_rfcomm_state(RFC_Disconnected), m_rfcomm_sdp_task(0), m_rfcomm_inbound(false), m_rfcomm_dcvoluntary(false), m_rfcomm_sock(-1), - m_rfcomm_not(0), m_rfcomm_secmode(RFCOMM_SEC_NONE) - + m_rfcomm_not(0), m_rfcomm_secmode(RFCOMM_SEC_NONE), + m_conn_autoreconnect(false), m_operation_timeout(0) { } @@ -429,6 +569,22 @@ RfcommSession:: assert(!m_rfcomm_not); } +void RfcommSession:: +SetAutoReconnect(bool enable) +{ + if (enable && !m_conn_autoreconnect) { + Get(); + m_conn_autoreconnect = true; + if (m_rfcomm_state == RFC_Disconnected) + GetService()->AddAutoReconnect(this, true); + } + else if (!enable && m_conn_autoreconnect) { + m_conn_autoreconnect = false; + if (m_rfcomm_state == RFC_Disconnected) + GetService()->RemoveAutoReconnect(this); + Put(); + } +} bool RfcommSession:: RfcommSdpLookupChannel(ErrorInfo *error) @@ -461,6 +617,8 @@ RfcommSdpLookupChannel(ErrorInfo *error) Get(); m_rfcomm_state = RFC_SdpLookupChannel; m_rfcomm_sdp_task = taskp; + if (m_conn_autoreconnect) + GetService()->RemoveAutoReconnect(this); return true; } @@ -473,6 +631,8 @@ RfcommSdpLookupChannelComplete(SdpTask *taskp) assert(taskp == m_rfcomm_sdp_task); assert(m_rfcomm_state == RFC_SdpLookupChannel); + (void) RfcommSetOperationTimeout(0, 0); + m_rfcomm_inbound = false; if (taskp->m_params.m_errno) { @@ -513,7 +673,7 @@ RfcommSdpLookupChannelComplete(SdpTask *taskp) bool RfcommSession:: -RfcommConnect(uint8_t channel, ErrorInfo *error) +RfcommConnect(uint8_t channel, ErrorInfo *error, int timeout) { struct sockaddr_rc raddr; BtHci *hcip; @@ -523,7 +683,8 @@ RfcommConnect(uint8_t channel, ErrorInfo *error) hcip = GetHub()->GetHci(); if (!hcip) { - error->SetNoMem(); + if (error) + error->SetNoMem(); return false; } @@ -583,6 +744,10 @@ RfcommConnect(uint8_t channel, ErrorInfo *error) goto failure; } + /* Set a connection attempt timeout */ + if (!RfcommSetOperationTimeout(timeout, error)) + goto failure; + /* We wait for the socket to become _writable_ */ m_rfcomm_not = GetDi()->NewSocket(rsock, true); if (!m_rfcomm_not) { @@ -601,12 +766,16 @@ RfcommConnect(uint8_t channel, ErrorInfo *error) m_rfcomm_inbound = false; m_rfcomm_sock = rsock; + if (!m_autoreconnect_links.Empty()) + GetService()->RemoveAutoReconnect(this); + if (!m_rfcomm_not) RfcommConnectNotify(NULL, rsock); return true; failure: + (void) RfcommSetOperationTimeout(0, 0); if (rsock >= 0) { close(rsock); } assert(!error || error->IsSet()); return false; @@ -631,6 +800,10 @@ RfcommAccept(int sock) m_rfcomm_state = RFC_Connected; Get(); + + if (m_conn_autoreconnect) + GetService()->RemoveAutoReconnect(this); + NotifyConnectionState(0); return true; } @@ -644,6 +817,8 @@ RfcommConnectNotify(SocketNotifier *notp, int fh) assert(m_rfcomm_state == RFC_Connecting); + (void) RfcommSetOperationTimeout(0, 0); + if (notp) { assert(notp == m_rfcomm_not); delete m_rfcomm_not; @@ -681,12 +856,120 @@ RfcommConnectNotify(SocketNotifier *notp, int fh) } bool RfcommSession:: -RfcommConnect(ErrorInfo *error) +RfcommConnect(ErrorInfo *error, int timeout) { if (m_rfcomm_state != RFC_Disconnected) return false; - return RfcommSdpLookupChannel(error); + if (!RfcommSetOperationTimeout(timeout, error)) + return false; + + if (!RfcommSdpLookupChannel(error)) { + (void) RfcommSetOperationTimeout(0, 0); + return false; + } + + return true; +} + +/* + * It seems sketchy to trust the Bluetooth stack to time out its + * operations correctly all the time. + */ +bool RfcommSession:: +RfcommSetOperationTimeout(int ms, ErrorInfo *error) +{ + if (!ms) { + if (m_operation_timeout) { + delete m_operation_timeout; + m_operation_timeout = 0; + } + return true; + } + + if (!m_operation_timeout) { + m_operation_timeout = GetDi()->NewTimer(); + if (!m_operation_timeout) { + if (error) + error->SetNoMem(); + return false; + } + + m_operation_timeout->Register(this, + &RfcommSession::RfcommOperationTimeout); + + } else { + m_operation_timeout->Cancel(); + } + + m_operation_timeout->Set(ms); + return true; +} + +void RfcommSession:: +RfcommOperationTimeout(TimerNotifier *timerp) +{ + ErrorInfo error; + + assert(IsRfcommConnecting() || IsRfcommConnected()); + assert(timerp == m_operation_timeout); + + error.Set(LIBHFP_ERROR_SUBSYS_BT, + LIBHFP_ERROR_BT_TIMEOUT, + "RFCOMM operation timed out"); + + __Disconnect(&error); +} + + +bool RfcommSession:: +RfcommSend(const uint8_t *buf, size_t len, ErrorInfo *error) +{ + ssize_t rl; + int err; + ErrorInfo local_error; + + if (!IsRfcommConnected()) { + if (error) + error->Set(LIBHFP_ERROR_SUBSYS_BT, + LIBHFP_ERROR_BT_NOT_CONNECTED, + "Device is not connected"); + return false; + } + + rl = send(m_rfcomm_sock, buf, len, MSG_NOSIGNAL); + + if (rl < 0) { + err = errno; + /* Problems!! */ + GetDi()->LogDebug(&local_error, + LIBHFP_ERROR_SUBSYS_BT, + LIBHFP_ERROR_BT_SYSCALL, + "Write to RFCOMM socket: %s", + strerror(err)); + if (error) + *error = local_error; + + __Disconnect(&local_error, ReadErrorVoluntary(err)); + + return false; + } + + if ((size_t) rl != len) { + /* Short write!? */ + GetDi()->LogWarn(&local_error, + LIBHFP_ERROR_SUBSYS_BT, + LIBHFP_ERROR_BT_SYSCALL, + "Short write: expected:%d sent:%d", + len, rl); + if (error) + *error = local_error; + + __Disconnect(&local_error); + return false; + } + + return true; } void RfcommSession:: @@ -699,6 +982,7 @@ void RfcommSession:: __Disconnect(ErrorInfo *reason, bool voluntary) { if (m_rfcomm_state != RFC_Disconnected) { + (void) RfcommSetOperationTimeout(0, 0); if (m_rfcomm_not) { assert(m_rfcomm_state > RFC_SdpLookupChannel); assert(m_rfcomm_sock >= 0); @@ -719,6 +1003,9 @@ __Disconnect(ErrorInfo *reason, bool voluntary) m_rfcomm_dcvoluntary = voluntary; m_rfcomm_state = RFC_Disconnected; + if (m_conn_autoreconnect) + GetService()->AddAutoReconnect(this); + NotifyConnectionState(reason); Put(); |